BIND 10 trac404, updated. 56b5bef538df18c95ad5df3aaf7ebff4464de7c6 Merge branch 'master' into work/serialize
BIND 10 source code commits
bind10-changes at lists.isc.org
Mon Apr 4 16:21:27 UTC 2011
The branch, trac404 has been updated
via 56b5bef538df18c95ad5df3aaf7ebff4464de7c6 (commit)
via 62a61edffac3ebdd91fec693fe2c1a94785ddb25 (commit)
via 68b9571be6f2370798951b05968d07688e96d56d (commit)
via 88c0d241fe05e5ea91b10f046f307177cc2f5bc5 (commit)
via 3ceeab28d48c23ac561a2ed75a57a8f4aa153858 (commit)
via 888bcbc103826ce8de73b29c3e8bf48d5925fe27 (commit)
via ea679d6d1fc5105027de1f0243817c66a32fa8ac (commit)
via 510924ebc57def8085cc0e5413deda990b2abeee (commit)
via 1d6b96dd3f682a4433a02f2f156fd118aac6f0e0 (commit)
via ece21d1bcb8b6ef6015622427cdc784e951b7396 (commit)
via 90946cbcad43d13bd6dec1a2a83eb75d019dd511 (commit)
via e28f5c6b01d30e48e8de45cb0312d360a44b7ca3 (commit)
via d151653e759da7ad45c28914a67988057e8cbd7f (commit)
via 9eb28db1b6b3c0b6cd76379416726b9931c729d2 (commit)
via 782a56fbd225e31f6b1341857ad9a5af20df4901 (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 681b1839bca185f19eca8e0fe567bb0012743a79 (commit)
via b5c567f94b74e671986d68ae6e7549e829f72fa9 (commit)
via 1960b5becbba05570b9c7adf5129e64338659f07 (commit)
via 87f24fca3b55591ede24de9f856c807ca65966b1 (commit)
via f0b271b93312a990a566380815d70b758293934e (commit)
via 1e4827d9858483f0cb35ff90d3a665c08f9527d1 (commit)
via 3811dac0257939f28cefa621226019e6d1c75239 (commit)
via 8d638e07f2347d5e713493ffed9659ea5048e872 (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 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 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 0f225ec3b92e320020b731720e8ebe4d5d1fadf8 (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 d6f579083977bb3a94ed1bf2ffcaa31da878e0bf (commit)
via cba9add1f69d78f1ace066e76136bcf6f0a0d3d9 (commit)
via f7d2e62980d949a74bcaf03f6079cb847d6f0f0f (commit)
via 0cd6c3812b7e7250be1381cf87140794c9fa3c42 (commit)
via bf0b8426e2d05c1fc30a2b29929531c430f8bcda (commit)
via a84eadd788ec9544c79b26a47f5ef34f6b34c359 (commit)
via d39787aa4305f91cfca004088c6f5ca2a3867622 (commit)
via 02d45b17f160bd3662ee765147debe770c6d3faa (commit)
via fc26c7396d98fa84a8f057cc409303f911792365 (commit)
via ba92da6356b8d3a5b164102af9d8d22259efccc9 (commit)
via 7c38ee8fabb2f93c83495e45c58a37712da25e67 (commit)
via b1bc4d63d568d15f6b21a3c25a2848bea2553ff7 (commit)
via a0d91794e105108425429a6ab9eed196171955eb (commit)
via db57bdd2f3f73b7d4048fec614f0a3db73c8489a (commit)
via 08d197753711038ef73a50ebc4458df5a454f648 (commit)
via cf2b8ace9b9c295e46fdd5373bd70a13e7e3fbee (commit)
via cbc69359f3d9841a217ee55f0e30937a86cb0097 (commit)
via 87d58087b71b509d9798bd700b9ba968712e142d (commit)
via 827babb0d6caade8ca685e5f3e779c756f9ebe1a (commit)
via 290bbf2d7c93c202b22f1f4e2638ae01038bc983 (commit)
via 1b24dfdd078a3eddf6198d9561ae8bb93803103d (commit)
via 45b76ba773367f8a490bbf19f0d74c02293c387b (commit)
via 86d0ba9023b4f4a23cf203d3c9bf7c2f3668315e (commit)
via 1ac4ea1a6996dd39d15d4f6cbdf0a766e8d8d569 (commit)
via f009a637580c569a49a74b87a384a242c5a5f30a (commit)
via 002aa60f46e33079f4d9bc131115a127ea5a5130 (commit)
via 3a6535a46295133690647d8fb75819abb5107bac (commit)
via 58da05cd5f8709c8f4e1824c5479a0e3cd2b4371 (commit)
via bf8ba76aed284a9f1907b3edde1f546c9a4c0a3c (commit)
via 416fcf7eb6c7c0dc249bcc838d75504f070750a1 (commit)
via 64978870d1b6843e94268160f5e9c7b7dfbeb755 (commit)
via b4149e03fd8f722fe1f0fe567b34d530da005109 (commit)
via a447b8e583551311e487816e11a171fdb0e8951a (commit)
via e0863720a874d75923ea66adcfbf5b2948efb10a (commit)
via 03de120c1148775977a8cad2b0697e934e2a823a (commit)
via 241571157deb66f6766918b783379409e294e2f5 (commit)
via 65a77d8fde64d464c75917a1ab9b6b3f02640ca6 (commit)
via 9287cc8f31d6ed03cdcd95f6ca59b58ed6d695b6 (commit)
via 34eaa7c5e831bb170f0fe8792e40df88f66ed8b9 (commit)
via 9dcb53261abe3c16324155fbc4c3436100b9e2ee (commit)
via 0eefd8be74290fd9e72e3d25356c64b24c844816 (commit)
via 2be9594e5c6cac0e01a75a06d8df31d8cef98380 (commit)
via 41ef6df597d8831d080d4dfc7c582f06e7f92511 (commit)
via 17f4eab177a8d459024a5fcf2b43e6826c129891 (commit)
via de2d7124bd8a6bf3f2c0a6e661dc5b6b178765cc (commit)
via 5b825a61a8f083aa879da6aedc6717d42534d5b4 (commit)
via 1af89c43e91b613bf3d8ece42ec5d93c19a5b225 (commit)
via 933b35dcb9cec5113853c93994d69cac44a97f4b (commit)
via e1b0deba4ea46e097e15f40b9825f7abffa00596 (commit)
via 71bcc9a5c413276bd90276c24ee28d59478a138e (commit)
via 6ac000df85625f5921e8895a1aafff5e4be3ba9c (commit)
via 7fef6a88c5ab2cedc5f2fa0003ae1beb7a380b9c (commit)
via b0c4e1599b68046bbd72455ce90433285b226ffe (commit)
via 56f0f77fe500b6cb41cda2a950b4349f0778561e (commit)
via e49c4b5a038981cea80bf81613efa2c538c8b3e0 (commit)
via 5222b51a047d8f2352bc9f92fd022baf1681ed81 (commit)
via 5ecd1322b677fa9808125986a65b28d619c79120 (commit)
via 78828990b58d0b791f94ed2441bf1267f81d58d3 (commit)
via ccd3fea75828ec4e0e2b20d8bba1d7b103164141 (commit)
via a663b567f5a57c19c033de049219e4a5eb6d936b (commit)
via 4cf5938f86b3c1a54beac417f6723505183ddf11 (commit)
via e33f9a6d1009753b2f02b43e43da1014b0df1964 (commit)
via 977a6a742c821ed49fc7d977f917b47f23563bdb (commit)
via 07c01a7a90476fd6b90659bf809e7135ce26cff2 (commit)
via a1d7f70e7c660e847fb388c6f17d6709c93fe8e2 (commit)
via 96e55aef179874427ea28641316dd145fbd98175 (commit)
via ccdce3bab3f04bade1e92e66e3929ffd0db97f0b (commit)
via 137a6934a14cf0c5b5c065e910b8b364beb0973f (commit)
via 79155a51eb422813900332ad1582ed5964d70f5c (commit)
via b23ef1cf58b0754e23e550362e7a979d5f8624ef (commit)
via bdc63200a7e2adcde2b0ce44aaeb8477fb059d17 (commit)
via 2495b96e3a3adeaaca091d7bf2533c1a58971ee5 (commit)
via eb6441aca464980d00e3ff827cbf4195c5a7afc5 (commit)
via 5e2e3fa2759475313d69d3f9cf1cd339f3db8bc7 (commit)
via bf57a576ff80578d47471569a48f0fbfd1fb6e2e (commit)
via 5ff855e14dca375c24404eebc4573b77dba915d3 (commit)
via 9d42d008eb9ab1a9f271c7c20754ba02785772f0 (commit)
via 117fa08fc7f8b7462190b75f99d7604c49f3b7c6 (commit)
via 447c40bcee5edbd71ee1b3812fa71727b2fdc7a6 (commit)
via 058f40e75eb59aea1d3e18ffcdfdacc8386aafa3 (commit)
via f1a6ba985d60a31bb5cffe0571c827c3f7b25ce3 (commit)
via f1396eba9c5c5b1291ba24e372a0b536e3deb1c0 (commit)
via 0b42ead35810e82a97561d71309283950ca9d5a3 (commit)
via ed6b07b60ae503e97e284eff351386ba24377981 (commit)
via 19197b5bc9f2955bd6a8ca48a2d04472ed696e81 (commit)
via fa64874fe02ff6ab8a782e0ede0d70ff2c95783e (commit)
via 44bf7654cb3ce85fe63fa47c7b39f48bb041d4de (commit)
via 1c8557996bdd428c9a3bf0935c30bf42fb167f35 (commit)
via dfbeea4eb3f414dcb66d2447e59216c23dade515 (commit)
via 54e61304131965c4a1d88c9151f8697dcbb3ce12 (commit)
via b9d341ecd382b44d72888899e3559d7d41baa203 (commit)
via 242cb68113bd4d2c0a3ea3b61cc56e143a42b38e (commit)
via 3ffa099e0969468d0f16f00c503b8936b895a2d4 (commit)
via e0d5584bdb3ca05b078693787c35d8f8f9228040 (commit)
via 4596c143eadfb9d67c5395bb6fa367470976d0cb (commit)
via f3bd834c8e9334004994c33d0798ed6b61a7a99b (commit)
via a73a3ef00b42b0fc5aa5ecf832cb713db74288a1 (commit)
via 860a2965d04f80fd0e90d3c5100332a8bb2ae9d8 (commit)
via c4f4d32eed0f4c26e4968336179a1e749e925aa7 (commit)
via b2d210a1ed486dc8c1083deb6f1b0bf2802e8d57 (commit)
via 3407184a20c367a0de02b41befb4db16971c9589 (commit)
via 610400fa75aa44d15fdc95dc8b69e02b45698765 (commit)
via cf020fa451a20df9625253b3b2ccbc00e8144c1c (commit)
via 6c1f3af003f02eee8952da8463d05732e667c986 (commit)
via be4c4ffc719d10735bfaea5a7cd5bdea7e5f06c7 (commit)
via fa83bb99518bd8c4dede832769fe4f7a6405ea62 (commit)
via 715fd606c4d77f1947c8c26142a5d516d54a00da (commit)
via d686e3ada2141094413e756d601d2e727fb6f760 (commit)
via d0dcd91d39a438d5a18e0726251e1a93212143ca (commit)
via b32ea06f28de94a8ec779385d5e86374e81c023d (commit)
via 0ca10e6357d7a41bc308f90183848ce41582adf8 (commit)
via 25a5f4ec755bc09b54410fcdff22691283147f32 (commit)
via 2275e0d7afa15b69bd7511616a6ebae0b9de0d22 (commit)
via 301da7d26d41e64d87c0cf72727f3347aa61fb40 (commit)
via 26582526e20e56ea4547a447cfa93e122af0f0e1 (commit)
via 6934390c92855d4006b727e1c8c1c89688afeb5f (commit)
via 333edc6244dafdab42258b84a46237c6dcf936a0 (commit)
via 8af2ccd45c7f070d2aa168763815fe8a82808e79 (commit)
via 19f9f10fa5594bfe311688823fba5dd149e76a59 (commit)
via 5851a6cc19356b49fcc2500f5d40e7861874561e (commit)
via 8be2bf19228473f5d54f5015e893b44791a5c0c2 (commit)
via edfcbcaccbf4b8098f5cfc1bd1943fe4542b3306 (commit)
via 7396247046ccc046d711265100aa8d46be057626 (commit)
via 9c3f27137967b5f92c430baab6ab442258d9d692 (commit)
via 56df4f9ab31b300b83c26fbeb1a7cf7951f8338e (commit)
via 2298392e3ce9b5a470f4c0e3b2a22ba571f9b8ab (commit)
via 309d02a51ee22ff4c0226784ecd7bb4394d19542 (commit)
via ddd7b7c3dfdebae68e3742e175e4931f0a5f6c5e (commit)
via a2609d0762b9dfdf2750a1bc44ea33525c634f5b (commit)
via de130e6d9b6dec3586126f660123a905cc9a284a (commit)
via 53b5297513ee2320556dead019f268e79b49f77a (commit)
via eeacd1d5adeb27ee536fb04296cc20ffa898cdab (commit)
via 33cf7e5a182bc98cd168928fa8b5c775581fe90a (commit)
via a9211d7973042503353e61f0e16ea092c0f621bf (commit)
via 48211526895ce56de771e65a7e6d23892c639660 (commit)
via 87829d23b5d698611c94ee92f758558d8ad95468 (commit)
via f73c46eb406d7234dd7519a1d330db67813b31ab (commit)
via 1faf02bda80053ddb2f815466d0372a2eeb6c08c (commit)
via 9e782794969ab034eb92dca6a8d5ee8f518ccc95 (commit)
via ff55e63140852f4c7cf8a0404dde36442c45ff4a (commit)
via b723719cdad0da4db9b2099596c09f407dbdb135 (commit)
via 996e2593c867478eec8f2b3700aa52776528394b (commit)
via 5cde27560e4b8e3cd098daae572ca4d0124358de (commit)
via 1f682b8172e368d7609aa2f066b778989a7fdf7e (commit)
via 87308eeddf767dea581b817d1d2ab2aaa4a99dd3 (commit)
via 1fd5547c170550880268ba0eb83a374231be348b (commit)
via f06ce638877acf6f8e1994962bf2dbfbab029edf (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 8053d85447560fa3c64f276881496513c66f4ba0 (commit)
via f664bc1482edb6da965169317f04f2d7b6458fbd (commit)
via e241028cc0993fb87306bd097a45f99a5ea3a50d (commit)
via e1db14abce5e64985340e4ebb9eb4e7bee56763e (commit)
via 6483bb374e7ac88e3b736f08bf11e292605aaeef (commit)
via a3d81c1479c88a8ba26941e351fce1b0b1e3a2ca (commit)
via 895e8df77e0d3f8957f3b518052ff874f67bb8b8 (commit)
via 5d246e92aef87505e9392274cacbabbd20478d3e (commit)
via cb13c22e8b2464d903d2254dec4d46c1103cf5ea (commit)
via 68c33a7725a2e41dab53b4d74e2aedec79090380 (commit)
via ae0b20c57c48c4932b9aff2146f5d76f9eff5a90 (commit)
via 0047c74393959975ffd9f75a687a60f1f1c42a9c (commit)
via 1d60afb59e9606f312caef352ecb2fe488c4e751 (commit)
via 9c22af762d0cb6cdcb0bcbcea2b302b1165a0f66 (commit)
via f9152fae80f751adc95c894b872204e98504dcb8 (commit)
via 40f87a0aa5e6b2e8f86346f181b5e1d785c36e67 (commit)
via 3d69f66a688c09a20083a52f2a64b9327ede70c6 (commit)
via 76843dc3d8538f932d58e55ab6189091cd709f48 (commit)
via 451fdd5e7209a31420d2a61df99d66d2abfe34ca (commit)
via 34066d5d8486b9d3dad08e0b13269db14a8d8aae (commit)
via 543f406610c4fc2c0236f8f9c7fdc37014937fb6 (commit)
via 42d9589eb5c3fb6dd9f1a73206510d01bb29bedc (commit)
via bb8d9d376a1e2caadfdc3631c221cb7ca118f60f (commit)
via 860be372d776c05c9535790257812dbde1b9f74f (commit)
via fa05a534849c2e3ba68d6fdc4c5ddc6531948fc3 (commit)
via b73ea473019bceeab7aab78ddc5d1c4a7710a8bd (commit)
via 70d71681b83dd3e0cb125e3f3e176d04e7461aae (commit)
via 21d48af75c1e756de9acea4c45dc35634c0475ac (commit)
via e55ec73b0bdcc2b7d6286c6a18886de194280947 (commit)
via 9ae710aa95a75d63e2a37ca7d79297f8ef308e6c (commit)
via cac094e7e14a483bbf84394adf55a86d70097188 (commit)
via 2f0de2e7a8d594fd40c4fb2449232bb5cd64efa9 (commit)
via dadeedb633df1f3793c32fa283c82f22fcdb7ff4 (commit)
via 5017cbe7d18c752b80e8e8289b9ab0315f5915d0 (commit)
via 0a9ce967a515894bd7c46cf86a6ff4bc3d856b3a (commit)
via 2e967b7775e023f77f082f49457342fff2c7be33 (commit)
via db2594e846ccf340ed9820f049b9839231a4832b (commit)
via b973f67520682b63ef38b1451d309be9f4f4b218 (commit)
via 0de367c06c8e4c730729de446cc5c54b7cee9ea4 (commit)
via a4a2b5aba9b2b16c4aa2cc8af01c19a4b9b61aea (commit)
via be8886364d3e7466a0a5007a75df797b6839004c (commit)
via 25747d9b65fccbe5e37b4c49377382381edf1e78 (commit)
via 77027490d53b644e157fce2df59c5dbd496d1e1a (commit)
via be701769ab3ba884c550d22455ba09af3b725a07 (commit)
via f4b20fc54b74d03279245942900806a5e7b57d39 (commit)
via 9186edcad7735f16c835bd845572e74f8069f2d3 (commit)
via 38f2d6e49c7f693c55e5d27b3b247a167895826c (commit)
via 120a7ea6efb5ba35008ed9b3502846f4b8fb2ed8 (commit)
via 682436e844799b2194eccc2ffdfb3fa3da527cea (commit)
via 8c7144c6aef7eb26215f045ee95551ed7a6fe307 (commit)
via a2dbc20364f13ddd393e51e711db7e5e3bd2551f (commit)
via 529119357642023c02dc40fd3c07bd2797062ba9 (commit)
via 28c720f2b0319ee8b2ee21cea1105e411a31360c (commit)
via d82bd9a601e95a301e268c21a8ddcfea560d38dc (commit)
via 3cb71c8c163525c612460eff4292adb997f8a797 (commit)
via 86355ec5ded2f1988dc466f8844360c78e8454a7 (commit)
via 2a0f21d3415558e8be812e74e554e11c6cbd6270 (commit)
via b13c2fd090114e2c0cfe164bae10cde145f509b4 (commit)
via 09191932190ac8a64a4b77def3877fc5801d8aeb (commit)
via f79cea1f5a7ce45498a7a94cb5ed9aae6dfe1a7f (commit)
via 9e7943a5c72c19247d6ae7e7c264ef37e11d3561 (commit)
via 96c47a07bb44b6667816e576d8907fc223d1d771 (commit)
via 6458b98ea487620fd4c47b0de5b5bc2e5fe97794 (commit)
via 910df2cc9298c1c7697f6f38c303b26169e62305 (commit)
via 2bd3a99bb2580fa3c783058f490eb1c8130c65ab (commit)
via 19603be4d345c061cb2345187cfed58a785bf03a (commit)
via ba727c17d3517232a3c40fa3a30c6924a30ed7dc (commit)
via cc7a7fb930a5300aae369665ddc882c8bbf73cb7 (commit)
via d3b169adf97f9c5a4990400e54562d644016fdaf (commit)
via 91193e42d664fbb494a15cdf5b01a0c5da19d0b7 (commit)
via ab86095a11e912123c40f6b41879dfa634e491a8 (commit)
via d1a73e27eb7d3c133e871809efeea5174b01a2ee (commit)
via 1022129fcea6743037ed7ae2b363b0af082afd30 (commit)
via 7de2a6f3f96d466ed24f7d8b24708a30ee679729 (commit)
via da0e644a1c4826da8587bee6fe1902a6f28e5931 (commit)
via 8f6657a13b4e6e140e3c5a52201fd4c998ee9560 (commit)
via 2e46317ec615067947983aa9ebcce371a6a8fd2f (commit)
via e5e62873bcc14a7aba87bf0bdc7d2d354aba331b (commit)
via a42df8e3040bc9cfa52aca8d45d09ef016810862 (commit)
via 5ac67ede03892a5eacf42ce3ace1e4e376164c9f (commit)
via a9e9a2b26fbe93fda5174e4482c9c13e05287539 (commit)
via f54d8505d8ac997fca63d2cd82485b19c248a804 (commit)
via 950fc0ab5f04dc55c41ad93179cc188f8695f335 (commit)
via b229aa1c060b0d3a21b3173b97c835ff10dc3b17 (commit)
via ba66913e80236f8b33afd055317fc0356580970e (commit)
via 4bdd50ce23081a45afbcb653a6b75a1b408c4e0b (commit)
via a232b085dfef35b196013423e25927cd6e230276 (commit)
via 4fa3cf5c5f623d24c357aac1d689a068528c73ce (commit)
via d3f8f8fce7957a2227dbc38d2faf58ca33f85fd1 (commit)
via 923a47079606a6ba9368d94007b3c24aaa4ca7e5 (commit)
via 6e0b7398570e219d417f991681f6d6428fb12a67 (commit)
via e8460092b6addf9ce69dddb49d80d91037178ba7 (commit)
via ddd04eed61f4945303400569629d66cf685833d2 (commit)
via add0f697aec63c9af1df661951bda2ae007c98bf (commit)
via 1a5be7480941be67947095ab12297d157f8fb572 (commit)
via 71d27e416b025bc77edd70f9e885fd804d043efd (commit)
via 89ad5194ebbbc808c7e5f317da47550e99efa9ea (commit)
via 9a75a9c73b0bb361f1952e92eb4b5a62694be4d6 (commit)
via 6fb5dd6bf0e9fcb0d00eb3f659b5b9c45340bb37 (commit)
via c3d81863bc201dc1fab9690565158b8f09d61061 (commit)
via 199599a979e331026ec5dc8ff07f1bb08f3228e9 (commit)
via d7ec12be1f1e87ca0cc4675165f126f8d010b6b3 (commit)
via 13204f9c20a03dcc041cd22fff22fb28af569eaf (commit)
via 7c9c7811d3d8642ae675073e0cf9e8b83df49c4c (commit)
via b7d8ac6afdd51bb18fcb0c1c0f7389d4975f7c54 (commit)
via 586df2b37d5f26ac1998d12c86f280b11bf077b7 (commit)
via 0ac0b4602fa30852b0d86cc3c0b4730deb1a58fe (commit)
via 40f74edaaf73a8a5a7798fd79646e2279b82b5cc (commit)
via 7c419681601df9c3a453f0e46756dd751344b1a8 (commit)
via 24ef7a16425e696fbb794605a89ff8d7bdd7172c (commit)
via 59455cd6b9de13d63c2b6cf17eb7e8c88c8a99cc (commit)
via 8a47d8d2b9123df707aebfa14141a5c11c5a6228 (commit)
via 6bdfcf31fed84b413714a0ef446578ebbb3002a9 (commit)
via f0dd8824dc9bb06420b960f2842902ca2d63fba3 (commit)
via 54f4650b7de4d275b29bc1d70b2cba98d59d305a (commit)
via af01e66fde8c0055319c47d775b0af1408a3e18a (commit)
via 33deff727f64139d15f45173679fd0e73531e7da (commit)
via 354ca192db138bdd942734ff985b6e21c8445a82 (commit)
via bb708b6586815ae9ac14d99b0d4bfd714f315273 (commit)
via d3120390ae945b88432c48aa82daf90ef67f6715 (commit)
via 27025d831a4d06672458004ec70b5c2cef73904d (commit)
via 34be7c60c8cd01a3693cdc35346ea8bed77a4c88 (commit)
via 008a3bf75b8aef0b813c5109fe8749f32ca6c7a9 (commit)
via cce105f44bf716ed738a1465e5412468d796aa13 (commit)
via 57b77aa7c09f568a9adf7f9ad475cc80f278fedc (commit)
via e5961a4ce06bb430469c457856f5392ca147d857 (commit)
via 14a51327ed408ed5a957147720f7ad3e3db6a95d (commit)
via 8f331c78a07959b413f4a00e3b3f7a935cd42b2f (commit)
via 60f301c235b9d83b7fde6f06abac18d0a1168260 (commit)
via 4a8925ec51c064a456795ba17042bdf30ff2b8ab (commit)
via d4af3712f3987069dffe6ed30919ce0b7bf84699 (commit)
via 0227b4b17e2bdbbf5472c145ce14f1fb08aa7791 (commit)
via 795a77a4651542f80ba8906d3f02b4987d414f79 (commit)
via 41a2bfb4045de0547186a8dca60c30314a3ae28c (commit)
via 8b41f77f0099ddc7ca7d34d39ad8c39bb1a8363c (commit)
via ea0d42e325de86353e17b29c26257333f0fe016e (commit)
via 34d182ec09a5674e87470772cce024d49043cbd9 (commit)
via f9e5f363f3abf3c08d65ff14421491b44ccbe8af (commit)
via e4ee8c985e9adf5f734f0f693b79fe459ef848db (commit)
via 9739cbce2eaffc7e80640db58a8513295cf684de (commit)
via 6df94e2db856c1adc020f658cc77da5edc967555 (commit)
via c6b5e0f57cb473f7e2731d8ca1d3619139263a12 (commit)
via 239114f5e894111f47dca785429a9180f2cb1598 (commit)
via 477b53cb86a431a7c38655db3972a554b412e700 (commit)
via 8ffbcda5bbf9729b9ab09d89531520c9f94618ee (commit)
via 77a02471ef75d226a6dbf7e962491fedfef6d6d9 (commit)
via 09b70f9b0f93da5c7034b02837987fc03a675472 (commit)
via de89048d524aef36e88ce770f3f953401e3a24a8 (commit)
via 94b5025336f509832addd712ed2f9f4fce48bfa4 (commit)
via 8578607b1c73c76e16a4eabd4ed45539260e0c74 (commit)
via 0503f4a60a6873d63bb1d9352c6b50a7a40130f9 (commit)
via 71c4e3cb8c0f6c4e51a8afd24928ee10710a49a2 (commit)
via bba7327c68c2242882dcaadbcae4b2db0aabedd3 (commit)
via 783c0a87b919e9d39ab271de6203bb4277758e2d (commit)
via f9be171869937e52f960568773c45cfda28baa3a (commit)
via 955a15527bec6ba0231f0bc377539a169fa34165 (commit)
via da6a33e379d7c8ad56087b9302d7dc5e6a6ed08d (commit)
via 094d2aa6c73201892be2362d6d43fdeb34a2650f (commit)
via d0181f27a73dc4b77bc9c6756cb6583226dafacc (commit)
via cf131c0c98c346b2f075b76c2fc92a658eb58f1f (commit)
via dee286dad1ba1b47d89659572fc2ce8fd1a94af6 (commit)
via d22c96099aacc094be00c1fe51433d4b39b9656f (commit)
via fcb8add55e0b6d5be9104e59e4f47a6d4d4ae9c5 (commit)
via ecbfb7cf929d62a018dd4cdc7a841add3d5a35ae (commit)
via 6689f461849841d7c5a724471107f758535c213e (commit)
via 004e382616150f8a2362e94d3458b59bb2710182 (commit)
via 6dad3efd9d023633d973d20b9102f2f8b10f3d17 (commit)
via 1355eed8148ae3199e5c047c004bc8ad839ad5f8 (commit)
via 5dc6a761125d48557a13a6354a6c373607fb2714 (commit)
via 76a5b79fe8b601f5c9aeafd68394628ac9552f16 (commit)
via 64dad23b34538632b6b2724c5d6c17b295baf9f5 (commit)
via 3ebbda21d35170b26d7db65686583617c8f0cc26 (commit)
via e7a46851671cd942d39e74d0456435401dc15881 (commit)
via ba870bd64170a8533f1bbabcd6df36edf31674c5 (commit)
via 1b7e11468e670385d56d05b97afac96c4da05b9a (commit)
via f3e39622ac840f6322cbc76277b2104ba4e8aee4 (commit)
via f9ed88b3ab1f080f2e96219a70c7f6ba9026d547 (commit)
via 04934bb05de6689d170ff4d52c2518301df0f960 (commit)
via 09ece8cdd41c0f025e8b897b4883885d88d4ba5d (commit)
via 296d6b7b9b3805b5bc71ece7e3f947604c641ffb (commit)
via 14b12e16f91cca0cd7c9053727832111538ecc85 (commit)
via 0e2743485d50f8987b9d1fe959b215e49039a965 (commit)
via e62c547688f1e4820fa8ba200149ea16960354b9 (commit)
via 319debfb957641f311102739a15059f8453c54ce (commit)
via 80a4c6e866a3f40462b26260c6de1ef05a334e53 (commit)
via 67ff28d5017ac0cb38b591de079809bfe765ddd1 (commit)
via 7e90e3729b54ca18425c3b5a5223230aa335efff (commit)
via bd8392ffd8873367f243121cf1e33eb7981b4f4d (commit)
via f8f81b7f1512760cb72592a79d2f49999f11be2c (commit)
via d6f67da406651c7cc013ce28c513c23ee43e2efc (commit)
via e6e3ba735b384ebe4ee1ccacb39af2100bdd5f19 (commit)
via 301cadd12c9f4aec34835ffde2abdc808ab28936 (commit)
via fa007383d4b4d71c5a4176c4bc6e11154d662d8d (commit)
via 3a9752bd2611ce206fe0526257876ba99a640e6b (commit)
via b6b79e9a9f444e2ab6b1a53fc1597c77cbcefd61 (commit)
via d58cbd26658afd381cb715d0ca976d11006a0445 (commit)
via a37dace36f7322764bb01cc790c3f36d524c0456 (commit)
via 9136696612968df28a925c910b54217ffe84d206 (commit)
via 236adec49e83cc6f5ce85c07766fe5f553e00812 (commit)
via c9a06623e3e6041342046d52f502ce5e478ef29c (commit)
via 85b6fa72d68d019149b8c751d495e34bbd4246a8 (commit)
via a2a0bf66d71c5b5adba4a1e0dca48496bfee0ce1 (commit)
via 004816211d6b0f438713c1a4e167be8ac25a356f (commit)
via e5fb3bc1ed5f3c0aec6eb40a16c63f3d0fc6a7b2 (commit)
via c78659bd80e45ddb44b3cafb4937aa7b4e5f1b14 (commit)
via 05f49a93714de9a09ad1c0a9d6e2a7365e890232 (commit)
via 3c06d0d6c0d002138ab3edbd9a6833a40de3f911 (commit)
via 64c233aa9b565df653928f79e987ff0ffacf68af (commit)
via f8e2229b901ea731ac1fd7e7bac0799b855cd88a (commit)
via ed89ba2b9f20a5aad5f213191f9b34981aa6a0b9 (commit)
via 8ad3c81393273b291cbe8e97f9ab7c260d8ca94f (commit)
via 5cfa9db0c1eea204a2f6161c4929da4d896afb41 (commit)
via cab140484a93d9fabc4c3a108ce4770e22b08dc4 (commit)
via 1c3d1954d25eed486f1fa97d4ae5493693b04945 (commit)
via 0fac71e31d16f1cfe127d8d0341842aad762a642 (commit)
via 6f031a09a248e7684723c000f3e8cc981dcdb349 (commit)
via b3d03749ba27521a93188402c18600360e44c497 (commit)
via 500a785136096ec3a7c5259ec348a80256aed494 (commit)
via 9c690982f24fef19c747a72f43c4298333a58f48 (commit)
via ce7bc899e76309c63b1ad270b9474fdc1407cf5e (commit)
via 96c902f2ffb43d75fbd138a6da15dbbc0d80a71f (commit)
via aa32fd56bf7b4eb3f1ee13b502f2724af8f7ac4d (commit)
via d0a29d0aa3931ad8091c24665ceeee95b94d58eb (commit)
via 6750a2bb8306b0c335f12f6d1bd97c1b0559bdd7 (commit)
via dab32ddc42b4707968fbc056261c50d4fa08e408 (commit)
via 73b2eaec32ef4dbfa93bb5009229e9ca261c3676 (commit)
via 72c4f5046949567927410bdb2ccb7ecb663d16dd (commit)
via 8f777e093c960f76ff489f12684005eb48580323 (commit)
via cb2cf2013b616b055904e301b3d5ff4c49475525 (commit)
via cbc9f118a77f69878f4cebf383db39d984201bae (commit)
via 689503220317cf2e86dae704a8e660ed646af67b (commit)
via 130ba31d0829fa47a5e245490a9ae172d1c91b7c (commit)
via ae9a05a4c5d73b3d32303b622c6b7d0c04d061d3 (commit)
via 57736fd520bda626de430e55d9f7f0b1c13e6353 (commit)
via 9b9f126d3e4adc4ef7e6b61945245499745996df (commit)
via b6bce27baf454101b3755d46877ac84c5eef20f2 (commit)
via 8661db658ebec93ba0e2e890ea7db2b9fa20df4d (commit)
via 3d61dceb1a9093ab110920fb436c3b2061f15857 (commit)
via 967fe3d95663240940e2c5e653fc3ecac7739037 (commit)
via 6bbb42b7fa668d7a8fcc3cf3809365179705a3a2 (commit)
via 1e3a2586abe597550611cca5697916f311ffd2a9 (commit)
via cad6c0a62b3c5cace703d9863d64dbdb1e047046 (commit)
via 949f642c90ab8ad7f0f02c35e383b9eb9e65792a (commit)
via f59af8201a12342a2779440a4c7afed1b51fef7f (commit)
via d7a9547198d61bc455d41ba45f6f14874f05c1d5 (commit)
via 9cf88a2d14abccc7b3ad6ffd8d299dfa464bee1c (commit)
via 17832da870f12a8bfb6ec4e8f91fda964c1afcbf (commit)
via cf0dac64cd9e7f132ca58f0df3609f00d2cc4a07 (commit)
via a9944090a851bd3be8f459c7185058394d31fd55 (commit)
via 4864018bdb5a6f3f9ea9d7a9cc608521655233dd (commit)
via 310d15bf23ac533d70a20c0a881fc285cb7af3f7 (commit)
via c2a34db25f4b0c903a025514cdff758e7ea55e42 (commit)
via ad418dc7853679f1d79c280af5993b82c43dc51a (commit)
via 98908690535371b9128a1263c7a4b59150609526 (commit)
via a01b61f9915856af9c915beca5061867804e5a86 (commit)
via 5ba0bcb7d12da8e28ca791fef5bdb82ee2619fa8 (commit)
via 866ae82be92451173b08ff1135c2ffe9ce820271 (commit)
via 634dd02746cd7d5cc6c702b9a3ea690825282c22 (commit)
via 24afa6a4a2bc6d4fb4151a866cb12ff5728db277 (commit)
via 00b804d7100f12f5ea261b4dccb01f1de4cd94cc (commit)
via 75cd27c3b2d1bde61ffef18bb53198cc459910ed (commit)
via b099d028354adfd16fe3dd838f1f36f024c79f40 (commit)
via ccdb524e2fa0b9d2ce5570b73023a3f127756c76 (commit)
via dd59f2ff605abcdb0a1e8b284c219b8d788c0585 (commit)
via 42cd95d56e1f9c71eeeb107284c4762cf1a9a726 (commit)
via 7ddfd9eca150efa2fed15114034e5297db765a53 (commit)
via 1888b383084177cbfab6c408e15565a3bec17ee9 (commit)
via 73837f513323e69175abfcfd04ca2f7b1fe22a4c (commit)
via 4f992e158c48eba97d4cfd497a1abba12336e38d (commit)
via 496a0353cded1f60342e7713c28fecdffcecbbbe (commit)
via 6c11d9c51b88e2a6d238eac951d1d0317978f636 (commit)
via cdd18357620129b8533e9619b30b37ecbc91ede0 (commit)
via 8aa3b2246ae095bbe7f855fd11656ae3bdb98986 (commit)
via adba8b7c9e699cf557646e942012567f8e5156c9 (commit)
via 541478374f183c2742a7f07e6558836a4dd09570 (commit)
via 17f237478961005707d649a661cc72a4a0d612d4 (commit)
via 12c23a667c1e9a4b92d1cd8fc07136faf4c41a8a (commit)
via dd61821a6c910d90c4a46024e303965048dde0aa (commit)
via 0e6a18e12f61cc482e07078776234f32605312e5 (commit)
via 756e56a8c683906b1b50bba5675c996fb1295820 (commit)
via f0324078a088345812c5397317b66445b02f82be (commit)
via e01aeb058be1d8bd7715ab603c097230cd18df5a (commit)
via 841627853b192480a8e663870ad3a1cb39bb724e (commit)
via bbb0031f3a987b3314e1b3e8db1c7ca155f9f480 (commit)
via 0cc21b601f0ef1e907deaf3dbf02813ec2e0f493 (commit)
via 26aae7d612bf0f01b22a7c4784fffb909d148970 (commit)
via afc4be09c37272fdadecbe8496b2579ef4cf3e42 (commit)
via 5266058997a5746c04db5a721ac60630bbbc2abd (commit)
via a30321812264615d0f64271ce6ba5e841f7977a5 (commit)
via 93b246f4689918c081d9ed889cea9140b75adc1f (commit)
via 5e357877317a7a70e9a66643a4f846c32f5dc2ae (commit)
via db9ebc20e4f4b401307d6237796e0252bfaa1c59 (commit)
via b9e1f48df9f398d7641f61317a7d969690af1ff2 (commit)
via d8989e0a05ae7a453a6768f2ff4019d41ad84aab (commit)
via a9ac4ee37388d93c0adceffa424599b5affc5c3a (commit)
via 297c145c01d18f73e2b453ef968426352815dda6 (commit)
via 1708a52ea3956e0e7027991f6cfeb6a1b9415b94 (commit)
via 47e53343554aa6b4323ab05799a6315fb1e42538 (commit)
via dfb68c22293f3e84aae8e81865525dd043f26e0d (commit)
via 72267db6056eca0fbc9ea9b656ffb42475e7480b (commit)
via a151c4a62009d73ab3f05607ccd12889de4b9718 (commit)
via 0d1efe0f87c1868ff5389c731dbfa7ead9416655 (commit)
via 0ae5d2d73aad78ee24e641870cec1ab06f65e3f6 (commit)
via 569afbc3c08776e04f1b755dce2ff5aa5c385647 (commit)
via 38778d44793426906ef7d0d25a70be4a36188765 (commit)
via 411b0f674614000d1f4606e0c225d078c1981628 (commit)
via 696cec5b0e99ebf65c3ea634abbc6114f1b92d8c (commit)
via 2c54e9c5f490b876eb59200cfcf9cc4623250bda (commit)
via e935dae553418e90ef4ac06d598da285e8b20bea (commit)
via 355e2efda4baba2827a9d95a648974ab3f5bcfa3 (commit)
via 9cdddf2713e4e4fe103cb3cdd6e9af44e827c5d8 (commit)
via 2cb4775feeb0be8174565e175c4f800899879c19 (commit)
via d74693d2cb99c839b5efdd7c1bc0188fe39bc86f (commit)
via e26d533eb62f553fcbae616d82e84e3784f750c6 (commit)
via c9eb48c9a8c49d99a31ce752b11ac507f3346356 (commit)
via 8697c90c350f8ec3c3c3677de0761230fedc8ef4 (commit)
via d619eab67a0c2255f249dfabd7c9db0e5d14b833 (commit)
via 6fbb0f720f6e6a5d58c0dfc6710f0e26e79816c4 (commit)
via 9cf25c82920546461f63a87359a9ef1aa00bfc78 (commit)
via 76e1d3b208772073e664437788865a5e3d067a68 (commit)
via d9fda268d2fcecbb69f06f3480bc9dcd058c582e (commit)
via e8cbf87a4e734023f9a79d131dbe1fde067fe0c3 (commit)
via 17a76d75e8ee9e88417058b220b01761c1fd370a (commit)
via af0e5cd93bebb27cb5c4457f7759d12c8bf953a6 (commit)
via 17f874bb877356f0eb3c777282e1a21172a241c9 (commit)
via 2d2093b3eda1d0d09ae852f2dc6feefc4886b71f (commit)
via 49e2c1ae796e46fca4f5c395a92387638bb828c6 (commit)
via a02e4f3441285f10958b72e2337aa1b6e290fb56 (commit)
via 16bbee505681b46ef60febb8067765704afdeb24 (commit)
via 53f4afe16013ef6f97556d3b49d1bc56443dc08c (commit)
via 180015eada7cdc67f8756a7425f9023d884b19df (commit)
via f9a11dcba2da26e3a27a4be00461aea07aa60d85 (commit)
via a43d8d0ef4cd44d5e0ab9fa9fc746e6d1ff353e0 (commit)
via 495087240c7b5736999e3edca697eb8509068ca3 (commit)
via f364f8feb190aa09fd0a65b35bdf05a3c7fe0932 (commit)
via 1b4b8193675f0ebc9b63480900257fa21e5cfa72 (commit)
via deedd6f7ff3339f822f9ed8de77a5a7164bda9a1 (commit)
via 214eb805737eed8035490dc8ada790bca5c14fb6 (commit)
via de81dbd26e42616b7c56718ffe1ffc7c6861b9e9 (commit)
via 406ff425ec228f5b99a61576d8a576fbc76b3122 (commit)
via ad91697c609b3ad79d8f2799a18ba63548a228a2 (commit)
via 2db67df75178a2bc407417540dde75e895eb619b (commit)
via 0da13ea71e97d62ee8cffeac9613a59f6dd07335 (commit)
via c95b25359f139a816f114378872bc08ab7e6aef2 (commit)
via 91a8eaa5b51c15b6d8f6cad3d75597678eeb03f1 (commit)
via 7f5705228eb95dba445d0d1d8297c6de0306360b (commit)
via 073d75e7ab64ff107198181cbc9ef4482e83b681 (commit)
via 4fd542a1c095efee2afb61c440784cd3984803cc (commit)
via 5400e06818b54c363c6006395ea51567caa651b3 (commit)
via 42e9021df5556117697f9228645a4ff3eee246ae (commit)
via 746980b33112b63147744796663389e5e1c3144c (commit)
via 47f69905aac05aa6f619cb143a81f206b396975a (commit)
via ca368d008315b135fa2ad3ff366aa5ae15eb4d12 (commit)
via c5dfa657c2431f3daea2dcc49741f96383f0d18d (commit)
via 1d018cd8a62fdf430f5322db023badc030cee6ee (commit)
via 7abd3c8866212a9c29920ee82be7390ab2499c43 (commit)
via 3c66bce7b2da45b00fa54358ef0728c0ab2e0a71 (commit)
via 0b1f8cb6dfa5481cb67e43ee8c2f24fac97ec95d (commit)
via 0c760d178eecf3162e848074e3fe631d690cc77a (commit)
via c393ec92641b6699822ebacd1c3f4c68496f3316 (commit)
via e7251d0c587ee8a794e590fdcdd4fa07f6a19c1c (commit)
via 26ab69e18bd0417086315c20192b8b93093bf6e3 (commit)
via a7cbba998123e58d89a9d0da2d77afa184ea357e (commit)
via a61a83e551549808ddc80fa99ad453932d001ff0 (commit)
via 13a3b9f237b1e29489804ccb5a25612cbea511f4 (commit)
via 0c25e92e3d59db2012909c9bb4c9663e220a9522 (commit)
via 3cddfb8bd95782eba2fa6c5935a9298500dcdd6a (commit)
via f059f96f5c390ffe7a3835ada1e4c6be4502985b (commit)
via b786dfa77ef76b5486367a40dfcc5f8b8681db52 (commit)
via 81c35ef7e5e01da333654443045de3dd2663482c (commit)
via 10b0c31c6762cfde1e466039d18e159a601f867f (commit)
via 691d3b625c7299fc9b7d58cb855615b9b11fe393 (commit)
via 8a2edd0d1909bfdb0f7ddbdfb953eac95c987f2f (commit)
via 3337b581fafb8c7a747badfabb84b2ad01007ae6 (commit)
via 7356673c98e3ca56e1ed4adecab4818c018ccf81 (commit)
via f16f9856058ea2dca3b0be5a964fc3926ee6f395 (commit)
via 3032690d227ee2fe8f660bdcd6c0782c6a635ef0 (commit)
via 66174ced68123b63ec6d23a5f4ecd17302fd2e8b (commit)
via f167a263da41987a76b76e8b71acb9620a0c89c5 (commit)
via c334da7773375a87b5e7154daabf132f945f154a (commit)
via 7b51465130cede5f68994d4535b86ece6cc2f11d (commit)
via 36fe3ea99b743875476fa2b229a5f60502b745a4 (commit)
via c02f78e1a0757e0590e81f6486007deed34f2d4d (commit)
via ef6da9e4e8d00024a6c7565eecc6216331ddfee1 (commit)
via fcf3cdd41a35c9c96e95272bc5d817b0457ab8dc (commit)
via 57fdfeac98e3519e64105849495c1557388bc402 (commit)
via bf5f7b5f56ed649f78206fa2c3b4f9acdee1e310 (commit)
via c9f6acc81e24c4b8f0eb351123dc7b43f64e0914 (commit)
via dd1809bf5b3c9b6ac372523f5fc96a1baaf7d0a5 (commit)
via 70c1df71874b71443129d7205297ae5ed630a83d (commit)
via 29c6946c6a6a13a833b6037057debfa82ee19c22 (commit)
via 6e31e88f3be72f3e774892b46e58dc00da565dac (commit)
via d2cb311448c6f9010ff2f614ca6ffbc0c0d668a8 (commit)
via 731862b901f5530ac764db9605468d2a60ced17b (commit)
via 65ff9720f00bccc88590d09aa8f6feae5356e081 (commit)
via a463f3d1b7b87d98f8c84d9d93a9fbd0285c53b9 (commit)
via 5607fdf2add21db2a5e080fdc6b146dea58e15cc (commit)
via f3d4565f0bf0839c6b5d3cfd8ae8ee48dbc20732 (commit)
via 5cd467d3209b8665c21fad038e79c5063ca95798 (commit)
via 4d29fda48f7304d2c5bfbc246326850ca5970b0f (commit)
via 00e1d85817e9304e1836bbf2f2e7eb194e4617c4 (commit)
via 5f59f72eeb28488a1a4e4c336b33fccc2215ba06 (commit)
via 1ed67ffd6fc90c04a79056766b2658c01d30c548 (commit)
via 2ebf994dfcb704b90f9b1dcccbf56a4b38c84a36 (commit)
via 2ad85af5e37a9e393903a8c0b3589b9c086c16ec (commit)
via cd6beb6ccd33f33efbb6ac078a53fa9e0de7f3d0 (commit)
via d588ec8aafe64a586cc1bccab6be4a9fc50886d4 (commit)
via 0c303cb20840f80038f98f023ebd6903a452efdf (commit)
via f436c5c20d296c17f8afcea1e6ba6242a9d09929 (commit)
via dcfa4914665900dc475ac1dbe98336d8daab1c61 (commit)
via 389cf02a183ea2223d1c44f68fc2f764ca5d69ea (commit)
via b832c3c975bcae66717ce28e392a189887b6f08f (commit)
via f1885e6e7b4d2688be602b189eece51e4e649f42 (commit)
via 9bfbbabf77cb41babe425c9006c050edcd3cda7b (commit)
via 32ca390dad48d4d5c8bea98248bdfc62894f87ed (commit)
via 0afb9dde6cef8abfa38935e85030ab975002cf91 (commit)
via 3b18410e04cda0411a9e5699005a452d144793d7 (commit)
via 81d20bfeeab719136909382cc2d9bc1aba5f8070 (commit)
via b6d39e3da5f916c5c79ea49d7aebeca64a2c03b6 (commit)
via 0c97558ecf0c9c35d9ac86e214a0cbd1c10aaec7 (commit)
via 8cce1250c4b1a5303b515a75278103118cee83e0 (commit)
via 1e5b246a41dbfee1fdeb672b07b4e73ee1b94353 (commit)
via 614ecf7cba243e94331171cc8d1761a99ebcab0f (commit)
via f7f035851da68ff926341eba4cad497ffa476115 (commit)
via 90980c5054e54aa637969ad59e2dc16b351d4488 (commit)
via 24bb934f0ad2732c50730187376d96ddadefecbc (commit)
via f0b6f321beecb5145776a730461b61426a52a4f1 (commit)
via 3715569dde8e4770696ba2552fb545d2cff81b87 (commit)
via c336287b3e5f3262b4f85ec45e7b38b893c0b2b8 (commit)
via bc1b548a3828f5cd722496dd8eb5c09fc1f95187 (commit)
via 66f1989566dd871d010b08506c2b3a130ad3ee13 (commit)
via c936d4d7d78f23bd06e5db397ac350d9e3deb1f3 (commit)
via 725aa66bb4eea86f29f5f851b4c188b864881e81 (commit)
via 89dd63f0d2b08ed9bb4e872ecd6b5987fa60523a (commit)
via 5ff48bacf28bfc49dc6dc6351eb72f84a207f3ee (commit)
via 32fcf336abbd3e989ab37108cc896c7f5d6984b2 (commit)
via 94cb95b1d508541201fc064302ba836164d3cbe6 (commit)
via 4d62e02fd9d410441b2f94f982e4a3b4b6e85ab4 (commit)
via f998d613872373c2fbf7088088d7464e79cc10c3 (commit)
via c5f4af2f749808f7e8b96dca09819f69bb094031 (commit)
via 7b1606cea7af15dc71f5ec1d70d958b00aa98af7 (commit)
via 76fa3d127cf0118000f368195cde54b9b49063f1 (commit)
via b9296ca023cc9e76cda48a7eeebb0119166592c5 (commit)
via 8cb9313e68bb0a7867e9c1dc6e51de1b3763fe95 (commit)
via 75692d6d89837df09f1e2c74d0fd069553fa13d8 (commit)
via cc5136da5b04edb3cdc66e90f2f60f0f0291e8ff (commit)
via 81ab8dbc34ed3b181170ecaf553266dfdfaf123f (commit)
via 18706a6018483a20fff84781c5d2fced811c7a4f (commit)
via ab2c79a8815be54d7f3482ea5608d2611b7de433 (commit)
via ce77ca466280e9c63f5c47c001cd3e98381cd56a (commit)
via 254f34910f282862727eaed7557657726887439d (commit)
via 92d766ce1407da1d1bed05e64c2b62b25af63303 (commit)
via 35d21690b35272d7046b54274ccbe87fd0279b30 (commit)
via 4aa5062b964d03a27adb9eef0d519aa26efa34a0 (commit)
via ab989937aef9282b9417a51ace071cdb199b290e (commit)
via bcac9ef1e7fb84fabbda7f41d360d4a437ae2102 (commit)
via 56baabf66b9a5224d0d2d545f65b82ab50ae781d (commit)
via c73a307cb41eb3e8bb6681dd963f6953973330ca (commit)
via a9c024d3ea91431cb4a5cab0ee53aaf4b1f808c0 (commit)
via da171d46c0a6f14f40c3730fd3d6ba34d4bf9dff (commit)
via dc5d961adfa0c6b05b911532535942c00eccda7d (commit)
via 1ab958cdc6f1ebdbf88f863a4a15d6a5783035bc (commit)
via e29c8e8050f63c1a8ca1bcd0dfa0949c3cc94063 (commit)
via 38bc36c4027fdcfbe74891f11f030014a34b46dc (commit)
via 7a264c3e91a77fed53fd305d799db6b4f671108a (commit)
via 3048f118d4b4c9995d7ee258c9405f9d7abbc576 (commit)
via 74e4f864d436d960d5feea540e5573cb21dbe39c (commit)
via 2931606d7c3d6709284788890b016ff434d0f5e2 (commit)
via 430e0e89ec5ab05bdbbd43b5fba9c93bdcf8f6c9 (commit)
via 1a52db4373a1c4169b0bbd9bc6a550e5a3e68b39 (commit)
via bbd103fff6a6a6e775a075c5132bc1cdd38a5acf (commit)
via ad83fe6b6c31a54e39116f12eb5c70d9fce8d9ea (commit)
via 12aa5f7fd9f8ce0aa10b66cbd3608c363eb3390f (commit)
via 873ab3f9db15055ec293533202bc73d908ab73c1 (commit)
via a929e898df74d43fc590e8df793edeb5f355e29d (commit)
via c11ea16f0ce2d7eac933dfc9e7b57e0e82972205 (commit)
via bc3d1d70a2a074f3c3360ed75ea87a7fe81a4da6 (commit)
via 709abc271e802e1a27b6f74fe54f7f5da5a7161e (commit)
via 18964f9374531c8f131065dcd53aefd998e080ac (commit)
via 8a4a87feea1dceee3b31a25b678d3e7ce45b2d5f (commit)
via 1c325e8b3512372f5d69b366975621d06b5c217e (commit)
via 58a43899ff533e725bb606a0359862dee416f7c7 (commit)
via f70d352db01e774ad9561499f2205de3e66e8dbb (commit)
via 640640363d2a29cc0f4ab8151ca5c02dc9f7c361 (commit)
via 0f31fd26c5525b7bd63acaea187f790c2fed8dc6 (commit)
via e4dd2ed94539a6e17070494878c9023e7a8472cb (commit)
via 2ec914c9ed52fd0f22501addacb726ad193e1fed (commit)
via bf944144b1283d93a5138145cbdb576cea7a9fa0 (commit)
via d1971c14bddf6bcdbc031d1e3195b48ed4bffd62 (commit)
via cfc8341a5ff3f2cc7873689b2b787e9ed6dd1b02 (commit)
via 08b215209a79db8420d565adaec7309413b01ea7 (commit)
via a0f8c9ee2bf78af6d95293a4d802e0205a4429c3 (commit)
via 086b420bfba5b31f13e2828ca1be842a8faad4aa (commit)
via 3372c331452e94681b82b708d11dd0f93c945153 (commit)
via 311eb1ae0574c5f981937ae41614ff0fad034c9a (commit)
via f33c6b79fa27d93bcf9be45aa6844e1c26f35a97 (commit)
via ef67acec41e9b83d4aacff8de12e6a3e37628227 (commit)
via 7c42c6d38d63dd55462eb01037a55a40185920a3 (commit)
via 6d9d7b76a3e55c6ca2a4c7af09674867a0e54c28 (commit)
via 950ffa229928d24ba8ebd74c5945e4cf0facf539 (commit)
via 06756779103175d716dbeee36aff59a116eef39d (commit)
via 184c6c7068ec90f2a85a859bfa56d779a4294382 (commit)
via 7c7c776676860ba64571154faef440093799e996 (commit)
via d351fe6ec30972595d70135cee24e6dd6d990d33 (commit)
via a697043708acddf2f9757615d173b514c98de9a4 (commit)
via 00dab940cb413e50ac20df693d8a05053608aa40 (commit)
via e321d8b344152b558cb42856deb5e8798f153d1b (commit)
via b7c5f0703226ea1f68af4802e4b410cc42e98144 (commit)
via cf6de64ab83a1db3a71aa065c1d247462f236ea8 (commit)
via 5f6cbcc31cc0f16af49dd433ac96da967d200d6c (commit)
via 923977389a20aa0396dccec4867f3721fc95a4bd (commit)
via d8a7320161d314a5863183a063d0e00fdd1017bd (commit)
via 74d131f92fbcd1fe21335a743dde205063b63d00 (commit)
via b086fbca9ce75d8950cd51d0b9de3d49715565a7 (commit)
via eb4c7905227413fe7d4ee8a42d1e8dfc185d36f1 (commit)
via 47dc887dfdf6d3501a73bad82f30f6148fc2ff8e (commit)
via 2565b9fe9daf6ba63234b06b9f0eb5b8168257b4 (commit)
via c2b374e5e2d608ce0236b7f49b63bf70b57c761f (commit)
via 2e93f9a6c23985a4e9968c13c3068c61f202ca4f (commit)
via 6031b9096d5a9c73468afa386a3e311984125522 (commit)
via 83904ae5b53bf4a5a5436902f4be88f818db984a (commit)
via f0a43d242f48103eec0703d7081522ec0f2225a9 (commit)
via a6f9d5cd07ea1b21641232312cc15adcce91f1ec (commit)
via 8ddc37ac3b6229f03a1fc122ea3c1c8906cbd75b (commit)
via cac61e91646c344359a5b767d2f2042d05cc1d74 (commit)
via fb4ca10d60279cc2d7da663b912dcb189849d0da (commit)
via 59dd3fb61b265455b544407fcd8976a04f152af3 (commit)
via 65ef7a150b4562f2e80222afd7fb010cbd55ec43 (commit)
via 026a4104701791649673a74591007e1d50167cbe (commit)
via 8909387beb99136bc5ec22574515c84e8ef526da (commit)
via 952124e0f4d2b64d8b3a47b52163edca7c3546ab (commit)
via 73a679d7b03fc3c53fb4826a95ee099321fb4c4d (commit)
via af4b23610ff5cb55748737dff2b67c4f905b2480 (commit)
via 8a18de79026a2620f82662d308c4f87e9a54faa3 (commit)
via 2363b50bc04e0218e185561a4e198f21e8783905 (commit)
via 4265faedd3e14e2f4afccd664df07a4e09e3ae7f (commit)
via 4db21f814bf18d17475dad5e81b8bbb8e0dfa570 (commit)
via b4aed028611c1f229d44cd688053b5635f6c909d (commit)
via c5d416a01e152b936c0023fcc65d3d468250388b (commit)
via f0a09daf74c62309e9de65adf3b99daf967ca0ac (commit)
via 9f172776a92413f7ba09fae87c98522c3d55fe63 (commit)
via a1e26d979de642bc8fc52d613f65c0dcd8fc8ee3 (commit)
via d6113a313ff848124a3ecd2e58b17bb04b5bcbee (commit)
via 578ea7f4ba94dc0d8a3d39231dad2be118e125a2 (commit)
via 210a3fa8f933d7141f0ca48e5088a89fcac611ef (commit)
via 1dbc33e995c77185505e91387359f0f4ff7adadd (commit)
via 661ee8d341d0a24b6cab13302b028a9efb40daa4 (commit)
via 1dd7c3d6806b8694fa315f55c105884245e486ea (commit)
via ed3436f599952a3e070dbf19668e5bcb2e9dacac (commit)
via dd2f336165e9f974aaed88569b5e718493cd5757 (commit)
via 5720ab60da4b443e9414852234d65acccd8e9e4e (commit)
via f4b2e3f8f7404dc3739a211d23022336f33609f8 (commit)
via 95861564f241fa7f657eb7c34449c3095fba1930 (commit)
via ad011294186315051152f2f2bb9d9af09b8f338d (commit)
via 8a175757cd9b46205b7118c3e7fad72883f130e5 (commit)
via 94b0beeb606dedc3c8fc44856e0a6f2d3c9754b4 (commit)
via 424bb3df5cd1e1cf8b64f76ff5f0118da05de9e6 (commit)
via 5d03cd4b844bf1288032ee9a148aef5077a167f3 (commit)
via c1dd1aeae9aa2cb06b372d2002fa15d57aa7c552 (commit)
via c8e84e56caeb809f91c7823ae1c4e9aa30e49c57 (commit)
via 324b135b99582c63aa49f50d8c80e00d6e43e2f9 (commit)
via 6fb3a7ca7f9f1b7018493a263b3063b0b0962b5e (commit)
via 239c9694953dcd1dc1ffdd60b7205fca5fdd651c (commit)
via b211d93037f31731426dbf073098ebfa8ae16bc4 (commit)
via 5823a2ff954e0965048c5d4cc33314d9bb2ae637 (commit)
via c30cd00cf1afde68776d7320cb283a8f9a911163 (commit)
via 6f6f5892046cb552bf334d2e5f84cf575dde3da5 (commit)
via a2a812ede6d3df4a70d6309437c6715cb09a7670 (commit)
via 749c26e2c6cdc98abf0f7d6dc693acf122e5d9f5 (commit)
via a5bd8c03942d981b01fb0be3f8ea54ebcd960945 (commit)
via 6c8cd2a184118179db9e54eed8967cedbafecbab (commit)
via acc6a3ab370cd5c22ad37bb79f819578bb1e6e97 (commit)
via 3e5f772632f1472a1125e0cd725adf6709635e8c (commit)
via a07e078b4feeb01949133fc88c9939254c38aa7c (commit)
via 748885cd80a12aad0f02fc7b53943c2620d9eb22 (commit)
via c5e46db3463427504c4814d1ad712e3f09ea9c36 (commit)
via 5609b0c697b2f3d4684ced27d35fcb91721a48e0 (commit)
via 93697f58e4d912fa87bc7f9a591c1febc9e0d139 (commit)
via 4b4813d5873856044ce28b91b1e14c8a0f84b59f (commit)
via 89cbb6aafeaf5cbc51b50a0caa4542c7b0f43eb4 (commit)
via d85c19ad4f3a92897eba8f2868f5a661bbc9a177 (commit)
via 7b84de4c0e11f4a070e038ca4f093486e55622af (commit)
via 90ede89d895da7a088715ee315b12dfb843ee2fe (commit)
via 18491370576e7438c7893f8551bbb8647001be9c (commit)
via b9b7ef39987bc2f0031bb7f46b9002665a62607a (commit)
via 98d6a12c51d58d7712c6876dde8c548162cbcbdd (commit)
via ed69c4be96f940b86a45823fa2efd010f53a357c (commit)
via 83033bbfd1298a45240e073c25bae9b0bb2f4e6f (commit)
via 6e9d5c8b61fff0b1f2a1dfb9d685bbd6ce090e8a (commit)
via 1133c5a571d5bbddaeb44c2313cce75ebd624eeb (commit)
via 3aa87f6826846585666e907b446a3809ccfce560 (commit)
via ee7acfccb0cd0d68dc4d7e64ae7fa0655314b444 (commit)
via 80dd41d265d1e2c366c8ea2dc1e79ee353f1e8f1 (commit)
via a8746fb17e10db78330cf69b2850515020f5cd56 (commit)
via 1243b751048365abdb5cec845a92674e0469b7b4 (commit)
via 89f3a8b45f18f1bf4e1a2e6082a93513582b6d55 (commit)
via 64ebfb19dd4c42b8ba81b258a1a408345c3b1caf (commit)
via 9d0ec38ee3ef9e08778abd66c6946bdec5f2d25b (commit)
via fb981291afb0a4dc0fcbd16b9bb552e5fbb5c20b (commit)
via 2f151e47e2579aed8c124385dacb3c971978ed45 (commit)
via bfa3f83286dd88d263e6c12c74ed3f2073210e03 (commit)
via a0d8f67619db8737abda16b3ef9b1b731ab1bfa1 (commit)
via 6b118748704d34fbea4d80cbed3a1094abd22e11 (commit)
via 51d35705f42ab9b20a315edbc5dd8a9541b0483c (commit)
via 3e3bcbeb2f3ceae0863e9ad943f589ac4c8e0ec3 (commit)
via 344dcf64d70d10d47d405b4e88e3cefca777aa64 (commit)
via a039907d688e2ae82af39369baa72a9c43805eb1 (commit)
via 57ba31639cd4f5686b220adbba6e91f80d3626c0 (commit)
via fd9e8511f7759b0eecab6e0a2447dfbea2b2de1a (commit)
via 94270478763c9c4000ca94afb94fb4762f56c3f9 (commit)
via bebfa15d5007c3ba8ce740403c8333ea6480b83e (commit)
via 42dbc3cabef1db3f4c9ce99bc0830ce34794a704 (commit)
via e7c61f1bb5cf375a1f005bb7d77080d2fcd8fb9e (commit)
via 3c337419ad37fb4af2d14e7d470e7c9c35790b40 (commit)
via dd37e9539f78bb16e4a044f4df3a7a6c8b284856 (commit)
via 88369b5ed0c0b023b1c985fa01964500a4c37a1f (commit)
via bc2a7f57b5a7dc127045309f7f2ff42badc4bb95 (commit)
via d6d7f1157668e98a1f849d53258b8f9c1dee1c78 (commit)
via b20ba8d51b2d395da64857e71151e0ef2f018b25 (commit)
via e5febac1248d25e3a026889ae2914fdb868361da (commit)
via e3e59cf1f240c7ab556ff8f827888f13d698e677 (commit)
via 1916a92fba92220b3a7b2149a325fc7eefca2c72 (commit)
via 30838487e181829182543e1a32e25ad313172b89 (commit)
via a8793f5ce8927d0a746c41636dc7ab414f8d21ff (commit)
via 77c5ed7fa2654d2740477b6e9e15c23fb635032a (commit)
via 1a11edc24cdbea4f90653cf0cd94344084b9f568 (commit)
via 3ccbc9347eb6fc3ea9d3b3ac9e181a1e1b94e42d (commit)
via 8f23ba4a72eed354852b023a4d41adfe4a62bb0c (commit)
via b3ec8dc3523bf2573cdc33a73eb21605d8a32305 (commit)
via 36b738c03eaba27228285a9ee00a424b799b1f61 (commit)
via 21e8607873dc52d860e3b82d00b0ab7a9eaa6deb (commit)
via 61aedbfffa0a5edf8e9e7076b54fafb927ddf3c2 (commit)
via b39bfceed83508b4e0a9932f864d15da1787c3f9 (commit)
via 93f50d1a63829198f9afc280dbd9e12057c434ed (commit)
via f9ceab26e3c573ccdabff7658b8221fbbe9a6122 (commit)
via ac8359174eaeb1df18bddcb0a57807d49ff8ed53 (commit)
via c94995ad3292958582d1ad491fdfbaa76e43cad8 (commit)
via fca9cd2ad55dfdff80ed3106c4cb40bf4d508d26 (commit)
via 692c6b15659b6af9b092dfb6263d152421cf05a7 (commit)
via 107229aeffecbac4278fdbf6515513c391e8ad2d (commit)
via 90c89f6d435109f6228f309ae1824e0b8c552416 (commit)
via 273d03db595f480800e4d071da9997cebbe976e9 (commit)
via ce2fead07b25247d05b3175a30863a898b819db1 (commit)
via 3e151d447774c4d1cc1cbf854f29a75aaa43d6ce (commit)
via 928d2871d02f7f9c17b27b2c91ae4e1314a72d24 (commit)
via 43adf3a50adde888fe01661f69069d4b8d690b18 (commit)
via 6ed7ca08bb751d131db2b3d7837e0c02e4704145 (commit)
via 1c6d27d61112ae3cfd1663d51863c58e37d5e675 (commit)
via cd2d518fbcb19a0119553f71593b7a9d908b90c3 (commit)
via ef811c6e76fe84505141ceb053af0b110a87c0b8 (commit)
via c14c4741b754a1eb226d3bdc3a7abbc4c5d727c0 (commit)
via 0e10d2264eae5b3067d5dd5d2e5d1a70d2016adf (commit)
via ce648a9362d9e10dfcfbda87546ea91c8bca5c61 (commit)
via 11da99d8c55b326acb8c7866ab9c7fe9c4800888 (commit)
via 70ca4dedec19d177198de545f8a330c8d55d33bc (commit)
via 62c72fcdf4617e4841e901408f1e7961255b8194 (commit)
via a81bd162356eea6812e6b8bdb64cd7d9eb495097 (commit)
via 3752558cd9e659d55cdd5f261e45df6a793f4aeb (commit)
via eca0f29b4b1159730e6ab82964880a98bb4cf7d6 (commit)
via cb38a8f920ba899ec0e1c674cce8e4b2fa1599bc (commit)
via d37301004adbac7209e1b9ad1a2fe6cfa0ea9354 (commit)
via 16be1dd56846ee964ffd527ec0291e13ce8d23a3 (commit)
via 395bbc339365b2d512eed63a04179845bc36c308 (commit)
via 7a8bfbdd24317e216fc10ae01d9a41fd791a8795 (commit)
via 66ace85fab74d0c2d5760f1d01a40e440161ddb7 (commit)
via 597bb698382ffdd04cdd2869ce5d2570dd5ac875 (commit)
via a26f28f2fec4d557a74c3582ed7230f87e7e9731 (commit)
via 063c1cf69047425e8553efd7ae3f48c5c0fac072 (commit)
via e01dadd724cfe1274f45c6be4b5ba6f532df8c0e (commit)
via bd45814672cd96ddb708af3eead4461a3870c440 (commit)
via db105a36beb8999996f9e4853a2cceadb074775f (commit)
via d0a9b15c2cc5a9f61b2a3f136d54192d6c4b212c (commit)
via 53033d94889ce0cadaf20f743b2deb6b68a08af7 (commit)
via 42beef26ba126bab9740af0a809617fc3c1906c6 (commit)
via 878a6a4b89e1efb2bab3d5d50d9e5144c5761d9b (commit)
via 3f791c7c437212e55981f044ef722a759f58a184 (commit)
via e64c9d4107c1d012b68239ecf4d828467f7eb6f7 (commit)
via c4f0d38193a1a6719bd31af29a60b51e405678d5 (commit)
via 5ed5d8034e14418db7f473d966cad68dc1629262 (commit)
via 182a134e8cd02ee7c32171d876fd4c96c009ead8 (commit)
via 2240f7d6ed56168afa2882b38e20d677548e3ab5 (commit)
via 1b44409d9774464306529b4174f34a4172e392d1 (commit)
via 006424d3ebd1a9b1d5b6f7daa1c98707265ec6fd (commit)
via 7c89825c3a8fe2c64fd046cf17ef90fd12836fd2 (commit)
via 03fddc0d2107ec9a573a66e75204fd1ac076f667 (commit)
via a1210be107e45632b7e4fb47d74d627a5ae18901 (commit)
via ea084ffdffb800600ccd66a2afe84579e36749d3 (commit)
via 64e656b8ca74d9198d5035dcc017d4e8347d089f (commit)
via ce5624a28b5eb026cd7d0bf42d1f0bf523013912 (commit)
via 4fb7fbb237becd17fc4fb6c370bd6ee6d02a4eca (commit)
via 47eb191bbb4990ca935d962ee31ab72bbc398194 (commit)
via 5dd96968ae574f78725e8cafeaebbee35f1eba79 (commit)
via bfebabee9abc9964671fb497279a3a00753b70fa (commit)
via 1de3a15628847340019d8f9a9f4d0ec7cce5b593 (commit)
via ba8595ee4dbcc87deb5aca78359dd7a42e4a6f45 (commit)
via 635c6e467ed5d9bfe731a726bec673a70aad0d0a (commit)
via 332f90f4789c9c39f8cbb571dcf0d1a24e1a1cf2 (commit)
via 2df894155657754151e0860e2ca9cdbed7317c70 (commit)
via ec3e659ad17adb738d543de2bc90cb13dc8a8c8a (commit)
via 28b0c586e432ced4d9b75e36411cc77827464a6d (commit)
via 0162b6441a567a0a687a726f651e23bb754bfd71 (commit)
via 04b099ccede0c62e4bf013fe38546b2b27afa6eb (commit)
via 285c5ee3d5582ed6df02d1aa00387f92a74e3695 (commit)
via 29bfd26b74745ed3d0278bfbad06a1defadff657 (commit)
via 108ad199f3dee74f9962b42766d8e2144760519a (commit)
via 9633a3adccd2a2ec770c4d1424c6cf7e284c7f4a (commit)
via 74266da0141650d6ca337a66f266ba675ad2d357 (commit)
via bd58459c94e2074aecb39b02fa47a2b79e48cfb6 (commit)
via 901c9eed835ce3e1d6e9596e27a27ce24e0b9938 (commit)
via a7724f030d3cbc03a6cf355a5714ec50835129fe (commit)
via 2a66c6a63915092248aa21261883ec8468d6bbd0 (commit)
via 50fd5ab8e643dfbb64fe6e890c5f7931606cea00 (commit)
via a669db434d32bb5e348ee8c2cc008c271f8e6f1f (commit)
via e493ae8b12335f3c4551477b61b25093d111f815 (commit)
via 5f0261d0ce8c7f5a372952a2f27161a6e5a8e1df (commit)
via a5c8450e8e73d296d3a08c65a4eacab4eb18aca7 (commit)
via 2cf809205ffedd9ddb853de58ee37531c16d109e (commit)
via 3552f7d297beb13109904ca407b5dd72dfb84e48 (commit)
via 73ad11062c6ccf59edbaf0cb480f2c0918aa33f1 (commit)
via fd7baa38c08d54d5b5f84930c1684c436d2776dc (commit)
via ce14d5ca3a63136de4879d912f57fe67f85cfa1b (commit)
via c772a141d7963b2d327287c37a0720fa65a192ae (commit)
via ee96cc0479b7fbb8a90817d43b4a778e94c28fdc (commit)
via caf6ca1dc4f8e014f975d168af370cf7a35a5fbf (commit)
via 6e4c7c07b7d6dc58213df0c67100ead14932c1ea (commit)
via e9c8b1402a1bd1834209c1f29aa121cc514388ce (commit)
via d55bcb9c5f3d7fe5bfde5f6335ef10449c18fc79 (commit)
via 692e3a1c221c7707f021d6c3508e39ab10ec41c4 (commit)
via b4c068b2189668459f6f945d6cbe51e294af4e8d (commit)
via c9aa8392c8de888b4bd42ee927741b8121098e25 (commit)
via b929be82fec5f92e115d8985552f84b4fdd385b9 (commit)
via 3be136324e68d5ebab0b0d3da88550a920662c98 (commit)
via 435b7a283437aa13047cc69c1b9e28284a3de46c (commit)
via 294251d62ece34da011ecf662df9dd0c1b141a53 (commit)
via 34a083b7721a2b32177ff9b4a4f768f03435a326 (commit)
via f11adb59bb8be08e4ee47340ff5be153646afbcd (commit)
via c98ce8727213ac274b460394f79f0eda6ed2e447 (commit)
via 3954c628c13ec90722a2d8816f52a380e0065bae (commit)
via 11f1c880e33fa9c40fc956a58ad56dca173d727d (commit)
via 1efc3bee0b08fe1abaabcee82f4391591b1be2a3 (commit)
via 681cb466d503e21aefc2d5b0c94a9b957f8a6f06 (commit)
via a48a734b469423c3b2993ba07a31c268312cd44c (commit)
via 59270dc809d9013d502f7ab66788e900eab53a3b (commit)
via d2a4816b0595fa3b8b0a3b9b55228108cddfa82f (commit)
via 0f06a4fca490a8de71e7f04d7004d57c535e31d4 (commit)
via 79ccd983f1120ea29a7bb240ec52cbc847795bd9 (commit)
via 2e4b0a5cae99f8859927a10dea175bff7bd04ad9 (commit)
via 9aa01c0740eb488685fe2c55b4788abfb5bfc55d (commit)
via ea4a481003d80caf2bff8d0187790efd526d72ca (commit)
via 238f276c9b5887fcf72e86011e610466ce163f5b (commit)
via 87c347e04505f141d624eb7be497181a37e346d0 (commit)
via 3f797298c00a8c595cce04751f901f350a4db749 (commit)
via 848b1823fc4c893f7f398759e3b6b9dde35b05f9 (commit)
via 7b4bd8aae373869067f7d8b5156925c87c37df8f (commit)
via cdf66cc9c391019d5d76c8685a719f38a06c5610 (commit)
via 8b8818946a143a7958307f43096c54d262f4c60f (commit)
via 5dbc036dd003905989d307e0821a6369f800465f (commit)
via 5474eba181cb2fdd80e2b2200e072cd0a13a4e52 (commit)
via 0bb6cd0687b5f92e354a222cce664dc5e19e9818 (commit)
via 758460fa8ad9cd35841f5c44dd700d9f74f3d070 (commit)
via 09a7dbf0d10ce5f6901c84b24d05104f2b61401e (commit)
via d800f669f62cd6fd96aa862e70996add0e1b7adf (commit)
via d0b73dfd4d6f13ebf7fa40cc2b27c8b5e4e620d2 (commit)
via 4d42a76c3ea99edfb0b64a300a83b1e55e5f5abd (commit)
via 93f9b680c3af48ce6f8a768656254564eff00800 (commit)
via 0de71f9b3452d89e014effa023ef5af8d17a9b53 (commit)
via 2a0cd9eaad704135d8e0d4a729d953c4770d8a9c (commit)
via 4f7e1f46da1046de527ab129a88f6aad3dba7562 (commit)
via 002ba326fc50cc8a995e5037c3170dbf1b7aa622 (commit)
via 1d7d3918661ba1c6a8b1e40d8fcbc5640a84df12 (commit)
via 880220478c3e8702d56d761b1e0b21b77d08ee5a (commit)
via 3b56eb515ddfd85ed2156398838a536b39ab7c86 (commit)
via cfde436fbd7ddf3f49cbbd153999656e8ca2a298 (commit)
via 3666c6d78561034b5235d91547f94c554adc4b11 (commit)
via f9107953a4df9a2e988794314d30b66a51591155 (commit)
via 14c9baf05ae96dce5461cd49a8cc0c4769a932e6 (commit)
via a104538a133a23e5283a77d74bbb6f731d3f3e13 (commit)
via 231849d98c9e95c832619e18d992da34918dc75e (commit)
via f8d17c606dce6b1048f791d79e5a6d11a2c10cc1 (commit)
via b62772e3735552686933247d2a3a72cbea979aa8 (commit)
via 7ec4491461e965cf9461345acc43a29b33edf7a4 (commit)
via 986bddd7e694155bdf433971785b6a3b0b2f84cc (commit)
via 3a899ef4c5c11d4bdfd9f50ac4aaa1b276060a65 (commit)
via d7cec19e7ab94b077da8a78bfa54183871b865e4 (commit)
via a99da0d489fbecc59b6d75a85e71e92ec7d173e3 (commit)
via 59c4afb96ca3d51d0807d6c744a5e94796ff5e93 (commit)
via a0e0f22323abff0332ac4dff60300874a3e2f1a0 (commit)
via 4b8be48804ddb865aa7175f2970348481b40db0b (commit)
via 4205ee154f209d4493e2b7176b53c79d5dbaf193 (commit)
via 0e1d792f2eb4eaee37a371dc299fa9ff9826bee3 (commit)
via a030bd71f096ca5456c5d764d2b7c2fbc99391e5 (commit)
via d24563e01d358675b8e44a6c60586a16dcae2f87 (commit)
via 6e5884cfb1610b47339be5f2578e8e77840d3095 (commit)
via 4e8c15eae9ffb0d159f5d9f138455aa0213163fe (commit)
via 83d5d9a0bc2599bd9a643c2c6294f382638486b6 (commit)
via e75ba8ce2f5b2f9814741d0ea633f3542b458a74 (commit)
via fb589ee796f4a528dfabf4f6e22bb45f256110d9 (commit)
via 229239d55239f3a9beac8059011057669b3e9563 (commit)
via f62f4f06cf94b4d979e5b036a41c06b4f53e5759 (commit)
via 9bc720853b84c7a23b805cd86e3790291af5d266 (commit)
via 345b81701d1bb3c7043d6ab69816cfd432b809d2 (commit)
via 9c1feda33a270ca4c1d7242c54ce0bf4ec2d582f (commit)
via 5a036f8ab292416108803cf9b8c20a10e6e0caff (commit)
via 39158d307dd4b4a74f89bb755d22d1edd3a04d80 (commit)
via 777f07c955102aaa875ed2567d788b4103f849e1 (commit)
via 2aff13f019888ae254733f2c16d9a8b995a48318 (commit)
via eef82e02f99e52cecd0e4bacd8c3cf2962e882b0 (commit)
via 0050a51bfb904132db32a2d4729064d8a35a5332 (commit)
via aeb98ed824c3a058814ecc34ba7fd5767d8886a0 (commit)
via 8bec2eca0a5223cba7b22d89f387fdf577d9d641 (commit)
via 9179014fed813460c75fcbb65ffa4ac717297510 (commit)
via d59b0258371ffe79f64fae1dddb4e63f0dc7c47b (commit)
via ebb344c1e2b8bb7eaa424ae4b03a46ac01787ff9 (commit)
via 23f7aacaf2dc07ad8c4f6bb7f06e917acf422ba4 (commit)
via c65657eb0a7759dfef0536c42df184dcd8258773 (commit)
via 1fb8fa24ebe2335892bcb823e877e5608ea2f89b (commit)
via 9a4f1e5f8a347987399245980edec0dc9f4da14e (commit)
via 96e33bafc88bbf91fba996d4c27ff32dc9101ab2 (commit)
via 558bed2f3b0cfa2736bed0509b26ac23935ffda2 (commit)
via 8dfd1b02eabe036e93b61d9668e997b8669b5094 (commit)
via 07dc4f7660d991e23064c9ff4c51903245882139 (commit)
via 77f677784ed005ff2cf6e5387d837897e3e3bdc4 (commit)
via f8a1c775e69ba46ee8c984ee7ba6cf2c131724d2 (commit)
via 886b10843e5e8fe2fd7b04684a590affe1b812ab (commit)
via 06cfe32314fee758783fc17f08dfb73f3825f7cb (commit)
via 87bc29c02f7fd571688c28ee91ecfe5c558da0f0 (commit)
via 91bd5f5545dc0c1f40f2218731e87da8466b9b5e (commit)
via 30e01f305821c460e601982ad1d1a99e933aa70d (commit)
via 0061de28095c4ef166de784c12366adabc8a6636 (commit)
via f64a1962849e0773c33f95b541daf18a95911265 (commit)
via 54fc4a12f1f0fdf7757f881ed098e0d84046e958 (commit)
via b36082974151c24efedf99b97f609d91d62056d2 (commit)
via 3c843988bec057c74b920219cdc4f8330f2e401f (commit)
via 8e7af7e1cd589aeef7ac064e66a7ac1cd8d2ff1b (commit)
via e10eec12285789fac49b9d0621c3f182ce06f073 (commit)
via 07e20270989ec37e95feb3a01fefeb5b80465bef (commit)
via a7bf808a220742d18fb1868241f1665f745ebdbc (commit)
via 8eafc24514f6d6cc1a8b71bef7358cb9bdff5a8c (commit)
via 42902cc0ac042fabafaa3be1c9d7950f7926cd2b (commit)
via 3f51d6ed3118f35106f23dfe6e08a154f84e8617 (commit)
via 0a4145a3c611f2769ae210c2fe5d7aba1c3c7435 (commit)
via 9e23db8d72d7acfc911cc27055e26da426e90161 (commit)
via db2a8bd66e46e76c62cfba8c32bde1d304b14dfa (commit)
via c1386433392285b6f676fb5f8c431caccbc2e598 (commit)
via 9c13aed74ec4c68bee8dfe4a7a286a6db6a36a03 (commit)
via f650e003dea0451e5114cfcb8ee4ac41b1ed6b3d (commit)
via 006c32f46db9ee75330c1992c9c9119337dd0818 (commit)
via 87428d78d08d7aa82a5ce6bd0caf4a5ed4329804 (commit)
via 25530c6ca2483cf92aab7f240c7fa70bd3c5b3a8 (commit)
via 5d731b74d984722aa6a77f048df47cc2268c46fb (commit)
via 33f0b8420f858e389d2fb0f27ddde1fa77287dc7 (commit)
via 1d14ed6d8f987b6fd4b9e140dd16be026ef89efb (commit)
via e8edbcf573f453fd1f72a3b899eaad9d195cc37c (commit)
via 0914959914d08400a001ad4907636e3fc40da0c1 (commit)
via c31de72c76258d7444acc1f1ae1f30024b772af7 (commit)
via 14824b3a8199caabbf76a5c7b933715a0f0c4fec (commit)
via 625080ef9e1c58fff54cce45ec865282e3a223b2 (commit)
via 8a9a510e562ee06569b3f381ba5b01b8e1e87cd1 (commit)
via ae44d5cb9ac9253b1749b397f1c12859673b952b (commit)
via 1e76af0605dabf51e344b69328f2099a50d4dd0d (commit)
via 2a507893c94ede606e59c94a85c7370aad8385ed (commit)
via 3c8c2f81dad2c4a210ae288e516a997b3f9121e7 (commit)
via 38e8504574d599e1e6d139ebcfe5f905de678e2d (commit)
via f63a2aa77cd0fc4cd34cf858301a4ae869ccb563 (commit)
via c708d5799c5b3b9ec39b1d8a6959a2840f0905f1 (commit)
via 00b6c55a6d2b94f81302ec86e09c129c87e8ab5d (commit)
via 56adb44d6c4118739c0ed74a5d36c7d3d5d50498 (commit)
via 78a330249aa56fe6130c506b455b1c97046b877e (commit)
via 7bfa3ce5dc0a5a309c76085ac67834cade262ae4 (commit)
via 499556d5b50543fc98f326bc3aa2c263d6e8353a (commit)
via b29fa20ed2bd606523e54755d5e7691b2d760b48 (commit)
via f7b67caa8fac893bfc9b29ef2597c4a74afdec8a (commit)
via c902e3c93f9be0f0cf003d0ab120ddcaa5b22676 (commit)
via 61e391f3bb0af427263c2374d613af3fd10a5fe4 (commit)
via 6c3a274305a7823a4060769ab21d55c5975615d6 (commit)
via ce2e289a200095260263f20591f78be72fab0463 (commit)
via 1391a2e171f5e558b200857b6b72db5eda0d3f05 (commit)
via fe5c224d0143d31bbacdafb17955a5d38bcf8782 (commit)
via 5fc69482a707c3ce00f147d0efcc6dd1ecdfb6b8 (commit)
via 6fa2e86bfd60a417c37cc4a1ff768c0f21b47565 (commit)
via f20193d545251670a23cc386753a41e129346b06 (commit)
via 01d4022b44729b53931f21f074dd7a8c443980b0 (commit)
via f0cd421ae839ce0dd35e9ff166420a03eafd948d (commit)
via 8c842dc3c7ee078b2561b1ca0d9c4e20fc93bee6 (commit)
via 819afa3dc6523c9765262452e4445f81440ea8dc (commit)
via 818530c7a1bebe4757cc5ca0b7481276967d11f2 (commit)
via ed65bd6dad7b56bf6742ee3292fecebcf7fb9d88 (commit)
via 34f8f0f476b1d6b1bf171f0b89758faaf43f12eb (commit)
via 7d371961852161f6b758699df2a72304ed2b8d85 (commit)
via 49526c4883098afa520afbc81a561552e3469ea6 (commit)
via 95d85541710261bb7241cea0d7cc7470687fc4d5 (commit)
via d9ec4df5fbc9689378931e0c216b53f4409f89ff (commit)
via a858cc182e603a18646c7d4cd55ac3adb46b3eb4 (commit)
via 3e663477ccb7f6d041526dfaded48a46752eee5c (commit)
via f7148489a4ce1e3c9963dfd0ea9670f4be41aff0 (commit)
via 0cb5cf23b7de74fe5078efe6d40773f0403f3dfe (commit)
via cabe7cdd3b718ccd01f899578352f0965010ab70 (commit)
via 8c5d4f5541187a19e83b9d9bf56dccdf6b028c04 (commit)
via 914a1886b53c99cfe05f15128068927c0c67d416 (commit)
via c7440bcd65796d9b07eace047fab5cac4c13cbff (commit)
via be382273032bea130312eac10bf9d0fffa3f8252 (commit)
via ec99fa2a77103688304f11c5721d932e836dc299 (commit)
via 313782b82421c97c4b21fcfb3d84350ad2fcec4e (commit)
via a192343d161a9d26e433be2c59f913997bef701c (commit)
via 07546be0261a1371bf2835d0b3a0d6aa7d048074 (commit)
via 2890dbb828e5b1d94ee5c5625080d7c8c074acb7 (commit)
via 51e85a3730104bce4eb047abdefa6483a4fe0f7d (commit)
via 199db30d8160d9b69dec5c123edaf91357737289 (commit)
via ce6482880d25c387923fe191c6c939d3ffea7b23 (commit)
via 3458712352aa239bdd987603e6d6919ba84cc55c (commit)
via 27966638a14159da40f834dd03d88e998efe047f (commit)
via 70585d0e35c4470a98a6bcc0d8edb9d89ae85405 (commit)
via 6c330ea5b74512542959a48d8f6961137b5b121b (commit)
via bf3da282894a7a24ceaef54dcc6428d1d0bb1014 (commit)
via 81ea70894cadc64d7a658eae7ea528675114377e (commit)
via ab298c36948a1d247f80fde422ebf1f6b6d2412d (commit)
via cc63feef8fc8d5f4c14547d9066c8c94d16a634d (commit)
via 484582d1e801750bd2fca2361594edcd95539109 (commit)
via a6130bacc0bf0895d3d018eb5e5a735964bcea4b (commit)
via 963fbfd681c5b6189a35d09e507fab046171577a (commit)
via 805519d7d7ab5f1777cc4b078a0c3342f5e797fe (commit)
via fdc2c41b8a4541e2aa72a1760111dc34ca74b884 (commit)
via ad3f6dc02e0e6d2a60a1bd90895292a42b70dc2c (commit)
via 955a5de8dde4039f116fbef43320d2cae04ce5ed (commit)
via a733d4c39a239930902c810bce751028cb39bfd2 (commit)
via 5358829f7b4b63880d2265b746d0321db3bd726c (commit)
via c6b91c2e230eb099354561dd88e93f97df223f16 (commit)
via 40d5e80dcfc212b8f708b5bb0df7d0f554888b13 (commit)
via e5014644417f17e009f974ae44cbe4c88dd33116 (commit)
via 29ef44f8e017412d339d1d87bba3f9ec100e2f65 (commit)
via 78144e38d1c6bfff16fe610d398bf6e15a432e4b (commit)
via 186097b07fdad1af80ddccd3ccf5fbdca5b81ecb (commit)
via cd266f9231c7466de4d1249b72bc0ffd4fe8f586 (commit)
via ddc4ec56ef0df074035fffec8278fbf4b141ac65 (commit)
via 203152faad9ccda93c9f799ef48c47d22da1d3bf (commit)
via b6b9f884f331a4f8726a3cd6a0f3eed7870b7cf8 (commit)
via 064529e8c0586386b1b1e5760deac453bd8e8374 (commit)
via 70833edc115fac08caaaed0f3782c608e6b12cc0 (commit)
via 12a331dc3dc5e5b98610619683bf5ffed50072a2 (commit)
via 8f985a64b49038675022f323f02759f651362908 (commit)
via 89c1175ec43bc4b87ed93745c306dca41e9b4e7a (commit)
via dc38532ab6fba33eb32250f819d518506efc8184 (commit)
via bf7abd12d316be7eca6340c12adbf0acbfa27f1b (commit)
via 5efcee702cfe268615733f30988abca319089531 (commit)
via 46d7cd5335f164910a205b02aa12582cef841bd1 (commit)
via 8a718fd4d8d2def906ddd9474e061bdda7df057e (commit)
via 9abd2de988dbd33bac4149e0d2cb1e4fec55413e (commit)
via 4c409c2281d8845e3590d5299e425fac93275d06 (commit)
via 182fdca6e3fd6b7e2058f42fe35fc5af6588d290 (commit)
via 9d0b49c75b47fbb0d8542658cb78d5b7cab8d95d (commit)
via a2bcdf7b77c0618ee4ba4d257be9e854e1888733 (commit)
via 5269121508eaea6f3e1ecfeca995f92c69750473 (commit)
via 0ba105610ca1d89ed1cf186dffc9c3fe442a1c6f (commit)
via c06879a025c79810f12b673b59a0f479e6039acf (commit)
via 90cf17371786d74ddcf793bcef6a9808edc16066 (commit)
via f0e553bcff50ab498e06294ba0ed449f65a0fb73 (commit)
via 5a7d082452848daa9dcb0b023f0829d457d8bcc0 (commit)
via a6f3e21d917a737162cccaf3bc8e66be53bba89f (commit)
via b1a824e0b7a8f80f598ef65b141eb93d72266313 (commit)
via a3c44e253e0da68fc59662cd38cf02fd75b1b8f8 (commit)
via a80861a90d4c0b22aa5b7c2505689c201b75bea6 (commit)
via f04d4835e756f6ab62420ef5195d19ccb3434783 (commit)
via a414307769e43a70d7aacc69ed2d21cc4344eccf (commit)
via 05b245d8e2a54628517654d3ff68c26bb43b980e (commit)
via 03d0f3f523dec6a220e8551ab52b91d4b19d0760 (commit)
via e29c442e9bc436faa296aef1bed07a843fcbfdc7 (commit)
via d561c905f2000df4cf63f1d56193aeabff87f362 (commit)
via 8279d21d83768f2eb42d308b5c424ad5d4992a7d (commit)
via f369da76382b7aa83b5c867418b4bf522e22897f (commit)
via 88316252c6e644619510256df47e85d912d75cb0 (commit)
via 9f10f3a78b626ac207103b678a6622788f226bb5 (commit)
via 751cd32785cfebde01ba63fec2f2b53ec4e562b3 (commit)
via c4ed9d019fe78b4f9cf30c6af3610d5896638b22 (commit)
via 289a358d8a2d756f3e229e012bebe77244d21690 (commit)
via d706825b2fe3739d3dd792ddc2b9ed7f1f86fade (commit)
via af0c29076835b9838813ae32f71dcd8e7560482d (commit)
via 7136134b3695edb4755aa323aa72da8467408886 (commit)
via c79365583658a998be89ac562ba406828c2927d7 (commit)
via 9aabaa391bf5de6cd733297545cee2bc74802f09 (commit)
via 5b25a47dcf50926259594cf704f4c898a6838703 (commit)
via de7e2596fa84458fe89f723e23a6b79da9bee5c5 (commit)
via 7ccec239ed0cd5daee03de340b5ba31aa2e11d4c (commit)
via b5ff3f23c1fefa1343e308f6db74e77f528f0d0d (commit)
via a4fbb679ee5b917dd25ef18e5e6aee416c0bbdc5 (commit)
via cf32a344e6ed4eb7b5f2fcfb5c4d91a2d972d7d3 (commit)
via 78f097fd5764cf5931405527f7dfff8c99022f0e (commit)
via 9374173b4cf4da87f3af7cb6fe2aac6d59fc6674 (commit)
via 32e9990ab838e4425927aed4dfea951ca722ec9a (commit)
via a2f96dea2a968b6a48588bbfbc9c1863b5e2f7ee (commit)
via 29296c5c5787f09a773558934c737a5ee65a92d6 (commit)
via 6484764f7df92b3f9d88b2718057d8de8c1d58e6 (commit)
via ff385d569e9a9b13c3c174b7d55f1916c75ce1df (commit)
via ab923703f1b754dd3546adb0705bc3b75bc2b929 (commit)
via 30c2e8884f2558ebf4b263fbf7738738b2394412 (commit)
via 442a62a4459f86b879589d4753f889272da1e0ca (commit)
via 38b34aa2b7d1d5185f63113d715a362b6d0d23e6 (commit)
via 791ff12b3df0f4032cb967622396efae1649b021 (commit)
via 6121f4b09e65c6b84c31793c473513db862fbd96 (commit)
via e1072235956f0a43ebcd4fb68494144b03f7d1ec (commit)
via a75ccad615a4637fd7eb30b52f1324f31b118509 (commit)
via 5f581c32a3904456430ed331151f003f3bf5dd2e (commit)
via 538b35e21c75c52701313bc94553cac846b8d7df (commit)
via 7987955eced0b40473bc65a72acbe86f86a41f1a (commit)
via 918ebce3b4518c8b10482213cc6c29981d7d4276 (commit)
via 17684b0c3c8dae44b22c9f69945fbd4b24faa9bf (commit)
via 11b304656bd1ad736d7777bdddceae2625ff5974 (commit)
via a8312db1d0f41a701abf6adb34f343d4bf02198b (commit)
via 98df087feada0ed8cdce15b2fe94ee8f77434c81 (commit)
via b61101c797d8140b9404cc930b307668d6becd83 (commit)
via e321647a82edecbb4cba0ed1a698aa4c6d3cc3a7 (commit)
via 9b215c0e03ddd1d6a12a20ce9edd90f8dc4dce92 (commit)
via f1a401dc35298b1720d4c0e75cc5b7fddd08fb95 (commit)
via 9d5c40be31c91d36e221ff2e8cfbe5e320c85006 (commit)
via 5d034fe6c389455469696e36d1861abe4047d007 (commit)
via b64a66545f337192977dca474e3055c3919a0b58 (commit)
via a33378661b735f20cc733312cf20fa58a2691356 (commit)
via 5ba94241b96d932450434e2cac2fae512a1e0233 (commit)
via af1eb5d05593233d97f1e142778ecd3369844044 (commit)
via 8a3fc03b8a43a720575d5c5f922b23b938baf32f (commit)
via 7099d4df871fb6186a64157c13b92b2d8b56de0e (commit)
via faebfc883f8886ba7d64760c2e0d3ed2645692bf (commit)
via 1b662b8dcab972fd07a7869b6b9108a8d128796f (commit)
via 74b6f51a3a112a71b26e9cad0558864af63f7c32 (commit)
via d308d15037f5cb69a08b348a1a91a503dfdd72da (commit)
via f3f2b1be9f723be131f2d89c02608a305be4a305 (commit)
via 1bf75137fe5fc75f56fe3100d53e237a71385cd5 (commit)
via 17e32022192faf733037c2533cc2dcbfc09c38cf (commit)
via 2b8a0c3773ece0f7eebb535b897ef397a97724ce (commit)
via f7fd282fa15e345d8395900e2cfdca9785064201 (commit)
via fa17460c2983972825dc9546d08aa1964ce37963 (commit)
via 3b0d4f6b5db8eadaf4a9d0a06ce0e337c6ed4e94 (commit)
via 2c54cb72a96644af7cef020ff510939d6845a092 (commit)
via fde995fe8b96c595e6a94d2dd4f455c8a4dc14a0 (commit)
via 391dbe2ae829796d957ebb55ef5119f909864bdb (commit)
via afa0f7eea0f9d339f956c3223ff64ebd01ef2997 (commit)
via c819b42e81247bb262c53e2d4a41a5b9ad917f58 (commit)
via d5862eba6199c30c0eb6d8a476268aabc6531271 (commit)
via 4997d3a19cb6380098027042d3ade4dcefdb6aea (commit)
via f9ccf457bf92dd1596ed53548a8ac13553fc1afc (commit)
via 837e5e5d84f85b5091d1d470052380278a136819 (commit)
via 2122248a51524e94f57cbd4c0f678858c807a0ae (commit)
via d1ac56213c6add530c6a7925a40748cede66b357 (commit)
via 88441f682d26a1acb506862c3dbd1b2ac6b2173d (commit)
via 7614c674367dbbb9d7aca95a1f3dbcef636f90c5 (commit)
via b1b01e6bbd53c3bcd705be43ddeec3255ba64bf2 (commit)
via 15a5dc303e142f8233e76f14d69c1211f1b43b4d (commit)
via c9074dc33c95a7042122888256443bb9e7697c26 (commit)
via fd5a75640432ac794e14e4d8469442e0ee78a67d (commit)
via f883cfbdc9bac9dcc46966d2b54e794998b943cc (commit)
via 8498369cf0c40604d994eff541815f446a390cd5 (commit)
via bfdcda657cb27347691eeecf27e3d2d20c3bd9ec (commit)
via 196beb7d583a75ee96d6b5f787e7af5f1dfe82bc (commit)
via 9d98002c0372083febfe7138b98179252de7dcf6 (commit)
via b71aa484f6a5fea4ef37282eb8220e0f1f3c669b (commit)
via abe0b52983869c8cb0a6925d833fcfb5fed46094 (commit)
via 7821901528d3f8adabbf6fc530c245dfb66d8ed4 (commit)
via 6d6a37d46170aeff879e7f194020719ad417b4c2 (commit)
via bf493dcb82494c7fb0ab5b9bbad7040c1ee2f7af (commit)
via 41362fbd04c53db9fe65929784424e394487438a (commit)
via 09b5e89b3ff80c5c77caa7febfedf6bd4ddc60ff (commit)
via 12a1030e021d1ac0835b73eeecbe9611ee7d205a (commit)
via 1de3838b24e7894cff64682c014897a8ca5daaa7 (commit)
via 7e0a51e98719f4d28dd6eedc74f19f8733928b75 (commit)
via 4b5490d406579ad1ad1f3c784f14406d920e3da3 (commit)
via 007be5adf50cfbc0863cf80c6444c43e42f95594 (commit)
via b8ea222afc2852cec67b0ae6aaab00646c620b5c (commit)
via 68d8e0fbbfc2b8f2f4b87eafa1301e98d08dc667 (commit)
via 4d5a92759459582d8aa047c2442b9259200275bf (commit)
via 77bbb1bf190bf8e0aefbbd0223dd46646beb30f8 (commit)
via f837cf8d55fa483f8c86b1f1b28fd116f5e0f16f (commit)
via 7f8e3a945da9be7901c1cbd652ee824aa3a00b8e (commit)
via fe0e648be5de8f7b19f0506fbb79843e89041d97 (commit)
via fc2977babdd52a4e6c48eac2df1d9f78765aeb98 (commit)
via c2d30a881d48536dd4d7c0160065a783010ea237 (commit)
via de4ae8a0235a4a2346a4db64140e6870b5c59640 (commit)
via c37298b9522772a4616f76b7e59127518fb4fc0e (commit)
via 311fb16e59d8aa191b982fb12915e29d738ae46d (commit)
via c4239d1c0f4cc8308dd904411f1709587ea14a7a (commit)
via 2744ad516b8e21f304aff28471f88f4ad7dba519 (commit)
via fe06277e37c1957a6c3752db1786d79eaac11f06 (commit)
via d526a6e74ef3ba9aad42853bed62bcf008251d70 (commit)
via 4c538c5118e206bd3fda488215bd7af4fefa62ea (commit)
via 23e6b6ecb48e69819002a5dd588ad81cc274f699 (commit)
via 0b626b87ecee7c1a33106612009b183a8da8567c (commit)
via 97172cc21fb53fe27475a17b4d793e5274c81927 (commit)
via 848fddd0d4ec29358ce9f1b0e0900cce0ac01f07 (commit)
via 2d263d9706576e88cfdc05c11652cacde8af3f81 (commit)
via bad80c53a54a342cd7c8079cc17b1d671b82aa5c (commit)
via ff146176d6946fa1f392fdc9330e043a7ee0b9ad (commit)
via 81e9aebe1d113d99f50deaca9815dbe3ca8da70f (commit)
via 2a02fd0c0dfffa86bcdceeee5540f7e68fc5a807 (commit)
via 8e5f623e1be9e792b372d8bff423d1974e086652 (commit)
via f37029389e4d4dfc8a806ebadcefb74a14d34df2 (commit)
via 28a00145ceed6ce544bb7d01ccc98e2a1be7dd69 (commit)
via fb41742d7d2c0dfb472c67e96f1b1cf7aa6f33aa (commit)
via 091781ffa2eebc61ef93b20eb03345d17c770aff (commit)
via f541cd3bba27b3bf41a0ca8e869731d5d91a8044 (commit)
via b0936aa5ae8588e0ffbc85171cbcdf49c5fec36c (commit)
via 977ca5983a40855f43125cbd19a419f57f0bee06 (commit)
via 3cc637ce6ffefdec27a6089eac7aa861028e60a4 (commit)
via 37d1c3896c54587641d2846cd4cf31eaa55a6156 (commit)
via 354c7cc33a4095e533801c540f78af62c2cdfc69 (commit)
via 039daebc9b0bd5866ad92acb2f16d7f8d54aa74d (commit)
via 91985854f2f52d106be9c4b2c55d7b139805b483 (commit)
via 1764dcff6c42fdf18b6c40b59c069dbd2aa3c8f3 (commit)
via 03dd78059cefaae27753f19a81d52bcf2920217d (commit)
via 8ef4d96f6e7213e68a85fe47316a71ed0f86f1c6 (commit)
via 1cd2164ad7707a2c128f1640261dcd37b2e02ede (commit)
via 93dd08df98c0e49f9114aade0f5c488b94c84152 (commit)
via eeadbac6e13da40fc1a33603a93e97308473b26d (commit)
via 8b6addc06ec6e5d57888af8a7f2742d8be2fd9ae (commit)
via ba058c9023a37bdd2456dc48ec6cba9034a5ecb6 (commit)
via 06ee4b94ab40b14196a99a9811253a61086a2322 (commit)
via 4a2f6d50c6083ce1ed0e467fec5ed8efaf648975 (commit)
via 3e05e0c8ed5a7be78e47c4c4123090a0fb3cbf44 (commit)
via c70735a22bbf2eb5faf0c204c495cd4bd35ca921 (commit)
via b3634f7c0e05a46fc585b29d6942af9facd3b0fd (commit)
via 5bf4372c4ddf6f89d4e449fb86c6b57e7b1c0801 (commit)
via ec3b8554a7af8dded1fb63c3ae854749056ff99b (commit)
via 038d1fcfd0dde1359eef1ac2d1783088f8fb0548 (commit)
via faa011f3c145cb8decf197182090fc312dd28c35 (commit)
via ae3304311401e6a978f5c56127e24e1a9e2f0efd (commit)
via e4bcfc1443914ab9c8621654c05042de89b79126 (commit)
via 3b0699d83d78ff73b94ee6e8e5d1e8d21f43fe0b (commit)
via a652352bd18c3c1d8f04c56e0a49fe58b7ac3c18 (commit)
via b39e15693c80f1e7fa7b0969b70e938280c98748 (commit)
via c3201cec5148f19b95945132564263e27b34c2e4 (commit)
via 8e9c9912d7052ce60cef24b6d213e4cde21cb762 (commit)
via 468ed141b73ce1b95352bcf1a4189b50b7a0896d (commit)
via cb2106effc08cea2d0576591cc2a0a2fe9e47065 (commit)
via 6dbf8b91037f9b39515c61775415702ea7dd400c (commit)
via ecba7782c6db6e90d73430fa3a1d48609b4a9791 (commit)
via 4861a979dbba14de2ef72b971ecaf6a94989e463 (commit)
via bb4c1fc2b222836c2f3e8cd4b2f71d5405b0ce38 (commit)
via 402c223760ad29b08c1309143512146a30ca1d38 (commit)
via 306cbcda6d22955e35ce579ee1079fa2719c457c (commit)
via c0c1cccc644c78c54c39f5487845fafe81f70eb7 (commit)
via 69f6e111f8f3dfa7c27daa73e0b743296e6c33cd (commit)
via ab0f944e4425265477b7a596ea45e3695ad9afa4 (commit)
via 81cfe6b61423ccb821b821aa492008b9533ebe2f (commit)
via 900c9940483dc593ba2feb4a80b51e11c1028979 (commit)
via c03594e2b7224ea0618c2753edcbccd51c3a1eab (commit)
via a66dd9815f6d2117e5168a2bb743f3ae929820e4 (commit)
via 0e880d4fb1d9afbc833209eb50fba69d32502ad6 (commit)
via aa8d028fa924a60612f5d314c44fd707917bd88b (commit)
via 3d605d9770e932b946b7b5dd0b66cff5e5b4fb01 (commit)
via 2d7e8185d4d72b1351b477b6f4dea4fb873b2f19 (commit)
via 350b9df1700a5a2204a2f1e2459b73993c51571b (commit)
via ea731a11da7234c180fe9a2fcf9bb7262c22e73c (commit)
via fb753bae04a563b03a8eb6ed7df122b723f5d6d4 (commit)
via 399a6d6900754df1f168be1b1eba2aa8c194f8ba (commit)
via b10cfd4552463194357f060ba453fd3c18e33878 (commit)
via c2a60ba2c23f2c22b2d22e291b0331e4d0e9d1a9 (commit)
via a5ef1c8e062f45c42d65c91f91ef32dbeabe761c (commit)
via 5c655d323beec857b3d5a777d01d50ffba228bfa (commit)
via 56ccb0230eec80905e6a01fb5f08cc7419f1b438 (commit)
via 41021f279af515ec35cb3fb535dbc4830628d286 (commit)
via 6f302b828819984ce6f857e0d0581ce59a0ffb2a (commit)
via 330e485343c4f3054146046dac4b98c937c35269 (commit)
via 8f62d19291e51e89e389ab72840a7c0b6a9abf52 (commit)
via ebd39d114589a2194f74286b6a5fc2e21e9cbd6f (commit)
via 61b54505307c19918b63c303fe2317bd1d37c982 (commit)
via c7a28ca72941c7924229310d7813deb81bff342a (commit)
via 62dfb9288981e7f79e5502df424c9bc5c1d95801 (commit)
via b645455b862ca254d54407e0b65e02fe16a6d70e (commit)
via 3524d934976f7a432a7ac5d04bc2ccb2e954ad33 (commit)
via 9851a189c6456b6ce5054cc4c9eea10a6866ce0f (commit)
via 4a80ec9e772974f0f750a10f80b81cf030026616 (commit)
via f42351b03e318642de11d06b06721b1415985721 (commit)
via 4bd81e036d0ca1ee99d49a1e3b15c337ad738462 (commit)
via 4548257a1d70b64890433443d156d62a27fcc32a (commit)
via af9d49f3d6f4ab78df87eefcb80f6152162c3630 (commit)
via 0dc29002cf8eb4da2da0c31040477065c3f14065 (commit)
via 06fdc8c5bff48e8cd0fa093dce018af40bdaa668 (commit)
via 3531995a7cc34aa1e0b735ac4c5ee87086d1f7fb (commit)
via ea18184fc67d0a59a4fb22039c3150d882920ff4 (commit)
via 62de0b8dbe2c06a30d28fa453a92ab47ebb5d97e (commit)
via 277f631c1dda25ac7bf40ffdfe5b30f2ce64a81a (commit)
via d3f82fd13c9bb5986321e3ead3135ca34da4cd0a (commit)
via f0ac2cd691484ae2ee46f2243442a00bc1dbe1c3 (commit)
via bc95d112576c15c3746b22a06e5977a1d0d58293 (commit)
via a578b2483bd1cb150b7a98a166d7644d3879967b (commit)
via 5925fd66c37dde4d6a546058259158ae03c2e35e (commit)
via 77935028a17e469d3a05c23da67c476776319038 (commit)
via f373cd95de9acb1911a9c47e51dc71fbd75911e7 (commit)
via 8b07c253cbd28d63b41804426fcef5a878c4c8c9 (commit)
via eaa82929e079667b5d60e0023ae4810eb34f12df (commit)
via 81769906dcc59c6ba841436d3738b800c415c579 (commit)
via 6ed83b7778ba4843f86368b4f4b59c34c9073682 (commit)
via 8b654f84bca4ae4547c82c01c6ef56b7e394e461 (commit)
via 1bc7461897198c84de68ff3735b972cc9faa1f79 (commit)
via f615cf8d49587c90df3d9dc39b66017989089b5c (commit)
via 85dc9a0f23dba1a8e6dfa0d5b66362fe16a1a9ef (commit)
via 1bd7db1b7594a9a2dda77989dd56656820adf8e8 (commit)
via 9e57f9633db967775586192c2cc60b06111f82c6 (commit)
via d261379d30fb084a4d535ee5ba834a00c60ec2aa (commit)
via ae94fdd38444c040f798d42a3bde88eff0ae208a (commit)
via 6c9124503c845f1b74e7fb9221add1a95bb6c907 (commit)
via 77f7b00bba4782861c501911cc3f1485cc8f3aa3 (commit)
via dc9725fb56b9625c83d368527094328e27f3efdf (commit)
via 91736af32995d6292cd82fd2e3a5424b68f1155c (commit)
via b84dcb714cfaa231e28f6e9c3c071d26c5459004 (commit)
via a8acd781da18dc988fb95209f3dc5888420c7b0c (commit)
via 7c14908b54b0c280f7e8703e1457f41d883ed22a (commit)
via 4d45442ed88f185e78703883d21888540136f3ad (commit)
via 9ac2d372a9ea771fe42b241bf8e887436d046651 (commit)
via 6c3ffe1c880656651d6efe8c4b924bef8e0fbfef (commit)
via a0dca17bddab59d2ca5127fe8391455c4fdd7aec (commit)
via 47a975518c9f5910867d0a4388144ec0290f968b (commit)
via 8e451b08f74b4a663e170ddb9c2d240e8d50e5b0 (commit)
via fe058b1fbbd2091ed1817c26b7d0a82e0c8d0487 (commit)
via fe68c56d4391892769ecfe37e661242ee4a9267e (commit)
via 05cd3092c1a79a422cc34741a2a9149259395ed4 (commit)
via 95a25728acac79139fcad8fda55941d046146e7e (commit)
via 145c123caa189921d757602f8c39c7321faabba0 (commit)
via 09c047417890fb5859b6120156062a2acb9b26e3 (commit)
via 928f33477be34dbf79338b8fb551d63abd394af4 (commit)
via c2db5f7eeadd88e1a546b749fb014e55570c27ba (commit)
via da0bb5777f2c0c981ad67ae14cf04f32a2c776f8 (commit)
via 23a2e4f691880daa50b2ec66059d3e770e089899 (commit)
via 234c924be529b9c6aaab14258a3bb5033038f9b0 (commit)
via dadd2f7f8f55329167168eab4145efa7b5d87b3f (commit)
via af62c6c6bb4398a6853f465ba987c843a668b65c (commit)
via b01cccd5fb3e368b68e9f8c191bb61dab5a58689 (commit)
via 96a97de26c4c69f76b5f6353a54b06960d0a9d17 (commit)
via 48f199a7979339a174f9ac79b8e0ac11db02680c (commit)
via 717cf89645ea1da344b64ab76eb5f4471b39914a (commit)
via 7da40bab8bfd2a7d4c7767693ef84f403c56fee1 (commit)
via ea4ec423d2e49a027dc0f2ce3a0ed6e465d0b27f (commit)
via 71e2459403c0fc432e4519249a16d7292f1ad089 (commit)
via 6eb9009f87ddfabdf50f882ad31b220b9be7fa29 (commit)
via 1ae6d476f4786c52c51896f12baae39ddbef06fa (commit)
via 243b5644870e5e5caf65b3487697b1c4e681ef9e (commit)
via ab83eac12ad869f87a75444a4c482739563bd453 (commit)
via d252a6f487ac84c7fda9b6787ae348594cbbd10d (commit)
via 28d9fe2640d9bb9a2cd37c8415710d612a94ac5f (commit)
via bbccbd2b63877931da795e8865fada2b4df42a0e (commit)
via b82835940c61b611121b9f3dab3e599ea5e5c18f (commit)
via dbfbbb2b32c0b9147ccf29009ad72196d9f269c7 (commit)
via e3f0ac33fc47ee80ce838a8fcf73d9f2438dc4ca (commit)
via aa71aa7ecfde38a46a05d4ea22e67ae975ae3d00 (commit)
via 57fa2206e4e6a50d40c4937c82655fe5d269a0ec (commit)
via d2a23b4636d4441f63224e3b6abf368409f35712 (commit)
via 56623f9a4cb347381231ac5d37436728a62f55cc (commit)
via fb7bf9b78c326022916fdf340d753024f58135e3 (commit)
via fe2cd9cc0abb397f22d19148f39e59d5822e2867 (commit)
via ba14c099c71148d08e92665ebb45eac3957edeec (commit)
via 342c20440323c8476f5fa227911b59fda215d712 (commit)
via 5f8501ff15445025f3104ca33e7f0194273714b7 (commit)
via c6504f7523c41b82a38748f8a78c318350ba78d9 (commit)
via 2278ce7794b40965184c2e5f3f23ce53bf0fc420 (commit)
via 31277816bbf143007d57b476943126930cf0cfb4 (commit)
via 500de4b34bcf3780bd54f9aba9b3ce8231e56175 (commit)
via 5d9e3cb66a70b09b4282139ff9225aac5fe6bda3 (commit)
via 26422e6b65bc20a805ebd274104325c049aa2bee (commit)
via ccad65107f0a5177f8de498735a7a579dfd33a56 (commit)
via 96a12cbadf5b63d7965b8f093b62d9a8680f0322 (commit)
via d6c44cc68bf1ed5a69a6085598a607e1c2bc18e4 (commit)
via 0309a0968bded98c042976c1e31b5eb52b005af4 (commit)
via 6b5cebef9345566fd96a91a11562b43c892103a2 (commit)
via 9282b638e6dbe7dbc2d26b955a6de2b42434a287 (commit)
via 8681b8636ff462920f9ca3b663632226e12d186c (commit)
via 73da73cac2f5c789db66d8c577cd5ba2239f03fd (commit)
via 64656dbfaaf8925aa6b4e61ba46127f54807d1a4 (commit)
via acdf418345d2c7d6703fe9278d597fe84a7271ed (commit)
via 1b97400f51539a008ec5e7e88a44bec9fc9b8bc9 (commit)
via 1d6285dc4347631ba044646b1fc331104c1c2cb4 (commit)
via c9511a8581461000cb7dd1de346a07c92e70a102 (commit)
via 521e6930d34e4d46a7276ef57084a82a77761896 (commit)
via b98533b40803048177d63c6754594b907355bfa0 (commit)
via 6f06fa88d5461fe20de760d0e4302188ebfe1aae (commit)
via 023228a0c5fa243eea82b385edfb5f389ec86fcc (commit)
via 3a03b35941eb37f2542ab022b63e4401574bab7e (commit)
via ca730c45a50b61fe572e4317f753fa7c08dc4df9 (commit)
via f4e7402099334af23ed88a5217081d2f65461b40 (commit)
via d096cf54e1bfd7099a7a057029269d4edd076418 (commit)
via c5f3332cd33a676b059fc3abd2433c700a719490 (commit)
via 3c079171576f31abf1cb5ab54f6e93d548a82ed4 (commit)
via d66118211679b98f083b640e550c6fe42bb699c8 (commit)
via 2f902e9e3e6985afe5613858cd12a253478579a3 (commit)
via a54bb067d334a82af961628ff4a94bdf77424944 (commit)
via 47ed4839b02626409d45fe62a44656c087a54927 (commit)
via e24f53189ad6e1556e52519f5dfa7a8b2f64aca7 (commit)
via 4612dbcd3bdf519e6321a23e824d6b46a51c2415 (commit)
via 6eef389c117bf5f81236dde13fa856c386143f0d (commit)
via 30c9e302d92544906c00499626993eab0c67dcf1 (commit)
via 53d3349c4bb27cf94eb06270725c9e2bbe3772d4 (commit)
via 3a60ac35af92d496c2435a5d3bcb318a26016741 (commit)
via 557ba17c6d0cc02747264828737b3ef743beb2a1 (commit)
via 5285361e18821f475d2231aaf00b0f364d88a8d5 (commit)
via add4c9ba37ff91ca5e53d2c40fbd30328ea39158 (commit)
via f62186e88cff4e9ae4146ea939e52330e89f41f6 (commit)
via e7e98ad31f45c56d259de6b81241388d32fb2713 (commit)
via bf4ed5396e2cdf6a382bda0fcad716fcc9d0ef61 (commit)
via faa0af464f4137775204abcf07404ead1b5e0af3 (commit)
via 13be464f5ec1c99a64354614730b9e55b8d5b855 (commit)
via 26c6fdf210f39559bc52023666ef80487a4da7e4 (commit)
via 9afb57662b0ef2b8d297bec81ea60dfdc3fe3b12 (commit)
via 697ab66d05b02350351f5f58e6706cf4e4c5fe0e (commit)
via dc5ef2b08ea8a73c1c02afe0bb332c9ec9859109 (commit)
via 0233ab566fa4a713f26b34fab85c2bdd2943101d (commit)
via b6c90de16d8033130f33a980b1c767e637ca6910 (commit)
via 3ebf78ce7ec0cfbaff2ebbf3f1c147fbdccb6146 (commit)
via 7a8a7f35b822406c7ae0c5a9014933084fdf2162 (commit)
via dcf3f92a3164b9e0c0b36547705f626fc4e1d96a (commit)
via 1d36ea0c2fbddaac8d3dce7eb8cfe296160fdf30 (commit)
via 7a8ebc83803e953bacae76b29b6b181709232761 (commit)
via abd1f76d32fb70d23e1c6e30dbaf7c831c9a3eb0 (commit)
via 7097d7cf7681ca9434303833f2c04afb68972471 (commit)
via 6ad0a21431aeed34661aff56d081d1ee241bc082 (commit)
via 9cad7d0bdd7935daa4bc092a8a1a6da9f3a71841 (commit)
via 3b0dbef9c86af7de7d38343db65a3cfe764ae570 (commit)
via 0f14d7d024ee276c52e6b213fb10f6969f2b1563 (commit)
via 1684d297c5d6c8a8e9fa61f9173cefb2628ceffe (commit)
via a9aa205ce958e80aba29c06b9b48dedf1e4c5e6d (commit)
via 944472604554d3c7d3ece792c32c6d730f5353e3 (commit)
via 1c4e89f8e1fcb541dc4a438ff1db5ec967eb8c2e (commit)
via 4ce734286e68a1e7d533e247cde794b44ff3f0b4 (commit)
via 897fc89b97927f9dbbbaa2dd4d1a39b8101ecaf0 (commit)
via f7fec300d95765b39e3f021fb2e0a6ed848b8167 (commit)
via 24cf0a095cc93409bac6532c96fcfb5aec001a2b (commit)
via 46e8a5ba6e64bdcff23fed3debc78b2a802b8fea (commit)
via c036dede063312b7c42c2b0a5b4801f00186b078 (commit)
via 558ae9436606b5702c013c308c36488df102724b (commit)
via fc971f48fdbb5883c106de2a463cde0e15a40686 (commit)
via aae3e976a4d68519499ec62b25c0d7456f354f8f (commit)
via ba058244f3cf3e8aa169fa2ff298d512d2bcaca8 (commit)
via 4137d05fa77925e0ea1334fa98706388d9912d27 (commit)
via fcddc7d5a8c64117275f775030f4cbd9177a5337 (commit)
via f0fc7d131dee861c629e7497ace76b2584f10e63 (commit)
via 22ba7f4f95b2572a4a4ddb2af1e7472a89ec466e (commit)
via 98aaf3b2d7bc0010536beb2c90623349e5da11e9 (commit)
via 888f88f112d5ba3a55933f0f3e0b3c7580f523bb (commit)
via 1bd1f1e59ce3debec72c46c0ca9250202b6a1297 (commit)
via 1119011034689f46c2f832f9ded05a741fa26821 (commit)
via 7424ff3506a6ce48e2aa0e654fdb0b18fc25a576 (commit)
via 8732968b14a35319d76bab59632faf55a5aca5ee (commit)
via f351c6b436cde9207dee7896c9ac464840f88b7e (commit)
via 3730624115d3fb257333cc9abb93666fa6f3e913 (commit)
via c5eac373c604bd076fd46043e04e3060e05095a4 (commit)
via 547358f2d233fefc31f43b995320f99105894804 (commit)
via b01b291f8cd4c7cabf04bbdc4ff058c02dbd590a (commit)
via e0dcd56b27870d102aa5e699d120cd35aa9d2e45 (commit)
via 5ad185a55cc3159c1c7bfd80bc8a75f526075da3 (commit)
via 7ba125969ac6f6d61c51b6ceddd25f164a7add27 (commit)
via 892f7260e06fdf8ea766e3d522e016d33a53d774 (commit)
via 76f00404a9478eb6adb5784beb1f0c7cbba3465c (commit)
via e6d8478ced2ecaa8a3bd208a4ec512520dd4abb0 (commit)
via f5045f8b0890217bce718fae6ecbdb33ec497ae2 (commit)
via 8ba69623d8370548b35113955c08f73b4963ee90 (commit)
via 22bc6aee3ff1f2b66e620c0ecfbf2003a7caabd9 (commit)
via 437bf78ff9defe04dd677174fe44efb9590c5f18 (commit)
via 8659d981fadb01a8c9589a981f8a3201f3f5705a (commit)
via 0f4cc5d04e42344d27c8299f2c2c359f4e638a53 (commit)
via 3210721b50acd284c1818970a85e3e08f4b83788 (commit)
via 3380af190bbb344d11fae33bc393ff097d76f7bc (commit)
via 5ebb4759f737a5079691c2e5341ae35147308d3d (commit)
via 397245dfa9ff0bdb9879ca251529deaa0a36091c (commit)
via 6a35dc76d5451d309e67715b12a1fc1951d9fc45 (commit)
via 0b32da17911694970db42d377bc1f4f588e966a6 (commit)
via b282bbc2161ba62ae9002f0d4b6ab6fd3760d964 (commit)
via 68e816a184a38fc57e881a5a7d4fd6e82c22bc6a (commit)
via ef2b7b2c3fdc04990baa7ffb61b7df09eb8cbed1 (commit)
via 45de3b479df9e9f6d214539efe454fe9163c7006 (commit)
via 3bcfc34b99f3e2ca4000eb30570bde4b8c9f0a1d (commit)
via 1652dabf3e5684bfdf9e3e3345463f9bee1190f3 (commit)
via ae929263f4cbfe5bdec2acad4385876e5a0c8c22 (commit)
via 350f287d1b741413a5026394838d1e85eaa84bbb (commit)
via 5f36a6494eda8e3807897cd57d1f929afc903188 (commit)
via 46c0c2faaaa2427a5921af2d0924492bd7a8f8f3 (commit)
via 7c68cbb3d60327a244275ab0a879ec3f2b1b3cea (commit)
via 9e3c10f6525bc5912230a44cbb950f6d72d11c2d (commit)
via 4cab82f2a782c3abd210b0cb9db3f8ef159e9f78 (commit)
via 56acf36582eda8689efc836adecd970a59ece1a6 (commit)
via 8f823b9fa00b34b007f4520f26d395419ee45f3f (commit)
via 8677186ac4f77e81ebcaa74fe14476aebc186a85 (commit)
via 0963188cf1d7f5c40a5eecfb6efc6abfdf5663c6 (commit)
via 81d271cea4dfbd66339eb0f9dc7ebf30aee5ab86 (commit)
via 35eef4b40d6653442549045dca0c02552fc09465 (commit)
via bbe84798a9af2346c7a75b4985293a802bd87a71 (commit)
via ec67580f2d3199cf6b02accd1d980f120c6ad2fc (commit)
via 3720e58fd9c6a73cf9884cfd5d34e325f0d76793 (commit)
via c8df23bf4eb84bdca1b9221fdf7a1d389b03e66e (commit)
via 0c6ed3db6d33354405d5136e8d2ca9e7e9f52143 (commit)
via adb04f0a5630ed4e88c7d469af0fa5fcbc2f61f6 (commit)
via 6cfc6a4854facb1e5b22aa8e29415376e342de75 (commit)
via 3f191da3a70eb1775e9492bbbc16167be9edea9d (commit)
via ce778a065cb6490210462d4192124bcb499a9331 (commit)
via e362d1a7457017199cc587105589bef120c9ba37 (commit)
via f38707f7eac704361266df16a2d5da56b7ef3815 (commit)
via 42fd38b8bc64c27e260e1acd0f956b345becf5a0 (commit)
via c28a86981ae8833363a80137ca00b9c94a6246bf (commit)
via 58af7fea27d9b59383699f876bb36b1c6db7368c (commit)
via 88d0f234443ef02e390ad0d00bb9675621805617 (commit)
via 1cf76154c6b1a656de43768ff47783b534e93e38 (commit)
via f7c04626101729366cc6d137d21dc4fe8591dc5e (commit)
via 4df2548127dd9b60e8d27c18f498b3b6bd18f4dc (commit)
via 36a9f6f87f25afdbad88e457987daacd57a532a4 (commit)
via 6e9430a2a850de2ea2912f2f0882bfe5cb9aafa3 (commit)
via d245b0e64917ab7a5b6c37218bfa5c3ab9e34ac2 (commit)
via 01592fabad5c925f9deeeafb6627db9f8f10f84a (commit)
via a27af596bb6de362f0dd801bde8c2414f5fa4489 (commit)
via 794d61e65659dea5ee45f7ce5031b989aaba9cbd (commit)
via 103706279d99ed204c333c4a5b7e6183148d54ad (commit)
via 9a316ebdf3e0bcd689af656223e2696248fb43f7 (commit)
via 105f74cdbe40917feb9e7a18dfd57fce24dfd24b (commit)
via 734a26831682a5d62523f5c3bb8f1d384a9120cd (commit)
via 77a5a8dc3111f1cc4ede61f9cc1fe75dab6cce30 (commit)
via 7b923dc94fac6399cf0ef15f4a5c928fd685efa2 (commit)
via cd8167307caa2bed38c36e42024ddc38cd414c30 (commit)
via 3ab8d9af58a0b797acc4929d2f211c0633ff9d00 (commit)
via 921de30e4a64468b4a5ccf82c03a7dd9c1d1bee9 (commit)
via 14937ed405a038a4572c5d61c0176c5df95cfb8f (commit)
via 876ce768082b283ce6d8ed2f551f16a4140edbf5 (commit)
via 49fdde93233edf9ab85e5bf90a02c188af16c3aa (commit)
via b72efb554825b64b171778053a824e3c56a42fb7 (commit)
via 022d80684ef893f2fc20a3d05d6552be49858a11 (commit)
via 71a6b941a1ae823acf98aebf79e2d1f15720ae54 (commit)
via 9f52917c76806fd46f16ccd32fd2fb5411a3af59 (commit)
via 359efa4b2178e6929e9226fdf1bce782fce0c002 (commit)
via 50248a257c0f942e4c560945597562de0d341621 (commit)
via 59017da8c2a7dd1dbfcf0c3fa64f241cb93be519 (commit)
via e3fac0c7ba21365909fb7467f6634cc80dcd93af (commit)
via 3c29b20ed01ec01c30aae563e96e30d1b3fb8ce4 (commit)
via 88effdc1b00be8636d40ef84f5556de60013347f (commit)
via 07111640e6827ccb8623b86e4010e74d363c73af (commit)
via 68d783623a56bc191eaedebf8e549e37e2666613 (commit)
via 99c32de032dc19d8b3b0398af44756f6b4258c01 (commit)
via 2aaf61c6cb47d77b553de4501a0d518f73752934 (commit)
via 7fdc74ff3aacbec715e2f0850f59e90cadf48c50 (commit)
via d64bf0a8636f0d13d13b00040684a4e7dc08d092 (commit)
via 5049b428306facc08f36d54cd8e46609a004b079 (commit)
via 4c3ee31b90f56c237e44fcf03238241ec4411897 (commit)
via d332e57dd221f25e30d5b407ed81b30dfe79cf9d (commit)
via 915d4cb62271d2b4929ff8a69fae453858e8a79a (commit)
via f8e50d09e6f8a51b994e7c0818468cc3b463a2ab (commit)
via 74c7fed15cec94a1dca2bd4d51df255560a5c4aa (commit)
via 87b73e10d2d32d11557c2780032af2e8d677fa20 (commit)
via 1c5c9eb96da8813f3f088600e186e5163d3f3ec3 (commit)
via 0e4b8f146128be0f3e9078d54b66dda1e5057a67 (commit)
via 1e3a58c23f510192b244f7151f3f2e0d9ea62e0b (commit)
via 79d41cbe19786ea218897b4d5d4eafe72d73a32f (commit)
via a73eb2ba4d2e2727ea886a07a92586a422f94244 (commit)
via f467ed39189ea3d735ec85637a50905b532e0a70 (commit)
via c3e4332096be47052f4fd2d3c44c148aba3b98aa (commit)
via 645f3474baa1391604700c054bc640765f8bd10f (commit)
via b9f69a785b5dfd2a423e21abc2af253615d87d6a (commit)
via 3d59979fae9e0b0502d6e3828db7a60dadec25c5 (commit)
via de6b350f9a35b95b925d767dc0a4a56746488048 (commit)
via 3c7604d41aa6d7ba34ed4038616233e531933c87 (commit)
via 61048bb729e653f46bce43d71ff37169817dba8b (commit)
via bf38ef9caaf188853d189370997c7691151ee64c (commit)
via 99c244d99f360fafba975d3ddcc0e8b91218a862 (commit)
via 7b5b7df0fb70e64900e7f638786e17b6dbf008ea (commit)
via 9d2c21066d66d5bad16bb8e5da6508cb04742dd4 (commit)
via 57d1aa5d3d357d6f6b61f1ff65c5877ff39140d7 (commit)
via f052573e0ba42bf3101374a4553fb7ed89a2c891 (commit)
via 3d9176ffae3e229382854e1d1e15c07c12d94d64 (commit)
via dee435795570ffa008b8dde51a6e002abd02f4e9 (commit)
via 3fe5cad035f802b0c60e147d380f28170801a9b5 (commit)
via dd47ffa1cd714faf79c774e3c99b401b9d60ab21 (commit)
via 75efd7945c953078b8566534f918e07537c890ab (commit)
via a90482b1de0ebe9020e0d9f7e4459c8a9df4b8ee (commit)
via 45842779ed9aac6ce31d7b02a70b0bd9020ea5af (commit)
via ccc50dcc14544f03bb198493bf7e92dd9ab4e63f (commit)
via b8adc2e7daf68b4f7b81f1e14fc13bb7c203eb86 (commit)
via faa06b2445721a968fe461d6c8c5514f2aea4435 (commit)
via f67562a8d398249b815549b27ca308d90752cb82 (commit)
via 0fcba7b23429a7dc74a184e0c663f0c60a72785b (commit)
via 78adae1235eb8646cf4aafd60d106397f0b6df34 (commit)
via 6316f23eb30279bbb2ad59cb9a8d7e7ae466b3a1 (commit)
via f6b4e9192e59fd99712648b6d4ce727440ba148e (commit)
via f4214c33fe3c48f72b005e4ef08aa791dcf02580 (commit)
via fec9e5c111a2c60597c195c79b823cdc7785b7a9 (commit)
via db9ded2871a6d65120ce89232c2f09951ce2ad4d (commit)
via a57c6c2aa96621fe8469634089cd4ab4b22ba67e (commit)
via 3fc3a584d5efe1db03d5905c6002b326c7684782 (commit)
via 9d92e49c79df8b452af3a62384d0d84fb9c89445 (commit)
via 73d5beccc19e692c9a27f4cb8bf24575195ba0c6 (commit)
via b723ec456dcfa791841416df9597d67dc34cfd66 (commit)
via 638f1ba4731001bd38835bd18f4058fa7852f838 (commit)
via 385bbf9393cf24a2dc8c06198a1ca787e36c84c3 (commit)
via 894e61966d2feb0c34e0074a049a05677e7b2a40 (commit)
via 01415964840f935cc81db2f511ece6124d5fd31c (commit)
via 809ba1dfd94a4ae88fda20e28e5360506d3b901a (commit)
via 5df6d28711b99022aebc3802472822f168c3df7f (commit)
via f8e8b2de4e153cdcdae3e0169f62714d09b7b56a (commit)
via d7b6c25f6daf13f000609cf5a97451fa43f14b44 (commit)
via 80eda916f4d29e6a86e0664a8088e3fbfdda70c9 (commit)
via ba808f126f5249a0ddcf66b2a2dfb846f07c5a0b (commit)
via b259c66a5a0920c4bc68150bf1ab8ceac977e074 (commit)
via 169abbec44ba5c47b2747974f5a11125dd09a267 (commit)
via d567d5f053fbee4bdef5a06977390d3b14c6724c (commit)
via 03183718b8aec6ccbe1134f8415cf8d82c34c98b (commit)
via 9c4cc9eb2f66bf5472512232cc426714efb16710 (commit)
via 2295886f8bc922710e07461903a693c2c0c0ecdc (commit)
via 5bb0f18a3afa6844092d59201fefdae269e337ed (commit)
via e4c54f133e42ef583c4c9cafecca02ec358e48e9 (commit)
via 63e6e8dbd38fceb11cd9b960d196fab27788c8d1 (commit)
via 125ea02a72bc3a7d572b6210bd55d2f924b52696 (commit)
via 8ec2113abe93e369071ee5a61d3c58335e926292 (commit)
via e08e6404bea7d20487e8345afe6798cbb1dec186 (commit)
via ad41fd35a35dc72de033aef2ca9d4d911955b7af (commit)
via db1f12078c47035c6a908faf14e12f2a8dbdae19 (commit)
via 6b8cfc83c52a449cc38646a3544f1831f8fdc296 (commit)
via ea6eedca681db1264e9bdfdae449a5cef5d12aee (commit)
via 97d0d8527fa6e6895a344f067cec0570d390b5d7 (commit)
via 625cf7a1f5ab91d3837f28e1e3be068048e99d6c (commit)
via 70c0fd16e630b027709975bb8f0d73c2b5b30949 (commit)
via fc1213ec31971a146c266a4a2f618981f0e5d78e (commit)
via d3dfb42b85ef9a1e17028e98ef2520f5204d578e (commit)
via 8ed70e1177630c186974802e0029fb2309be8aa7 (commit)
via f0f818b27be500f71fcbc9ca13a17a01d6562e3a (commit)
via ad55ea2da0e4786857ee66e1a51f46b42e596a24 (commit)
via 9d353f1ce52ad4cd32e0f70f514dd3ad8c1fad75 (commit)
via 2eff5cec94bb1cfcee3f497309de81d851061c2c (commit)
via ced315637ae03754d02b2722fa483a276f0a13f8 (commit)
via e08892e97e66437a0d8ba75cc7ad85d72fa5ec23 (commit)
via 2bef8123f445d68dfb7ff06319a58b21ee43f854 (commit)
via 4cf1bd8a3477a1a7c68d0f7cdc7dedc4e9d40a77 (commit)
via 20ab020b51ac6f1c29e1c3d397d6f8c04e69c0c7 (commit)
via 9bc9618f8dd5175d1c85ddf7655d95706e62bb5b (commit)
via 82f08a5c729af2552b211118321fd91fe537f8b9 (commit)
via f60239a8037dac85424253457f8b0a89c784a02b (commit)
via dcad339935dcc011640c6d236978ce2ff1a298b0 (commit)
via bbb9277960f014d2d57c6c0e102f64e8beb62873 (commit)
via 0089203f35b84b071442dc53e59d78ec4ee6476a (commit)
via 816a8d3e8bfcef7ce8c25b9d63b9594c0beaf7c1 (commit)
via ca39f460da694b6634235d9fcd813fd2b50f4ecf (commit)
via 53a82252ed3d8ae79617164abac4e4236634b4c7 (commit)
via 7783f8a3aa1cc8a893b45be039ea0b7acddb0d06 (commit)
via 6b2895175e7861fe0eec0ec9eb146bc1d96f6618 (commit)
via d09afbc685ef805352acfeadeb9258fcb63ced2a (commit)
via 06fd85168207d9dac819c40e099edfc22f12a80c (commit)
from 22e21e72190a88d50dcca4c7ebf0394074d0adc3 (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 56b5bef538df18c95ad5df3aaf7ebff4464de7c6
Merge: 22e21e72190a88d50dcca4c7ebf0394074d0adc3 62a61edffac3ebdd91fec693fe2c1a94785ddb25
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Mon Apr 4 17:36:26 2011 +0200
Merge branch 'master' into work/serialize
Conflicts:
src/lib/dns/messagerenderer.h
src/lib/dns/rdata/ch_3/a_1.cc
src/lib/dns/rdata/generic/opt_41.cc
src/lib/dns/rdata/hs_4/a_1.cc
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 663 ++++++++++-
INSTALL | 4 +-
Makefile.am | 66 +-
README | 57 +-
configure.ac | 213 +++-
doc/Doxyfile | 2 +-
doc/Makefile.am | 3 +
doc/guide/Makefile | 13 -
doc/guide/Makefile.am | 16 +
doc/guide/bind10-guide.html | 190 ++-
doc/guide/bind10-guide.xml | 247 +++-
doc/version.ent.in | 1 +
ext/asio/asio/detail/epoll_reactor.hpp | 2 +-
ext/asio/asio/detail/kqueue_reactor.hpp | 2 +-
ext/asio/asio/detail/null_thread.hpp | 2 +-
ext/coroutine/coroutine.h | 133 ++
src/bin/Makefile.am | 2 +-
src/bin/auth/Makefile.am | 39 +-
src/bin/auth/asio_link.cc | 671 ----------
src/bin/auth/asio_link.h | 452 -------
src/bin/auth/auth.spec.pre.in | 108 ++-
src/bin/auth/auth_srv.cc | 473 ++++++--
src/bin/auth/auth_srv.h | 211 +++-
src/bin/auth/b10-auth.8 | 153 ++-
src/bin/auth/b10-auth.xml | 167 ++-
src/bin/auth/benchmarks/Makefile.am | 8 +-
src/bin/auth/benchmarks/query_bench.cc | 231 +++-
src/bin/auth/change_user.cc | 9 +-
src/bin/auth/change_user.h | 2 -
src/bin/auth/command.cc | 252 ++++
src/bin/auth/command.h | 61 +
src/bin/auth/common.h | 16 +-
src/bin/auth/config.cc | 347 +++++
src/bin/auth/config.h | 202 +++
src/bin/auth/main.cc | 141 +--
src/bin/auth/query.cc | 253 ++++
src/bin/auth/query.h | 222 ++++
src/bin/auth/statistics.cc | 160 +++
src/bin/auth/statistics.h | 145 +++
src/bin/auth/tests/Makefile.am | 22 +-
src/bin/auth/tests/asio_link_unittest.cc | 357 ------
src/bin/auth/tests/auth_srv_unittest.cc | 883 ++++++--------
src/bin/auth/tests/change_user_unittest.cc | 2 -
src/bin/auth/tests/command_unittest.cc | 290 +++++
src/bin/auth/tests/config_unittest.cc | 393 ++++++
src/bin/auth/tests/query_unittest.cc | 686 ++++++++++
src/bin/auth/tests/run_unittests.cc | 2 -
src/bin/auth/tests/statistics_unittest.cc | 213 ++++
.../auth/tests/testdata/queryBadEDNS_fromWire.spec | 2 +-
src/bin/bind10/Makefile.am | 3 +-
src/bin/bind10/bind10.8 | 56 +-
src/bin/bind10/bind10.py.in | 775 ++++++++----
src/bin/bind10/bind10.xml | 66 +-
src/bin/bind10/bob.spec | 22 +
src/bin/bind10/run_bind10.sh.in | 6 +-
src/bin/bind10/tests/Makefile.am | 12 +-
src/bin/bind10/tests/bind10_test.in | 32 -
src/bin/bind10/tests/bind10_test.py | 121 --
src/bin/bind10/tests/bind10_test.py.in | 649 ++++++++++
src/bin/bindctl/Makefile.am | 11 +-
src/bin/bindctl/bindcmd.py | 297 +++--
src/bin/bindctl/bindctl-source.py.in | 126 --
src/bin/bindctl/bindctl.1 | 70 +-
src/bin/bindctl/bindctl.xml | 111 ++-
src/bin/bindctl/bindctl_main.py.in | 138 ++
src/bin/bindctl/cmdparse.py | 56 +-
src/bin/bindctl/moduleinfo.py | 68 +-
src/bin/bindctl/run_bindctl.sh.in | 5 +-
src/bin/bindctl/tests/Makefile.am | 14 +-
src/bin/bindctl/tests/bindctl_test.py | 214 +++-
src/bin/bindctl/tests/cmdparse_test.py | 88 ++
src/bin/cfgmgr/Makefile.am | 1 -
src/bin/cfgmgr/b10-cfgmgr.8 | 18 +-
src/bin/cfgmgr/b10-cfgmgr.py.in | 37 +-
src/bin/cfgmgr/b10-cfgmgr.xml | 49 +-
src/bin/cfgmgr/tests/Makefile.am | 10 +-
src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in | 71 +-
src/bin/cmdctl/Makefile.am | 3 +-
src/bin/cmdctl/b10-cmdctl.xml | 1 -
src/bin/cmdctl/cmdctl.py.in | 1 -
src/bin/cmdctl/run_b10-cmdctl.sh.in | 6 +-
src/bin/cmdctl/tests/Makefile.am | 10 +-
src/bin/host/host.cc | 18 +-
src/bin/loadzone/b10-loadzone.xml | 1 -
src/bin/loadzone/run_loadzone.sh.in | 5 +-
src/bin/loadzone/tests/correct/Makefile.am | 11 +-
src/bin/loadzone/tests/correct/mix1.db | 1 -
src/bin/loadzone/tests/correct/ttlext.db | 1 -
src/bin/loadzone/tests/error/Makefile.am | 12 +-
src/bin/msgq/Makefile.am | 1 -
src/bin/msgq/msgq.py.in | 128 ++-
src/bin/msgq/msgq.xml | 1 -
src/bin/msgq/run_msgq.sh.in | 5 +-
src/bin/msgq/tests/Makefile.am | 10 +-
src/bin/msgq/tests/msgq_test.py | 129 ++-
src/bin/resolver/Makefile.am | 62 +
src/bin/resolver/b10-resolver.8 | 129 ++
src/bin/resolver/b10-resolver.xml | 245 ++++
src/bin/resolver/main.cc | 225 ++++
src/bin/resolver/resolver.cc | 658 ++++++++++
src/bin/resolver/resolver.h | 249 ++++
src/bin/resolver/resolver.spec.pre.in | 127 ++
src/bin/resolver/response_scrubber.cc | 189 +++
src/bin/resolver/response_scrubber.h | 422 +++++++
src/bin/resolver/spec_config.h.pre.in | 15 +
src/bin/resolver/tests/Makefile.am | 55 +
src/bin/resolver/tests/resolver_config_unittest.cc | 227 ++++
src/bin/resolver/tests/resolver_unittest.cc | 139 ++
.../resolver/tests/response_scrubber_unittest.cc | 542 ++++++++
src/bin/resolver/tests/run_unittests.cc | 26 +
src/bin/stats/Makefile.am | 3 +-
src/bin/stats/b10-stats.xml | 4 +-
src/bin/stats/run_b10-stats.sh.in | 5 +-
src/bin/stats/run_b10-stats_stub.sh.in | 5 +-
src/bin/stats/stats.py.in | 1 -
src/bin/stats/stats_stub.py.in | 1 -
src/bin/stats/tests/Makefile.am | 10 +-
src/bin/stats/tests/b10-stats_stub_test.py | 1 -
src/bin/stats/tests/b10-stats_test.py | 1 -
src/bin/stats/tests/fake_time.py | 1 -
src/bin/stats/tests/isc/cc/session.py | 1 -
src/bin/stats/tests/isc/config/ccsession.py | 1 -
src/bin/stats/tests/isc/util/process.py | 2 -
src/bin/stats/tests/isc/utils/process.py | 2 -
src/bin/tests/Makefile.am | 10 +-
src/bin/tests/process_rename_test.py.in | 2 +-
src/bin/usermgr/Makefile.am | 3 +-
src/bin/usermgr/b10-cmdctl-usermgr.py.in | 2 +-
src/bin/usermgr/b10-cmdctl-usermgr.xml | 1 -
src/bin/usermgr/run_b10-cmdctl-usermgr.sh.in | 6 +-
src/bin/xfrin/Makefile.am | 3 +-
src/bin/xfrin/b10-xfrin.xml | 4 +-
src/bin/xfrin/run_b10-xfrin.sh.in | 6 +-
src/bin/xfrin/tests/Makefile.am | 10 +-
src/bin/xfrin/tests/xfrin_test.py | 6 +-
src/bin/xfrin/xfrin.py.in | 33 +-
src/bin/xfrout/Makefile.am | 3 +-
src/bin/xfrout/b10-xfrout.8 | 29 +-
src/bin/xfrout/b10-xfrout.xml | 37 +-
src/bin/xfrout/run_b10-xfrout.sh.in | 6 +-
src/bin/xfrout/tests/Makefile.am | 10 +-
src/bin/xfrout/tests/xfrout_test.py | 140 ++-
src/bin/xfrout/xfrout.py.in | 287 +++--
src/bin/xfrout/xfrout.spec.pre.in | 8 +-
src/bin/zonemgr/Makefile.am | 2 +-
src/bin/zonemgr/b10-zonemgr.8 | 24 +-
src/bin/zonemgr/b10-zonemgr.xml | 35 +-
src/bin/zonemgr/run_b10-zonemgr.sh.in | 6 +-
src/bin/zonemgr/tests/Makefile.am | 11 +-
src/bin/zonemgr/tests/zonemgr_test.py | 3 +
src/bin/zonemgr/zonemgr.py.in | 82 +-
src/cppcheck-suppress.lst | 15 +
src/lib/Makefile.am | 3 +-
src/lib/asiolink/Makefile.am | 51 +
src/lib/asiolink/README | 182 +++
src/lib/asiolink/asiodef.cc | 37 +
src/lib/asiolink/asiodef.h | 21 +
src/lib/asiolink/asiodef.msg | 56 +
src/lib/asiolink/asiolink.h | 86 ++
src/lib/asiolink/asiolink_utilities.h | 61 +
src/lib/asiolink/dns_answer.h | 73 ++
src/lib/asiolink/dns_lookup.h | 83 ++
src/lib/asiolink/dns_server.h | 155 +++
src/lib/asiolink/dns_service.cc | 200 +++
src/lib/asiolink/dns_service.h | 112 ++
src/lib/asiolink/doc/auth_process.jpg | Bin 0 -> 79057 bytes
src/lib/asiolink/doc/recursive_process.jpg | Bin 0 -> 76349 bytes
src/lib/asiolink/dummy_io_cb.h | 59 +
src/lib/asiolink/interval_timer.cc | 136 ++
src/lib/asiolink/interval_timer.h | 133 ++
src/lib/asiolink/io_address.cc | 65 +
src/lib/asiolink/io_address.h | 123 ++
src/lib/asiolink/io_asio_socket.h | 399 ++++++
src/lib/asiolink/io_endpoint.cc | 60 +
src/lib/asiolink/io_endpoint.h | 121 ++
src/lib/asiolink/io_error.h | 35 +
src/lib/asiolink/io_fetch.cc | 380 ++++++
src/lib/asiolink/io_fetch.h | 179 +++
src/lib/asiolink/io_message.h | 100 ++
src/lib/asiolink/io_service.cc | 98 ++
src/lib/asiolink/io_service.h | 77 ++
src/lib/asiolink/io_socket.cc | 65 +
src/lib/asiolink/io_socket.h | 124 ++
src/lib/asiolink/qid_gen.cc | 54 +
src/lib/asiolink/qid_gen.h | 85 ++
src/lib/asiolink/simple_callback.h | 71 ++
src/lib/asiolink/tcp_endpoint.h | 113 ++
src/lib/asiolink/tcp_server.cc | 239 ++++
src/lib/asiolink/tcp_server.h | 120 ++
src/lib/asiolink/tcp_socket.h | 416 ++++++
src/lib/asiolink/tests/Makefile.am | 58 +
.../asiolink/tests/asiolink_utilities_unittest.cc | 74 ++
src/lib/asiolink/tests/dns_server_unittest.cc | 501 ++++++++
src/lib/asiolink/tests/interval_timer_unittest.cc | 296 +++++
src/lib/asiolink/tests/io_address_unittest.cc | 63 +
src/lib/asiolink/tests/io_endpoint_unittest.cc | 124 ++
src/lib/asiolink/tests/io_fetch_unittest.cc | 723 +++++++++++
src/lib/asiolink/tests/io_service_unittest.cc | 116 ++
src/lib/asiolink/tests/io_socket_unittest.cc | 32 +
src/lib/asiolink/tests/qid_gen_unittest.cc | 59 +
src/lib/asiolink/tests/run_unittests.cc | 28 +
src/lib/asiolink/tests/tcp_endpoint_unittest.cc | 55 +
src/lib/asiolink/tests/tcp_socket_unittest.cc | 515 ++++++++
src/lib/asiolink/tests/udp_endpoint_unittest.cc | 55 +
src/lib/asiolink/tests/udp_socket_unittest.cc | 333 +++++
src/lib/asiolink/udp_endpoint.h | 113 ++
src/lib/asiolink/udp_server.cc | 321 +++++
src/lib/asiolink/udp_server.h | 106 ++
src/lib/asiolink/udp_socket.h | 322 +++++
src/lib/bench/benchmark.h | 6 +-
src/lib/bench/benchmark_util.cc | 2 -
src/lib/bench/benchmark_util.h | 2 -
src/lib/bench/example/search_bench.cc | 2 -
src/lib/bench/tests/benchmark_unittest.cc | 21 +-
src/lib/bench/tests/loadquery_unittest.cc | 16 +-
src/lib/bench/tests/run_unittests.cc | 2 -
src/lib/cache/Makefile.am | 34 +
src/lib/cache/TODO | 18 +
src/lib/cache/cache_entry_key.cc | 42 +
src/lib/cache/cache_entry_key.h | 54 +
src/lib/cache/local_zone_data.cc | 56 +
src/lib/cache/local_zone_data.h | 64 +
src/lib/cache/message_cache.cc | 120 ++
src/lib/cache/message_cache.h | 104 ++
src/lib/cache/message_entry.cc | 345 +++++
src/lib/cache/message_entry.h | 205 +++
src/lib/cache/message_utility.cc | 80 ++
src/lib/cache/message_utility.h | 66 +
src/lib/cache/resolver_cache.cc | 252 ++++
src/lib/cache/resolver_cache.h | 337 +++++
src/lib/cache/rrset_cache.cc | 102 ++
src/lib/cache/rrset_cache.h | 111 ++
src/lib/cache/rrset_copy.cc | 38 +
src/lib/cache/rrset_copy.h | 42 +
src/lib/cache/rrset_entry.cc | 66 +
src/lib/cache/rrset_entry.h | 135 ++
src/lib/cache/tests/Makefile.am | 80 ++
src/lib/cache/tests/cache_test_messagefromfile.h | 39 +
src/lib/cache/tests/cache_test_sectioncount.h | 44 +
src/lib/cache/tests/local_zone_data_unittest.cc | 64 +
src/lib/cache/tests/message_cache_unittest.cc | 162 +++
src/lib/cache/tests/message_entry_unittest.cc | 309 +++++
src/lib/cache/tests/negative_cache_unittest.cc | 242 ++++
src/lib/cache/tests/resolver_cache_unittest.cc | 128 ++
src/lib/cache/tests/rrset_cache_unittest.cc | 127 ++
src/lib/cache/tests/rrset_entry_unittest.cc | 106 ++
src/lib/cache/tests/run_unittests.cc | 28 +
.../tests/testdata/message_cname_referral.wire | 56 +
.../tests/testdata/message_example_com_soa.wire | 57 +
.../tests/testdata/message_fromWire1 | 0
src/lib/cache/tests/testdata/message_fromWire2 | 22 +
src/lib/cache/tests/testdata/message_fromWire3 | 76 ++
src/lib/cache/tests/testdata/message_fromWire4 | 80 ++
src/lib/cache/tests/testdata/message_fromWire5 | 36 +
src/lib/cache/tests/testdata/message_fromWire6 | 40 +
src/lib/cache/tests/testdata/message_fromWire7 | 27 +
src/lib/cache/tests/testdata/message_fromWire8 | 23 +
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.cc | 54 +-
src/lib/cc/data.h | 14 +-
src/lib/cc/session.cc | 4 +-
src/lib/cc/session.h | 4 +-
src/lib/cc/tests/data_unittests.cc | 2 -
src/lib/cc/tests/run_unittests.cc | 2 -
src/lib/cc/tests/session_unittests.cc | 4 +-
src/lib/config/ccsession.cc | 75 +-
src/lib/config/ccsession.h | 40 +-
src/lib/config/config_data.cc | 7 -
src/lib/config/config_data.h | 2 -
src/lib/config/module_spec.cc | 80 +-
src/lib/config/module_spec.h | 63 +-
src/lib/config/tests/Makefile.am | 4 -
src/lib/config/tests/ccsession_unittests.cc | 72 +-
src/lib/config/tests/config_data_unittests.cc | 12 +-
src/lib/config/tests/fake_session.cc | 17 +-
src/lib/config/tests/fake_session.h | 4 +-
src/lib/config/tests/module_spec_unittests.cc | 124 ++-
src/lib/config/tests/run_unittests.cc | 2 -
src/lib/config/tests/testdata/Makefile.am | 3 +
src/lib/config/tests/testdata/data22_10.data | 11 +
src/lib/config/tests/testdata/data22_8.data | 3 +-
src/lib/config/tests/testdata/data22_9.data | 11 +
src/lib/config/tests/testdata/spec22.spec | 4 +-
src/lib/config/tests/testdata/spec29.spec | 35 +
src/lib/datasrc/Makefile.am | 5 +
src/lib/datasrc/cache.cc | 2 -
src/lib/datasrc/cache.h | 12 +-
src/lib/datasrc/data_source.cc | 201 ++--
src/lib/datasrc/data_source.h | 4 +-
src/lib/datasrc/memory_datasrc.cc | 644 ++++++++++
src/lib/datasrc/memory_datasrc.h | 310 +++++
src/lib/datasrc/query.cc | 24 +-
src/lib/datasrc/query.h | 18 +-
src/lib/datasrc/rbtree.h | 1323 ++++++++++++++++++++
src/lib/datasrc/result.h | 39 +
src/lib/datasrc/sqlite3_datasrc.cc | 2 -
src/lib/datasrc/sqlite3_datasrc.h | 2 -
src/lib/datasrc/static_datasrc.cc | 17 +-
src/lib/datasrc/static_datasrc.h | 8 +-
src/lib/datasrc/tests/Makefile.am | 6 +-
src/lib/datasrc/tests/cache_unittest.cc | 2 -
src/lib/datasrc/tests/datasrc_unittest.cc | 495 +++++---
src/lib/datasrc/tests/memory_datasrc_unittest.cc | 1036 +++++++++++++++
src/lib/datasrc/tests/query_unittest.cc | 4 +-
src/lib/datasrc/tests/rbtree_unittest.cc | 566 +++++++++
src/lib/datasrc/tests/run_unittests.cc | 2 -
src/lib/datasrc/tests/sqlite3_unittest.cc | 3 -
src/lib/datasrc/tests/static_unittest.cc | 8 +-
src/lib/datasrc/tests/test_datasrc.cc | 45 +-
src/lib/datasrc/tests/test_datasrc.h | 2 -
.../datasrc/tests/testdata/duplicate_rrset.zone | 4 +
src/lib/datasrc/tests/testdata/mkbrokendb.c | 2 -
src/lib/datasrc/tests/zonetable_unittest.cc | 115 ++
src/lib/datasrc/zone.h | 217 ++++
src/lib/datasrc/zonetable.cc | 129 ++
src/lib/datasrc/zonetable.h | 129 ++
src/lib/dns/Makefile.am | 12 +-
src/lib/dns/buffer.h | 29 +-
src/lib/dns/dnssectime.cc | 153 ++-
src/lib/dns/dnssectime.h | 100 ++-
src/lib/dns/edns.cc | 6 +-
src/lib/dns/edns.h | 6 +-
src/lib/dns/exceptions.cc | 2 -
src/lib/dns/exceptions.h | 2 -
src/lib/dns/gen-rdatacode.py.in | 2 -
src/lib/dns/masterload.cc | 162 +++
src/lib/dns/masterload.h | 246 ++++
src/lib/dns/message.cc | 378 ++++---
src/lib/dns/message.h | 412 ++++---
src/lib/dns/messagerenderer.cc | 2 -
src/lib/dns/messagerenderer.h | 2 -
src/lib/dns/name.cc | 2 -
src/lib/dns/name.h | 2 -
src/lib/dns/opcode.cc | 2 -
src/lib/dns/opcode.h | 2 -
src/lib/dns/python/Makefile.am | 1 +
src/lib/dns/python/edns_python.cc | 2 -
src/lib/dns/python/message_python.cc | 612 +++-------
src/lib/dns/python/messagerenderer_python.cc | 47 +-
src/lib/dns/python/name_python.cc | 8 +-
src/lib/dns/python/opcode_python.cc | 34 +-
src/lib/dns/python/pydnspp.cc | 11 +-
src/lib/dns/python/pydnspp_common.cc | 2 -
src/lib/dns/python/pydnspp_common.h | 2 -
src/lib/dns/python/question_python.cc | 2 -
src/lib/dns/python/rcode_python.cc | 36 +-
src/lib/dns/python/rdata_python.cc | 2 -
src/lib/dns/python/rrclass_python.cc | 12 +-
src/lib/dns/python/rrset_python.cc | 6 +-
src/lib/dns/python/rrttl_python.cc | 2 -
src/lib/dns/python/rrtype_python.cc | 40 +-
src/lib/dns/python/tests/Makefile.am | 11 +-
src/lib/dns/python/tests/edns_python_test.py | 2 -
src/lib/dns/python/tests/message_python_test.py | 222 ++--
.../python/tests/messagerenderer_python_test.py | 34 +-
src/lib/dns/python/tests/testutil.py | 2 -
src/lib/dns/python/tests/tsigkey_python_test.py | 172 +++
src/lib/dns/python/tsigkey_python.cc | 453 +++++++
src/lib/dns/question.cc | 2 -
src/lib/dns/question.h | 36 +-
src/lib/dns/rcode.cc | 2 -
src/lib/dns/rcode.h | 2 -
src/lib/dns/rdata.cc | 2 -
src/lib/dns/rdata.h | 2 -
src/lib/dns/rdata/any_255/tsig_250.cc | 499 ++++++++
src/lib/dns/rdata/any_255/tsig_250.h | 158 +++
src/lib/dns/rdata/ch_3/a_1.cc | 16 +-
src/lib/dns/rdata/ch_3/a_1.h | 2 -
src/lib/dns/rdata/generic/cname_5.cc | 4 +-
src/lib/dns/rdata/generic/cname_5.h | 2 -
src/lib/dns/rdata/generic/detail/nsec_bitmap.cc | 78 ++
src/lib/dns/rdata/generic/detail/nsec_bitmap.h | 51 +
src/lib/dns/rdata/generic/dname_39.cc | 4 +-
src/lib/dns/rdata/generic/dname_39.h | 2 -
src/lib/dns/rdata/generic/dnskey_48.cc | 2 -
src/lib/dns/rdata/generic/dnskey_48.h | 2 -
src/lib/dns/rdata/generic/ds_43.cc | 2 -
src/lib/dns/rdata/generic/ds_43.h | 2 -
src/lib/dns/rdata/generic/mx_15.cc | 4 +-
src/lib/dns/rdata/generic/mx_15.h | 2 -
src/lib/dns/rdata/generic/ns_2.cc | 4 +-
src/lib/dns/rdata/generic/ns_2.h | 2 -
src/lib/dns/rdata/generic/nsec3_50.cc | 123 +-
src/lib/dns/rdata/generic/nsec3_50.h | 5 +-
src/lib/dns/rdata/generic/nsec3param_51.cc | 2 -
src/lib/dns/rdata/generic/nsec3param_51.h | 2 -
src/lib/dns/rdata/generic/nsec_47.cc | 45 +-
src/lib/dns/rdata/generic/nsec_47.h | 2 -
src/lib/dns/rdata/generic/opt_41.cc | 10 +-
src/lib/dns/rdata/generic/opt_41.h | 2 -
src/lib/dns/rdata/generic/ptr_12.cc | 4 +-
src/lib/dns/rdata/generic/ptr_12.h | 2 -
src/lib/dns/rdata/generic/rrsig_46.cc | 13 +-
src/lib/dns/rdata/generic/rrsig_46.h | 2 -
src/lib/dns/rdata/generic/soa_6.cc | 4 +-
src/lib/dns/rdata/generic/soa_6.h | 2 -
src/lib/dns/rdata/generic/txt_16.cc | 2 -
src/lib/dns/rdata/generic/txt_16.h | 2 -
src/lib/dns/rdata/hs_4/a_1.cc | 16 +-
src/lib/dns/rdata/hs_4/a_1.h | 2 -
src/lib/dns/rdata/in_1/a_1.cc | 2 -
src/lib/dns/rdata/in_1/a_1.h | 2 -
src/lib/dns/rdata/in_1/aaaa_28.cc | 2 -
src/lib/dns/rdata/in_1/aaaa_28.h | 2 -
src/lib/dns/rdata/template.cc | 2 -
src/lib/dns/rdata/template.h | 2 -
src/lib/dns/rrclass-placeholder.h | 13 +-
src/lib/dns/rrclass.cc | 2 -
src/lib/dns/rrparamregistry-placeholder.cc | 2 -
src/lib/dns/rrparamregistry.h | 2 -
src/lib/dns/rrset.cc | 7 +-
src/lib/dns/rrset.h | 44 +-
src/lib/dns/rrsetlist.cc | 2 -
src/lib/dns/rrsetlist.h | 23 +-
src/lib/dns/rrttl.cc | 2 -
src/lib/dns/rrttl.h | 8 +-
src/lib/dns/rrtype-placeholder.h | 2 -
src/lib/dns/rrtype.cc | 2 -
src/lib/dns/tests/Makefile.am | 5 +-
src/lib/dns/tests/base32hex_unittest.cc | 2 -
src/lib/dns/tests/base64_unittest.cc | 2 -
src/lib/dns/tests/buffer_unittest.cc | 14 +-
src/lib/dns/tests/dnssectime_unittest.cc | 149 ++-
src/lib/dns/tests/edns_unittest.cc | 2 -
src/lib/dns/tests/hex_unittest.cc | 2 -
src/lib/dns/tests/masterload_unittest.cc | 268 ++++
src/lib/dns/tests/message_unittest.cc | 389 ++++++-
src/lib/dns/tests/messagerenderer_unittest.cc | 2 -
src/lib/dns/tests/name_unittest.cc | 4 +-
src/lib/dns/tests/opcode_unittest.cc | 2 -
src/lib/dns/tests/question_unittest.cc | 35 +-
src/lib/dns/tests/rcode_unittest.cc | 2 -
src/lib/dns/tests/rdata_cname_unittest.cc | 2 -
src/lib/dns/tests/rdata_dname_unittest.cc | 2 -
src/lib/dns/tests/rdata_dnskey_unittest.cc | 2 -
src/lib/dns/tests/rdata_ds_unittest.cc | 2 -
src/lib/dns/tests/rdata_in_a_unittest.cc | 2 -
src/lib/dns/tests/rdata_in_aaaa_unittest.cc | 2 -
src/lib/dns/tests/rdata_mx_unittest.cc | 7 +-
src/lib/dns/tests/rdata_ns_unittest.cc | 2 -
src/lib/dns/tests/rdata_nsec3_unittest.cc | 129 ++-
src/lib/dns/tests/rdata_nsec3param_unittest.cc | 2 -
src/lib/dns/tests/rdata_nsec_unittest.cc | 43 +-
src/lib/dns/tests/rdata_nsecbitmap_unittest.cc | 103 ++
src/lib/dns/tests/rdata_opt_unittest.cc | 2 -
src/lib/dns/tests/rdata_ptr_unittest.cc | 2 -
src/lib/dns/tests/rdata_rrsig_unittest.cc | 2 -
src/lib/dns/tests/rdata_soa_unittest.cc | 2 -
src/lib/dns/tests/rdata_tsig_unittest.cc | 366 ++++++
src/lib/dns/tests/rdata_txt_unittest.cc | 2 -
src/lib/dns/tests/rdata_unittest.cc | 2 -
src/lib/dns/tests/rdata_unittest.h | 2 -
src/lib/dns/tests/rdatafields_unittest.cc | 2 +-
src/lib/dns/tests/rrclass_unittest.cc | 2 -
src/lib/dns/tests/rrparamregistry_unittest.cc | 2 -
src/lib/dns/tests/rrset_unittest.cc | 6 +-
src/lib/dns/tests/rrsetlist_unittest.cc | 3 -
src/lib/dns/tests/rrttl_unittest.cc | 2 -
src/lib/dns/tests/rrtype_unittest.cc | 2 -
src/lib/dns/tests/run_unittests.cc | 2 -
src/lib/dns/tests/sha1_unittest.cc | 2 -
src/lib/dns/tests/testdata/Makefile.am | 42 +-
src/lib/dns/tests/testdata/edns_toWire4.spec | 3 +-
src/lib/dns/tests/testdata/gen-wiredata.py.in | 175 +++-
src/lib/dns/tests/testdata/masterload.txt | 5 +
src/lib/dns/tests/testdata/rdata_mx_toWire2 | 12 +
.../dns/tests/testdata/rdata_nsec3_fromWire1.spec | 7 +
.../dns/tests/testdata/rdata_nsec3_fromWire10.spec | 8 +
.../dns/tests/testdata/rdata_nsec3_fromWire11.spec | 8 +
.../dns/tests/testdata/rdata_nsec3_fromWire12.spec | 9 +
.../dns/tests/testdata/rdata_nsec3_fromWire13.spec | 9 +
.../dns/tests/testdata/rdata_nsec3_fromWire14.spec | 9 +
.../dns/tests/testdata/rdata_nsec3_fromWire15.spec | 10 +
src/lib/dns/tests/testdata/rdata_nsec3_fromWire2 | 12 -
.../dns/tests/testdata/rdata_nsec3_fromWire2.spec | 9 +
.../dns/tests/testdata/rdata_nsec3_fromWire4.spec | 9 +
.../dns/tests/testdata/rdata_nsec3_fromWire5.spec | 13 +
.../dns/tests/testdata/rdata_nsec3_fromWire6.spec | 11 +
.../dns/tests/testdata/rdata_nsec3_fromWire7.spec | 9 +
.../dns/tests/testdata/rdata_nsec3_fromWire8.spec | 9 +
.../dns/tests/testdata/rdata_nsec3_fromWire9.spec | 10 +
.../dns/tests/testdata/rdata_tsig_fromWire1.spec | 6 +
.../dns/tests/testdata/rdata_tsig_fromWire2.spec | 8 +
.../dns/tests/testdata/rdata_tsig_fromWire3.spec | 8 +
.../dns/tests/testdata/rdata_tsig_fromWire4.spec | 11 +
.../dns/tests/testdata/rdata_tsig_fromWire5.spec | 7 +
.../dns/tests/testdata/rdata_tsig_fromWire6.spec | 7 +
.../dns/tests/testdata/rdata_tsig_fromWire7.spec | 8 +
.../dns/tests/testdata/rdata_tsig_fromWire8.spec | 8 +
.../dns/tests/testdata/rdata_tsig_fromWire9.spec | 8 +
src/lib/dns/tests/testdata/rdata_tsig_toWire1.spec | 11 +
src/lib/dns/tests/testdata/rdata_tsig_toWire2.spec | 13 +
src/lib/dns/tests/testdata/rdata_tsig_toWire3.spec | 15 +
src/lib/dns/tests/testdata/rdata_tsig_toWire4.spec | 13 +
src/lib/dns/tests/testdata/rdata_tsig_toWire5.spec | 13 +
src/lib/dns/tests/tsig_unittest.cc | 41 -
src/lib/dns/tests/tsigkey_unittest.cc | 230 ++++
src/lib/dns/tests/unittest_util.cc | 30 +-
src/lib/dns/tests/unittest_util.h | 17 +-
src/lib/dns/tsig.cc | 33 -
src/lib/dns/tsig.h | 72 --
src/lib/dns/tsigkey.cc | 165 +++
src/lib/dns/tsigkey.h | 298 +++++
src/lib/dns/util/base32hex.h | 2 -
src/lib/dns/util/base64.h | 2 -
src/lib/dns/util/base_n.cc | 2 -
src/lib/dns/util/hex.h | 2 -
src/lib/exceptions/exceptions.cc | 2 -
src/lib/exceptions/exceptions.h | 6 +-
src/lib/exceptions/tests/exceptions_unittest.cc | 2 -
src/lib/exceptions/tests/run_unittests.cc | 2 -
src/lib/log/Makefile.am | 41 +
src/lib/log/README | 376 ++++++
src/lib/log/compiler/Makefile.am | 18 +
src/lib/log/compiler/message.cc | 546 ++++++++
src/lib/log/debug_levels.h | 29 +
src/lib/log/dummylog.cc | 37 +
src/lib/log/dummylog.h | 61 +
src/lib/log/filename.cc | 138 ++
src/lib/log/filename.h | 161 +++
src/lib/log/logger.cc | 175 +++
src/lib/log/logger.h | 252 ++++
src/lib/log/logger_impl.cc | 221 ++++
src/lib/log/logger_impl.h | 267 ++++
src/lib/log/logger_impl_log4cxx.cc | 241 ++++
src/lib/log/logger_impl_log4cxx.h | 315 +++++
src/lib/log/logger_levels.h | 42 +
src/lib/log/logger_support.cc | 132 ++
src/lib/log/logger_support.h | 46 +
src/lib/log/message_dictionary.cc | 113 ++
src/lib/log/message_dictionary.h | 190 +++
src/lib/log/message_exception.cc | 26 +
src/lib/log/message_exception.h | 88 ++
src/lib/log/message_initializer.cc | 44 +
src/lib/log/message_initializer.h | 75 ++
src/lib/log/message_reader.cc | 233 ++++
src/lib/log/message_reader.h | 202 +++
src/lib/log/message_types.h | 37 +
src/lib/log/messagedef.cc | 57 +
src/lib/log/messagedef.h | 32 +
src/lib/log/messagedef.mes | 119 ++
src/lib/log/root_logger_name.cc | 44 +
src/lib/log/root_logger_name.h | 46 +
src/lib/log/strutil.cc | 135 ++
src/lib/log/strutil.h | 145 +++
src/lib/log/tests/Makefile.am | 45 +
src/lib/log/tests/filename_unittest.cc | 179 +++
src/lib/log/tests/logger_impl_log4cxx_unittest.cc | 91 ++
src/lib/log/tests/logger_support_test.cc | 104 ++
src/lib/log/tests/logger_unittest.cc | 345 +++++
src/lib/log/tests/message_dictionary_unittest.cc | 197 +++
src/lib/log/tests/message_initializer_unittest.cc | 70 +
.../log/tests/message_initializer_unittest_2.cc | 39 +
src/lib/log/tests/message_reader_unittest.cc | 264 ++++
src/lib/log/tests/root_logger_name_unittest.cc | 50 +
src/lib/log/tests/run_time_init_test.sh.in | 89 ++
src/lib/log/tests/run_unittests.cc | 21 +
src/lib/log/tests/strutil_unittest.cc | 214 ++++
src/lib/log/tests/xdebuglevel_unittest.cc | 203 +++
src/lib/log/xdebuglevel.cc | 146 +++
src/lib/log/xdebuglevel.h | 162 +++
src/lib/nsas/Makefile.am | 42 +
src/lib/nsas/README | 7 +
src/lib/nsas/TODO | 40 +
src/lib/nsas/address_entry.cc | 44 +
src/lib/nsas/address_entry.h | 102 ++
src/lib/nsas/address_request_callback.h | 72 ++
src/lib/nsas/asiolink.h | 21 +
src/lib/nsas/fetchable.h | 66 +
src/lib/nsas/glue_hints.cc | 168 +++
src/lib/nsas/glue_hints.h | 71 ++
src/lib/nsas/hash.cc | 168 +++
src/lib/nsas/hash.h | 125 ++
src/lib/nsas/hash_deleter.h | 74 ++
src/lib/nsas/hash_key.cc | 51 +
src/lib/nsas/hash_key.h | 96 ++
src/lib/nsas/hash_table.h | 336 +++++
src/lib/nsas/locks.h | 116 ++
src/lib/nsas/lru_list.h | 260 ++++
src/lib/nsas/nameserver_address.cc | 32 +
src/lib/nsas/nameserver_address.h | 117 ++
src/lib/nsas/nameserver_address_store.cc | 114 ++
src/lib/nsas/nameserver_address_store.h | 127 ++
src/lib/nsas/nameserver_entry.cc | 444 +++++++
src/lib/nsas/nameserver_entry.h | 283 +++++
src/lib/nsas/nsas_entry.h | 138 ++
src/lib/nsas/nsas_entry_compare.h | 53 +
src/lib/nsas/nsas_types.h | 47 +
src/lib/nsas/random_number_generator.h | 207 +++
src/lib/nsas/tests/Makefile.am | 61 +
src/lib/nsas/tests/address_entry_unittest.cc | 116 ++
src/lib/nsas/tests/fetchable_unittest.cc | 34 +
src/lib/nsas/tests/hash_deleter_unittest.cc | 116 ++
src/lib/nsas/tests/hash_key_unittest.cc | 86 ++
src/lib/nsas/tests/hash_table_unittest.cc | 256 ++++
src/lib/nsas/tests/hash_unittest.cc | 194 +++
src/lib/nsas/tests/lru_list_unittest.cc | 318 +++++
.../tests/nameserver_address_store_unittest.cc | 398 ++++++
src/lib/nsas/tests/nameserver_address_unittest.cc | 135 ++
src/lib/nsas/tests/nameserver_entry_unittest.cc | 518 ++++++++
src/lib/nsas/tests/nsas_entry_compare_unittest.cc | 61 +
src/lib/nsas/tests/nsas_test.h | 433 +++++++
.../nsas/tests/random_number_generator_unittest.cc | 309 +++++
src/lib/nsas/tests/run_unittests.cc | 26 +
src/lib/nsas/tests/zone_entry_unittest.cc | 753 +++++++++++
src/lib/nsas/zone_entry.cc | 568 +++++++++
src/lib/nsas/zone_entry.h | 191 +++
src/lib/python/isc/Makefile.am | 2 +-
src/lib/python/isc/cc/data.py | 212 +++-
src/lib/python/isc/cc/session.py | 5 +-
src/lib/python/isc/cc/tests/Makefile.am | 11 +-
src/lib/python/isc/cc/tests/data_test.py | 93 ++-
src/lib/python/isc/cc/tests/session_test.py | 5 +
src/lib/python/isc/config/ccsession.py | 85 +-
src/lib/python/isc/config/cfgmgr.py | 116 +-
src/lib/python/isc/config/config_data.py | 269 +++--
src/lib/python/isc/config/module_spec.py | 22 +-
src/lib/python/isc/config/tests/Makefile.am | 10 +-
src/lib/python/isc/config/tests/ccsession_test.py | 40 +-
src/lib/python/isc/config/tests/cfgmgr_test.py | 102 ++-
.../python/isc/config/tests/config_data_test.py | 161 ++-
.../python/isc/config/tests/module_spec_test.py | 18 +-
.../isc/config/tests/unittest_fakesession.py | 2 -
src/lib/python/isc/datasrc/Makefile.am | 2 +
src/lib/python/isc/datasrc/master.py | 4 +-
src/lib/python/isc/datasrc/sqlite3_ds.py | 172 ++-
src/lib/python/isc/datasrc/tests/Makefile.am | 20 +
src/lib/python/isc/datasrc/tests/master_test.py | 35 +
.../python/isc/datasrc/tests/sqlite3_ds_test.py | 43 +
.../isc}/datasrc/tests/testdata/brokendb.sqlite3 | Bin 2048 -> 2048 bytes
.../datasrc/tests/testdata/example.com.sqlite3} | Bin 43008 -> 43008 bytes
src/lib/python/isc/log/tests/Makefile.am | 10 +-
src/lib/python/isc/net/parse.py | 2 +-
src/lib/python/isc/net/tests/Makefile.am | 10 +-
src/lib/python/isc/net/tests/parse_test.py | 2 +-
src/lib/python/isc/notify/notify_out.py | 7 +-
src/lib/python/isc/notify/tests/Makefile.am | 10 +-
src/lib/python/isc/notify/tests/notify_out_test.py | 267 +++--
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/socketserver_mixin.py | 6 +-
src/lib/python/isc/util/tests/Makefile.am | 10 +-
src/lib/python/isc/util/tests/process_test.py | 2 +-
.../isc/util/tests/socketserver_mixin_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 | 29 +
src/lib/resolve/recursive_query.cc | 802 ++++++++++++
src/lib/resolve/recursive_query.h | 133 ++
src/lib/resolve/resolve.cc | 78 ++
src/lib/resolve/resolve.h | 96 ++
src/lib/resolve/resolver_callback.cc | 36 +
src/lib/resolve/resolver_callback.h | 48 +
src/lib/resolve/resolver_interface.h | 98 ++
src/lib/resolve/response_classifier.cc | 278 ++++
src/lib/resolve/response_classifier.h | 157 +++
src/lib/resolve/tests/Makefile.am | 36 +
src/lib/resolve/tests/recursive_query_unittest.cc | 861 +++++++++++++
.../resolve/tests/recursive_query_unittest_2.cc | 677 ++++++++++
src/lib/resolve/tests/resolve_unittest.cc | 198 +++
.../resolve/tests/resolver_callback_unittest.cc | 90 ++
.../resolve/tests/response_classifier_unittest.cc | 554 ++++++++
src/lib/resolve/tests/run_unittests.cc | 24 +
src/lib/server_common/Makefile.am | 26 +
src/lib/server_common/portconfig.cc | 119 ++
src/lib/server_common/portconfig.h | 121 ++
src/lib/server_common/tests/Makefile.am | 42 +
src/lib/server_common/tests/portconfig_unittest.cc | 182 +++
src/lib/server_common/tests/run_unittests.cc | 26 +
src/lib/testutils/Makefile.am | 17 +
src/lib/testutils/README | 2 +
src/lib/testutils/dnsmessage_test.cc | 115 ++
src/lib/testutils/dnsmessage_test.h | 304 +++++
src/lib/testutils/mockups.h | 151 +++
src/lib/testutils/portconfig.h | 189 +++
src/lib/testutils/srv_test.cc | 235 ++++
src/lib/testutils/srv_test.h | 112 ++
src/lib/testutils/testdata/Makefile.am | 35 +
.../testdata/badExampleQuery_fromWire.spec | 0
.../tests => lib/testutils}/testdata/example.com | 0
src/lib/testutils/testdata/example.com.zone | 3 +
src/lib/testutils/testdata/example.net.zone | 3 +
src/lib/testutils/testdata/example.org.zone | 3 +
.../testutils}/testdata/example.sqlite3 | Bin 11264 -> 11264 bytes
src/lib/testutils/testdata/example.zone | 3 +
.../testutils}/testdata/examplequery_fromWire.spec | 0
src/lib/testutils/testdata/iquery_fromWire.spec | 8 +
.../testdata/iquery_response_fromWire.spec | 9 +
.../testdata/iqueryresponse_fromWire.spec | 0
.../testdata/multiquestion_fromWire.spec | 0
.../testutils}/testdata/queryBadEDNS_fromWire.spec | 0
.../testutils}/testdata/shortanswer_fromWire.spec | 0
.../testutils}/testdata/shortmessage_fromWire | 0
.../testutils}/testdata/shortquestion_fromWire | 0
.../testutils}/testdata/shortresponse_fromWire | 0
.../testutils}/testdata/simplequery_fromWire.spec | 0
.../testdata/simpleresponse_fromWire.spec | 0
src/lib/testutils/testdata/test1-broken.zone.in | 5 +
src/lib/testutils/testdata/test1-new.zone.in | 4 +
src/lib/testutils/testdata/test1.zone.in | 3 +
src/lib/testutils/testdata/test2-new.zone.in | 4 +
src/lib/testutils/testdata/test2.zone.in | 3 +
src/lib/xfr/fd_share.cc | 4 +-
src/lib/xfr/fd_share.h | 12 +-
src/lib/xfr/fdshare_python.cc | 21 +-
src/lib/xfr/python_xfr.cc | 2 -
src/lib/xfr/xfrout_client.cc | 10 +-
src/lib/xfr/xfrout_client.h | 2 -
tests/Makefile.am | 1 +
tests/system/Makefile.am | 16 +
tests/system/README | 63 +
tests/system/bindctl/clean.sh | 20 +
.../system/bindctl/nsx1/b10-config.db.template.in | 10 +
tests/system/bindctl/nsx1/example-normalized.db | 3 +
tests/system/bindctl/nsx1/root.db | 25 +
tests/system/bindctl/setup.sh | 26 +
tests/system/bindctl/tests.sh | 106 ++
tests/system/cleanall.sh | 33 +
tests/system/common/default_user.csv | 1 +
tests/system/conf.sh.in | 57 +
tests/system/glue/auth.good | 15 +
tests/system/glue/clean.sh | 23 +
tests/system/glue/example.good | 19 +
tests/system/glue/noglue.good | 14 +
tests/system/glue/nsx1/b10-config.db.in | 9 +
tests/system/glue/nsx1/com.db | 31 +
tests/system/glue/nsx1/net.db | 32 +
tests/system/glue/nsx1/root-servers.nil.db | 26 +
tests/system/glue/nsx1/root.db | 55 +
tests/system/glue/setup.sh.in | 25 +
tests/system/glue/test.good | 19 +
tests/system/glue/tests.sh | 66 +
tests/system/ifconfig.sh | 226 ++++
tests/system/run.sh | 125 ++
tests/system/runall.sh | 44 +
tests/system/start.pl | 226 ++++
tests/system/stop.pl | 188 +++
tools/README | 5 +-
tools/import_boost.sh | 74 --
tools/tests_in_valgrind.sh | 75 ++
tools/valgrind_test_cleaner.pl | 64 +
752 files changed, 60164 insertions(+), 7333 deletions(-)
create mode 100644 doc/Makefile.am
delete mode 100644 doc/guide/Makefile
create mode 100644 doc/guide/Makefile.am
create mode 100644 doc/version.ent.in
create mode 100644 ext/coroutine/coroutine.h
delete mode 100644 src/bin/auth/asio_link.cc
delete mode 100644 src/bin/auth/asio_link.h
create mode 100644 src/bin/auth/command.cc
create mode 100644 src/bin/auth/command.h
create mode 100644 src/bin/auth/config.cc
create mode 100644 src/bin/auth/config.h
create mode 100644 src/bin/auth/query.cc
create mode 100644 src/bin/auth/query.h
create mode 100644 src/bin/auth/statistics.cc
create mode 100644 src/bin/auth/statistics.h
delete mode 100644 src/bin/auth/tests/asio_link_unittest.cc
create mode 100644 src/bin/auth/tests/command_unittest.cc
create mode 100644 src/bin/auth/tests/config_unittest.cc
create mode 100644 src/bin/auth/tests/query_unittest.cc
create mode 100644 src/bin/auth/tests/statistics_unittest.cc
mode change 100644 => 100755 src/bin/bind10/bind10.py.in
delete mode 100755 src/bin/bind10/tests/bind10_test.in
delete mode 100644 src/bin/bind10/tests/bind10_test.py
create mode 100644 src/bin/bind10/tests/bind10_test.py.in
delete mode 100644 src/bin/bindctl/bindctl-source.py.in
create mode 100755 src/bin/bindctl/bindctl_main.py.in
create mode 100644 src/bin/bindctl/tests/cmdparse_test.py
mode change 100644 => 100755 src/bin/cfgmgr/b10-cfgmgr.py.in
mode change 100644 => 100755 src/bin/msgq/msgq.py.in
create mode 100644 src/bin/resolver/Makefile.am
create mode 100644 src/bin/resolver/b10-resolver.8
create mode 100644 src/bin/resolver/b10-resolver.xml
create mode 100644 src/bin/resolver/main.cc
create mode 100644 src/bin/resolver/resolver.cc
create mode 100644 src/bin/resolver/resolver.h
create mode 100644 src/bin/resolver/resolver.spec.pre.in
create mode 100644 src/bin/resolver/response_scrubber.cc
create mode 100644 src/bin/resolver/response_scrubber.h
create mode 100644 src/bin/resolver/spec_config.h.pre.in
create mode 100644 src/bin/resolver/tests/Makefile.am
create mode 100644 src/bin/resolver/tests/resolver_config_unittest.cc
create mode 100644 src/bin/resolver/tests/resolver_unittest.cc
create mode 100644 src/bin/resolver/tests/response_scrubber_unittest.cc
create mode 100644 src/bin/resolver/tests/run_unittests.cc
mode change 100644 => 100755 src/bin/stats/stats.py.in
mode change 100644 => 100755 src/bin/stats/stats_stub.py.in
mode change 100644 => 100755 src/bin/xfrin/xfrin.py.in
mode change 100644 => 100755 src/bin/xfrout/xfrout.py.in
mode change 100644 => 100755 src/bin/zonemgr/zonemgr.py.in
create mode 100644 src/cppcheck-suppress.lst
create mode 100644 src/lib/asiolink/Makefile.am
create mode 100644 src/lib/asiolink/README
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.h
create mode 100644 src/lib/asiolink/asiolink_utilities.h
create mode 100644 src/lib/asiolink/dns_answer.h
create mode 100644 src/lib/asiolink/dns_lookup.h
create mode 100644 src/lib/asiolink/dns_server.h
create mode 100644 src/lib/asiolink/dns_service.cc
create mode 100644 src/lib/asiolink/dns_service.h
create mode 100644 src/lib/asiolink/doc/auth_process.jpg
create mode 100644 src/lib/asiolink/doc/recursive_process.jpg
create mode 100644 src/lib/asiolink/dummy_io_cb.h
create mode 100644 src/lib/asiolink/interval_timer.cc
create mode 100644 src/lib/asiolink/interval_timer.h
create mode 100644 src/lib/asiolink/io_address.cc
create mode 100644 src/lib/asiolink/io_address.h
create mode 100644 src/lib/asiolink/io_asio_socket.h
create mode 100644 src/lib/asiolink/io_endpoint.cc
create mode 100644 src/lib/asiolink/io_endpoint.h
create mode 100644 src/lib/asiolink/io_error.h
create mode 100644 src/lib/asiolink/io_fetch.cc
create mode 100644 src/lib/asiolink/io_fetch.h
create mode 100644 src/lib/asiolink/io_message.h
create mode 100644 src/lib/asiolink/io_service.cc
create mode 100644 src/lib/asiolink/io_service.h
create mode 100644 src/lib/asiolink/io_socket.cc
create mode 100644 src/lib/asiolink/io_socket.h
create mode 100644 src/lib/asiolink/qid_gen.cc
create mode 100644 src/lib/asiolink/qid_gen.h
create mode 100644 src/lib/asiolink/simple_callback.h
create mode 100644 src/lib/asiolink/tcp_endpoint.h
create mode 100644 src/lib/asiolink/tcp_server.cc
create mode 100644 src/lib/asiolink/tcp_server.h
create mode 100644 src/lib/asiolink/tcp_socket.h
create mode 100644 src/lib/asiolink/tests/Makefile.am
create mode 100644 src/lib/asiolink/tests/asiolink_utilities_unittest.cc
create mode 100644 src/lib/asiolink/tests/dns_server_unittest.cc
create mode 100644 src/lib/asiolink/tests/interval_timer_unittest.cc
create mode 100644 src/lib/asiolink/tests/io_address_unittest.cc
create mode 100644 src/lib/asiolink/tests/io_endpoint_unittest.cc
create mode 100644 src/lib/asiolink/tests/io_fetch_unittest.cc
create mode 100644 src/lib/asiolink/tests/io_service_unittest.cc
create mode 100644 src/lib/asiolink/tests/io_socket_unittest.cc
create mode 100644 src/lib/asiolink/tests/qid_gen_unittest.cc
create mode 100644 src/lib/asiolink/tests/run_unittests.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/asiolink/tests/udp_endpoint_unittest.cc
create mode 100644 src/lib/asiolink/tests/udp_socket_unittest.cc
create mode 100644 src/lib/asiolink/udp_endpoint.h
create mode 100644 src/lib/asiolink/udp_server.cc
create mode 100644 src/lib/asiolink/udp_server.h
create mode 100644 src/lib/asiolink/udp_socket.h
create mode 100644 src/lib/cache/Makefile.am
create mode 100644 src/lib/cache/TODO
create mode 100644 src/lib/cache/cache_entry_key.cc
create mode 100644 src/lib/cache/cache_entry_key.h
create mode 100644 src/lib/cache/local_zone_data.cc
create mode 100644 src/lib/cache/local_zone_data.h
create mode 100644 src/lib/cache/message_cache.cc
create mode 100644 src/lib/cache/message_cache.h
create mode 100644 src/lib/cache/message_entry.cc
create mode 100644 src/lib/cache/message_entry.h
create mode 100644 src/lib/cache/message_utility.cc
create mode 100644 src/lib/cache/message_utility.h
create mode 100644 src/lib/cache/resolver_cache.cc
create mode 100644 src/lib/cache/resolver_cache.h
create mode 100644 src/lib/cache/rrset_cache.cc
create mode 100644 src/lib/cache/rrset_cache.h
create mode 100644 src/lib/cache/rrset_copy.cc
create mode 100644 src/lib/cache/rrset_copy.h
create mode 100644 src/lib/cache/rrset_entry.cc
create mode 100644 src/lib/cache/rrset_entry.h
create mode 100644 src/lib/cache/tests/Makefile.am
create mode 100644 src/lib/cache/tests/cache_test_messagefromfile.h
create mode 100644 src/lib/cache/tests/cache_test_sectioncount.h
create mode 100644 src/lib/cache/tests/local_zone_data_unittest.cc
create mode 100644 src/lib/cache/tests/message_cache_unittest.cc
create mode 100644 src/lib/cache/tests/message_entry_unittest.cc
create mode 100644 src/lib/cache/tests/negative_cache_unittest.cc
create mode 100644 src/lib/cache/tests/resolver_cache_unittest.cc
create mode 100644 src/lib/cache/tests/rrset_cache_unittest.cc
create mode 100644 src/lib/cache/tests/rrset_entry_unittest.cc
create mode 100644 src/lib/cache/tests/run_unittests.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
copy src/lib/{dns => cache}/tests/testdata/message_fromWire1 (100%)
create mode 100644 src/lib/cache/tests/testdata/message_fromWire2
create mode 100644 src/lib/cache/tests/testdata/message_fromWire3
create mode 100644 src/lib/cache/tests/testdata/message_fromWire4
create mode 100644 src/lib/cache/tests/testdata/message_fromWire5
create mode 100644 src/lib/cache/tests/testdata/message_fromWire6
create mode 100644 src/lib/cache/tests/testdata/message_fromWire7
create mode 100644 src/lib/cache/tests/testdata/message_fromWire8
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/config/tests/testdata/data22_9.data
create mode 100644 src/lib/config/tests/testdata/spec29.spec
create mode 100644 src/lib/datasrc/memory_datasrc.cc
create mode 100644 src/lib/datasrc/memory_datasrc.h
create mode 100644 src/lib/datasrc/rbtree.h
create mode 100644 src/lib/datasrc/result.h
create mode 100644 src/lib/datasrc/tests/memory_datasrc_unittest.cc
create mode 100644 src/lib/datasrc/tests/rbtree_unittest.cc
create mode 100644 src/lib/datasrc/tests/testdata/duplicate_rrset.zone
create mode 100644 src/lib/datasrc/tests/zonetable_unittest.cc
create mode 100644 src/lib/datasrc/zone.h
create mode 100644 src/lib/datasrc/zonetable.cc
create mode 100644 src/lib/datasrc/zonetable.h
create mode 100644 src/lib/dns/masterload.cc
create mode 100644 src/lib/dns/masterload.h
create mode 100644 src/lib/dns/python/tests/tsigkey_python_test.py
create mode 100644 src/lib/dns/python/tsigkey_python.cc
create mode 100644 src/lib/dns/rdata/any_255/tsig_250.cc
create mode 100644 src/lib/dns/rdata/any_255/tsig_250.h
create mode 100644 src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
create mode 100644 src/lib/dns/rdata/generic/detail/nsec_bitmap.h
create mode 100644 src/lib/dns/tests/masterload_unittest.cc
create mode 100644 src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
create mode 100644 src/lib/dns/tests/rdata_tsig_unittest.cc
create mode 100644 src/lib/dns/tests/testdata/masterload.txt
create mode 100644 src/lib/dns/tests/testdata/rdata_mx_toWire2
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire1.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.spec
delete mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire2
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_tsig_fromWire1.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_tsig_fromWire2.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_tsig_fromWire3.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_tsig_fromWire4.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_tsig_fromWire5.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_tsig_fromWire6.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_tsig_fromWire7.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_tsig_fromWire8.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_tsig_fromWire9.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_tsig_toWire1.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_tsig_toWire2.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_tsig_toWire3.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_tsig_toWire4.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_tsig_toWire5.spec
delete mode 100644 src/lib/dns/tests/tsig_unittest.cc
create mode 100644 src/lib/dns/tests/tsigkey_unittest.cc
delete mode 100644 src/lib/dns/tsig.cc
delete mode 100644 src/lib/dns/tsig.h
create mode 100644 src/lib/dns/tsigkey.cc
create mode 100644 src/lib/dns/tsigkey.h
create mode 100644 src/lib/log/Makefile.am
create mode 100644 src/lib/log/README
create mode 100644 src/lib/log/compiler/Makefile.am
create mode 100644 src/lib/log/compiler/message.cc
create mode 100644 src/lib/log/debug_levels.h
create mode 100644 src/lib/log/dummylog.cc
create mode 100644 src/lib/log/dummylog.h
create mode 100644 src/lib/log/filename.cc
create mode 100644 src/lib/log/filename.h
create mode 100644 src/lib/log/logger.cc
create mode 100644 src/lib/log/logger.h
create mode 100644 src/lib/log/logger_impl.cc
create mode 100644 src/lib/log/logger_impl.h
create mode 100644 src/lib/log/logger_impl_log4cxx.cc
create mode 100644 src/lib/log/logger_impl_log4cxx.h
create mode 100644 src/lib/log/logger_levels.h
create mode 100644 src/lib/log/logger_support.cc
create mode 100644 src/lib/log/logger_support.h
create mode 100644 src/lib/log/message_dictionary.cc
create mode 100644 src/lib/log/message_dictionary.h
create mode 100644 src/lib/log/message_exception.cc
create mode 100644 src/lib/log/message_exception.h
create mode 100644 src/lib/log/message_initializer.cc
create mode 100644 src/lib/log/message_initializer.h
create mode 100644 src/lib/log/message_reader.cc
create mode 100644 src/lib/log/message_reader.h
create mode 100644 src/lib/log/message_types.h
create mode 100644 src/lib/log/messagedef.cc
create mode 100644 src/lib/log/messagedef.h
create mode 100644 src/lib/log/messagedef.mes
create mode 100644 src/lib/log/root_logger_name.cc
create mode 100644 src/lib/log/root_logger_name.h
create mode 100644 src/lib/log/strutil.cc
create mode 100644 src/lib/log/strutil.h
create mode 100644 src/lib/log/tests/Makefile.am
create mode 100644 src/lib/log/tests/filename_unittest.cc
create mode 100644 src/lib/log/tests/logger_impl_log4cxx_unittest.cc
create mode 100644 src/lib/log/tests/logger_support_test.cc
create mode 100644 src/lib/log/tests/logger_unittest.cc
create mode 100644 src/lib/log/tests/message_dictionary_unittest.cc
create mode 100644 src/lib/log/tests/message_initializer_unittest.cc
create mode 100644 src/lib/log/tests/message_initializer_unittest_2.cc
create mode 100644 src/lib/log/tests/message_reader_unittest.cc
create mode 100644 src/lib/log/tests/root_logger_name_unittest.cc
create mode 100755 src/lib/log/tests/run_time_init_test.sh.in
create mode 100644 src/lib/log/tests/run_unittests.cc
create mode 100644 src/lib/log/tests/strutil_unittest.cc
create mode 100644 src/lib/log/tests/xdebuglevel_unittest.cc
create mode 100644 src/lib/log/xdebuglevel.cc
create mode 100644 src/lib/log/xdebuglevel.h
create mode 100644 src/lib/nsas/Makefile.am
create mode 100644 src/lib/nsas/README
create mode 100644 src/lib/nsas/TODO
create mode 100644 src/lib/nsas/address_entry.cc
create mode 100644 src/lib/nsas/address_entry.h
create mode 100644 src/lib/nsas/address_request_callback.h
create mode 100644 src/lib/nsas/asiolink.h
create mode 100644 src/lib/nsas/fetchable.h
create mode 100644 src/lib/nsas/glue_hints.cc
create mode 100644 src/lib/nsas/glue_hints.h
create mode 100644 src/lib/nsas/hash.cc
create mode 100644 src/lib/nsas/hash.h
create mode 100644 src/lib/nsas/hash_deleter.h
create mode 100644 src/lib/nsas/hash_key.cc
create mode 100644 src/lib/nsas/hash_key.h
create mode 100644 src/lib/nsas/hash_table.h
create mode 100644 src/lib/nsas/locks.h
create mode 100644 src/lib/nsas/lru_list.h
create mode 100644 src/lib/nsas/nameserver_address.cc
create mode 100644 src/lib/nsas/nameserver_address.h
create mode 100644 src/lib/nsas/nameserver_address_store.cc
create mode 100644 src/lib/nsas/nameserver_address_store.h
create mode 100644 src/lib/nsas/nameserver_entry.cc
create mode 100644 src/lib/nsas/nameserver_entry.h
create mode 100644 src/lib/nsas/nsas_entry.h
create mode 100644 src/lib/nsas/nsas_entry_compare.h
create mode 100644 src/lib/nsas/nsas_types.h
create mode 100644 src/lib/nsas/random_number_generator.h
create mode 100644 src/lib/nsas/tests/Makefile.am
create mode 100644 src/lib/nsas/tests/address_entry_unittest.cc
create mode 100644 src/lib/nsas/tests/fetchable_unittest.cc
create mode 100644 src/lib/nsas/tests/hash_deleter_unittest.cc
create mode 100644 src/lib/nsas/tests/hash_key_unittest.cc
create mode 100644 src/lib/nsas/tests/hash_table_unittest.cc
create mode 100644 src/lib/nsas/tests/hash_unittest.cc
create mode 100644 src/lib/nsas/tests/lru_list_unittest.cc
create mode 100644 src/lib/nsas/tests/nameserver_address_store_unittest.cc
create mode 100644 src/lib/nsas/tests/nameserver_address_unittest.cc
create mode 100644 src/lib/nsas/tests/nameserver_entry_unittest.cc
create mode 100644 src/lib/nsas/tests/nsas_entry_compare_unittest.cc
create mode 100644 src/lib/nsas/tests/nsas_test.h
create mode 100644 src/lib/nsas/tests/random_number_generator_unittest.cc
create mode 100644 src/lib/nsas/tests/run_unittests.cc
create mode 100644 src/lib/nsas/tests/zone_entry_unittest.cc
create mode 100644 src/lib/nsas/zone_entry.cc
create mode 100644 src/lib/nsas/zone_entry.h
create mode 100644 src/lib/python/isc/datasrc/tests/Makefile.am
create mode 100644 src/lib/python/isc/datasrc/tests/master_test.py
create mode 100644 src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py
copy src/lib/{ => python/isc}/datasrc/tests/testdata/brokendb.sqlite3 (100%)
copy src/lib/{datasrc/tests/testdata/test.sqlite3 => python/isc/datasrc/tests/testdata/example.com.sqlite3} (100%)
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/Makefile.am
create mode 100644 src/lib/resolve/recursive_query.cc
create mode 100644 src/lib/resolve/recursive_query.h
create mode 100644 src/lib/resolve/resolve.cc
create mode 100644 src/lib/resolve/resolve.h
create mode 100644 src/lib/resolve/resolver_callback.cc
create mode 100644 src/lib/resolve/resolver_callback.h
create mode 100644 src/lib/resolve/resolver_interface.h
create mode 100644 src/lib/resolve/response_classifier.cc
create mode 100644 src/lib/resolve/response_classifier.h
create mode 100644 src/lib/resolve/tests/Makefile.am
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/resolve/tests/resolve_unittest.cc
create mode 100644 src/lib/resolve/tests/resolver_callback_unittest.cc
create mode 100644 src/lib/resolve/tests/response_classifier_unittest.cc
create mode 100644 src/lib/resolve/tests/run_unittests.cc
create mode 100644 src/lib/server_common/Makefile.am
create mode 100644 src/lib/server_common/portconfig.cc
create mode 100644 src/lib/server_common/portconfig.h
create mode 100644 src/lib/server_common/tests/Makefile.am
create mode 100644 src/lib/server_common/tests/portconfig_unittest.cc
create mode 100644 src/lib/server_common/tests/run_unittests.cc
create mode 100644 src/lib/testutils/Makefile.am
create mode 100644 src/lib/testutils/README
create mode 100644 src/lib/testutils/dnsmessage_test.cc
create mode 100644 src/lib/testutils/dnsmessage_test.h
create mode 100644 src/lib/testutils/mockups.h
create mode 100644 src/lib/testutils/portconfig.h
create mode 100644 src/lib/testutils/srv_test.cc
create mode 100644 src/lib/testutils/srv_test.h
create mode 100644 src/lib/testutils/testdata/Makefile.am
copy src/{bin/auth/tests => lib/testutils}/testdata/badExampleQuery_fromWire.spec (100%)
copy src/{bin/auth/tests => lib/testutils}/testdata/example.com (100%)
create mode 100644 src/lib/testutils/testdata/example.com.zone
create mode 100644 src/lib/testutils/testdata/example.net.zone
create mode 100644 src/lib/testutils/testdata/example.org.zone
copy src/{bin/auth/tests => lib/testutils}/testdata/example.sqlite3 (100%)
create mode 100644 src/lib/testutils/testdata/example.zone
copy src/{bin/auth/tests => lib/testutils}/testdata/examplequery_fromWire.spec (100%)
create mode 100644 src/lib/testutils/testdata/iquery_fromWire.spec
create mode 100644 src/lib/testutils/testdata/iquery_response_fromWire.spec
copy src/{bin/auth/tests => lib/testutils}/testdata/iqueryresponse_fromWire.spec (100%)
copy src/{bin/auth/tests => lib/testutils}/testdata/multiquestion_fromWire.spec (100%)
copy src/{bin/auth/tests => lib/testutils}/testdata/queryBadEDNS_fromWire.spec (100%)
copy src/{bin/auth/tests => lib/testutils}/testdata/shortanswer_fromWire.spec (100%)
copy src/{bin/auth/tests => lib/testutils}/testdata/shortmessage_fromWire (100%)
copy src/{bin/auth/tests => lib/testutils}/testdata/shortquestion_fromWire (100%)
copy src/{bin/auth/tests => lib/testutils}/testdata/shortresponse_fromWire (100%)
copy src/{bin/auth/tests => lib/testutils}/testdata/simplequery_fromWire.spec (100%)
copy src/{bin/auth/tests => lib/testutils}/testdata/simpleresponse_fromWire.spec (100%)
create mode 100644 src/lib/testutils/testdata/test1-broken.zone.in
create mode 100644 src/lib/testutils/testdata/test1-new.zone.in
create mode 100644 src/lib/testutils/testdata/test1.zone.in
create mode 100644 src/lib/testutils/testdata/test2-new.zone.in
create mode 100644 src/lib/testutils/testdata/test2.zone.in
create mode 100644 tests/Makefile.am
create mode 100644 tests/system/Makefile.am
create mode 100644 tests/system/README
create mode 100755 tests/system/bindctl/clean.sh
create mode 100644 tests/system/bindctl/nsx1/b10-config.db.template.in
create mode 100644 tests/system/bindctl/nsx1/example-normalized.db
create mode 100644 tests/system/bindctl/nsx1/root.db
create mode 100755 tests/system/bindctl/setup.sh
create mode 100755 tests/system/bindctl/tests.sh
create mode 100755 tests/system/cleanall.sh
create mode 100644 tests/system/common/default_user.csv
create mode 100755 tests/system/conf.sh.in
create mode 100644 tests/system/glue/auth.good
create mode 100755 tests/system/glue/clean.sh
create mode 100644 tests/system/glue/example.good
create mode 100644 tests/system/glue/noglue.good
create mode 100644 tests/system/glue/nsx1/b10-config.db.in
create mode 100644 tests/system/glue/nsx1/com.db
create mode 100644 tests/system/glue/nsx1/net.db
create mode 100644 tests/system/glue/nsx1/root-servers.nil.db
create mode 100644 tests/system/glue/nsx1/root.db
create mode 100755 tests/system/glue/setup.sh.in
create mode 100644 tests/system/glue/test.good
create mode 100755 tests/system/glue/tests.sh
create mode 100755 tests/system/ifconfig.sh
create mode 100755 tests/system/run.sh
create mode 100755 tests/system/runall.sh
create mode 100755 tests/system/start.pl
create mode 100755 tests/system/stop.pl
delete mode 100755 tools/import_boost.sh
create mode 100755 tools/tests_in_valgrind.sh
create mode 100755 tools/valgrind_test_cleaner.pl
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 85a3280..0c92bb8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,9 +1,628 @@
+ 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)
+
+ 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
+ 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
+ Listen on standard domain port 53 for b10-auth and
+ b10-resolver.
+ (Trac #617, #618, git 137a6934a14cf0c5b5c065e910b8b364beb0973f)
+
+ 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
+ perl, an inherited dependency from the original framework.
+ Also, mainly for the purpose of tests, a new option "--pid-file"
+ was added to BoB, with which the boss process will dump its PID
+ to the specified file.
+ (Trac #606, git 6ac000df85625f5921e8895a1aafff5e4be3ba9c)
+
+ 190. [func] jelte
+ Resolver now sets random qids on outgoing queries using
+ the boost::mt19937 prng.
+ (Trac #583, git 5222b51a047d8f2352bc9f92fd022baf1681ed81)
+
+ 189. [bug] jreed
+ Do not install the log message compiler.
+ (Trac #634, git eb6441aca464980d00e3ff827cbf4195c5a7afc5)
+
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ (at least with cppcheck 1.47).
+ Note: the suppression list isn't included in the final
+ distributions. It should be created by hand or retrieved from
+ the git repository.
+ (Trac #613, git b973f67520682b63ef38b1451d309be9f4f4b218)
+
+ 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
+ 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
+ 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
+ Resolver now makes (limited) use of the cache
+ (Trac #491, git 8b41f77f0099ddc7ca7d34d39ad8c39bb1a8363c)
+
+ 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
+ src/lib/cache: Rename one interface: from lookupClosestRRset()
+ to lookupDeepestNS(), and remove one parameter of it.
+ (Trac #492, git ecbfb7cf929d62a018dd4cdc7a841add3d5a35ae)
+
+ 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
+ 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").
+ Also handled 32-bit wrap around issues more explicitly, with more
+ detailed tests. The function API has been changed, but the effect
+ should be minimal because these functions are mostly private.
+ (Trac #61, git 09ece8cdd41c0f025e8b897b4883885d88d4ba5d)
+
+ 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
+ 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
+ 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,
+ git 6f031a09a248e7684723c000f3e8cc981dcdb349)
+
+ 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
+ Added a basic implementation for a resolver cache (though not
+ used yet).
+ (Trac #449, git 8aa3b2246ae095bbe7f855fd11656ae3bdb98986)
+
+ 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
+ Fixed failure of termination of msgq_test.py with python3
+ coverage(3.3.1)
+ (Trac #573, git 0e6a18e12f61cc482e07078776234f32605312e5)
+
+ 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
+ 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
+ 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
+ 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
+ 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.
+ The contents of the dictionary can be replaced at run-time by
+ locale-specific messages. A message compiler program is provided
+ to create message header files and supply the default messages.
+ (Trac #438, git 7b1606cea7af15dc71f5ec1d70d958b00aa98af7)
+
+ 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
+ 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)
+
+ 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
+ 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
+ contain glue addresses at this point, and does no other processing
+ of authoritative answers.
+ (Trac #484, git 7b84de4c0e11f4a070e038ca4f093486e55622af)
+
+ 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
+ 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
+ Miscellaneous documentation improvements for man pages and
+ the guide, including auth, resolver, stats, xfrout, and
+ zonemgr. (git c14c4741b754a1eb226d3bdc3a7abbc4c5d727c0)
+
+ 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
+ on CC channels would stall (as noted in #420 and #513). This is
+ an urgent care fix due to the severity of the problem; we'll need
+ to revisit it for cleaner fix later.
+ (Trac #516, git 62c72fcdf4617e4841e901408f1e7961255b8194)
+
+ 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
+ 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
+ the value to 0. Disabling statistics updates will also work as
+ a temporary workaround of a known issue that b10-auth can block in
+ sending statistics and stop responding to queries as a result.
+ (Trac #513, git 285c5ee3d5582ed6df02d1aa00387f92a74e3695)
+
+ 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)
+
+ 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
+ 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)
+
+ 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
+ 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
+ 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
+ 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
+ zone "example.com" via bindctl, execute the command as follows:
+ > Auth loadzone origin = example.com
+ (Trac #467 git 4f7e1f46da1046de527ab129a88f6aad3dba7562
+ from 1d7d3918661ba1c6a8b1e40d8fcbc5640a84df12)
+
+ 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
+ script with CXX_LIBTOOL_LDFLAGS=-L/usr/lib (the path actually
+ doesn't matter; the important part is the -L flag). This
+ workaround is not automatically enabled as it's difficult to
+ detect the need for it dynamically, and must be enabled via the
+ variable by hand.
+ (Trac #474, git cfde436fbd7ddf3f49cbbd153999656e8ca2a298)
+
+ 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
+ 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
+ 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
+ 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.
+ The counters can be seen using the "Stats show" command from
+ bindctl. The result would look like:
+ ... "auth.queries.tcp": 1, "auth.queries.udp": 1 ...
+ Using the "Auth sendstats" command you can make b10-auth send the
+ counters to b10-stats immediately.
+ (Trac #347, svn r4026)
+
+ 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
+ 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"
+ zone with the zone file named "example.com.zone":
+ > config set Auth/datasources/ [{"type": "memory", "zones": \
+ [{"origin": "example.com", "file": "example.com.zone"}]}]
+ By default, the memory data source is disabled; it must be
+ configured explicitly. To disable it again, specify a null list
+ for Auth/datasources:
+ > config set Auth/datasources/ []
+ Notes: it's currently for class IN only. The zone files are not
+ actually loaded into memory yet (which will soon be implemented).
+ This is an experimental feature and the syntax may change in
+ future versions.
+ (Trac #446, svn r3998)
+
+ 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
+ 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
+ Add b10-resolver. This is an example recursive server that
+ currently does forwarding only and no caching.
+ (Trac #327, svn r3903)
+
+ 134. [func] vorner
+ b10-resolver supports timeouts and retries in forwarder mode.
+ (Trac #401, svn r3660)
+
+ 133. [func] vorner
+ New temporary logging function available in isc::log. It is used by
+ b10-resolver.
+ (Trac #393, r3602)
+
+ 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
+ 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
+ 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).
+ (Trac #422, svn r3866)
+
+ 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
+ Test for query name = '.', type = DS to authoritative nameserver
+ for root zone was added.
+ (Trac #85, svn r3836)
+
+ 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
+ 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.
+ (Trac #356, Trac #408, svn r3823)
+
+bind10-devel-20101201 released on December 01, 2010
+
+ 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
+ (starting at 0)
+ (Trac #405, svn r3739)
+
+ 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
+ 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
+ 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
+ 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
+ src/lib/dns: introduced two new classes, TSIGKey and TSIGKeyRing,
+ to manage TSIG keys. (Trac #381, svn r3622)
+
+ 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
+ load a zone file that contains such a name.
+ Note: this fix is incomplete and the loadzone would still be
+ confused if the owner name is a syntactically indistinguishable
+ 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
+ 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
+ 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
+ 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
+ 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
Supported clang++. Note: Boost >= 1.44 is required.
(Trac #365, svn r3383)
113. [func]* zhanglikun
- Folder name 'utils'(the folder in /src/lib/python/isc/) has been
+ 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
@@ -11,17 +630,15 @@
112. [func] zhang likun
Add one mixin class to override the naive serve_forever() provided
- in python library socketserver. Instead of polling for shutdwon
+ 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)
+ the waiting server. (Trac #352, svn r3366)
111. [bug]* zhanglikun, Michal Vaner
- Make sure process xfrin/xfrout/zonemgr/cmdctl can be stoped
+ 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.
+ through bindctl. The ZonemgrRefresh.run_timer and
+ NotifyOut.dispatcher spawn a thread themselves.
(Trac #335, svn r3273)
110. [func] Michal Vaner
@@ -33,7 +650,7 @@
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)
+ 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])
@@ -60,11 +677,11 @@
104. [bug] jerry
bin/zonemgr: zonemgr should be attempting to refresh expired zones.
(Trac #336, r3139)
-
+
103. [bug] jerry
lib/python/isc/log: Fixed an issue with python logging,
- python log shouldn't die with OSError.(Trac #267, r3137)
-
+ python log shouldn't die with OSError. (Trac #267, r3137)
+
102. [build] jinmei
Disable threads in ASIO to minimize build time dependency.
(Trac #345, r3100)
@@ -117,7 +734,7 @@ bind10-devel-20100917 released on September 17, 2010
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)
+ is stored in the hot spot cache. (Trac #307, svn r2923)
92. [func]* jelte
libdns_python (the python wrappers for libdns++) has been renamed
@@ -297,7 +914,7 @@ bind10-devel-20100701 released on July 1, 2010
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,
+ of the same CNAME from showing up when there's a loop. (Trac #69,
svn r2350)
65. [func] shentingting
@@ -419,7 +1036,7 @@ bind10-devel-20100602 released on June 2, 2010
#205, svn r1957)
44. [build] jreed
- Install headers for libdns and libexception. (Trac #68,
+ Install headers for libdns and libexception. (Trac #68,
svn r1941)
43. [func] jelte
@@ -427,7 +1044,7 @@ bind10-devel-20100602 released on June 2, 2010
42. [func] jelte
lib/python/isc/config: Make temporary file with python
- tempfile module instead of manual with fixed name. (Trac
+ tempfile module instead of manual with fixed name. (Trac
#184, svn r1859)
41. [func] jelte
@@ -435,7 +1052,7 @@ bind10-devel-20100602 released on June 2, 2010
40. [build] jreed
Report detected features and configure settings at end of
- configure output. (svn r1836)
+ configure output. (svn r1836)
39. [func]* each
Renamed libauth to libdatasrc.
@@ -448,7 +1065,7 @@ bind10-devel-20100602 released on June 2, 2010
(Trac #135, #151, #134, svn r1797)
37. [build] jinmei
- Check for the availability of python-config. (Trac #159,
+ Check for the availability of python-config. (Trac #159,
svn r1794)
36. [func] shane
@@ -493,7 +1110,7 @@ bind10-devel-20100421 released on April 21, 2010
27. [build]
Add missing copyright license statements to various source
- files. (svn r1750)
+ files. (svn r1750)
26. [func]
Use PACKAGE_STRING (name + version) from config.h instead
@@ -594,8 +1211,12 @@ bind10-devel-20100421 released on April 21, 2010
bind10-devel-20100319 released on March 19, 2010
For complete code revision history, see http://bind10.isc.org/browser
-Specific subversion changesets can be accessed at:
- http://bind10.isc.org/changeset/rrrr
+Specific git changesets can be accessed at:
+ http://bind10.isc.org/changeset/?reponame=&old=rrrr^&new=rrrr
+or after cloning the original git repository by executing:
+ % git diff rrrr^ rrrr
+Subversion changesets are not accessible any more. The subversion
+revision numbers will be replaced with corresponding git revisions.
Trac tickets can be accessed at: https://bind10.isc.org/ticket/nnn
LEGEND
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/Makefile.am b/Makefile.am
index 7069f1d..e31a1a5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = src
+SUBDIRS = doc src tests
USE_LCOV=@USE_LCOV@
LCOV=@LCOV@
GENHTML=@GENHTML@
@@ -8,20 +8,30 @@ DISTCLEANFILES = config.report
# When running distcheck target, do not install the configurations
DISTCHECK_CONFIGURE_FLAGS = --disable-install-configurations
-clean-coverage:
+clean-cpp-coverage:
@if [ $(USE_LCOV) = yes ] ; then \
$(LCOV) --directory . --zerocounters; \
rm -rf coverage/; \
else \
- echo "Code coverage not enabled at configuration time"; \
- exit 1; \
+ echo "C++ code coverage not enabled at configuration time." ; \
+ echo "Use: ./configure --with-lcov" ; \
+ fi
+
+clean-python-coverage:
+ @if [ $(USE_PYCOVERAGE) = yes ] ; then \
+ rm -f $(abs_top_srcdir)/.coverage ; \
+ rm -rf $(abs_top_srcdir)/py-coverage-html ; \
+ else \
+ echo "Python code coverage not enabled at configuration time." ; \
+ echo "Use: ./configure --with-pycoverage" ; \
fi
perform-coverage: check
-report-coverage:
- $(LCOV) --capture --directory . --output-file all.info
- $(LCOV) --remove all.info \
+report-cpp-coverage:
+ @if [ $(USE_LCOV) = yes ] ; then \
+ $(LCOV) --capture --directory . --output-file all.info ; \
+ $(LCOV) --remove all.info \
c++/4.4\*/\* \
c++/4.4\*/backward/\* \
c++/4.4\*/bits/\* \
@@ -36,11 +46,42 @@ report-coverage:
\*_unittests.cc \
\*_unittest.cc \
\*_unittests.h \
- --output report.info
- $(GENHTML) -o coverage report.info
+ --output report.info ; \
+ $(GENHTML) --legend -o $(abs_top_builddir)/coverage-cpp-html report.info ; \
+ echo "Generated C++ Code Coverage report in HTML at $(abs_top_builddir)/coverage-cpp-html" ; \
+ else \
+ echo "C++ code coverage not enabled at configuration time." ; \
+ echo "Use: ./configure --with-lcov" ; \
+ fi
+
+report-python-coverage:
+ @if [ $(USE_PYCOVERAGE) = yes ] ; then \
+ $(PYCOVERAGE) html -d $(abs_top_builddir)/coverage-python-html --omit=src/bin/bind10/tests/,src/bin/bindctl/tests/,src/bin/cfgmgr/tests/,src/bin/cmdctl/tests/,src/bin/loadzone/tests/,src/bin/msgq/tests/,src/bin/stats/tests/,src/bin/tests/,src/bin/xfrin/tests/,src/bin/xfrout/tests/,src/bin/zonemgr/tests/,src/lib/dns/python/tests/,src/lib/dns/tests/,src/lib/python/isc/cc/tests/,src/lib/python/isc/config/tests/,src/lib/python/isc/datasrc/tests/,src/lib/python/isc/log/tests/,src/lib/python/isc/net/tests/,src/lib/python/isc/notify/tests/,src/lib/python/isc/util/tests/ ; \
+ echo "Generated Python Code Coverage report in HTML at $(abs_top_builddir)/coverage-python-html" ; \
+ else \
+ echo "Python code coverage not enabled at configuration time." ; \
+ echo "Use: ./configure --with-pycoverage" ; \
+ fi
+# for python and c++ test coverage
coverage: clean-coverage perform-coverage report-coverage
+clean-coverage: clean-cpp-coverage clean-python-coverage
+
+report-coverage: report-cpp-coverage report-python-coverage
+
+# for static C++ check using cppcheck (when available)
+cppcheck:
+ cppcheck --enable=all --suppressions src/cppcheck-suppress.lst \
+ --quiet --error-exitcode=1 \
+ --template '{file}:{line}: check_fail: {message} ({severity},{id})' \
+ src
+
+# system tests
+systest:
+ cd tests/system; \
+ sh $(abs_srcdir)/tests/system/runall.sh
+
#### include external sources in the distributed tarball:
EXTRA_DIST = ext/asio/README
EXTRA_DIST += ext/asio/asio/local/stream_protocol.hpp
@@ -253,9 +294,4 @@ EXTRA_DIST += ext/asio/asio/is_write_buffered.hpp
EXTRA_DIST += ext/asio/asio/buffered_read_stream_fwd.hpp
EXTRA_DIST += ext/asio/asio/socket_acceptor_service.hpp
EXTRA_DIST += ext/asio/asio.hpp
-
-## include the guide in tarball, later will include the other parts there
-## but they cleanup.
-EXTRA_DIST += doc/guide/bind10-guide.css
-EXTRA_DIST += doc/guide/bind10-guide.html
-EXTRA_DIST += doc/guide/bind10-guide.xml
+EXTRA_DIST += ext/coroutine/coroutine.h
diff --git a/README b/README
index 496297f..5320a6e 100644
--- a/README
+++ b/README
@@ -14,11 +14,12 @@ five year plan are described here:
https://bind10.isc.org/wiki/Year2Milestones
This release includes the bind10 master process, b10-msgq message
-bus, b10-auth authoritative DNS server (with SQLite3 backend),
-b10-cmdctl remote control daemon, b10-cfgmgr configuration manager,
-b10-xfrin AXFR inbound service, b10-xfrout outgoing AXFR service,
-b10-zonemgr secondary manager, and a new libdns++ library for C++
-with a python wrapper.
+bus, b10-auth authoritative DNS server (with SQLite3 and in-memory
+backends), b10-resolver recursive or forwarding DNS server, b10-cmdctl
+remote control daemon, b10-cfgmgr configuration manager, b10-xfrin
+AXFR inbound service, b10-xfrout outgoing AXFR service, b10-zonemgr
+secondary manager, b10-stats statistics collection and reporting
+daemon, and a new libdns++ library for C++ with a python wrapper.
Documentation is included and also available via the BIND 10
website at http://bind10.isc.org/
@@ -47,7 +48,7 @@ Simple build instructions:
./configure
make
-If building from Subversion repository, run:
+If building from Git repository, run:
autoreconf --install
@@ -92,26 +93,55 @@ Then run "make check" to run these tests.
TEST COVERAGE
+Code coverage reports may be generated using make. These are
+based on running on the unit tests. The resulting reports are placed
+in coverage-cpp-html and coverage-python-html directories for C++
+and Python, respectively.
+
The code coverage report for the C++ tests uses LCOV. It is available
-from http://ltp.sourceforge.net/. To generate your own HTML report,
+from http://ltp.sourceforge.net/. To generate the HTML report,
first configure BIND 10 with:
./configure --with-lcov
+The code coverage report for the Python tests uses coverage.py (aka
+pycoverage). It is available from http://nedbatchelder.com/code/coverage/.
+To generate the HTML report, first configure BIND 10 with:
+
+ ./configure --with-pycoverage
+
Doing code coverage tests:
make coverage
- Does the following:
+ Does the clean, perform, and report targets for C++ and Python.
make clean-coverage
- Zeroes the lcov code coverage counters and removes the coverage HTML.
+ Zeroes the code coverage counters and removes the HTML reports
+ for C++ and Python.
make perform-coverage
- Runs the C++ tests (using googletests framework).
+ Runs the C++ (using the googletests framework) and Python
+ tests.
make report-coverage
- Generates the coverage HTML, excluding some unrelated headers.
- The HTML reports are placed in a directory called coverage/.
+ Generates the coverage reports in HTML for C++ and Python.
+
+ make clean-cpp-coverage
+ Zeroes the code coverage counters and removes the HTML report
+ for the C++ tests.
+
+ make clean-python-coverage
+ Zeroes the code coverage counters and removes the HTML report
+ for the Python tests.
+
+ make report-cpp-coverage
+ Generates the coverage report in HTML for C++, excluding
+ some unrelated headers. The HTML reports are placed in a
+ directory called coverage-cpp-html/.
+
+ make report-python-coverage
+ Generates the coverage report in HTML for Python. The HTML
+ reports are placed in a directory called coverage-python-html/.
DEVELOPERS
@@ -134,8 +164,6 @@ source tree:
(Which will use the modules and configurations also from the source
tree.)
-The server will listen on port 5300 for DNS requests.
-
CONFIGURATION
Commands can be given through the bindctl tool.
@@ -163,6 +191,7 @@ config revert: Revert all changes that have not been committed
config commit: Commit all changes
config diff: Show the changes that have not been committed yet
+
EXAMPLE SESSION
~> bindctl
diff --git a/configure.ac b/configure.ac
index ee442fd..31f64b3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,14 +2,34 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.59])
-AC_INIT(bind10-devel, 20101013, 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])
# Checks for programs.
AC_PROG_CXX
+
+# Libtool configuration
+#
+# On FreeBSD (and probably some others), clang++ does not meet an autoconf
+# assumption in identifying libtool configuration regarding shared library:
+# the configure script will execute "$CC -shared $CFLAGS/$CXXFLAGS -v" and
+# expect the output contains -Lxxx or -Ryyy. This is the case for g++, but
+# not for clang++, and, as a result, it will cause various errors in linking
+# programs or running them with a shared object (such as some of our python
+# scripts).
+# To work around this problem we define a temporary variable
+# "CXX_LIBTOOL_LDFLAGS". It's expected to be defined as, e.g, "-L/usr/lib"
+# to temporarily fake the output so that it will be compatible with that of
+# g++.
+CFLAGS_SAVED=$CFLAGS
+CXXFLAGS_SAVED=$CXXFLAGS
+CFLAGS="$CFLAGS $CXX_LIBTOOL_LDFLAGS"
+CXXFLAGS="$CXXFLAGS $CXX_LIBTOOL_LDFLAGS"
AC_PROG_LIBTOOL
+CFLAGS=$CFLAGS_SAVED
+CXXFLAGS=$CXXFLAGS_SAVED
# Use C++ language
AC_LANG([C++])
@@ -50,6 +70,11 @@ if test $enable_shared = no; then
AC_MSG_ERROR([BIND 10 requires shared libraries to be built])
fi
+AC_ARG_ENABLE(boost-threads,
+AC_HELP_STRING([--enable-boost-threads],
+ [use boost threads. Currently this only means using its locks instead of dummy locks, in the cache and NSAS]),
+ use_boost_threads=$enableval, use_boost_threads=no)
+
# allow configuring without setproctitle.
AC_ARG_ENABLE(setproctitle-check,
AC_HELP_STRING([--disable-setproctitle-check],
@@ -67,7 +92,7 @@ case "$host" in
CPPFLAGS="$CPPFLAGS -D_XPG4_2 -D__EXTENSIONS__"
;;
*-apple-darwin*)
- # libtool doesn't work pefectly with Darwin: libtool embeds the
+ # libtool doesn't work perfectly with Darwin: libtool embeds the
# final install path in dynamic libraries and our loadable python
# modules always refer to that path even if it's loaded within the
# source tree. This prevents pre-install tests from working.
@@ -177,8 +202,9 @@ if test "$setproctitle_check" = "yes" ; then
AC_MSG_RESULT(ok)
else
AC_MSG_RESULT(missing)
- AC_MSG_ERROR([Missing setproctitle module. Either install it or provide --disable-setproctitle-check.
-In that case we will continue, but naming of python processes will not work.])
+ AC_MSG_WARN([Missing setproctitle python module.
+Use --disable-setproctitle-check to skip this check.
+In this case we will continue, but naming of python processes will not work.])
fi
fi
@@ -186,7 +212,7 @@ fi
# Compiler dependent settings: define some mandatory CXXFLAGS here.
# We also use a separate variable B10_CXXFLAGS. This will (and should) be
-# used as the default value for each specifc AM_CXXFLAGS:
+# used as the default value for each specific AM_CXXFLAGS:
# AM_CXXFLAGS = $(B10_CXXFLAGS)
# AM_CXXFLAGS += ... # add module specific flags
# We need this so that we can disable some specific compiler warnings per
@@ -195,18 +221,50 @@ fi
# specify the default warning flags in CXXFLAGS and let specific modules
# "override" the default.
+# This may be used to try linker flags.
+AC_DEFUN([BIND10_CXX_TRY_FLAG], [
+ AC_MSG_CHECKING([whether $CXX supports $1])
+
+ bind10_save_CXXFLAGS="$CXXFLAGS"
+ CXXFLAGS="$CXXFLAGS $1"
+
+ AC_LINK_IFELSE([int main(void){ return 0;} ],
+ [bind10_cxx_flag=yes], [bind10_cxx_flag=no])
+ CXXFLAGS="$bind10_save_CXXFLAGS"
+
+ if test "x$bind10_cxx_flag" = "xyes"; then
+ ifelse([$2], , :, [$2])
+ else
+ ifelse([$3], , :, [$3])
+ fi
+
+ AC_MSG_RESULT([$bind10_cxx_flag])
+])
+
werror_ok=0
# SunStudio compiler requires special compiler options for boost
# (http://blogs.sun.com/sga/entry/boost_mini_howto)
if test "$SUNCXX" = "yes"; then
CXXFLAGS="$CXXFLAGS -library=stlport4 -features=tmplife -features=tmplrefstatic"
+MULTITHREADING_FLAG="-mt"
fi
+BIND10_CXX_TRY_FLAG(-Wno-missing-field-initializers,
+ [WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG="-Wno-missing-field-initializers"])
+AC_SUBST(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
# gcc specific settings:
if test "X$GXX" = "Xyes"; then
B10_CXXFLAGS="-Wall -Wextra -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare"
-UNUSED_PARAM_ATTRIBUTE='__attribute__((unused))'
+case "$host" in
+*-solaris*)
+ MULTITHREADING_FLAG=-pthreads
+ ;;
+*)
+ MULTITHREADING_FLAG=-pthread
+ ;;
+esac
# Certain versions of gcc (g++) have a bug that incorrectly warns about
# the use of anonymous name spaces even if they're closed in a single
@@ -225,7 +283,6 @@ CXXFLAGS="$CXXFLAGS_SAVED"
fi dnl GXX = yes
AM_CONDITIONAL(GCC_WERROR_OK, test $werror_ok = 1)
-AC_DEFINE_UNQUOTED(UNUSED_PARAM, $UNUSED_PARAM_ATTRIBUTE, Define to compiler keyword indicating a function argument is intentionally unused)
# produce PIC unless we disable shared libraries. need this for python bindings.
if test $enable_shared != "no" -a "X$GXX" = "Xyes"; then
@@ -238,6 +295,7 @@ AC_SUBST(B10_CXXFLAGS)
AC_SEARCH_LIBS(inet_pton, [nsl])
AC_SEARCH_LIBS(recvfrom, [socket])
+AC_SEARCH_LIBS(nanosleep, [rt])
# Checks for header files.
@@ -256,6 +314,26 @@ AC_TRY_COMPILE([
AC_DEFINE(HAVE_SA_LEN, 1, [Define to 1 if sockaddr has a sa_len member, and corresponding sin_len and sun_len])],
AC_MSG_RESULT(no))
+AC_ARG_WITH(pycoverage,
+[ --with-pycoverage[=PROGRAM] enable python code coverage using the specified coverage], pycoverage="$withval", pycoverage="no")
+if test "$pycoverage" = "no" ; then
+ # just run the tests normally with python
+ PYCOVERAGE_RUN="${PYTHON}"
+ USE_PYCOVERAGE="no"
+elif test "$pycoverage" = "yes" ; then
+ PYCOVERAGE="coverage"
+ PYCOVERAGE_RUN="${PYCOVERAGE} run --branch --append"
+ USE_PYCOVERAGE="yes"
+else
+ PYCOVERAGE="$pycoverage"
+ PYCOVERAGE_RUN="${PYCOVERAGE} run --branch --append"
+ USE_PYCOVERAGE="yes"
+fi
+AM_CONDITIONAL(ENABLE_PYTHON_COVERAGE, test x$USE_PYCOVERAGE != xno)
+AC_SUBST(PYCOVERAGE)
+AC_SUBST(PYCOVERAGE_RUN)
+AC_SUBST(USE_PYCOVERAGE)
+
AC_ARG_WITH(lcov,
[ --with-lcov[=PROGRAM] enable gtest and coverage target using the specified lcov], lcov="$withval", lcov="no")
@@ -320,11 +398,74 @@ if test "${boost_include_path}" ; then
BOOST_INCLUDES="-I${boost_include_path}"
CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
fi
-AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp],,
+AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
AC_MSG_ERROR([Missing required header files.]))
CPPFLAGS="$CPPFLAGS_SAVES"
AC_SUBST(BOOST_INCLUDES)
+
+if test "${use_boost_threads}" = "yes" ; then
+ AC_DEFINE([USE_BOOST_THREADS], [], [Use boost threads])
+
+ # Using boost::mutex can result in requiring libboost_thread with older
+ # versions of Boost. We'd like to avoid relying on a compiled Boost library
+ # whenever possible, so we need to check for it step by step.
+ #
+ # NOTE: another fix of this problem is to simply require newer versions of
+ # boost. If we choose that solution we should simplify the following tricky
+ # checks accordingly and all Makefile.am's that refer to NEED_LIBBOOST_THREAD.
+ AC_MSG_CHECKING(for boost::mutex)
+ CPPFLAGS_SAVES="$CPPFLAGS"
+ LIBS_SAVES="$LIBS"
+ CPPFLAGS="$BOOST_INCLUDES $CPPFLAGS $MULTITHREADING_FLAG"
+ need_libboost_thread=0
+ need_sunpro_workaround=0
+ AC_TRY_LINK([
+ #include <boost/thread.hpp>
+ ],[
+ boost::mutex m;
+ ],
+ [ AC_MSG_RESULT(yes (without libboost_thread)) ],
+ # there is one specific problem with SunStudio 5.10
+ # where including boost/thread causes a compilation failure
+ # There is a workaround in boost but it checks the version not being 5.10
+ # This will probably be fixed in the future, in which case this
+ # is only a temporary workaround
+ [ AC_TRY_LINK([
+ #if defined(__SUNPRO_CC) && __SUNPRO_CC == 0x5100
+ #undef __SUNPRO_CC
+ #define __SUNPRO_CC 0x5090
+ #endif
+ #include <boost/thread.hpp>
+ ],[
+ boost::mutex m;
+ ],
+ [ AC_MSG_RESULT(yes (with SUNOS workaround))
+ need_sunpro_workaround=1 ],
+ [ LIBS=" $LIBS -lboost_thread"
+ AC_TRY_LINK([
+ #include <boost/thread.hpp>
+ ],[
+ boost::mutex m;
+ ],
+ [ AC_MSG_RESULT(yes (with libboost_thread))
+ need_libboost_thread=1 ],
+ [ AC_MSG_RESULT(no)
+ AC_MSG_ERROR([boost::mutex cannot be linked in this build environment.
+ Perhaps you are using an older version of Boost that requires libboost_thread for the mutex support, which does not appear to be available.
+ You may want to check the availability of the library or to upgrade Boost.])
+ ])])])
+ CPPFLAGS="$CPPFLAGS_SAVES"
+ LIBS="$LIBS_SAVES"
+ AM_CONDITIONAL(NEED_LIBBOOST_THREAD, test $need_libboost_thread = 1)
+ if test $need_sunpro_workaround = 1; then
+ AC_DEFINE([NEED_SUNPRO_WORKAROUND], [], [Need boost sunstudio workaround])
+ fi
+else
+ AM_CONDITIONAL(NEED_LIBBOOST_THREAD, test "${use_boost_threads}" = "yes")
+fi
+
+
#
# Check availability of gtest, which will be used for unit tests.
#
@@ -390,6 +531,8 @@ PTHREAD_LDFLAGS=
AC_CHECK_LIB(pthread, pthread_create,[ PTHREAD_LDFLAGS=-lpthread ], [])
AC_SUBST(PTHREAD_LDFLAGS)
+AC_SUBST(MULTITHREADING_FLAG)
+
#
# ASIO: we extensively use it as the C++ event management module.
#
@@ -397,6 +540,9 @@ AC_SUBST(PTHREAD_LDFLAGS)
#
CPPFLAGS="$CPPFLAGS -I\$(top_srcdir)/ext/asio"
#
+# Use our 'coroutine' header from ext
+CPPFLAGS="$CPPFLAGS -I\$(top_srcdir)/ext/coroutine"
+#
# Disable threads: Currently we don't use them.
CPPFLAGS="$CPPFLAGS -DASIO_DISABLE_THREADS=1"
#
@@ -430,13 +576,19 @@ fi
# So, for the moment, we simply disable the use of /dev/poll. Unless we
# implement recursive DNS server with randomized ports, we don't need the
# scalability that /dev/poll can provide, so this decision wouldn't affect
-# run time performance. Hpefully we can find a better solution or the ASIO
+# run time performance. Hopefully we can find a better solution or the ASIO
# code will be updated by the time we really need it.
AC_CHECK_HEADERS(sys/devpoll.h, ac_cv_have_devpoll=yes, ac_cv_have_devpoll=no)
if test "X$ac_cv_have_devpoll" = "Xyes" -a "X$GXX" = "Xyes"; then
CPPFLAGS="$CPPFLAGS -DASIO_DISABLE_DEV_POLL=1"
fi
+#
+# Perl is optional; it is used only by some of the system test scripts.
+#
+AC_PATH_PROGS(PERL, perl5 perl)
+AC_SUBST(PERL)
+
AC_ARG_ENABLE(man, [AC_HELP_STRING([--enable-man],
[regenerate man pages [default=no]])], enable_man=yes, enable_man=no)
@@ -449,6 +601,8 @@ AC_ARG_ENABLE(install-configurations,
AM_CONDITIONAL(INSTALL_CONFIGURATIONS, test x$install_configurations = xyes || test x$install_configurations = xtrue)
AC_CONFIG_FILES([Makefile
+ doc/Makefile
+ doc/guide/Makefile
src/Makefile
src/bin/Makefile
src/bin/bind10/Makefile
@@ -467,8 +621,9 @@ AC_CONFIG_FILES([Makefile
src/bin/msgq/tests/Makefile
src/bin/auth/Makefile
src/bin/auth/tests/Makefile
- src/bin/auth/tests/testdata/Makefile
src/bin/auth/benchmarks/Makefile
+ src/bin/resolver/Makefile
+ src/bin/resolver/tests/Makefile
src/bin/xfrin/Makefile
src/bin/xfrin/tests/Makefile
src/bin/xfrout/Makefile
@@ -485,6 +640,8 @@ AC_CONFIG_FILES([Makefile
src/bin/usermgr/Makefile
src/bin/tests/Makefile
src/lib/Makefile
+ src/lib/asiolink/Makefile
+ src/lib/asiolink/tests/Makefile
src/lib/bench/Makefile
src/lib/bench/example/Makefile
src/lib/bench/tests/Makefile
@@ -495,6 +652,7 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/util/Makefile
src/lib/python/isc/util/tests/Makefile
src/lib/python/isc/datasrc/Makefile
+ src/lib/python/isc/datasrc/tests/Makefile
src/lib/python/isc/cc/Makefile
src/lib/python/isc/cc/tests/Makefile
src/lib/python/isc/config/Makefile
@@ -505,6 +663,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
@@ -519,8 +678,24 @@ AC_CONFIG_FILES([Makefile
src/lib/datasrc/Makefile
src/lib/datasrc/tests/Makefile
src/lib/xfr/Makefile
+ src/lib/log/Makefile
+ src/lib/log/compiler/Makefile
+ src/lib/log/tests/Makefile
+ src/lib/resolve/Makefile
+ src/lib/resolve/tests/Makefile
+ src/lib/testutils/Makefile
+ src/lib/testutils/testdata/Makefile
+ src/lib/nsas/Makefile
+ src/lib/nsas/tests/Makefile
+ src/lib/cache/Makefile
+ src/lib/cache/tests/Makefile
+ src/lib/server_common/Makefile
+ src/lib/server_common/tests/Makefile
+ tests/Makefile
+ tests/system/Makefile
])
-AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
+AC_OUTPUT([doc/version.ent
+ src/bin/cfgmgr/b10-cfgmgr.py
src/bin/cfgmgr/tests/b10-cfgmgr_test.py
src/bin/cmdctl/cmdctl.py
src/bin/cmdctl/run_b10-cmdctl.sh
@@ -533,6 +708,8 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
src/bin/xfrout/xfrout.spec.pre
src/bin/xfrout/tests/xfrout_test
src/bin/xfrout/run_b10-xfrout.sh
+ src/bin/resolver/resolver.spec.pre
+ src/bin/resolver/spec_config.h.pre
src/bin/zonemgr/zonemgr.py
src/bin/zonemgr/zonemgr.spec.pre
src/bin/zonemgr/tests/zonemgr_test
@@ -544,10 +721,10 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
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/run_bind10.sh
+ src/bin/bind10/tests/bind10_test.py
src/bin/bindctl/run_bindctl.sh
- src/bin/bindctl/bindctl-source.py
+ src/bin/bindctl/bindctl_main.py
src/bin/bindctl/tests/bindctl_test
src/bin/loadzone/run_loadzone.sh
src/bin/loadzone/tests/correct/correct_test.sh
@@ -571,6 +748,11 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
src/lib/dns/tests/testdata/gen-wiredata.py
src/lib/cc/session_config.h.pre
src/lib/cc/tests/session_unittests_config.h
+ src/lib/log/tests/run_time_init_test.sh
+ tests/system/conf.sh
+ tests/system/glue/setup.sh
+ tests/system/glue/nsx1/b10-config.db
+ tests/system/bindctl/nsx1/b10-config.db.template
], [
chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
chmod +x src/bin/xfrin/run_b10-xfrin.sh
@@ -594,6 +776,8 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
chmod +x src/bin/msgq/tests/msgq_test
chmod +x src/lib/dns/gen-rdatacode.py
chmod +x src/lib/dns/tests/testdata/gen-wiredata.py
+ chmod +x src/lib/log/tests/run_time_init_test.sh
+ chmod +x tests/system/conf.sh
])
AC_OUTPUT
@@ -629,7 +813,8 @@ Features:
Developer:
Google Tests: $gtest_path
- Code Coverage: $USE_LCOV
+ C++ Code Coverage: $USE_LCOV
+ Python Code Coverage: $USE_PYCOVERAGE
Generate Manuals: $enable_man
END
diff --git a/doc/Doxyfile b/doc/Doxyfile
index be85bf3..46aa178 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -568,7 +568,7 @@ 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/lib/bench
+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/
# 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/Makefile.am b/doc/Makefile.am
new file mode 100644
index 0000000..31d0cfa
--- /dev/null
+++ b/doc/Makefile.am
@@ -0,0 +1,3 @@
+SUBDIRS = guide
+
+EXTRA_DIST = version.ent.in
diff --git a/doc/guide/Makefile b/doc/guide/Makefile
deleted file mode 100644
index 23882b2..0000000
--- a/doc/guide/Makefile
+++ /dev/null
@@ -1,13 +0,0 @@
-#
-# Quick and dirty makefile
-#
-
-bind10-guide.html: bind10-guide.xml
- xsltproc --novalid --xinclude --nonet \
- -o bind10-guide.html \
- --stringparam html.stylesheet bind10-guide.css \
- http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl \
- bind10-guide.xml
-
-clean:
- rm -f bind10-guide.html
diff --git a/doc/guide/Makefile.am b/doc/guide/Makefile.am
new file mode 100644
index 0000000..c790139
--- /dev/null
+++ b/doc/guide/Makefile.am
@@ -0,0 +1,16 @@
+EXTRA_DIST = bind10-guide.css
+EXTRA_DIST += bind10-guide.html
+EXTRA_DIST += bind10-guide.xml
+
+# This is not a "man" manual, but reuse this for now for docbook.
+if ENABLE_MAN
+
+bind10-guide.html: bind10-guide.xml
+ xsltproc --novalid --xinclude --nonet \
+ --path $(top_builddir)/doc \
+ -o $@ \
+ --stringparam html.stylesheet $(srcdir)/bind10-guide.css \
+ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl \
+ $(srcdir)/bind10-guide.xml
+
+endif
diff --git a/doc/guide/bind10-guide.html b/doc/guide/bind10-guide.html
index e82dc4a..a631a9c 100644
--- a/doc/guide/bind10-guide.html
+++ b/doc/guide/bind10-guide.html
@@ -1,22 +1,26 @@
-<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="This is the reference guide for BIND 10. 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="copyright">Copyright © 2010 Internet Systems Consortium, Inc.</p></div><div><div class="abstract" title="Abstract"><p class="title"><b>Abstract</b></p><p>This is the reference guide for BIND 10.</p><p>
- 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="#id1168230299028">Supported Platforms</a></span></dt><dt><span class="section"><a href="#id1168230299056">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="#id1168230284542">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="#id1168230284728">Download Tar File</a></span></dt><dt><s
pan class="section"><a href="#id1168230284748">Retrieve from Subversion</a></span></dt><dt><span class="section"><a href="#id1168230284809">Configure before the build</a></span></dt><dt><span class="section"><a href="#id1168230284906">Build</a></span></dt><dt><span class="section"><a href="#id1168230284921">Install</a></span></dt><dt><span class="section"><a href="#id1168230284946">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 href="#cmdctl">6. Remote control daemon</a></span></dt><dd><dl><dt><span class="section"><a href="#cmdctl.spec">Configuration specifi
cation 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="#id1168230285515">Server Configurations</a></span></dt><dt><span class="section"><a href="#id1168230285580">Data Source Backends</a></span></dt><dt><span class="section"><a href="#id1168230285610">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></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="#id1168230299028">Supported Platforms</a></span></dt><dt><span class="section"><a href="#id1168230299056">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>
+<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 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="#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 version of
- BIND 10.
+ This guide covers the experimental prototype of
+ 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, at this time, does not provide a recursive
- DNS server. It does provide a EDNS0- and DNSSEC-capable
- authoritative DNS server.
- </p></div><div class="section" title="Supported Platforms"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230299028"></a>Supported Platforms</h2></div></div></div><p>
+ BIND 10 provides a EDNS0- and DNSSEC-capable
+ authoritative DNS server and a caching recursive name server
+ which also provides forwarding.
+ </p></div><div class="section" title="Supported Platforms"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230299038"></a>Supported Platforms</h2></div></div></div><p>
BIND 10 builds have been tested on Debian GNU/Linux 5,
- Ubuntu 9.10, NetBSD 5, Solaris 10, FreeBSD 7, and CentOS
+ Ubuntu 9.10, NetBSD 5, Solaris 10, FreeBSD 7 and 8, and CentOS
Linux 5.3.
It has been tested on Sparc, i386, and amd64 hardware
@@ -24,13 +28,11 @@
It is planned for BIND 10 to build, install and run on
Windows and standard Unix-type platforms.
- </p></div><div class="section" title="Required Software"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230299056"></a>Required Software</h2></div></div></div><p>
+ </p></div><div class="section" title="Required Software"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230299065"></a>Required Software</h2></div></div></div><p>
BIND 10 requires Python 3.1. Later versions may work, but Python
3.1 is the minimum version which will work.
</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
- For this development prototype release, the only supported
- data source backend is SQLite3. The authoritative server
- requires SQLite 3.3.9 or newer.
+ The authoritative server requires SQLite 3.3.9 or newer.
The <span class="command"><strong>b10-xfrin</strong></span>, <span class="command"><strong>b10-xfrout</strong></span>,
and <span class="command"><strong>b10-zonemgr</strong></span> modules require the
libpython3 library and the Python _sqlite3.so module.
@@ -73,6 +75,15 @@
Command and control service.
This process allows external control of the BIND 10 system.
</li><li class="listitem">
+ <span class="command"><strong>b10-resolver</strong></span> —
+ Recursive name server.
+ This process handles incoming queries.
+
+ </li><li class="listitem">
+ <span class="command"><strong>b10-stats</strong></span> —
+ Statistics collection daemon.
+ This process collects and reports statistics data.
+ </li><li class="listitem">
<span class="command"><strong>b10-xfrin</strong></span> —
Incoming zone transfer service.
This process is used to transfer a new copy
@@ -121,14 +132,14 @@
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="#id1168230284542">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="#id1168230284728">Download Tar File</a></span></dt><dt><span class="section"><a href="#id1168230284748">Retrieve from Subversion</a></span></dt><dt><span class="section"><a href="#id1168230284809">Configure before the build</a></span></dt><dt><span class="section"><a href="#id1168230284906">Build</a></span></dt><dt><span class="section"><a href="#id1168230284921">Install</a></span></dt><dt><span class="section"><a href="#id1168230284946">Install Hie
rarchy</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="id1168230284542"></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
libraries, to build BIND 10 from source code.
</p></div><p>
Building from source code requires the Boost
- build-time headers. At least Boost version 1.34 is required.
+ build-time headers. At least Boost version 1.35 is required.
</p><p>
@@ -147,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">
@@ -170,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">
@@ -178,35 +185,37 @@
</li></ol></div></div><div class="section" title="Installation from source"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="install"></a>Installation from source</h2></div></div></div><p>
BIND 10 is open source software written in C++ and Python.
It is freely available in source code form from ISC via
- the Subversion code revision control system or as a downloadable
+ 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="id1168230284728"></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 Subversion"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230284748"></a>Retrieve from Subversion</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.
</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
- When using source code retrieved via Subversion additional
+ When using source code retrieved via Git additional
software will be required: automake (v1.11 or newer),
libtoolize, and autoconf (2.59 or newer).
These may need to be installed.
</p></div><p>
The latest development code, including temporary experiments
and un-reviewed code, is available via the BIND 10 code revision
- control system. This is powered by Subversion and all the BIND 10
+ control system. This is powered by Git and all the BIND 10
development is public.
- The leading development is done in the <span class="quote">“<span class="quote">trunk</span>”</span>.
+ The leading development is done in the <span class="quote">“<span class="quote">master</span>”</span>.
</p><p>
- The code can be checked out from <code class="filename">svn://bind10.isc.org/svn/bind10</code>; for example to check out the trunk:
+ The code can be checked out from
+ <code class="filename">git://bind10.isc.org/bind10</code>;
+ for example:
- </p><pre class="screen">$ <strong class="userinput"><code>svn co svn://bind10.isc.org/svn/bind10/trunk</code></strong></pre><p>
+ </p><pre class="screen">$ <strong class="userinput"><code>git clone git://bind10.isc.org/bind10</code></strong></pre><p>
</p><p>
When checking out the code from
the code version control system, it doesn't include the
@@ -220,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="id1168230284809"></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:
@@ -251,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="id1168230284906"></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="id1168230284921"></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="id1168230284946"></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> —
@@ -311,7 +320,9 @@
about other modules.
The <span class="command"><strong>bind10</strong></span> master process will also start up
<span class="command"><strong>b10-cmdctl</strong></span> for admins to communicate with the
- system, <span class="command"><strong>b10-auth</strong></span> for Authoritative DNS service,
+ system, <span class="command"><strong>b10-auth</strong></span> for authoritative DNS service or
+ <span class="command"><strong>b10-resolver</strong></span> for recursive name service,
+ <span class="command"><strong>b10-stats</strong></span> for statistics collection,
<span class="command"><strong>b10-xfrin</strong></span> for inbound DNS zone transfers,
<span class="command"><strong>b10-xfrout</strong></span> for outbound DNS zone transfers,
and <span class="command"><strong>b10-zonemgr</strong></span> for secondary service.
@@ -449,6 +460,8 @@ accounts_file
</p><p>
The control commands are:
print_settings
+
+
shutdown
</p></div></div><div class="chapter" title="Chapter 7. Control and configure user interface"><div class="titlepage"><div><div><h2 class="title"><a name="bindctl"></a>Chapter 7. Control and configure user interface</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 this development prototype release, <span class="command"><strong>bindctl</strong></span>
@@ -473,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="#id1168230285515">Server Configurations</a></span></dt><dt><span class="section"><a href="#id1168230285580">Data Source Backends</a></span></dt><dt><span class="section"><a href="#id1168230285610">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="id1168230285515"></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>.
@@ -501,11 +511,12 @@ 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="id1168230285580"></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>
- only supports the SQLite3 data source backend.
+ supports a SQLite3 data source backend and in-memory data source
+ backend.
Upcoming versions will be able to use multiple different
- data sources, such as MySQL, Berkeley DB, or in-memory DB.
+ data sources, such as MySQL and Berkeley DB.
</p></div><p>
By default, the SQLite3 backend uses the data file located at
<code class="filename">/usr/local/var/bind10-devel/zone.sqlite3</code>.
@@ -514,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="id1168230285610"></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.
@@ -544,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
@@ -556,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.
@@ -590,4 +603,81 @@ 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></body></html>
+ </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>.
+
+ </p><p>
+ The main <span class="command"><strong>bind10</strong></span> process can be configured
+ to select to run either the authoritative or resolver.
+ By default, it starts the authoritative service.
+
+
+ You may change this using <span class="command"><strong>bindctl</strong></span>, for example:
+
+ </p><pre class="screen">
+> <strong class="userinput"><code>config set Boss/start_auth false</code></strong>
+> <strong class="userinput"><code>config set Boss/start_resolver true</code></strong>
+> <strong class="userinput"><code>config commit</code></strong>
+</pre><p>
+
+ </p><p>
+ The master <span class="command"><strong>bind10</strong></span> will stop and start
+ the desired services.
+ </p><p>
+ The resolver also needs to be configured to listen on an address
+ and port:
+
+ </p><pre class="screen">
+> <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><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>
+
+ 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:
+
+ </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><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
+ and aggregates it.
+
+ </p><p>
+
+ This stats daemon provides commands to identify if it is running,
+ show specified or all statistics data, set values, remove data,
+ and reset data.
+
+ For example, using <span class="command"><strong>bindctl</strong></span>:
+
+ </p><pre class="screen">
+> <strong class="userinput"><code>Stats show</code></strong>
+{
+ "auth.queries.tcp": 1749,
+ "auth.queries.udp": 867868,
+ "bind10.boot_time": "2011-01-20T16:59:03Z",
+ "report_time": "2011-01-20T17:04:06Z",
+ "stats.boot_time": "2011-01-20T16:59:05Z",
+ "stats.last_update_time": "2011-01-20T17:04:05Z",
+ "stats.lname": "4d3869d9_a at jreed.example.net",
+ "stats.start_time": "2011-01-20T16:59:05Z",
+ "stats.timestamp": 1295543046.823504
+}
+ </pre><p>
+ </p></div></div></body></html>
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 696fb45..c020f11 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -2,6 +2,8 @@
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [
<!ENTITY mdash "—" >
+<!ENTITY % version SYSTEM "version.ent">
+%version;
]>
<book>
<?xml-stylesheet href="bind10-guide.css" type="text/css"?>
@@ -15,13 +17,20 @@
</copyright>
<abstract>
- <para>This is the reference guide for BIND 10.</para>
- <para>
- The most up-to-date version of this document, along with other documents
- for BIND 10, can be found at
- <ulink url="http://bind10.isc.org/docs"/>.
+ <para>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.
</para>
- </abstract>
+ <para>
+ This is the reference guide for BIND 10 version &__VERSION__;.
+ The most up-to-date version of this document, along with
+ other documents for BIND 10, can be found at <ulink
+ url="http://bind10.isc.org/docs"/>. </para> </abstract>
+
+ <releaseinfo>This is the reference guide for BIND 10 version
+ &__VERSION__;.</releaseinfo>
+
</bookinfo>
<chapter id="intro">
@@ -35,16 +44,16 @@
<note>
<para>
- This guide covers the experimental prototype version of
- BIND 10.
+ This guide covers the experimental prototype of
+ BIND 10 version &__VERSION__;.
</para>
</note>
<note>
<para>
- BIND 10, at this time, does not provide a recursive
- DNS server. It does provide a EDNS0- and DNSSEC-capable
- authoritative DNS server.
+ BIND 10 provides a EDNS0- and DNSSEC-capable
+ authoritative DNS server and a caching recursive name server
+ which also provides forwarding.
</para>
</note>
@@ -52,7 +61,7 @@
<title>Supported Platforms</title>
<para>
BIND 10 builds have been tested on Debian GNU/Linux 5,
- Ubuntu 9.10, NetBSD 5, Solaris 10, FreeBSD 7, and CentOS
+ Ubuntu 9.10, NetBSD 5, Solaris 10, FreeBSD 7 and 8, and CentOS
Linux 5.3.
It has been tested on Sparc, i386, and amd64 hardware
@@ -71,9 +80,7 @@
</para>
<note><para>
- For this development prototype release, the only supported
- data source backend is SQLite3. The authoritative server
- requires SQLite 3.3.9 or newer.
+ The authoritative server requires SQLite 3.3.9 or newer.
The <command>b10-xfrin</command>, <command>b10-xfrout</command>,
and <command>b10-zonemgr</command> modules require the
libpython3 library and the Python _sqlite3.so module.
@@ -116,6 +123,7 @@
<para>
<itemizedlist>
+
<listitem>
<simpara>
<command>b10-msgq</command> —
@@ -124,6 +132,7 @@
BIND 10 processes.
</simpara>
</listitem>
+
<listitem>
<simpara>
<command>b10-auth</command> —
@@ -131,6 +140,7 @@
This process serves DNS requests.
</simpara>
</listitem>
+
<listitem>
<simpara>
<command>b10-cfgmgr</command> —
@@ -138,6 +148,7 @@
This process maintains all of the configuration for BIND 10.
</simpara>
</listitem>
+
<listitem>
<simpara>
<command>b10-cmdctl</command> —
@@ -148,6 +159,23 @@
<listitem>
<simpara>
+ <command>b10-resolver</command> —
+ Recursive name server.
+ This process handles incoming queries.
+<!-- TODO: -->
+ </simpara>
+ </listitem>
+
+ <listitem>
+ <simpara>
+ <command>b10-stats</command> —
+ Statistics collection daemon.
+ This process collects and reports statistics data.
+ </simpara>
+ </listitem>
+
+ <listitem>
+ <simpara>
<command>b10-xfrin</command> —
Incoming zone transfer service.
This process is used to transfer a new copy
@@ -274,7 +302,7 @@ var/
<para>
Building from source code requires the Boost
- build-time headers. At least Boost version 1.34 is required.
+ build-time headers. At least Boost version 1.35 is required.
<!-- TODO: we don't check for this version -->
<!-- NOTE: jreed has tested with 1.34, 1.38, and 1.41. -->
</para>
@@ -308,14 +336,6 @@ var/
</simpara>
</note>
- <note>
- <simpara>
- 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.)
- </simpara>
- </note>
-
<para>
To quickly get started with BIND 10, follow these steps.
</para>
@@ -369,7 +389,7 @@ var/
<listitem>
<para>Test it; for example:
- <screen>$ <userinput>dig @127.0.0.1 -p 5300 -c CH -t TXT authors.bind</userinput></screen>
+ <screen>$ <userinput>dig @127.0.0.1 -c CH -t TXT authors.bind</userinput></screen>
</para>
</listitem>
@@ -394,7 +414,7 @@ var/
<para>
BIND 10 is open source software written in C++ and Python.
It is freely available in source code form from ISC via
- the Subversion code revision control system or as a downloadable
+ 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.
</para>
@@ -415,7 +435,7 @@ var/
</section>
<section>
- <title>Retrieve from Subversion</title>
+ <title>Retrieve from Git</title>
<para>
Downloading this "bleeding edge" code is recommended only for
developers or advanced users. Using development code in a production
@@ -424,7 +444,7 @@ var/
<note>
<para>
- When using source code retrieved via Subversion additional
+ When using source code retrieved via Git additional
software will be required: automake (v1.11 or newer),
libtoolize, and autoconf (2.59 or newer).
These may need to be installed.
@@ -434,14 +454,16 @@ var/
<para>
The latest development code, including temporary experiments
and un-reviewed code, is available via the BIND 10 code revision
- control system. This is powered by Subversion and all the BIND 10
+ control system. This is powered by Git and all the BIND 10
development is public.
- The leading development is done in the <quote>trunk</quote>.
+ The leading development is done in the <quote>master</quote>.
</para>
<para>
- The code can be checked out from <filename>svn://bind10.isc.org/svn/bind10</filename>; for example to check out the trunk:
+ The code can be checked out from
+ <filename>git://bind10.isc.org/bind10</filename>;
+ for example:
- <screen>$ <userinput>svn co svn://bind10.isc.org/svn/bind10/trunk</userinput></screen>
+ <screen>$ <userinput>git clone git://bind10.isc.org/bind10</userinput></screen>
</para>
<para>
@@ -658,7 +680,9 @@ var/
about other modules.
The <command>bind10</command> master process will also start up
<command>b10-cmdctl</command> for admins to communicate with the
- system, <command>b10-auth</command> for Authoritative DNS service,
+ system, <command>b10-auth</command> for authoritative DNS service or
+ <command>b10-resolver</command> for recursive name service,
+ <command>b10-stats</command> for statistics collection,
<command>b10-xfrin</command> for inbound DNS zone transfers,
<command>b10-xfrout</command> for outbound DNS zone transfers,
and <command>b10-zonemgr</command> for secondary service.
@@ -949,6 +973,8 @@ accounts_file
<para>
The control commands are:
print_settings
+<!-- TODO: remove that -->
+
shutdown
</para>
<!-- TODO -->
@@ -1010,11 +1036,6 @@ TODO
process.
</para>
- <note><simpara>
- This development prototype release listens on all interfaces
- and the non-standard port 5300.
- </simpara></note>
-
<section>
<title>Server Configurations</title>
@@ -1073,9 +1094,10 @@ This may be a temporary setting until then.
<note><para>
For the development prototype release, <command>b10-auth</command>
- only supports the SQLite3 data source backend.
+ supports a SQLite3 data source backend and in-memory data source
+ backend.
Upcoming versions will be able to use multiple different
- data sources, such as MySQL, Berkeley DB, or in-memory DB.
+ data sources, such as MySQL and Berkeley DB.
</para></note>
@@ -1177,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
@@ -1191,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.
@@ -1201,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">
@@ -1262,6 +1300,127 @@ what is XfroutClient xfr_client??
</chapter>
+ <chapter id="resolverserver">
+ <title>Recursive Name Server</title>
+
+ <para>
+ The <command>b10-resolver</command> process is started by
+ <command>bind10</command>.
+<!-- TODO
+ It provides a resolver so DNS clients can ask it to do recursion
+ and it will return answers.
+-->
+ </para>
+
+ <para>
+ The main <command>bind10</command> process can be configured
+ to select to run either the authoritative or resolver.
+ By default, it starts the authoritative service.
+<!-- TODO: later both -->
+
+ You may change this using <command>bindctl</command>, for example:
+
+ <screen>
+> <userinput>config set Boss/start_auth false</userinput>
+> <userinput>config set Boss/start_resolver true</userinput>
+> <userinput>config commit</userinput>
+</screen>
+
+ </para>
+
+ <para>
+ The master <command>bind10</command> will stop and start
+ the desired services.
+ </para>
+
+ <para>
+ The resolver also needs to be configured to listen on an address
+ and port:
+
+ <screen>
+> <userinput>config set Resolver/listen_on [{ "address": "127.0.0.1", "port": 53 }]</userinput>
+> <userinput>config commit</userinput>
+</screen>
+ </para>
+
+<!-- TODO: later the above will have some defaults -->
+
+ <section>
+ <title>Forwarding</title>
+
+ <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>
+
+ <para>
+ Normal iterative name service can be re-enabled by clearing the
+ forwarding address(es); for example:
+
+ <screen>
+> <userinput>config set Resolver/forward_addresses []</userinput>
+> <userinput>config commit</userinput>
+</screen>
+ </para>
+
+ </section>
+
+<!-- TODO: later try this
+
+> config set Resolver/forward_addresses[0]/address "192.168.8.8"
+> config set Resolver/forward_addresses[0]/port 53
+then change those defaults with config set Resolver/forward_addresses[0]/address "1.2.3.4"
+> config set Resolver/forward_addresses[0]/address "1.2.3.4"
+-->
+
+ </chapter>
+
+ <chapter id="statistics">
+ <title>Statistics</title>
+
+ <para>
+ The <command>b10-stats</command> process is started by
+ <command>bind10</command>.
+ It periodically collects statistics data from various modules
+ and aggregates it.
+<!-- TODO -->
+ </para>
+
+ <para>
+
+ This stats daemon provides commands to identify if it is running,
+ show specified or all statistics data, set values, remove data,
+ and reset data.
+
+ For example, using <command>bindctl</command>:
+
+ <screen>
+> <userinput>Stats show</userinput>
+{
+ "auth.queries.tcp": 1749,
+ "auth.queries.udp": 867868,
+ "bind10.boot_time": "2011-01-20T16:59:03Z",
+ "report_time": "2011-01-20T17:04:06Z",
+ "stats.boot_time": "2011-01-20T16:59:05Z",
+ "stats.last_update_time": "2011-01-20T17:04:05Z",
+ "stats.lname": "4d3869d9_a at jreed.example.net",
+ "stats.start_time": "2011-01-20T16:59:05Z",
+ "stats.timestamp": 1295543046.823504
+}
+ </screen>
+ </para>
+
+ </chapter>
+
<!-- TODO: how to help: run unit tests, join lists, review trac tickets -->
<!-- <index> <title>Index</title> </index> -->
diff --git a/doc/version.ent.in b/doc/version.ent.in
new file mode 100644
index 0000000..9a72f7e
--- /dev/null
+++ b/doc/version.ent.in
@@ -0,0 +1 @@
+<!ENTITY __VERSION__ "@PACKAGE_VERSION@">
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/ext/coroutine/coroutine.h b/ext/coroutine/coroutine.h
new file mode 100644
index 0000000..985888b
--- /dev/null
+++ b/ext/coroutine/coroutine.h
@@ -0,0 +1,133 @@
+//
+// coroutine.h
+// ~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef COROUTINE_HPP
+#define COROUTINE_HPP
+
+
+// \brief Coroutine object
+//
+// A coroutine object maintains the state of a re-enterable routine. It
+// is assignable and copy-constructable, and can be used as a base class
+// for a class that uses it, or as a data member. The copy overhead is
+// a single int.
+//
+// A reenterable function contains a CORO_REENTER (coroutine) { ... }
+// block. Whenever an asychrnonous operation is initiated within the
+// routine, the function is provided as the handler object. (The simplest
+// way to do this is to have the reenterable function be the operator()
+// member for the coroutine object itself.) For example:
+//
+// CORO_YIELD socket->async_read_some(buffer, *this);
+//
+// The CORO_YIELD keyword updates the current status of the coroutine to
+// indicate the line number currently being executed. The
+// async_read_some() call is initiated, with a copy of the updated
+// corotutine as its handler object, and the current coroutine exits. When
+// the async_read_some() call finishes, the copied coroutine will be
+// called, and will resume processing exactly where the original one left
+// off--right after asynchronous call. This allows asynchronous I/O
+// routines to be written with a logical flow, step following step, rather
+// than as a linked chain of separate handler functions.
+//
+// When necessary, a coroutine can fork itself using the CORO_FORK keyword.
+// This updates the status of the coroutine and makes a copy. The copy can
+// then be called directly or posted to the ASIO service queue so that both
+// coroutines will continue forward, one "parent" and one "child". The
+// is_parent() and is_child() methods indicate which is which.
+//
+// The CORO_REENTER, CORO_YIELD and CORO_FORK keywords are implemented
+// via preprocessor macros. The CORO_REENTER block is actually a large,
+// complex switch statement. Because of this, inline variable declaration
+// is impossible within CORO_REENTER unless it is done in a subsidiary
+// scope--and if it is, that scope cannot contain CORO_YIELD or CORO_FORK
+// keywords.
+//
+// Because coroutines are frequently copied, it is best to minimize copy
+// overhead by limiting the size of data members in derived classes.
+//
+// It should be noted that when a coroutine falls out of scope its memory
+// is reclaimed, even though it may be scheduled to resume when an
+// asynchronous operation completes. Any shared_ptr<> objects declared in
+// the coroutine may be destroyed if their reference count drops to zero,
+// in which case the coroutine will have serious problems once it resumes.
+// One solution so this is to have the space that will be used by a
+// coroutine pre-allocated and stored on a free list; a new coroutine can
+// fetch the block of space off a free list, place a shared pointer to it
+// on an "in use" list, and carry on. The reference in the "in use" list
+// would prevent the data from being destroyed.
+class coroutine
+{
+public:
+ coroutine() : value_(0) {}
+ virtual ~coroutine() {}
+ bool is_child() const { return value_ < 0; }
+ bool is_parent() const { return !is_child(); }
+ bool is_complete() const { return value_ == -1; }
+ int get_value() const { return value_; }
+private:
+ friend class coroutine_ref;
+ int value_;
+};
+
+class coroutine_ref
+{
+public:
+ coroutine_ref(coroutine& c) : value_(c.value_), modified_(false) {}
+ coroutine_ref(coroutine* c) : value_(c->value_), modified_(false) {}
+ ~coroutine_ref() { if (!modified_) value_ = -1; }
+ operator int() const { return value_; }
+ int& operator=(int v) { modified_ = true; return value_ = v; }
+private:
+ void operator=(const coroutine_ref&);
+ int& value_;
+ bool modified_;
+};
+
+#define CORO_REENTER(c) \
+ switch (coroutine_ref _coro_value = c) \
+ case -1: if (_coro_value) \
+ { \
+ goto terminate_coroutine; \
+ terminate_coroutine: \
+ _coro_value = -1; \
+ goto bail_out_of_coroutine; \
+ bail_out_of_coroutine: \
+ break; \
+ } \
+ else case 0:
+
+#define CORO_YIELD \
+ for (_coro_value = __LINE__;;) \
+ if (_coro_value == 0) \
+ { \
+ case __LINE__: ; \
+ break; \
+ } \
+ else \
+ switch (_coro_value ? 0 : 1) \
+ for (;;) \
+ case -1: if (_coro_value) \
+ goto terminate_coroutine; \
+ else for (;;) \
+ case 1: if (_coro_value) \
+ goto bail_out_of_coroutine; \
+ else case 0:
+
+#define CORO_FORK \
+ for (_coro_value = -__LINE__;; _coro_value = __LINE__) \
+ if (_coro_value == __LINE__) \
+ { \
+ case -__LINE__: ; \
+ break; \
+ } \
+ else
+#endif // COROUTINE_HPP
+
diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am
index 65ca0e6..1768ce7 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
+ usermgr zonemgr stats tests resolver
check-recursive: all-recursive
diff --git a/src/bin/auth/Makefile.am b/src/bin/auth/Makefile.am
index cacc958..cdfc55e 100644
--- a/src/bin/auth/Makefile.am
+++ b/src/bin/auth/Makefile.am
@@ -3,8 +3,9 @@ SUBDIRS = . tests benchmarks
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
-AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
-AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/cc -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/asiolink
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/asiolink
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -33,43 +34,29 @@ auth.spec: auth.spec.pre
spec_config.h: spec_config.h.pre
$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
-# This is a wrapper library solely used for b10-auth. The ASIO header files
-# have some code fragments that would hit gcc's unused-parameter warning,
-# which would make the build fail with -Werror (our default setting).
-# We don't want to lower the warning level for our own code just for ASIO,
-# so as a workaround we extract the ASIO related code into a separate library,
-# only for which we accept the unused-parameter warning.
-lib_LIBRARIES = libasio_link.a
-libasio_link_a_SOURCES = asio_link.cc asio_link.h
-# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
-# B10_CXXFLAGS)
-libasio_link_a_CXXFLAGS = $(AM_CXXFLAGS)
-if USE_GXX
-libasio_link_a_CXXFLAGS += -Wno-unused-parameter
-endif
-if USE_CLANGPP
-# Same for clang++, but we need to turn off -Werror completely.
-libasio_link_a_CXXFLAGS += -Wno-error
-endif
-libasio_link_a_CPPFLAGS = $(AM_CPPFLAGS)
-
-BUILT_SOURCES = spec_config.h
+BUILT_SOURCES = spec_config.h
pkglibexec_PROGRAMS = b10-auth
-b10_auth_SOURCES = auth_srv.cc auth_srv.h
+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 += command.cc command.h
b10_auth_SOURCES += common.h
+b10_auth_SOURCES += statistics.cc statistics.h
b10_auth_SOURCES += main.cc
b10_auth_LDADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
b10_auth_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
b10_auth_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
b10_auth_LDADD += $(top_builddir)/src/lib/cc/libcc.la
b10_auth_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
-b10_auth_LDADD += libasio_link.a
+b10_auth_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+b10_auth_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
b10_auth_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
+b10_auth_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
b10_auth_LDADD += $(SQLITE_LIBS)
# TODO: config.h.in is wrong because doesn't honor pkgdatadir
# and can't use @datadir@ because doesn't expand default ${prefix}
-b10_authdir = $(DESTDIR)$(pkgdatadir)
+b10_authdir = $(pkgdatadir)
b10_auth_DATA = auth.spec
diff --git a/src/bin/auth/asio_link.cc b/src/bin/auth/asio_link.cc
deleted file mode 100644
index f633cc2..0000000
--- a/src/bin/auth/asio_link.cc
+++ /dev/null
@@ -1,671 +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.
-
-// $Id$
-
-#include <config.h>
-
-#include <unistd.h> // for some IPC/network system calls
-#include <sys/socket.h>
-#include <netinet/in.h>
-
-#include <asio.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/bind.hpp>
-
-#include <boost/shared_ptr.hpp>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-#include <dns/messagerenderer.h>
-
-#include <asio_link.h>
-
-#include <auth/auth_srv.h>
-#include <auth/common.h>
-
-using namespace asio;
-using asio::ip::udp;
-using asio::ip::tcp;
-
-using namespace std;
-using namespace isc::dns;
-
-namespace asio_link {
-IOAddress::IOAddress(const string& address_str)
- // XXX: we cannot simply construct the address in the initialization list
- // because we'd like to throw our own exception on failure.
-{
- error_code err;
- asio_address_ = ip::address::from_string(address_str, err);
- if (err) {
- isc_throw(IOError, "Failed to convert string to address '"
- << address_str << "': " << err.message());
- }
-}
-
-IOAddress::IOAddress(const ip::address& asio_address) :
- asio_address_(asio_address)
-{}
-
-string
-IOAddress::toText() const {
- return (asio_address_.to_string());
-}
-
-/// \brief The \c TCPEndpoint class is a concrete derived class of
-/// \c IOEndpoint that represents an endpoint of a TCP connection.
-///
-/// 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.
-class TCPEndpoint : public IOEndpoint {
-public:
- ///
- /// \name Constructors and Destructor
- ///
- //@{
- /// \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 tcp::endpoint(ip::address::from_string(address.toText()),
- port)),
- asio_endpoint_(*asio_endpoint_placeholder_)
- {}
-
- /// \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 tcp::endpoint& asio_endpoint) :
- asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
- {}
-
- /// \brief The destructor.
- ~TCPEndpoint() { delete asio_endpoint_placeholder_; }
- //@}
-
- virtual IOAddress getAddress() const {
- return (asio_endpoint_.address());
- }
-private:
- const tcp::endpoint* asio_endpoint_placeholder_;
- const tcp::endpoint& asio_endpoint_;
-};
-
-/// \brief The \c UDPEndpoint class is a concrete derived class of
-/// \c IOEndpoint that represents an endpoint of a UDP packet.
-///
-/// Other notes about \c TCPEndpoint applies to this class, too.
-class UDPEndpoint : public IOEndpoint {
-public:
- ///
- /// \name Constructors and Destructor.
- ///
- //@{
- /// \brief Constructor from a pair of address and port.
- ///
- /// \param address The IP address of the endpoint.
- /// \param port The UDP port number of the endpoint.
- UDPEndpoint(const IOAddress& address, const unsigned short port) :
- asio_endpoint_placeholder_(
- new udp::endpoint(ip::address::from_string(address.toText()),
- port)),
- asio_endpoint_(*asio_endpoint_placeholder_)
- {}
-
- /// \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 UDP endpoint.
- UDPEndpoint(const udp::endpoint& asio_endpoint) :
- asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
- {}
-
- /// \brief The destructor.
- ~UDPEndpoint() { delete asio_endpoint_placeholder_; }
- //@}
-
- virtual IOAddress getAddress() const {
- return (asio_endpoint_.address());
- }
-private:
- const udp::endpoint* asio_endpoint_placeholder_;
- const udp::endpoint& asio_endpoint_;
-};
-
-const IOEndpoint*
-IOEndpoint::create(const int protocol, const IOAddress& address,
- const unsigned short port)
-{
- if (protocol == IPPROTO_UDP) {
- return (new UDPEndpoint(address, port));
- } else if (protocol == IPPROTO_TCP) {
- return (new TCPEndpoint(address, port));
- }
- isc_throw(IOError,
- "IOEndpoint creation attempt for unsupported protocol: " <<
- protocol);
-}
-
-/// \brief The \c TCPSocket class is a concrete derived class of
-/// \c IOSocket that represents a TCP socket.
-///
-/// 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 IOSocket.
-/// This design may be changed when we generalize the wrapper interface.
-class TCPSocket : public IOSocket {
-private:
- TCPSocket(const TCPSocket& source);
- TCPSocket& operator=(const TCPSocket& source);
-public:
- /// \brief Constructor from an ASIO TCP socket.
- ///
- /// \param socket The ASIO representation of the TCP socket.
- TCPSocket(tcp::socket& socket) : socket_(socket) {}
-
- virtual int getNative() const { return (socket_.native()); }
- virtual int getProtocol() const { return (IPPROTO_TCP); }
-private:
- tcp::socket& socket_;
-};
-
-/// \brief The \c UDPSocket class is a concrete derived class of
-/// \c IOSocket that represents a UDP socket.
-///
-/// Other notes about \c TCPSocket applies to this class, too.
-class UDPSocket : public IOSocket {
-private:
- UDPSocket(const UDPSocket& source);
- UDPSocket& operator=(const UDPSocket& source);
-public:
- /// \brief Constructor from an ASIO UDP socket.
- ///
- /// \param socket The ASIO representation of the UDP socket.
- UDPSocket(udp::socket& socket) : socket_(socket) {}
-
- virtual int getNative() const { return (socket_.native()); }
- virtual int getProtocol() const { return (IPPROTO_UDP); }
-private:
- udp::socket& socket_;
-};
-
-/// \brief The \c DummySocket class is a concrete derived class of
-/// \c IOSocket that is not associated with any real socket.
-///
-/// This main purpose of this class is tests, where it may be desirable to
-/// instantiate an \c IOSocket object without involving system resource
-/// allocation such as real network sockets.
-class DummySocket : public IOSocket {
-private:
- DummySocket(const DummySocket& source);
- DummySocket& operator=(const DummySocket& source);
-public:
- /// \brief Constructor from the protocol number.
- ///
- /// The protocol must validly identify a standard network protocol.
- /// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
- ///
- /// \param protocol The network protocol number for the socket.
- DummySocket(const int protocol) : protocol_(protocol) {}
-
- /// \brief A dummy derived method of \c IOSocket::getNative().
- ///
- /// This version of method always returns -1 as the object is not
- /// associated with a real (native) socket.
- virtual int getNative() const { return (-1); }
-
- virtual int getProtocol() const { return (protocol_); }
-private:
- const int protocol_;
-};
-
-IOSocket&
-IOSocket::getDummyUDPSocket() {
- static DummySocket socket(IPPROTO_UDP);
- return (socket);
-}
-
-IOSocket&
-IOSocket::getDummyTCPSocket() {
- static DummySocket socket(IPPROTO_TCP);
- return (socket);
-}
-
-IOMessage::IOMessage(const void* data, const size_t data_size,
- IOSocket& io_socket, const IOEndpoint& remote_endpoint) :
- data_(data), data_size_(data_size), io_socket_(io_socket),
- remote_endpoint_(remote_endpoint)
-{}
-
-//
-// Helper classes for asynchronous I/O using asio
-//
-class TCPClient {
-public:
- TCPClient(AuthSrv* auth_server, io_service& io_service) :
- auth_server_(auth_server),
- socket_(io_service),
- io_socket_(socket_),
- response_buffer_(0),
- responselen_buffer_(TCP_MESSAGE_LENGTHSIZE),
- response_renderer_(response_buffer_),
- dns_message_(Message::PARSE),
- custom_callback_(NULL)
- {}
-
- void start() {
- // Check for queued configuration commands
- if (auth_server_ != NULL &&
- auth_server_->getConfigSession()->hasQueuedMsgs()) {
- auth_server_->getConfigSession()->checkCommand();
- }
- async_read(socket_, asio::buffer(data_, TCP_MESSAGE_LENGTHSIZE),
- boost::bind(&TCPClient::headerRead, this,
- placeholders::error,
- placeholders::bytes_transferred));
- }
-
- tcp::socket& getSocket() { return (socket_); }
-
- void headerRead(const asio::error_code& error,
- size_t bytes_transferred)
- {
- if (!error) {
- InputBuffer dnsbuffer(data_, bytes_transferred);
-
- uint16_t msglen = dnsbuffer.readUint16();
- async_read(socket_, asio::buffer(data_, msglen),
- boost::bind(&TCPClient::requestRead, this,
- placeholders::error,
- placeholders::bytes_transferred));
- } else {
- delete this;
- }
- }
-
- void requestRead(const asio::error_code& error,
- size_t bytes_transferred)
- {
- if (!error) {
- const TCPEndpoint remote_endpoint(socket_.remote_endpoint());
- const IOMessage io_message(data_, bytes_transferred, io_socket_,
- remote_endpoint);
- // currently, for testing purpose only
- if (custom_callback_ != NULL) {
- (*custom_callback_)(io_message);
- start();
- return;
- }
-
- if (auth_server_->processMessage(io_message, dns_message_,
- response_renderer_)) {
- responselen_buffer_.writeUint16(
- response_buffer_.getLength());
- async_write(socket_,
- asio::buffer(
- responselen_buffer_.getData(),
- responselen_buffer_.getLength()),
- boost::bind(&TCPClient::responseWrite, this,
- placeholders::error));
- } else {
- delete this;
- }
- } else {
- delete this;
- }
- }
-
- void responseWrite(const asio::error_code& error) {
- if (!error) {
- async_write(socket_,
- asio::buffer(response_buffer_.getData(),
- response_buffer_.getLength()),
- boost::bind(&TCPClient::handleWrite, this,
- placeholders::error));
- } else {
- delete this;
- }
- }
-
- void handleWrite(const asio::error_code& error) {
- if (!error) {
- start(); // handle next request, if any.
- } else {
- delete this;
- }
- }
-
- // Currently this is for tests only
- void setCallBack(const IOService::IOCallBack* callback) {
- custom_callback_ = callback;
- }
-
-private:
- AuthSrv* auth_server_;
- tcp::socket socket_;
- TCPSocket io_socket_;
- OutputBuffer response_buffer_;
- OutputBuffer responselen_buffer_;
- MessageRenderer response_renderer_;
- Message dns_message_;
- enum { MAX_LENGTH = 65535 };
- static const size_t TCP_MESSAGE_LENGTHSIZE = 2;
- char data_[MAX_LENGTH];
-
- // currently, for testing purpose only.
- const IOService::IOCallBack* custom_callback_;
-};
-
-class TCPServer {
-public:
- TCPServer(AuthSrv* auth_server, io_service& io_service,
- const ip::address& addr, const uint16_t port) :
- auth_server_(auth_server), io_service_(io_service),
- acceptor_(io_service_), listening_(new TCPClient(auth_server_,
- io_service_)),
- custom_callback_(NULL)
- {
- tcp::endpoint endpoint(addr, port);
- acceptor_.open(endpoint.protocol());
- // Set v6-only (we use a different instantiation for v4,
- // otherwise asio will bind to both v4 and v6
- if (addr.is_v6()) {
- acceptor_.set_option(ip::v6_only(true));
- }
- acceptor_.set_option(tcp::acceptor::reuse_address(true));
- acceptor_.bind(endpoint);
- acceptor_.listen();
- acceptor_.async_accept(listening_->getSocket(),
- boost::bind(&TCPServer::handleAccept, this,
- listening_, placeholders::error));
- }
-
- ~TCPServer() { delete listening_; }
-
- void handleAccept(TCPClient* new_client,
- const asio::error_code& error)
- {
- if (!error) {
- assert(new_client == listening_);
- new_client->setCallBack(custom_callback_);
- new_client->start();
- listening_ = new TCPClient(auth_server_, io_service_);
- acceptor_.async_accept(listening_->getSocket(),
- boost::bind(&TCPServer::handleAccept,
- this, listening_,
- placeholders::error));
- } else {
- delete new_client;
- }
- }
-
- // Currently this is for tests only
- void setCallBack(const IOService::IOCallBack* callback) {
- custom_callback_ = callback;
- }
-
-private:
- AuthSrv* auth_server_;
- io_service& io_service_;
- tcp::acceptor acceptor_;
- TCPClient* listening_;
-
- // currently, for testing purpose only.
- const IOService::IOCallBack* custom_callback_;
-};
-
-class UDPServer {
-public:
- UDPServer(AuthSrv* auth_server, io_service& io_service,
- const ip::address& addr, const uint16_t port) :
- auth_server_(auth_server),
- io_service_(io_service),
- socket_(io_service, addr.is_v6() ? udp::v6() : udp::v4()),
- io_socket_(socket_),
- response_buffer_(0),
- response_renderer_(response_buffer_),
- dns_message_(Message::PARSE),
- custom_callback_(NULL)
- {
- socket_.set_option(socket_base::reuse_address(true));
- // Set v6-only (we use a different instantiation for v4,
- // otherwise asio will bind to both v4 and v6
- if (addr.is_v6()) {
- socket_.set_option(asio::ip::v6_only(true));
- socket_.bind(udp::endpoint(addr, port));
- } else {
- socket_.bind(udp::endpoint(addr, port));
- }
- startReceive();
- }
-
- void handleRequest(const asio::error_code& error,
- size_t bytes_recvd)
- {
- // Check for queued configuration commands
- if (auth_server_ != NULL &&
- auth_server_->getConfigSession()->hasQueuedMsgs()) {
- auth_server_->getConfigSession()->checkCommand();
- }
- if (!error && bytes_recvd > 0) {
- const UDPEndpoint remote_endpoint(sender_endpoint_);
- const IOMessage io_message(data_, bytes_recvd, io_socket_,
- remote_endpoint);
- // currently, for testing purpose only
- if (custom_callback_ != NULL) {
- (*custom_callback_)(io_message);
- startReceive();
- return;
- }
-
- dns_message_.clear(Message::PARSE);
- response_renderer_.clear();
- if (auth_server_->processMessage(io_message, dns_message_,
- response_renderer_)) {
- socket_.async_send_to(
- asio::buffer(response_buffer_.getData(),
- response_buffer_.getLength()),
- sender_endpoint_,
- boost::bind(&UDPServer::sendCompleted,
- this,
- placeholders::error,
- placeholders::bytes_transferred));
- } else {
- startReceive();
- }
- } else {
- startReceive();
- }
- }
-
- void sendCompleted(const asio::error_code& error UNUSED_PARAM,
- size_t bytes_sent UNUSED_PARAM)
- {
- // Even if error occurred there's nothing to do. Simply handle
- // the next request.
- startReceive();
- }
-
- // Currently this is for tests only
- void setCallBack(const IOService::IOCallBack* callback) {
- custom_callback_ = callback;
- }
-private:
- void startReceive() {
- socket_.async_receive_from(
- asio::buffer(data_, MAX_LENGTH), sender_endpoint_,
- boost::bind(&UDPServer::handleRequest, this,
- placeholders::error,
- placeholders::bytes_transferred));
- }
-
-private:
- AuthSrv* auth_server_;
- io_service& io_service_;
- udp::socket socket_;
- UDPSocket io_socket_;
- OutputBuffer response_buffer_;
- MessageRenderer response_renderer_;
- Message dns_message_;
- udp::endpoint sender_endpoint_;
- enum { MAX_LENGTH = 4096 };
- char data_[MAX_LENGTH];
-
- // currently, for testing purpose only.
- const IOService::IOCallBack* custom_callback_;
-};
-
-class IOServiceImpl {
-public:
- IOServiceImpl(AuthSrv* auth_server, const char& port,
- const ip::address* v4addr, const ip::address* v6addr);
- asio::io_service io_service_;
- AuthSrv* auth_server_;
-
- typedef boost::shared_ptr<UDPServer> UDPServerPtr;
- typedef boost::shared_ptr<TCPServer> TCPServerPtr;
- UDPServerPtr udp4_server_;
- UDPServerPtr udp6_server_;
- TCPServerPtr tcp4_server_;
- TCPServerPtr tcp6_server_;
-
- // This member is used only for testing at the moment.
- IOService::IOCallBack callback_;
-};
-
-IOServiceImpl::IOServiceImpl(AuthSrv* auth_server, const char& port,
- const ip::address* const v4addr,
- const ip::address* const v6addr) :
- auth_server_(auth_server),
- udp4_server_(UDPServerPtr()), udp6_server_(UDPServerPtr()),
- tcp4_server_(TCPServerPtr()), tcp6_server_(TCPServerPtr())
-{
- uint16_t portnum;
-
- try {
- // XXX: SunStudio with stlport4 doesn't reject some invalid
- // representation such as "-1" by lexical_cast<uint16_t>, so
- // we convert it into a signed integer of a larger size and perform
- // range check ourselves.
- const int32_t portnum32 = boost::lexical_cast<int32_t>(&port);
- if (portnum32 < 0 || portnum32 > 65535) {
- isc_throw(IOError, "Invalid port number '" << &port);
- }
- portnum = portnum32;
- } catch (const boost::bad_lexical_cast& ex) {
- isc_throw(IOError, "Invalid port number '" << &port << "': " <<
- ex.what());
- }
-
- try {
- if (v4addr != NULL) {
- udp4_server_ = UDPServerPtr(new UDPServer(auth_server, io_service_,
- *v4addr, portnum));
- tcp4_server_ = TCPServerPtr(new TCPServer(auth_server, io_service_,
- *v4addr, portnum));
- }
- if (v6addr != NULL) {
- udp6_server_ = UDPServerPtr(new UDPServer(auth_server, io_service_,
- *v6addr, portnum));
- tcp6_server_ = TCPServerPtr(new TCPServer(auth_server, io_service_,
- *v6addr, portnum));
- }
- } catch (const asio::system_error& err) {
- // We need to catch and convert any ASIO level exceptions.
- // This can happen for unavailable address, binding a privilege port
- // without the privilege, etc.
- isc_throw(IOError, "Failed to initialize network servers: " <<
- err.what());
- }
-}
-
-IOService::IOService(AuthSrv* auth_server, const char& port,
- const char& address) :
- impl_(NULL)
-{
- error_code err;
- const ip::address addr = ip::address::from_string(&address, err);
- if (err) {
- isc_throw(IOError, "Invalid IP address '" << &address << "': "
- << err.message());
- }
-
- impl_ = new IOServiceImpl(auth_server, port,
- addr.is_v4() ? &addr : NULL,
- addr.is_v6() ? &addr : NULL);
-}
-
-IOService::IOService(AuthSrv* auth_server, const char& port,
- const bool use_ipv4, const bool use_ipv6) :
- impl_(NULL)
-{
- const ip::address v4addr_any = ip::address(ip::address_v4::any());
- const ip::address* const v4addrp = use_ipv4 ? &v4addr_any : NULL;
- const ip::address v6addr_any = ip::address(ip::address_v6::any());
- const ip::address* const v6addrp = use_ipv6 ? &v6addr_any : NULL;
- impl_ = new IOServiceImpl(auth_server, port, v4addrp, v6addrp);
-}
-
-IOService::~IOService() {
- delete impl_;
-}
-
-void
-IOService::run() {
- impl_->io_service_.run();
-}
-
-void
-IOService::stop() {
- impl_->io_service_.stop();
-}
-
-asio::io_service&
-IOService::get_io_service() {
- return (impl_->io_service_);
-}
-
-void
-IOService::setCallBack(const IOCallBack callback) {
- impl_->callback_ = callback;
- if (impl_->udp4_server_ != NULL) {
- impl_->udp4_server_->setCallBack(&impl_->callback_);
- }
- if (impl_->udp6_server_ != NULL) {
- impl_->udp6_server_->setCallBack(&impl_->callback_);
- }
- if (impl_->tcp4_server_ != NULL) {
- impl_->tcp4_server_->setCallBack(&impl_->callback_);
- }
- if (impl_->tcp6_server_ != NULL) {
- impl_->tcp6_server_->setCallBack(&impl_->callback_);
- }
-}
-}
diff --git a/src/bin/auth/asio_link.h b/src/bin/auth/asio_link.h
deleted file mode 100644
index 37522e6..0000000
--- a/src/bin/auth/asio_link.h
+++ /dev/null
@@ -1,452 +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.
-
-// $Id$
-
-#ifndef __ASIO_LINK_H
-#define __ASIO_LINK_H 1
-
-// IMPORTANT NOTE: only very few ASIO headers files can be included in
-// this file. In particular, asio.hpp should never be included here.
-// See the description of the namespace below.
-#include <unistd.h> // for some network system calls
-#include <asio/ip/address.hpp>
-
-#include <functional>
-#include <string>
-
-#include <boost/function.hpp>
-
-#include <exceptions/exceptions.h>
-
-namespace asio {
-// forward declaration for IOService::get_io_service() below
-class io_service;
-}
-
-class AuthSrv;
-
-/// \namespace asio_link
-/// \brief A wrapper interface for the ASIO library.
-///
-/// The \c asio_link namespace is used to define a set of wrapper interfaces
-/// for the ASIO library.
-///
-/// BIND 10 uses the non-Boost version of ASIO because it's header-only,
-/// i.e., does not require a separate library object to be linked, and thus
-/// lowers the bar for introduction.
-///
-/// But the advantage comes with its own costs: since the header-only version
-/// includes more definitions in public header files, it tends to trigger
-/// more compiler warnings for our own sources, and, depending on the
-/// compiler options, may make the build fail.
-///
-/// We also found it may be tricky to use ASIO and standard C++ libraries
-/// in a single translation unit, i.e., a .cc file: depending on the order
-/// of including header files, ASIO may or may not work on some platforms.
-///
-/// This wrapper interface is intended to centralize these
-/// problematic issues in a single sub module. Other BIND 10 modules should
-/// simply include \c asio_link.h and use the wrapper API instead of
-/// including ASIO header files and using ASIO-specific classes directly.
-///
-/// This wrapper may be used for other IO libraries if and when we want to
-/// switch, but generality for that purpose is not the primary goal of
-/// this module. The resulting interfaces are thus straightforward mapping
-/// to the ASIO counterparts.
-///
-/// Notes to developers:
-/// Currently the wrapper interface is specific to the authoritative
-/// server implementation. But the plan is to generalize it and have
-/// other modules use it.
-///
-/// One obvious drawback of this approach is performance overhead
-/// due to the additional layer. We should eventually evaluate the cost
-/// of the wrapper abstraction in benchmark tests. Another drawback is
-/// that the wrapper interfaces don't provide all features of ASIO
-/// (at least for the moment). We should also re-evaluate the
-/// maintenance overhead of providing necessary wrappers as we develop
-/// more.
-///
-/// On the other hand, we may be able to exploit the wrapper approach to
-/// simplify the interfaces (by limiting the usage) and unify performance
-/// optimization points.
-///
-/// As for optimization, we may want to provide a custom allocator for
-/// the placeholder of callback handlers:
-/// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html
-
-namespace asio_link {
-class IOServiceImpl;
-
-/// \brief An exception that is thrown if an error occurs within the IO
-/// module. This is mainly intended to be a wrapper exception class for
-/// ASIO specific exceptions.
-class IOError : public isc::Exception {
-public:
- IOError(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-/// \brief The \c IOAddress class represents an IP addresses (version
-/// agnostic)
-///
-/// This class is a wrapper for the ASIO \c ip::address class.
-class IOAddress {
-public:
- ///
- /// \name Constructors and Destructor
- ///
- /// This class is copyable. We use default versions of copy constructor
- /// and the assignment operator.
- /// We use the default destructor.
- //@{
- /// \brief Constructor from string.
- ///
- /// This constructor converts a textual representation of IPv4 and IPv6
- /// addresses into an IOAddress object.
- /// If \c address_str is not a valid representation of any type of
- /// address, an exception of class \c IOError will be thrown.
- /// This constructor allocates memory for the object, and if that fails
- /// a corresponding standard exception will be thrown.
- ///
- /// \param address_str Textual representation of address.
- IOAddress(const std::string& address_str);
-
- /// \brief Constructor from an ASIO \c ip::address object.
- ///
- /// This constructor is intended to be used within the wrapper
- /// implementation; user applications of the wrapper API won't use it.
- ///
- /// This constructor never throws an exception.
- ///
- /// \param asio_address The ASIO \c ip::address to be converted.
- IOAddress(const asio::ip::address& asio_adress);
- //@}
-
- /// \brief Convert the address to a string.
- ///
- /// This method is basically expected to be exception free, but
- /// generating the string will involve resource allocation,
- /// and if it fails the corresponding standard exception will be thrown.
- ///
- /// \return A string representation of the address.
- std::string toText() const;
-private:
- asio::ip::address asio_address_;
-};
-
-/// \brief The \c IOEndpoint class is an abstract base class to represent
-/// a communication endpoint.
-///
-/// This class is a wrapper for the ASIO endpoint classes such as
-/// \c ip::tcp::endpoint and \c ip::udp::endpoint.
-///
-/// Derived class implementations are completely hidden within the
-/// implementation. User applications only get access to concrete
-/// \c IOEndpoint objects via the abstract interfaces.
-class IOEndpoint {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- IOEndpoint(const IOEndpoint& source);
- IOEndpoint& operator=(const IOEndpoint& 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).
- IOEndpoint() {}
-public:
- /// The destructor.
- virtual ~IOEndpoint() {}
- //@}
-
- /// \brief Returns the address of the endpoint.
- ///
- /// This method returns an IOAddress object corresponding to \c this
- /// endpoint.
- /// Note that the return value is a real object, not a reference or
- /// a pointer.
- /// This is aligned with the interface of the ASIO counterpart:
- /// the \c address() method of \c ip::xxx::endpoint classes returns
- /// an \c ip::address object.
- /// This also means handling the address of an endpoint using this method
- /// can be expensive. If the address information is necessary in a
- /// performance sensitive context and there's a more efficient interface
- /// for that purpose, it's probably better to avoid using this method.
- ///
- /// This method never throws an exception.
- ///
- /// \return A copy of \c IOAddress object corresponding to the endpoint.
- virtual IOAddress getAddress() const = 0;
-
- /// \brief A polymorphic factory of endpoint from address and port.
- ///
- /// This method creates a new instance of (a derived class of)
- /// \c IOEndpoint object that identifies the pair of given address
- /// and port.
- /// The appropriate derived class is chosen based on the specified
- /// transport protocol. If the \c protocol doesn't specify a protocol
- /// supported in this implementation, an exception of class \c IOError
- /// will be thrown.
- ///
- /// Memory for the created object will be dynamically allocated. It's
- /// the caller's responsibility to \c delete it later.
- /// If resource allocation for the new object fails, a corresponding
- /// standard exception will be thrown.
- ///
- /// \param protocol The transport protocol used for the endpoint.
- /// Currently, only \c IPPROTO_UDP and \c IPPROTO_TCP can be specified.
- /// \param address The (IP) address of the endpoint.
- /// \param port The transport port number of the endpoint
- /// \return A pointer to a newly created \c IOEndpoint object.
- static const IOEndpoint* create(const int protocol,
- const IOAddress& address,
- const unsigned short port);
-};
-
-/// \brief The \c IOSocket class is an abstract base class to represent
-/// various types of network sockets.
-///
-/// This class is a wrapper for the ASIO socket classes such as
-/// \c ip::tcp::socket and \c ip::udp::socket.
-///
-/// Derived class implementations are completely hidden within the
-/// implementation. User applications only get access to concrete
-/// \c IOSocket objects via the abstract interfaces.
-/// We may revisit this decision when we generalize the wrapper and more
-/// modules use it. Also, at that point we may define a separate (visible)
-/// derived class for testing purposes rather than providing factory methods
-/// (i.e., getDummy variants below).
-class IOSocket {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- IOSocket(const IOSocket& source);
- IOSocket& operator=(const IOSocket& 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).
- IOSocket() {}
-public:
- /// The destructor.
- virtual ~IOSocket() {}
- //@}
-
- /// \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 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.
- ///
- /// This method never throws an exception.
- ///
- /// \return The native representation of the socket. This is the socket
- /// file descriptor for UNIX-like systems.
- virtual int getNative() const = 0;
-
- /// \brief Return the transport protocol of the socket.
- ///
- /// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
- /// \c IPPROTO_TCP for TCP sockets.
- ///
- /// This method never throws an exception.
- ///
- /// \return IPPROTO_UDP for UDP sockets
- /// \return IPPROTO_TCP for TCP sockets
- virtual int getProtocol() const = 0;
-
- /// \brief Return a non-usable "dummy" UDP socket for testing.
- ///
- /// This is a class method that returns a "mock" of UDP socket.
- /// This is not associated with any actual socket, and its only
- /// responsibility is to return \c IPPROTO_UDP from \c getProtocol().
- /// The only feasible usage of this socket is for testing so that
- /// the test code can prepare some "UDP data" even without opening any
- /// actual socket.
- ///
- /// This method never throws an exception.
- ///
- /// \return A reference to an \c IOSocket object whose \c getProtocol()
- /// returns \c IPPROTO_UDP.
- static IOSocket& getDummyUDPSocket();
-
- /// \brief Return a non-usable "dummy" TCP socket for testing.
- ///
- /// See \c getDummyUDPSocket(). This method is its TCP version.
- ///
- /// \return A reference to an \c IOSocket object whose \c getProtocol()
- /// returns \c IPPROTO_TCP.
- static IOSocket& getDummyTCPSocket();
-};
-
-/// \brief The \c IOMessage class encapsulates an incoming message received
-/// on a socket.
-///
-/// An \c IOMessage object represents a tuple of a chunk of data
-/// (a UDP packet or some segment of TCP stream), the socket over which the
-/// data is passed, the information about the other end point of the
-/// communication, and perhaps more.
-///
-/// The current design and interfaces of this class is tentative.
-/// It only provides a minimal level of support that is necessary for
-/// the current implementation of the authoritative server.
-/// A future version of this class will definitely support more.
-class IOMessage {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- IOMessage(const IOMessage& source);
- IOMessage& operator=(const IOMessage& source);
-public:
- /// \brief Constructor from message information.
- ///
- /// This constructor needs to handle the ASIO \c ip::address class,
- /// and is intended to be used within this wrapper implementation.
- /// Once the \c IOMessage object is created, the application can
- /// get access to the information via the wrapper interface such as
- /// \c getRemoteAddress().
- ///
- /// This constructor never throws an exception.
- ///
- /// \param data A pointer to the message data.
- /// \param data_size The size of the message data in bytes.
- /// \param io_socket The socket over which the data is given.
- /// \param remote_endpoint The other endpoint of the socket, that is,
- /// the sender of the message.
- IOMessage(const void* data, const size_t data_size, IOSocket& io_socket,
- const IOEndpoint& remote_endpoint);
- //@}
-
- /// \brief Returns a pointer to the received data.
- const void* getData() const { return (data_); }
-
- /// \brief Returns the size of the received data in bytes.
- size_t getDataSize() const { return (data_size_); }
-
- /// \brief Returns the socket on which the message arrives.
- const IOSocket& getSocket() const { return (io_socket_); }
-
- /// \brief Returns the endpoint that sends the message.
- const IOEndpoint& getRemoteEndpoint() const { return (remote_endpoint_); }
-private:
- const void* data_;
- const size_t data_size_;
- IOSocket& io_socket_;
- const IOEndpoint& remote_endpoint_;
-};
-
-/// \brief The \c IOService class is a wrapper for the ASIO \c io_service
-/// class.
-///
-/// Currently, the interface of this class is very specific to the
-/// authoritative server implementation as indicated in the signature of
-/// the constructor, but the plan is to generalize it so that other BIND 10
-/// modules can use this interface, too.
-class IOService {
- ///
- /// \name Constructors and Destructor
- ///
- /// These are currently very specific to the authoritative server
- /// implementation.
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- IOService(const IOService& source);
- IOService& operator=(const IOService& source);
-public:
- /// \brief The constructor with a specific IP address and port on which
- /// the services listen on.
- IOService(AuthSrv* auth_server, const char& port, const char& address);
- /// \brief The constructor with a specific port on which the services
- /// listen on.
- ///
- /// It effectively listens on "any" IPv4 and/or IPv6 addresses.
- /// IPv4/IPv6 services will be available if and only if \c use_ipv4
- /// or \c use_ipv6 is \c true, respectively.
- IOService(AuthSrv* auth_server, const char& port,
- const bool use_ipv4, const bool use_ipv6);
- /// \brief The destructor.
- ~IOService();
- //@}
-
- /// \brief Start the underlying event loop.
- ///
- /// This method does not return control to the caller until
- /// the \c stop() method is called via some handler.
- void run();
-
- /// \brief Stop the underlying event loop.
- ///
- /// This will return the control to the caller of the \c run() method.
- void stop();
-
- /// \brief Return the native \c io_service object used in this wrapper.
- ///
- /// This is a short term work around to support other BIND 10 modules
- /// that share the same \c io_service with the authoritative server.
- /// It will eventually be removed once the wrapper interface is
- /// generalized.
- asio::io_service& get_io_service();
-
- /// \brief A functor(-like) class that specifies a custom call back
- /// invoked from the event loop instead of the embedded authoritative
- /// server callbacks.
- ///
- /// Currently, the callback is intended to be used only for testing
- /// purposes. But we'll need a generic callback type like this to
- /// generalize the wrapper interface.
- typedef boost::function<void(const IOMessage& io_message)> IOCallBack;
-
- /// \brief Set the custom call back invoked from the event loop.
- ///
- /// Right now this method is only for testing, but will eventually be
- /// generalized.
- void setCallBack(IOCallBack callback);
-private:
- IOServiceImpl* impl_;
-};
-} // asio_link
-#endif // __ASIO_LINK_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/bin/auth/auth.spec.pre.in b/src/bin/auth/auth.spec.pre.in
index b74fe47..d88ffb5 100644
--- a/src/bin/auth/auth.spec.pre.in
+++ b/src/bin/auth/auth.spec.pre.in
@@ -7,6 +7,90 @@
"item_type": "string",
"item_optional": true,
"item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3"
+ },
+ { "item_name": "datasources",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [],
+ "list_item_spec":
+ { "item_name": "list_element",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "type",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ { "item_name": "class",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "IN"
+ },
+ { "item_name": "zones",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ { "item_name": "list_element",
+ "item_type": "map",
+ "item_optional": true,
+ "item_default": { "origin": "", "file": "" },
+ "map_item_spec": [
+ { "item_name": "origin",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ { "item_name": "file",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }]
+ }
+ }]
+ }
+ },
+ { "item_name": "statistics-interval",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 60
+ },
+ {
+ "item_name": "listen_on",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [
+ {
+ "address": "::",
+ "port": 53
+ },
+ {
+ "address": "0.0.0.0",
+ "port": 53
+ }
+ ],
+ "list_item_spec": {
+ "item_name": "address",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "address",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "::1"
+ },
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 53
+ }
+ ]
+ }
}
],
"commands": [
@@ -14,8 +98,30 @@
"command_name": "shutdown",
"command_description": "Shut down authoritative DNS server",
"command_args": []
+ },
+ {
+ "command_name": "sendstats",
+ "command_description": "Send data to a statistics module at once",
+ "command_args": []
+ },
+ {
+ "command_name": "loadzone",
+ "command_description": "(Re)load a specified zone",
+ "command_args": [
+ {
+ "item_name": "class", "item_type": "string",
+ "item_optional": true, "item_default": "IN"
+ },
+ {
+ "item_name": "origin", "item_type": "string",
+ "item_optional": false, "item_default": ""
+ },
+ {
+ "item_name": "datasrc", "item_type": "string",
+ "item_optional": true, "item_default": "memory"
+ }
+ ]
}
]
}
}
-
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index 378cc7c..f46752a 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -12,7 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
+#include <config.h>
#include <netinet/in.h>
@@ -21,6 +21,14 @@
#include <iostream>
#include <vector>
+#include <boost/bind.hpp>
+
+#include <asiolink/asiolink.h>
+
+#include <config/ccsession.h>
+
+#include <cc/data.h>
+
#include <exceptions/exceptions.h>
#include <dns/buffer.h>
@@ -34,22 +42,20 @@
#include <dns/rrset.h>
#include <dns/rrttl.h>
#include <dns/message.h>
-#include <config/ccsession.h>
-#include <cc/data.h>
-#include <exceptions/exceptions.h>
#include <datasrc/query.h>
#include <datasrc/data_source.h>
+#include <datasrc/memory_datasrc.h>
#include <datasrc/static_datasrc.h>
#include <datasrc/sqlite3_datasrc.h>
-#include <cc/data.h>
-
#include <xfr/xfrout_client.h>
#include <auth/common.h>
+#include <auth/config.h>
#include <auth/auth_srv.h>
-#include <auth/asio_link.h>
+#include <auth/query.h>
+#include <auth/statistics.h>
using namespace std;
@@ -57,11 +63,13 @@ using namespace isc;
using namespace isc::cc;
using namespace isc::datasrc;
using namespace isc::dns;
+using namespace isc::auth;
using namespace isc::dns::rdata;
using namespace isc::data;
using namespace isc::config;
using namespace isc::xfr;
-using namespace asio_link;
+using namespace asiolink;
+using namespace isc::server_common::portconfig;
class AuthSrvImpl {
private:
@@ -73,38 +81,61 @@ public:
~AuthSrvImpl();
isc::data::ConstElementPtr setDbFile(isc::data::ConstElementPtr config);
- bool processNormalQuery(const IOMessage& io_message, Message& message,
- MessageRenderer& response_renderer);
- bool processAxfrQuery(const IOMessage& io_message, Message& message,
- MessageRenderer& response_renderer);
- bool processNotify(const IOMessage& io_message, Message& message,
- MessageRenderer& response_renderer);
- std::string db_file_;
+ bool processNormalQuery(const IOMessage& io_message, MessagePtr message,
+ OutputBufferPtr buffer);
+ bool processAxfrQuery(const IOMessage& io_message, MessagePtr message,
+ OutputBufferPtr buffer);
+ bool processNotify(const IOMessage& io_message, MessagePtr message,
+ OutputBufferPtr buffer);
+
+ IOService io_service_;
+
+ /// Currently non-configurable, but will be.
+ static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
+
+ /// These members are public because AuthSrv accesses them directly.
ModuleCCSession* config_session_;
+ bool verbose_mode_;
+ AbstractSession* xfrin_session_;
+
+ /// In-memory data source. Currently class IN only for simplicity.
+ const RRClass memory_datasrc_class_;
+ AuthSrv::MemoryDataSrcPtr memory_datasrc_;
+
+ /// Hot spot cache
+ isc::datasrc::HotCache cache_;
+
+ /// Interval timer for periodic submission of statistics counters.
+ IntervalTimer statistics_timer_;
+
+ /// Query counters for statistics
+ AuthCounters counters_;
+
+ /// Addresses we listen on
+ AddressList listen_addresses_;
+private:
+ std::string db_file_;
+
MetaDataSrc data_sources_;
/// We keep a pointer to the currently running sqlite datasource
/// so that we can specifically remove that one should the database
/// file change
ConstDataSrcPtr cur_datasrc_;
- bool verbose_mode_;
-
- AbstractSession* xfrin_session_;
-
bool xfrout_connected_;
AbstractXfroutClient& xfrout_client_;
- /// Currently non-configurable, but will be.
- static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
-
- /// Hot spot cache
- isc::datasrc::HotCache cache_;
+ /// Increment query counter
+ void incCounter(const int protocol);
};
AuthSrvImpl::AuthSrvImpl(const bool use_cache,
AbstractXfroutClient& xfrout_client) :
config_session_(NULL), verbose_mode_(false),
xfrin_session_(NULL),
+ memory_datasrc_class_(RRClass::IN()),
+ statistics_timer_(io_service_),
+ counters_(verbose_mode_),
xfrout_connected_(false),
xfrout_client_(xfrout_client)
{
@@ -126,60 +157,123 @@ AuthSrvImpl::~AuthSrvImpl() {
}
}
+// This is a derived class of \c DNSLookup, to serve as a
+// callback in the asiolink module. It calls
+// AuthSrv::processMessage() on a single DNS message.
+class MessageLookup : public DNSLookup {
+public:
+ MessageLookup(AuthSrv* srv) : server_(srv) {}
+ virtual void operator()(const IOMessage& io_message,
+ MessagePtr message,
+ MessagePtr answer_message,
+ OutputBufferPtr buffer,
+ DNSServer* server) const
+ {
+ (void) answer_message;
+ server_->processMessage(io_message, message, buffer, server);
+ }
+private:
+ AuthSrv* server_;
+};
+
+// This is a derived class of \c DNSAnswer, to serve as a callback in the
+// asiolink module. We actually shouldn't do anything in this class because
+// we build complete response messages in the process methods; otherwise
+// the response message will contain trailing garbage. In future, we should
+// probably even drop the reliance on DNSAnswer. We don't need the coroutine
+// tricks provided in that framework, and its overhead would be significant
+// in terms of performance consideration for the authoritative server
+// implementation.
+class MessageAnswer : public DNSAnswer {
+public:
+ MessageAnswer(AuthSrv*) {}
+ virtual void operator()(const IOMessage&, MessagePtr,
+ MessagePtr, OutputBufferPtr) const
+ {}
+};
+
+// This is a derived class of \c SimpleCallback, to serve
+// as a callback in the asiolink module. It checks for queued
+// configuration messages, and executes them if found.
+class ConfigChecker : public SimpleCallback {
+public:
+ ConfigChecker(AuthSrv* srv) : server_(srv) {}
+ virtual void operator()(const IOMessage&) const {
+ if (server_->getConfigSession()->hasQueuedMsgs()) {
+ server_->getConfigSession()->checkCommand();
+ }
+ }
+private:
+ AuthSrv* server_;
+};
+
AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client) :
- impl_(new AuthSrvImpl(use_cache, xfrout_client))
+ impl_(new AuthSrvImpl(use_cache, xfrout_client)),
+ checkin_(new ConfigChecker(this)),
+ dns_lookup_(new MessageLookup(this)),
+ dns_answer_(new MessageAnswer(this))
{}
+void
+AuthSrv::stop() {
+ impl_->io_service_.stop();
+}
+
AuthSrv::~AuthSrv() {
delete impl_;
+ delete checkin_;
+ delete dns_lookup_;
+ delete dns_answer_;
}
namespace {
class QuestionInserter {
public:
- QuestionInserter(Message* message) : message_(message) {}
+ QuestionInserter(MessagePtr message) : message_(message) {}
void operator()(const QuestionPtr question) {
message_->addQuestion(question);
}
- Message* message_;
+ MessagePtr message_;
};
void
-makeErrorMessage(Message& message, MessageRenderer& renderer,
+makeErrorMessage(MessagePtr message, OutputBufferPtr buffer,
const Rcode& rcode, const bool verbose_mode)
{
// extract the parameters that should be kept.
// XXX: with the current implementation, it's not easy to set EDNS0
// depending on whether the query had it. So we'll simply omit it.
- const qid_t qid = message.getQid();
- const bool rd = message.getHeaderFlag(MessageFlag::RD());
- const bool cd = message.getHeaderFlag(MessageFlag::CD());
- const Opcode& opcode = message.getOpcode();
+ const qid_t qid = message->getQid();
+ const bool rd = message->getHeaderFlag(Message::HEADERFLAG_RD);
+ const bool cd = message->getHeaderFlag(Message::HEADERFLAG_CD);
+ const Opcode& opcode = message->getOpcode();
vector<QuestionPtr> questions;
// If this is an error to a query or notify, we should also copy the
// question section.
if (opcode == Opcode::QUERY() || opcode == Opcode::NOTIFY()) {
- questions.assign(message.beginQuestion(), message.endQuestion());
+ questions.assign(message->beginQuestion(), message->endQuestion());
}
- message.clear(Message::RENDER);
- message.setQid(qid);
- message.setOpcode(opcode);
- message.setHeaderFlag(MessageFlag::QR());
+ message->clear(Message::RENDER);
+ message->setQid(qid);
+ message->setOpcode(opcode);
+ message->setHeaderFlag(Message::HEADERFLAG_QR);
if (rd) {
- message.setHeaderFlag(MessageFlag::RD());
+ message->setHeaderFlag(Message::HEADERFLAG_RD);
}
if (cd) {
- message.setHeaderFlag(MessageFlag::CD());
+ message->setHeaderFlag(Message::HEADERFLAG_CD);
}
- for_each(questions.begin(), questions.end(), QuestionInserter(&message));
- message.setRcode(rcode);
- message.toWire(renderer);
+ for_each(questions.begin(), questions.end(), QuestionInserter(message));
+ message->setRcode(rcode);
+
+ MessageRenderer renderer(*buffer);
+ message->toWire(renderer);
if (verbose_mode) {
cerr << "[b10-auth] sending an error response (" <<
- renderer.getLength() << " bytes):\n" << message.toText() << endl;
+ renderer.getLength() << " bytes):\n" << message->toText() << endl;
}
}
}
@@ -194,6 +288,11 @@ AuthSrv::getVerbose() const {
return (impl_->verbose_mode_);
}
+IOService&
+AuthSrv::getIOService() {
+ return (impl_->io_service_);
+}
+
void
AuthSrv::setCacheSlots(const size_t slots) {
impl_->cache_.setSlots(slots);
@@ -214,154 +313,244 @@ AuthSrv::setConfigSession(ModuleCCSession* config_session) {
impl_->config_session_ = config_session;
}
+void
+AuthSrv::setStatisticsSession(AbstractSession* statistics_session) {
+ impl_->counters_.setStatisticsSession(statistics_session);
+}
+
ModuleCCSession*
AuthSrv::getConfigSession() const {
return (impl_->config_session_);
}
-bool
-AuthSrv::processMessage(const IOMessage& io_message, Message& message,
- MessageRenderer& response_renderer)
+AuthSrv::MemoryDataSrcPtr
+AuthSrv::getMemoryDataSrc(const RRClass& rrclass) {
+ // XXX: for simplicity, we only support the IN class right now.
+ if (rrclass != impl_->memory_datasrc_class_) {
+ isc_throw(InvalidParameter,
+ "Memory data source is not supported for RR class "
+ << rrclass);
+ }
+ return (impl_->memory_datasrc_);
+}
+
+void
+AuthSrv::setMemoryDataSrc(const isc::dns::RRClass& rrclass,
+ MemoryDataSrcPtr memory_datasrc)
+{
+ // XXX: see above
+ if (rrclass != impl_->memory_datasrc_class_) {
+ isc_throw(InvalidParameter,
+ "Memory data source is not supported for RR class "
+ << rrclass);
+ }
+ if (impl_->verbose_mode_) {
+ if (!impl_->memory_datasrc_ && memory_datasrc) {
+ cerr << "[b10-auth] Memory data source is enabled for class "
+ << rrclass << endl;
+ } else if (impl_->memory_datasrc_ && !memory_datasrc) {
+ cerr << "[b10-auth] Memory data source is disabled for class "
+ << rrclass << endl;
+ }
+ }
+ impl_->memory_datasrc_ = memory_datasrc;
+}
+
+uint32_t
+AuthSrv::getStatisticsTimerInterval() const {
+ return (impl_->statistics_timer_.getInterval() / 1000);
+}
+
+void
+AuthSrv::setStatisticsTimerInterval(uint32_t interval) {
+ if (interval == impl_->statistics_timer_.getInterval()) {
+ return;
+ }
+ if (interval > 86400) {
+ // It can't occur since the value is checked in
+ // statisticsIntervalConfig::build().
+ isc_throw(InvalidParameter, "Too long interval: " << interval);
+ }
+ if (interval == 0) {
+ impl_->statistics_timer_.cancel();
+ } else {
+ impl_->statistics_timer_.setup(boost::bind(&AuthSrv::submitStatistics,
+ this),
+ interval * 1000);
+ }
+ if (impl_->verbose_mode_) {
+ if (interval == 0) {
+ cerr << "[b10-auth] Disabled statistics timer" << endl;
+ } else {
+ cerr << "[b10-auth] Set statistics timer to " << interval
+ << " seconds" << endl;
+ }
+ }
+}
+
+void
+AuthSrv::processMessage(const IOMessage& io_message, MessagePtr message,
+ OutputBufferPtr buffer, DNSServer* server)
{
InputBuffer request_buffer(io_message.getData(), io_message.getDataSize());
// First, check the header part. If we fail even for the base header,
// just drop the message.
try {
- message.parseHeader(request_buffer);
+ message->parseHeader(request_buffer);
// Ignore all responses.
- if (message.getHeaderFlag(MessageFlag::QR())) {
+ if (message->getHeaderFlag(Message::HEADERFLAG_QR)) {
if (impl_->verbose_mode_) {
cerr << "[b10-auth] received unexpected response, ignoring"
<< endl;
}
- return (false);
+ server->resume(false);
+ return;
}
} catch (const Exception& ex) {
- return (false);
+ if (impl_->verbose_mode_) {
+ cerr << "[b10-auth] DNS packet exception: " << ex.what() << endl;
+ }
+ server->resume(false);
+ return;
}
- // Parse the message. On failure, return an appropriate error.
try {
- message.fromWire(request_buffer);
+ // Parse the message.
+ message->fromWire(request_buffer);
} catch (const DNSProtocolError& error) {
if (impl_->verbose_mode_) {
cerr << "[b10-auth] returning " << error.getRcode().toText()
<< ": " << error.what() << endl;
}
- makeErrorMessage(message, response_renderer, error.getRcode(),
+ makeErrorMessage(message, buffer, error.getRcode(),
impl_->verbose_mode_);
- return (true);
+ server->resume(true);
+ return;
} catch (const Exception& ex) {
if (impl_->verbose_mode_) {
cerr << "[b10-auth] returning SERVFAIL: " << ex.what() << endl;
}
- makeErrorMessage(message, response_renderer, Rcode::SERVFAIL(),
+ makeErrorMessage(message, buffer, Rcode::SERVFAIL(),
impl_->verbose_mode_);
- return (true);
+ server->resume(true);
+ return;
} // other exceptions will be handled at a higher layer.
if (impl_->verbose_mode_) {
- cerr << "[b10-auth] received a message:\n" << message.toText() << endl;
+ cerr << "[b10-auth] received a message:\n" << message->toText() << endl;
}
// Perform further protocol-level validation.
- if (message.getOpcode() == Opcode::NOTIFY()) {
- return (impl_->processNotify(io_message, message, response_renderer));
- } else if (message.getOpcode() != Opcode::QUERY()) {
+ bool sendAnswer = true;
+ if (message->getOpcode() == Opcode::NOTIFY()) {
+ sendAnswer = impl_->processNotify(io_message, message, buffer);
+ } else if (message->getOpcode() != Opcode::QUERY()) {
if (impl_->verbose_mode_) {
cerr << "[b10-auth] unsupported opcode" << endl;
}
- makeErrorMessage(message, response_renderer, Rcode::NOTIMP(),
- impl_->verbose_mode_);
- return (true);
- }
-
- if (message.getRRCount(Section::QUESTION()) != 1) {
- makeErrorMessage(message, response_renderer, Rcode::FORMERR(),
+ makeErrorMessage(message, buffer, Rcode::NOTIMP(),
impl_->verbose_mode_);
- return (true);
- }
-
- ConstQuestionPtr question = *message.beginQuestion();
- const RRType &qtype = question->getType();
- if (qtype == RRType::AXFR()) {
- return (impl_->processAxfrQuery(io_message, message,
- response_renderer));
- } else if (qtype == RRType::IXFR()) {
- makeErrorMessage(message, response_renderer, Rcode::NOTIMP(),
+ } else if (message->getRRCount(Message::SECTION_QUESTION) != 1) {
+ makeErrorMessage(message, buffer, Rcode::FORMERR(),
impl_->verbose_mode_);
- return (true);
} else {
- return (impl_->processNormalQuery(io_message, message,
- response_renderer));
+ ConstQuestionPtr question = *message->beginQuestion();
+ const RRType &qtype = question->getType();
+ if (qtype == RRType::AXFR()) {
+ sendAnswer = impl_->processAxfrQuery(io_message, message, buffer);
+ } else if (qtype == RRType::IXFR()) {
+ makeErrorMessage(message, buffer, Rcode::NOTIMP(),
+ impl_->verbose_mode_);
+ } else {
+ sendAnswer = impl_->processNormalQuery(io_message, message, buffer);
+ }
}
+
+ server->resume(sendAnswer);
}
bool
-AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
- MessageRenderer& response_renderer)
+AuthSrvImpl::processNormalQuery(const IOMessage& io_message, MessagePtr message,
+ OutputBufferPtr buffer)
{
- ConstEDNSPtr remote_edns = message.getEDNS();
+ ConstEDNSPtr remote_edns = message->getEDNS();
const bool dnssec_ok = remote_edns && remote_edns->getDNSSECAwareness();
const uint16_t remote_bufsize = remote_edns ? remote_edns->getUDPSize() :
- Message::DEFAULT_MAX_UDPSIZE;
+ Message::DEFAULT_MAX_UDPSIZE;
+
+ message->makeResponse();
+ message->setHeaderFlag(Message::HEADERFLAG_AA);
+ message->setRcode(Rcode::NOERROR());
- message.makeResponse();
- message.setHeaderFlag(MessageFlag::AA());
- message.setRcode(Rcode::NOERROR());
+ // Increment query counter.
+ incCounter(io_message.getSocket().getProtocol());
if (remote_edns) {
EDNSPtr local_edns = EDNSPtr(new EDNS());
local_edns->setDNSSECAwareness(dnssec_ok);
local_edns->setUDPSize(AuthSrvImpl::DEFAULT_LOCAL_UDPSIZE);
- message.setEDNS(local_edns);
+ message->setEDNS(local_edns);
}
try {
- Query query(message, cache_, dnssec_ok);
- data_sources_.doQuery(query);
+ // If a memory data source is configured call the separate
+ // Query::process()
+ const ConstQuestionPtr question = *message->beginQuestion();
+ if (memory_datasrc_ && memory_datasrc_class_ == question->getClass()) {
+ const RRType& qtype = question->getType();
+ const Name& qname = question->getName();
+ auth::Query(*memory_datasrc_, qname, qtype, *message).process();
+ } else {
+ datasrc::Query query(*message, cache_, dnssec_ok);
+ data_sources_.doQuery(query);
+ }
} catch (const Exception& ex) {
if (verbose_mode_) {
cerr << "[b10-auth] Internal error, returning SERVFAIL: " <<
ex.what() << endl;
}
- makeErrorMessage(message, response_renderer, Rcode::SERVFAIL(),
- verbose_mode_);
+ makeErrorMessage(message, buffer, Rcode::SERVFAIL(), verbose_mode_);
return (true);
}
+ MessageRenderer renderer(*buffer);
const bool udp_buffer =
(io_message.getSocket().getProtocol() == IPPROTO_UDP);
- response_renderer.setLengthLimit(udp_buffer ? remote_bufsize : 65535);
- message.toWire(response_renderer);
+ renderer.setLengthLimit(udp_buffer ? remote_bufsize : 65535);
+ message->toWire(renderer);
+
if (verbose_mode_) {
cerr << "[b10-auth] sending a response ("
- << response_renderer.getLength()
- << " bytes):\n" << message.toText() << endl;
+ << renderer.getLength()
+ << " bytes):\n" << message->toText() << endl;
}
return (true);
}
bool
-AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, Message& message,
- MessageRenderer& response_renderer)
+AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, MessagePtr message,
+ OutputBufferPtr buffer)
{
+ // Increment query counter.
+ incCounter(io_message.getSocket().getProtocol());
+
if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
if (verbose_mode_) {
cerr << "[b10-auth] AXFR query over UDP isn't allowed" << endl;
}
- makeErrorMessage(message, response_renderer, Rcode::FORMERR(),
- verbose_mode_);
+ makeErrorMessage(message, buffer, Rcode::FORMERR(), verbose_mode_);
return (true);
}
try {
- xfrout_client_.connect();
- xfrout_connected_ = true;
+ if (!xfrout_connected_) {
+ xfrout_client_.connect();
+ xfrout_connected_ = true;
+ }
xfrout_client_.sendXfroutRequestInfo(
io_message.getSocket().getNative(),
io_message.getData(),
@@ -375,45 +564,39 @@ AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, Message& message,
xfrout_client_.disconnect();
xfrout_connected_ = false;
}
-
+
if (verbose_mode_) {
cerr << "[b10-auth] Error in handling XFR request: " << err.what()
<< endl;
}
- makeErrorMessage(message, response_renderer, Rcode::SERVFAIL(),
- verbose_mode_);
+ makeErrorMessage(message, buffer, Rcode::SERVFAIL(), verbose_mode_);
return (true);
}
- xfrout_client_.disconnect();
- xfrout_connected_ = false;
-
return (false);
}
bool
-AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
- MessageRenderer& response_renderer)
+AuthSrvImpl::processNotify(const IOMessage& io_message, MessagePtr message,
+ OutputBufferPtr buffer)
{
// The incoming notify must contain exactly one question for SOA of the
// zone name.
- if (message.getRRCount(Section::QUESTION()) != 1) {
+ if (message->getRRCount(Message::SECTION_QUESTION) != 1) {
if (verbose_mode_) {
cerr << "[b10-auth] invalid number of questions in notify: "
- << message.getRRCount(Section::QUESTION()) << endl;
+ << message->getRRCount(Message::SECTION_QUESTION) << endl;
}
- makeErrorMessage(message, response_renderer, Rcode::FORMERR(),
- verbose_mode_);
+ makeErrorMessage(message, buffer, Rcode::FORMERR(), verbose_mode_);
return (true);
}
- ConstQuestionPtr question = *message.beginQuestion();
+ ConstQuestionPtr question = *message->beginQuestion();
if (question->getType() != RRType::SOA()) {
if (verbose_mode_) {
cerr << "[b10-auth] invalid question RR type in notify: "
<< question->getType() << endl;
}
- makeErrorMessage(message, response_renderer, Rcode::FORMERR(),
- verbose_mode_);
+ makeErrorMessage(message, buffer, Rcode::FORMERR(), verbose_mode_);
return (true);
}
@@ -435,7 +618,7 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
}
return (false);
}
-
+
const string remote_ip_address =
io_message.getRemoteEndpoint().getAddress().toText();
static const string command_template_start =
@@ -446,7 +629,7 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
try {
ConstElementPtr notify_command = Element::fromJSON(
- command_template_start + question->getName().toText() +
+ command_template_start + question->getName().toText() +
command_template_master + remote_ip_address +
command_template_rrclass + question->getClass().toText() +
command_template_end);
@@ -460,7 +643,7 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
if (rcode != 0) {
if (verbose_mode_) {
cerr << "[b10-auth] failed to notify Zonemgr: "
- << parsed_answer->str() << endl;
+ << parsed_answer->str() << endl;
}
return (false);
}
@@ -471,13 +654,28 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
return (false);
}
- message.makeResponse();
- message.setHeaderFlag(MessageFlag::AA());
- message.setRcode(Rcode::NOERROR());
- message.toWire(response_renderer);
+ message->makeResponse();
+ message->setHeaderFlag(Message::HEADERFLAG_AA);
+ message->setRcode(Rcode::NOERROR());
+
+ MessageRenderer renderer(*buffer);
+ message->toWire(renderer);
return (true);
}
+void
+AuthSrvImpl::incCounter(const int protocol) {
+ // Increment query counter.
+ if (protocol == IPPROTO_UDP) {
+ counters_.inc(AuthCounters::COUNTER_UDP_QUERY);
+ } else if (protocol == IPPROTO_TCP) {
+ counters_.inc(AuthCounters::COUNTER_TCP_QUERY);
+ } else {
+ // unknown protocol
+ isc_throw(Unexpected, "Unknown protocol: " << protocol);
+ }
+}
+
ConstElementPtr
AuthSrvImpl::setDbFile(ConstElementPtr config) {
ConstElementPtr answer = isc::config::createAnswer();
@@ -536,6 +734,9 @@ AuthSrv::updateConfig(ConstElementPtr new_config) {
try {
// the ModuleCCSession has already checked if we have
// the correct ElementPtr type as specified in our .spec file
+ if (new_config) {
+ configureAuthServer(*this, new_config);
+ }
return (impl_->setDbFile(new_config));
} catch (const isc::Exception& error) {
if (impl_->verbose_mode_) {
@@ -544,3 +745,27 @@ AuthSrv::updateConfig(ConstElementPtr new_config) {
return (isc::config::createAnswer(1, error.what()));
}
}
+
+bool AuthSrv::submitStatistics() const {
+ return (impl_->counters_.submitStatistics());
+}
+
+uint64_t
+AuthSrv::getCounter(const AuthCounters::CounterType type) const {
+ return (impl_->counters_.getCounter(type));
+}
+
+const AddressList&
+AuthSrv::getListenAddresses() const {
+ return (impl_->listen_addresses_);
+}
+
+void
+AuthSrv::setListenAddresses(const AddressList& addresses) {
+ installListenAddresses(addresses, impl_->listen_addresses_, *dnss_);
+}
+
+void
+AuthSrv::setDNSService(asiolink::DNSService& dnss) {
+ dnss_ = &dnss;
+}
diff --git a/src/bin/auth/auth_srv.h b/src/bin/auth/auth_srv.h
index 56c8ed1..8253c85 100644
--- a/src/bin/auth/auth_srv.h
+++ b/src/bin/auth/auth_srv.h
@@ -12,32 +12,32 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __AUTH_SRV_H
#define __AUTH_SRV_H 1
#include <string>
+// For MemoryDataSrcPtr below. This should be a temporary definition until
+// we reorganize the data source framework.
+#include <boost/shared_ptr.hpp>
+
#include <cc/data.h>
#include <config/ccsession.h>
+#include <asiolink/asiolink.h>
+#include <server_common/portconfig.h>
+#include <auth/statistics.h>
+
namespace isc {
-namespace dns {
-class InputBuffer;
-class Message;
-class MessageRenderer;
+namespace datasrc {
+class MemoryDataSrc;
}
-
namespace xfr {
class AbstractXfroutClient;
-};
}
-
-namespace asio_link {
-class IOMessage;
}
+
/// \brief The implementation class for the \c AuthSrv class using the pimpl
/// idiom.
class AuthSrvImpl;
@@ -62,6 +62,7 @@ class AuthSrvImpl;
///
/// The design of this class is still in flux. It's quite likely to change
/// in future versions.
+///
class AuthSrv {
///
/// \name Constructors, Assignment Operator and Destructor.
@@ -84,11 +85,36 @@ public:
isc::xfr::AbstractXfroutClient& xfrout_client);
~AuthSrv();
//@}
- /// \return \c true if the \message contains a response to be returned;
- /// otherwise \c false.
- bool processMessage(const asio_link::IOMessage& io_message,
- isc::dns::Message& message,
- isc::dns::MessageRenderer& response_renderer);
+
+ /// Stop the server.
+ ///
+ /// It stops the internal event loop of the server and subsequently
+ /// returns the control to the top level context.
+ ///
+ /// This method should never throw an exception.
+ void stop();
+
+ /// \brief Process an incoming DNS message, then signal 'server' to resume
+ ///
+ /// A DNS query (or other message) has been received by a \c DNSServer
+ /// object. Find an answer, then post the \c DNSServer object on the
+ /// I/O service queue and return. When the server resumes, it can
+ /// send the reply.
+ ///
+ /// \param io_message The raw message received
+ /// \param message Pointer to the \c Message object
+ /// \param buffer Pointer to an \c OutputBuffer for the resposne
+ /// \param server Pointer to the \c DNSServer
+ ///
+ /// \throw isc::Unexpected Protocol type of \a message is unexpected
+ void processMessage(const asiolink::IOMessage& io_message,
+ isc::dns::MessagePtr message,
+ isc::dns::OutputBufferPtr buffer,
+ asiolink::DNSServer* server);
+
+ /// \brief Set verbose flag
+ ///
+ /// \param on The new value of the verbose flag
/// \brief Enable or disable verbose logging.
///
@@ -103,6 +129,8 @@ public:
/// This method never throws an exception.
///
/// \return \c true if verbose logging is enabled; otherwise \c false.
+
+ /// \brief Get the current value of the verbose flag
bool getVerbose() const;
/// \brief Updates the data source for the \c AuthSrv object.
@@ -114,9 +142,11 @@ public:
/// If there is a data source installed, it will be replaced with the
/// new one.
///
- /// In the current implementation, the SQLite data source is assumed.
- /// The \c config parameter will simply be passed to the initialization
- /// routine of the \c Sqlite3DataSrc class.
+ /// In the current implementation, the SQLite data source and MemoryDataSrc
+ /// are assumed.
+ /// We can enable memory data source and get the path of SQLite database by
+ /// the \c config parameter. If we disabled memory data source, the SQLite
+ /// data source will be used.
///
/// On success this method returns a data \c Element (in the form of a
/// pointer like object) indicating the successful result,
@@ -138,7 +168,7 @@ public:
/// containing the result of the update operation.
isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr config);
- /// \param Returns the command and configuration session for the
+ /// \brief Returns the command and configuration session for the
/// \c AuthSrv.
///
/// This method never throws an exception.
@@ -162,6 +192,18 @@ public:
/// control commands and configuration updates.
void setConfigSession(isc::config::ModuleCCSession* config_session);
+ /// \brief Return this object's ASIO IO Service queue
+ asiolink::IOService& getIOService();
+
+ /// \brief Return pointer to the DNS Lookup callback function
+ asiolink::DNSLookup* getDNSLookupProvider() const { return (dns_lookup_); }
+
+ /// \brief Return pointer to the DNS Answer callback function
+ asiolink::DNSAnswer* getDNSAnswerProvider() const { return (dns_answer_); }
+
+ /// \brief Return pointer to the Checkin callback function
+ asiolink::SimpleCallback* getCheckinProvider() const { return (checkin_); }
+
/// \brief Set or update the size (number of slots) of hot spot cache.
///
/// If the specified size is 0, it means the size will be unlimited.
@@ -199,8 +241,137 @@ public:
/// is shutdown.
///
void setXfrinSession(isc::cc::AbstractSession* xfrin_session);
+
+ /// A shared pointer type for \c MemoryDataSrc.
+ ///
+ /// This is defined inside the \c AuthSrv class as it's supposed to be
+ /// a short term interface until we integrate the in-memory and other
+ /// data source frameworks.
+ typedef boost::shared_ptr<isc::datasrc::MemoryDataSrc> MemoryDataSrcPtr;
+
+ /// An immutable shared pointer type for \c MemoryDataSrc.
+ typedef boost::shared_ptr<const isc::datasrc::MemoryDataSrc>
+ ConstMemoryDataSrcPtr;
+
+ /// Returns the in-memory data source configured for the \c AuthSrv,
+ /// if any.
+ ///
+ /// The in-memory data source is configured per RR class. However,
+ /// the data source may not be available for all RR classes.
+ /// If it is not available for the specified RR class, an exception of
+ /// class \c InvalidParameter will be thrown.
+ /// This method never throws an exception otherwise.
+ ///
+ /// Even for supported RR classes, the in-memory data source is not
+ /// configured by default. In that case a NULL (shared) pointer will
+ /// be returned.
+ ///
+ /// \param rrclass The RR class of the requested in-memory data source.
+ /// \return A pointer to the in-memory data source, if configured;
+ /// otherwise NULL.
+ MemoryDataSrcPtr getMemoryDataSrc(const isc::dns::RRClass& rrclass);
+
+ /// Sets or replaces the in-memory data source of the specified RR class.
+ ///
+ /// As noted in \c getMemoryDataSrc(), some RR classes may not be
+ /// supported, in which case an exception of class \c InvalidParameter
+ /// will be thrown.
+ /// This method never throws an exception otherwise.
+ ///
+ /// If there is already an in memory data source configured, it will be
+ /// replaced with the newly specified one.
+ /// \c memory_datasrc can be NULL, in which case it will (re)disable the
+ /// in-memory data source.
+ ///
+ /// \param rrclass The RR class of the in-memory data source to be set.
+ /// \param memory_datasrc A (shared) pointer to \c MemoryDataSrc to be set.
+ void setMemoryDataSrc(const isc::dns::RRClass& rrclass,
+ MemoryDataSrcPtr memory_datasrc);
+
+ /// \brief Set the communication session with Statistics.
+ ///
+ /// This function never throws an exception as far as
+ /// AuthCounters::setStatisticsSession() doesn't throw.
+ ///
+ /// Note: this interface is tentative. We'll revisit the ASIO and
+ /// session frameworks, at which point the session will probably
+ /// be passed on construction of the server.
+ ///
+ /// \param statistics_session A Session object over which statistics
+ /// information is exchanged with statistics module.
+ /// The session must be established before setting in the server
+ /// object.
+ /// Ownership isn't transferred: the caller is responsible for keeping
+ /// this object to be valid while the server object is working and for
+ /// disconnecting the session and destroying the object when the server
+ /// is shutdown.
+ void setStatisticsSession(isc::cc::AbstractSession* statistics_session);
+
+ /// Return the interval of periodic submission of statistics in seconds.
+ ///
+ /// If the statistics submission is disabled, it returns 0.
+ ///
+ /// This method never throws an exception.
+ uint32_t getStatisticsTimerInterval() const;
+
+ /// Set the interval of periodic submission of statistics.
+ ///
+ /// If the specified value is non 0, the \c AuthSrv object will submit
+ /// its statistics to the statistics module every \c interval seconds.
+ /// If it's 0, and \c AuthSrv currently submits statistics, the submission
+ /// will be disabled. \c interval must be equal to or shorter than 86400
+ /// seconds (1 day).
+ ///
+ /// This method should normally not throw an exception; however, its
+ /// underlying library routines may involve resource allocation, and
+ /// when it fails it would result in a corresponding standard exception.
+ ///
+ /// \param interval The submission interval in seconds if non 0;
+ /// or a value of 0 to disable the submission.
+ void setStatisticsTimerInterval(uint32_t interval);
+
+ /// \brief Submit statistics counters to statistics module.
+ ///
+ /// This function can throw an exception from
+ /// AuthCounters::submitStatistics().
+ ///
+ /// \return true on success, false on failure (e.g. session timeout,
+ /// session error).
+ bool submitStatistics() const;
+
+ /// \brief Get the value of counter in the AuthCounters.
+ ///
+ /// This function calls AuthCounters::getCounter() and
+ /// returns its return value.
+ ///
+ /// This function never throws an exception as far as
+ /// AuthCounters::getCounter() doesn't throw.
+ ///
+ /// Note: Currently this function is for testing purpose only.
+ ///
+ /// \param type Type of a counter to get the value of
+ ///
+ /// \return the value of the counter.
+ uint64_t getCounter(const AuthCounters::CounterType type) const;
+
+ /**
+ * \brief Set and get the addresses we listen on.
+ */
+ void setListenAddresses(const isc::server_common::portconfig::AddressList&
+ addreses);
+ const isc::server_common::portconfig::AddressList& getListenAddresses()
+ const;
+
+ /// \brief Assign an ASIO DNS Service queue to this Auth object
+ void setDNSService(asiolink::DNSService& dnss);
+
+
private:
AuthSrvImpl* impl_;
+ asiolink::SimpleCallback* checkin_;
+ asiolink::DNSLookup* dns_lookup_;
+ asiolink::DNSAnswer* dns_answer_;
+ asiolink::DNSService* dnss_;
};
#endif // __AUTH_SRV_H
diff --git a/src/bin/auth/b10-auth.8 b/src/bin/auth/b10-auth.8
index 144caf2..0356683 100644
--- a/src/bin/auth/b10-auth.8
+++ b/src/bin/auth/b10-auth.8
@@ -2,12 +2,12 @@
.\" Title: b10-auth
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: July 29, 2010
+.\" Date: March 8, 2011
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-AUTH" "8" "July 29, 2010" "BIND10" "BIND10"
+.TH "B10\-AUTH" "8" "March 8, 2011" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -22,7 +22,7 @@
b10-auth \- Authoritative DNS server
.SH "SYNOPSIS"
.HP \w'\fBb10\-auth\fR\ 'u
-\fBb10\-auth\fR [\fB\-4\fR] [\fB\-6\fR] [\fB\-a\ \fR\fB\fIaddress\fR\fR] [\fB\-n\fR] [\fB\-p\ \fR\fB\fInumber\fR\fR] [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR]
+\fBb10\-auth\fR [\fB\-n\fR] [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR]
.SH "DESCRIPTION"
.PP
The
@@ -36,11 +36,66 @@ This daemon communicates with other BIND 10 components over a
C\-Channel connection\&. If this connection is not established,
\fBb10\-auth\fR
will exit\&.
+It receives its configurations from
+\fBb10-cfgmgr\fR(8)\&.
+.SH "OPTIONS"
+.PP
+The arguments are as follows:
+.PP
+\fB\-n\fR
+.RS 4
+Do not cache answers in memory\&. The default is to use the cache for faster responses\&. The cache keeps the most recent 30,000 answers (positive and negative) in memory for 30 seconds (instead of querying the data source, such as SQLite3 database, each time)\&.
+.RE
+.PP
+\fB\-u \fR\fB\fIusername\fR\fR
+.RS 4
+The user name of the
+\fBb10\-auth\fR
+daemon\&. If specified, the daemon changes the process owner to the specified user\&. The
+\fIusername\fR
+must be either a valid numeric user ID or a valid user name\&. By default the daemon runs as the user who invokes it\&.
+.RE
.PP
-It also receives its configurations from
-\fBb10-cfgmgr\fR(8)\&. It will honor the
+\fB\-v\fR
+.RS 4
+Enabled verbose mode\&. This enables diagnostic messages to STDERR\&.
+.RE
+.SH "CONFIGURATION AND COMMANDS"
+.PP
+The configurable settings are:
+.PP
+
\fIdatabase_file\fR
-configuration to point to the SQLite3 zone file\&.
+defines the path to the SQLite3 zone file when using the sqlite datasource\&. The default is
+/usr/local/var/bind10\-devel/zone\&.sqlite3\&.
+.PP
+
+\fIlisten_on\fR
+is a list of addresses and ports for
+\fBb10\-auth\fR
+to listen on\&. The list items are the
+\fIaddress\fR
+string and
+\fIport\fR
+number\&. By default,
+\fBb10\-auth\fR
+listens on port 53 on the IPv6 (::) and IPv4 (0\&.0\&.0\&.0) wildcard addresses\&.
+.PP
+
+\fIdatasources\fR
+configures data sources\&. The list items include:
+\fItype\fR
+to optionally choose the data source type (such as
+\(lqmemory\(rq);
+\fIclass\fR
+to optionally select the class (it defaults to
+\(lqIN\(rq); and
+\fIzones\fR
+to define the
+\fIfile\fR
+path name and the
+\fIorigin\fR
+(default domain)\&. By default, this is empty\&.
.if n \{\
.sp
.\}
@@ -53,46 +108,33 @@ configuration to point to the SQLite3 zone file\&.
\fBNote\fR
.ps -1
.br
-.PP
-This prototype version uses SQLite3 as its data source backend\&. Future versions will be configurable, supporting multiple data storage types\&.
+.sp
+In this development version, currently this is only used for the memory data source\&. Only the IN class is supported at this time\&. By default, the memory data source is disabled\&. Also, currently the zone file must be canonical such as generated by \fBnamed\-compilezone \-D\fR\&.
.sp .5v
.RE
-.SH "OPTIONS"
.PP
-The arguments are as follows:
-.PP
-\fB\-4\fR
-.RS 4
-Enables IPv4 only mode\&. This switch may not be used with
-\fB\-6\fR
-nor
-\fB\-a\fR\&. By default, it listens on both IPv4 and IPv6 (if capable)\&.
-.RE
-.PP
-\fB\-6\fR
-.RS 4
-Enables IPv6 only mode\&. This switch may not be used with
-\fB\-4\fR
-nor
-\fB\-a\fR\&. By default, it listens on both IPv4 and IPv6 (if capable)\&.
-.RE
-.PP
-\fB\-a \fR\fB\fIaddress\fR\fR
-.RS 4
-The IPv4 or IPv6 address to listen on\&. This switch may not be used with
-\fB\-4\fR
-nor
-\fB\-6\fR\&. The default is to listen on all addresses\&. (This is a short term workaround\&. This argument may change\&.)
-.RE
+
+\fIstatistics\-interval\fR
+is the timer interval in seconds for
+\fBb10\-auth\fR
+to share its statistics information to
+\fBb10-stats\fR(8)\&. Statistics updates can be disabled by setting this to 0\&. The default is 60\&.
.PP
-\fB\-n\fR
-.RS 4
-Do not cache answers in memory\&. The default is to use the cache for faster responses\&. The cache keeps the most recent 30,000 answers (positive and negative) in memory for 30 seconds (instead of querying the data source, such as SQLite3 database, each time)\&.
-.RE
+The configuration commands are:
.PP
-\fB\-p \fR\fB\fInumber\fR\fR
-.RS 4
-The port number it listens on\&. The default is 5300\&.
+
+\fBloadzone\fR
+tells
+\fBb10\-auth\fR
+to load or reload a zone file\&. The arguments include:
+\fIclass\fR
+which optionally defines the class (it defaults to
+\(lqIN\(rq);
+\fIorigin\fR
+is the domain name of the zone; and
+\fIdatasrc\fR
+optionally defines the type of datasource (it defaults to
+\(lqmemory\(rq)\&.
.if n \{\
.sp
.\}
@@ -105,28 +147,27 @@ The port number it listens on\&. The default is 5300\&.
\fBNote\fR
.ps -1
.br
-The Y1 prototype runs on all interfaces and on this nonstandard port\&.
+.sp
+In this development version, currently this only supports the IN class and the memory data source\&.
.sp .5v
.RE
-.RE
.PP
-\fB\-u \fR\fB\fIusername\fR\fR
-.RS 4
-The user name of the
+
+\fBsendstats\fR
+tells
\fBb10\-auth\fR
-daemon\&. If specified, the daemon changes the process owner to the specified user\&. The
-\fIusername\fR
-must be either a valid numeric user ID or a valid user name\&. By default the daemon runs as the user who invokes it\&.
-.RE
+to send its statistics data to
+\fBb10-stats\fR(8)
+immediately\&.
.PP
-\fB\-v\fR
-.RS 4
-Enabled verbose mode\&. This enables diagnostic messages to STDERR\&.
-.RE
+
+\fBshutdown\fR
+exits
+\fBb10\-auth\fR\&. (Note that the BIND 10 boss process will restart this service\&.)
.SH "FILES"
.PP
-/usr/local/var/db/zone\&.sqlite3
+/usr/local/var/bind10\-devel/zone\&.sqlite3
\(em Location for the SQLite3 zone database when
\fIdatabase_file\fR
configuration is not defined\&.
@@ -134,9 +175,9 @@ configuration is not defined\&.
.PP
\fBb10-cfgmgr\fR(8),
-\fBb10-cmdctl\fR(8),
\fBb10-loadzone\fR(8),
\fBb10-msgq\fR(8),
+\fBb10-stats\fR(8),
\fBb10-zonemgr\fR(8),
\fBbind10\fR(8),
BIND 10 Guide\&.
diff --git a/src/bin/auth/b10-auth.xml b/src/bin/auth/b10-auth.xml
index 619fd08..2b53394 100644
--- a/src/bin/auth/b10-auth.xml
+++ b/src/bin/auth/b10-auth.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
@@ -17,11 +17,10 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
-<!-- $Id$ -->
<refentry>
<refentryinfo>
- <date>July 29, 2010</date>
+ <date>March 8, 2011</date>
</refentryinfo>
<refmeta>
@@ -45,11 +44,7 @@
<refsynopsisdiv>
<cmdsynopsis>
<command>b10-auth</command>
- <arg><option>-4</option></arg>
- <arg><option>-6</option></arg>
- <arg><option>-a <replaceable>address</replaceable></option></arg>
<arg><option>-n</option></arg>
- <arg><option>-p <replaceable>number</replaceable></option></arg>
<arg><option>-u <replaceable>username</replaceable></option></arg>
<arg><option>-v</option></arg>
</cmdsynopsis>
@@ -70,22 +65,13 @@
C-Channel connection. If this connection is not established,
<command>b10-auth</command> will exit.
<!-- TODO what if msgq connection closes later, will b10-auth exit? -->
- </para>
-
- <para>
- It also receives its configurations from
+ It receives its configurations from
<citerefentry><refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
- It will honor the <emphasis>database_file</emphasis> configuration
- to point to the SQLite3 zone file.
-<!-- TODO: data source -->
</para>
- <note><para>
- This prototype version uses SQLite3 as its data source backend.
- Future versions will be configurable, supporting multiple
- data storage types.
- </para></note>
+<!-- TODO: mention xfrin, xfrout, zonemgr ? -->
+
</refsect1>
<refsect1>
@@ -95,39 +81,6 @@
<variablelist>
<varlistentry>
- <term><option>-4</option></term>
- <listitem><para>
- Enables IPv4 only mode.
- This switch may not be used with <option>-6</option> nor
- <option>-a</option>.
- By default, it listens on both IPv4 and IPv6 (if capable).
- </para></listitem>
- </varlistentry>
-
- <varlistentry>
- <term><option>-6</option></term>
- <listitem><para>
- Enables IPv6 only mode.
- This switch may not be used with <option>-4</option> nor
- <option>-a</option>.
- By default, it listens on both IPv4 and IPv6 (if capable).
- </para></listitem>
- </varlistentry>
-
- <varlistentry>
- <term><option>-a <replaceable>address</replaceable></option></term>
-
- <listitem>
- <para>The IPv4 or IPv6 address to listen on.
- This switch may not be used with <option>-4</option> nor
- <option>-6</option>.
- The default is to listen on all addresses.
- (This is a short term workaround. This argument may change.)
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
<term><option>-n</option></term>
<listitem><para>
Do not cache answers in memory.
@@ -136,16 +89,7 @@
and negative) in memory for 30 seconds (instead of querying
the data source, such as SQLite3 database, each time).
</para></listitem>
- </varlistentry>
-
- <varlistentry>
- <term><option>-p <replaceable>number</replaceable></option></term>
- <listitem><para>
- The port number it listens on.
- The default is 5300.</para>
- <note><simpara>The Y1 prototype runs on all interfaces
- and on this nonstandard port.</simpara></note>
- </listitem>
+<!-- TODO: this is SQLite3 only -->
</varlistentry>
<varlistentry>
@@ -175,14 +119,103 @@
</refsect1>
<refsect1>
+ <title>CONFIGURATION AND COMMANDS</title>
+ <para>
+ The configurable settings are:
+ </para>
+
+ <para>
+ <varname>database_file</varname> defines the path to the
+ SQLite3 zone file when using the sqlite datasource.
+ The default is
+ <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>.
+ </para>
+
+ <para>
+ <varname>listen_on</varname> is a list of addresses and ports for
+ <command>b10-auth</command> to listen on.
+ The list items are the <varname>address</varname> string
+ and <varname>port</varname> number.
+ By default, <command>b10-auth</command> listens on port 53
+ on the IPv6 (::) and IPv4 (0.0.0.0) wildcard addresses.
+ </para>
+
+ <para>
+ <varname>datasources</varname> configures data sources.
+ The list items include:
+ <varname>type</varname> to optionally choose the data source type
+ (such as <quote>memory</quote>);
+ <varname>class</varname> to optionally select the class
+ (it defaults to <quote>IN</quote>);
+ and
+ <varname>zones</varname> to define the
+ <varname>file</varname> path name and the
+ <varname>origin</varname> (default domain).
+
+ By default, this is empty.
+
+ <note><simpara>
+ In this development version, currently this is only used for the
+ memory data source.
+ Only the IN class is supported at this time.
+ By default, the memory data source is disabled.
+ Also, currently the zone file must be canonical such as
+ generated by <command>named-compilezone -D</command>.
+ </simpara></note>
+ </para>
+
+ <para>
+ <varname>statistics-interval</varname> is the timer interval
+ in seconds for <command>b10-auth</command> to share its
+ statistics information to
+ <citerefentry><refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+ Statistics updates can be disabled by setting this to 0.
+ The default is 60.
+ </para>
+
+<!-- TODO: formating -->
+ <para>
+ The configuration commands are:
+ </para>
+
+ <para>
+ <command>loadzone</command> tells <command>b10-auth</command>
+ to load or reload a zone file. The arguments include:
+ <varname>class</varname> which optionally defines the class
+ (it defaults to <quote>IN</quote>);
+ <varname>origin</varname> is the domain name of the zone;
+ and
+ <varname>datasrc</varname> optionally defines the type of datasource
+ (it defaults to <quote>memory</quote>).
+
+ <note><simpara>
+ In this development version, currently this only supports the
+ IN class and the memory data source.
+ </simpara></note>
+ </para>
+
+ <para>
+ <command>sendstats</command> tells <command>b10-auth</command>
+ to send its statistics data to
+ <citerefentry><refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ immediately.
+ </para>
+
+ <para>
+ <command>shutdown</command> exits <command>b10-auth</command>.
+ (Note that the BIND 10 boss process will restart this service.)
+ </para>
+
+ </refsect1>
+
+ <refsect1>
<title>FILES</title>
<para>
- <filename>/usr/local/var/db/zone.sqlite3</filename>
+ <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>
— Location for the SQLite3 zone database
when <emphasis>database_file</emphasis> configuration is not
defined.
</para>
-<!-- TODO: this is not correct yet. -->
</refsect1>
<refsect1>
@@ -192,15 +225,15 @@
<refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
</citerefentry>,
<citerefentry>
- <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
- </citerefentry>,
- <citerefentry>
<refentrytitle>b10-loadzone</refentrytitle><manvolnum>8</manvolnum>
</citerefentry>,
<citerefentry>
<refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum>
</citerefentry>,
<citerefentry>
+ <refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
<refentrytitle>b10-zonemgr</refentrytitle><manvolnum>8</manvolnum>
</citerefentry>,
<citerefentry>
diff --git a/src/bin/auth/benchmarks/Makefile.am b/src/bin/auth/benchmarks/Makefile.am
index e82fa97..3078dd5 100644
--- a/src/bin/auth/benchmarks/Makefile.am
+++ b/src/bin/auth/benchmarks/Makefile.am
@@ -8,7 +8,10 @@ CLEANFILES = *.gcno *.gcda
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 += ../statistics.h ../statistics.cc
query_bench_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
query_bench_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
@@ -17,5 +20,8 @@ query_bench_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
query_bench_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
query_bench_LDADD += $(top_builddir)/src/lib/cc/libcc.la
query_bench_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
-query_bench_LDADD += $(top_builddir)/src/bin/auth/libasio_link.a
+query_bench_LDADD += $(top_builddir)/src/lib/log/liblog.la
+query_bench_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
+query_bench_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+query_bench_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
query_bench_LDADD += $(SQLITE_LIBS)
diff --git a/src/bin/auth/benchmarks/query_bench.cc b/src/bin/auth/benchmarks/query_bench.cc
index 67c4994..5e69134 100644
--- a/src/bin/auth/benchmarks/query_bench.cc
+++ b/src/bin/auth/benchmarks/query_bench.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdlib.h>
#include <iostream>
@@ -26,7 +24,6 @@
#include <dns/buffer.h>
#include <dns/message.h>
-#include <dns/messagerenderer.h>
#include <dns/name.h>
#include <dns/question.h>
#include <dns/rrclass.h>
@@ -34,86 +31,166 @@
#include <xfr/xfrout_client.h>
#include <auth/auth_srv.h>
-#include <auth/asio_link.h>
+#include <auth/config.h>
+#include <auth/query.h>
+
+#include <asiolink/asiolink.h>
using namespace std;
using namespace isc;
using namespace isc::data;
+using namespace isc::auth;
using namespace isc::dns;
using namespace isc::xfr;
using namespace isc::bench;
-using namespace asio_link;
+using namespace asiolink;
namespace {
// Commonly used constant:
XfroutClient xfrout_client("dummy_path"); // path doesn't matter
+// Just something to pass as the server to resume
+class DummyServer : public DNSServer {
+ public:
+ virtual void operator()(asio::error_code, size_t) {}
+ virtual void resume(const bool) {}
+ virtual DNSServer* clone() {
+ return (new DummyServer(*this));
+ }
+};
+
class QueryBenchMark {
-private:
+protected:
// Maintain dynamically generated objects via shared pointers because
// QueryBenchMark objects will be copied.
typedef boost::shared_ptr<AuthSrv> AuthSrvPtr;
+private:
typedef boost::shared_ptr<const IOEndpoint> IOEndpointPtr;
-public:
- QueryBenchMark(const int cache_slots, const char* const datasrc_file,
- const BenchQueries& queries, Message& query_message,
- MessageRenderer& renderer) :
- server_(new AuthSrv(cache_slots >= 0 ? true : false, xfrout_client)),
+protected:
+ QueryBenchMark(const bool enable_cache,
+ const BenchQueries& queries, MessagePtr query_message,
+ OutputBufferPtr buffer) :
+ server_(new AuthSrv(enable_cache, xfrout_client)),
queries_(queries),
query_message_(query_message),
- renderer_(renderer),
+ buffer_(buffer),
dummy_socket(IOSocket::getDummyUDPSocket()),
dummy_endpoint(IOEndpointPtr(IOEndpoint::create(IPPROTO_UDP,
IOAddress("192.0.2.1"),
- 5300)))
- {
- if (cache_slots >= 0) {
- server_->setCacheSlots(cache_slots);
- }
- server_->updateConfig(Element::fromJSON("{\"database_file\": \"" +
- string(datasrc_file) + "\"}"));
- }
+ 53210)))
+ {}
+public:
unsigned int run() {
BenchQueries::const_iterator query;
const BenchQueries::const_iterator query_end = queries_.end();
+ DummyServer server;
for (query = queries_.begin(); query != query_end; ++query) {
IOMessage io_message(&(*query)[0], (*query).size(), dummy_socket,
*dummy_endpoint);
- query_message_.clear(Message::PARSE);
- renderer_.clear();
- server_->processMessage(io_message, query_message_, renderer_);
+ query_message_->clear(Message::PARSE);
+ buffer_->clear();
+ server_->processMessage(io_message, query_message_, buffer_,
+ &server);
}
return (queries_.size());
}
-private:
+protected:
AuthSrvPtr server_;
+private:
const BenchQueries& queries_;
- Message& query_message_;
- MessageRenderer& renderer_;
+ MessagePtr query_message_;
+ OutputBufferPtr buffer_;
IOSocket& dummy_socket;
IOEndpointPtr dummy_endpoint;
};
+
+class Sqlite3QueryBenchMark : public QueryBenchMark {
+public:
+ Sqlite3QueryBenchMark(const int cache_slots,
+ const char* const datasrc_file,
+ const BenchQueries& queries,
+ MessagePtr query_message,
+ OutputBufferPtr buffer) :
+ QueryBenchMark(cache_slots >= 0 ? true : false, queries,
+ query_message, buffer)
+ {
+ if (cache_slots >= 0) {
+ server_->setCacheSlots(cache_slots);
+ }
+ server_->updateConfig(Element::fromJSON("{\"database_file\": \"" +
+ string(datasrc_file) + "\"}"));
+ }
+};
+
+class MemoryQueryBenchMark : public QueryBenchMark {
+public:
+ MemoryQueryBenchMark(const char* const zone_file,
+ const char* const zone_origin,
+ const BenchQueries& queries,
+ MessagePtr query_message,
+ OutputBufferPtr buffer) :
+ QueryBenchMark(false, queries, query_message, buffer)
+ {
+ configureAuthServer(*server_,
+ Element::fromJSON(
+ "{\"datasources\": "
+ " [{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"" +
+ string(zone_origin) + "\","
+ " \"file\": \"" +
+ string(zone_file) + "\"}]}]}"));
+ }
+};
+
+void
+printQPSResult(unsigned int iteration, double duration,
+ double iteration_per_second)
+{
+ cout.precision(6);
+ cout << "Processed " << iteration << " queries in "
+ << fixed << duration << "s";
+ cout.precision(2);
+ cout << " (" << fixed << iteration_per_second << "qps)" << endl;
+}
}
namespace isc {
namespace bench {
template<>
void
-BenchMark<QueryBenchMark>::printResult() const {
- cout.precision(6);
- cout << "Processed " << getIteration() << " queries in "
- << fixed << getDuration() << "s";
- cout.precision(2);
- cout << " (" << fixed << getIterationPerSecond() << "qps)" << endl;
+BenchMark<Sqlite3QueryBenchMark>::printResult() const {
+ printQPSResult(getIteration(), getDuration(), getIterationPerSecond());
+}
+
+template<>
+void
+BenchMark<MemoryQueryBenchMark>::printResult() const {
+ printQPSResult(getIteration(), getDuration(), getIterationPerSecond());
}
}
}
namespace {
+const int ITERATION_DEFAULT = 1;
+enum DataSrcType {
+ SQLITE3,
+ MEMORY
+};
+
void
usage() {
- cerr << "Usage: query_bench [-n iterations] datasrc_file query_datafile"
+ cerr <<
+ "Usage: query_bench [-n iterations] [-t datasrc_type] [-o origin] "
+ "datasrc_file query_datafile\n"
+ " -n Number of iterations per test case (default: "
+ << ITERATION_DEFAULT << ")\n"
+ " -t Type of data source: sqlite3|memory (default: sqlite3)\n"
+ " -o Origin name of datasrc_file necessary for \"memory\", "
+ "ignored for others\n"
+ " datasrc_file: sqlite3 DB file for \"sqlite3\", "
+ "textual master file for \"memory\" datasrc\n"
+ " query_datafile: queryperf style input data"
<< endl;
exit (1);
}
@@ -122,12 +199,20 @@ usage() {
int
main(int argc, char* argv[]) {
int ch;
- int iteration = 1;
- while ((ch = getopt(argc, argv, "n:")) != -1) {
+ int iteration = ITERATION_DEFAULT;
+ const char* opt_datasrc_type = "sqlite3";
+ const char* origin = NULL;
+ while ((ch = getopt(argc, argv, "n:t:o:")) != -1) {
switch (ch) {
case 'n':
iteration = atoi(optarg);
break;
+ case 't':
+ opt_datasrc_type = optarg;
+ break;
+ case 'o':
+ origin = optarg;
+ break;
case '?':
default:
usage();
@@ -141,40 +226,68 @@ main(int argc, char* argv[]) {
const char* const datasrc_file = argv[0];
const char* const query_data_file = argv[1];
+ DataSrcType datasrc_type = SQLITE3;
+ if (strcmp(opt_datasrc_type, "sqlite3") == 0) {
+ ; // no need to override
+ } else if (strcmp(opt_datasrc_type, "memory") == 0) {
+ datasrc_type = MEMORY;
+ } else {
+ cerr << "Unknown data source type: " << datasrc_type << endl;
+ return (1);
+ }
+
+ if (datasrc_type == MEMORY && origin == NULL) {
+ cerr << "'-o Origin' is missing for memory data source " << endl;
+ return (1);
+ }
+
BenchQueries queries;
loadQueryData(query_data_file, queries, RRClass::IN());
- OutputBuffer buffer(4096);
- MessageRenderer renderer(buffer);
- Message message(Message::PARSE);
+ OutputBufferPtr buffer(new OutputBuffer(4096));
+ MessagePtr message(new Message(Message::PARSE));
cout << "Parameters:" << endl;
cout << " Iterations: " << iteration << endl;
- cout << " Data Source: " << datasrc_file << endl;
+ cout << " Data Source: type=" << opt_datasrc_type << ", file=" <<
+ datasrc_file << endl;
+ if (origin != NULL) {
+ cout << " Origin: " << origin << endl;
+ }
cout << " Query data: file=" << query_data_file << " (" << queries.size()
<< " queries)" << endl << endl;
- cout << "Benchmark enabling Hot Spot Cache with unlimited slots "
- << endl;
- BenchMark<QueryBenchMark>(iteration,
- QueryBenchMark(0, datasrc_file, queries, message,
- renderer));
+ switch (datasrc_type) {
+ case SQLITE3:
+ cout << "Benchmark enabling Hot Spot Cache with unlimited slots "
+ << endl;
+ BenchMark<Sqlite3QueryBenchMark>(
+ iteration, Sqlite3QueryBenchMark(0, datasrc_file, queries,
+ message, buffer));
- cout << "Benchmark enabling Hot Spot Cache with 10*#queries slots "
- << endl;
- BenchMark<QueryBenchMark>(iteration,
- QueryBenchMark(10 * queries.size(), datasrc_file,
- queries, message, renderer));
+ cout << "Benchmark enabling Hot Spot Cache with 10*#queries slots "
+ << endl;
+ BenchMark<Sqlite3QueryBenchMark>(
+ iteration, Sqlite3QueryBenchMark(10 * queries.size(), datasrc_file,
+ queries, message, buffer));
- cout << "Benchmark enabling Hot Spot Cache with #queries/2 slots "
- << endl;
- BenchMark<QueryBenchMark>(iteration,
- QueryBenchMark(queries.size() / 2, datasrc_file,
- queries, message, renderer));
-
- cout << "Benchmark disabling Hot Spot Cache" << endl;
- BenchMark<QueryBenchMark>(iteration,
- QueryBenchMark(-1, datasrc_file, queries,
- message, renderer));
+ cout << "Benchmark enabling Hot Spot Cache with #queries/2 slots "
+ << endl;
+ BenchMark<Sqlite3QueryBenchMark>(
+ iteration, Sqlite3QueryBenchMark(queries.size() / 2, datasrc_file,
+ queries, message, buffer));
+
+ cout << "Benchmark disabling Hot Spot Cache" << endl;
+ BenchMark<Sqlite3QueryBenchMark>(
+ iteration, Sqlite3QueryBenchMark(-1, datasrc_file, queries,
+ message, buffer));
+ break;
+ case MEMORY:
+ cout << "Benchmark with In Memory Data Source" << endl;
+ BenchMark<MemoryQueryBenchMark>(
+ iteration, MemoryQueryBenchMark(datasrc_file, origin, queries,
+ message, buffer));
+ break;
+ }
return (0);
}
diff --git a/src/bin/auth/change_user.cc b/src/bin/auth/change_user.cc
index 97bbbb0..253b8fb 100644
--- a/src/bin/auth/change_user.cc
+++ b/src/bin/auth/change_user.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <errno.h>
#include <string.h>
#include <pwd.h>
@@ -26,6 +24,7 @@
#include <auth/common.h>
using namespace boost;
+using namespace std;
void
changeUser(const char* const username) {
@@ -42,14 +41,14 @@ changeUser(const char* const username) {
}
}
if (runas_pw == NULL) {
- isc_throw(FatalError, "Unknown user name or UID:" << username);
+ throw FatalError("Unknown user name or UID:" + string(username));
}
if (setgid(runas_pw->pw_gid) < 0) {
- isc_throw(FatalError, "setgid() failed: " << strerror(errno));
+ throw FatalError("setgid() failed: " + string(strerror(errno)));
}
if (setuid(runas_pw->pw_uid) < 0) {
- isc_throw(FatalError, "setuid() failed: " << strerror(errno));
+ throw FatalError("setuid() failed: " + string(strerror(errno)));
}
}
diff --git a/src/bin/auth/change_user.h b/src/bin/auth/change_user.h
index 7a18926..e4fc5ee 100644
--- a/src/bin/auth/change_user.h
+++ b/src/bin/auth/change_user.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __CHANGE_USER_H
#define __CHANGE_USER_H 1
diff --git a/src/bin/auth/command.cc b/src/bin/auth/command.cc
new file mode 100644
index 0000000..eafcae8
--- /dev/null
+++ b/src/bin/auth/command.cc
@@ -0,0 +1,252 @@
+// 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 <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rrclass.h>
+
+#include <cc/data.h>
+
+#include <datasrc/memory_datasrc.h>
+
+#include <config/ccsession.h>
+
+#include <auth/auth_srv.h>
+#include <auth/command.h>
+
+using namespace std;
+using boost::shared_ptr;
+using boost::scoped_ptr;
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::datasrc;
+using namespace isc::config;
+
+namespace {
+/// An exception that is thrown if an error occurs while handling a command
+/// on an \c AuthSrv object.
+///
+/// Currently it's only used internally, since \c execAuthServerCommand()
+/// (which is the only interface to this module) catches all \c isc::
+/// exceptions and converts them.
+class AuthCommandError : public isc::Exception {
+public:
+ AuthCommandError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// An abstract base class that represents a single command identifier
+/// for an \c AuthSrv object.
+///
+/// Each of derived classes of \c AuthCommand, which are hidden inside the
+/// implementation, corresponds to a command executed on \c AuthSrv, such as
+/// "shutdown". The derived class is responsible to execute the corresponding
+/// command with the given command arguments (if any) in its \c exec()
+/// method.
+///
+/// In the initial implementation the existence of the command classes is
+/// hidden inside the implementation since the only public interface is
+/// \c execAuthServerCommand(), which does not expose this class.
+/// In future, we may want to make this framework more dynamic, i.e.,
+/// registering specific derived classes run time outside of this
+/// implementation. If and when that happens the definition of the abstract
+/// class will be published.
+class AuthCommand {
+ ///
+ /// \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:
+ AuthCommand(const AuthCommand& source);
+ AuthCommand& operator=(const AuthCommand& 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).
+ AuthCommand() {}
+public:
+ /// The destructor.
+ virtual ~AuthCommand() {}
+ //@}
+
+ /// Execute a single control command.
+ ///
+ /// Specific derived methods can throw exceptions. When called via
+ /// \c execAuthServerCommand(), all BIND 10 exceptions are caught
+ /// and converted into an error code.
+ /// The derived method may also throw an exception of class
+ /// \c AuthCommandError when it encounters an internal error, such as
+ /// semantics error on the command arguments.
+ ///
+ /// \param server The \c AuthSrv object on which the command is executed.
+ /// \param args Command specific argument.
+ virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) = 0;
+};
+
+// Handle the "shutdown" command. No argument is assumed.
+class ShutdownCommand : public AuthCommand {
+public:
+ virtual void exec(AuthSrv& server, isc::data::ConstElementPtr) {
+ server.stop();
+ }
+};
+
+// Handle the "sendstats" command. No argument is assumed.
+class SendStatsCommand : public AuthCommand {
+public:
+ virtual void exec(AuthSrv& server, isc::data::ConstElementPtr) {
+ if (server.getVerbose()) {
+ cerr << "[b10-auth] command 'sendstats' received" << endl;
+ }
+ server.submitStatistics();
+ }
+};
+
+// Handle the "loadzone" command.
+class LoadZoneCommand : public AuthCommand {
+public:
+ virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) {
+ // parse and validate the args.
+ if (!validate(server, args)) {
+ return;
+ }
+
+ // Load a new zone and replace the current zone with the new one.
+ // TODO: eventually this should be incremental or done in some way
+ // that doesn't block other server operations.
+ // TODO: we may (should?) want to check the "last load time" and
+ // the timestamp of the file and skip loading if the file isn't newer.
+ shared_ptr<MemoryZone> newzone(new MemoryZone(oldzone->getClass(),
+ oldzone->getOrigin()));
+ newzone->load(oldzone->getFileName());
+ oldzone->swap(*newzone);
+
+ if (server.getVerbose()) {
+ cerr << "[b10-auth] Loaded zone '" << newzone->getOrigin()
+ << "'/" << newzone->getClass() << endl;
+ }
+ }
+
+private:
+ shared_ptr<MemoryZone> oldzone; // zone to be updated with the new file.
+
+ // A helper private method to parse and validate command parameters.
+ // On success, it sets 'oldzone' to the zone to be updated.
+ // It returns true if everything is okay; and false if the command is
+ // valid but there's no need for further process.
+ bool validate(AuthSrv& server, isc::data::ConstElementPtr args) {
+ if (args == NULL) {
+ isc_throw(AuthCommandError, "Null argument");
+ }
+
+ // In this initial implementation, we assume memory data source
+ // for class IN by default.
+ ConstElementPtr datasrc_elem = args->get("datasrc");
+ if (datasrc_elem) {
+ if (datasrc_elem->stringValue() == "sqlite3") {
+ if (server.getVerbose()) {
+ cerr << "[b10-auth] Nothing to do for loading sqlite3"
+ << endl;
+ }
+ return (false);
+ } else if (datasrc_elem->stringValue() != "memory") {
+ // (note: at this point it's guaranteed that datasrc_elem
+ // is of string type)
+ isc_throw(AuthCommandError,
+ "Data source type " << datasrc_elem->stringValue()
+ << " is not supported");
+ }
+ }
+
+ ConstElementPtr class_elem = args->get("class");
+ const RRClass zone_class = class_elem ?
+ RRClass(class_elem->stringValue()) : RRClass::IN();
+
+ AuthSrv::MemoryDataSrcPtr datasrc(server.getMemoryDataSrc(zone_class));
+ if (datasrc == NULL) {
+ isc_throw(AuthCommandError, "Memory data source is disabled");
+ }
+
+ ConstElementPtr origin_elem = args->get("origin");
+ if (!origin_elem) {
+ isc_throw(AuthCommandError, "Zone origin is missing");
+ }
+ const Name origin(origin_elem->stringValue());
+
+ // Get the current zone
+ const MemoryDataSrc::FindResult result = datasrc->findZone(origin);
+ if (result.code != result::SUCCESS) {
+ isc_throw(AuthCommandError, "Zone " << origin <<
+ " is not found in data source");
+ }
+
+ oldzone = boost::dynamic_pointer_cast<MemoryZone>(result.zone);
+
+ return (true);
+ }
+};
+
+// The factory of command objects.
+AuthCommand*
+createAuthCommand(const string& command_id) {
+ // For the initial implementation we use a naive if-else blocks
+ // (see also createAuthConfigParser())
+ if (command_id == "shutdown") {
+ return (new ShutdownCommand());
+ } else if (command_id == "sendstats") {
+ return (new SendStatsCommand());
+ } else if (command_id == "loadzone") {
+ return (new LoadZoneCommand());
+ } else if (false && command_id == "_throw_exception") {
+ // This is for testing purpose only and should not appear in the
+ // actual configuration syntax.
+ // XXX: ModuleCCSession doesn't seem to validate commands (unlike
+ // config), so we should disable this case for now.
+ throw runtime_error("throwing for test");
+ }
+
+ isc_throw(AuthCommandError, "Unknown command identifier: " << command_id);
+}
+} // end of unnamed namespace
+
+ConstElementPtr
+execAuthServerCommand(AuthSrv& server, const string& command_id,
+ ConstElementPtr args)
+{
+ if (server.getVerbose()) {
+ cerr << "[b10-auth] Received '" << command_id << "' command" << endl;
+ }
+
+ try {
+ scoped_ptr<AuthCommand>(createAuthCommand(command_id))->exec(server,
+ args);
+ } catch (const isc::Exception& ex) {
+ if (server.getVerbose()) {
+ cerr << "[b10-auth] Command '" << command_id
+ << "' execution failed: " << ex.what() << endl;
+ }
+ return (createAnswer(1, ex.what()));
+ }
+
+ return (createAnswer());
+}
diff --git a/src/bin/auth/command.h b/src/bin/auth/command.h
new file mode 100644
index 0000000..7db5cd6
--- /dev/null
+++ b/src/bin/auth/command.h
@@ -0,0 +1,61 @@
+// 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 <cc/data.h>
+
+#ifndef __COMMAND_H
+#define __COMMAND_H 1
+
+class AuthSrv;
+
+/// Execute a control command on \c AuthSrv
+///
+/// It executes the operation identified by \c command_id with arguments
+/// \c args on \c server, and returns the result in the form of the standard
+/// command/config answer message (see \c isc::config::createAnswer()).
+///
+/// This method internally performs minimal validation on \c command_id and
+/// \c args to not cause a surprising result such as a crash, but it is
+/// generally expected that the caller has performed syntax level validation
+/// based on the command specification for the authoritative server.
+/// For example, the caller is responsible \c command_id is a valid command
+/// name for the authoritative server.
+///
+/// The execution of the command may internally trigger an exception; for
+/// instance, if a string that is expected to be a valid domain name is bogus,
+/// the underlying DNS library will throw an exception. These internal
+/// exceptions will be caught inside this function, and will be converted
+/// to a return value with a non 0 error code.
+/// However, exceptions thrown outside of BIND 10 modules (including standard
+/// exceptions) are expected to be handled at a higher layer, and will be
+/// propagated to the caller. In particular if memory allocation fails and
+/// \c std::bad_alloc is thrown it will be propagated.
+///
+/// \param server The \c AuthSrv object on which the command is executed.
+/// \param command_id The identifier of the command (such as "shutdown")
+/// \param args Command specific argument in a map type of
+/// \c isc::data::Element, or a \c NULL \c ElementPtr if the argument isn't
+/// specified.
+/// \return The result of the command operation.
+isc::data::ConstElementPtr
+execAuthServerCommand(AuthSrv& server, const std::string& command_id,
+ isc::data::ConstElementPtr args);
+
+#endif // __COMMAND_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/bin/auth/common.h b/src/bin/auth/common.h
index d8bfd39..6af09fb 100644
--- a/src/bin/auth/common.h
+++ b/src/bin/auth/common.h
@@ -12,17 +12,21 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __COMMON_H
#define __COMMON_H 1
-#include <exceptions/exceptions.h>
+#include <stdexcept>
+#include <string>
-class FatalError : public isc::Exception {
+/// An exception class that is thrown in an unrecoverable error condition.
+///
+/// This exception should not be caught except at the highest level of
+/// the application only for terminating the program gracefully, and so
+/// it cannot be a derived class of \c isc::Exception.
+class FatalError : public std::runtime_error {
public:
- FatalError(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
+ FatalError(const std::string& what) : std::runtime_error(what)
+ {}
};
#endif // __COMMON_H
diff --git a/src/bin/auth/config.cc b/src/bin/auth/config.cc
new file mode 100644
index 0000000..f289ca0
--- /dev/null
+++ b/src/bin/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/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
new file mode 100644
index 0000000..6f18810
--- /dev/null
+++ b/src/bin/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/main.cc b/src/bin/auth/main.cc
index 2778a6b..fad4a72 100644
--- a/src/bin/auth/main.cc
+++ b/src/bin/auth/main.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
@@ -25,8 +23,6 @@
#include <cassert>
#include <iostream>
-#include <boost/foreach.hpp>
-
#include <exceptions/exceptions.h>
#include <dns/buffer.h>
@@ -41,9 +37,12 @@
#include <auth/spec_config.h>
#include <auth/common.h>
+#include <auth/config.h>
+#include <auth/command.h>
#include <auth/change_user.h>
#include <auth/auth_srv.h>
-#include <auth/asio_link.h>
+#include <asiolink/asiolink.h>
+#include <log/dummylog.h>
using namespace std;
using namespace isc::data;
@@ -51,21 +50,17 @@ using namespace isc::cc;
using namespace isc::config;
using namespace isc::dns;
using namespace isc::xfr;
+using namespace asiolink;
namespace {
bool verbose_mode = false;
-const string PROGRAM = "Auth";
-const char* DNSPORT = "5300";
-
/* need global var for config/command handlers.
* todo: turn this around, and put handlers in the authserver
* class itself? */
AuthSrv *auth_server;
-asio_link::IOService* io_service;
-
ConstElementPtr
my_config_handler(ConstElementPtr new_config) {
return (auth_server->updateConfig(new_config));
@@ -73,22 +68,17 @@ my_config_handler(ConstElementPtr new_config) {
ConstElementPtr
my_command_handler(const string& command, ConstElementPtr args) {
- ConstElementPtr answer = createAnswer();
-
- if (command == "print_message") {
- cout << args << endl;
- /* let's add that message to our answer as well */
- answer = createAnswer(0, args);
- } else if (command == "shutdown") {
- io_service->stop();
- }
-
- return (answer);
+ assert(auth_server != NULL);
+ return (execAuthServerCommand(*auth_server, command, args));
}
void
usage() {
- cerr << "Usage: b10-auth [-a address] [-p port] [-4|-6] [-nv]" << endl;
+ cerr << "Usage: b10-auth [-u user] [-nv]"
+ << endl;
+ cerr << "\t-n: do not cache answers in memory" << endl;
+ cerr << "\t-u: change process UID to the specified user" << endl;
+ cerr << "\t-v: verbose output" << endl;
exit(1);
}
} // end of anonymous namespace
@@ -96,38 +86,20 @@ usage() {
int
main(int argc, char* argv[]) {
int ch;
- const char* port = DNSPORT;
- const char* address = NULL;
const char* uid = NULL;
- bool use_ipv4 = true, use_ipv6 = true, cache = true;
+ bool cache = true;
- while ((ch = getopt(argc, argv, "46a:np:u:v")) != -1) {
+ while ((ch = getopt(argc, argv, ":nu:v")) != -1) {
switch (ch) {
- case '4':
- // Note that -4 means "ipv4 only", we need to set "use_ipv6" here,
- // not "use_ipv4". We could use something like "ipv4_only", but
- // we found the negatively named variable could confuse the code
- // logic.
- use_ipv6 = false;
- break;
- case '6':
- // The same note as -4 applies.
- use_ipv4 = false;
- break;
case 'n':
cache = false;
break;
- case 'a':
- address = optarg;
- break;
- case 'p':
- port = optarg;
- break;
case 'u':
uid = optarg;
break;
case 'v':
verbose_mode = true;
+ isc::log::denabled = true;
break;
case '?':
default:
@@ -139,26 +111,24 @@ main(int argc, char* argv[]) {
usage();
}
- if (!use_ipv4 && !use_ipv6) {
- cerr << "[b10-auth] Error: -4 and -6 can't coexist" << endl;
- usage();
- }
-
- if ((!use_ipv4 || !use_ipv6) && address != NULL) {
- cerr << "[b10-auth] Error: -4|-6 and -a can't coexist" << endl;
- usage();
- }
-
int ret = 0;
// XXX: we should eventually pass io_service here.
Session* cc_session = NULL;
Session* xfrin_session = NULL;
+ Session* statistics_session = NULL;
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) {
- xfrout_socket_path = string(getenv("B10_FROM_BUILD")) + "/auth_xfrout_conn";
+ 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;
}
@@ -176,22 +146,16 @@ main(int argc, char* argv[]) {
auth_server->setVerbose(verbose_mode);
cout << "[b10-auth] Server created." << endl;
- if (address != NULL) {
- // XXX: we can only specify at most one explicit address.
- // This also means the server cannot run in the dual address
- // family mode if explicit addresses need to be specified.
- // We don't bother to fix this problem, however. The -a option
- // is a short term workaround until we support dynamic listening
- // port allocation.
- io_service = new asio_link::IOService(auth_server, *port,
- *address);
- } else {
- io_service = new asio_link::IOService(auth_server, *port,
- use_ipv4, use_ipv6);
- }
- cout << "[b10-auth] IOService created." << endl;
+ SimpleCallback* checkin = auth_server->getCheckinProvider();
+ IOService& io_service = auth_server->getIOService();
+ DNSLookup* lookup = auth_server->getDNSLookupProvider();
+ DNSAnswer* answer = auth_server->getDNSAnswerProvider();
- cc_session = new Session(io_service->get_io_service());
+ DNSService dns_service(io_service, checkin, lookup, answer);
+ auth_server->setDNSService(dns_service);
+ cout << "[b10-auth] DNSServices created." << endl;
+
+ cc_session = new Session(io_service.get_io_service());
cout << "[b10-auth] Configuration session channel created." << endl;
config_session = new ModuleCCSession(specfile, *cc_session,
@@ -199,40 +163,53 @@ 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());
+ xfrin_session = new Session(io_service.get_io_service());
cout << "[b10-auth] Xfrin session channel created." << endl;
xfrin_session->establish(NULL);
xfrin_session_established = true;
cout << "[b10-auth] Xfrin session channel established." << endl;
- // XXX: with the current interface to asio_link we have to create
- // auth_server before io_service while Session needs io_service.
- // In a next step of refactoring we should make asio_link independent
- // from auth_server, and create io_service, auth_server, and
- // sessions in that order.
+ statistics_session = new Session(io_service.get_io_service());
+ cout << "[b10-auth] Statistics session channel created." << endl;
+ statistics_session->establish(NULL);
+ statistics_session_established = true;
+ cout << "[b10-auth] Statistics session channel established." << endl;
+
auth_server->setXfrinSession(xfrin_session);
+ auth_server->setStatisticsSession(statistics_session);
+
+ // Configure the server. configureAuthServer() is expected to install
+ // all initial configurations, but as a short term workaround we
+ // handle the traditional "database_file" setup by directly calling
+ // updateConfig().
auth_server->setConfigSession(config_session);
+ configureAuthServer(*auth_server, config_session->getFullConfig());
auth_server->updateConfig(ElementPtr());
+ if (uid != NULL) {
+ changeUser(uid);
+ }
+
cout << "[b10-auth] Server started." << endl;
- io_service->run();
+ io_service.run();
+
} catch (const std::exception& ex) {
- cerr << "[b10-auth] Initialization failed: " << ex.what() << endl;
+ cerr << "[b10-auth] Server failed: " << ex.what() << endl;
ret = 1;
}
+ if (statistics_session_established) {
+ statistics_session->disconnect();
+ }
+
if (xfrin_session_established) {
xfrin_session->disconnect();
}
+ delete statistics_session;
delete xfrin_session;
delete config_session;
delete cc_session;
- delete io_service;
delete auth_server;
return (ret);
diff --git a/src/bin/auth/query.cc b/src/bin/auth/query.cc
new file mode 100644
index 0000000..323f890
--- /dev/null
+++ b/src/bin/auth/query.cc
@@ -0,0 +1,253 @@
+// 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 <vector>
+#include <boost/foreach.hpp>
+
+#include <dns/message.h>
+#include <dns/rcode.h>
+#include <dns/rdataclass.h>
+
+#include <datasrc/memory_datasrc.h>
+
+#include <auth/query.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc;
+using namespace isc::dns::rdata;
+
+namespace isc {
+namespace auth {
+
+void
+Query::getAdditional(const Zone& zone, const RRset& rrset) const {
+ RdataIteratorPtr rdata_iterator(rrset.getRdataIterator());
+ for (; !rdata_iterator->isLast(); rdata_iterator->next()) {
+ const Rdata& rdata(rdata_iterator->getCurrent());
+ if (rrset.getType() == RRType::NS()) {
+ // Need to perform the search in the "GLUE OK" mode.
+ const generic::NS& ns = dynamic_cast<const generic::NS&>(rdata);
+ findAddrs(zone, ns.getNSName(), Zone::FIND_GLUE_OK);
+ } else if (rrset.getType() == RRType::MX()) {
+ const generic::MX& mx(dynamic_cast<const generic::MX&>(rdata));
+ findAddrs(zone, mx.getMXName());
+ }
+ }
+}
+
+void
+Query::findAddrs(const Zone& zone, const Name& qname,
+ const Zone::FindOptions options) const
+{
+ // Out of zone name
+ NameComparisonResult result = zone.getOrigin().compare(qname);
+ if ((result.getRelation() != NameComparisonResult::SUPERDOMAIN) &&
+ (result.getRelation() != NameComparisonResult::EQUAL))
+ return;
+
+ // Omit additional data which has already been provided in the answer
+ // section from the additional.
+ //
+ // All the address rrset with the owner name of qname have been inserted
+ // into ANSWER section.
+ if (qname_ == qname && qtype_ == RRType::ANY())
+ return;
+
+ // Find A rrset
+ if (qname_ != qname || qtype_ != RRType::A()) {
+ Zone::FindResult a_result = zone.find(qname, RRType::A(), NULL,
+ options);
+ if (a_result.code == Zone::SUCCESS) {
+ response_.addRRset(Message::SECTION_ADDITIONAL,
+ boost::const_pointer_cast<RRset>(a_result.rrset));
+ }
+ }
+
+ // Find AAAA rrset
+ if (qname_ != qname || qtype_ != RRType::AAAA()) {
+ Zone::FindResult aaaa_result =
+ zone.find(qname, RRType::AAAA(), NULL, options);
+ if (aaaa_result.code == Zone::SUCCESS) {
+ response_.addRRset(Message::SECTION_ADDITIONAL,
+ boost::const_pointer_cast<RRset>(aaaa_result.rrset));
+ }
+ }
+}
+
+void
+Query::putSOA(const Zone& zone) const {
+ Zone::FindResult soa_result(zone.find(zone.getOrigin(),
+ RRType::SOA()));
+ if (soa_result.code != Zone::SUCCESS) {
+ isc_throw(NoSOA, "There's no SOA record in zone " <<
+ zone.getOrigin().toText());
+ } else {
+ /*
+ * FIXME:
+ * The const-cast is wrong, but the Message interface seems
+ * to insist.
+ */
+ response_.addRRset(Message::SECTION_AUTHORITY,
+ boost::const_pointer_cast<RRset>(soa_result.rrset));
+ }
+}
+
+void
+Query::getAuthAdditional(const Zone& zone) const {
+ // Fill in authority and addtional sections.
+ Zone::FindResult ns_result = zone.find(zone.getOrigin(), RRType::NS());
+ // zone origin name should have NS records
+ if (ns_result.code != Zone::SUCCESS) {
+ isc_throw(NoApexNS, "There's no apex NS records in zone " <<
+ zone.getOrigin().toText());
+ } else {
+ response_.addRRset(Message::SECTION_AUTHORITY,
+ boost::const_pointer_cast<RRset>(ns_result.rrset));
+ // Handle additional for authority section
+ getAdditional(zone, *ns_result.rrset);
+ }
+}
+
+void
+Query::process() const {
+ bool keep_doing = true;
+ const bool qtype_is_any = (qtype_ == RRType::ANY());
+
+ response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
+ const MemoryDataSrc::FindResult result =
+ memory_datasrc_.findZone(qname_);
+
+ // If we have no matching authoritative zone for the query name, return
+ // REFUSED. In short, this is to be compatible with BIND 9, but the
+ // background discussion is not that simple. See the relevant topic
+ // at the BIND 10 developers's ML:
+ // https://lists.isc.org/mailman/htdig/bind10-dev/2010-December/001633.html
+ if (result.code != result::SUCCESS &&
+ result.code != result::PARTIALMATCH) {
+ response_.setRcode(Rcode::REFUSED());
+ return;
+ }
+
+ // Found a zone which is the nearest ancestor to QNAME, set the AA bit
+ response_.setHeaderFlag(Message::HEADERFLAG_AA);
+ response_.setRcode(Rcode::NOERROR());
+ while (keep_doing) {
+ keep_doing = false;
+ std::auto_ptr<RRsetList> target(qtype_is_any ? new RRsetList : NULL);
+ const Zone::FindResult db_result(result.zone->find(qname_, qtype_,
+ target.get()));
+
+ switch (db_result.code) {
+ case Zone::DNAME: {
+ // First, put the dname into the answer
+ response_.addRRset(Message::SECTION_ANSWER,
+ boost::const_pointer_cast<RRset>(db_result.rrset));
+ /*
+ * Empty DNAME should never get in, as it is impossible to
+ * create one in master file.
+ *
+ * FIXME: Other way to prevent this should be done
+ */
+ assert(db_result.rrset->getRdataCount() > 0);
+ // Get the data of DNAME
+ const rdata::generic::DNAME& dname(
+ dynamic_cast<const rdata::generic::DNAME&>(
+ db_result.rrset->getRdataIterator()->getCurrent()));
+ // The yet unmatched prefix dname
+ const Name prefix(qname_.split(0, qname_.getLabelCount() -
+ db_result.rrset->getName().getLabelCount()));
+ // If we put it together, will it be too long?
+ // (The prefix contains trailing ., which will be removed
+ if (prefix.getLength() - Name::ROOT_NAME().getLength() +
+ dname.getDname().getLength() > Name::MAX_WIRE) {
+ /*
+ * In case the synthesized name is too long, section 4.1
+ * of RFC 2672 mandates we return YXDOMAIN.
+ */
+ response_.setRcode(Rcode::YXDOMAIN());
+ return;
+ }
+ // The new CNAME we are creating (it will be unsigned even
+ // with DNSSEC, the DNAME is signed and it can be validated
+ // by that)
+ RRsetPtr cname(new RRset(qname_, db_result.rrset->getClass(),
+ RRType::CNAME(), db_result.rrset->getTTL()));
+ // Construct the new target by replacing the end
+ cname->addRdata(rdata::generic::CNAME(qname_.split(0,
+ qname_.getLabelCount() -
+ db_result.rrset->getName().getLabelCount()).
+ concatenate(dname.getDname())));
+ response_.addRRset(Message::SECTION_ANSWER, cname);
+ break;
+ }
+ case Zone::CNAME:
+ /*
+ * We don't do chaining yet. Therefore handling a CNAME is
+ * mostly the same as handling SUCCESS, but we didn't get
+ * what we expected. It means no exceptions in ANY or NS
+ * on the origin (though CNAME in origin is probably
+ * forbidden anyway).
+ *
+ * So, just put it there.
+ */
+ response_.addRRset(Message::SECTION_ANSWER,
+ boost::const_pointer_cast<RRset>(db_result.rrset));
+ break;
+ case Zone::SUCCESS:
+ if (qtype_is_any) {
+ // If quety type is ANY, insert all RRs under the domain
+ // 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,
+ boost::const_pointer_cast<RRset>(db_result.rrset));
+ // Handle additional for answer section
+ getAdditional(*result.zone, *db_result.rrset);
+ }
+ // If apex NS records haven't been provided in the answer
+ // section, insert apex NS records into the authority section
+ // and AAAA/A RRS of each of the NS RDATA into the additional
+ // section.
+ if (qname_ != result.zone->getOrigin() ||
+ db_result.code != Zone::SUCCESS ||
+ (qtype_ != RRType::NS() && !qtype_is_any))
+ {
+ getAuthAdditional(*result.zone);
+ }
+ break;
+ case Zone::DELEGATION:
+ response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
+ response_.addRRset(Message::SECTION_AUTHORITY,
+ boost::const_pointer_cast<RRset>(db_result.rrset));
+ getAdditional(*result.zone, *db_result.rrset);
+ break;
+ case Zone::NXDOMAIN:
+ // Just empty answer with SOA in authority section
+ response_.setRcode(Rcode::NXDOMAIN());
+ putSOA(*result.zone);
+ break;
+ case Zone::NXRRSET:
+ // Just empty answer with SOA in authority section
+ putSOA(*result.zone);
+ break;
+ }
+ }
+}
+
+}
+}
diff --git a/src/bin/auth/query.h b/src/bin/auth/query.h
new file mode 100644
index 0000000..e0c6323
--- /dev/null
+++ b/src/bin/auth/query.h
@@ -0,0 +1,222 @@
+/*
+ * 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 <exceptions/exceptions.h>
+#include <datasrc/zone.h>
+
+namespace isc {
+namespace dns {
+class Message;
+class Name;
+class RRType;
+class RRset;
+}
+
+namespace datasrc {
+class MemoryDataSrc;
+}
+
+namespace auth {
+
+/// The \c Query class represents a standard DNS query that encapsulates
+/// processing logic to answer the query.
+///
+/// Many of the design details for this class are still in flux.
+/// We'll revisit and update them as we add more functionality, for example:
+/// - memory_datasrc parameter of the constructor. It is a data source that
+/// uses in memory dedicated backend.
+/// - as a related point, we may have to pass the RR class of the query.
+/// in the initial implementation the RR class is an attribute of memory
+/// datasource and omitted. It's not clear if this assumption holds with
+/// generic data sources. On the other hand, it will help keep
+/// implementation simpler, and we might rather want to modify the design
+/// of the data source on this point.
+/// - return value of process(). rather than or in addition to setting the
+/// Rcode, we might use it as a return value of \c process().
+/// - we'll have to be able to specify whether DNSSEC is requested.
+/// It's an open question whether it should be in the constructor or via a
+/// separate attribute setter.
+/// - likewise, we'll eventually need to do per zone access control, for which
+/// we need querier's information such as its IP address.
+/// - memory_datasrc and response may better be parameters to process() instead
+/// of the constructor.
+///
+/// <b>Note:</b> The class name is intentionally the same as the one used in
+/// the datasrc library. This is because the plan is to eventually merge
+/// the two classes. We could give it a different name such as "AuthQuery"
+/// to avoid possible ambiguity, but it may sound redundant in that it's
+/// obvious that this class is for authoritative queries.
+/// Since the interfaces are very different for now and it's less
+/// likely to misuse one of the classes instead of the other
+/// accidentally, and since it's considered a temporary development state,
+/// we keep this name at the moment.
+class Query {
+private:
+
+ /// \brief Adds a SOA.
+ ///
+ /// Adds a SOA of the zone into the authority zone of response_.
+ /// Can throw NoSOA.
+ ///
+ void putSOA(const isc::datasrc::Zone& zone) const;
+
+ /// \brief Look up additional data (i.e., address records for the names
+ /// included in NS or MX records).
+ ///
+ /// Note: Any additional data which has already been provided in the
+ /// answer section (i.e., if the original query happend to be for the
+ /// address of the DNS server), it should be omitted from the additional.
+ ///
+ /// This method may throw a exception because its underlying methods may
+ /// throw exceptions.
+ ///
+ /// \param zone The Zone wherein the additional data to the query is bo be
+ /// found.
+ /// \param rrset The RRset (i.e., NS or MX rrset) which require additional
+ /// processing.
+ void getAdditional(const isc::datasrc::Zone& zone,
+ const isc::dns::RRset& rrset) const;
+
+ /// \brief Find address records for a specified name.
+ ///
+ /// Search the specified zone for AAAA/A RRs of each of the NS/MX RDATA
+ /// (domain name), and insert the found ones into the additional section
+ /// if address records are available. By default the search will stop
+ /// once it encounters a zone cut.
+ ///
+ /// Note: we need to perform the search in the "GLUE OK" mode for NS RDATA,
+ /// which means that we should include A/AAAA RRs under a zone cut.
+ /// The glue records must exactly match the name in the NS RDATA, without
+ /// CNAME or wildcard processing.
+ ///
+ /// \param zone The \c Zone wherein the address records is to be found.
+ /// \param qname The name in rrset RDATA.
+ /// \param options The search options.
+ void findAddrs(const isc::datasrc::Zone& zone,
+ const isc::dns::Name& qname,
+ const isc::datasrc::Zone::FindOptions options
+ = isc::datasrc::Zone::FIND_DEFAULT) const;
+
+ /// \brief Look up \c Zone's NS and address records for the NS RDATA
+ /// (domain name) for authoritative answer.
+ ///
+ /// On returning an authoritative answer, insert the \c Zone's NS into the
+ /// authority section and AAAA/A RRs of each of the NS RDATA into the
+ /// additional section.
+ ///
+ /// <b>Notes to developer:</b>
+ ///
+ /// We should omit address records which has already been provided in the
+ /// answer section from the additional.
+ ///
+ /// For now, in order to optimize the additional section processing, we
+ /// include AAAA/A RRs under a zone cut in additional section. (BIND 9
+ /// excludes under-cut RRs; NSD include them.)
+ ///
+ /// \param zone The \c Zone wherein the additional data to the query is to
+ /// be found.
+ void getAuthAdditional(const isc::datasrc::Zone& zone) const;
+
+public:
+ /// Constructor from query parameters.
+ ///
+ /// This constructor never throws an exception.
+ ///
+ /// \param memory_datasrc The memory datasource wherein the answer to the query is
+ /// to be found.
+ /// \param qname The query name
+ /// \param qtype The RR type of the query
+ /// \param response The response message to store the answer to the query.
+ Query(const isc::datasrc::MemoryDataSrc& memory_datasrc,
+ const isc::dns::Name& qname, const isc::dns::RRType& qtype,
+ isc::dns::Message& response) :
+ memory_datasrc_(memory_datasrc), qname_(qname), qtype_(qtype),
+ response_(response)
+ {}
+
+ /// Process the query.
+ ///
+ /// This method first identifies the zone that best matches the query
+ /// name (and in some cases RR type when the search is dependent on the
+ /// type) and then searches the zone for an entry that best matches the
+ /// query name.
+ /// It then updates the response message accordingly; for example, a
+ /// successful search would result in adding a corresponding RRset to
+ /// the answer section of the response.
+ ///
+ /// If no matching zone is found in the memory datasource, the RCODE of
+ /// SERVFAIL will be set in the response.
+ /// <b>Note:</b> this is different from the error code that BIND 9 returns
+ /// by default when it's configured as an authoritative-only server (and
+ /// from the behavior of the BIND 10 datasrc library, which was implemented
+ /// to be compatible with BIND 9).
+ /// The difference comes from the fact that BIND 9 returns REFUSED as a
+ /// result of access control check on the use of its cache.
+ /// Since BIND 10's authoritative server doesn't have the notion of cache
+ /// by design, it doesn't make sense to return REFUSED. On the other hand,
+ /// providing compatible behavior may have its own benefit, so this point
+ /// should be revisited later.
+ ///
+ /// This might throw BadZone or any of its specific subclasses, but that
+ /// shouldn't happen in real-life (as BadZone means wrong data, it should
+ /// have been rejected upon loading).
+ void process() const;
+
+ /// \short Bad zone data encountered.
+ ///
+ /// This is thrown when process encounteres misconfigured zone in a way
+ /// it can't continue. This throws, not sets the Rcode, because such
+ /// misconfigured zone should not be present in the data source and
+ /// should have been rejected sooner.
+ struct BadZone : public isc::Exception {
+ BadZone(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ {}
+ };
+
+ /// \short Zone is missing its SOA record.
+ ///
+ /// We tried to add a SOA into the authoritative section, but the zone
+ /// does not contain one.
+ struct NoSOA : public BadZone {
+ NoSOA(const char* file, size_t line, const char* what) :
+ BadZone(file, line, what)
+ {}
+ };
+
+ /// \short Zone is missing its apex NS records.
+ ///
+ /// We tried to add apex NS records into the authority section, but the
+ /// zone does not contain any.
+ struct NoApexNS: public BadZone {
+ NoApexNS(const char* file, size_t line, const char* what) :
+ BadZone(file, line, what)
+ {}
+ };
+
+private:
+ const isc::datasrc::MemoryDataSrc& memory_datasrc_;
+ const isc::dns::Name& qname_;
+ const isc::dns::RRType& qtype_;
+ isc::dns::Message& response_;
+};
+
+}
+}
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/bin/auth/statistics.cc b/src/bin/auth/statistics.cc
new file mode 100644
index 0000000..e68793c
--- /dev/null
+++ b/src/bin/auth/statistics.cc
@@ -0,0 +1,160 @@
+// 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 <auth/statistics.h>
+
+#include <cc/data.h>
+#include <cc/session.h>
+
+#include <sstream>
+#include <iostream>
+
+// TODO: We need a namespace ("auth_server"?) to hold
+// AuthSrv and AuthCounters.
+
+class AuthCountersImpl {
+private:
+ // prohibit copy
+ AuthCountersImpl(const AuthCountersImpl& source);
+ AuthCountersImpl& operator=(const AuthCountersImpl& source);
+public:
+ // References verbose_mode flag in AuthSrvImpl
+ // TODO: Fix this short term workaround for logging
+ // after we have logging framework
+ AuthCountersImpl(const bool& verbose_mode);
+ ~AuthCountersImpl();
+ void inc(const AuthCounters::CounterType type);
+ bool submitStatistics() const;
+ void setStatisticsSession(isc::cc::AbstractSession* statistics_session);
+ // Currently for testing purpose only
+ uint64_t getCounter(const AuthCounters::CounterType type) const;
+private:
+ std::vector<uint64_t> counters_;
+ isc::cc::AbstractSession* statistics_session_;
+ const bool& verbose_mode_;
+};
+
+AuthCountersImpl::AuthCountersImpl(const bool& verbose_mode) :
+ // initialize counter
+ // size: AuthCounters::COUNTER_TYPES, initial value: 0
+ counters_(AuthCounters::COUNTER_TYPES, 0),
+ statistics_session_(NULL),
+ verbose_mode_(verbose_mode)
+{}
+
+AuthCountersImpl::~AuthCountersImpl()
+{}
+
+void
+AuthCountersImpl::inc(const AuthCounters::CounterType type) {
+ ++counters_.at(type);
+}
+
+bool
+AuthCountersImpl::submitStatistics() const {
+ if (statistics_session_ == NULL) {
+ if (verbose_mode_) {
+ std::cerr << "[b10-auth] "
+ << "session interface for statistics"
+ << " is not available" << std::endl;
+ }
+ return (false);
+ }
+ std::stringstream statistics_string;
+ statistics_string << "{\"command\": [\"set\","
+ << "{ \"stats_data\": "
+ << "{ \"auth.queries.udp\": "
+ << counters_.at(AuthCounters::COUNTER_UDP_QUERY)
+ << ", \"auth.queries.tcp\": "
+ << counters_.at(AuthCounters::COUNTER_TCP_QUERY)
+ << " }"
+ << "}"
+ << "]}";
+ isc::data::ConstElementPtr statistics_element =
+ isc::data::Element::fromJSON(statistics_string);
+ try {
+ // group_{send,recv}msg() can throw an exception when encountering
+ // an error, and group_recvmsg() will throw an exception on timeout.
+ // We don't want to kill the main server just due to this, so we
+ // handle them here.
+ const int seq =
+ statistics_session_->group_sendmsg(statistics_element, "Stats");
+ isc::data::ConstElementPtr env, answer;
+ if (verbose_mode_) {
+ std::cerr << "[b10-auth] "
+ << "send statistics data" << std::endl;
+ }
+ // TODO: parse and check response from statistics module
+ // currently it just returns empty message
+ statistics_session_->group_recvmsg(env, answer, false, seq);
+ } catch (const isc::cc::SessionError& ex) {
+ if (verbose_mode_) {
+ std::cerr << "[b10-auth] "
+ << "communication error in sending statistics data: "
+ << ex.what() << std::endl;
+ }
+ return (false);
+ } catch (const isc::cc::SessionTimeout& ex) {
+ if (verbose_mode_) {
+ std::cerr << "[b10-auth] "
+ << "timeout happened while sending statistics data: "
+ << ex.what() << std::endl;
+ }
+ return (false);
+ }
+ return (true);
+}
+
+void
+AuthCountersImpl::setStatisticsSession
+ (isc::cc::AbstractSession* statistics_session)
+{
+ statistics_session_ = statistics_session;
+}
+
+// Currently for testing purpose only
+uint64_t
+AuthCountersImpl::getCounter(const AuthCounters::CounterType type) const {
+ return (counters_.at(type));
+}
+
+AuthCounters::AuthCounters(const bool& verbose_mode) :
+ impl_(new AuthCountersImpl(verbose_mode))
+{}
+
+AuthCounters::~AuthCounters() {
+ delete impl_;
+}
+
+void
+AuthCounters::inc(const AuthCounters::CounterType type) {
+ impl_->inc(type);
+}
+
+bool
+AuthCounters::submitStatistics() const {
+ return (impl_->submitStatistics());
+}
+
+void
+AuthCounters::setStatisticsSession
+ (isc::cc::AbstractSession* statistics_session)
+{
+ impl_->setStatisticsSession(statistics_session);
+}
+
+uint64_t
+AuthCounters::getCounter(const AuthCounters::CounterType type) const {
+ return (impl_->getCounter(type));
+}
diff --git a/src/bin/auth/statistics.h b/src/bin/auth/statistics.h
new file mode 100644
index 0000000..9e5240e
--- /dev/null
+++ b/src/bin/auth/statistics.h
@@ -0,0 +1,145 @@
+// 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 __STATISTICS_H
+#define __STATISTICS_H 1
+
+#include <cc/session.h>
+#include <stdint.h>
+
+class AuthCountersImpl;
+
+/// \brief Set of query counters.
+///
+/// \c AuthCounters is set of query counters class. It holds query counters
+/// and provides an interface to increment the counter of specified type
+/// (e.g. UDP query, TCP query).
+///
+/// This class also provides a function to send statistics information to
+/// statistics module.
+///
+/// This class is designed to be a part of \c AuthSrv.
+/// Call \c setStatisticsSession() to set a session to communicate with
+/// statistics module like Xfrin session.
+/// Call \c inc() to increment a counter for specific type of query in
+/// the query processing function. use \c enum \c CounterType to specify
+/// the type of query.
+/// Call \c submitStatistics() to submit statistics information to statistics
+/// module with statistics_session, periodically or at a time the command
+/// \c sendstats is received.
+///
+/// We may eventually want to change the structure to hold values that are
+/// not counters (such as concurrent TCP connections), or seperate generic
+/// part to src/lib to share with the other modules.
+///
+/// This class uses pimpl idiom and hides detailed implementation.
+/// This class is constructed on startup of the server, so
+/// construction overhead of this approach should be acceptable.
+///
+/// \todo Hold counters for each query types (Notify, Axfr, Ixfr, Normal)
+/// \todo Consider overhead of \c AuthCounters::inc()
+class AuthCounters {
+private:
+ AuthCountersImpl* impl_;
+public:
+ // Enum for the type of counter
+ enum CounterType {
+ COUNTER_UDP_QUERY = 0, ///< COUNTER_UDP_QUERY: counter for UDP queries
+ COUNTER_TCP_QUERY = 1, ///< COUNTER_TCP_QUERY: counter for TCP queries
+ COUNTER_TYPES = 2 ///< The number of defined counters
+ };
+ /// The constructor.
+ ///
+ /// \param verbose_mode reference to verbose_mode_ of AuthSrvImpl
+ ///
+ /// This constructor is mostly exception free. But it may still throw
+ /// a standard exception if memory allocation fails inside the method.
+ ///
+ /// \todo Fix this short term workaround for logging
+ /// after we have logging framework.
+ ///
+ AuthCounters(const bool& verbose_mode);
+ /// The destructor.
+ ///
+ /// This method never throws an exception.
+ ///
+ ~AuthCounters();
+
+ /// \brief Increment the counter specified by the parameter.
+ ///
+ /// \param type Type of a counter to increment.
+ ///
+ /// \throw std::out_of_range \a type is unknown.
+ ///
+ /// usage: counter.inc(CounterType::COUNTER_UDP_QUERY);
+ ///
+ void inc(const CounterType type);
+
+ /// \brief Submit statistics counters to statistics module.
+ ///
+ /// This method is desinged to be called periodically
+ /// with \c asio_link::StatisticsSendTimer, or arbitrary
+ /// by the command 'sendstats'.
+ ///
+ /// Note: Set the session to communicate with statistics module
+ /// by \c setStatisticsSession() before calling \c submitStatistics().
+ ///
+ /// This method is mostly exception free (error conditions are
+ /// represented via the return value). But it may still throw
+ /// a standard exception if memory allocation fails inside the method.
+ ///
+ /// \return true on success, false on error.
+ ///
+ /// \todo Do not block message handling in auth_srv while submitting
+ /// statistics data.
+ ///
+ bool submitStatistics() const;
+
+ /// \brief Set the session to communicate with Statistics
+ /// module.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// Note: this interface is tentative. We'll revisit the ASIO and session
+ /// frameworks, at which point the session will probably be passed on
+ /// construction of the server.
+ ///
+ /// Ownership isn't transferred: the caller is responsible for keeping
+ /// this object to be valid while the server object is working and for
+ /// disconnecting the session and destroying the object when the server
+ /// is shutdown.
+ ///
+ /// \param statistics_session A pointer to the session
+ ///
+ void setStatisticsSession(isc::cc::AbstractSession* statistics_session);
+
+ /// \brief Get a value of a counter in the AuthCounters.
+ ///
+ /// This function returns a value of the counter specified by \a type.
+ /// This method never throws an exception.
+ ///
+ /// Note: Currently this function is for testing purpose only.
+ ///
+ /// \param type Type of a counter to get the value of
+ ///
+ /// \return the value of the counter specified by \a type.
+ ///
+ uint64_t getCounter(const AuthCounters::CounterType type) const;
+};
+
+#endif // __STATISTICS_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index e73b541..7d489a1 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -1,11 +1,10 @@
-SUBDIRS = testdata .
-
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
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)
-AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
-AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/auth/tests/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -21,22 +20,33 @@ 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 += ../command.h ../command.cc
+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 += query_unittest.cc
run_unittests_SOURCES += change_user_unittest.cc
-run_unittests_SOURCES += asio_link_unittest.cc
+run_unittests_SOURCES += statistics_unittest.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 += $(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/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
-run_unittests_LDADD += $(top_builddir)/src/bin/auth/libasio_link.a
run_unittests_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
+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/nsas/libnsas.la
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/auth/tests/asio_link_unittest.cc b/src/bin/auth/tests/asio_link_unittest.cc
deleted file mode 100644
index d1cf122..0000000
--- a/src/bin/auth/tests/asio_link_unittest.cc
+++ /dev/null
@@ -1,357 +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.
-
-// $Id$
-
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netdb.h>
-
-#include <stdint.h>
-
-#include <functional>
-#include <string>
-#include <vector>
-
-#include <gtest/gtest.h>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/tests/unittest_util.h>
-
-#include <auth/asio_link.h>
-
-using isc::UnitTestUtil;
-using namespace std;
-using namespace asio_link;
-
-namespace {
-const char* const TEST_PORT = "53535";
-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};
-
-TEST(IOAddressTest, fromText) {
- IOAddress io_address_v4("192.0.2.1");
- EXPECT_EQ("192.0.2.1", io_address_v4.toText());
-
- IOAddress io_address_v6("2001:db8::1234");
- EXPECT_EQ("2001:db8::1234", io_address_v6.toText());
-
- // bogus IPv4 address-like input
- EXPECT_THROW(IOAddress("192.0.2.2.1"), IOError);
-
- // bogus IPv6 address-like input
- EXPECT_THROW(IOAddress("2001:db8:::1234"), IOError);
-}
-
-TEST(IOEndpointTest, create) {
- const IOEndpoint* ep;
- ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5300);
- EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
- delete ep;
-
- ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5300);
- EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
- delete ep;
-
- ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5300);
- EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
- delete ep;
-
- ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5300);
- EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
- delete ep;
-
- EXPECT_THROW(IOEndpoint::create(IPPROTO_IP, IOAddress("192.0.2.1"),
- 5300)->getAddress().toText(),
- IOError);
-}
-
-TEST(IOSocketTest, dummySockets) {
- EXPECT_EQ(IPPROTO_UDP, IOSocket::getDummyUDPSocket().getProtocol());
- EXPECT_EQ(IPPROTO_TCP, IOSocket::getDummyTCPSocket().getProtocol());
- EXPECT_EQ(-1, IOSocket::getDummyUDPSocket().getNative());
- EXPECT_EQ(-1, IOSocket::getDummyTCPSocket().getNative());
-}
-
-TEST(IOServiceTest, badPort) {
- EXPECT_THROW(IOService(NULL, *"65536", true, false), IOError);
- EXPECT_THROW(IOService(NULL, *"5300.0", true, false), IOError);
- EXPECT_THROW(IOService(NULL, *"-1", true, false), IOError);
- EXPECT_THROW(IOService(NULL, *"domain", true, false), IOError);
-}
-
-TEST(IOServiceTest, badAddress) {
- EXPECT_THROW(IOService(NULL, *TEST_PORT, *"192.0.2.1.1"),
- IOError);
- EXPECT_THROW(IOService(NULL, *TEST_PORT, *"2001:db8:::1"),
- IOError);
- EXPECT_THROW(IOService(NULL, *TEST_PORT, *"localhost"),
- IOError);
-}
-
-TEST(IOServiceTest, unavailableAddress) {
- // These addresses should generally be unavailable as a valid local
- // address, although there's no guarantee in theory.
- EXPECT_THROW(IOService(NULL, *TEST_PORT, *"255.255.0.0"), IOError);
-
- // Some OSes would simply reject binding attempt for an AF_INET6 socket
- // to an IPv4-mapped IPv6 address. Even if those that allow it, since
- // the corresponding IPv4 address is the same as the one used in the
- // AF_INET socket case above, it should at least show the same result
- // as the previous one.
- EXPECT_THROW(IOService(NULL, *TEST_PORT, *"::ffff:255.255.0.0"), IOError);
-}
-
-TEST(IOServiceTest, duplicateBind) {
- // In each sub test case, second attempt should fail due to duplicate bind
-
- // IPv6, "any" address
- IOService* io_service = new IOService(NULL, *TEST_PORT, false, true);
- EXPECT_THROW(IOService(NULL, *TEST_PORT, false, true), IOError);
- delete io_service;
-
- // IPv6, specific address
- io_service = new IOService(NULL, *TEST_PORT, *TEST_IPV6_ADDR);
- EXPECT_THROW(IOService(NULL, *TEST_PORT, *TEST_IPV6_ADDR), IOError);
- delete io_service;
-
- // IPv4, "any" address
- io_service = new IOService(NULL, *TEST_PORT, true, false);
- EXPECT_THROW(IOService(NULL, *TEST_PORT, true, false), IOError);
- delete io_service;
-
- // IPv4, specific address
- io_service = new IOService(NULL, *TEST_PORT, *TEST_IPV4_ADDR);
- EXPECT_THROW(IOService(NULL, *TEST_PORT, *TEST_IPV4_ADDR), IOError);
- delete io_service;
-}
-
-struct addrinfo*
-resolveAddress(const int family, const int sock_type, const int protocol) {
- const char* const addr = (family == AF_INET6) ?
- TEST_IPV6_ADDR : TEST_IPV4_ADDR;
-
- struct addrinfo hints;
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = family;
- hints.ai_socktype = sock_type;
- hints.ai_protocol = protocol;
-
- struct addrinfo* res;
- const int error = getaddrinfo(addr, TEST_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
-// receives 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 setIOService() method.
-// Note: the set of tests in ASIOLinkTest use actual network services and may
-// involve undesirable side effect such as blocking.
-class ASIOLinkTest : public ::testing::Test {
-protected:
- ASIOLinkTest();
- ~ASIOLinkTest() {
- if (res_ != NULL) {
- freeaddrinfo(res_);
- }
- if (sock_ != -1) {
- close(sock_);
- }
- delete io_service_;
- }
- void sendUDP(const int family) {
- res_ = resolveAddress(family, SOCK_DGRAM, IPPROTO_UDP);
-
- 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();
- }
- void sendTCP(const int family) {
- res_ = resolveAddress(family, SOCK_STREAM, IPPROTO_TCP);
-
- 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 sendto result: " << cc);
- }
- io_service_->run();
- }
- void setIOService(const char& address) {
- delete io_service_;
- io_service_ = NULL;
- io_service_ = new IOService(NULL, *TEST_PORT, address);
- io_service_->setCallBack(ASIOCallBack(this));
- }
- void setIOService(const bool use_ipv4, const bool use_ipv6) {
- delete io_service_;
- io_service_ = NULL;
- io_service_ = new IOService(NULL, *TEST_PORT, use_ipv4, use_ipv6);
- io_service_->setCallBack(ASIOCallBack(this));
- }
- 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);
- }
-private:
- class ASIOCallBack : public std::unary_function<IOMessage, void> {
- public:
- ASIOCallBack(ASIOLinkTest* test_obj) : test_obj_(test_obj) {}
- void operator()(const IOMessage& io_message) const {
- test_obj_->callBack(io_message);
- }
- private:
- ASIOLinkTest* 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:
- IOService* io_service_;
- int callback_protocol_;
- int callback_native_;
- string callback_address_;
- vector<uint8_t> callback_data_;
- int sock_;
-private:
- struct addrinfo* res_;
-};
-
-ASIOLinkTest::ASIOLinkTest() :
- io_service_(NULL), sock_(-1), res_(NULL)
-{
- setIOService(true, true);
-}
-
-TEST_F(ASIOLinkTest, v6UDPSend) {
- doTest(AF_INET6, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v6TCPSend) {
- doTest(AF_INET6, IPPROTO_TCP);
-}
-
-TEST_F(ASIOLinkTest, v4UDPSend) {
- doTest(AF_INET, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v4TCPSend) {
- doTest(AF_INET, IPPROTO_TCP);
-}
-
-TEST_F(ASIOLinkTest, 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.
-
- setIOService(*TEST_IPV6_ADDR);
- doTest(AF_INET6, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v6TCPSendSpecific) {
- setIOService(*TEST_IPV6_ADDR);
- doTest(AF_INET6, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(ASIOLinkTest, v4UDPSendSpecific) {
- setIOService(*TEST_IPV4_ADDR);
- doTest(AF_INET, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v4TCPSendSpecific) {
- setIOService(*TEST_IPV4_ADDR);
- doTest(AF_INET, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-TEST_F(ASIOLinkTest, 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.
- setIOService(false, true);
- EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(ASIOLinkTest, v4TCPOnly) {
- setIOService(true, false);
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-}
diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc
index 236efb7..379342e 100644
--- a/src/bin/auth/tests/auth_srv_unittest.cc
+++ b/src/bin/auth/tests/auth_srv_unittest.cc
@@ -12,38 +12,42 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config.h>
+#include <vector>
+
#include <gtest/gtest.h>
-#include <dns/buffer.h>
-#include <dns/name.h>
#include <dns/message.h>
#include <dns/messagerenderer.h>
-#include <dns/opcode.h>
-#include <dns/rcode.h>
+#include <dns/name.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
-#include <cc/data.h>
-#include <cc/session.h>
-
-#include <xfr/xfrout_client.h>
+#include <server_common/portconfig.h>
+#include <datasrc/memory_datasrc.h>
#include <auth/auth_srv.h>
-#include <auth/asio_link.h>
+#include <auth/common.h>
+#include <auth/statistics.h>
#include <dns/tests/unittest_util.h>
+#include <testutils/dnsmessage_test.h>
+#include <testutils/srv_test.h>
+#include <testutils/portconfig.h>
-using isc::UnitTestUtil;
using namespace std;
using namespace isc::cc;
using namespace isc::dns;
+using namespace isc::dns::rdata;
using namespace isc::data;
using namespace isc::xfr;
-using namespace asio_link;
+using namespace asiolink;
+using namespace isc::testutils;
+using namespace isc::server_common::portconfig;
+using isc::UnitTestUtil;
namespace {
const char* const CONFIG_TESTDB =
@@ -52,307 +56,131 @@ const char* const CONFIG_TESTDB =
// the sqlite3 test).
const char* const BADCONFIG_TESTDB =
"{ \"database_file\": \"" TEST_DATA_DIR "/nodir/notexist\"}";
-const char* const DEFAULT_REMOTE_ADDRESS = "192.0.2.1";
-
-class AuthSrvTest : public ::testing::Test {
-private:
- class MockXfroutClient : public AbstractXfroutClient {
- public:
- MockXfroutClient() :
- is_connected_(false), connect_ok_(true), send_ok_(true),
- disconnect_ok_(true)
- {}
- virtual void connect();
- virtual void disconnect();
- virtual int sendXfroutRequestInfo(int tcp_sock, const void* msg_data,
- uint16_t msg_len);
- bool isConnected() const { return (is_connected_); }
- void disableConnect() { connect_ok_ = false; }
- void disableDisconnect() { disconnect_ok_ = false; }
- void enableDisconnect() { disconnect_ok_ = true; }
- void disableSend() { send_ok_ = false; }
- private:
- bool is_connected_;
- bool connect_ok_;
- bool send_ok_;
- bool disconnect_ok_;
- };
-
- class MockSession : public AbstractSession {
- public:
- MockSession() :
- // by default we return a simple "success" message.
- msg_(Element::fromJSON("{\"result\": [0, \"SUCCESS\"]}")),
- send_ok_(true), receive_ok_(true)
- {}
- virtual void establish(const char* socket_file);
- virtual void disconnect();
- virtual int group_sendmsg(ConstElementPtr msg, string group,
- string instance, string to);
- virtual bool group_recvmsg(ConstElementPtr& envelope,
- ConstElementPtr& msg,
- bool nonblock, int seq);
- virtual void subscribe(string group, string instance);
- virtual void unsubscribe(string group, string instance);
- virtual void startRead(boost::function<void()> read_callback);
- virtual int reply(ConstElementPtr envelope, ConstElementPtr newmsg);
- virtual bool hasQueuedMsgs() const;
- virtual void setTimeout(size_t timeout UNUSED_PARAM) {};
- virtual size_t getTimeout() const { return 0; };
-
- void setMessage(ConstElementPtr msg) { msg_ = msg; }
- void disableSend() { send_ok_ = false; }
- void disableReceive() { receive_ok_ = false; }
-
- ConstElementPtr sent_msg;
- string msg_destination;
- private:
- ConstElementPtr msg_;
- bool send_ok_;
- bool receive_ok_;
- };
+class AuthSrvTest : public SrvTestBase {
protected:
- AuthSrvTest() : server(true, xfrout),
- request_message(Message::RENDER),
- parse_message(Message::PARSE), default_qid(0x1035),
- opcode(Opcode::QUERY()), qname("www.example.com"),
- qclass(RRClass::IN()), qtype(RRType::A()),
- io_message(NULL), endpoint(NULL), request_obuffer(0),
- request_renderer(request_obuffer),
- response_obuffer(0), response_renderer(response_obuffer)
+ AuthSrvTest() :
+ dnss_(ios_, NULL, NULL, NULL),
+ server(true, xfrout),
+ rrclass(RRClass::IN())
{
+ server.setDNSService(dnss_);
server.setXfrinSession(¬ify_session);
+ server.setStatisticsSession(&statistics_session);
}
- ~AuthSrvTest() {
- delete io_message;
- delete endpoint;
+ virtual void processMessage() {
+ server.processMessage(*io_message, parse_message, response_obuffer,
+ &dnsserv);
}
- MockSession notify_session;
+ IOService ios_;
+ DNSService dnss_;
+ MockSession statistics_session;
MockXfroutClient xfrout;
AuthSrv server;
- Message request_message;
- Message parse_message;
- const qid_t default_qid;
- const Opcode opcode;
- const Name qname;
- const RRClass qclass;
- const RRType qtype;
- IOMessage* io_message;
- const IOEndpoint* endpoint;
- OutputBuffer request_obuffer;
- MessageRenderer request_renderer;
- OutputBuffer response_obuffer;
- MessageRenderer response_renderer;
- vector<uint8_t> data;
-
- void createDataFromFile(const char* const datafile, int protocol);
- void createRequestMessage(const Opcode& opcode, const Name& request_name,
- const RRClass& rrclass, const RRType& rrtype);
- void createRequestPacket(const Opcode& opcode, const Name& request_name,
- const RRClass& rrclass, const RRType& rrtype,
- int protocol);
- void createRequestPacket(int protocol);
+ const RRClass rrclass;
+ vector<uint8_t> response_data;
};
+// A helper function that builds a response to version.bind/TXT/CH that
+// should be identical to the response from our builtin (static) data source
+// by default. The resulting wire-format data will be stored in 'data'.
void
-AuthSrvTest::MockSession::establish(const char* socket_file UNUSED_PARAM) {}
-
-void
-AuthSrvTest::MockSession::disconnect() {}
-
-void
-AuthSrvTest::MockSession::subscribe(string group UNUSED_PARAM,
- string instance UNUSED_PARAM)
-{}
-
-void
-AuthSrvTest::MockSession::unsubscribe(string group UNUSED_PARAM,
- string instance UNUSED_PARAM)
-{}
-
-void
-AuthSrvTest::MockSession::startRead(
- boost::function<void()> read_callback UNUSED_PARAM)
-{}
-
-int
-AuthSrvTest::MockSession::reply(ConstElementPtr envelope UNUSED_PARAM,
- ConstElementPtr newmsg UNUSED_PARAM)
-{
- return (-1);
-}
-
-bool
-AuthSrvTest::MockSession::hasQueuedMsgs() const {
- return (false);
-}
-
-int
-AuthSrvTest::MockSession::group_sendmsg(ConstElementPtr msg, string group,
- string instance UNUSED_PARAM,
- string to UNUSED_PARAM)
-{
- if (!send_ok_) {
- isc_throw(XfroutError, "mock session send is disabled for test");
- }
-
- sent_msg = msg;
- msg_destination = group;
- return (0);
-}
-
-bool
-AuthSrvTest::MockSession::group_recvmsg(ConstElementPtr& envelope UNUSED_PARAM,
- ConstElementPtr& msg,
- bool nonblock UNUSED_PARAM,
- int seq UNUSED_PARAM)
-{
- if (!receive_ok_) {
- isc_throw(XfroutError, "mock session receive is disabled for test");
- }
-
- msg = msg_;
- return (true);
-}
-
-void
-AuthSrvTest::MockXfroutClient::connect() {
- if (!connect_ok_) {
- isc_throw(XfroutError, "xfrout connection disabled for test");
- }
- is_connected_ = true;
-}
-
-void
-AuthSrvTest::MockXfroutClient::disconnect() {
- if (!disconnect_ok_) {
- isc_throw(XfroutError,
- "closing xfrout connection is disabled for test");
- }
- is_connected_ = false;
-}
+createBuiltinVersionResponse(const qid_t qid, vector<uint8_t>& data) {
+ const Name version_name("version.bind");
+ Message message(Message::RENDER);
+
+ UnitTestUtil::createRequestMessage(message, Opcode::QUERY(),
+ qid, version_name,
+ RRClass::CH(), RRType::TXT());
+ message.setHeaderFlag(Message::HEADERFLAG_QR);
+ message.setHeaderFlag(Message::HEADERFLAG_AA);
+ RRsetPtr rrset_version = RRsetPtr(new RRset(version_name, RRClass::CH(),
+ RRType::TXT(), RRTTL(0)));
+ rrset_version->addRdata(generic::TXT(PACKAGE_STRING));
+ message.addRRset(Message::SECTION_ANSWER, rrset_version);
+
+ RRsetPtr rrset_version_ns = RRsetPtr(new RRset(version_name, RRClass::CH(),
+ RRType::NS(), RRTTL(0)));
+ rrset_version_ns->addRdata(generic::NS(version_name));
+ message.addRRset(Message::SECTION_AUTHORITY, rrset_version_ns);
+
+ OutputBuffer obuffer(0);
+ MessageRenderer renderer(obuffer);
+ message.toWire(renderer);
-int
-AuthSrvTest::MockXfroutClient::sendXfroutRequestInfo(
- const int tcp_sock UNUSED_PARAM,
- const void* msg_data UNUSED_PARAM,
- const uint16_t msg_len UNUSED_PARAM)
-{
- if (!send_ok_) {
- isc_throw(XfroutError, "xfrout connection send is disabled for test");
- }
- return (0);
-}
-
-
-// These are flags to indicate whether the corresponding flag bit of the
-// DNS header is to be set in the test cases. (Note that the flag values
-// is irrelevant to their wire-format values)
-const unsigned int QR_FLAG = 0x1;
-const unsigned int AA_FLAG = 0x2;
-const unsigned int TC_FLAG = 0x4;
-const unsigned int RD_FLAG = 0x8;
-const unsigned int RA_FLAG = 0x10;
-const unsigned int AD_FLAG = 0x20;
-const unsigned int CD_FLAG = 0x40;
-
-void
-AuthSrvTest::createDataFromFile(const char* const datafile,
- const int protocol = IPPROTO_UDP)
-{
- delete io_message;
data.clear();
-
- delete endpoint;
- endpoint = IOEndpoint::create(protocol,
- IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
- UnitTestUtil::readWireData(datafile, data);
- io_message = new IOMessage(&data[0], data.size(),
- protocol == IPPROTO_UDP ?
- IOSocket::getDummyUDPSocket() :
- IOSocket::getDummyTCPSocket(), *endpoint);
-}
-
-void
-AuthSrvTest::createRequestMessage(const Opcode& opcode,
- const Name& request_name,
- const RRClass& rrclass,
- const RRType& rrtype)
-{
- request_message.clear(Message::RENDER);
- request_message.setOpcode(opcode);
- request_message.setRcode(Rcode::NOERROR());
- request_message.setQid(default_qid);
- request_message.addQuestion(Question(request_name, rrclass, rrtype));
-}
-
-void
-AuthSrvTest::createRequestPacket(const Opcode& opcode,
- const Name& request_name,
- const RRClass& rrclass, const RRType& rrtype,
- const int protocol = IPPROTO_UDP)
-{
- createRequestMessage(opcode, request_name, rrclass, rrtype);
- createRequestPacket(protocol);
-}
-
-void
-AuthSrvTest::createRequestPacket(const int protocol = IPPROTO_UDP) {
- request_message.toWire(request_renderer);
-
- delete io_message;
- endpoint = IOEndpoint::create(protocol,
- IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
- io_message = new IOMessage(request_renderer.getData(),
- request_renderer.getLength(),
- protocol == IPPROTO_UDP ?
- IOSocket::getDummyUDPSocket() :
- IOSocket::getDummyTCPSocket(), *endpoint);
-}
-
-void
-headerCheck(const Message& message, const qid_t qid, const Rcode& rcode,
- const uint16_t opcodeval, const unsigned int flags,
- const unsigned int qdcount,
- const unsigned int ancount, const unsigned int nscount,
- const unsigned int arcount)
-{
- EXPECT_EQ(qid, message.getQid());
- EXPECT_EQ(rcode, message.getRcode());
- EXPECT_EQ(opcodeval, message.getOpcode().getCode());
- EXPECT_EQ((flags & QR_FLAG) != 0, message.getHeaderFlag(MessageFlag::QR()));
- EXPECT_EQ((flags & AA_FLAG) != 0, message.getHeaderFlag(MessageFlag::AA()));
- EXPECT_EQ((flags & TC_FLAG) != 0, message.getHeaderFlag(MessageFlag::TC()));
- EXPECT_EQ((flags & RA_FLAG) != 0, message.getHeaderFlag(MessageFlag::RA()));
- EXPECT_EQ((flags & RD_FLAG) != 0, message.getHeaderFlag(MessageFlag::RD()));
- EXPECT_EQ((flags & AD_FLAG) != 0, message.getHeaderFlag(MessageFlag::AD()));
- EXPECT_EQ((flags & CD_FLAG) != 0, message.getHeaderFlag(MessageFlag::CD()));
-
- EXPECT_EQ(qdcount, message.getRRCount(Section::QUESTION()));
- EXPECT_EQ(ancount, message.getRRCount(Section::ANSWER()));
- EXPECT_EQ(nscount, message.getRRCount(Section::AUTHORITY()));
- EXPECT_EQ(arcount, message.getRRCount(Section::ADDITIONAL()));
+ data.assign(static_cast<const uint8_t*>(renderer.getData()),
+ static_cast<const uint8_t*>(renderer.getData()) +
+ renderer.getLength());
+}
+
+// In the following tests we confirm the response data is rendered in
+// wire format in the expected way.
+
+// The most primitive check: checking the result of the processMessage()
+// method
+TEST_F(AuthSrvTest, builtInQuery) {
+ UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+ default_qid, Name("version.bind"),
+ RRClass::CH(), RRType::TXT());
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, parse_message, response_obuffer,
+ &dnsserv);
+ createBuiltinVersionResponse(default_qid, response_data);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ response_obuffer->getData(),
+ response_obuffer->getLength(),
+ &response_data[0], response_data.size());
+}
+
+// Same test emulating the UDPServer class behavior (defined in libasiolink).
+// This is not a good test in that it assumes internal implementation details
+// of UDPServer, but we've encountered a regression due to the introduction
+// of that class, so we add a test for that case to prevent such a regression
+// in future.
+// Besides, the generalization of UDPServer is probably too much for the
+// authoritative only server in terms of performance, and it's quite likely
+// we need to drop it for the authoritative server implementation.
+// At that point we can drop this test, too.
+TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
+ UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+ default_qid, Name("version.bind"),
+ RRClass::CH(), RRType::TXT());
+ createRequestPacket(request_message, IPPROTO_UDP);
+
+ (*server.getDNSLookupProvider())(*io_message, parse_message,
+ response_message,
+ response_obuffer, &dnsserv);
+ (*server.getDNSAnswerProvider())(*io_message, parse_message,
+ response_message, response_obuffer);
+
+ createBuiltinVersionResponse(default_qid, response_data);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ response_obuffer->getData(),
+ response_obuffer->getLength(),
+ &response_data[0], response_data.size());
+}
+
+// Same type of test as builtInQueryViaDNSServer but for an error response.
+TEST_F(AuthSrvTest, iqueryViaDNSServer) {
+ createDataFromFile("iquery_fromWire.wire");
+ (*server.getDNSLookupProvider())(*io_message, parse_message,
+ response_message,
+ response_obuffer, &dnsserv);
+ (*server.getDNSAnswerProvider())(*io_message, parse_message,
+ response_message, response_obuffer);
+
+ UnitTestUtil::readWireData("iquery_response_fromWire.wire",
+ response_data);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ response_obuffer->getData(),
+ response_obuffer->getLength(),
+ &response_data[0], response_data.size());
}
// Unsupported requests. Should result in NOTIMP.
TEST_F(AuthSrvTest, unsupportedRequest) {
- for (unsigned int i = 0; i < 16; ++i) {
- // set Opcode to 'i', which iterators over all possible codes except
- // the standard query and notify
- if (i == Opcode::QUERY().getCode() ||
- i == Opcode::NOTIFY().getCode()) {
- continue;
- }
- createDataFromFile("simplequery_fromWire.wire");
- data[2] = ((i << 3) & 0xff);
-
- parse_message.clear(Message::PARSE);
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
- headerCheck(parse_message, default_qid, Rcode::NOTIMP(), i, QR_FLAG,
- 0, 0, 0, 0);
- }
+ unsupportedRequest();
}
// Simple API check
@@ -366,155 +194,83 @@ TEST_F(AuthSrvTest, verbose) {
// Multiple questions. Should result in FORMERR.
TEST_F(AuthSrvTest, multiQuestion) {
- createDataFromFile("multiquestion_fromWire.wire");
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
- headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
- QR_FLAG, 2, 0, 0, 0);
-
- QuestionIterator qit = parse_message.beginQuestion();
- EXPECT_EQ(Name("example.com"), (*qit)->getName());
- EXPECT_EQ(RRClass::IN(), (*qit)->getClass());
- EXPECT_EQ(RRType::A(), (*qit)->getType());
- ++qit;
- EXPECT_EQ(Name("example.com"), (*qit)->getName());
- EXPECT_EQ(RRClass::IN(), (*qit)->getClass());
- EXPECT_EQ(RRType::AAAA(), (*qit)->getType());
- ++qit;
- EXPECT_TRUE(qit == parse_message.endQuestion());
+ multiQuestion();
}
// Incoming data doesn't even contain the complete header. Must be silently
// dropped.
TEST_F(AuthSrvTest, shortMessage) {
- createDataFromFile("shortmessage_fromWire");
- EXPECT_FALSE(server.processMessage(*io_message, parse_message,
- response_renderer));
+ shortMessage();
}
// Response messages. Must be silently dropped, whether it's a valid response
// or malformed or could otherwise cause a protocol error.
TEST_F(AuthSrvTest, response) {
- // A valid (although unusual) response
- createDataFromFile("simpleresponse_fromWire.wire");
- EXPECT_FALSE(server.processMessage(*io_message, parse_message,
- response_renderer));
-
- // A response with a broken question section. must be dropped rather than
- // returning FORMERR.
- createDataFromFile("shortresponse_fromWire");
- EXPECT_FALSE(server.processMessage(*io_message, parse_message,
- response_renderer));
-
- // A response to iquery. must be dropped rather than returning NOTIMP.
- createDataFromFile("iqueryresponse_fromWire.wire");
- EXPECT_FALSE(server.processMessage(*io_message, parse_message,
- response_renderer));
+ response();
}
// Query with a broken question
TEST_F(AuthSrvTest, shortQuestion) {
- createDataFromFile("shortquestion_fromWire");
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
- // Since the query's question is broken, the question section of the
- // response should be empty.
- headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
- QR_FLAG, 0, 0, 0, 0);
+ shortQuestion();
}
// Query with a broken answer section
TEST_F(AuthSrvTest, shortAnswer) {
- createDataFromFile("shortanswer_fromWire.wire");
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
-
- // This is a bogus query, but question section is valid. So the response
- // should copy the question section.
- headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
- QR_FLAG, 1, 0, 0, 0);
-
- QuestionIterator qit = parse_message.beginQuestion();
- EXPECT_EQ(Name("example.com"), (*qit)->getName());
- EXPECT_EQ(RRClass::IN(), (*qit)->getClass());
- EXPECT_EQ(RRType::A(), (*qit)->getType());
- ++qit;
- EXPECT_TRUE(qit == parse_message.endQuestion());
+ shortAnswer();
}
// Query with unsupported version of EDNS.
TEST_F(AuthSrvTest, ednsBadVers) {
- createDataFromFile("queryBadEDNS_fromWire.wire");
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
-
- // The response must have an EDNS OPT RR in the additional section, but
- // it will be added automatically at the render time.
- // Note that the DNSSEC DO bit is cleared even if this bit in the query
- // is set. This is a limitation of the current implementation.
- headerCheck(parse_message, default_qid, Rcode::BADVERS(), opcode.getCode(),
- QR_FLAG, 1, 0, 0, 1);
- EXPECT_FALSE(parse_message.getEDNS()); // EDNS isn't added at this point
-
- parse_message.clear(Message::PARSE);
- InputBuffer ib(response_renderer.getData(), response_renderer.getLength());
- parse_message.fromWire(ib);
- EXPECT_EQ(Rcode::BADVERS(), parse_message.getRcode());
- EXPECT_TRUE(parse_message.getEDNS());
- EXPECT_FALSE(parse_message.getEDNS()->getDNSSECAwareness());
+ ednsBadVers();
}
TEST_F(AuthSrvTest, AXFROverUDP) {
- // AXFR over UDP is invalid and should result in FORMERR.
- createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
- RRType::AXFR(), IPPROTO_UDP);
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
- headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
- QR_FLAG, 1, 0, 0, 0);
+ axfrOverUDP();
}
TEST_F(AuthSrvTest, AXFRSuccess) {
EXPECT_FALSE(xfrout.isConnected());
- createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
- RRType::AXFR(), IPPROTO_TCP);
+ UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+ Name("example.com"), RRClass::IN(), RRType::AXFR());
+ createRequestPacket(request_message, IPPROTO_TCP);
// On success, the AXFR query has been passed to a separate process,
// so we shouldn't have to respond.
- EXPECT_FALSE(server.processMessage(*io_message, parse_message,
- response_renderer));
- EXPECT_FALSE(xfrout.isConnected());
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_FALSE(dnsserv.hasAnswer());
+ EXPECT_TRUE(xfrout.isConnected());
}
TEST_F(AuthSrvTest, AXFRConnectFail) {
EXPECT_FALSE(xfrout.isConnected()); // check prerequisite
xfrout.disableConnect();
- createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
- RRType::AXFR(), IPPROTO_TCP);
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
- headerCheck(parse_message, default_qid, Rcode::SERVFAIL(),
+ UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+ Name("example.com"), RRClass::IN(), RRType::AXFR());
+ createRequestPacket(request_message, IPPROTO_TCP);
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
- // For a shot term workaround with xfrout we currently close the connection
- // for each AXFR attempt
EXPECT_FALSE(xfrout.isConnected());
}
TEST_F(AuthSrvTest, AXFRSendFail) {
// first send a valid query, making the connection with the xfr process
// open.
- createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
- RRType::AXFR(), IPPROTO_TCP);
- server.processMessage(*io_message, parse_message, response_renderer);
- EXPECT_FALSE(xfrout.isConnected()); // see above
+ UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+ Name("example.com"), RRClass::IN(), RRType::AXFR());
+ createRequestPacket(request_message, IPPROTO_TCP);
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_TRUE(xfrout.isConnected());
xfrout.disableSend();
- parse_message.clear(Message::PARSE);
- response_renderer.clear();
- createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
- RRType::AXFR(), IPPROTO_TCP);
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
- headerCheck(parse_message, default_qid, Rcode::SERVFAIL(),
+ parse_message->clear(Message::PARSE);
+ response_obuffer->clear();
+ UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+ Name("example.com"), RRClass::IN(), RRType::AXFR());
+ createRequestPacket(request_message, IPPROTO_TCP);
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
// The connection should have been closed due to the send failure.
@@ -526,10 +282,11 @@ TEST_F(AuthSrvTest, AXFRDisconnectFail) {
// should it be thrown.
xfrout.disableSend();
xfrout.disableDisconnect();
- createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
- RRType::AXFR(), IPPROTO_TCP);
+ UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+ Name("example.com"), RRClass::IN(), RRType::AXFR());
+ createRequestPacket(request_message, IPPROTO_TCP);
EXPECT_THROW(server.processMessage(*io_message, parse_message,
- response_renderer),
+ response_obuffer, &dnsserv),
XfroutError);
EXPECT_TRUE(xfrout.isConnected());
// XXX: we need to re-enable disconnect. otherwise an exception would be
@@ -538,31 +295,31 @@ TEST_F(AuthSrvTest, AXFRDisconnectFail) {
}
TEST_F(AuthSrvTest, notify) {
- createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
- RRType::SOA());
- request_message.setHeaderFlag(MessageFlag::AA());
- createRequestPacket(IPPROTO_UDP);
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+ Name("example.com"), RRClass::IN(), RRType::SOA());
+ request_message.setHeaderFlag(Message::HEADERFLAG_AA);
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
// An internal command message should have been created and sent to an
// external module. Check them.
- EXPECT_EQ("Zonemgr", notify_session.msg_destination);
+ EXPECT_EQ("Zonemgr", notify_session.getMessageDest());
EXPECT_EQ("notify",
- notify_session.sent_msg->get("command")->get(0)->stringValue());
+ notify_session.getSentMessage()->get("command")->get(0)->stringValue());
ConstElementPtr notify_args =
- notify_session.sent_msg->get("command")->get(1);
+ notify_session.getSentMessage()->get("command")->get(1);
EXPECT_EQ("example.com.", notify_args->get("zone_name")->stringValue());
EXPECT_EQ(DEFAULT_REMOTE_ADDRESS,
notify_args->get("master")->stringValue());
EXPECT_EQ("IN", notify_args->get("zone_class")->stringValue());
// On success, the server should return a response to the notify.
- headerCheck(parse_message, default_qid, Rcode::NOERROR(),
+ headerCheck(*parse_message, default_qid, Rcode::NOERROR(),
Opcode::NOTIFY().getCode(), QR_FLAG | AA_FLAG, 1, 0, 0, 0);
// The question must be identical to that of the received notify
- ConstQuestionPtr question = *parse_message.beginQuestion();
+ ConstQuestionPtr question = *parse_message->beginQuestion();
EXPECT_EQ(Name("example.com"), question->getName());
EXPECT_EQ(RRClass::IN(), question->getClass());
EXPECT_EQ(RRType::SOA(), question->getType());
@@ -570,17 +327,17 @@ TEST_F(AuthSrvTest, notify) {
TEST_F(AuthSrvTest, notifyForCHClass) {
// Same as the previous test, but for the CH RRClass.
- createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::CH(),
- RRType::SOA());
- request_message.setHeaderFlag(MessageFlag::AA());
- createRequestPacket(IPPROTO_UDP);
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+ Name("example.com"), RRClass::CH(), RRType::SOA());
+ request_message.setHeaderFlag(Message::HEADERFLAG_AA);
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
// Other conditions should be the same, so simply confirm the RR class is
// set correctly.
ConstElementPtr notify_args =
- notify_session.sent_msg->get("command")->get(1);
+ notify_session.getSentMessage()->get("command")->get(1);
EXPECT_EQ("CH", notify_args->get("zone_class")->stringValue());
}
@@ -588,129 +345,130 @@ TEST_F(AuthSrvTest, notifyEmptyQuestion) {
request_message.clear(Message::RENDER);
request_message.setOpcode(Opcode::NOTIFY());
request_message.setRcode(Rcode::NOERROR());
- request_message.setHeaderFlag(MessageFlag::AA());
+ request_message.setHeaderFlag(Message::HEADERFLAG_AA);
request_message.setQid(default_qid);
request_message.toWire(request_renderer);
- createRequestPacket(IPPROTO_UDP);
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
- headerCheck(parse_message, default_qid, Rcode::FORMERR(),
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::FORMERR(),
Opcode::NOTIFY().getCode(), QR_FLAG, 0, 0, 0, 0);
}
TEST_F(AuthSrvTest, notifyMultiQuestions) {
- createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
- RRType::SOA());
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+ Name("example.com"), RRClass::IN(), RRType::SOA());
// add one more SOA question
request_message.addQuestion(Question(Name("example.com"), RRClass::IN(),
RRType::SOA()));
- request_message.setHeaderFlag(MessageFlag::AA());
- createRequestPacket(IPPROTO_UDP);
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
- headerCheck(parse_message, default_qid, Rcode::FORMERR(),
+ request_message.setHeaderFlag(Message::HEADERFLAG_AA);
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::FORMERR(),
Opcode::NOTIFY().getCode(), QR_FLAG, 2, 0, 0, 0);
}
TEST_F(AuthSrvTest, notifyNonSOAQuestion) {
- createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
- RRType::NS());
- request_message.setHeaderFlag(MessageFlag::AA());
- createRequestPacket(IPPROTO_UDP);
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
- headerCheck(parse_message, default_qid, Rcode::FORMERR(),
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+ Name("example.com"), RRClass::IN(), RRType::NS());
+ request_message.setHeaderFlag(Message::HEADERFLAG_AA);
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::FORMERR(),
Opcode::NOTIFY().getCode(), QR_FLAG, 1, 0, 0, 0);
}
TEST_F(AuthSrvTest, notifyWithoutAA) {
// implicitly leave the AA bit off. our implementation will accept it.
- createRequestPacket(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
- RRType::SOA());
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
- headerCheck(parse_message, default_qid, Rcode::NOERROR(),
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+ Name("example.com"), RRClass::IN(), RRType::SOA());
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::NOERROR(),
Opcode::NOTIFY().getCode(), QR_FLAG | AA_FLAG, 1, 0, 0, 0);
}
TEST_F(AuthSrvTest, notifyWithErrorRcode) {
- createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
- RRType::SOA());
- request_message.setHeaderFlag(MessageFlag::AA());
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+ Name("example.com"), RRClass::IN(), RRType::SOA());
+ request_message.setHeaderFlag(Message::HEADERFLAG_AA);
request_message.setRcode(Rcode::SERVFAIL());
- createRequestPacket(IPPROTO_UDP);
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
- headerCheck(parse_message, default_qid, Rcode::NOERROR(),
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::NOERROR(),
Opcode::NOTIFY().getCode(), QR_FLAG | AA_FLAG, 1, 0, 0, 0);
}
TEST_F(AuthSrvTest, notifyWithoutSession) {
server.setXfrinSession(NULL);
- createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
- RRType::SOA());
- request_message.setHeaderFlag(MessageFlag::AA());
- createRequestPacket(IPPROTO_UDP);
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+ Name("example.com"), RRClass::IN(), RRType::SOA());
+ request_message.setHeaderFlag(Message::HEADERFLAG_AA);
+ createRequestPacket(request_message, IPPROTO_UDP);
// we simply ignore the notify and let it be resent if an internal error
// happens.
- EXPECT_FALSE(server.processMessage(*io_message, parse_message,
- response_renderer));
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_FALSE(dnsserv.hasAnswer());
}
TEST_F(AuthSrvTest, notifySendFail) {
notify_session.disableSend();
- createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
- RRType::SOA());
- request_message.setHeaderFlag(MessageFlag::AA());
- createRequestPacket(IPPROTO_UDP);
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+ Name("example.com"), RRClass::IN(), RRType::SOA());
+ request_message.setHeaderFlag(Message::HEADERFLAG_AA);
+ createRequestPacket(request_message, IPPROTO_UDP);
- EXPECT_FALSE(server.processMessage(*io_message, parse_message,
- response_renderer));
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_FALSE(dnsserv.hasAnswer());
}
TEST_F(AuthSrvTest, notifyReceiveFail) {
notify_session.disableReceive();
- createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
- RRType::SOA());
- request_message.setHeaderFlag(MessageFlag::AA());
- createRequestPacket(IPPROTO_UDP);
- EXPECT_FALSE(server.processMessage(*io_message, parse_message,
- response_renderer));
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+ Name("example.com"), RRClass::IN(), RRType::SOA());
+ request_message.setHeaderFlag(Message::HEADERFLAG_AA);
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_FALSE(dnsserv.hasAnswer());
}
TEST_F(AuthSrvTest, notifyWithBogusSessionMessage) {
notify_session.setMessage(Element::fromJSON("{\"foo\": 1}"));
- createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
- RRType::SOA());
- request_message.setHeaderFlag(MessageFlag::AA());
- createRequestPacket(IPPROTO_UDP);
- EXPECT_FALSE(server.processMessage(*io_message, parse_message,
- response_renderer));
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+ Name("example.com"), RRClass::IN(), RRType::SOA());
+ request_message.setHeaderFlag(Message::HEADERFLAG_AA);
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_FALSE(dnsserv.hasAnswer());
}
TEST_F(AuthSrvTest, notifyWithSessionMessageError) {
notify_session.setMessage(
Element::fromJSON("{\"result\": [1, \"FAIL\"]}"));
- createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
- RRType::SOA());
- request_message.setHeaderFlag(MessageFlag::AA());
- createRequestPacket(IPPROTO_UDP);
- EXPECT_FALSE(server.processMessage(*io_message, parse_message,
- response_renderer));
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+ Name("example.com"), RRClass::IN(), RRType::SOA());
+ request_message.setHeaderFlag(Message::HEADERFLAG_AA);
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_FALSE(dnsserv.hasAnswer());
}
void
-updateConfig(AuthSrv* server, const char* const dbfile,
+updateConfig(AuthSrv* server, const char* const config_data,
const bool expect_success)
{
ConstElementPtr config_answer =
- server->updateConfig(Element::fromJSON(dbfile));
+ server->updateConfig(Element::fromJSON(config_data));
EXPECT_EQ(Element::map, config_answer->getType());
EXPECT_TRUE(config_answer->contains("result"));
@@ -727,9 +485,9 @@ TEST_F(AuthSrvTest, updateConfig) {
// response should have the AA flag on, and have an RR in each answer
// and authority section.
createDataFromFile("examplequery_fromWire.wire");
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
- headerCheck(parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
QR_FLAG | AA_FLAG, 1, 1, 1, 0);
}
@@ -741,9 +499,9 @@ TEST_F(AuthSrvTest, datasourceFail) {
// in a SERVFAIL response, and the answer and authority sections should
// be empty.
createDataFromFile("badExampleQuery_fromWire.wire");
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
- headerCheck(parse_message, default_qid, Rcode::SERVFAIL(),
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
}
@@ -756,12 +514,50 @@ TEST_F(AuthSrvTest, updateConfigFail) {
// The original data source should still exist.
createDataFromFile("examplequery_fromWire.wire");
- EXPECT_TRUE(server.processMessage(*io_message, parse_message,
- response_renderer));
- headerCheck(parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
QR_FLAG | AA_FLAG, 1, 1, 1, 0);
}
+TEST_F(AuthSrvTest, updateWithMemoryDataSrc) {
+ // Test configuring memory data source. Detailed test cases are covered
+ // in the configuration tests. We only check the AuthSrv interface here.
+
+ // By default memory data source isn't enabled
+ EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
+ updateConfig(&server,
+ "{\"datasources\": [{\"type\": \"memory\"}]}", true);
+ // after successful configuration, we should have one (with empty zoneset).
+ ASSERT_NE(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
+ EXPECT_EQ(0, server.getMemoryDataSrc(rrclass)->getZoneCount());
+
+ // The memory data source is empty, should return REFUSED rcode.
+ createDataFromFile("examplequery_fromWire.wire");
+ server.processMessage(*io_message, parse_message, response_obuffer,
+ &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::REFUSED(),
+ opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+}
+
+TEST_F(AuthSrvTest, chQueryWithMemoryDataSrc) {
+ // Configure memory data source for class IN
+ updateConfig(&server, "{\"datasources\": "
+ "[{\"class\": \"IN\", \"type\": \"memory\"}]}", true);
+
+ // This shouldn't affect the result of class CH query
+ UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+ default_qid, Name("version.bind"),
+ RRClass::CH(), RRType::TXT());
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, parse_message, response_obuffer,
+ &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::NOERROR(),
+ opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 1, 0);
+}
+
TEST_F(AuthSrvTest, cacheSlots) {
// simple check for the get/set operations
server.setCacheSlots(10); // 10 = arbitrary choice
@@ -771,4 +567,103 @@ TEST_F(AuthSrvTest, cacheSlots) {
server.setCacheSlots(0);
EXPECT_EQ(00, server.getCacheSlots());
}
+
+// Submit UDP normal query and check query counter
+TEST_F(AuthSrvTest, queryCounterUDPNormal) {
+ // The counter should be initialized to 0.
+ EXPECT_EQ(0, server.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+ // Create UDP message and process.
+ UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+ default_qid, Name("example.com"),
+ RRClass::IN(), RRType::NS());
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, parse_message, response_obuffer,
+ &dnsserv);
+ // After processing UDP query, the counter should be 1.
+ EXPECT_EQ(1, server.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+}
+
+// Submit TCP normal query and check query counter
+TEST_F(AuthSrvTest, queryCounterTCPNormal) {
+ // The counter should be initialized to 0.
+ EXPECT_EQ(0, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+ // Create TCP message and process.
+ UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+ default_qid, Name("example.com"),
+ RRClass::IN(), RRType::NS());
+ createRequestPacket(request_message, IPPROTO_TCP);
+ server.processMessage(*io_message, parse_message, response_obuffer,
+ &dnsserv);
+ // After processing TCP query, the counter should be 1.
+ EXPECT_EQ(1, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+}
+
+// Submit TCP AXFR query and check query counter
+TEST_F(AuthSrvTest, queryCounterTCPAXFR) {
+ // The counter should be initialized to 0.
+ EXPECT_EQ(0, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+ UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+ Name("example.com"), RRClass::IN(), RRType::AXFR());
+ createRequestPacket(request_message, IPPROTO_TCP);
+ // On success, the AXFR query has been passed to a separate process,
+ // so we shouldn't have to respond.
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ // After processing TCP AXFR query, the counter should be 1.
+ EXPECT_EQ(1, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+}
+
+// class for queryCounterUnexpected test
+// getProtocol() returns IPPROTO_IP
+class DummyUnknownSocket : public IOSocket {
+public:
+ DummyUnknownSocket() {}
+ virtual int getNative() const { return (0); }
+ virtual int getProtocol() const { return (IPPROTO_IP); }
+};
+
+// function for queryCounterUnexpected test
+// returns a reference to a static object of DummyUnknownSocket
+IOSocket&
+getDummyUnknownSocket() {
+ static DummyUnknownSocket socket;
+ return (socket);
+}
+
+// Submit unexpected type of query and check it throws isc::Unexpected
+TEST_F(AuthSrvTest, queryCounterUnexpected) {
+ // This code isn't exception safe, but we'd rather keep the code
+ // simpler and more readable as this is only for tests and if it throws
+ // the program would immediately terminate anyway.
+
+ // Create UDP query packet.
+ UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+ default_qid, Name("example.com"),
+ RRClass::IN(), RRType::NS());
+ createRequestPacket(request_message, IPPROTO_UDP);
+
+ // Modify the message.
+ delete io_message;
+ endpoint = IOEndpoint::create(IPPROTO_UDP,
+ IOAddress(DEFAULT_REMOTE_ADDRESS), 53210);
+ io_message = new IOMessage(request_renderer.getData(),
+ request_renderer.getLength(),
+ getDummyUnknownSocket(), *endpoint);
+
+ EXPECT_THROW(server.processMessage(*io_message, parse_message,
+ response_obuffer, &dnsserv),
+ isc::Unexpected);
+}
+
+TEST_F(AuthSrvTest, stop) {
+ // normal case is covered in command_unittest.cc. we should primarily
+ // test it here, but the current design of the stop test takes time,
+ // so we consolidate the cases in the command tests.
+ // If/when the interval timer has finer granularity we'll probably add
+ // our own tests here, so we keep this empty test case.
+}
+
+TEST_F(AuthSrvTest, listenAddresses) {
+ isc::testutils::portconfig::listenAddresses(server);
+}
+
}
diff --git a/src/bin/auth/tests/change_user_unittest.cc b/src/bin/auth/tests/change_user_unittest.cc
index 299b521..33897b6 100644
--- a/src/bin/auth/tests/change_user_unittest.cc
+++ b/src/bin/auth/tests/change_user_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdlib.h>
#include <unistd.h> // for getuid
diff --git a/src/bin/auth/tests/command_unittest.cc b/src/bin/auth/tests/command_unittest.cc
new file mode 100644
index 0000000..f788d9e
--- /dev/null
+++ b/src/bin/auth/tests/command_unittest.cc
@@ -0,0 +1,290 @@
+// 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 <cassert>
+#include <cstdlib>
+#include <string>
+#include <stdexcept>
+
+#include <boost/bind.hpp>
+
+#include <gtest/gtest.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <cc/data.h>
+
+#include <config/ccsession.h>
+
+#include <datasrc/memory_datasrc.h>
+
+#include <auth/auth_srv.h>
+#include <auth/config.h>
+#include <auth/command.h>
+
+#include <asiolink/asiolink.h>
+
+#include <testutils/mockups.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::datasrc;
+using namespace isc::config;
+
+namespace {
+class AuthConmmandTest : public ::testing::Test {
+protected:
+ AuthConmmandTest() : server(false, xfrout), rcode(-1) {
+ server.setStatisticsSession(&statistics_session);
+ }
+ void checkAnswer(const int expected_code) {
+ parseAnswer(rcode, result);
+ EXPECT_EQ(expected_code, rcode);
+ }
+ MockSession statistics_session;
+ MockXfroutClient xfrout;
+ AuthSrv server;
+ AuthSrv::ConstMemoryDataSrcPtr memory_datasrc;
+ ConstElementPtr result;
+ int rcode;
+public:
+ void stopServer(); // need to be public for boost::bind
+};
+
+TEST_F(AuthConmmandTest, unknownCommand) {
+ result = execAuthServerCommand(server, "no_such_command",
+ ConstElementPtr());
+ parseAnswer(rcode, result);
+ EXPECT_EQ(1, rcode);
+}
+
+TEST_F(AuthConmmandTest, DISABLED_unexpectedException) {
+ // execAuthServerCommand() won't catch standard exceptions.
+ // Skip this test for now: ModuleCCSession doesn't seem to validate
+ // commands.
+ EXPECT_THROW(execAuthServerCommand(server, "_throw_exception",
+ ConstElementPtr()),
+ runtime_error);
+}
+
+TEST_F(AuthConmmandTest, sendStatistics) {
+ result = execAuthServerCommand(server, "sendstats", ConstElementPtr());
+ // Just check some message has been sent. Detailed tests specific to
+ // statistics are done in its own tests.
+ EXPECT_EQ("Stats", statistics_session.getMessageDest());
+ checkAnswer(0);
+}
+
+void
+AuthConmmandTest::stopServer() {
+ result = execAuthServerCommand(server, "shutdown", ConstElementPtr());
+ parseAnswer(rcode, result);
+ assert(rcode == 0); // make sure the test stops when something is wrong
+}
+
+TEST_F(AuthConmmandTest, shutdown) {
+ asiolink::IntervalTimer itimer(server.getIOService());
+ itimer.setup(boost::bind(&AuthConmmandTest::stopServer, this), 1);
+ server.getIOService().run();
+ EXPECT_EQ(0, rcode);
+}
+
+// A helper function commonly used for the "loadzone" command tests.
+// It configures the server with a memory data source containing two
+// zones, and checks the zones are correctly loaded.
+void
+zoneChecks(AuthSrv& server) {
+ EXPECT_TRUE(server.getMemoryDataSrc(RRClass::IN()));
+ EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(RRClass::IN())->
+ findZone(Name("ns.test1.example")).zone->
+ find(Name("ns.test1.example"), RRType::A()).code);
+ EXPECT_EQ(Zone::NXRRSET, server.getMemoryDataSrc(RRClass::IN())->
+ findZone(Name("ns.test1.example")).zone->
+ find(Name("ns.test1.example"), RRType::AAAA()).code);
+ EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(RRClass::IN())->
+ findZone(Name("ns.test2.example")).zone->
+ find(Name("ns.test2.example"), RRType::A()).code);
+ EXPECT_EQ(Zone::NXRRSET, server.getMemoryDataSrc(RRClass::IN())->
+ findZone(Name("ns.test2.example")).zone->
+ find(Name("ns.test2.example"), RRType::AAAA()).code);
+}
+
+void
+configureZones(AuthSrv& server) {
+ ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR "/test1.zone.in "
+ TEST_DATA_BUILDDIR "/test1.zone.copied"));
+ ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR "/test2.zone.in "
+ TEST_DATA_BUILDDIR "/test2.zone.copied"));
+ configureAuthServer(server, Element::fromJSON(
+ "{\"datasources\": "
+ " [{\"type\": \"memory\","
+ " \"zones\": "
+ "[{\"origin\": \"test1.example\","
+ " \"file\": \""
+ TEST_DATA_BUILDDIR "/test1.zone.copied\"},"
+ " {\"origin\": \"test2.example\","
+ " \"file\": \""
+ TEST_DATA_BUILDDIR "/test2.zone.copied\"}"
+ "]}]}"));
+ zoneChecks(server);
+}
+
+void
+newZoneChecks(AuthSrv& server) {
+ EXPECT_TRUE(server.getMemoryDataSrc(RRClass::IN()));
+ EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(RRClass::IN())->
+ findZone(Name("ns.test1.example")).zone->
+ find(Name("ns.test1.example"), RRType::A()).code);
+ // now test1.example should have ns/AAAA
+ EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(RRClass::IN())->
+ findZone(Name("ns.test1.example")).zone->
+ find(Name("ns.test1.example"), RRType::AAAA()).code);
+
+ // test2.example shouldn't change
+ EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(RRClass::IN())->
+ findZone(Name("ns.test2.example")).zone->
+ find(Name("ns.test2.example"), RRType::A()).code);
+ EXPECT_EQ(Zone::NXRRSET, server.getMemoryDataSrc(RRClass::IN())->
+ findZone(Name("ns.test2.example")).zone->
+ find(Name("ns.test2.example"), RRType::AAAA()).code);
+}
+
+TEST_F(AuthConmmandTest, loadZone) {
+ configureZones(server);
+
+ ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
+ "/test1-new.zone.in "
+ TEST_DATA_BUILDDIR "/test1.zone.copied"));
+ ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
+ "/test2-new.zone.in "
+ TEST_DATA_BUILDDIR "/test2.zone.copied"));
+
+ result = execAuthServerCommand(server, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\"}"));
+ checkAnswer(0);
+ newZoneChecks(server);
+}
+
+TEST_F(AuthConmmandTest, loadBrokenZone) {
+ configureZones(server);
+
+ ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
+ "/test1-broken.zone.in "
+ TEST_DATA_BUILDDIR "/test1.zone.copied"));
+ result = execAuthServerCommand(server, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\"}"));
+ checkAnswer(1);
+ zoneChecks(server); // zone shouldn't be replaced
+}
+
+TEST_F(AuthConmmandTest, loadUnreadableZone) {
+ configureZones(server);
+
+ // install the zone file as unreadable
+ ASSERT_EQ(0, system(INSTALL_PROG " -m 000 " TEST_DATA_DIR
+ "/test1.zone.in "
+ TEST_DATA_BUILDDIR "/test1.zone.copied"));
+ result = execAuthServerCommand(server, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\"}"));
+ checkAnswer(1);
+ zoneChecks(server); // zone shouldn't be replaced
+}
+
+TEST_F(AuthConmmandTest, loadZoneWithoutDataSrc) {
+ // try to execute load command without configuring the zone beforehand.
+ // it should fail.
+ result = execAuthServerCommand(server, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\"}"));
+ checkAnswer(1);
+}
+
+TEST_F(AuthConmmandTest, loadSqlite3DataSrc) {
+ // For sqlite3 data source we don't have to do anything (the data source
+ // (re)loads itself automatically)
+ result = execAuthServerCommand(server, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\","
+ " \"datasrc\": \"sqlite3\"}"));
+ checkAnswer(0);
+}
+
+TEST_F(AuthConmmandTest, loadZoneInvalidParams) {
+ configureZones(server);
+
+ // null arg
+ result = execAuthServerCommand(server, "loadzone", ElementPtr());
+ checkAnswer(1);
+
+ // zone class is bogus
+ result = execAuthServerCommand(server, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\","
+ " \"class\": \"no_such_class\"}"));
+ checkAnswer(1);
+
+ result = execAuthServerCommand(server, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\","
+ " \"class\": 1}"));
+ checkAnswer(1);
+
+ // unsupported zone class
+ result = execAuthServerCommand(server, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\","
+ " \"class\": \"CH\"}"));
+ checkAnswer(1);
+
+ // unsupported data source class
+ result = execAuthServerCommand(server, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\","
+ " \"datasrc\": \"not supported\"}"));
+ checkAnswer(1);
+
+ // data source is bogus
+ result = execAuthServerCommand(server, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\","
+ " \"datasrc\": 0}"));
+ checkAnswer(1);
+
+ // origin is missing
+ result = execAuthServerCommand(server, "loadzone",
+ Element::fromJSON("{}"));
+ checkAnswer(1);
+
+ // zone doesn't exist in the data source
+ result = execAuthServerCommand(server, "loadzone",
+ Element::fromJSON("{\"origin\": \"xx\"}"));
+ checkAnswer(1);
+
+ // origin is bogus
+ result = execAuthServerCommand(server, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"...\"}"));
+ checkAnswer(1);
+
+ result = execAuthServerCommand(server, "loadzone",
+ Element::fromJSON("{\"origin\": 10}"));
+ checkAnswer(1);
+}
+}
diff --git a/src/bin/auth/tests/config_unittest.cc b/src/bin/auth/tests/config_unittest.cc
new file mode 100644
index 0000000..8cce0af
--- /dev/null
+++ b/src/bin/auth/tests/config_unittest.cc
@@ -0,0 +1,393 @@
+// 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 <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rrclass.h>
+#include <dns/masterload.h>
+
+#include <cc/data.h>
+
+#include <datasrc/memory_datasrc.h>
+
+#include <xfr/xfrout_client.h>
+
+#include <auth/auth_srv.h>
+#include <auth/config.h>
+#include <auth/common.h>
+
+#include <testutils/mockups.h>
+#include <testutils/portconfig.h>
+
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::datasrc;
+using namespace asiolink;
+
+namespace {
+class AuthConfigTest : public ::testing::Test {
+protected:
+ AuthConfigTest() :
+ dnss_(ios_, NULL, NULL, NULL),
+ rrclass(RRClass::IN()),
+ server(true, xfrout)
+ {
+ server.setDNSService(dnss_);
+ }
+ IOService ios_;
+ DNSService dnss_;
+ const RRClass rrclass;
+ MockXfroutClient xfrout;
+ AuthSrv server;
+};
+
+TEST_F(AuthConfigTest, datasourceConfig) {
+ // By default, we don't have any in-memory data source.
+ EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
+ configureAuthServer(server, Element::fromJSON(
+ "{\"datasources\": [{\"type\": \"memory\"}]}"));
+ // after successful configuration, we should have one (with empty zoneset).
+ ASSERT_NE(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
+ EXPECT_EQ(0, server.getMemoryDataSrc(rrclass)->getZoneCount());
+}
+
+TEST_F(AuthConfigTest, databaseConfig) {
+ // right now, "database_file" is handled separately, so the parser
+ // doesn't recognize it, but it shouldn't throw an exception due to that.
+ EXPECT_NO_THROW(configureAuthServer(
+ server,
+ Element::fromJSON(
+ "{\"database_file\": \"should_be_ignored\"}")));
+}
+
+TEST_F(AuthConfigTest, exceptionGuarantee) {
+ EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
+ // This configuration contains an invalid item, which will trigger
+ // an exception.
+ EXPECT_THROW(configureAuthServer(
+ server,
+ Element::fromJSON(
+ "{\"datasources\": [{\"type\": \"memory\"}], "
+ " \"no_such_config_var\": 1}")),
+ AuthConfigError);
+ // The server state shouldn't change
+ EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
+}
+
+TEST_F(AuthConfigTest, exceptionConversion) {
+ // This configuration contains a bogus RR class, which will trigger an
+ // exception from libdns++. configureAuthServer() should convert this
+ // to AuthConfigError and rethrow the converted one.
+ EXPECT_THROW(configureAuthServer(
+ server,
+ Element::fromJSON(
+ "{\"datasources\": "
+ " [{\"type\": \"memory\","
+ " \"class\": \"BADCLASS\","
+ " \"zones\": [{\"origin\": \"example.com\","
+ " \"file\": \"example.zone\"}]}]}")),
+ AuthConfigError);
+}
+
+TEST_F(AuthConfigTest, badConfig) {
+ // These should normally not happen, but should be handled to avoid
+ // an unexpected crash due to a bug of the caller.
+ EXPECT_THROW(configureAuthServer(server, ElementPtr()), AuthConfigError);
+ EXPECT_THROW(configureAuthServer(server, Element::fromJSON("[]")),
+ AuthConfigError);
+}
+
+TEST_F(AuthConfigTest, unknownConfigVar) {
+ EXPECT_THROW(createAuthConfigParser(server, "no_such_config_var"),
+ AuthConfigError);
+}
+
+TEST_F(AuthConfigTest, exceptionFromCommit) {
+ EXPECT_THROW(configureAuthServer(server, Element::fromJSON(
+ "{\"_commit_throw\": 10}")),
+ FatalError);
+}
+
+// Test invalid address configs are rejected
+TEST_F(AuthConfigTest, invalidListenAddressConfig) {
+ // This currently passes simply because the config doesn't know listen_on
+ isc::testutils::portconfig::invalidListenAddressConfig(server);
+}
+
+// Try setting addresses trough config
+TEST_F(AuthConfigTest, listenAddressConfig) {
+ isc::testutils::portconfig::listenAddressConfig(server);
+}
+
+class MemoryDatasrcConfigTest : public AuthConfigTest {
+protected:
+ MemoryDatasrcConfigTest() :
+ parser(createAuthConfigParser(server, "datasources"))
+ {}
+ ~MemoryDatasrcConfigTest() {
+ delete parser;
+ }
+ AuthConfigParser* parser;
+};
+
+TEST_F(MemoryDatasrcConfigTest, addZeroDataSrc) {
+ parser->build(Element::fromJSON("[]"));
+ parser->commit();
+ EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
+}
+
+TEST_F(MemoryDatasrcConfigTest, addEmpty) {
+ // By default, we don't have any in-memory data source.
+ EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
+ parser->build(Element::fromJSON("[{\"type\": \"memory\"}]"));
+ parser->commit();
+ EXPECT_EQ(0, server.getMemoryDataSrc(rrclass)->getZoneCount());
+}
+
+TEST_F(MemoryDatasrcConfigTest, addZeroZone) {
+ parser->build(Element::fromJSON("[{\"type\": \"memory\","
+ " \"zones\": []}]"));
+ parser->commit();
+ EXPECT_EQ(0, server.getMemoryDataSrc(rrclass)->getZoneCount());
+}
+
+TEST_F(MemoryDatasrcConfigTest, addOneZone) {
+ EXPECT_NO_THROW(parser->build(Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example.com\","
+ " \"file\": \"" TEST_DATA_DIR
+ "/example.zone\"}]}]")));
+ EXPECT_NO_THROW(parser->commit());
+ EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
+ // Check it actually loaded something
+ EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(rrclass)->findZone(
+ Name("ns.example.com.")).zone->find(Name("ns.example.com."),
+ RRType::A()).code);
+}
+
+TEST_F(MemoryDatasrcConfigTest, addMultiZones) {
+ EXPECT_NO_THROW(parser->build(Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example.com\","
+ " \"file\": \"" TEST_DATA_DIR
+ "/example.zone\"},"
+ " {\"origin\": \"example.org\","
+ " \"file\": \"" TEST_DATA_DIR
+ "/example.org.zone\"},"
+ " {\"origin\": \"example.net\","
+ " \"file\": \"" TEST_DATA_DIR
+ "/example.net.zone\"}]}]")));
+ EXPECT_NO_THROW(parser->commit());
+ EXPECT_EQ(3, server.getMemoryDataSrc(rrclass)->getZoneCount());
+}
+
+TEST_F(MemoryDatasrcConfigTest, replace) {
+ EXPECT_NO_THROW(parser->build(Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example.com\","
+ " \"file\": \"" TEST_DATA_DIR
+ "/example.zone\"}]}]")));
+ EXPECT_NO_THROW(parser->commit());
+ EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
+ EXPECT_EQ(isc::datasrc::result::SUCCESS,
+ server.getMemoryDataSrc(rrclass)->findZone(
+ Name("example.com")).code);
+
+ // create a new parser, and install a new set of configuration. It
+ // should replace the old one.
+ delete parser;
+ parser = createAuthConfigParser(server, "datasources");
+ EXPECT_NO_THROW(parser->build(Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example.org\","
+ " \"file\": \"" TEST_DATA_DIR
+ "/example.org.zone\"},"
+ " {\"origin\": \"example.net\","
+ " \"file\": \"" TEST_DATA_DIR
+ "/example.net.zone\"}]}]")));
+ EXPECT_NO_THROW(parser->commit());
+ EXPECT_EQ(2, server.getMemoryDataSrc(rrclass)->getZoneCount());
+ EXPECT_EQ(isc::datasrc::result::NOTFOUND,
+ server.getMemoryDataSrc(rrclass)->findZone(
+ Name("example.com")).code);
+}
+
+TEST_F(MemoryDatasrcConfigTest, exception) {
+ // Load a zone
+ EXPECT_NO_THROW(parser->build(Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example.com\","
+ " \"file\": \"" TEST_DATA_DIR
+ "/example.zone\"}]}]")));
+ EXPECT_NO_THROW(parser->commit());
+ EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
+ EXPECT_EQ(isc::datasrc::result::SUCCESS,
+ server.getMemoryDataSrc(rrclass)->findZone(
+ Name("example.com")).code);
+
+ // create a new parser, and try to load something. It will throw,
+ // the given master file should not exist
+ delete parser;
+ parser = createAuthConfigParser(server, "datasources");
+ EXPECT_THROW(parser->build(Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example.org\","
+ " \"file\": \"" TEST_DATA_DIR
+ "/example.org.zone\"},"
+ " {\"origin\": \"example.net\","
+ " \"file\": \"" TEST_DATA_DIR
+ "/nonexistent.zone\"}]}]")), isc::dns::MasterLoadError);
+ // As that one throwed exception, it is not expected from us to
+ // commit it
+
+ // The original should be untouched
+ EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
+ EXPECT_EQ(isc::datasrc::result::SUCCESS,
+ server.getMemoryDataSrc(rrclass)->findZone(
+ Name("example.com")).code);
+}
+
+TEST_F(MemoryDatasrcConfigTest, remove) {
+ EXPECT_NO_THROW(parser->build(Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example.com\","
+ " \"file\": \"" TEST_DATA_DIR
+ "/example.zone\"}]}]")));
+ EXPECT_NO_THROW(parser->commit());
+ EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
+
+ delete parser;
+ parser = createAuthConfigParser(server, "datasources");
+ EXPECT_NO_THROW(parser->build(Element::fromJSON("[]")));
+ EXPECT_NO_THROW(parser->commit());
+ EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
+}
+
+TEST_F(MemoryDatasrcConfigTest, adDuplicateZones) {
+ EXPECT_THROW(parser->build(
+ Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example.com\","
+ " \"file\": \"" TEST_DATA_DIR
+ "/example.zone\"},"
+ " {\"origin\": \"example.com\","
+ " \"file\": \"" TEST_DATA_DIR
+ "/example.com.zone\"}]}]")),
+ AuthConfigError);
+}
+
+TEST_F(MemoryDatasrcConfigTest, addBadZone) {
+ // origin is missing
+ EXPECT_THROW(parser->build(
+ Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{\"file\": \"example.zone\"}]}]")),
+ AuthConfigError);
+
+ // missing zone file
+ EXPECT_THROW(parser->build(
+ Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example.com\"}]}]")),
+ AuthConfigError);
+
+ // bogus origin name
+ EXPECT_THROW(parser->build(Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example..com\","
+ " \"file\": \"example.zone\"}]}]")),
+ EmptyLabel);
+
+ // bogus RR class name
+ EXPECT_THROW(parser->build(
+ Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"class\": \"BADCLASS\","
+ " \"zones\": [{\"origin\": \"example.com\","
+ " \"file\": \"example.zone\"}]}]")),
+ InvalidRRClass);
+
+ // valid RR class, but not currently supported
+ EXPECT_THROW(parser->build(
+ Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"class\": \"CH\","
+ " \"zones\": [{\"origin\": \"example.com\","
+ " \"file\": \"example.zone\"}]}]")),
+ isc::InvalidParameter);
+}
+
+TEST_F(MemoryDatasrcConfigTest, badDatasrcType) {
+ EXPECT_THROW(parser->build(Element::fromJSON("[{\"type\": \"badsrc\"}]")),
+ AuthConfigError);
+ EXPECT_THROW(parser->build(Element::fromJSON("[{\"notype\": \"memory\"}]")),
+ AuthConfigError);
+ EXPECT_THROW(parser->build(Element::fromJSON("[{\"type\": 1}]")),
+ isc::data::TypeError);
+ EXPECT_THROW(parser->build(Element::fromJSON("[{\"type\": \"memory\"},"
+ " {\"type\": \"memory\"}]")),
+ AuthConfigError);
+}
+
+class StatisticsIntervalConfigTest : public AuthConfigTest {
+protected:
+ StatisticsIntervalConfigTest() :
+ parser(createAuthConfigParser(server, "statistics-interval"))
+ {}
+ ~StatisticsIntervalConfigTest() {
+ delete parser;
+ }
+ AuthConfigParser* parser;
+};
+
+TEST_F(StatisticsIntervalConfigTest, setInterval) {
+ // initially the timer is not configured.
+ EXPECT_EQ(0, server.getStatisticsTimerInterval());
+
+ // initialize the timer
+ parser->build(Element::fromJSON("5"));
+ parser->commit();
+ EXPECT_EQ(5, server.getStatisticsTimerInterval());
+
+ // reset the timer with a new interval
+ delete parser;
+ parser = createAuthConfigParser(server, "statistics-interval");
+ ASSERT_NE(static_cast<void*>(NULL), parser);
+ parser->build(Element::fromJSON("10"));
+ parser->commit();
+ EXPECT_EQ(10, server.getStatisticsTimerInterval());
+
+ // disable the timer again
+ delete parser;
+ parser = createAuthConfigParser(server, "statistics-interval");
+ ASSERT_NE(static_cast<void*>(NULL), parser);
+ parser->build(Element::fromJSON("0"));
+ parser->commit();
+ EXPECT_EQ(0, server.getStatisticsTimerInterval());
+}
+
+TEST_F(StatisticsIntervalConfigTest, badInterval) {
+ EXPECT_THROW(parser->build(Element::fromJSON("\"should be integer\"")),
+ isc::data::TypeError);
+ EXPECT_THROW(parser->build(Element::fromJSON("2.5")),
+ isc::data::TypeError);
+ EXPECT_THROW(parser->build(Element::fromJSON("-1")), AuthConfigError);
+ // bounds check: interval value must be equal to or shorter than
+ // 86400 seconds (1 day)
+ EXPECT_NO_THROW(parser->build(Element::fromJSON("86400")));
+ EXPECT_THROW(parser->build(Element::fromJSON("86401")), AuthConfigError);
+}
+}
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
new file mode 100644
index 0000000..c68b672
--- /dev/null
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -0,0 +1,686 @@
+// 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 <sstream>
+#include <vector>
+#include <map>
+
+#include <boost/bind.hpp>
+
+#include <dns/masterload.h>
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/rrttl.h>
+#include <dns/rrtype.h>
+#include <dns/rdataclass.h>
+
+#include <datasrc/memory_datasrc.h>
+
+#include <auth/query.h>
+
+#include <testutils/dnsmessage_test.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::datasrc;
+using namespace isc::auth;
+using namespace isc::testutils;
+
+namespace {
+
+// This is the content of the mock zone (see below).
+// It's a sequence of textual RRs that is supposed to be parsed by
+// dns::masterLoad(). Some of the RRs are also used as the expected
+// data in specific tests, in which case they are referenced via specific
+// local variables (such as soa_txt).
+const char* const soa_txt = "example.com. 3600 IN SOA . . 0 0 0 0 0\n";
+const char* const zone_ns_txt =
+ "example.com. 3600 IN NS glue.delegation.example.com.\n"
+ "example.com. 3600 IN NS noglue.example.com.\n"
+ "example.com. 3600 IN NS example.net.\n";
+const char* const ns_addrs_txt =
+ "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
+ "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n"
+ "noglue.example.com. 3600 IN A 192.0.2.53\n";
+const char* const delegation_txt =
+ "delegation.example.com. 3600 IN NS glue.delegation.example.com.\n"
+ "delegation.example.com. 3600 IN NS noglue.example.com.\n"
+ "delegation.example.com. 3600 IN NS cname.example.com.\n"
+ "delegation.example.com. 3600 IN NS example.org.\n";
+const char* const mx_txt =
+ "mx.example.com. 3600 IN MX 10 www.example.com.\n"
+ "mx.example.com. 3600 IN MX 20 mailer.example.org.\n"
+ "mx.example.com. 3600 IN MX 30 mx.delegation.example.com.\n";
+const char* const www_a_txt = "www.example.com. 3600 IN A 192.0.2.80\n";
+const char* const cname_txt =
+ "cname.example.com. 3600 IN CNAME www.example.com.\n";
+const char* const cname_nxdom_txt =
+ "cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.\n";
+// CNAME Leading out of zone
+const char* const cname_out_txt =
+ "cnameout.example.com. 3600 IN CNAME www.example.org.\n";
+// The DNAME to do tests against
+const char* const dname_txt =
+ "dname.example.com. 3600 IN DNAME "
+ "somethinglong.dnametarget.example.com.\n";
+// Some data at the dname node (allowed by RFC 2672)
+const char* const dname_a_txt =
+ "dname.example.com. 3600 IN A 192.0.2.5\n";
+// This is not inside the zone, this is created at runtime
+const char* const synthetized_cname_txt =
+ "www.dname.example.com. 3600 IN CNAME "
+ "www.somethinglong.dnametarget.example.com.\n";
+// The rest of data won't be referenced from the test cases.
+const char* const other_zone_rrs =
+ "cnamemailer.example.com. 3600 IN CNAME www.example.com.\n"
+ "cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.\n"
+ "mx.delegation.example.com. 3600 IN A 192.0.2.100\n";
+
+// This is a mock Zone class for testing.
+// It is a derived class of Zone for the convenient of tests.
+// Its find() method emulates the common behavior of protocol compliant
+// zone classes, but simplifies some minor cases and also supports broken
+// behavior.
+// For simplicity, most names are assumed to be "in zone"; there's only
+// one zone cut at the point of name "delegation.example.com".
+// Another special name is "dname.example.com". Query names under this name
+// will result in DNAME.
+// This mock zone doesn't handle empty non terminal nodes (if we need to test
+// such cases find() should have specialized code for it).
+class MockZone : public Zone {
+public:
+ MockZone() :
+ origin_(Name("example.com")),
+ delegation_name_("delegation.example.com"),
+ dname_name_("dname.example.com"),
+ has_SOA_(true),
+ has_apex_NS_(true),
+ rrclass_(RRClass::IN())
+ {
+ stringstream zone_stream;
+ zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
+ delegation_txt << mx_txt << www_a_txt << cname_txt <<
+ cname_nxdom_txt << cname_out_txt << dname_txt << dname_a_txt <<
+ other_zone_rrs;
+
+ masterLoad(zone_stream, origin_, rrclass_,
+ boost::bind(&MockZone::loadRRset, this, _1));
+ }
+ virtual const isc::dns::Name& getOrigin() const { return (origin_); }
+ virtual const isc::dns::RRClass& getClass() const { return (rrclass_); }
+ virtual FindResult find(const isc::dns::Name& name,
+ const isc::dns::RRType& type,
+ RRsetList* target = NULL,
+ const FindOptions options = FIND_DEFAULT) const;
+
+ // If false is passed, it makes the zone broken as if it didn't have the
+ // SOA.
+ void setSOAFlag(bool on) { has_SOA_ = on; }
+
+ // If false is passed, it makes the zone broken as if it didn't have
+ // the apex NS.
+ void setApexNSFlag(bool on) { has_apex_NS_ = on; }
+
+private:
+ typedef map<RRType, ConstRRsetPtr> RRsetStore;
+ typedef map<Name, RRsetStore> Domains;
+ Domains domains_;
+ void loadRRset(ConstRRsetPtr rrset) {
+ domains_[rrset->getName()][rrset->getType()] = rrset;
+ if (rrset->getName() == delegation_name_ &&
+ rrset->getType() == RRType::NS()) {
+ delegation_rrset_ = rrset;
+ } else if (rrset->getName() == dname_name_ &&
+ rrset->getType() == RRType::DNAME()) {
+ dname_rrset_ = rrset;
+ }
+ }
+
+ const Name origin_;
+ // Names where we delegate somewhere else
+ const Name delegation_name_;
+ const Name dname_name_;
+ bool has_SOA_;
+ bool has_apex_NS_;
+ ConstRRsetPtr delegation_rrset_;
+ ConstRRsetPtr dname_rrset_;
+ const RRClass rrclass_;
+};
+
+Zone::FindResult
+MockZone::find(const Name& name, const RRType& type,
+ RRsetList* target, const FindOptions options) const
+{
+ // Emulating a broken zone: mandatory apex RRs are missing if specifically
+ // configured so (which are rare cases).
+ if (name == origin_ && type == RRType::SOA() && !has_SOA_) {
+ return (FindResult(NXDOMAIN, RRsetPtr()));
+ } else if (name == origin_ && type == RRType::NS() && !has_apex_NS_) {
+ return (FindResult(NXDOMAIN, RRsetPtr()));
+ }
+
+ // Special case for names on or under a zone cut
+ if ((options & FIND_GLUE_OK) == 0 &&
+ (name == delegation_name_ ||
+ name.compare(delegation_name_).getRelation() ==
+ NameComparisonResult::SUBDOMAIN)) {
+ return (FindResult(DELEGATION, delegation_rrset_));
+ // And under DNAME
+ } else if (name.compare(dname_name_).getRelation() ==
+ NameComparisonResult::SUBDOMAIN) {
+ return (FindResult(DNAME, dname_rrset_));
+ }
+
+ // normal cases. names are searched for only per exact-match basis
+ // for simplicity.
+ const Domains::const_iterator found_domain = domains_.find(name);
+ if (found_domain != domains_.end()) {
+ // First, try exact match.
+ RRsetStore::const_iterator found_rrset =
+ found_domain->second.find(type);
+ if (found_rrset != found_domain->second.end()) {
+ return (FindResult(SUCCESS, found_rrset->second));
+ }
+
+ // If not found but we have a target, fill it with all RRsets here
+ if (!found_domain->second.empty() && target != NULL) {
+ for (found_rrset = found_domain->second.begin();
+ found_rrset != found_domain->second.end(); ++found_rrset) {
+ // Insert RRs under the domain name into target
+ target->addRRset(
+ boost::const_pointer_cast<RRset>(found_rrset->second));
+ }
+ return (FindResult(SUCCESS, found_domain->second.begin()->second));
+ }
+
+ // Otherwise, if this domain name has CNAME, return it.
+ found_rrset = found_domain->second.find(RRType::CNAME());
+ if (found_rrset != found_domain->second.end()) {
+ return (FindResult(CNAME, found_rrset->second));
+ }
+
+ // Otherwise it's NXRRSET case.
+ return (FindResult(NXRRSET, RRsetPtr()));
+ }
+
+ // query name isn't found in our domains. returns NXDOMAIN.
+ return (FindResult(NXDOMAIN, RRsetPtr()));
+}
+
+class QueryTest : public ::testing::Test {
+protected:
+ QueryTest() :
+ qname(Name("www.example.com")), qclass(RRClass::IN()),
+ qtype(RRType::A()), response(Message::RENDER),
+ qid(response.getQid()), query_code(Opcode::QUERY().getCode())
+ {
+ response.setRcode(Rcode::NOERROR());
+ response.setOpcode(Opcode::QUERY());
+ // create and add a matching zone.
+ mock_zone = new MockZone();
+ memory_datasrc.addZone(ZonePtr(mock_zone));
+ }
+ MockZone* mock_zone;
+ MemoryDataSrc memory_datasrc;
+ const Name qname;
+ const RRClass qclass;
+ const RRType qtype;
+ Message response;
+ const qid_t qid;
+ const uint16_t query_code;
+};
+
+// A wrapper to check resulting response message commonly used in
+// tests below.
+// check_origin needs to be specified only when the authority section has
+// an SOA RR. The interface is not generic enough but should be okay
+// for our test cases in practice.
+void
+responseCheck(Message& response, const isc::dns::Rcode& rcode,
+ unsigned int flags, const unsigned int ancount,
+ const unsigned int nscount, const unsigned int arcount,
+ const char* const expected_answer,
+ const char* const expected_authority,
+ const char* const expected_additional,
+ const Name& check_origin = Name::ROOT_NAME())
+{
+ // In our test cases QID, Opcode, and QDCOUNT should be constant, so
+ // we don't bother the test cases specifying these values.
+ headerCheck(response, response.getQid(), rcode, Opcode::QUERY().getCode(),
+ flags, 0, ancount, nscount, arcount);
+ if (expected_answer != NULL) {
+ rrsetsCheck(expected_answer,
+ response.beginSection(Message::SECTION_ANSWER),
+ response.endSection(Message::SECTION_ANSWER),
+ check_origin);
+ }
+ if (expected_authority != NULL) {
+ rrsetsCheck(expected_authority,
+ response.beginSection(Message::SECTION_AUTHORITY),
+ response.endSection(Message::SECTION_AUTHORITY),
+ check_origin);
+ }
+ if (expected_additional != NULL) {
+ rrsetsCheck(expected_additional,
+ response.beginSection(Message::SECTION_ADDITIONAL),
+ response.endSection(Message::SECTION_ADDITIONAL));
+ }
+}
+
+TEST_F(QueryTest, noZone) {
+ // There's no zone in the memory datasource. So the response should have
+ // REFUSED.
+ MemoryDataSrc empty_memory_datasrc;
+ Query nozone_query(empty_memory_datasrc, qname, qtype, response);
+ EXPECT_NO_THROW(nozone_query.process());
+ EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
+}
+
+TEST_F(QueryTest, exactMatch) {
+ Query query(memory_datasrc, qname, qtype, response);
+ EXPECT_NO_THROW(query.process());
+ // find match rrset
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ www_a_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, exactAddrMatch) {
+ // find match rrset, omit additional data which has already been provided
+ // in the answer section from the additional.
+ EXPECT_NO_THROW(Query(memory_datasrc, Name("noglue.example.com"), qtype,
+ response).process());
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 2,
+ "noglue.example.com. 3600 IN A 192.0.2.53\n", zone_ns_txt,
+ "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
+ "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
+}
+
+TEST_F(QueryTest, apexNSMatch) {
+ // find match rrset, omit authority data which has already been provided
+ // in the answer section from the authority section.
+ EXPECT_NO_THROW(Query(memory_datasrc, Name("example.com"), RRType::NS(),
+ response).process());
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 0, 3,
+ zone_ns_txt, NULL, ns_addrs_txt);
+}
+
+// test type any query logic
+TEST_F(QueryTest, exactAnyMatch) {
+ // find match rrset, omit additional data which has already been provided
+ // in the answer section from the additional.
+ EXPECT_NO_THROW(Query(memory_datasrc, Name("noglue.example.com"),
+ RRType::ANY(), response).process());
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 2,
+ "noglue.example.com. 3600 IN A 192.0.2.53\n",
+ zone_ns_txt,
+ "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
+ "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
+}
+
+TEST_F(QueryTest, apexAnyMatch) {
+ // find match rrset, omit additional data which has already been provided
+ // 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, 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, 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) {
+ EXPECT_NO_THROW(Query(memory_datasrc, Name("delegation.example.com"),
+ RRType::ANY(), response).process());
+ responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
+ NULL, delegation_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, nodomainANY) {
+ EXPECT_NO_THROW(Query(memory_datasrc, Name("nxdomain.example.com"),
+ RRType::ANY(), response).process());
+ responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
+ NULL, soa_txt, NULL, mock_zone->getOrigin());
+}
+
+// This tests that when we need to look up Zone's apex NS records for
+// authoritative answer, and there is no apex NS records. It should
+// throw in that case.
+TEST_F(QueryTest, noApexNS) {
+ // Disable apex NS record
+ mock_zone->setApexNSFlag(false);
+
+ EXPECT_THROW(Query(memory_datasrc, Name("noglue.example.com"), qtype,
+ response).process(), Query::NoApexNS);
+ // We don't look into the response, as it threw
+}
+
+TEST_F(QueryTest, delegation) {
+ EXPECT_NO_THROW(Query(memory_datasrc, Name("delegation.example.com"),
+ qtype, response).process());
+
+ responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
+ NULL, delegation_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, nxdomain) {
+ EXPECT_NO_THROW(Query(memory_datasrc, Name("nxdomain.example.com"), qtype,
+ response).process());
+ responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
+ NULL, soa_txt, NULL, mock_zone->getOrigin());
+}
+
+TEST_F(QueryTest, nxrrset) {
+ EXPECT_NO_THROW(Query(memory_datasrc, Name("www.example.com"),
+ RRType::TXT(), response).process());
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
+ NULL, soa_txt, NULL, mock_zone->getOrigin());
+}
+
+/*
+ * This tests that when there's no SOA and we need a negative answer. It should
+ * throw in that case.
+ */
+TEST_F(QueryTest, noSOA) {
+ // disable zone's SOA RR.
+ mock_zone->setSOAFlag(false);
+
+ // The NX Domain
+ EXPECT_THROW(Query(memory_datasrc, Name("nxdomain.example.com"),
+ qtype, response).process(), Query::NoSOA);
+ // Of course, we don't look into the response, as it throwed
+
+ // NXRRSET
+ EXPECT_THROW(Query(memory_datasrc, Name("nxrrset.example.com"),
+ qtype, response).process(), Query::NoSOA);
+}
+
+TEST_F(QueryTest, noMatchZone) {
+ // there's a zone in the memory datasource but it doesn't match the qname.
+ // should result in REFUSED.
+ Query(memory_datasrc, Name("example.org"), qtype, response).process();
+ EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
+}
+
+/*
+ * Test MX additional processing.
+ *
+ * The MX RRset has two RRs, one pointing to a known domain with
+ * A record, other to unknown out of zone one.
+ */
+TEST_F(QueryTest, MX) {
+ Query(memory_datasrc, Name("mx.example.com"), RRType::MX(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 3, 4,
+ mx_txt, NULL,
+ (string(ns_addrs_txt) + string(www_a_txt)).c_str());
+}
+
+/*
+ * Test when we ask for MX whose exchange is an alias (CNAME in this case).
+ *
+ * This should not trigger the additional processing for the exchange.
+ */
+TEST_F(QueryTest, MXAlias) {
+ Query(memory_datasrc, Name("cnamemx.example.com"), RRType::MX(),
+ response).process();
+
+ // there shouldn't be no additional RRs for the exchanges (we have 3
+ // RRs for the NS). The normal MX case is tested separately so we don't
+ // bother to examine the answer (and authority) sections.
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ NULL, NULL, ns_addrs_txt);
+}
+
+/*
+ * Tests encountering a cname.
+ *
+ * There are tests leading to successful answers, NXRRSET, NXDOMAIN and
+ * out of the zone.
+ *
+ * TODO: We currently don't do chaining, so only the CNAME itself should be
+ * returned.
+ */
+TEST_F(QueryTest, CNAME) {
+ Query(memory_datasrc, Name("cname.example.com"), RRType::A(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+ cname_txt, NULL, NULL);
+}
+
+TEST_F(QueryTest, explicitCNAME) {
+ // same owner name as the CNAME test but explicitly query for CNAME RR.
+ // expect the same response as we don't provide a full chain yet.
+ Query(memory_datasrc, Name("cname.example.com"), RRType::CNAME(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ cname_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, CNAME_NX_RRSET) {
+ // Leads to www.example.com, it doesn't have TXT
+ // note: with chaining, what should be expected is not trivial:
+ // BIND 9 returns the CNAME in answer and SOA in authority, no additional.
+ // NSD returns the CNAME, NS in authority, A/AAAA for NS in additional.
+ Query(memory_datasrc, Name("cname.example.com"), RRType::TXT(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+ cname_txt, NULL, NULL);
+}
+
+TEST_F(QueryTest, explicitCNAME_NX_RRSET) {
+ // same owner name as the NXRRSET test but explicitly query for CNAME RR.
+ Query(memory_datasrc, Name("cname.example.com"), RRType::CNAME(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ cname_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, CNAME_NX_DOMAIN) {
+ // Leads to nxdomain.example.com
+ // note: with chaining, what should be expected is not trivial:
+ // BIND 9 returns the CNAME in answer and SOA in authority, no additional,
+ // RCODE being NXDOMAIN.
+ // NSD returns the CNAME, NS in authority, A/AAAA for NS in additional,
+ // RCODE being NOERROR.
+ Query(memory_datasrc, Name("cnamenxdom.example.com"), RRType::A(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+ cname_nxdom_txt, NULL, NULL);
+}
+
+TEST_F(QueryTest, explicitCNAME_NX_DOMAIN) {
+ // same owner name as the NXDOMAIN test but explicitly query for CNAME RR.
+ Query(memory_datasrc, Name("cnamenxdom.example.com"), RRType::CNAME(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ cname_nxdom_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, CNAME_OUT) {
+ /*
+ * This leads out of zone. This should have only the CNAME even
+ * when we do chaining.
+ *
+ * TODO: We should be able to have two zones in the mock data source.
+ * Then the same test should be done with .org included there and
+ * see what it does (depends on what we want to do)
+ */
+ Query(memory_datasrc, Name("cnameout.example.com"), RRType::A(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+ cname_out_txt, NULL, NULL);
+}
+
+TEST_F(QueryTest, explicitCNAME_OUT) {
+ // same owner name as the OUT test but explicitly query for CNAME RR.
+ Query(memory_datasrc, Name("cnameout.example.com"), RRType::CNAME(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ cname_out_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+/*
+ * Test a query under a domain with DNAME. We should get a synthetized CNAME
+ * as well as the DNAME.
+ *
+ * TODO: Once we have CNAME chaining, check it works with synthetized CNAMEs
+ * as well. This includes tests pointing inside the zone, outside the zone,
+ * pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
+ */
+TEST_F(QueryTest, DNAME) {
+ Query(memory_datasrc, Name("www.dname.example.com"), RRType::A(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
+ (string(dname_txt) + synthetized_cname_txt).c_str(),
+ NULL, NULL);
+}
+
+/*
+ * Ask an ANY query below a DNAME. Should return the DNAME and synthetized
+ * CNAME.
+ *
+ * ANY is handled specially sometimes. We check it is not the case with
+ * DNAME.
+ */
+TEST_F(QueryTest, DNAME_ANY) {
+ Query(memory_datasrc, Name("www.dname.example.com"), RRType::ANY(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
+ (string(dname_txt) + synthetized_cname_txt).c_str(), NULL, NULL);
+}
+
+// Test when we ask for DNAME explicitly, it does no synthetizing.
+TEST_F(QueryTest, explicitDNAME) {
+ Query(memory_datasrc, Name("dname.example.com"), RRType::DNAME(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ dname_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+/*
+ * Request a RRset at the domain with DNAME. It should not synthetize
+ * the CNAME, it should return the RRset.
+ */
+TEST_F(QueryTest, DNAME_A) {
+ Query(memory_datasrc, Name("dname.example.com"), RRType::A(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ dname_a_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+/*
+ * Request a RRset at the domain with DNAME that is not there (NXRRSET).
+ * It should not synthetize the CNAME.
+ */
+TEST_F(QueryTest, DNAME_NX_RRSET) {
+ EXPECT_NO_THROW(Query(memory_datasrc, Name("dname.example.com"),
+ RRType::TXT(), response).process());
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
+ NULL, soa_txt, NULL, mock_zone->getOrigin());
+}
+
+/*
+ * Constructing the CNAME will result in a name that is too long. This,
+ * however, should not throw (and crash the server), but respond with
+ * YXDOMAIN.
+ */
+TEST_F(QueryTest, LongDNAME) {
+ // A name that is as long as it can be
+ Name longname(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "dname.example.com.");
+ EXPECT_NO_THROW(Query(memory_datasrc, longname, RRType::A(),
+ response).process());
+
+ responseCheck(response, Rcode::YXDOMAIN(), AA_FLAG, 1, 0, 0,
+ dname_txt, NULL, NULL);
+}
+
+/*
+ * Constructing the CNAME will result in a name of maximal length.
+ * This tests that we don't reject valid one by some kind of off by
+ * one mistake.
+ */
+TEST_F(QueryTest, MaxLenDNAME) {
+ Name longname(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "dname.example.com.");
+ EXPECT_NO_THROW(Query(memory_datasrc, longname, RRType::A(),
+ response).process());
+
+ // Check the answer is OK
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
+ NULL, NULL, NULL);
+
+ // Check that the CNAME has the maximal length.
+ bool ok(false);
+ for (RRsetIterator i(response.beginSection(Message::SECTION_ANSWER));
+ i != response.endSection(Message::SECTION_ANSWER); ++ i) {
+ if ((*i)->getType() == RRType::CNAME()) {
+ ok = true;
+ RdataIteratorPtr ci((*i)->getRdataIterator());
+ ASSERT_FALSE(ci->isLast()) << "The CNAME is empty";
+ /*
+ * Does anybody have a clue why, if the Name::MAX_WIRE is put
+ * directly inside ASSERT_EQ, it fails to link and complains
+ * it is unresolved external?
+ */
+ const size_t max_len(Name::MAX_WIRE);
+ ASSERT_EQ(max_len, dynamic_cast<const rdata::generic::CNAME&>(
+ ci->getCurrent()).getCname().getLength());
+ }
+ }
+ EXPECT_TRUE(ok) << "The synthetized CNAME not found";
+}
+
+}
diff --git a/src/bin/auth/tests/run_unittests.cc b/src/bin/auth/tests/run_unittests.cc
index 4bc529c..6ae848d 100644
--- a/src/bin/auth/tests/run_unittests.cc
+++ b/src/bin/auth/tests/run_unittests.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <gtest/gtest.h>
#include <dns/tests/unittest_util.h>
diff --git a/src/bin/auth/tests/statistics_unittest.cc b/src/bin/auth/tests/statistics_unittest.cc
new file mode 100644
index 0000000..062b70d
--- /dev/null
+++ b/src/bin/auth/tests/statistics_unittest.cc
@@ -0,0 +1,213 @@
+// 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 <config.h>
+
+#include <gtest/gtest.h>
+
+#include <cc/data.h>
+#include <cc/session.h>
+
+#include <auth/statistics.h>
+
+#include <dns/tests/unittest_util.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::cc;
+using namespace isc::dns;
+using namespace isc::data;
+
+namespace {
+
+class AuthCountersTest : public ::testing::Test {
+private:
+ class MockSession : public AbstractSession {
+ public:
+ MockSession() :
+ // by default we return a simple "success" message.
+ msg_(Element::fromJSON("{\"result\": [0, \"SUCCESS\"]}")),
+ throw_session_error_(false), throw_session_timeout_(false)
+ {}
+ virtual void establish(const char* socket_file);
+ virtual void disconnect();
+ virtual int group_sendmsg(ConstElementPtr msg, string group,
+ string instance, string to);
+ virtual bool group_recvmsg(ConstElementPtr& envelope,
+ ConstElementPtr& msg,
+ bool nonblock, int seq);
+ virtual void subscribe(string group, string instance);
+ virtual void unsubscribe(string group, string instance);
+ virtual void startRead(boost::function<void()> read_callback);
+ virtual int reply(ConstElementPtr envelope, ConstElementPtr newmsg);
+ virtual bool hasQueuedMsgs() const;
+ virtual void setTimeout(size_t) {}
+ virtual size_t getTimeout() const { return (0); };
+
+ void setThrowSessionError(bool flag);
+ void setThrowSessionTimeout(bool flag);
+
+ void setMessage(ConstElementPtr msg) { msg_ = msg; }
+
+ ConstElementPtr sent_msg;
+ string msg_destination;
+ private:
+ ConstElementPtr msg_;
+ bool throw_session_error_;
+ bool throw_session_timeout_;
+ };
+
+protected:
+ AuthCountersTest() : verbose_mode_(false), counters(verbose_mode_) {
+ counters.setStatisticsSession(&statistics_session_);
+ }
+ ~AuthCountersTest() {
+ }
+ MockSession statistics_session_;
+ bool verbose_mode_;
+ AuthCounters counters;
+};
+
+void
+AuthCountersTest::MockSession::establish(const char*) {}
+
+void
+AuthCountersTest::MockSession::disconnect() {}
+
+void
+AuthCountersTest::MockSession::subscribe(string, string)
+{}
+
+void
+AuthCountersTest::MockSession::unsubscribe(string, string)
+{}
+
+void
+AuthCountersTest::MockSession::startRead(boost::function<void()>)
+{}
+
+int
+AuthCountersTest::MockSession::reply(ConstElementPtr, ConstElementPtr) {
+ return (-1);
+}
+
+bool
+AuthCountersTest::MockSession::hasQueuedMsgs() const {
+ return (false);
+}
+
+int
+AuthCountersTest::MockSession::group_sendmsg(ConstElementPtr msg,
+ string group, string, string)
+{
+ if (throw_session_error_) {
+ isc_throw(SessionError, "Session Error");
+ }
+ sent_msg = msg;
+ msg_destination = group;
+ return (0);
+}
+
+bool
+AuthCountersTest::MockSession::group_recvmsg(ConstElementPtr&,
+ ConstElementPtr& msg, bool, int)
+{
+ if (throw_session_timeout_) {
+ isc_throw(SessionTimeout, "Session Timeout");
+ }
+ msg = msg_;
+ return (true);
+}
+
+void
+AuthCountersTest::MockSession::setThrowSessionError(bool flag) {
+ throw_session_error_ = flag;
+}
+
+void
+AuthCountersTest::MockSession::setThrowSessionTimeout(bool flag) {
+ throw_session_timeout_ = flag;
+}
+
+TEST_F(AuthCountersTest, incrementUDPCounter) {
+ // The counter should be initialized to 0.
+ EXPECT_EQ(0, counters.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+ EXPECT_NO_THROW(counters.inc(AuthCounters::COUNTER_UDP_QUERY));
+ // After increment, the counter should be 1.
+ EXPECT_EQ(1, counters.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+}
+
+TEST_F(AuthCountersTest, incrementTCPCounter) {
+ // The counter should be initialized to 0.
+ EXPECT_EQ(0, counters.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+ EXPECT_NO_THROW(counters.inc(AuthCounters::COUNTER_TCP_QUERY));
+ // After increment, the counter should be 1.
+ EXPECT_EQ(1, counters.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+}
+
+TEST_F(AuthCountersTest, incrementInvalidCounter) {
+ // Expect to throw isc::InvalidParameter if the type of the counter is
+ // invalid.
+ EXPECT_THROW(counters.inc(AuthCounters::COUNTER_TYPES),
+ std::out_of_range);
+}
+
+TEST_F(AuthCountersTest, submitStatisticsWithoutSession) {
+ // Set statistics_session to NULL and call submitStatistics().
+ // Expect to return false.
+ counters.setStatisticsSession(NULL);
+ EXPECT_FALSE(counters.submitStatistics());
+}
+
+TEST_F(AuthCountersTest, submitStatisticsWithException) {
+ // Exception SessionError and SessionTimeout will be thrown
+ // while sending statistics data.
+ // Both expect to return false.
+ statistics_session_.setThrowSessionError(true);
+ EXPECT_FALSE(counters.submitStatistics());
+ statistics_session_.setThrowSessionError(false);
+ statistics_session_.setThrowSessionTimeout(true);
+ EXPECT_FALSE(counters.submitStatistics());
+ statistics_session_.setThrowSessionTimeout(false);
+}
+
+TEST_F(AuthCountersTest, submitStatistics) {
+ // Submit statistics data.
+ // Validate if it submits correct data.
+
+ // Counters should be initialized to 0.
+ EXPECT_EQ(0, counters.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+ EXPECT_EQ(0, counters.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+
+ // UDP query counter is set to 2.
+ counters.inc(AuthCounters::COUNTER_UDP_QUERY);
+ counters.inc(AuthCounters::COUNTER_UDP_QUERY);
+ // TCP query counter is set to 1.
+ counters.inc(AuthCounters::COUNTER_TCP_QUERY);
+ counters.submitStatistics();
+
+ // Destination is "Stats".
+ EXPECT_EQ("Stats", statistics_session_.msg_destination);
+ // Command is "set".
+ EXPECT_EQ("set", statistics_session_.sent_msg->get("command")
+ ->get(0)->stringValue());
+ ConstElementPtr statistics_data = statistics_session_.sent_msg
+ ->get("command")->get(1)
+ ->get("stats_data");
+ // UDP query counter is 2 and TCP query counter is 1.
+ EXPECT_EQ(2, statistics_data->get("auth.queries.udp")->intValue());
+ EXPECT_EQ(1, statistics_data->get("auth.queries.tcp")->intValue());
+}
+
+}
diff --git a/src/bin/auth/tests/testdata/queryBadEDNS_fromWire.spec b/src/bin/auth/tests/testdata/queryBadEDNS_fromWire.spec
index 4ffb29f..a648d47 100644
--- a/src/bin/auth/tests/testdata/queryBadEDNS_fromWire.spec
+++ b/src/bin/auth/tests/testdata/queryBadEDNS_fromWire.spec
@@ -1,5 +1,5 @@
#
-# A QUERY message with unsupported version of EDNS..
+# A QUERY message with unsupported version of EDNS.
#
[header]
diff --git a/src/bin/bind10/Makefile.am b/src/bin/bind10/Makefile.am
index 1445b95..254875f 100644
--- a/src/bin/bind10/Makefile.am
+++ b/src/bin/bind10/Makefile.am
@@ -5,7 +5,7 @@ CLEANFILES = bind10 bind10.pyc
pkglibexecdir = $(libexecdir)/@PACKAGE@
-bind10dir = $(DESTDIR)$(pkgdatadir)
+bind10dir = $(pkgdatadir)
bind10_DATA = bob.spec
EXTRA_DIST = bob.spec
@@ -19,7 +19,6 @@ bind10.8: bind10.xml
endif
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
bind10: bind10.py
$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
diff --git a/src/bin/bind10/bind10.8 b/src/bin/bind10/bind10.8
index 09963a6..b275f2d 100644
--- a/src/bin/bind10/bind10.8
+++ b/src/bin/bind10/bind10.8
@@ -1,22 +1,13 @@
'\" t
.\" Title: bind10
.\" Author: [see the "AUTHORS" section]
-.\" Generator: DocBook XSL Stylesheets v1.76.0 <http://docbook.sf.net/>
-.\" Date: July 29, 2010
+.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
+.\" Date: February 22, 2011
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "BIND10" "8" "July 29, 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 '
+.TH "BIND10" "8" "February 22, 2011" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -31,7 +22,7 @@
bind10 \- BIND 10 boss process
.SH "SYNOPSIS"
.HP \w'\fBbind10\fR\ 'u
-\fBbind10\fR [\fB\-a\ \fR\fB\fIaddress\fR\fR] [\fB\-m\ \fR\fB\fIfile\fR\fR] [\fB\-n\fR] [\fB\-p\ \fR\fB\fInumber\fR\fR] [\fB\-u\ \fR\fB\fIuser\fR\fR] [\fB\-v\fR] [\fB\-\-address\ \fR\fB\fIaddress\fR\fR] [\fB\-\-msgq\-socket\-file\ \fR\fB\fIfile\fR\fR] [\fB\-\-no\-cache\fR] [\fB\-\-port\ \fR\fB\fInumber\fR\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
@@ -41,13 +32,6 @@ daemon starts up other BIND 10 required daemons\&. It handles restarting of exit
.PP
The arguments are as follows:
.PP
-\fB\-a\fR \fIaddress\fR, \fB\-\-address\fR \fIaddress\fR
-.RS 4
-The IPv4 or IPv6 address for the
-\fBb10-auth\fR(8)
-daemon to listen on\&. The default is to listen on all addresses\&. (This is a short term workaround\&. This argument may change\&.)
-.RE
-.PP
\fB\-m\fR \fIfile\fR, \fB\-\-msgq\-socket\-file\fR \fIfile\fR
.RS 4
The UNIX domain socket file for the
@@ -63,28 +47,6 @@ Disables the hot\-spot caching used by the
daemon\&.
.RE
.PP
-\fB\-p\fR \fInumber\fR, \fB\-\-port\fR \fInumber\fR
-.RS 4
-The port number for the
-\fBb10-auth\fR(8)
-daemon to listen on\&. The default is 5300\&.
-.if n \{\
-.sp
-.\}
-.RS 4
-.it 1 an-trap
-.nr an-no-space-flag 1
-.nr an-break-flag 1
-.br
-.ps +1
-\fBNote\fR
-.ps -1
-.br
-The Y1 prototype release uses a non\-default port for domain service\&.
-.sp .5v
-.RE
-.RE
-.PP
\fB\-u\fR \fIuser\fR, \fB\-\-user\fR \fIname\fR
.RS 4
The username for
@@ -104,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
@@ -134,5 +104,5 @@ The
daemon was initially designed by Shane Kerr of ISC\&.
.SH "COPYRIGHT"
.br
-Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")
+Copyright \(co 2011 Internet Systems Consortium, Inc. ("ISC")
.br
diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in
old mode 100644
new mode 100755
index b37b87b..9224ffa
--- 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
@@ -15,7 +15,7 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-"""\
+"""
This file implements the Boss of Bind (BoB, or bob) program.
Its purpose is to start up the BIND 10 system, and then manage the
@@ -72,7 +72,7 @@ isc.util.process.rename(sys.argv[0])
# This is the version that gets displayed to the user.
# The VERSION string consists of the module name, the module version
# number, and the overall BIND 10 version number (set in configure.ac).
-VERSION = "bind10 20100916 (BIND 10 @PACKAGE_VERSION@)"
+VERSION = "bind10 20110223 (BIND 10 @PACKAGE_VERSION@)"
# This is for bind10.boottime of stats module
_BASETIME = time.gmtime()
@@ -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,291 +187,484 @@ 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()
+class CChannelConnectError(Exception): pass
+
class BoB:
"""Boss of BIND class."""
- def __init__(self, msgq_socket_file=None, auth_port=5300, address=None,
- nocache=False, verbose=False, setuid=None, username=None):
- """Initialize the Boss of BIND. This is a singleton (only one
- can run).
+ 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.
+ 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.verbose = verbose
- self.msgq_socket_file = msgq_socket_file
- self.auth_port = auth_port
- self.address = None
- if address:
- self.address = address
self.cc_session = None
self.ccs = None
- self.processes = {}
+ self.cfg_start_auth = True
+ self.cfg_start_resolver = False
+ self.started_auth_family = False
+ self.started_resolver_family = False
+ self.curproc = None
self.dead_processes = {}
+ self.msgq_socket_file = msgq_socket_file
+ self.nocache = nocache
+ self.processes = {}
+ self.expected_shutdowns = {}
self.runnable = False
self.uid = setuid
self.username = username
- self.nocache = nocache
+ 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
+ if not self.runnable:
+ return
+ # Now we declare few functions used only internally here. Besides the
+ # benefit of not polluting the name space, they are closures, so we
+ # don't need to pass some variables
+ def start_stop(name, started, start, stop):
+ if not'start_' + name in new_config:
+ return
+ if new_config['start_' + name]:
+ if not started:
+ if self.uid is not None:
+ sys.stderr.write("[bind10] Starting " + name + " as " +
+ "a user, not root. This might fail.\n")
+ start()
+ else:
+ stop()
+ # These four functions are passed to start_stop (smells like functional
+ # programming little bit)
+ def resolver_on():
+ self.start_resolver(self.c_channel_env)
+ self.started_resolver_family = True
+ def resolver_off():
+ self.stop_resolver()
+ self.started_resolver_family = False
+ def auth_on():
+ self.start_auth(self.c_channel_env)
+ self.start_xfrout(self.c_channel_env)
+ self.start_xfrin(self.c_channel_env)
+ self.start_zonemgr(self.c_channel_env)
+ self.started_auth_family = True
+ def auth_off():
+ self.stop_zonemgr()
+ self.stop_xfrin()
+ self.stop_xfrout()
+ self.stop_auth()
+ self.started_auth_family = False
+
+ # The real code of the config handler function follows here
if self.verbose:
- sys.stdout.write("[bind10] handling new config:\n")
- sys.stdout.write(new_config + "\n")
+ sys.stdout.write("[bind10] Handling new configuration: " +
+ str(new_config) + "\n")
+ start_stop('resolver', self.started_resolver_family, resolver_on,
+ resolver_off)
+ start_stop('auth', self.started_auth_family, auth_on, auth_off)
+
answer = isc.config.ccsession.create_answer(0)
return answer
- # TODO
+
+ 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:\n")
- sys.stdout.write(command + "\n")
+ sys.stdout.write("[bind10] Boss got command: " + 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")
else:
- cmd = command
- if cmd == "shutdown":
- sys.stdout.write("[bind10] got shutdown command\n")
+ if command == "shutdown":
self.runnable = False
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
-
- def startup(self):
- """Start the BoB instance.
-
- Returns None if successful, otherwise an string describing the
- problem.
+
+ def kill_started_processes(self):
+ """
+ Called as part of the exception handling when a process fails to
+ start, this runs through the list of started processes, killing
+ each one. It then clears that list.
"""
- # try to connect to the c-channel daemon,
- # to see if it is already running
- c_channel_env = {}
- if self.msgq_socket_file is not None:
- c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file
if self.verbose:
- sys.stdout.write("[bind10] Checking for already running b10-msgq\n")
- # try to connect, and if we can't wait a short while
- try:
- self.cc_session = isc.cc.Session(self.msgq_socket_file)
- return "b10-msgq already running, or socket file not cleaned , cannot start"
- except isc.cc.session.SessionError:
- # this is the case we want, where the msgq is not running
- pass
+ sys.stdout.write("[bind10] killing started processes:\n")
+
+ for pid in self.processes:
+ if self.verbose:
+ sys.stdout.write("[bind10] - %s\n" % self.processes[pid].name)
+ self.processes[pid].process.kill()
+ self.processes = {}
- # start the c-channel daemon
+ def read_bind10_config(self):
+ """
+ Reads the parameters associated with the BoB module itself.
+
+ At present these are the components to start although arguably this
+ information should be in the configuration for the appropriate
+ module itself. (However, this would cause difficulty in the case of
+ xfrin/xfrout and zone manager as we don't need to start those if we
+ are not running the authoritative server.)
+ """
if self.verbose:
- if self.msgq_socket_file:
- sys.stdout.write("[bind10] Starting b10-msgq\n")
- try:
- c_channel = ProcessInfo("b10-msgq", ["b10-msgq"], c_channel_env,
- True, not self.verbose, uid=self.uid,
- username=self.username)
- except Exception as e:
- return "Unable to start b10-msgq; " + str(e)
- self.processes[c_channel.pid] = c_channel
+ sys.stdout.write("[bind10] Reading Boss configuration:\n")
+
+ config_data = self.ccs.get_full_config()
+ self.cfg_start_auth = config_data.get("start_auth")
+ self.cfg_start_resolver = config_data.get("start_resolver")
+
if self.verbose:
- sys.stdout.write("[bind10] Started b10-msgq (PID %d)\n" %
- c_channel.pid)
+ sys.stdout.write("[bind10] - start_auth: %s\n" %
+ str(self.cfg_start_auth))
+ sys.stdout.write("[bind10] - start_resolver: %s\n" %
+ str(self.cfg_start_resolver))
- # now connect to the c-channel
+ def log_starting(self, process, port = None, address = None):
+ """
+ A convenience function to output a "Starting xxx" message if the
+ verbose option is set. Putting this into a separate method ensures
+ that the output form is consistent across all processes.
+
+ The process name (passed as the first argument) is put into
+ self.curproc, and is used to indicate which process failed to
+ start if there is an error (and is used in the "Started" message
+ on success). The optional port and address information are
+ appended to the message (if present).
+ """
+ self.curproc = process
+ if self.verbose:
+ sys.stdout.write("[bind10] Starting %s" % self.curproc)
+ if port is not None:
+ sys.stdout.write(" on port %d" % port)
+ if address is not None:
+ sys.stdout.write(" (address %s)" % str(address))
+ sys.stdout.write("\n")
+
+ def log_started(self, pid = None):
+ """
+ A convenience function to output a 'Started xxxx (PID yyyy)'
+ message. As with starting_message(), this ensures a consistent
+ format.
+ """
+ if self.verbose:
+ sys.stdout.write("[bind10] Started %s" % self.curproc)
+ if pid is not None:
+ sys.stdout.write(" (PID %d)" % pid)
+ sys.stdout.write("\n")
+
+ # The next few methods start the individual processes of BIND-10. They
+ # are called via start_all_processes(). If any fail, an exception is
+ # raised which is caught by the caller of start_all_processes(); this kills
+ # processes started up to that point before terminating the program.
+
+ def start_msgq(self, c_channel_env):
+ """
+ Start the message queue and connect to the command channel.
+ """
+ self.log_starting("b10-msgq")
+ 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)
+
+ # Now connect to the c-channel
cc_connect_start = time.time()
while self.cc_session is None:
# if we have been trying for "a while" give up
if (time.time() - cc_connect_start) > 5:
- c_channel.process.kill()
- return "Unable to connect to c-channel after 5 seconds"
+ raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
+
# try to connect, and if we can't wait a short while
try:
self.cc_session = isc.cc.Session(self.msgq_socket_file)
except isc.cc.session.SessionError:
time.sleep(0.1)
- # start the configuration manager
- if self.verbose:
- sys.stdout.write("[bind10] Starting b10-cfgmgr\n")
- try:
- bind_cfgd = ProcessInfo("b10-cfgmgr", ["b10-cfgmgr"],
- c_channel_env, uid=self.uid,
- username=self.username)
- except Exception as e:
- c_channel.process.kill()
- return "Unable to start b10-cfgmgr; " + str(e)
+ def start_cfgmgr(self, c_channel_env):
+ """
+ Starts the configuration manager process
+ """
+ self.log_starting("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
- if self.verbose:
- sys.stdout.write("[bind10] Started b10-cfgmgr (PID %d)\n" %
- bind_cfgd.pid)
+ self.log_started(bind_cfgd.pid)
# sleep until b10-cfgmgr is fully up and running, this is a good place
# to have a (short) timeout on synchronized groupsend/receive
# TODO: replace the sleep by a listen for ConfigManager started
# message
time.sleep(1)
- if self.verbose:
- sys.stdout.write("[bind10] starting ccsession\n")
+
+ def start_ccsession(self, c_channel_env):
+ """
+ Start the CC Session
+
+ The argument c_channel_env is unused but is supplied to keep the
+ argument list the same for all start_xxx methods.
+ """
+ self.log_starting("ccsession")
self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
self.config_handler, self.command_handler)
self.ccs.start()
+ self.log_started()
+
+ # A couple of utility methods for starting processes...
+
+ def start_process(self, name, args, c_channel_env, port=None, address=None):
+ """
+ Given a set of command arguments, start the process and output
+ appropriate log messages. If the start is successful, the process
+ is added to the list of started processes.
+
+ The port and address arguments are for log messages only.
+ """
+ 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)
+
+ def start_simple(self, name, c_channel_env, port=None, address=None):
+ """
+ Most of the BIND-10 processes are started with the command:
+
+ <process-name> [-v]
+
+ ... where -v is appended if verbose is enabled. This method
+ generates the arguments from the name and starts the process.
+
+ The port and address arguments are for log messages only.
+ """
+ # Set up the command arguments.
+ args = [name]
if self.verbose:
- sys.stdout.write("[bind10] ccsession started\n")
+ args += ['-v']
- # start b10-auth
- # XXX: this must be read from the configuration manager in the future
- authargs = ['b10-auth', '-p', str(self.auth_port)]
- if self.address:
- authargs += ['-a', str(self.address)]
+ # ... and start the process
+ self.start_process(name, args, c_channel_env, port, address)
+
+ # The next few methods start up the rest of the BIND-10 processes.
+ # Although many of these methods are little more than a call to
+ # start_simple, they are retained (a) for testing reasons and (b) as a place
+ # where modifications can be made if the process start-up sequence changes
+ # for a given process.
+
+ def start_auth(self, c_channel_env):
+ """
+ Start the Authoritative server
+ """
+ authargs = ['b10-auth']
if self.nocache:
authargs += ['-n']
if self.uid:
authargs += ['-u', str(self.uid)]
if self.verbose:
authargs += ['-v']
- sys.stdout.write("Starting b10-auth using port %d" %
- self.auth_port)
- if self.address:
- sys.stdout.write(" on %s" % str(self.address))
- sys.stdout.write("\n")
- try:
- auth = ProcessInfo("b10-auth", authargs,
- c_channel_env)
- except Exception as e:
- c_channel.process.kill()
- bind_cfgd.process.kill()
- xfrout.process.kill()
- return "Unable to start b10-auth; " + str(e)
- self.processes[auth.pid] = auth
- if self.verbose:
- sys.stdout.write("[bind10] Started b10-auth (PID %d)\n" % auth.pid)
- # everything after the authoritative server can run as non-root
- if self.uid is not None:
- posix.setuid(self.uid)
+ # ... and start
+ self.start_process("b10-auth", authargs, c_channel_env)
- # start the xfrout before auth-server, to make sure every xfr-query can
- # be processed properly.
- xfrout_args = ['b10-xfrout']
- if self.verbose:
- sys.stdout.write("[bind10] Starting b10-xfrout\n")
- xfrout_args += ['-v']
- try:
- xfrout = ProcessInfo("b10-xfrout", xfrout_args,
- c_channel_env )
- except Exception as e:
- c_channel.process.kill()
- bind_cfgd.process.kill()
- return "Unable to start b10-xfrout; " + str(e)
- self.processes[xfrout.pid] = xfrout
+ def start_resolver(self, c_channel_env):
+ """
+ Start the Resolver. At present, all these arguments and switches
+ are pure speculation. As with the auth daemon, they should be
+ read from the configuration database.
+ """
+ self.curproc = "b10-resolver"
+ # XXX: this must be read from the configuration manager in the future
+ resargs = ['b10-resolver']
+ if self.uid:
+ resargs += ['-u', str(self.uid)]
if self.verbose:
- sys.stdout.write("[bind10] Started b10-xfrout (PID %d)\n" %
- xfrout.pid)
+ resargs += ['-v']
- # start b10-xfrin
- xfrin_args = ['b10-xfrin']
- if self.verbose:
- sys.stdout.write("[bind10] Starting b10-xfrin\n")
- xfrin_args += ['-v']
- try:
- xfrind = ProcessInfo("b10-xfrin", xfrin_args,
- c_channel_env)
- except Exception as e:
- c_channel.process.kill()
- bind_cfgd.process.kill()
- xfrout.process.kill()
- auth.process.kill()
- return "Unable to start b10-xfrin; " + str(e)
- self.processes[xfrind.pid] = xfrind
- if self.verbose:
- sys.stdout.write("[bind10] Started b10-xfrin (PID %d)\n" %
- xfrind.pid)
+ # ... and start
+ self.start_process("b10-resolver", resargs, c_channel_env)
- # start b10-zonemgr
- zonemgr_args = ['b10-zonemgr']
- if self.verbose:
- sys.stdout.write("[bind10] Starting b10-zonemgr\n")
- zonemgr_args += ['-v']
- try:
- zonemgr = ProcessInfo("b10-zonemgr", zonemgr_args,
- c_channel_env)
- except Exception as e:
- c_channel.process.kill()
- bind_cfgd.process.kill()
- xfrout.process.kill()
- auth.process.kill()
- xfrind.process.kill()
- return "Unable to start b10-zonemgr; " + str(e)
- self.processes[zonemgr.pid] = zonemgr
- if self.verbose:
- sys.stdout.write("[bind10] Started b10-zonemgr(PID %d)\n" %
- zonemgr.pid)
+ def start_xfrout(self, c_channel_env):
+ self.start_simple("b10-xfrout", c_channel_env)
+
+ def start_xfrin(self, c_channel_env):
+ self.start_simple("b10-xfrin", c_channel_env)
+
+ def start_zonemgr(self, c_channel_env):
+ self.start_simple("b10-zonemgr", c_channel_env)
- # start b10-stats
- stats_args = ['b10-stats']
+ def start_stats(self, c_channel_env):
+ self.start_simple("b10-stats", c_channel_env)
+
+ def start_cmdctl(self, c_channel_env):
+ """
+ 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):
+ """
+ Starts up all the processes. Any exception generated during the
+ starting of the processes is handled by the caller.
+ """
+ c_channel_env = self.c_channel_env
+ self.start_msgq(c_channel_env)
+ self.start_cfgmgr(c_channel_env)
+ self.start_ccsession(c_channel_env)
+
+ # Extract the parameters associated with Bob. This can only be
+ # done after the CC Session is started.
+ self.read_bind10_config()
+
+ # Continue starting the processes. The authoritative server (if
+ # selected):
+ if self.cfg_start_auth:
+ self.start_auth(c_channel_env)
+
+ # ... and resolver (if selected):
+ if self.cfg_start_resolver:
+ self.start_resolver(c_channel_env)
+ self.started_resolver_family = True
+
+ # Everything after the main components can run as non-root.
+ # TODO: this is only temporary - once the privileged socket creator is
+ # fully working, nothing else will run as root.
+ if self.uid is not None:
+ posix.setuid(self.uid)
+
+ # xfrin/xfrout and the zone manager are only meaningful if the
+ # authoritative server has been started.
+ if self.cfg_start_auth:
+ self.start_xfrout(c_channel_env)
+ self.start_xfrin(c_channel_env)
+ self.start_zonemgr(c_channel_env)
+ self.started_auth_family = True
+
+ # ... and finally start the remaining processes
+ self.start_stats(c_channel_env)
+ self.start_cmdctl(c_channel_env)
+
+ def startup(self):
+ """
+ Start the BoB instance.
+
+ Returns None if successful, otherwise an string describing the
+ problem.
+ """
+ # Try to connect to the c-channel daemon, to see if it is already
+ # running
+ c_channel_env = {}
+ if self.msgq_socket_file is not None:
+ c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file
if self.verbose:
- sys.stdout.write("[bind10] Starting b10-stats\n")
- stats_args += ['-v']
+ sys.stdout.write("[bind10] Checking for already running b10-msgq\n")
+ # try to connect, and if we can't wait a short while
try:
- statsd = ProcessInfo("b10-stats", stats_args,
- c_channel_env)
- except Exception as e:
- c_channel.process.kill()
- bind_cfgd.process.kill()
- xfrout.process.kill()
- auth.process.kill()
- xfrind.process.kill()
- zonemgr.process.kill()
- return "Unable to start b10-stats; " + str(e)
-
- self.processes[statsd.pid] = statsd
- if self.verbose:
- sys.stdout.write("[bind10] Started b10-stats (PID %d)\n" % statsd.pid)
+ self.cc_session = isc.cc.Session(self.msgq_socket_file)
+ return "b10-msgq already running, or socket file not cleaned , cannot start"
+ except isc.cc.session.SessionError:
+ # this is the case we want, where the msgq is not running
+ pass
- # start the b10-cmdctl
- # XXX: we hardcode port 8080
- cmdctl_args = ['b10-cmdctl']
- if self.verbose:
- sys.stdout.write("[bind10] Starting b10-cmdctl on port 8080\n")
- cmdctl_args += ['-v']
+ # Start all processes. If any one fails to start, kill all started
+ # processes and exit with an error indication.
try:
- cmd_ctrld = ProcessInfo("b10-cmdctl", cmdctl_args,
- c_channel_env)
+ self.c_channel_env = c_channel_env
+ self.start_all_processes()
except Exception as e:
- c_channel.process.kill()
- bind_cfgd.process.kill()
- xfrout.process.kill()
- auth.process.kill()
- xfrind.process.kill()
- zonemgr.process.kill()
- statsd.process.kill()
- return "Unable to start b10-cmdctl; " + str(e)
- self.processes[cmd_ctrld.pid] = cmd_ctrld
- if self.verbose:
- sys.stdout.write("[bind10] Started b10-cmdctl (PID %d)\n" %
- cmd_ctrld.pid)
+ self.kill_started_processes()
+ return "Unable to start " + self.curproc + ": " + str(e)
+ # Started successfully
self.runnable = True
-
return None
def stop_all_processes(self):
"""Stop all processes."""
cmd = { "command": ['shutdown']}
+
self.cc_session.group_sendmsg(cmd, 'Cmdctl', 'Cmdctl')
self.cc_session.group_sendmsg(cmd, "ConfigManager", "ConfigManager")
self.cc_session.group_sendmsg(cmd, "Auth", "Auth")
+ self.cc_session.group_sendmsg(cmd, "Resolver", "Resolver")
self.cc_session.group_sendmsg(cmd, "Xfrout", "Xfrout")
self.cc_session.group_sendmsg(cmd, "Xfrin", "Xfrin")
self.cc_session.group_sendmsg(cmd, "Zonemgr", "Zonemgr")
- self.cc_session.group_sendmsg(cmd, "Boss", "Stats")
+ self.cc_session.group_sendmsg(cmd, "Stats", "Stats")
+
+ def stop_process(self, process, recipient):
+ """
+ Stop the given process, friendly-like. The process is the name it has
+ (in logs, etc), the recipient is the address on msgq.
+ """
+ if self.verbose:
+ sys.stdout.write("[bind10] Asking %s to terminate\n" % process)
+ # TODO: Some timeout to solve processes that don't want to die would
+ # help. We can even store it in the dict, it is used only as a set
+ self.expected_shutdowns[process] = 1
+ # Ask the process to die willingly
+ self.cc_session.group_sendmsg({'command': ['shutdown']}, recipient,
+ recipient)
+
+ # Series of stop_process wrappers
+ def stop_resolver(self):
+ self.stop_process('b10-resolver', 'Resolver')
+
+ def stop_auth(self):
+ self.stop_process('b10-auth', 'Auth')
+
+ def stop_xfrout(self):
+ self.stop_process('b10-xfrout', 'Xfrout')
- def stop_process(self, process):
- """Stop the given process, friendly-like."""
- # XXX nothing yet
- pass
+ def stop_xfrin(self):
+ self.stop_process('b10-xfrin', 'Xfrin')
+
+ def stop_zonemgr(self):
+ self.stop_process('b10-zonemgr', 'Zonemgr')
def shutdown(self):
"""Stop the BoB instance."""
@@ -517,40 +711,64 @@ 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()
self.dead_processes[proc_info.pid] = proc_info
- if self.verbose:
- sys.stdout.write("[bind10] Process %s (PID %d) died.\n" %
- (proc_info.name, proc_info.pid))
- if proc_info.name == "b10-msgq":
- if self.verbose and self.runnable:
+
+ # Write out message, but only if in the running state:
+ # During startup and shutdown, these messages are handled
+ # elsewhere.
+ if self.runnable:
+ if exit_status is None:
+ sys.stdout.write(
+ "[bind10] Process %s (PID %d) died: exit status not available" %
+ (proc_info.name, proc_info.pid))
+ else:
+ sys.stdout.write(
+ "[bind10] Process %s (PID %d) terminated, exit status = %d\n" %
+ (proc_info.name, proc_info.pid, exit_status))
+
+ # Was it a special process?
+ if proc_info.name == "b10-msgq":
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)
def restart_processes(self):
- """Restart any dead processes.
- Returns the time when the next process is ready to be restarted.
- If the server is shutting down, returns 0.
- If there are no processes, returns None.
- The values returned can be safely passed into select() as the
- timeout value."""
+ """
+ Restart any dead processes:
+
+ * Returns the time when the next process is ready to be restarted.
+ * If the server is shutting down, returns 0.
+ * If there are no processes, returns None.
+
+ The values returned can be safely passed into select() as the
+ timeout value.
+ """
next_restart = None
# if we're shutting down, then don't restart
if not self.runnable:
@@ -559,6 +777,10 @@ class BoB:
still_dead = {}
now = time.time()
for proc_info in self.dead_processes.values():
+ if proc_info.name in self.expected_shutdowns:
+ # We don't restart, we wanted it to die
+ del self.expected_shutdowns[proc_info.name]
+ continue
restart_time = proc_info.restart_schedule.get_restart_time(now)
if restart_time > now:
if (next_restart is None) or (next_restart > restart_time):
@@ -567,13 +789,12 @@ class BoB:
else:
if self.verbose:
sys.stdout.write("[bind10] Resurrecting dead %s process...\n" %
- proc_info.name)
+ proc_info.name)
try:
proc_info.respawn()
self.processes[proc_info.pid] = proc_info
- if self.verbose:
- sys.stdout.write("[bind10] Resurrected %s (PID %d)\n" %
- (proc_info.name, proc_info.pid))
+ sys.stdout.write("[bind10] Resurrected %s (PID %d)\n" %
+ (proc_info.name, proc_info.pid))
except:
still_dead[proc_info.pid] = proc_info
# remember any processes that refuse to be resurrected
@@ -610,65 +831,94 @@ def fatal_signal(signal_number, stack_frame):
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
boss_of_bind.runnable = False
-def check_port(option, opt_str, value, parser):
- """Function to insure that the port we are passed is actually
- a valid port number. Used by OptionParser() on startup."""
- try:
- if opt_str in ['-p', '--port']:
- parser.values.auth_port = isc.net.parse.port_parse(value)
- else:
- raise OptionValueError("Unknown option " + opt_str)
- except ValueError as e:
- raise OptionValueError(str(e))
-
-def check_addr(option, opt_str, value, parser):
- """Function to insure that the address we are passed is actually
- a valid address. Used by OptionParser() on startup."""
- try:
- if opt_str in ['-a', '--address']:
- parser.values.address = isc.net.parse.addr_parse(value)
- else:
- raise OptionValueError("Unknown option " + opt_str)
- except ValueError:
- raise OptionValueError("%s requires a valid IPv4 or IPv6 address" % opt_str)
-
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 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("-a", "--address", dest="address", type="string",
- action="callback", callback=check_addr, default='',
- help="address the b10-auth daemon will use (default: listen on all addresses)")
+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 b10-auth")
- parser.add_option("-p", "--port", dest="auth_port", type="int",
- action="callback", callback=check_port, default=5300,
- help="port the b10-auth daemon will use (default 5300)")
- parser.add_option("-u", "--user", dest="user",
- type="string", default=None,
+ 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, ...)")
- (options, args) = parser.parse_args()
+ 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
+ file is None this function does nothing. If the file already exists,
+ the existing content will be removed. If a system error happens in
+ creating or writing to the file, the corresponding exception will be
+ propagated to the caller.
+ """
+ if pid_file is None:
+ return
+ f = open(pid_file, "w")
+ f.write('%d\n' % os.getpid())
+ f.close()
+
+def unlink_pid_file(pid_file):
+ """
+ Remove the given file, which is basically expected to be the PID file
+ created by dump_pid(). The specified may or may not exist; if it
+ doesn't this function does nothing. Other system level errors in removing
+ the file will be propagated as the corresponding exception.
+ """
+ if pid_file is None:
+ return
+ try:
+ os.unlink(pid_file)
+ except OSError as error:
+ 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)
+
+ options = parse_args()
+
# Check user ID.
setuid = None
username = None
@@ -702,10 +952,6 @@ def main():
if options.verbose:
sys.stdout.write("%s\n" % VERSION)
- # TODO: set process name, perhaps by:
- # http://code.google.com/p/procname/
- # http://github.com/lericson/procname/
-
# Create wakeup pipe for signal handlers
wakeup_pipe = os.pipe()
signal.set_wakeup_fd(wakeup_pipe[1])
@@ -721,14 +967,15 @@ def main():
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
# Go bob!
- boss_of_bind = BoB(options.msgq_socket_file, options.auth_port,
- options.address, 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)
sys.exit(1)
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
@@ -781,6 +1028,8 @@ def main():
# shutdown
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
boss_of_bind.shutdown()
+ sys.stdout.write("[bind10] BIND 10 exiting\n");
+ unlink_pid_file(options.pid_file)
sys.exit(0)
if __name__ == "__main__":
diff --git a/src/bin/bind10/bind10.xml b/src/bin/bind10/bind10.xml
index 25a8804..6331503 100644
--- a/src/bin/bind10/bind10.xml
+++ b/src/bin/bind10/bind10.xml
@@ -17,11 +17,10 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
-<!-- $Id$ -->
<refentry>
<refentryinfo>
- <date>July 29, 2010</date>
+ <date>February 22, 2011</date>
</refentryinfo>
<refmeta>
@@ -37,24 +36,22 @@
<docinfo>
<copyright>
- <year>2010</year>
+ <year>2011</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
<refsynopsisdiv>
<cmdsynopsis>
- <command>bind10</command>
- <arg><option>-a <replaceable>address</replaceable></option></arg>
+ <command>bind10</command>
<arg><option>-m <replaceable>file</replaceable></option></arg>
<arg><option>-n</option></arg>
- <arg><option>-p <replaceable>number</replaceable></option></arg>
<arg><option>-u <replaceable>user</replaceable></option></arg>
<arg><option>-v</option></arg>
- <arg><option>--address <replaceable>address</replaceable></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>--port <replaceable>number</replaceable></option></arg>
<arg><option>--user <replaceable>user</replaceable></option></arg>
<arg><option>--pretty-name <replaceable>name</replaceable></option></arg>
<arg><option>--verbose</option></arg>
@@ -85,18 +82,30 @@
<para>The arguments are as follows:</para>
<variablelist>
-
<varlistentry>
- <term><option>-a</option> <replaceable>address</replaceable>, <option>--address</option> <replaceable>address</replaceable></term>
+ <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 IPv4 or IPv6 address for the
- <citerefentry><refentrytitle>b10-auth</refentrytitle><manvolnum>8</manvolnum></citerefentry>
- daemon to listen on.
- The default is to listen on all addresses.
- (This is a short term workaround. This argument may change.)
- </para>
- </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>
@@ -124,20 +133,6 @@
</varlistentry>
<varlistentry>
- <term><option>-p</option> <replaceable>number</replaceable>, <option>--port</option> <replaceable>number</replaceable></term>
-
- <listitem>
- <para>The port number for the
- <citerefentry><refentrytitle>b10-auth</refentrytitle><manvolnum>8</manvolnum></citerefentry>
- daemon to listen on.
- The default is 5300.</para>
-<!-- TODO: -->
- <note><simpara>The Y1 prototype release uses a non-default
- port for domain service.</simpara></note>
- </listitem>
- </varlistentry>
-
- <varlistentry>
<term><option>-u</option> <replaceable>user</replaceable>, <option>--user</option> <replaceable>name</replaceable></term>
<listitem>
@@ -156,7 +151,11 @@
<para>The name this process should have in tools like
<command>ps</command> or <command>top</command>. This
is handy if you have multiple versions/installations
- of <command>bind10</command>.</para>
+ of <command>bind10</command>.
+<!-- TODO: only supported with setproctitle feature
+The default is the basename of ARG 0.
+-->
+</para>
</listitem>
</varlistentry>
@@ -173,6 +172,9 @@
</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 b890487..c206b34 100644
--- a/src/bin/bind10/bob.spec
+++ b/src/bin/bind10/bob.spec
@@ -3,12 +3,34 @@
"module_name": "Boss",
"module_description": "Master process",
"config_data": [
+ {
+ "item_name": "start_auth",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": true
+ },
+ {
+ "item_name": "start_resolver",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": false
+ }
],
"commands": [
{
"command_name": "shutdown",
"command_description": "Shut down BIND 10",
"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/run_bind10.sh.in b/src/bin/bind10/run_bind10.sh.in
index 18c2964..edc01fe 100644
--- a/src/bin/bind10/run_bind10.sh.in
+++ b/src/bin/bind10/run_bind10.sh.in
@@ -20,10 +20,10 @@ export PYTHON_EXEC
BIND10_PATH=@abs_top_builddir@/src/bin/bind10
-PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:$PATH
+PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:$PATH
export PATH
-PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs
+PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs
export PYTHONPATH
# If necessary (rare cases), explicitly specify paths to dynamic libraries
@@ -46,5 +46,5 @@ BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
export BIND10_MSGQ_SOCKET_FILE
cd ${BIND10_PATH}
-exec ${PYTHON_EXEC} -O bind10 $*
+exec ${PYTHON_EXEC} -O bind10 "$@"
diff --git a/src/bin/bind10/tests/Makefile.am b/src/bin/bind10/tests/Makefile.am
index 3679ad7..d05d977 100644
--- a/src/bin/bind10/tests/Makefile.am
+++ b/src/bin/bind10/tests/Makefile.am
@@ -1,12 +1,18 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+#PYTESTS = args_test.py bind10_test.py
PYTESTS = bind10_test.py
EXTRA_DIST = $(PYTESTS)
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/bind10 \
- $(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+ BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$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 b/src/bin/bind10/tests/bind10_test.py
deleted file mode 100644
index fd9f755..0000000
--- a/src/bin/bind10/tests/bind10_test.py
+++ /dev/null
@@ -1,121 +0,0 @@
-from bind10 import ProcessInfo, BoB
-
-# XXX: environment tests are currently disabled, due to the preprocessor
-# setup that we have now complicating the environment
-
-import unittest
-import sys
-import os
-import signal
-import socket
-from isc.net.addr import IPAddr
-
-class TestProcessInfo(unittest.TestCase):
- def setUp(self):
- # redirect stdout to a pipe so we can check that our
- # process spawning is doing the right thing with stdout
- self.old_stdout = os.dup(sys.stdout.fileno())
- self.pipes = os.pipe()
- os.dup2(self.pipes[1], sys.stdout.fileno())
- os.close(self.pipes[1])
- # note that we use dup2() to restore the original stdout
- # to the main program ASAP in each test... this prevents
- # hangs reading from the child process (as the pipe is only
- # open in the child), and also insures nice pretty output
-
- def tearDown(self):
- # clean up our stdout munging
- os.dup2(self.old_stdout, sys.stdout.fileno())
- os.close(self.pipes[0])
-
- def test_init(self):
- pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
- os.dup2(self.old_stdout, sys.stdout.fileno())
- self.assertEqual(pi.name, 'Test Process')
- self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
-# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
-# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
- self.assertEqual(pi.dev_null_stdout, False)
- self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
- self.assertNotEqual(pi.process, None)
- self.assertTrue(type(pi.pid) is int)
-
-# def test_setting_env(self):
-# pi = ProcessInfo('Test Process', [ '/bin/true' ], env={'FOO': 'BAR'})
-# os.dup2(self.old_stdout, sys.stdout.fileno())
-# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
-# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'],
-# 'FOO': 'BAR' })
-
- def test_setting_null_stdout(self):
- pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ],
- dev_null_stdout=True)
- 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' ])
- # wait for old process to work...
- self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
- # respawn it
- old_pid = pi.pid
- pi.respawn()
- os.dup2(self.old_stdout, sys.stdout.fileno())
- # make sure the new one started properly
- self.assertEqual(pi.name, 'Test Process')
- self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
-# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
-# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
- self.assertEqual(pi.dev_null_stdout, False)
- self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
- self.assertNotEqual(pi.process, None)
- self.assertTrue(type(pi.pid) is int)
- self.assertNotEqual(pi.pid, old_pid)
-
-class TestBoB(unittest.TestCase):
- def test_init(self):
- bob = BoB()
- self.assertEqual(bob.verbose, False)
- self.assertEqual(bob.msgq_socket_file, None)
- self.assertEqual(bob.auth_port, 5300)
- self.assertEqual(bob.cc_session, None)
- self.assertEqual(bob.address, None)
- self.assertEqual(bob.processes, {})
- self.assertEqual(bob.dead_processes, {})
- self.assertEqual(bob.runnable, False)
-
- def test_init_alternate_socket(self):
- bob = BoB("alt_socket_file")
- self.assertEqual(bob.verbose, False)
- self.assertEqual(bob.msgq_socket_file, "alt_socket_file")
- self.assertEqual(bob.cc_session, None)
- self.assertEqual(bob.processes, {})
- self.assertEqual(bob.dead_processes, {})
- self.assertEqual(bob.runnable, False)
-
- def test_init_alternate_auth_port(self):
- bob = BoB(None, 9999)
- self.assertEqual(bob.verbose, False)
- self.assertEqual(bob.msgq_socket_file, None)
- self.assertEqual(bob.auth_port, 9999)
- self.assertEqual(bob.cc_session, None)
- self.assertEqual(bob.address, None)
- self.assertEqual(bob.processes, {})
- self.assertEqual(bob.dead_processes, {})
- self.assertEqual(bob.runnable, False)
-
- def test_init_alternate_address(self):
- bob = BoB(None, 5300, IPAddr('127.127.127.127'))
- self.assertEqual(bob.verbose, False)
- self.assertEqual(bob.auth_port, 5300)
- self.assertEqual(bob.msgq_socket_file, None)
- self.assertEqual(bob.cc_session, None)
- self.assertEqual(bob.address.addr, socket.inet_aton('127.127.127.127'))
- self.assertEqual(bob.processes, {})
- self.assertEqual(bob.dead_processes, {})
- self.assertEqual(bob.runnable, False)
- # verbose testing...
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
new file mode 100644
index 0000000..2d58b61
--- /dev/null
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -0,0 +1,649 @@
+# 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
+
+# XXX: environment tests are currently disabled, due to the preprocessor
+# setup that we have now complicating the environment
+
+import unittest
+import sys
+import os
+import signal
+import socket
+from isc.net.addr import IPAddr
+from isc.testutils.parse_args import TestOptParser, OptsError
+
+class TestProcessInfo(unittest.TestCase):
+ def setUp(self):
+ # redirect stdout to a pipe so we can check that our
+ # process spawning is doing the right thing with stdout
+ self.old_stdout = os.dup(sys.stdout.fileno())
+ self.pipes = os.pipe()
+ os.dup2(self.pipes[1], sys.stdout.fileno())
+ os.close(self.pipes[1])
+ # note that we use dup2() to restore the original stdout
+ # to the main program ASAP in each test... this prevents
+ # hangs reading from the child process (as the pipe is only
+ # open in the child), and also insures nice pretty output
+
+ def tearDown(self):
+ # clean up our stdout munging
+ os.dup2(self.old_stdout, sys.stdout.fileno())
+ os.close(self.pipes[0])
+
+ 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' ])
+# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
+# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
+ self.assertEqual(pi.dev_null_stdout, False)
+ self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
+ self.assertNotEqual(pi.process, None)
+ self.assertTrue(type(pi.pid) is int)
+
+# def test_setting_env(self):
+# pi = ProcessInfo('Test Process', [ '/bin/true' ], env={'FOO': 'BAR'})
+# os.dup2(self.old_stdout, sys.stdout.fileno())
+# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
+# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'],
+# 'FOO': 'BAR' })
+
+ 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
+ old_pid = pi.pid
+ pi.respawn()
+ os.dup2(self.old_stdout, sys.stdout.fileno())
+ # make sure the new one started properly
+ self.assertEqual(pi.name, 'Test Process')
+ self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
+# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
+# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
+ self.assertEqual(pi.dev_null_stdout, False)
+ self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
+ self.assertNotEqual(pi.process, None)
+ self.assertTrue(type(pi.pid) is int)
+ self.assertNotEqual(pi.pid, old_pid)
+
+class TestBoB(unittest.TestCase):
+ def test_init(self):
+ bob = BoB()
+ self.assertEqual(bob.verbose, False)
+ self.assertEqual(bob.msgq_socket_file, None)
+ self.assertEqual(bob.cc_session, None)
+ self.assertEqual(bob.ccs, None)
+ self.assertEqual(bob.processes, {})
+ self.assertEqual(bob.dead_processes, {})
+ self.assertEqual(bob.runnable, False)
+ self.assertEqual(bob.uid, None)
+ self.assertEqual(bob.username, None)
+ self.assertEqual(bob.nocache, False)
+ self.assertEqual(bob.cfg_start_auth, True)
+ self.assertEqual(bob.cfg_start_resolver, False)
+
+ def test_init_alternate_socket(self):
+ bob = BoB("alt_socket_file")
+ self.assertEqual(bob.verbose, False)
+ self.assertEqual(bob.msgq_socket_file, "alt_socket_file")
+ self.assertEqual(bob.cc_session, None)
+ self.assertEqual(bob.ccs, None)
+ self.assertEqual(bob.processes, {})
+ self.assertEqual(bob.dead_processes, {})
+ self.assertEqual(bob.runnable, False)
+ self.assertEqual(bob.uid, None)
+ self.assertEqual(bob.username, None)
+ self.assertEqual(bob.nocache, False)
+ self.assertEqual(bob.cfg_start_auth, True)
+ self.assertEqual(bob.cfg_start_resolver, False)
+
+# Class for testing the BoB without actually starting processes.
+# This is used for testing the start/stop components routines and
+# the BoB commands.
+#
+# 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 MockBob(BoB):
+ def __init__(self):
+ BoB.__init__(self)
+
+ # Set flags as to which of the overridden methods has been run.
+ self.msgq = False
+ self.cfgmgr = False
+ self.ccsession = False
+ self.auth = False
+ self.resolver = False
+ self.xfrout = False
+ self.xfrin = False
+ self.zonemgr = False
+ self.stats = False
+ self.cmdctl = False
+ self.c_channel_env = {}
+ self.processes = { }
+
+ def read_bind10_config(self):
+ # Configuration options are set directly
+ pass
+
+ 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):
+ """
+ Check that the start_all_processes method starts the right combination
+ of processes and that the right processes are started and stopped
+ according to changes in configuration.
+ """
+ def check_started(self, bob, core, auth, resolver):
+ """
+ Check that the right sets of services are started. The ones that
+ should be running are specified by the core, auth and resolver parameters
+ (they are groups of processes, eg. auth means b10-auth, -xfrout, -xfrin
+ and -zonemgr).
+ """
+ self.assertEqual(bob.msgq, core)
+ self.assertEqual(bob.cfgmgr, core)
+ self.assertEqual(bob.ccsession, core)
+ self.assertEqual(bob.auth, auth)
+ self.assertEqual(bob.resolver, resolver)
+ self.assertEqual(bob.xfrout, auth)
+ self.assertEqual(bob.xfrin, auth)
+ self.assertEqual(bob.zonemgr, auth)
+ self.assertEqual(bob.stats, core)
+ self.assertEqual(bob.cmdctl, core)
+
+ def check_preconditions(self, bob):
+ self.check_started(bob, False, False, False)
+
+ def check_started_none(self, bob):
+ """
+ Check that the situation is according to configuration where no servers
+ should be started. Some processes still need to be running.
+ """
+ self.check_started(bob, True, False, False)
+
+ def check_started_both(self, bob):
+ """
+ Check the situation is according to configuration where both servers
+ (auth and resolver) are enabled.
+ """
+ self.check_started(bob, True, True, True)
+
+ def check_started_auth(self, bob):
+ """
+ Check the set of processes needed to run auth only is started.
+ """
+ self.check_started(bob, True, True, False)
+
+ def check_started_resolver(self, bob):
+ """
+ Check the set of processes needed to run resolver only is started.
+ """
+ self.check_started(bob, True, False, True)
+
+ # Checks the processes started when starting neither auth nor resolver
+ # is specified.
+ def test_start_none(self):
+ # Create BoB and ensure correct initialization
+ bob = MockBob()
+ self.check_preconditions(bob)
+
+ # Start processes and check what was started
+ bob.cfg_start_auth = False
+ bob.cfg_start_resolver = False
+
+ bob.start_all_processes()
+ self.check_started_none(bob)
+
+ # Checks the processes started when starting only the auth process
+ def test_start_auth(self):
+ # Create BoB and ensure correct initialization
+ bob = MockBob()
+ self.check_preconditions(bob)
+
+ # Start processes and check what was started
+ bob.cfg_start_auth = True
+ bob.cfg_start_resolver = False
+
+ bob.start_all_processes()
+
+ self.check_started_auth(bob)
+
+ # Checks the processes started when starting only the resolver process
+ def test_start_resolver(self):
+ # Create BoB and ensure correct initialization
+ bob = MockBob()
+ self.check_preconditions(bob)
+
+ # Start processes and check what was started
+ bob.cfg_start_auth = False
+ bob.cfg_start_resolver = True
+
+ bob.start_all_processes()
+
+ self.check_started_resolver(bob)
+
+ # Checks the processes started when starting both auth and resolver process
+ def test_start_both(self):
+ # Create BoB and ensure correct initialization
+ bob = MockBob()
+ self.check_preconditions(bob)
+
+ # Start processes and check what was started
+ bob.cfg_start_auth = True
+ bob.cfg_start_resolver = True
+
+ bob.start_all_processes()
+
+ self.check_started_both(bob)
+
+ def test_config_start(self):
+ """
+ Test that the configuration starts and stops processes according
+ to configuration changes.
+ """
+
+ # Create BoB and ensure correct initialization
+ bob = MockBob()
+ self.check_preconditions(bob)
+
+ # Start processes (nothing much should be started, as in
+ # test_start_none)
+ bob.cfg_start_auth = False
+ bob.cfg_start_resolver = False
+
+ bob.start_all_processes()
+ bob.runnable = True
+ self.check_started_none(bob)
+
+ # Enable both at once
+ bob.config_handler({'start_auth': True, 'start_resolver': True})
+ self.check_started_both(bob)
+
+ # Not touched by empty change
+ bob.config_handler({})
+ self.check_started_both(bob)
+
+ # Not touched by change to the same configuration
+ bob.config_handler({'start_auth': True, 'start_resolver': True})
+ self.check_started_both(bob)
+
+ # Turn them both off again
+ bob.config_handler({'start_auth': False, 'start_resolver': False})
+ self.check_started_none(bob)
+
+ # Not touched by empty change
+ bob.config_handler({})
+ self.check_started_none(bob)
+
+ # Not touched by change to the same configuration
+ bob.config_handler({'start_auth': False, 'start_resolver': False})
+ self.check_started_none(bob)
+
+ # Start and stop auth separately
+ bob.config_handler({'start_auth': True})
+ self.check_started_auth(bob)
+
+ bob.config_handler({'start_auth': False})
+ self.check_started_none(bob)
+
+ # Start and stop resolver separately
+ bob.config_handler({'start_resolver': True})
+ self.check_started_resolver(bob)
+
+ bob.config_handler({'start_resolver': False})
+ self.check_started_none(bob)
+
+ # Alternate
+ bob.config_handler({'start_auth': True})
+ self.check_started_auth(bob)
+
+ bob.config_handler({'start_auth': False, 'start_resolver': True})
+ self.check_started_resolver(bob)
+
+ bob.config_handler({'start_auth': True, 'start_resolver': False})
+ self.check_started_auth(bob)
+
+ def test_config_start_once(self):
+ """
+ Tests that a process is started only once.
+ """
+ # Create BoB and ensure correct initialization
+ bob = MockBob()
+ self.check_preconditions(bob)
+
+ # Start processes (both)
+ bob.cfg_start_auth = True
+ bob.cfg_start_resolver = True
+
+ bob.start_all_processes()
+ bob.runnable = True
+ self.check_started_both(bob)
+
+ bob.start_auth = lambda: self.fail("Started auth again")
+ bob.start_xfrout = lambda: self.fail("Started xfrout again")
+ bob.start_xfrin = lambda: self.fail("Started xfrin again")
+ bob.start_zonemgr = lambda: self.fail("Started zonemgr again")
+ bob.start_resolver = lambda: self.fail("Started resolver again")
+
+ # Send again we want to start them. Should not do it, as they are.
+ bob.config_handler({'start_auth': True})
+ bob.config_handler({'start_resolver': True})
+
+ def test_config_not_started_early(self):
+ """
+ Test that processes are not started by the config handler before
+ startup.
+ """
+ bob = MockBob()
+ self.check_preconditions(bob)
+
+ bob.start_auth = lambda: self.fail("Started auth again")
+ bob.start_xfrout = lambda: self.fail("Started xfrout again")
+ bob.start_xfrin = lambda: self.fail("Started xfrin again")
+ bob.start_zonemgr = lambda: self.fail("Started zonemgr again")
+ bob.start_resolver = lambda: self.fail("Started resolver again")
+
+ 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'
+ if os.path.exists(self.pid_file):
+ os.unlink(self.pid_file)
+
+ def tearDown(self):
+ if os.path.exists(self.pid_file):
+ os.unlink(self.pid_file)
+
+ def check_pid_file(self):
+ # dump PID to the file, and confirm the content is correct
+ dump_pid(self.pid_file)
+ my_pid = os.getpid()
+ self.assertEqual(my_pid, int(open(self.pid_file, "r").read()))
+
+ def test_dump_pid(self):
+ self.check_pid_file()
+
+ # make sure any existing content will be removed
+ open(self.pid_file, "w").write('dummy data\n')
+ self.check_pid_file()
+
+ def test_unlink_pid_file_notexist(self):
+ dummy_data = 'dummy_data\n'
+ open(self.pid_file, "w").write(dummy_data)
+ unlink_pid_file("no_such_pid_file")
+ # the file specified for unlink_pid_file doesn't exist,
+ # and the original content of the file should be intact.
+ self.assertEqual(dummy_data, open(self.pid_file, "r").read())
+
+ def test_dump_pid_with_none(self):
+ # Check the behavior of dump_pid() and unlink_pid_file() with None.
+ # This should be no-op.
+ dump_pid(None)
+ self.assertFalse(os.path.exists(self.pid_file))
+
+ dummy_data = 'dummy_data\n'
+ open(self.pid_file, "w").write(dummy_data)
+ unlink_pid_file(None)
+ self.assertEqual(dummy_data, open(self.pid_file, "r").read())
+
+ def test_dump_pid_failure(self):
+ # the attempt to open file will fail, which should result in exception.
+ 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/Makefile.am b/src/bin/bindctl/Makefile.am
index da9b63d..2f412ec 100644
--- a/src/bin/bindctl/Makefile.am
+++ b/src/bin/bindctl/Makefile.am
@@ -5,12 +5,13 @@ man_MANS = bindctl.1
EXTRA_DIST = $(man_MANS) bindctl.xml
-python_PYTHON = __init__.py bindcmd.py cmdparse.py exception.py moduleinfo.py mycollections.py
+python_PYTHON = __init__.py bindcmd.py cmdparse.py exception.py moduleinfo.py \
+ mycollections.py
pythondir = $(pyexecdir)/bindctl
-bindctldir = $(DESTDIR)$(pkgdatadir)
+bindctldir = $(pkgdatadir)
-CLEANFILES = bindctl
+CLEANFILES = bindctl bindctl_main.pyc
if ENABLE_MAN
@@ -19,8 +20,8 @@ bindctl.1: bindctl.xml
endif
-bindctl: bindctl-source.py
+bindctl: bindctl_main.py
$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
-e "s|@@SYSCONFDIR@@|@sysconfdir@|" \
- -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" bindctl-source.py >$@
+ -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" bindctl_main.py >$@
chmod a+x $@
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index 1704ce4..8973aa5 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -51,7 +51,6 @@ except ImportError:
my_readline = sys.stdin.readline
CSV_FILE_NAME = 'default_user.csv'
-FAIL_TO_CONNECT_WITH_CMDCTL = "Fail to connect with b10-cmdctl module, is it running?"
CONFIG_MODULE_NAME = 'config'
CONST_BINDCTL_HELP = """
usage: <module name> <command name> [param1 = value1 [, param2 = value2]]
@@ -88,20 +87,29 @@ class ValidatedHTTPSConnection(http.client.HTTPSConnection):
class BindCmdInterpreter(Cmd):
"""simple bindctl example."""
- def __init__(self, server_port = 'localhost:8080', pem_file = None):
+ def __init__(self, server_port='localhost:8080', pem_file=None,
+ csv_file_dir=None):
Cmd.__init__(self)
self.location = ""
self.prompt_end = '> '
- self.prompt = self.prompt_end
+ if sys.stdin.isatty():
+ self.prompt = self.prompt_end
+ else:
+ self.prompt = ""
self.ruler = '-'
self.modules = OrderedDict()
- self.add_module_info(ModuleInfo("help", desc = "Get help for bindctl"))
+ self.add_module_info(ModuleInfo("help", desc = "Get help for bindctl."))
self.server_port = server_port
self.conn = ValidatedHTTPSConnection(self.server_port,
ca_certs=pem_file)
self.session_id = self._get_session_id()
self.config_data = None
-
+ if csv_file_dir is not None:
+ self.csv_file_dir = csv_file_dir
+ else:
+ self.csv_file_dir = pwd.getpwnam(getpass.getuser()).pw_dir + \
+ os.sep + '.bind10' + os.sep
+
def _get_session_id(self):
'''Generate one session id for the connection. '''
rand = os.urandom(16)
@@ -115,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:
- print(err)
- print(FAIL_TO_CONNECT_WITH_CMDCTL)
+ # 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
@@ -173,9 +186,7 @@ class BindCmdInterpreter(Cmd):
time, username and password saved in 'default_user.csv' will be
used first.
'''
- csv_file_dir = pwd.getpwnam(getpass.getuser()).pw_dir
- csv_file_dir += os.sep + '.bind10' + os.sep
- users = self._get_saved_user_info(csv_file_dir, CSV_FILE_NAME)
+ users = self._get_saved_user_info(self.csv_file_dir, CSV_FILE_NAME)
for row in users:
param = {'username': row[0], 'password' : row[1]}
try:
@@ -186,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")
@@ -209,7 +222,8 @@ class BindCmdInterpreter(Cmd):
raise FailToLogin()
if response.status == http.client.OK:
- self._save_user_info(username, passwd, csv_file_dir, CSV_FILE_NAME)
+ self._save_user_info(username, passwd, self.csv_file_dir,
+ CSV_FILE_NAME)
return True
def _update_commands(self):
@@ -217,16 +231,24 @@ class BindCmdInterpreter(Cmd):
for module_name in self.config_data.get_config_item_list():
self._prepare_module_commands(self.config_data.get_module_spec(module_name))
+ def _send_message(self, url, body):
+ headers = {"cookie" : self.session_id}
+ self.conn.request('GET', url, body, headers)
+ res = self.conn.getresponse()
+ return res.status, res.read()
+
def send_GET(self, url, body = None):
'''Send GET request to cmdctl, session id is send with the name
'cookie' in header.
'''
- headers = {"cookie" : self.session_id}
- self.conn.request('GET', url, body, headers)
- res = self.conn.getresponse()
- reply_msg = res.read()
+ status, reply_msg = self._send_message(url, body)
+ if status == http.client.UNAUTHORIZED:
+ if self.login_to_cmdctl():
+ # successful, so try send again
+ status, reply_msg = self._send_message(url, body)
+
if reply_msg:
- return json.loads(reply_msg.decode())
+ return json.loads(reply_msg.decode())
else:
return {}
@@ -258,12 +280,15 @@ 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'''
- self.prompt = self.location + self.prompt_end
+ '''Update the prompt after every command, but only if we
+ have a tty as output'''
+ if sys.stdin.isatty():
+ self.prompt = self.location + self.prompt_end
return stop
def _prepare_module_commands(self, module_spec):
@@ -367,7 +392,14 @@ class BindCmdInterpreter(Cmd):
if cmd.command == "help" or ("help" in cmd.params.keys()):
self._handle_help(cmd)
elif cmd.module == CONFIG_MODULE_NAME:
- self.apply_config_cmd(cmd)
+ try:
+ self.apply_config_cmd(cmd)
+ except isc.cc.data.DataTypeError as dte:
+ print("Error: " + str(dte))
+ except isc.cc.data.DataNotFoundError as dnfe:
+ print("Error: " + str(dnfe))
+ except KeyError as ke:
+ print("Error: missing " + str(ke))
else:
self.apply_cmd(cmd)
@@ -388,9 +420,24 @@ class BindCmdInterpreter(Cmd):
def do_help(self, name):
print(CONST_BINDCTL_HELP)
- for k in self.modules.keys():
- print("\t", self.modules[k])
-
+ for k in self.modules.values():
+ n = k.get_name()
+ if len(n) >= CONST_BINDCTL_HELP_INDENT_WIDTH:
+ print(" %s" % n)
+ print(textwrap.fill(k.get_desc(),
+ initial_indent=" ",
+ subsequent_indent=" " +
+ " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
+ width=70))
+ else:
+ print(textwrap.fill("%s%s%s" %
+ (k.get_name(),
+ " "*(CONST_BINDCTL_HELP_INDENT_WIDTH - len(k.get_name())),
+ k.get_desc()),
+ initial_indent=" ",
+ subsequent_indent=" " +
+ " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
+ width=70))
def onecmd(self, line):
if line == 'EOF' or line.lower() == "quit":
@@ -403,7 +450,19 @@ class BindCmdInterpreter(Cmd):
Cmd.onecmd(self, line)
def remove_prefix(self, list, prefix):
- return [(val[len(prefix):]) for val in list]
+ """Removes the prefix already entered, and all elements from the
+ list that don't match it"""
+ if prefix.startswith('/'):
+ prefix = prefix[1:]
+
+ new_list = []
+ for val in list:
+ if val.startswith(prefix):
+ new_val = val[len(prefix):]
+ if new_val.startswith("/"):
+ new_val = new_val[1:]
+ new_list.append(new_val)
+ return new_list
def complete(self, text, state):
if 0 == state:
@@ -494,8 +553,7 @@ class BindCmdInterpreter(Cmd):
self._validate_cmd(cmd)
self._handle_cmd(cmd)
except (IOError, http.client.HTTPException) as err:
- print('Error!', err)
- print(FAIL_TO_CONNECT_WITH_CMDCTL)
+ print('Error: ', err)
except BindCtlException as err:
print("Error! ", err)
self._print_correct_usage(err)
@@ -533,84 +591,115 @@ class BindCmdInterpreter(Cmd):
Raises a KeyError if the command was not complete
'''
identifier = self.location
- try:
- if 'identifier' in cmd.params:
- if not identifier.endswith("/"):
- identifier += "/"
- if cmd.params['identifier'].startswith("/"):
- identifier = cmd.params['identifier']
+ if 'identifier' in cmd.params:
+ if not identifier.endswith("/"):
+ identifier += "/"
+ if cmd.params['identifier'].startswith("/"):
+ identifier = cmd.params['identifier']
+ else:
+ if cmd.params['identifier'].startswith('['):
+ identifier = identifier[:-1]
+ identifier += cmd.params['identifier']
+
+ # Check if the module is known; for unknown modules
+ # we currently deny setting preferences, as we have
+ # no way yet to determine if they are ok.
+ module_name = identifier.split('/')[1]
+ if module_name != "" and (self.config_data is None or \
+ not self.config_data.have_specification(module_name)):
+ print("Error: Module '" + module_name + "' unknown or not running")
+ return
+
+ if cmd.command == "show":
+ # check if we have the 'all' argument
+ show_all = False
+ if 'argument' in cmd.params:
+ if cmd.params['argument'] == 'all':
+ show_all = True
+ elif 'identifier' not in cmd.params:
+ # no 'all', no identifier, assume this is the
+ #identifier
+ identifier += cmd.params['argument']
else:
- identifier += cmd.params['identifier']
-
- # Check if the module is known; for unknown modules
- # we currently deny setting preferences, as we have
- # no way yet to determine if they are ok.
- module_name = identifier.split('/')[1]
- if self.config_data is None or \
- not self.config_data.have_specification(module_name):
- print("Error: Module '" + module_name + "' unknown or not running")
+ print("Error: unknown argument " + cmd.params['argument'] + ", or multiple identifiers given")
return
-
- if cmd.command == "show":
- values = self.config_data.get_value_maps(identifier)
- for value_map in values:
- line = value_map['name']
- if value_map['type'] in [ 'module', 'map', 'list' ]:
- line += "/"
- else:
- line += ":\t" + str(value_map['value'])
- line += "\t" + value_map['type']
- line += "\t"
- if value_map['default']:
- line += "(default)"
- if value_map['modified']:
- line += "(modified)"
- print(line)
- elif cmd.command == "add":
+ values = self.config_data.get_value_maps(identifier, show_all)
+ for value_map in values:
+ line = value_map['name']
+ if value_map['type'] in [ 'module', 'map' ]:
+ line += "/"
+ elif value_map['type'] == 'list' \
+ and value_map['value'] != []:
+ # do not print content of non-empty lists if
+ # we have more data to show
+ line += "/"
+ else:
+ line += "\t" + json.dumps(value_map['value'])
+ line += "\t" + value_map['type']
+ line += "\t"
+ if value_map['default']:
+ line += "(default)"
+ if value_map['modified']:
+ line += "(modified)"
+ print(line)
+ elif cmd.command == "show_json":
+ if identifier == "":
+ print("Need at least the module to show the configuration in JSON format")
+ else:
+ data, default = self.config_data.get_value(identifier)
+ print(json.dumps(data))
+ elif cmd.command == "add":
+ if 'value' in cmd.params:
self.config_data.add_value(identifier, cmd.params['value'])
- elif cmd.command == "remove":
+ else:
+ self.config_data.add_value(identifier)
+ elif cmd.command == "remove":
+ if 'value' in cmd.params:
self.config_data.remove_value(identifier, cmd.params['value'])
- elif cmd.command == "set":
- if 'identifier' not in cmd.params:
- print("Error: missing identifier or value")
- else:
- parsed_value = None
- try:
- parsed_value = json.loads(cmd.params['value'])
- except Exception as exc:
- # ok could be an unquoted string, interpret as such
- parsed_value = cmd.params['value']
- self.config_data.set_value(identifier, parsed_value)
- elif cmd.command == "unset":
- self.config_data.unset(identifier)
- elif cmd.command == "revert":
- self.config_data.clear_local_changes()
- elif cmd.command == "commit":
- self.config_data.commit()
- elif cmd.command == "diff":
- print(self.config_data.get_local_changes());
- elif cmd.command == "go":
- self.go(identifier)
- except isc.cc.data.DataTypeError as dte:
- print("Error: " + str(dte))
- except isc.cc.data.DataNotFoundError as dnfe:
- print("Error: " + identifier + " not found")
- except KeyError as ke:
- print("Error: missing " + str(ke))
- raise ke
+ else:
+ self.config_data.remove_value(identifier, None)
+ elif cmd.command == "set":
+ if 'identifier' not in cmd.params:
+ print("Error: missing identifier or value")
+ else:
+ parsed_value = None
+ try:
+ parsed_value = json.loads(cmd.params['value'])
+ except Exception as exc:
+ # ok could be an unquoted string, interpret as such
+ parsed_value = cmd.params['value']
+ self.config_data.set_value(identifier, parsed_value)
+ elif cmd.command == "unset":
+ self.config_data.unset(identifier)
+ elif cmd.command == "revert":
+ self.config_data.clear_local_changes()
+ elif cmd.command == "commit":
+ self.config_data.commit()
+ elif cmd.command == "diff":
+ print(self.config_data.get_local_changes());
+ elif cmd.command == "go":
+ self.go(identifier)
def go(self, identifier):
'''Handles the config go command, change the 'current' location
- within the configuration tree'''
- # this is just to see if it exists
- self.config_data.get_value(identifier)
- # some sanitizing
- identifier = identifier.replace("//", "/")
- if not identifier.startswith("/"):
- identifier = "/" + identifier
- if identifier.endswith("/"):
- identifier = identifier[:-1]
- self.location = identifier
+ within the configuration tree. '..' will be interpreted as
+ 'up one level'.'''
+ id_parts = isc.cc.data.split_identifier(identifier)
+
+ new_location = ""
+ for id_part in id_parts:
+ if (id_part == ".."):
+ # go 'up' one level
+ new_location, a, b = new_location.rpartition("/")
+ else:
+ new_location += "/" + id_part
+ # check if exists, if not, revert and error
+ v,d = self.config_data.get_value(new_location)
+ if v is None:
+ print("Error: " + identifier + " not found")
+ return
+
+ self.location = new_location
def apply_cmd(self, cmd):
'''Handles a general module command'''
@@ -619,9 +708,11 @@ class BindCmdInterpreter(Cmd):
if (len(cmd.params) != 0):
cmd_params = json.dumps(cmd.params)
- print("send the command to cmd-ctrld")
reply = self.send_POST(url, cmd.params)
data = reply.read().decode()
- print("received reply:", data)
+ # The reply is a string containing JSON data,
+ # parse it, then prettyprint
+ if data != "" and data != "{}":
+ print(json.dumps(json.loads(data), sort_keys=True, indent=4))
diff --git a/src/bin/bindctl/bindctl-source.py.in b/src/bin/bindctl/bindctl-source.py.in
deleted file mode 100644
index 30b1d2a..0000000
--- a/src/bin/bindctl/bindctl-source.py.in
+++ /dev/null
@@ -1,126 +0,0 @@
-#!@PYTHON@
-
-# 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.
-
-"""This is the main calling class for the bindctl configuration and
- command tool. It sets up a command interpreter and runs that."""
-
-import sys; sys.path.append ('@@PYTHONPATH@@')
-
-from bindctl.moduleinfo import *
-from bindctl.bindcmd import *
-import pprint
-from optparse import OptionParser, OptionValueError
-import isc.util.process
-
-isc.util.process.rename()
-
-__version__ = 'Bindctl'
-
-def prepare_config_commands(tool):
- '''Prepare fixed commands for local configuration editing'''
- module = ModuleInfo(name = CONFIG_MODULE_NAME, desc = "Configuration commands")
- cmd = CommandInfo(name = "show", desc = "Show configuration")
- param = ParamInfo(name = "identifier", type = "string", optional=True)
- cmd.add_param(param)
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "add", desc = "Add entry to configuration list")
- param = ParamInfo(name = "identifier", type = "string", optional=True)
- cmd.add_param(param)
- param = ParamInfo(name = "value", type = "string", optional=False)
- cmd.add_param(param)
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list")
- param = ParamInfo(name = "identifier", type = "string", optional=True)
- cmd.add_param(param)
- param = ParamInfo(name = "value", type = "string", optional=False)
- cmd.add_param(param)
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "set", desc = "Set a configuration value")
- param = ParamInfo(name = "identifier", type = "string", optional=True)
- cmd.add_param(param)
- param = ParamInfo(name = "value", type = "string", optional=False)
- cmd.add_param(param)
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "unset", desc = "Unset a configuration value")
- param = ParamInfo(name = "identifier", type = "string", optional=False)
- cmd.add_param(param)
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "diff", desc = "Show all local changes")
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "revert", desc = "Revert all local changes")
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "commit", desc = "Commit all local changes")
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part")
- param = ParamInfo(name = "identifier", type="string", optional=False)
- cmd.add_param(param)
- module.add_command(cmd)
-
- tool.add_module_info(module)
-
-def check_port(option, opt_str, value, parser):
- if (value < 0) or (value > 65535):
- raise OptionValueError('%s requires a port number (0-65535)' % opt_str)
- parser.values.port = value
-
-def check_addr(option, opt_str, value, parser):
- ipstr = value
- ip_family = socket.AF_INET
- if (ipstr.find(':') != -1):
- ip_family = socket.AF_INET6
-
- try:
- socket.inet_pton(ip_family, ipstr)
- except:
- raise OptionValueError("%s invalid ip address" % ipstr)
-
- parser.values.addr = value
-
-def set_bindctl_options(parser):
- parser.add_option('-p', '--port', dest = 'port', type = 'int',
- action = 'callback', callback=check_port,
- default = '8080', help = 'port for cmdctl of bind10')
-
- parser.add_option('-a', '--address', dest = 'addr', type = 'string',
- action = 'callback', callback=check_addr,
- default = '127.0.0.1', help = 'IP address for cmdctl of bind10')
-
- parser.add_option('-c', '--certificate-chain', dest = 'cert_chain',
- type = 'string', action = 'store',
- help = 'PEM formatted server certificate validation chain file')
-
-if __name__ == '__main__':
- try:
- parser = OptionParser(version = __version__)
- set_bindctl_options(parser)
- (options, args) = parser.parse_args()
- server_addr = options.addr + ':' + str(options.port)
- tool = BindCmdInterpreter(server_addr, pem_file=options.cert_chain)
- prepare_config_commands(tool)
- tool.run()
- except Exception as e:
- print(e, "\nFailed to connect with b10-cmdctl module, is it running?")
-
-
diff --git a/src/bin/bindctl/bindctl.1 b/src/bin/bindctl/bindctl.1
index e97c213..97700d6 100644
--- a/src/bin/bindctl/bindctl.1
+++ b/src/bin/bindctl/bindctl.1
@@ -2,12 +2,12 @@
.\" Title: bindctl
.\" Author: [see the "AUTHORS" section]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: March 18, 2010
+.\" Date: December 23, 2010
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "BINDCTL" "1" "March 18, 2010" "BIND10" "BIND10"
+.TH "BINDCTL" "1" "December 23, 2010" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -22,7 +22,7 @@
bindctl \- control and configure BIND 10
.SH "SYNOPSIS"
.HP \w'\fBbindctl\fR\ 'u
-\fBbindctl\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
@@ -32,15 +32,69 @@ via its interactive command interpreter\&.
.PP
\fBbindctl\fR
-communicates over the REST\-ful interface provided by
+communicates over a HTTPS REST\-ful interface provided by
\fBb10-cmdctl\fR(8)\&. The
\fBb10-cfgmgr\fR(8)
daemon stores the configurations and defines the commands\&.
+.SH "ARGUMENTS"
+.PP
+The arguments are as follows:
+.PP
+\fB\-a\fR \fIaddress\fR, \fB\-\-address\fR \fIaddress\fR
+.RS 4
+The IPv4 or IPv6 address to use to connect to the running
+\fBb10-cmdctl\fR(8)
+daemon\&. The default is 127\&.0\&.0\&.1\&.
+.RE
+.PP
+\fB\-c\fR \fIfile\fR, \fB\-\-certificate\-chain\fR \fIfile\fR
+.RS 4
+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\&.
+.RE
+.PP
+\fB\-p\fR \fInumber\fR, \fB\-\-port\fR \fInumber\fR
+.RS 4
+The port number to use to connect to the running
+\fBb10-cmdctl\fR(8)
+daemon\&. The default is 8080\&.
+.if n \{\
+.sp
+.\}
+.RS 4
+.it 1 an-trap
+.nr an-no-space-flag 1
+.nr an-break-flag 1
+.br
+.ps +1
+\fBNote\fR
+.ps -1
+.br
+This default port number may change\&.
+.sp .5v
+.RE
+.RE
+.PP
+\fB\-\-version\fR
+.RS 4
+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
\fBbindctl\fR
diff --git a/src/bin/bindctl/bindctl.xml b/src/bin/bindctl/bindctl.xml
index c709de6..eff1de2 100644
--- a/src/bin/bindctl/bindctl.xml
+++ b/src/bin/bindctl/bindctl.xml
@@ -17,11 +17,10 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
-<!-- $Id$ -->
<refentry>
<refentryinfo>
- <date>March 18, 2010</date>
+ <date>December 23, 2010</date>
</refentryinfo>
<refmeta>
@@ -45,6 +44,16 @@
<refsynopsisdiv>
<cmdsynopsis>
<command>bindctl</command>
+ <arg><option>-a <replaceable>address</replaceable></option></arg>
+ <arg><option>-h</option></arg>
+ <arg><option>-c <replaceable>file</replaceable></option></arg>
+ <arg><option>-p <replaceable>number</replaceable></option></arg>
+ <arg><option>--address <replaceable>address</replaceable></option></arg>
+ <arg><option>--help</option></arg>
+ <arg><option>--certificate-chain <replaceable>file</replaceable></option></arg>
+ <arg><option>--csv-file-dir<replaceable>file</replaceable></option></arg>
+ <arg><option>--port <replaceable>number</replaceable></option></arg>
+ <arg><option>--version</option></arg>
</cmdsynopsis>
</refsynopsisdiv>
@@ -60,7 +69,7 @@
</para>
<para>
- <command>bindctl</command> communicates over the REST-ful
+ <command>bindctl</command> communicates over a HTTPS REST-ful
interface provided by
<citerefentry><refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
The
@@ -68,13 +77,107 @@
daemon stores the configurations and defines the commands.
</para>
+ </refsect1>
+
+ <refsect1>
+ <title>ARGUMENTS</title>
+
+ <para>The arguments are as follows:</para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><option>-a</option> <replaceable>address</replaceable>, <option>--address</option> <replaceable>address</replaceable></term>
+
+ <listitem>
+ <para>The IPv4 or IPv6 address to use to connect to the running
+ <citerefentry><refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ daemon.
+ The default is 127.0.0.1.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-c</option> <replaceable>file</replaceable>,
+ <option>--certificate-chain</option> <replaceable>file</replaceable></term>
+
+ <listitem>
+ <para>The PEM formatted server certificate validation chain file.
+ </para>
+<!-- TODO: any default? -->
+<!-- TODO: any way to choose this for cmdctl? -->
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>--csv-file-dir</option><replaceable>file</replaceable>
+ </term>
+
+ <listitem>
+ <para>
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-h</option>,
+ <option>--help</option></term>
+ <listitem><para>
+ Display command usage.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-p</option> <replaceable>number</replaceable>, <option>--port</option> <replaceable>number</replaceable></term>
+
+ <listitem>
+ <para>The port number to use to connect to the running
+ <citerefentry><refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ daemon.
+ The default is 8080.</para>
+<!-- TODO: -->
+ <note><simpara>This default port number may change.</simpara></note>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>--version</option></term>
+ <listitem><para>
+ Display the version number and exit.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </refsect1>
+
+ <refsect1>
+ <title>AUTHENTICATION</title>
+
<para>
The tool will authenticate using a username and password.
On the first successful login, it will save the details to
- <filename>~/.bind10/default_user.csv</filename>
+ a comma-separated-value (CSV) file
which will be used for later uses of <command>bindctl</command>.
+ The file name is <filename>default_user.csv</filename>
+ located under the directory specified by the --csv-file-dir option.
</para>
+<!-- TODO: mention HTTPS? -->
+
+ </refsect1>
+
+ <refsect1>
+ <title>USAGE</title>
+
<para>
The <command>bindctl</command> prompt shows
<quote>> </quote>.
diff --git a/src/bin/bindctl/bindctl_main.py.in b/src/bin/bindctl/bindctl_main.py.in
new file mode 100755
index 0000000..01307e9
--- /dev/null
+++ b/src/bin/bindctl/bindctl_main.py.in
@@ -0,0 +1,138 @@
+#!@PYTHON@
+
+# 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.
+
+"""This is the main calling class for the bindctl configuration and
+ command tool. It sets up a command interpreter and runs that."""
+
+import sys; sys.path.append ('@@PYTHONPATH@@')
+
+from bindctl.moduleinfo import *
+from bindctl.bindcmd import *
+import pprint
+from optparse import OptionParser, OptionValueError
+import isc.util.process
+
+isc.util.process.rename()
+
+# This is the version that gets displayed to the user.
+# The VERSION string consists of the module name, the module version
+# number, and the overall BIND 10 version number (set in configure.ac).
+VERSION = "bindctl 20110217 (BIND 10 @PACKAGE_VERSION@)"
+
+DEFAULT_IDENTIFIER_DESC = "The identifier specifies the config item. Child elements are separated with the '/' character. List indices can be specified with '[i]', where i is an integer specifying the index, starting with 0. Examples: 'Boss/start_auth', 'Recurse/listen_on[0]/address'. If no identifier is given, shows the item at the current location."
+
+def prepare_config_commands(tool):
+ '''Prepare fixed commands for local configuration editing'''
+ module = ModuleInfo(name = CONFIG_MODULE_NAME, desc = "Configuration commands.")
+ cmd = CommandInfo(name = "show", desc = "Show configuration.")
+ param = ParamInfo(name = "argument", type = "string", optional=True, desc = "If you specify the argument 'all' (before the identifier), recursively show all child elements for the given identifier.")
+ cmd.add_param(param)
+ param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "show_json", desc = "Show full configuration in JSON format.")
+ param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "add", desc = "Add an entry to configuration list. If no value is given, a default value is added.")
+ param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ cmd.add_param(param)
+ param = ParamInfo(name = "value", type = "string", optional=True, desc = "Specifies a value to add to the list. It must be in correct JSON format and complete.")
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list.")
+ param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ cmd.add_param(param)
+ param = ParamInfo(name = "value", type = "string", optional=True, desc = "Specifies a value to remove from the list. It must be in correct JSON format and complete.")
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "set", desc = "Set a configuration value.")
+ param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ cmd.add_param(param)
+ param = ParamInfo(name = "value", type = "string", optional=False, desc = "Specifies a value to set. It must be in correct JSON format and complete.")
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "unset", desc = "Unset a configuration value (i.e. revert to the default, if any).")
+ param = ParamInfo(name = "identifier", type = "string", optional=False, desc = DEFAULT_IDENTIFIER_DESC)
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "diff", desc = "Show all local changes that have not been committed.")
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "revert", desc = "Revert all local changes.")
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "commit", desc = "Commit all local changes.")
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part.")
+ param = ParamInfo(name = "identifier", type="string", optional=False, desc = DEFAULT_IDENTIFIER_DESC)
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ tool.add_module_info(module)
+
+def check_port(option, opt_str, value, parser):
+ if (value < 0) or (value > 65535):
+ raise OptionValueError('%s requires a port number (0-65535)' % opt_str)
+ parser.values.port = value
+
+def check_addr(option, opt_str, value, parser):
+ ipstr = value
+ ip_family = socket.AF_INET
+ if (ipstr.find(':') != -1):
+ ip_family = socket.AF_INET6
+
+ try:
+ socket.inet_pton(ip_family, ipstr)
+ except:
+ raise OptionValueError("%s invalid ip address" % ipstr)
+
+ parser.values.addr = value
+
+def set_bindctl_options(parser):
+ parser.add_option('-p', '--port', dest='port', type='int',
+ action='callback', callback=check_port,
+ default='8080', help='port for cmdctl of bind10')
+
+ parser.add_option('-a', '--address', dest='addr', type='string',
+ action='callback', callback=check_addr,
+ default='127.0.0.1', help='IP address for cmdctl of bind10')
+
+ parser.add_option('-c', '--certificate-chain', dest='cert_chain',
+ type='string', action='store',
+ help='PEM formatted server certificate validation chain file')
+
+ parser.add_option('--csv-file-dir', dest='csv_file_dir', type='string',
+ default=None, action='store',
+ help='Directory to store the password CSV file')
+
+if __name__ == '__main__':
+ parser = OptionParser(version = VERSION)
+ set_bindctl_options(parser)
+ (options, args) = parser.parse_args()
+ server_addr = options.addr + ':' + str(options.port)
+ tool = BindCmdInterpreter(server_addr, pem_file=options.cert_chain,
+ csv_file_dir=options.csv_file_dir)
+ prepare_config_commands(tool)
+ tool.run()
diff --git a/src/bin/bindctl/cmdparse.py b/src/bin/bindctl/cmdparse.py
index ab891d7..c624cba 100644
--- a/src/bin/bindctl/cmdparse.py
+++ b/src/bin/bindctl/cmdparse.py
@@ -33,6 +33,7 @@ param_value_str = "(?P<param_value>[^\'\" ][^, ]+)"
param_value_with_quota_str = "[\"\'](?P<param_value>.+?)(?<!\\\)[\"\']"
next_params_str = "(?P<blank>\s*)(?P<comma>,?)(?P<next_params>.*)$"
+
PARAM_WITH_QUOTA_PATTERN = re.compile(param_name_str +
param_value_with_quota_str +
next_params_str)
@@ -40,8 +41,58 @@ PARAM_PATTERN = re.compile(param_name_str + param_value_str + next_params_str)
# Used for module and command name
NAME_PATTERN = re.compile("^\s*(?P<name>[\w]+)(?P<blank>\s*)(?P<others>.*)$")
+# this removes all whitespace in the given string, except when
+# between " quotes
+_remove_unquoted_whitespace = \
+ lambda text:'"'.join( it if i%2 else ''.join(it.split())
+ for i,it in enumerate(text.split('"')) )
+
+
+def _remove_list_and_map_whitespace(text):
+ """Returns a string where the whitespace between matching [ and ]
+ is removed, unless quoted"""
+ # regular expression aren't really the right tool, since we may have
+ # nested structures
+ result = []
+ start_pos = 0
+ pos = 0
+ list_count = 0
+ map_count = 0
+ cur_start_list_pos = None
+ cur_start_map_pos = None
+ for i in text:
+ if i == '[' and map_count == 0:
+ if list_count == 0:
+ result.append(text[start_pos:pos + 1])
+ cur_start_list_pos = pos + 1
+ list_count = list_count + 1
+ elif i == ']' and map_count == 0:
+ if list_count > 0:
+ list_count = list_count - 1
+ if list_count == 0:
+ result.append(_remove_unquoted_whitespace(text[cur_start_list_pos:pos + 1]))
+ start_pos = pos + 1
+ if i == '{' and list_count == 0:
+ if map_count == 0:
+ result.append(text[start_pos:pos + 1])
+ cur_start_map_pos = pos + 1
+ map_count = map_count + 1
+ elif i == '}' and list_count == 0:
+ if map_count > 0:
+ map_count = map_count - 1
+ if map_count == 0:
+ result.append(_remove_unquoted_whitespace(text[cur_start_map_pos:pos + 1]))
+ start_pos = pos + 1
+
+
+ pos = pos + 1
+ if start_pos <= len(text):
+ result.append(text[start_pos:len(text)])
+ return "".join(result)
+
+
class BindCmdParse:
- """ This class will parse the command line usr input into three part
+ """ This class will parse the command line user input into three parts:
module name, command, parameters
the first two parts are strings and parameter is one hash,
parameters part is optional
@@ -86,9 +137,12 @@ class BindCmdParse:
self._parse_params(param_str)
+ def _remove_list_whitespace(self, text):
+ return ""
def _parse_params(self, param_text):
"""convert a=b,c=d into one hash """
+ param_text = _remove_list_and_map_whitespace(param_text)
# Check parameter name "help"
param = NAME_PATTERN.match(param_text)
diff --git a/src/bin/bindctl/moduleinfo.py b/src/bin/bindctl/moduleinfo.py
index 015ef16..6e41dce 100644
--- a/src/bin/bindctl/moduleinfo.py
+++ b/src/bin/bindctl/moduleinfo.py
@@ -16,6 +16,8 @@
"""This module holds classes representing modules, commands and
parameters for use in bindctl"""
+import textwrap
+
try:
from collections import OrderedDict
except ImportError:
@@ -30,6 +32,9 @@ MODULE_NODE_NAME = 'module'
COMMAND_NODE_NAME = 'command'
PARAM_NODE_NAME = 'param'
+# this is used to align the descriptions in help output
+CONST_BINDCTL_HELP_INDENT_WIDTH=12
+
class ParamInfo:
"""One parameter of one command.
@@ -52,6 +57,12 @@ class ParamInfo:
def __str__(self):
return str("\t%s <type: %s> \t(%s)" % (self.name, self.type, self.desc))
+ def get_name(self):
+ return "%s <type: %s>" % (self.name, self.type)
+
+ def get_desc(self):
+ return self.desc
+
class CommandInfo:
"""One command which is provided by one bind10 module, it has zero
or more parameters
@@ -63,13 +74,18 @@ class CommandInfo:
self.params = OrderedDict()
# Set default parameter "help"
self.add_param(ParamInfo("help",
- desc = "Get help for command",
+ desc = "Get help for command.",
optional = True))
def __str__(self):
return str("%s \t(%s)" % (self.name, self.desc))
-
+ def get_name(self):
+ return self.name
+
+ def get_desc(self):
+ return self.desc;
+
def add_param(self, paraminfo):
"""Add a ParamInfo object to this CommandInfo"""
self.params[paraminfo.name] = paraminfo
@@ -144,22 +160,30 @@ class CommandInfo:
del params["help"]
if len(params) == 0:
- print("\tNo parameters for the command")
+ print("No parameters for the command")
return
- print("\n\tMandatory parameters:")
+ print("\nMandatory parameters:")
mandatory_infos = []
for info in params.values():
if not info.is_optional:
- print("\t", info)
+ print(" %s" % info.get_name())
+ print(textwrap.fill(info.get_desc(),
+ initial_indent=" ",
+ subsequent_indent=" ",
+ width=70))
mandatory_infos.append(info)
optional_infos = [info for info in params.values()
if info not in mandatory_infos]
if len(optional_infos) > 0:
- print("\n\tOptional parameters:")
+ print("\nOptional parameters:")
for info in optional_infos:
- print("\t", info)
+ print(" %s" % info.get_name())
+ print(textwrap.fill(info.get_desc(),
+ initial_indent=" ",
+ subsequent_indent=" ",
+ width=70))
class ModuleInfo:
@@ -172,11 +196,17 @@ class ModuleInfo:
self.desc = desc
self.commands = OrderedDict()
self.add_command(CommandInfo(name = "help",
- desc = "Get help for module"))
+ desc = "Get help for module."))
def __str__(self):
return str("%s \t%s" % (self.name, self.desc))
-
+
+ def get_name(self):
+ return self.name
+
+ def get_desc(self):
+ return self.desc
+
def add_command(self, command_info):
"""Add a CommandInfo to this ModuleInfo."""
self.commands[command_info.name] = command_info
@@ -201,8 +231,24 @@ class ModuleInfo:
def module_help(self):
"""Prints the help info for this module to stdout"""
print("Module ", self, "\nAvailable commands:")
- for k in self.commands.keys():
- print("\t", self.commands[k])
+ for k in self.commands.values():
+ n = k.get_name()
+ if len(n) >= CONST_BINDCTL_HELP_INDENT_WIDTH:
+ print(" %s" % n)
+ print(textwrap.fill(k.get_desc(),
+ initial_indent=" ",
+ subsequent_indent=" " +
+ " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
+ width=70))
+ else:
+ print(textwrap.fill("%s%s%s" %
+ (k.get_name(),
+ " "*(CONST_BINDCTL_HELP_INDENT_WIDTH - len(k.get_name())),
+ k.get_desc()),
+ initial_indent=" ",
+ subsequent_indent=" " +
+ " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
+ width=70))
def command_help(self, command):
"""Prints the help info for the command with the given name.
diff --git a/src/bin/bindctl/run_bindctl.sh.in b/src/bin/bindctl/run_bindctl.sh.in
index aa57022..730ce1e 100644
--- a/src/bin/bindctl/run_bindctl.sh.in
+++ b/src/bin/bindctl/run_bindctl.sh.in
@@ -26,5 +26,8 @@ export PYTHONPATH
B10_FROM_SOURCE=@abs_top_srcdir@
export B10_FROM_SOURCE
+BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
+export BIND10_MSGQ_SOCKET_FILE
+
cd ${BINDCTL_PATH}
-exec ${PYTHON_EXEC} -O bindctl $*
+exec ${PYTHON_EXEC} -O bindctl "$@"
diff --git a/src/bin/bindctl/tests/Makefile.am b/src/bin/bindctl/tests/Makefile.am
index aaf15d8..d2bb90f 100644
--- a/src/bin/bindctl/tests/Makefile.am
+++ b/src/bin/bindctl/tests/Makefile.am
@@ -1,12 +1,16 @@
-PYTESTS = bindctl_test.py
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = bindctl_test.py cmdparse_test.py
EXTRA_DIST = $(PYTESTS)
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
- env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_srcdir)/src/bin \
- $(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+ env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/bindctl:$(abs_top_srcdir)/src/bin \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/bindctl/tests/bindctl_test.py b/src/bin/bindctl/tests/bindctl_test.py
index 653c908..0635b32 100644
--- a/src/bin/bindctl/tests/bindctl_test.py
+++ b/src/bin/bindctl/tests/bindctl_test.py
@@ -17,6 +17,17 @@
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
from bindctl.moduleinfo import *
@@ -238,11 +249,167 @@ class TestNameSequence(unittest.TestCase):
assert self.random_names[i] == module_names[i+1]
i = i + 1
- def test_apply_cfg_command(self):
+# tine class to fake a UIModuleCCSession, but only the config data
+# parts for the next set of tests
+class FakeCCSession(MultiConfigData):
+ def __init__(self):
+ self._local_changes = {}
+ self._current_config = {}
+ self._specifications = {}
+ self.add_foo_spec()
+
+ def add_foo_spec(self):
+ spec = { "module_name": "foo",
+ "config_data": [
+ { "item_name": "an_int",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 1
+ },
+ { "item_name": "a_list",
+ "item_type": "list",
+ "item_optional": False,
+ "item_default": [],
+ "list_item_spec":
+ { "item_name": "a_string",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "bar"
+ }
+ }
+ ]
+ }
+ 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):
+ self.tool = bindcmd.BindCmdInterpreter()
+ 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 = '/'
- cmd = cmdparse.BindCmdParse("config set identifier=\"foo/bar\" value=\"5\"")
+
+ self.assertEqual((1, MultiConfigData.DEFAULT),
+ self.tool.config_data.get_value("/foo/an_int"))
+
+ cmd = cmdparse.BindCmdParse("config set identifier=\"foo/an_int\" value=\"5\"")
self.tool.apply_config_cmd(cmd)
-
+ self.assertEqual((5, MultiConfigData.LOCAL),
+ self.tool.config_data.get_value("/foo/an_int"))
+
+ # this should raise a NotFoundError
+ cmd = cmdparse.BindCmdParse("config set identifier=\"foo/bar\" value=\"[]\"")
+ self.assertRaises(isc.cc.data.DataNotFoundError, self.tool.apply_config_cmd, cmd)
+
+ # this should raise a TypeError
+ cmd = cmdparse.BindCmdParse("config set identifier=\"foo/an_int\" value=\"[]\"")
+ self.assertRaises(isc.cc.data.DataTypeError, self.tool.apply_config_cmd, cmd)
+
+ # this is a very specific one for use with a set of list tests
+ # to try out the flexibility of the parser (only in the next test)
+ def clt(self, full_cmd_string, item_value):
+ cmd = cmdparse.BindCmdParse(full_cmd_string)
+ self.tool.apply_config_cmd(cmd)
+ self.assertEqual(([item_value], MultiConfigData.LOCAL),
+ self.tool.config_data.get_value("/foo/a_list"))
+
+ def test_apply_cfg_command_list(self):
+ self.tool.location = '/'
+
+ self.assertEqual(([], MultiConfigData.DEFAULT),
+ self.tool.config_data.get_value("/foo/a_list"))
+
+ self.clt("config set identifier=\"foo/a_list\" value=[\"a\"]", "a")
+ self.clt("config set identifier=\"foo/a_list\" value =[\"b\"]", "b")
+ self.clt("config set identifier=\"foo/a_list\" value= [\"c\"]", "c")
+ self.clt("config set identifier=\"foo/a_list\" value = [\"d\"]", "d")
+ self.clt("config set identifier =\"foo/a_list\" value=[\"e\"]", "e")
+ self.clt("config set identifier= \"foo/a_list\" value=[\"f\"]", "f")
+ self.clt("config set identifier = \"foo/a_list\" value=[\"g\"]", "g")
+ self.clt("config set identifier = \"foo/a_list\" value = [\"h\"]", "h")
+ self.clt("config set identifier = \"foo/a_list\" value=[\"i\" ]", "i")
+ self.clt("config set identifier = \"foo/a_list\" value=[ \"j\"]", "j")
+ self.clt("config set identifier = \"foo/a_list\" value=[ \"k\" ]", "k")
+
+ # 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
@@ -257,16 +424,51 @@ class TestBindCmdInterpreter(unittest.TestCase):
writer.writerow(['name2'])
csvfile.close()
+ def test_csv_file_dir(self):
+ # Checking default value
+ if "HOME" in os.environ:
+ home_dir = os.environ["HOME"]
+ else:
+ home_dir = pwd.getpwnam(getpass.getuser()).pw_dir
+ self.assertEqual(home_dir + os.sep + '.bind10' + os.sep,
+ bindcmd.BindCmdInterpreter().csv_file_dir)
+
+ new_csv_dir = '/something/different/'
+ custom_cmd = bindcmd.BindCmdInterpreter(csv_file_dir=new_csv_dir)
+ self.assertEqual(new_csv_dir, custom_cmd.csv_file_dir)
+
def test_get_saved_user_info(self):
- cmd = FakeBindCmdInterpreter()
- users = cmd._get_saved_user_info('/notexist', 'cvs_file.cvs')
+ 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):
+ def setUp(self):
+ self.parser = TestOptParser()
+ set_bindctl_options(self.parser)
+
+ def test_csv_file_dir(self):
+ # by default the option is "undefined"
+ (options, _) = self.parser.parse_args([])
+ self.assertEqual(None, options.csv_file_dir)
+
+ # specify the option, valid case.
+ (options, _) = self.parser.parse_args(['--csv-file-dir', 'some_dir'])
+ self.assertEqual('some_dir', options.csv_file_dir)
+
+ # missing option arg; should trigger parser error.
+ self.assertRaises(OptsError, self.parser.parse_args,
+ ['--csv-file-dir'])
if __name__== "__main__":
unittest.main()
diff --git a/src/bin/bindctl/tests/cmdparse_test.py b/src/bin/bindctl/tests/cmdparse_test.py
new file mode 100644
index 0000000..9150ed3
--- /dev/null
+++ b/src/bin/bindctl/tests/cmdparse_test.py
@@ -0,0 +1,88 @@
+# 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.
+
+
+import unittest
+from bindctl import cmdparse
+
+class TestCmdParse(unittest.TestCase):
+
+ def test_remove_unquoted_whitespace(self):
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a"), "a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" a"), "a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a "), "a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" a "), "a")
+ self.assertNotEqual(cmdparse._remove_unquoted_whitespace("a"), "a ")
+ self.assertNotEqual(cmdparse._remove_unquoted_whitespace(" a"), " a")
+ self.assertNotEqual(cmdparse._remove_unquoted_whitespace("a "), "a ")
+ self.assertNotEqual(cmdparse._remove_unquoted_whitespace(" a "), " a ")
+ self.assertNotEqual(cmdparse._remove_unquoted_whitespace(" a "), "b")
+
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("\"abc\""), "\"abc\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" \"abc\""), "\"abc\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("\"abc\" "), "\"abc\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" \"abc\" "), "\"abc\"")
+
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("\" abc\""), "\" abc\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" \"a bc\""), "\"a bc\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("\"ab c\" "), "\"ab c\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" \"abc \" "), "\"abc \"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" \" a b c \" "), "\" a b c \"")
+
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a\" abc\"a"), "a\" abc\"a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a \"a bc\"a"), "a\"a bc\"a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a\"ab c\" a"), "a\"ab c\"a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a \"abc \" a"), "a\"abc \"a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a \" a b c \" a"), "a\" a b c \"a")
+
+ # short-hand function to make the set of tests more readable
+ def rws(self, a, b):
+ self.assertEqual(cmdparse._remove_list_and_map_whitespace(a), b)
+
+ def test_remove_list_whitespace(self):
+ self.rws("a", "a")
+ self.rws(" a ", " a ")
+ self.rws(" [a] ", " [a] ")
+ self.rws(" [ a] ", " [a] ")
+ self.rws(" [ a ] ", " [a] ")
+ self.rws(" [ a b c ] ", " [abc] ")
+ self.rws(" [ a \"b c\" ] ", " [a\"b c\"] ")
+ self.rws("a [ a \"b c\" ] a", "a [a\"b c\"] a")
+ self.rws("a] [ a \"b c\" ] a", "a] [a\"b c\"] a")
+ self.rws(" [ a [b c] ] ", " [a[bc]] ")
+ self.rws(" [ a b][ c d ] ", " [ab][cd] ")
+ self.rws(" [ a b] [ c d ] ", " [ab] [cd] ")
+
+ self.rws("a", "a")
+ self.rws(" a ", " a ")
+ self.rws(" {a} ", " {a} ")
+ self.rws(" { a} ", " {a} ")
+ self.rws(" { a } ", " {a} ")
+ self.rws(" { a b c } ", " {abc} ")
+ self.rws(" { a \"b c\" } ", " {a\"b c\"} ")
+ self.rws("a { a \"b c\" } a", "a {a\"b c\"} a")
+ self.rws("a} { a \"b c\" } a", "a} {a\"b c\"} a")
+ self.rws(" { a {b c} } ", " {a{bc}} ")
+ self.rws(" { a b}{ c d } ", " {ab}{cd} ")
+ self.rws(" { a b} { c d } ", " {ab} {cd} ")
+
+ self.rws(" [ a b]{ c d } ", " [ab]{cd} ")
+ self.rws(" [ a b{ c d }] ", " [ab{cd}] ")
+ self.rws(" [ a b{ \"c d\" }] ", " [ab{\"c d\"}] ")
+
+
+if __name__== "__main__":
+ unittest.main()
+
diff --git a/src/bin/cfgmgr/Makefile.am b/src/bin/cfgmgr/Makefile.am
index e092070..a41448b 100644
--- a/src/bin/cfgmgr/Makefile.am
+++ b/src/bin/cfgmgr/Makefile.am
@@ -19,7 +19,6 @@ b10-cfgmgr.8: b10-cfgmgr.xml
endif
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-cfgmgr: b10-cfgmgr.py
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" b10-cfgmgr.py >$@
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
old mode 100644
new mode 100755
index 98e33ff..5355582
--- a/src/bin/cfgmgr/b10-cfgmgr.py.in
+++ b/src/bin/cfgmgr/b10-cfgmgr.py.in
@@ -15,8 +15,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
import sys; sys.path.append ('@@PYTHONPATH@@')
from isc.config.cfgmgr import ConfigManager, ConfigManagerDataReadError
@@ -24,29 +22,54 @@ from isc.cc import SessionError
import isc.util.process
import signal
import os
+from optparse import OptionParser
isc.util.process.rename()
# If B10_FROM_SOURCE is set in the environment, we use data files
-# from a directory relative to that, otherwise we use the ones
-# installed on the system
+# from a directory relative to the value of that variable, or, if defined,
+# relative to the value of B10_FROM_SOURCE_LOCALSTATEDIR. Otherwise
+# we use the ones installed on the system.
+# B10_FROM_SOURCE_LOCALSTATEDIR is specifically intended to be used for
+# tests where we want to use variuos types of configuration within the test
+# environment. (We may want to make it even more generic so that the path is
+# passed from the boss process)
if "B10_FROM_SOURCE" in os.environ:
- DATA_PATH = os.environ["B10_FROM_SOURCE"]
+ if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
+ DATA_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
+ else:
+ DATA_PATH = os.environ["B10_FROM_SOURCE"]
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()
@@ -61,8 +84,6 @@ def main():
except ConfigManagerDataReadError as cmdre:
print("[b10-cfgmgr] " + str(cmdre))
return 2
- if cm:
- return cm.write_config()
return 0
if __name__ == "__main__":
diff --git a/src/bin/cfgmgr/b10-cfgmgr.xml b/src/bin/cfgmgr/b10-cfgmgr.xml
index 20224dc..785a058 100644
--- a/src/bin/cfgmgr/b10-cfgmgr.xml
+++ b/src/bin/cfgmgr/b10-cfgmgr.xml
@@ -17,7 +17,6 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
-<!-- $Id$ -->
<refentry>
<refentryinfo>
@@ -42,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>
@@ -88,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/Makefile.am b/src/bin/cfgmgr/tests/Makefile.am
index 3ae7e5d..16e6223 100644
--- a/src/bin/cfgmgr/tests/Makefile.am
+++ b/src/bin/cfgmgr/tests/Makefile.am
@@ -1,13 +1,17 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = b10-cfgmgr_test.py
EXTRA_DIST = $(PYTESTS)
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/cfgmgr \
- $(PYCOVERAGE) $(abs_builddir)/$$pytest || exit ; \
+ $(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
done
diff --git a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
index 05339a1..037a106 100644
--- a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
+++ b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
@@ -13,8 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id: cfgmgr_test.py 2126 2010-06-16 14:40:22Z jelte $
-
#
# Tests for the configuration manager run script
#
@@ -22,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
@@ -58,7 +57,8 @@ class TestConfigManagerStartup(unittest.TestCase):
self.assertTrue(b.cm.read_config_called)
self.assertTrue(b.cm.notify_boss_called)
self.assertTrue(b.cm.run_called)
- self.assertTrue(b.cm.write_config_called)
+ # if there are no changes, config is not written
+ self.assertFalse(b.cm.write_config_called)
self.assertTrue(b.cm.running)
b.signal_handler(None, None)
@@ -89,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/Makefile.am b/src/bin/cmdctl/Makefile.am
index 76572ab..04cf5e2 100644
--- a/src/bin/cmdctl/Makefile.am
+++ b/src/bin/cmdctl/Makefile.am
@@ -4,7 +4,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-cmdctl
-b10_cmdctldir = $(DESTDIR)$(pkgdatadir)
+b10_cmdctldir = $(pkgdatadir)
# NOTE: this will overwrite on install
# So these generic copies are placed in share/bind10 instead of to etc
@@ -33,7 +33,6 @@ endif
cmdctl.spec: cmdctl.spec.pre
$(SED) -e "s|@@SYSCONFDIR@@|$(sysconfdir)|" cmdctl.spec.pre >$@
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-cmdctl: cmdctl.py
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" cmdctl.py >$@
diff --git a/src/bin/cmdctl/b10-cmdctl.xml b/src/bin/cmdctl/b10-cmdctl.xml
index c0ac36a..06953a4 100644
--- a/src/bin/cmdctl/b10-cmdctl.xml
+++ b/src/bin/cmdctl/b10-cmdctl.xml
@@ -17,7 +17,6 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
-<!-- $Id$ -->
<refentry>
<refentryinfo>
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/cmdctl/run_b10-cmdctl.sh.in b/src/bin/cmdctl/run_b10-cmdctl.sh.in
index 631cdf9..6a519e1 100644
--- a/src/bin/cmdctl/run_b10-cmdctl.sh.in
+++ b/src/bin/cmdctl/run_b10-cmdctl.sh.in
@@ -22,6 +22,8 @@ CMD_CTRLD_PATH=@abs_top_builddir@/src/bin/cmdctl
PYTHONPATH=@abs_top_srcdir@/src/lib/python
export PYTHONPATH
-cd ${CMD_CTRLD_PATH}
-${PYTHON_EXEC} b10-cmdctl
+BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
+export BIND10_MSGQ_SOCKET_FILE
+cd ${CMD_CTRLD_PATH}
+exec ${PYTHON_EXEC} b10-cmdctl "$@"
diff --git a/src/bin/cmdctl/tests/Makefile.am b/src/bin/cmdctl/tests/Makefile.am
index 2c29dc1..6a4d7d4 100644
--- a/src/bin/cmdctl/tests/Makefile.am
+++ b/src/bin/cmdctl/tests/Makefile.am
@@ -1,14 +1,18 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
PYTESTS = cmdctl_test.py
EXTRA_DIST = $(PYTESTS)
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/cmdctl \
CMDCTL_SPEC_PATH=$(abs_top_builddir)/src/bin/cmdctl \
CMDCTL_SRC_PATH=$(abs_top_srcdir)/src/bin/cmdctl \
- $(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/host/host.cc b/src/bin/host/host.cc
index 2ee2339..c513b5a 100644
--- a/src/bin/host/host.cc
+++ b/src/bin/host/host.cc
@@ -58,7 +58,7 @@ host_lookup(const char* const name, const char* const type) {
msg.setOpcode(Opcode::QUERY());
msg.setRcode(Rcode::NOERROR());
if (recursive_bit) {
- msg.setHeaderFlag(MessageFlag::RD()); // set recursive bit
+ msg.setHeaderFlag(Message::HEADERFLAG_RD); // set recursive bit
}
msg.addQuestion(Question(Name(name),
@@ -70,12 +70,15 @@ host_lookup(const char* const name, const char* const type) {
msg.toWire(renderer);
struct addrinfo hints, *res;
- int e;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = 0; // not using AI_NUMERICHOST in case to bootstrap
- e = getaddrinfo(server, server_port, &hints, &res);
+ if (getaddrinfo(server, server_port, &hints, &res) != 0) {
+ cerr << "address/port conversion for " << server << ":"
+ << server_port << " failed" << endl;
+ return (1);
+ }
if (verbose) {
cout << "Trying \"" << name << "\"\n";
@@ -122,15 +125,16 @@ host_lookup(const char* const name, const char* const type) {
rmsg.fromWire(ibuffer);
if (!verbose) {
- for (RRsetIterator it = rmsg.beginSection(Section::ANSWER());
- it != rmsg.endSection(Section::ANSWER());
- ++it) {
+ for (RRsetIterator it =
+ rmsg.beginSection(Message::SECTION_ANSWER);
+ it != rmsg.endSection(Message::SECTION_ANSWER);
+ ++it) {
if ((*it)->getType() != RRType::A()) {
continue;
}
RdataIteratorPtr rit = (*it)->getRdataIterator();
- for (rit->first(); !rit->isLast(); rit->next()) {
+ for (; !rit->isLast(); rit->next()) {
// instead of using my name, maybe use returned label?
cout << name << " has address " <<
(*rit).getCurrent().toText() << endl;
diff --git a/src/bin/loadzone/b10-loadzone.xml b/src/bin/loadzone/b10-loadzone.xml
index 3bed19e..25e23a5 100644
--- a/src/bin/loadzone/b10-loadzone.xml
+++ b/src/bin/loadzone/b10-loadzone.xml
@@ -17,7 +17,6 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
-<!-- $Id$ -->
<refentry>
<refentryinfo>
diff --git a/src/bin/loadzone/run_loadzone.sh.in b/src/bin/loadzone/run_loadzone.sh.in
index 94aa47b..b7ac19f 100644
--- a/src/bin/loadzone/run_loadzone.sh.in
+++ b/src/bin/loadzone/run_loadzone.sh.in
@@ -21,5 +21,8 @@ export PYTHON_EXEC
PYTHONPATH=@abs_top_builddir@/src/lib/python
export PYTHONPATH
+BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
+export BIND10_MSGQ_SOCKET_FILE
+
LOADZONE_PATH=@abs_top_builddir@/src/bin/loadzone
-exec ${LOADZONE_PATH}/b10-loadzone $*
+exec ${LOADZONE_PATH}/b10-loadzone "$@"
diff --git a/src/bin/loadzone/tests/correct/Makefile.am b/src/bin/loadzone/tests/correct/Makefile.am
index a652239..a90cab2 100644
--- a/src/bin/loadzone/tests/correct/Makefile.am
+++ b/src/bin/loadzone/tests/correct/Makefile.am
@@ -1,4 +1,3 @@
-PYTESTS = correct_test.sh
EXTRA_DIST = get_zonedatas.py
EXTRA_DIST += include.db
EXTRA_DIST += inclsub.db
@@ -14,12 +13,8 @@ EXTRA_DIST += ttl2.db
EXTRA_DIST += ttlext.db
EXTRA_DIST += example.db
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
+# TODO: maybe use TESTS?
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
- 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/loadzone \
- $(SHELL) $(abs_builddir)/$$pytest || exit ; \
- done
+ echo Running test: correct_test.sh
+ $(SHELL) $(abs_builddir)/correct_test.sh
diff --git a/src/bin/loadzone/tests/correct/mix1.db b/src/bin/loadzone/tests/correct/mix1.db
index da562ff..a9d58a8 100644
--- a/src/bin/loadzone/tests/correct/mix1.db
+++ b/src/bin/loadzone/tests/correct/mix1.db
@@ -1,4 +1,3 @@
-; $Id: ttl1.db,v 1.6 2007/06/19 23:47:04 tbox Exp $
$ORIGIN mix1.
@ IN SOA ns hostmaster (
1 ; serial
diff --git a/src/bin/loadzone/tests/correct/ttlext.db b/src/bin/loadzone/tests/correct/ttlext.db
index 8ad6103..f8b96ea 100644
--- a/src/bin/loadzone/tests/correct/ttlext.db
+++ b/src/bin/loadzone/tests/correct/ttlext.db
@@ -1,4 +1,3 @@
-; $Id: ttl1.db,v 1.6 2007/06/19 23:47:04 tbox Exp $
$ORIGIN ttlext.
@ IN SOA ns hostmaster (
1 ; serial
diff --git a/src/bin/loadzone/tests/error/Makefile.am b/src/bin/loadzone/tests/error/Makefile.am
index 3e6b1e2..bbeec07 100644
--- a/src/bin/loadzone/tests/error/Makefile.am
+++ b/src/bin/loadzone/tests/error/Makefile.am
@@ -1,5 +1,3 @@
-PYTESTS = error_test.sh
-
EXTRA_DIST = error.known
EXTRA_DIST += formerr1.db
EXTRA_DIST += formerr2.db
@@ -14,12 +12,8 @@ EXTRA_DIST += keyerror3.db
EXTRA_DIST += originerr1.db
EXTRA_DIST += originerr2.db
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
+# TODO: use TESTS ?
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
- for pytest in $(PYTESTS) ; do \
- echo Running test: $$pytest ; \
- env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/bin/loadzone \
- $(SHELL) $(abs_builddir)/$$pytest || exit ; \
- done
+ echo Running test: error_test.sh
+ $(SHELL) $(abs_builddir)/error_test.sh
diff --git a/src/bin/msgq/Makefile.am b/src/bin/msgq/Makefile.am
index 9ed4717..61d4f23 100644
--- a/src/bin/msgq/Makefile.am
+++ b/src/bin/msgq/Makefile.am
@@ -16,7 +16,6 @@ b10-msgq.8: msgq.xml
endif
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-msgq: msgq.py
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" msgq.py >$@
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
old mode 100644
new mode 100755
index e09d519..06fe840
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -38,7 +38,9 @@ import isc.cc
isc.util.process.rename()
# This is the version that gets displayed to the user.
-__version__ = "v20091030 (Paving the DNS Parking Lot)"
+# The VERSION string consists of the module name, the module version
+# number, and the overall BIND 10 version number (set in configure.ac).
+VERSION = "b10-msgq 20110127 (BIND 10 @PACKAGE_VERSION@)"
class MsgQReceiveError(Exception): pass
@@ -125,6 +127,7 @@ class MsgQ:
self.hostname = socket.gethostname()
self.subs = SubscriptionManager()
self.lnames = {}
+ self.sendbuffs = {}
def setup_poller(self):
"""Set up the poll thing. Internal function."""
@@ -133,12 +136,29 @@ class MsgQ:
except AttributeError:
self.kqueue = select.kqueue()
- def add_kqueue_socket(self, socket):
- event = select.kevent(socket.fileno(),
- select.KQ_FILTER_READ,
+ def add_kqueue_socket(self, socket, write_filter=False):
+ """Add a kquque filter for a socket. By default the read
+ filter is used; if write_filter is set to True, the write
+ filter is used. We use a boolean value instead of a specific
+ filter constant, because kqueue filter values do not seem to
+ be defined on some systems. The use of boolean makes the
+ interface restrictive because there are other filters, but this
+ method is mostly only for our internal use, so it should be
+ acceptable at least for now."""
+ filter_type = select.KQ_FILTER_WRITE if write_filter else \
+ select.KQ_FILTER_READ
+ event = select.kevent(socket.fileno(), filter_type,
select.KQ_EV_ADD | select.KQ_EV_ENABLE)
self.kqueue.control([event], 0)
+ def delete_kqueue_socket(self, socket, write_filter=False):
+ """Delete a kqueue filter for socket. See add_kqueue_socket()
+ for the semantics and notes about write_filter."""
+ filter_type = select.KQ_FILTER_WRITE if write_filter else \
+ select.KQ_FILTER_READ
+ event = select.kevent(socket.fileno(), filter_type,
+ select.KQ_EV_DELETE)
+ self.kqueue.control([event], 0)
def setup_listener(self):
"""Set up the listener socket. Internal function."""
@@ -185,6 +205,12 @@ class MsgQ:
# TODO: When we have logging, we might want
# to add a debug message here that a new connection
# was made
+ self.register_socket(newsocket)
+
+ def register_socket(self, newsocket):
+ """
+ Internal function to insert a socket. Used by process_accept and some tests.
+ """
self.sockets[newsocket.fileno()] = newsocket
lname = self.newlname()
self.lnames[lname] = newsocket
@@ -196,10 +222,10 @@ class MsgQ:
def process_socket(self, fd):
"""Process a read on a socket."""
- sock = self.sockets[fd]
- if sock == None:
+ if not fd in self.sockets:
sys.stderr.write("[b10-msgq] Got read on Strange Socket fd %d\n" % fd)
return
+ sock = self.sockets[fd]
# sys.stderr.write("[b10-msgq] Got read on fd %d\n" %fd)
self.process_packet(fd, sock)
@@ -211,7 +237,9 @@ class MsgQ:
lname = [ k for k, v in self.lnames.items() if v == sock ][0]
del self.lnames[lname]
sock.close()
- self.sockets[fd] = None
+ del self.sockets[fd]
+ if fd in self.sendbuffs:
+ del self.sendbuffs[fd]
sys.stderr.write("[b10-msgq] Closing socket fd %d\n" % fd)
def getbytes(self, fd, sock, length):
@@ -285,6 +313,9 @@ class MsgQ:
self.process_command_unsubscribe(sock, routing, data)
elif cmd == 'getlname':
self.process_command_getlname(sock, routing, data)
+ elif cmd == 'ping':
+ # Command for testing purposes
+ self.process_command_ping(sock, routing, data)
else:
sys.stderr.write("[b10-msgq] Invalid command: %s\n" % cmd)
@@ -303,10 +334,67 @@ class MsgQ:
return ret
def sendmsg(self, sock, env, msg = None):
- sock.send(self.preparemsg(env, msg))
+ self.send_prepared_msg(sock, self.preparemsg(env, msg))
+
+ def __send_data(self, sock, data):
+ try:
+ # We set the socket nonblocking, MSG_DONTWAIT doesn't exist
+ # on some OSes
+ sock.setblocking(0)
+ return sock.send(data)
+ except socket.error as e:
+ if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK:
+ return 0
+ else:
+ raise e
+ finally:
+ # And set it back again
+ sock.setblocking(1)
def send_prepared_msg(self, sock, msg):
- sock.send(msg)
+ # Try to send the data, but only if there's nothing waiting
+ fileno = sock.fileno()
+ if fileno in self.sendbuffs:
+ amount_sent = 0
+ else:
+ amount_sent = self.__send_data(sock, msg)
+
+ # Still something to send
+ if amount_sent < len(msg):
+ now = time.clock()
+ # Append it to buffer (but check the data go away)
+ if fileno in self.sendbuffs:
+ (last_sent, buff) = self.sendbuffs[fileno]
+ if now - last_sent > 0.1:
+ self.kill_socket(fileno, sock)
+ return
+ buff += msg
+ else:
+ buff = msg[amount_sent:]
+ last_sent = now
+ if self.poller:
+ self.poller.register(fileno, select.POLLIN |
+ select.POLLOUT)
+ else:
+ self.add_kqueue_socket(sock, True)
+ self.sendbuffs[fileno] = (last_sent, buff)
+
+ def __process_write(self, fileno):
+ # Try to send some data from the buffer
+ (_, msg) = self.sendbuffs[fileno]
+ sock = self.sockets[fileno]
+ amount_sent = self.__send_data(sock, msg)
+ # Keep the rest
+ msg = msg[amount_sent:]
+ if len(msg) == 0:
+ # If there's no more, stop requesting for write availability
+ if self.poller:
+ self.poller.register(fileno, select.POLLIN)
+ else:
+ self.delete_kqueue_socket(sock, True)
+ del self.sendbuffs[fileno]
+ else:
+ self.sendbuffs[fileno] = (time.clock(), msg)
def newlname(self):
"""Generate a unique connection identifier for this socket.
@@ -315,6 +403,9 @@ class MsgQ:
self.connection_counter += 1
return "%x_%x@%s" % (time.time(), self.connection_counter, self.hostname)
+ def process_command_ping(self, sock, routing, data):
+ self.sendmsg(sock, { "type" : "pong" }, data)
+
def process_command_getlname(self, sock, routing, data):
lname = [ k for k, v in self.lnames.items() if v == sock ][0]
self.sendmsg(sock, { "type" : "getlname" }, { "lname" : lname })
@@ -377,22 +468,29 @@ class MsgQ:
if fd == self.listen_socket.fileno():
self.process_accept()
else:
- self.process_socket(fd)
+ if event & select.POLLOUT:
+ self.__process_write(fd)
+ if event & select.POLLIN:
+ self.process_socket(fd)
def run_kqueue(self):
while True:
events = self.kqueue.control(None, 10)
if not events:
raise RuntimeError('serve: kqueue returned no events')
-
+
for event in events:
if event.ident == self.listen_socket.fileno():
self.process_accept()
else:
- if event.flags & select.KQ_FILTER_READ and event.data > 0:
+ if event.filter == select.KQ_FILTER_WRITE:
+ self.__process_write(event.ident)
+ if event.filter == select.KQ_FILTER_READ and \
+ event.data > 0:
self.process_socket(event.ident)
elif event.flags & select.KQ_EV_EOF:
- self.kill_socket(event.ident, self.sockets[event.ident])
+ self.kill_socket(event.ident,
+ self.sockets[event.ident])
def shutdown(self):
"""Stop the MsgQ master."""
@@ -421,7 +519,7 @@ if __name__ == "__main__":
parser.values.msgq_port = intval
# Parse any command-line options.
- parser = OptionParser(version=__version__)
+ parser = OptionParser(version=VERSION)
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
help="display more about what is going on")
parser.add_option("-s", "--socket-file", dest="msgq_socket_file",
@@ -433,7 +531,7 @@ if __name__ == "__main__":
# Announce startup.
if options.verbose:
- sys.stdout.write("[b10-msgq] MsgQ %s\n" % __version__)
+ sys.stdout.write("[b10-msgq] %s\n" % VERSION)
msgq = MsgQ(options.msgq_socket_file, options.verbose)
diff --git a/src/bin/msgq/msgq.xml b/src/bin/msgq/msgq.xml
index dc60a01..fd9518d 100644
--- a/src/bin/msgq/msgq.xml
+++ b/src/bin/msgq/msgq.xml
@@ -17,7 +17,6 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
-<!-- $Id$ -->
<refentry>
<refentryinfo>
diff --git a/src/bin/msgq/run_msgq.sh.in b/src/bin/msgq/run_msgq.sh.in
index b3c672b..3e464be 100644
--- a/src/bin/msgq/run_msgq.sh.in
+++ b/src/bin/msgq/run_msgq.sh.in
@@ -23,5 +23,8 @@ MYPATH_PATH=@abs_top_builddir@/src/bin/msgq
PYTHONPATH=@abs_top_srcdir@/src/lib/python
export PYTHONPATH
+BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
+export BIND10_MSGQ_SOCKET_FILE
+
cd ${MYPATH_PATH}
-exec ${PYTHON_EXEC} -O b10-msgq $*
+exec ${PYTHON_EXEC} -O b10-msgq "$@"
diff --git a/src/bin/msgq/tests/Makefile.am b/src/bin/msgq/tests/Makefile.am
index 095ece8..0bbb964 100644
--- a/src/bin/msgq/tests/Makefile.am
+++ b/src/bin/msgq/tests/Makefile.am
@@ -1,14 +1,18 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = msgq_test.py
EXTRA_DIST = $(PYTESTS)
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_builddir)/src/bin/msgq:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python \
BIND10_TEST_SOCKET_FILE=$(builddir)/test_msgq_socket.sock \
- $(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index 2c10ed2..f926845 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -3,10 +3,14 @@ from msgq import SubscriptionManager, MsgQ
import unittest
import os
import socket
+import signal
+import sys
+import time
+import isc.cc
#
-# Currently only the subscription part is implemented... I'd have to mock
-# out a socket, which, while not impossible, is not trivial.
+# Currently only the subscription part and some sending is implemented...
+# I'd have to mock out a socket, which, while not impossible, is not trivial.
#
class TestSubscriptionManager(unittest.TestCase):
@@ -108,5 +112,126 @@ class TestSubscriptionManager(unittest.TestCase):
msgq = MsgQ("/does/not/exist")
self.assertRaises(socket.error, msgq.setup)
+class SendNonblock(unittest.TestCase):
+ """
+ Tests that the whole thing will not get blocked if someone does not read.
+ """
+
+ def terminate_check(self, task, timeout=30):
+ """
+ Runs task in separate process (task is a function) and checks
+ it terminates sooner than timeout.
+ """
+ task_pid = os.fork()
+ if task_pid == 0:
+ # Kill the forked process after timeout by SIGALRM
+ signal.alarm(timeout)
+ # Run the task
+ # If an exception happens or we run out of time, we terminate
+ # with non-zero
+ task()
+ # If we got here, then everything worked well and in time
+ # In that case, we terminate successfully
+ sys.exit(0) # needs exit code
+ else:
+ (pid, status) = os.waitpid(task_pid, 0)
+ self.assertEqual(0, status,
+ "The task did not complete successfully in time")
+
+ def infinite_sender(self, sender):
+ """
+ Sends data until an exception happens. socket.error is caught,
+ as it means the socket got closed. Sender is called to actually
+ send the data.
+ """
+ msgq = MsgQ()
+ # We do only partial setup, so we don't create the listening socket
+ msgq.setup_poller()
+ (read, write) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+ msgq.register_socket(write)
+ # Keep sending while it is not closed by the msgq
+ try:
+ while True:
+ sender(msgq, write)
+ except socket.error:
+ pass
+
+ def test_infinite_sendmsg(self):
+ """
+ Tries sending messages (and not reading them) until it either times
+ out (in blocking call, wrong) or closes it (correct).
+ """
+ data = "data"
+ for i in range(1, 10):
+ data += data
+ self.terminate_check(lambda: self.infinite_sender(
+ lambda msgq, socket: msgq.sendmsg(socket, {}, {"message" : data})))
+
+ def test_infinite_sendprepared(self):
+ """
+ Tries sending data (and not reading them) until it either times
+ out (in blocking call, wrong) or closes it (correct).
+ """
+ data = b"data"
+ for i in range(1, 10):
+ data += data
+ self.terminate_check(lambda: self.infinite_sender(
+ lambda msgq, socket: msgq.send_prepared_msg(socket, data)))
+
+ def send_many(self, data):
+ """
+ Tries that sending a command many times and getting an answer works.
+ """
+ msgq = MsgQ()
+ # msgq.run needs to compare with the listen_socket, so we provide
+ # a replacement
+ class DummySocket:
+ def fileno():
+ return -1
+ msgq.listen_socket = DummySocket
+ (queue, out) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+ def run():
+ length = len(data)
+ queue_pid = os.fork()
+ if queue_pid == 0:
+ signal.alarm(120)
+ msgq.setup_poller()
+ msgq.register_socket(queue)
+ msgq.run()
+ else:
+ try:
+ def killall(signum, frame):
+ os.kill(queue_pid, signal.SIGTERM)
+ sys.exit(1)
+ signal.signal(signal.SIGALRM, killall)
+ msg = msgq.preparemsg({"type" : "ping"}, data)
+ now = time.clock()
+ while time.clock() - now < 0.2:
+ out.sendall(msg)
+ # Check the answer
+ (routing, received) = msgq.read_packet(out.fileno(),
+ out)
+ self.assertEqual({"type" : "pong"},
+ isc.cc.message.from_wire(routing))
+ self.assertEqual(data, received)
+ finally:
+ os.kill(queue_pid, signal.SIGTERM)
+ self.terminate_check(run)
+
+ def test_small_sends(self):
+ """
+ Tests sending small data many times.
+ """
+ self.send_many(b"data")
+
+ def test_large_sends(self):
+ """
+ Tests sending large data many times.
+ """
+ data = b"data"
+ for i in range(1, 20):
+ data = data + data
+ self.send_many(data)
+
if __name__ == '__main__':
unittest.main()
diff --git a/src/bin/resolver/Makefile.am b/src/bin/resolver/Makefile.am
new file mode 100644
index 0000000..54e15bd
--- /dev/null
+++ b/src/bin/resolver/Makefile.am
@@ -0,0 +1,62 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/cc -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/asiolink
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/asiolink
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+CLEANFILES = *.gcno *.gcda resolver.spec spec_config.h
+
+man_MANS = b10-resolver.8
+EXTRA_DIST = $(man_MANS) b10-resolver.xml
+
+if ENABLE_MAN
+
+b10-resolver.8: b10-resolver.xml
+ xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-resolver.xml
+
+endif
+
+resolver.spec: resolver.spec.pre
+ $(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" resolver.spec.pre >$@
+
+spec_config.h: spec_config.h.pre
+ $(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
+
+BUILT_SOURCES = spec_config.h
+pkglibexec_PROGRAMS = b10-resolver
+b10_resolver_SOURCES = resolver.cc resolver.h
+b10_resolver_SOURCES += response_scrubber.cc response_scrubber.h
+b10_resolver_SOURCES += $(top_builddir)/src/bin/auth/change_user.h
+b10_resolver_SOURCES += $(top_builddir)/src/bin/auth/common.h
+b10_resolver_SOURCES += main.cc
+b10_resolver_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/cc/libcc.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
+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
+
+# TODO: config.h.in is wrong because doesn't honor pkgdatadir
+# and can't use @datadir@ because doesn't expand default ${prefix}
+b10_resolverdir = $(pkgdatadir)
+b10_resolver_DATA = resolver.spec
+
diff --git a/src/bin/resolver/b10-resolver.8 b/src/bin/resolver/b10-resolver.8
new file mode 100644
index 0000000..849092c
--- /dev/null
+++ b/src/bin/resolver/b10-resolver.8
@@ -0,0 +1,129 @@
+'\" t
+.\" Title: b10-resolver
+.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
+.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
+.\" Date: February 17, 2011
+.\" Manual: BIND10
+.\" Source: BIND10
+.\" Language: English
+.\"
+.TH "B10\-RESOLVER" "8" "February 17, 2011" "BIND10" "BIND10"
+.\" -----------------------------------------------------------------
+.\" * set default formatting
+.\" -----------------------------------------------------------------
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.\" -----------------------------------------------------------------
+.\" * MAIN CONTENT STARTS HERE *
+.\" -----------------------------------------------------------------
+.SH "NAME"
+b10-resolver \- Recursive DNS server
+.SH "SYNOPSIS"
+.HP \w'\fBb10\-resolver\fR\ 'u
+\fBb10\-resolver\fR [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR]
+.SH "DESCRIPTION"
+.PP
+The
+\fBb10\-resolver\fR
+daemon provides the BIND 10 recursive DNS server\&. Normally it is started by the
+\fBbind10\fR(8)
+boss process\&.
+.PP
+This daemon communicates with other BIND 10 components over a
+\fBb10-msgq\fR(8)
+C\-Channel connection\&. If this connection is not established,
+\fBb10\-resolver\fR
+will exit\&.
+.PP
+It also receives its configurations from
+\fBb10-cfgmgr\fR(8)\&.
+.SH "OPTIONS"
+.PP
+The arguments are as follows:
+.PP
+\fB\-u \fR\fB\fIusername\fR\fR
+.RS 4
+The user name of the
+\fBb10\-resolver\fR
+daemon\&. If specified, the daemon changes the process owner to the specified user\&. The
+\fIusername\fR
+must be either a valid numeric user ID or a valid user name\&. By default the daemon runs as the user who invokes it\&.
+.RE
+.PP
+\fB\-v\fR
+.RS 4
+Enabled verbose mode\&. This enables diagnostic messages to STDERR\&.
+.RE
+.SH "CONFIGURATION AND COMMANDS"
+.PP
+The configurable settings are:
+.PP
+
+\fIforward_addresses\fR
+defines the list of addresses and ports that
+\fBb10\-resolver\fR
+should forward queries to\&. Defining this enables forwarding\&.
+.PP
+
+\fIlisten_on\fR
+is a list of addresses and ports for
+\fBb10\-resolver\fR
+to listen on\&. The list items are the
+\fIaddress\fR
+string and
+\fIport\fR
+number\&. The defaults are address ::1 port 53 and address 127\&.0\&.0\&.1 port 53\&.
+.PP
+
+\fIretries\fR
+is the number of times to retry (resend query) after a query timeout (\fItimeout_query\fR)\&. The default is 3\&.
+.PP
+
+\fIroot_addresses\fR
+is a list of addresses and ports for
+\fBb10\-resolver\fR
+to use directly as root servers to start resolving\&. The list items are the
+\fIaddress\fR
+string and
+\fIport\fR
+number\&. If empty, a hardcoded address for F\-root (192\&.5\&.5\&.241) is used\&.
+.PP
+
+\fItimeout_client\fR
+is the number of milliseconds to wait before timing out the incoming client query\&. If set to \-1, this timeout is disabled\&. The default is 4000\&. After this timeout, a SERVFAIL is sent back to the client asking the question\&. (The lookup may continue after the timeout, but a later answer is not returned for the now\-past query\&.)
+.PP
+
+\fItimeout_lookup\fR
+is the number of milliseconds before it stops trying the query\&. If set to \-1, this timeout is disabled\&. The default is 30000\&.
+.PP
+
+
+\fItimeout_query\fR
+is the number of milliseconds to wait before it retries a query\&. If set to \-1, this timeout is disabled\&. The default is 2000\&.
+.PP
+The configuration command is:
+.PP
+
+\fBshutdown\fR
+exits
+\fBb10\-resolver\fR\&. (Note that the BIND 10 boss process will restart this service\&.)
+.SH "SEE ALSO"
+.PP
+
+\fBb10-cfgmgr\fR(8),
+\fBb10-cmdctl\fR(8),
+\fBb10-msgq\fR(8),
+\fBbind10\fR(8),
+BIND 10 Guide\&.
+.SH "HISTORY"
+.PP
+The
+\fBb10\-resolver\fR
+daemon was first coded in September 2010\&. The initial implementation only provided forwarding\&. Iteration was introduced in January 2011\&.
+
+.SH "COPYRIGHT"
+.br
+Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")
+.br
diff --git a/src/bin/resolver/b10-resolver.xml b/src/bin/resolver/b10-resolver.xml
new file mode 100644
index 0000000..bdf4f8a
--- /dev/null
+++ b/src/bin/resolver/b10-resolver.xml
@@ -0,0 +1,245 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+ [<!ENTITY mdash "—">]>
+<!--
+ - Copyright (C) 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
+ - copyright notice and this permission notice appear in all copies.
+ -
+ - THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ - PERFORMANCE OF THIS SOFTWARE.
+-->
+
+<refentry>
+
+ <refentryinfo>
+ <date>February 17, 2011</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>b10-resolver</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo>BIND10</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>b10-resolver</refname>
+ <refpurpose>Recursive DNS server</refpurpose>
+ </refnamediv>
+
+ <docinfo>
+ <copyright>
+ <year>2010</year>
+ <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+ </copyright>
+ </docinfo>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>b10-resolver</command>
+ <arg><option>-u <replaceable>username</replaceable></option></arg>
+ <arg><option>-v</option></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>The <command>b10-resolver</command> daemon provides the BIND 10
+ recursive DNS server. Normally it is started by the
+ <citerefentry><refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ boss process.
+ </para>
+
+ <para>
+ This daemon communicates with other BIND 10 components over a
+ <citerefentry><refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ C-Channel connection. If this connection is not established,
+ <command>b10-resolver</command> will exit.
+ </para>
+
+ <para>
+ It also receives its configurations from
+<citerefentry><refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+ </para>
+
+<!--
+ <note><para>
+ Future versions will introduce lookup of local authoritative
+ data (as in <command>b10-auth</command>) and DNSSEC validation.
+ </para></note>
+-->
+
+ </refsect1>
+
+ <refsect1>
+ <title>OPTIONS</title>
+
+ <para>The arguments are as follows:</para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><option>-u <replaceable>username</replaceable></option></term>
+ <listitem>
+ <para>
+ The user name of the <command>b10-resolver</command> daemon.
+ If specified, the daemon changes the process owner to the
+ specified user.
+ The <replaceable>username</replaceable> must be either a
+ valid numeric user ID or a valid user name.
+ By default the daemon runs as the user who invokes it.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-v</option></term>
+ <listitem><para>
+ Enabled verbose mode. This enables diagnostic messages to
+ STDERR.
+ </para></listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </refsect1>
+
+ <refsect1>
+ <title>CONFIGURATION AND COMMANDS</title>
+ <para>
+ The configurable settings are:
+ </para>
+
+ <para>
+ <varname>forward_addresses</varname> defines the list of addresses
+ and ports that <command>b10-resolver</command> should forward
+ queries to.
+ Defining this enables forwarding.
+<!-- TODO: list
+address
+ ::1
+port
+ 53
+-->
+ </para>
+
+<!-- trac384:
+
+once that is merged you can for instance do 'config add Resolver/forward_addresses { "port": 123 } and it will fill in the rest (in this case ::1 for the address)
+
+-->
+
+ <para>
+ <varname>listen_on</varname> is a list of addresses and ports for
+ <command>b10-resolver</command> to listen on.
+ The list items are the <varname>address</varname> string
+ and <varname>port</varname> number.
+ The defaults are address ::1 port 53 and
+ address 127.0.0.1 port 53.
+<!-- TODO: but defaults are not used, Trac #518 -->
+ </para>
+
+ <para>
+ <varname>retries</varname> is the number of times to retry
+ (resend query) after a query timeout
+ (<varname>timeout_query</varname>).
+ The default is 3.
+ </para>
+
+ <para>
+ <varname>root_addresses</varname> is a list of addresses and ports
+ for <command>b10-resolver</command> to use directly as
+ root servers to start resolving.
+ The list items are the <varname>address</varname> string
+ and <varname>port</varname> number.
+ If empty, a hardcoded address for F-root (192.5.5.241) is used.
+ </para>
+
+ <para>
+ <varname>timeout_client</varname> is the number of milliseconds
+ to wait before timing out the incoming client query.
+ If set to -1, this timeout is disabled.
+ The default is 4000.
+ After this timeout, a SERVFAIL is sent back to the client asking
+ the question.
+ (The lookup may continue after the timeout, but a later answer
+ is not returned for the now-past query.)
+ </para>
+
+ <para>
+ <varname>timeout_lookup</varname> is the number of milliseconds
+ before it stops trying the query.
+ If set to -1, this timeout is disabled.
+ The default is 30000.
+ </para>
+
+ <para>
+<!-- previous timeout was renamed to timeout_query -->
+ <varname>timeout_query</varname> is the number of milliseconds to
+ wait before it retries a query.
+ If set to -1, this timeout is disabled.
+ The default is 2000.
+ </para>
+
+<!-- TODO: formating -->
+ <para>
+ The configuration command is:
+ </para>
+
+ <para>
+ <command>shutdown</command> exits <command>b10-resolver</command>.
+ (Note that the BIND 10 boss process will restart this service.)
+ </para>
+
+ </refsect1>
+
+<!--
+ <refsect1>
+ <title>FILES</title>
+ <para>
+ None.
+ </para>
+ </refsect1>
+-->
+
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citetitle>BIND 10 Guide</citetitle>.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>HISTORY</title>
+ <para>
+ The <command>b10-resolver</command> daemon was first coded in
+ September 2010. The initial implementation only provided
+ forwarding. Iteration was introduced in January 2011.
+<!-- TODO: document when caching was added -->
+<!-- TODO: document when validation was added -->
+ </para>
+ </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->
diff --git a/src/bin/resolver/main.cc b/src/bin/resolver/main.cc
new file mode 100644
index 0000000..1d76e0b
--- /dev/null
+++ b/src/bin/resolver/main.cc
@@ -0,0 +1,225 @@
+// Copyright (C) 2009 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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <string>
+#include <iostream>
+
+#include <boost/foreach.hpp>
+
+#include <asiolink/asiolink.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/buffer.h>
+#include <dns/rcode.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+
+#include <cc/session.h>
+#include <cc/data.h>
+#include <config/ccsession.h>
+
+#include <xfr/xfrout_client.h>
+
+#include <auth/change_user.h>
+#include <auth/common.h>
+
+#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;
+using namespace isc::cc;
+using namespace isc::config;
+using namespace isc::data;
+using isc::log::dlog;
+using namespace asiolink;
+
+namespace {
+
+static const string PROGRAM = "Resolver";
+
+IOService io_service;
+static boost::shared_ptr<Resolver> resolver;
+
+ConstElementPtr
+my_config_handler(ConstElementPtr new_config) {
+ return (resolver->updateConfig(new_config));
+}
+
+ConstElementPtr
+my_command_handler(const string& command, ConstElementPtr args) {
+ ConstElementPtr answer = createAnswer();
+
+ if (command == "print_message") {
+ cout << args << endl;
+ /* let's add that message to our answer as well */
+ answer = createAnswer(0, args);
+ } else if (command == "shutdown") {
+ io_service.stop();
+ }
+
+ return (answer);
+}
+
+void
+usage() {
+ cerr << "Usage: b10-resolver [-u user] [-v]" << endl;
+ cerr << "\t-u: change process UID to the specified user" << endl;
+ cerr << "\t-v: verbose output" << endl;
+ exit(1);
+}
+} // end of anonymous namespace
+
+int
+main(int argc, char* argv[]) {
+ isc::log::dprefix = "b10-resolver";
+ int ch;
+ const char* uid = NULL;
+
+ while ((ch = getopt(argc, argv, "u:v")) != -1) {
+ switch (ch) {
+ case 'u':
+ uid = optarg;
+ break;
+ case 'v':
+ isc::log::denabled = true;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ if (argc - optind > 0) {
+ usage();
+ }
+
+ if (isc::log::denabled) { // Show the command line
+ string cmdline("Command line:");
+ for (int i = 0; i < argc; ++ i) {
+ cmdline = cmdline + " " + argv[i];
+ }
+ dlog(cmdline);
+ }
+
+ int ret = 0;
+
+ Session* cc_session = NULL;
+ ModuleCCSession* config_session = NULL;
+ try {
+ string specfile;
+ if (getenv("B10_FROM_BUILD")) {
+ specfile = string(getenv("B10_FROM_BUILD")) +
+ "/src/bin/resolver/resolver.spec";
+ } else {
+ specfile = string(RESOLVER_SPECFILE_LOCATION);
+ }
+
+ 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.");
+
+ cc_session = new Session(io_service.get_io_service());
+ dlog("Configuration session channel created.");
+
+ config_session = new ModuleCCSession(specfile, *cc_session,
+ my_config_handler,
+ my_command_handler);
+ dlog("Configuration channel established.");
+
+ // FIXME: This does not belong here, but inside Boss
+ if (uid != NULL) {
+ changeUser(uid);
+ }
+
+ resolver->setConfigSession(config_session);
+ dlog("Config loaded");
+
+ dlog("Server started.");
+ io_service.run();
+ } catch (const std::exception& ex) {
+ dlog(string("Server failed: ") + ex.what(),true);
+ ret = 1;
+ }
+
+ delete config_session;
+ delete cc_session;
+
+ return (ret);
+}
diff --git a/src/bin/resolver/resolver.cc b/src/bin/resolver/resolver.cc
new file mode 100644
index 0000000..2322076
--- /dev/null
+++ b/src/bin/resolver/resolver.cc
@@ -0,0 +1,658 @@
+// Copyright (C) 2009 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 <netinet/in.h>
+
+#include <algorithm>
+#include <vector>
+#include <cassert>
+
+#include <asiolink/asiolink.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <config/ccsession.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/name.h>
+#include <dns/question.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <server_common/portconfig.h>
+
+#include <resolve/recursive_query.h>
+
+#include <log/dummylog.h>
+
+#include <resolver/resolver.h>
+
+using namespace std;
+
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::config;
+using isc::log::dlog;
+using namespace asiolink;
+using namespace isc::server_common::portconfig;
+
+class ResolverImpl {
+private:
+ // prohibit copy
+ ResolverImpl(const ResolverImpl& source);
+ ResolverImpl& operator=(const ResolverImpl& source);
+public:
+ ResolverImpl() :
+ config_session_(NULL),
+ query_timeout_(2000),
+ client_timeout_(4000),
+ lookup_timeout_(30000),
+ retries_(3),
+ rec_query_(NULL)
+ {}
+
+ ~ResolverImpl() {
+ queryShutdown();
+ }
+
+ 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,
+ nsas, cache,
+ upstream_,
+ upstream_root_,
+ query_timeout_,
+ client_timeout_,
+ lookup_timeout_,
+ retries_);
+ }
+
+ void queryShutdown() {
+ // only shut down if we have actually called querySetup before
+ // (this is not a safety check, just to prevent logging of
+ // actions that are not performed
+ if (rec_query_) {
+ dlog("Query shutdown");
+ delete rec_query_;
+ rec_query_ = NULL;
+ }
+ }
+
+ void setForwardAddresses(const AddressList& upstream,
+ DNSService *dnss)
+ {
+ upstream_ = upstream;
+ if (dnss) {
+ if (!upstream_.empty()) {
+ dlog("Setting forward addresses:");
+ BOOST_FOREACH(const AddressPair& address, upstream) {
+ dlog(" " + address.first + ":" +
+ boost::lexical_cast<string>(address.second));
+ }
+ } else {
+ dlog("No forward addresses, running in recursive mode");
+ }
+ }
+ }
+
+ void setRootAddresses(const AddressList& upstream_root,
+ DNSService *dnss)
+ {
+ upstream_root_ = upstream_root;
+ if (dnss) {
+ if (!upstream_root_.empty()) {
+ dlog("Setting root addresses:");
+ BOOST_FOREACH(const AddressPair& address, upstream_root) {
+ dlog(" " + address.first + ":" +
+ boost::lexical_cast<string>(address.second));
+ }
+ } else {
+ dlog("No root addresses");
+ }
+ }
+ }
+
+ void resolve(const isc::dns::QuestionPtr& question,
+ const isc::resolve::ResolverInterface::CallbackPtr& callback);
+
+ void processNormalQuery(const Question& question,
+ MessagePtr answer_message,
+ OutputBufferPtr buffer,
+ DNSServer* server);
+
+ /// Currently non-configurable, but will be.
+ static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
+
+ /// These members are public because Resolver accesses them directly.
+ ModuleCCSession* config_session_;
+ /// Addresses of the root nameserver(s)
+ AddressList upstream_root_;
+ /// Addresses of the forward nameserver
+ AddressList upstream_;
+ /// Addresses we listen on
+ AddressList listen_;
+
+ /// Timeout for outgoing queries in milliseconds
+ int query_timeout_;
+ /// Timeout for incoming client queries in milliseconds
+ int client_timeout_;
+ /// Timeout for lookup processing in milliseconds
+ int lookup_timeout_;
+
+ /// Number of retries after timeout
+ unsigned retries_;
+
+private:
+
+ /// Object to handle upstream queries
+ RecursiveQuery* rec_query_;
+};
+
+/*
+ * std::for_each has a broken interface. It makes no sense in a language
+ * without lambda functions/closures. These two classes emulate the lambda
+ * functions so for_each can be used.
+ */
+class QuestionInserter {
+public:
+ QuestionInserter(MessagePtr message) : message_(message) {}
+ void operator()(const QuestionPtr question) {
+ dlog(string("Adding question ") + question->getName().toText() +
+ " to message");
+ message_->addQuestion(question);
+ }
+ MessagePtr message_;
+};
+
+
+// TODO: REMOVE, USE isc::resolve::MakeErrorMessage?
+void
+makeErrorMessage(MessagePtr message, MessagePtr answer_message,
+ OutputBufferPtr buffer, const Rcode& rcode)
+{
+ // extract the parameters that should be kept.
+ // XXX: with the current implementation, it's not easy to set EDNS0
+ // depending on whether the query had it. So we'll simply omit it.
+ const qid_t qid = message->getQid();
+ const bool rd = message->getHeaderFlag(Message::HEADERFLAG_RD);
+ const bool cd = message->getHeaderFlag(Message::HEADERFLAG_CD);
+ const Opcode& opcode = message->getOpcode();
+ vector<QuestionPtr> questions;
+
+ // answer_message is actually ignored right now,
+ // see the comment in #607
+ answer_message->setRcode(rcode);
+ answer_message->setOpcode(opcode);
+ answer_message->setQid(qid);
+
+ // If this is an error to a query or notify, we should also copy the
+ // question section.
+ if (opcode == Opcode::QUERY() || opcode == Opcode::NOTIFY()) {
+ questions.assign(message->beginQuestion(), message->endQuestion());
+ }
+
+ message->clear(Message::RENDER);
+ message->setQid(qid);
+ message->setOpcode(opcode);
+ message->setHeaderFlag(Message::HEADERFLAG_QR);
+ if (rd) {
+ message->setHeaderFlag(Message::HEADERFLAG_RD);
+ }
+ if (cd) {
+ message->setHeaderFlag(Message::HEADERFLAG_CD);
+ }
+ for_each(questions.begin(), questions.end(), QuestionInserter(message));
+ message->setRcode(rcode);
+ MessageRenderer renderer(*buffer);
+ message->toWire(renderer);
+
+ dlog(string("Sending an error response (") +
+ boost::lexical_cast<string>(renderer.getLength()) + " bytes):\n" +
+ message->toText());
+}
+
+// This is a derived class of \c DNSLookup, to serve as a
+// callback in the asiolink module. It calls
+// Resolver::processMessage() on a single DNS message.
+class MessageLookup : public DNSLookup {
+public:
+ MessageLookup(Resolver* srv) : server_(srv) {}
+
+ // \brief Handle the DNS Lookup
+ virtual void operator()(const IOMessage& io_message,
+ MessagePtr query_message,
+ MessagePtr answer_message,
+ OutputBufferPtr buffer,
+ DNSServer* server) const
+ {
+ server_->processMessage(io_message, query_message,
+ answer_message, buffer, server);
+ }
+private:
+ Resolver* server_;
+};
+
+// This is a derived class of \c DNSAnswer, to serve as a
+// callback in the asiolink module. It takes a completed
+// set of answer data from the DNS lookup and assembles it
+// into a wire-format response.
+class MessageAnswer : public DNSAnswer {
+public:
+ virtual void operator()(const IOMessage& io_message,
+ MessagePtr query_message,
+ MessagePtr answer_message,
+ OutputBufferPtr buffer) const
+ {
+ const qid_t qid = query_message->getQid();
+ const bool rd = query_message->getHeaderFlag(Message::HEADERFLAG_RD);
+ const bool cd = query_message->getHeaderFlag(Message::HEADERFLAG_CD);
+
+ // The opcode and question section should have already been set,
+ // fill in the final details of the answer message
+ answer_message->setQid(qid);
+
+ answer_message->setHeaderFlag(Message::HEADERFLAG_QR);
+ answer_message->setHeaderFlag(Message::HEADERFLAG_RA);
+ answer_message->setHeaderFlag(Message::HEADERFLAG_RD, rd);
+ answer_message->setHeaderFlag(Message::HEADERFLAG_CD, cd);
+
+ // Now we can clear the buffer and render the new message into it
+ buffer->clear();
+ MessageRenderer renderer(*buffer);
+
+ ConstEDNSPtr edns(query_message->getEDNS());
+ const bool dnssec_ok = edns && edns->getDNSSECAwareness();
+ if (edns) {
+ EDNSPtr edns_response(new EDNS());
+ edns_response->setDNSSECAwareness(dnssec_ok);
+
+ // TODO: We should make our own edns bufsize length configurable
+ edns_response->setUDPSize(Message::DEFAULT_MAX_EDNS0_UDPSIZE);
+ answer_message->setEDNS(edns_response);
+ }
+
+ if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
+ if (edns) {
+ renderer.setLengthLimit(edns->getUDPSize());
+ } else {
+ renderer.setLengthLimit(Message::DEFAULT_MAX_UDPSIZE);
+ }
+ } else {
+ renderer.setLengthLimit(65535);
+ }
+
+ answer_message->toWire(renderer);
+
+ dlog(string("sending a response (") +
+ boost::lexical_cast<string>(renderer.getLength()) + "bytes): \n" +
+ answer_message->toText());
+ }
+};
+
+// This is a derived class of \c SimpleCallback, to serve
+// as a callback in the asiolink module. It checks for queued
+// configuration messages, and executes them if found.
+class ConfigCheck : public SimpleCallback {
+public:
+ ConfigCheck(Resolver* srv) : server_(srv) {}
+ virtual void operator()(const IOMessage&) const {
+ if (server_->getConfigSession()->hasQueuedMsgs()) {
+ server_->getConfigSession()->checkCommand();
+ }
+ }
+private:
+ Resolver* server_;
+};
+
+Resolver::Resolver() :
+ impl_(new ResolverImpl()),
+ checkin_(new ConfigCheck(this)),
+ dns_lookup_(new MessageLookup(this)),
+ dns_answer_(new MessageAnswer),
+ configured_(false)
+{}
+
+Resolver::~Resolver() {
+ delete impl_;
+ delete checkin_;
+ delete dns_lookup_;
+ delete dns_answer_;
+}
+
+void
+Resolver::setDNSService(asiolink::DNSService& dnss) {
+ dnss_ = &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;
+}
+
+ModuleCCSession*
+Resolver::getConfigSession() const {
+ return (impl_->config_session_);
+}
+
+void
+Resolver::resolve(const isc::dns::QuestionPtr& question,
+ const isc::resolve::ResolverInterface::CallbackPtr& callback)
+{
+ impl_->resolve(question, callback);
+}
+
+
+void
+Resolver::processMessage(const IOMessage& io_message,
+ MessagePtr query_message,
+ MessagePtr answer_message,
+ OutputBufferPtr buffer,
+ DNSServer* server)
+{
+ dlog("Got a DNS message");
+ InputBuffer request_buffer(io_message.getData(), io_message.getDataSize());
+ // First, check the header part. If we fail even for the base header,
+ // just drop the message.
+ try {
+ query_message->parseHeader(request_buffer);
+
+ // Ignore all responses.
+ if (query_message->getHeaderFlag(Message::HEADERFLAG_QR)) {
+ dlog("Received unexpected response, ignoring");
+ server->resume(false);
+ return;
+ }
+ } catch (const Exception& ex) {
+ dlog(string("DNS packet exception: ") + ex.what(),true);
+ server->resume(false);
+ return;
+ }
+
+ // Parse the message. On failure, return an appropriate error.
+ try {
+ query_message->fromWire(request_buffer);
+ } catch (const DNSProtocolError& error) {
+ dlog(string("returning ") + error.getRcode().toText() + ": " +
+ error.what());
+ makeErrorMessage(query_message, answer_message,
+ buffer, error.getRcode());
+ server->resume(true);
+ return;
+ } catch (const Exception& ex) {
+ dlog(string("returning SERVFAIL: ") + ex.what());
+ makeErrorMessage(query_message, answer_message,
+ buffer, Rcode::SERVFAIL());
+ server->resume(true);
+ return;
+ } // other exceptions will be handled at a higher layer.
+
+ dlog("received a message:\n" + query_message->toText());
+
+ // Perform further protocol-level validation.
+ bool sendAnswer = true;
+ if (query_message->getOpcode() == Opcode::NOTIFY()) {
+ makeErrorMessage(query_message, answer_message,
+ buffer, Rcode::NOTAUTH());
+ dlog("Notify arrived, but we are not authoritative");
+ } else if (query_message->getOpcode() != Opcode::QUERY()) {
+ dlog("Unsupported opcode (got: " + query_message->getOpcode().toText() +
+ ", expected: " + Opcode::QUERY().toText());
+ makeErrorMessage(query_message, answer_message,
+ buffer, Rcode::NOTIMP());
+ } else if (query_message->getRRCount(Message::SECTION_QUESTION) != 1) {
+ dlog("The query contained " +
+ boost::lexical_cast<string>(query_message->getRRCount(
+ Message::SECTION_QUESTION) + " questions, exactly one expected"));
+ makeErrorMessage(query_message, answer_message,
+ buffer, Rcode::FORMERR());
+ } else {
+ ConstQuestionPtr question = *query_message->beginQuestion();
+ const RRType &qtype = question->getType();
+ if (qtype == RRType::AXFR()) {
+ if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
+ makeErrorMessage(query_message, answer_message,
+ buffer, Rcode::FORMERR());
+ } else {
+ makeErrorMessage(query_message, answer_message,
+ buffer, Rcode::NOTIMP());
+ }
+ } 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.
+ sendAnswer = false;
+ impl_->processNormalQuery(*question, answer_message,
+ buffer, server);
+ }
+ }
+
+ if (sendAnswer) {
+ server->resume(true);
+ }
+}
+
+void
+ResolverImpl::resolve(const QuestionPtr& question,
+ const isc::resolve::ResolverInterface::CallbackPtr& callback)
+{
+ rec_query_->resolve(question, callback);
+}
+
+void
+ResolverImpl::processNormalQuery(const Question& question,
+ MessagePtr answer_message,
+ OutputBufferPtr buffer,
+ DNSServer* server)
+{
+ dlog("Processing normal query");
+ rec_query_->resolve(question, answer_message, buffer, server);
+}
+
+ConstElementPtr
+Resolver::updateConfig(ConstElementPtr config) {
+ dlog("New config comes: " + config->toWire());
+
+ try {
+ // Parse forward_addresses
+ ConstElementPtr rootAddressesE(config->get("root_addresses"));
+ AddressList rootAddresses(parseAddresses(rootAddressesE,
+ "root_addresses"));
+ ConstElementPtr forwardAddressesE(config->get("forward_addresses"));
+ AddressList forwardAddresses(parseAddresses(forwardAddressesE,
+ "forward_addresses"));
+ ConstElementPtr listenAddressesE(config->get("listen_on"));
+ AddressList listenAddresses(parseAddresses(listenAddressesE,
+ "listen_on"));
+ bool set_timeouts(false);
+ int qtimeout = impl_->query_timeout_;
+ int ctimeout = impl_->client_timeout_;
+ int ltimeout = impl_->lookup_timeout_;
+ unsigned retries = impl_->retries_;
+ ConstElementPtr qtimeoutE(config->get("timeout_query")),
+ ctimeoutE(config->get("timeout_client")),
+ ltimeoutE(config->get("timeout_lookup")),
+ retriesE(config->get("retries"));
+ if (qtimeoutE) {
+ // It should be safe to just get it, the config manager should
+ // check for us
+ qtimeout = qtimeoutE->intValue();
+ if (qtimeout < -1) {
+ isc_throw(BadValue, "Query timeout too small");
+ }
+ set_timeouts = true;
+ }
+ if (ctimeoutE) {
+ ctimeout = ctimeoutE->intValue();
+ if (ctimeout < -1) {
+ isc_throw(BadValue, "Client timeout too small");
+ }
+ set_timeouts = true;
+ }
+ if (ltimeoutE) {
+ ltimeout = ltimeoutE->intValue();
+ if (ltimeout < -1) {
+ isc_throw(BadValue, "Lookup timeout too small");
+ }
+ set_timeouts = true;
+ }
+ if (retriesE) {
+ if (retriesE->intValue() < 0) {
+ isc_throw(BadValue, "Negative number of retries");
+ }
+ retries = retriesE->intValue();
+ set_timeouts = true;
+ }
+ // Everything OK, so commit the changes
+ // listenAddresses can fail to bind, so try them first
+ bool need_query_restart = false;
+
+ 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);
+ need_query_restart = true;
+ }
+ if (rootAddressesE) {
+ setRootAddresses(rootAddresses);
+ need_query_restart = true;
+ }
+ if (set_timeouts) {
+ setTimeouts(qtimeout, ctimeout, ltimeout, retries);
+ need_query_restart = true;
+ }
+
+ if (need_query_restart) {
+ impl_->queryShutdown();
+ impl_->querySetup(*dnss_, *nsas_, *cache_);
+ }
+ setConfigured();
+ return (isc::config::createAnswer());
+ } catch (const isc::Exception& error) {
+ dlog(string("error in config: ") + error.what(),true);
+ return (isc::config::createAnswer(1, error.what()));
+ }
+}
+
+void
+Resolver::setForwardAddresses(const AddressList& addresses)
+{
+ impl_->setForwardAddresses(addresses, dnss_);
+}
+
+void
+Resolver::setRootAddresses(const AddressList& addresses)
+{
+ impl_->setRootAddresses(addresses, dnss_);
+}
+
+bool
+Resolver::isForwarding() const {
+ return (!impl_->upstream_.empty());
+}
+
+AddressList
+Resolver::getForwardAddresses() const {
+ return (impl_->upstream_);
+}
+
+AddressList
+Resolver::getRootAddresses() const {
+ return (impl_->upstream_root_);
+}
+
+void
+Resolver::setListenAddresses(const AddressList& addresses) {
+ installListenAddresses(addresses, impl_->listen_, *dnss_);
+}
+
+void
+Resolver::setTimeouts(int query_timeout, int client_timeout,
+ int lookup_timeout, unsigned retries) {
+ dlog("Setting query timeout to " + boost::lexical_cast<string>(query_timeout) +
+ ", client timeout to " + boost::lexical_cast<string>(client_timeout) +
+ ", lookup timeout to " + boost::lexical_cast<string>(lookup_timeout) +
+ " and retry count to " + boost::lexical_cast<string>(retries));
+ impl_->query_timeout_ = query_timeout;
+ impl_->client_timeout_ = client_timeout;
+ impl_->lookup_timeout_ = lookup_timeout;
+ impl_->retries_ = retries;
+}
+
+int
+Resolver::getQueryTimeout() const {
+ return impl_->query_timeout_;
+}
+
+int
+Resolver::getClientTimeout() const {
+ return impl_->client_timeout_;
+}
+
+int
+Resolver::getLookupTimeout() const {
+ return impl_->lookup_timeout_;
+}
+
+int
+Resolver::getRetries() const {
+ return impl_->retries_;
+}
+
+AddressList
+Resolver::getListenAddresses() const {
+ return (impl_->listen_);
+}
diff --git a/src/bin/resolver/resolver.h b/src/bin/resolver/resolver.h
new file mode 100644
index 0000000..002e58b
--- /dev/null
+++ b/src/bin/resolver/resolver.h
@@ -0,0 +1,249 @@
+// Copyright (C) 2009 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 __RESOLVER_H
+#define __RESOLVER_H 1
+
+#include <string>
+#include <vector>
+#include <utility>
+
+#include <cc/data.h>
+#include <config/ccsession.h>
+
+#include <asiolink/asiolink.h>
+
+#include <nsas/nameserver_address_store.h>
+#include <cache/resolver_cache.h>
+
+#include <resolve/resolver_interface.h>
+
+class ResolverImpl;
+
+/**
+ * \short The recursive nameserver.
+ *
+ * It is a concreate class implementing recursive DNS server protocol
+ * processing. It is responsible for handling incoming DNS requests. It parses
+ * them, passes them deeper into the resolving machinery and then creates the
+ * answer. It doesn't really know about chasing referrals and similar, it
+ * simply plugs the parts that know into the network handling code.
+ */
+class Resolver : public isc::resolve::ResolverInterface {
+ ///
+ /// \name Constructors, Assignment Operator and Destructor.
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private.
+ //@{
+private:
+ Resolver(const Resolver& source);
+ Resolver& operator=(const Resolver& source);
+public:
+ /// The constructor.
+ Resolver();
+ ~Resolver();
+ //@}
+
+ virtual void resolve(
+ const isc::dns::QuestionPtr& question,
+ const isc::resolve::ResolverInterface::CallbackPtr& callback);
+
+ /// \brief Process an incoming DNS message, then signal 'server' to resume
+ ///
+ /// A DNS query (or other message) has been received by a \c DNSServer
+ /// object. Find an answer, then post the \c DNSServer object on the
+ /// I/O service queue and return. When the server resumes, it can
+ /// send the reply.
+ ///
+ /// \param io_message The raw message received
+ /// \param query_message Pointer to the query Message object we
+ /// received from the client
+ /// \param answer_message Pointer to the anwer Message object we
+ /// shall return to the client
+ /// \param buffer Pointer to an \c OutputBuffer for the resposne
+ /// \param server Pointer to the \c DNSServer
+ void processMessage(const asiolink::IOMessage& io_message,
+ isc::dns::MessagePtr query_message,
+ isc::dns::MessagePtr answer_message,
+ isc::dns::OutputBufferPtr buffer,
+ asiolink::DNSServer* server);
+
+ /// \brief Set and get the config session
+ isc::config::ModuleCCSession* getConfigSession() const;
+ void setConfigSession(isc::config::ModuleCCSession* config_session);
+
+ /// \brief Handle commands from the config session
+ isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr config);
+
+ /// \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_); }
+
+ /// \brief Return pointer to the DNS Answer callback function
+ asiolink::DNSAnswer* getDNSAnswerProvider() { return (dns_answer_); }
+
+ /// \brief Return pointer to the Checkin callback function
+ 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
+ * to. If the list is empty, this server is set to full recursive mode.
+ * If it is non-empty, it switches to forwarder.
+ *
+ * @param addresses The list of addresses to use (each one is the address
+ * and port pair).
+ */
+ void setForwardAddresses(const std::vector<std::pair<std::string,
+ uint16_t> >& addresses);
+ /**
+ * \short Get list of upstream addresses.
+ *
+ * \see setForwardAddresses.
+ */
+ std::vector<std::pair<std::string, uint16_t> > getForwardAddresses() const;
+ /// Return if we are in forwarding mode (if not, we are in fully recursive)
+ bool isForwarding() const;
+
+ /**
+ * \brief Specify the list of root nameservers.
+ *
+ * Specify the list of addresses of root nameservers
+ *
+ * @param addresses The list of addresses to use (each one is the address
+ * and port pair).
+ */
+ void setRootAddresses(const std::vector<std::pair<std::string,
+ uint16_t> >& addresses);
+
+ /**
+ * \short Get list of root addresses.
+ *
+ * \see setRootAddresses.
+ */
+ std::vector<std::pair<std::string, uint16_t> > getRootAddresses() const;
+
+ /**
+ * Set and get the addresses we listen on.
+ */
+ void setListenAddresses(const std::vector<std::pair<std::string,
+ uint16_t> >& addresses);
+ std::vector<std::pair<std::string, uint16_t> > getListenAddresses() const;
+
+ /**
+ * \short Set options related to timeouts.
+ *
+ * This sets the time of timeout and number of retries.
+ * \param query_timeout The timeout we use for queries we send
+ * \param client_timeout The timeout at which point we send back a
+ * SERVFAIL (while continuing to resolve the query)
+ * \param lookup_timeout The timeout at which point we give up and
+ * stop.
+ * \param retries The number of retries (0 means try the first time only,
+ * do not retry).
+ */
+ void setTimeouts(int query_timeout = 2000,
+ int client_timeout = 4000,
+ int lookup_timeout = 30000,
+ unsigned retries = 3);
+
+ /**
+ * \short Get info about timeouts.
+ *
+ * \returns Timeout and retries (as described in setTimeouts).
+ */
+ std::pair<int, unsigned> getTimeouts() const;
+
+ /**
+ * \brief Get the timeout for outgoing queries
+ *
+ * \returns Timeout for outgoing queries
+ */
+ int getQueryTimeout() const;
+
+ /**
+ * \brief Get the timeout for incoming client queries
+ *
+ * After this timeout, a SERVFAIL shall be sent back
+ * (internal resolving on the query will continue, see
+ * \c getLookupTimeout())
+ *
+ * \returns Timeout for outgoing queries
+ */
+ int getClientTimeout() const;
+
+ /**
+ * \brief Get the timeout for lookups
+ *
+ * After this timeout, internal processing shall stop
+ */
+ int getLookupTimeout() const;
+
+ /**
+ * \brief Get the number of retries for outgoing queries
+ *
+ * If a query times out (value of \c getQueryTimeout()), we
+ * will retry this number of times
+ */
+ int getRetries() const;
+
+private:
+ ResolverImpl* impl_;
+ asiolink::DNSService* dnss_;
+ 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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/bin/resolver/resolver.spec.pre.in b/src/bin/resolver/resolver.spec.pre.in
new file mode 100644
index 0000000..9df1e75
--- /dev/null
+++ b/src/bin/resolver/resolver.spec.pre.in
@@ -0,0 +1,127 @@
+{
+ "module_spec": {
+ "module_name": "Resolver",
+ "module_description": "Recursive service",
+ "config_data": [
+ {
+ "item_name": "timeout_query",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 2000
+ },
+ {
+ "item_name": "timeout_client",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 4000
+ },
+ {
+ "item_name": "timeout_lookup",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 30000
+ },
+ {
+ "item_name": "retries",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 3
+ },
+ {
+ "item_name": "forward_addresses",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [],
+ "list_item_spec" : {
+ "item_name": "address",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "address",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "::1"
+ },
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 53
+ }
+ ]
+ }
+ },
+ {
+ "item_name": "root_addresses",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [],
+ "list_item_spec" : {
+ "item_name": "address",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "address",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "::1"
+ },
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 53
+ }
+ ]
+ }
+ },
+ {
+ "item_name": "listen_on",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [
+ {
+ "address": "::1",
+ "port": 53
+ },
+ {
+ "address": "127.0.0.1",
+ "port": 53
+ }
+ ],
+ "list_item_spec": {
+ "item_name": "address",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "address",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "::1"
+ },
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 53
+ }
+ ]
+ }
+ }
+ ],
+ "commands": [
+ {
+ "command_name": "shutdown",
+ "command_description": "Shut down recursive DNS server",
+ "command_args": []
+ }
+ ]
+ }
+}
+
diff --git a/src/bin/resolver/response_scrubber.cc b/src/bin/resolver/response_scrubber.cc
new file mode 100644
index 0000000..060a8b1
--- /dev/null
+++ b/src/bin/resolver/response_scrubber.cc
@@ -0,0 +1,189 @@
+
+// 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 <iostream>
+#include <vector>
+#include <dns/message.h>
+#include <dns/rrset.h>
+#include <dns/name.h>
+#include "response_scrubber.h"
+
+using namespace isc::dns;
+using namespace std;
+
+// Compare addresses etc.
+
+ResponseScrubber::Category ResponseScrubber::addressCheck(
+ const asiolink::IOEndpoint& to, const asiolink::IOEndpoint& from)
+{
+ if (from.getProtocol() == to.getProtocol()) {
+ if (from.getAddress() == to.getAddress()) {
+ if (from.getPort() == to.getPort()) {
+ return (ResponseScrubber::SUCCESS);
+ } else {
+ return (ResponseScrubber::PORT);
+ }
+ } else {
+ return (ResponseScrubber::ADDRESS);
+ }
+ }
+ return (ResponseScrubber::PROTOCOL);
+}
+
+// Do a general scrubbing. The QNAMES of RRsets in the specified section are
+// compared against the list of name given and if they are not equal and not in
+// the specified relationship (generally superdomain or subdomain) to at least
+// of of the given names, they are removed.
+
+unsigned int
+ResponseScrubber::scrubSection(Message& message,
+ const vector<const Name*>& names,
+ const NameComparisonResult::NameRelation connection,
+ const Message::Section section)
+{
+ unsigned int count = 0; // Count of RRsets removed
+ unsigned int kept = 0; // Count of RRsets kept
+ bool removed = true; // Set true if RRset removed in a pass
+
+ // Need to go through the section multiple times as when an RRset is
+ // removed, all iterators into the section are invalidated. This condition
+ // is flagged by "remove" being set true when an RRset is removed.
+
+ while (removed) {
+ RRsetIterator i = message.beginSection(section);
+
+ // Skips the ones that have been checked (and retained) in a previous
+ // pass through the "while" loop. (Although RRset removal invalidates
+ // iterators, it does not change the relative order of the retained
+ // RRsets in the section.)
+ for (int j = 0; j < kept; ++j) {
+ ++i;
+ }
+
+ // Start looking at the remaining entries in the section.
+ removed = false;
+ for (; (i != message.endSection(section)) && (!removed); ++i) {
+
+ // Loop through the list of names given and see if any are in the
+ // given relationship with the QNAME of this RRset
+ bool nomatch = true;
+ for (vector<const Name*>::const_iterator n = names.begin();
+ ((n != names.end()) && nomatch); ++n) {
+ NameComparisonResult result = (*i)->getName().compare(**n);
+ NameComparisonResult::NameRelation relationship =
+ result.getRelation();
+ if ((relationship == NameComparisonResult::EQUAL) ||
+ (relationship == connection)) {
+
+ // RRset in the specified relationship, so a match has
+ // been found
+ nomatch = false;
+ }
+ }
+
+ // Remove the RRset if there was no match to one of the given names.
+ if (nomatch) {
+ message.removeRRset(section, i);
+ ++count; // One more RRset removed
+ removed = true; // Something was removed
+ } else {
+
+ // There was a match so this is one more entry we can skip next
+ // time.
+ ++kept;
+ }
+ }
+ }
+
+ return count;
+}
+
+// Perform the scrubbing of all sections of the message.
+
+unsigned int
+ResponseScrubber::scrubAllSections(Message& message, const Name& bailiwick) {
+
+ // Leave the question section alone. Just go through the RRsets in the
+ // answer, authority and additional sections.
+ unsigned int count = 0;
+ const vector<const Name*> bailiwick_names(1, &bailiwick);
+ count += scrubSection(message, bailiwick_names,
+ NameComparisonResult::SUBDOMAIN, Message::SECTION_ANSWER);
+ count += scrubSection(message, bailiwick_names,
+ NameComparisonResult::SUBDOMAIN, Message::SECTION_AUTHORITY);
+ count += scrubSection(message, bailiwick_names,
+ NameComparisonResult::SUBDOMAIN, Message::SECTION_ADDITIONAL);
+
+ return count;
+}
+
+// Scrub across sections.
+
+unsigned int
+ResponseScrubber::scrubCrossSections(isc::dns::Message& message) {
+
+ // Get a list of the names in the answer section or, failing this, the
+ // question section. Note that pointers to the names within "message" are
+ // stored; this is OK as the relevant sections in "message" will not change
+ // during the lifetime of this method (it only affects the authority
+ // section).
+ vector<const Name*> source;
+ if (message.getRRCount(Message::SECTION_ANSWER) != 0) {
+ for (RRsetIterator i = message.beginSection(Message::SECTION_ANSWER);
+ i != message.endSection(Message::SECTION_ANSWER); ++i) {
+ const Name& qname = (*i)->getName();
+ source.push_back(&qname);
+ }
+
+ } else {
+ for (QuestionIterator i = message.beginQuestion();
+ i != message.endQuestion(); ++i) {
+ const Name& qname = (*i)->getName();
+ source.push_back(&qname);
+ }
+ }
+
+ if (source.empty()) {
+ // TODO: Log the fact - should be at least a question present
+ return (0);
+ }
+
+ // Could be duplicates, especially in the answer section, so sort the
+ // names and remove them.
+ sort(source.begin(), source.end(), ResponseScrubber::compareNameLt);
+ vector<const Name*>::iterator endunique =
+ unique(source.begin(), source.end(), ResponseScrubber::compareNameEq);
+ source.erase(endunique, source.end());
+
+ // Now purge the authority section of RRsets that are not equal to or a
+ // superdomain of the names in the question/answer section.
+ return (scrubSection(message, source,
+ NameComparisonResult::SUPERDOMAIN, Message::SECTION_AUTHORITY));
+
+}
+
+// Scrub a message
+
+unsigned int
+ResponseScrubber::scrub(const isc::dns::MessagePtr& message,
+ const isc::dns::Name& bailiwick)
+{
+ unsigned int sections_removed = scrubAllSections(*message, bailiwick);
+ sections_removed += scrubCrossSections(*message);
+
+ return sections_removed;
+}
+
+
diff --git a/src/bin/resolver/response_scrubber.h b/src/bin/resolver/response_scrubber.h
new file mode 100644
index 0000000..680aa5a
--- /dev/null
+++ b/src/bin/resolver/response_scrubber.h
@@ -0,0 +1,422 @@
+// 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 __RESPONSE_SCRUBBER_H
+#define __RESPONSE_SCRUBBER_H
+
+/// \page DataScrubbing Data Scrubbing
+/// \section DataScrubbingIntro Introduction
+/// When a response is received from an authoritative server, it should be
+/// checked to ensure that the data contained in it is valid. Signed data is
+/// not a problem - validating the signatures is a sufficient check. But
+/// unsigned data in a response is more of a problem. (Note that even data from
+/// signed zones may be not be signed, e.g. delegations are not signed.) In
+/// particular, how do we know that the server from which the response was
+/// received was authoritive for the data it returned?
+///
+/// The part of the code that checks for this is the "Data Scrubbing" module.
+/// Although it includes the checking of IP addresses and ports, it is called
+/// "Scrubbing" because it "scrubs" the returned message and removes doubtful
+/// information.
+///
+/// \section DataScrubbingBasic Basic Checks
+/// The first part - how do we know that the response comes from the correct
+/// server - is relatively trivial, albeit not foolproof (which is why DNSSEC
+/// was developed). The following are checked:
+///
+/// - The IP address from which the response was received is the same as the
+/// one to which the query was sent.
+/// - The port on which the response was received is the same as the one from
+/// which the query was sent.
+///
+/// (These tests need not not done for a TCP connection - if data is received
+/// over the TCP stream, it is assumed that it comes from the address and port
+/// to which a connection was made.)
+///
+/// - The protocol used to send the question is the same as the protocol on
+/// which an answer was received.
+///
+/// (Strictly speaking, if this check fails it is a programming error - the
+/// code should not mix up UPD and TCP messages.)
+///
+/// - The QID in the response message is the same as the QID in the query
+/// message sent.
+///
+/// If the conditions are met, then the data - in all three response sections -
+/// is scanned and out of bailiwick data is removed ("scrubbed").
+///
+/// \section DataScrubbingBailiwick Bailiwick
+/// Bailiwick means "district or jurisdiction of bailie or bailiff" (Concise
+/// Oxford Dictionary, 7th Edition). It is not a term mentioned in any RFC
+/// (or at least, any RFC up to RFC 5997) but is widely used in DNS literature.
+/// In this context it is taken to mean the data for which a DNS server has
+/// authority. So when we speak of the information being "in bailiwick", we
+/// mean that the the server is the ultimate source of authority for that data.
+///
+/// In practice, determining this from the response alone is difficult. In
+/// particular, as a server may be authoritative for many zones, it could in
+/// theory be authoritative for any combination of RRsets that appear in a
+/// response.
+///
+/// For this reason, bailiwick is dependent on the query. If, for example, a
+/// query for www.example.com is sent to the nameservers for example.com
+/// (because of a referral of from the com. servers), the bailiwick for the
+/// query is example.com. This means that any information returned on domains
+/// other than example.com may not be authoritative. More exactly, it may be
+/// authoritative (because the server is also authoritative for the zone
+/// concerned), but based on the information available (in this example, that
+/// the response originated from a nameserver for the zone example.com) it is
+/// not possible to be certain.
+///
+/// Ideally, out of bailiwick data should be excluded from further processing
+/// as it may be incorrect and corrupt the cache. In practice, there are
+/// two cases to consider:
+///
+/// The first is when the data has a qname that is not example.com or a
+/// subdomain of it (e.g. xyz.com, www.example.net). In this case the data can
+/// be retrieved by an independent query - no path from the root zone to the
+/// data goes through the current bailiwick, so there is no chance of ending up
+/// in a loop. In this case, data that appears to be out of bailiwick can be
+/// dropped from the response.
+///
+/// The second case is when the QNAME of the data is a subdomain of the
+/// bailiwick. Here the server may or may not be authoritative for the data.
+/// For example, if the name queried for were www.sub.example.com and the
+/// example.com nameservers supplied an answer:
+///
+/// - The answer could be authoritative - www.sub.example.com could be
+/// in the example.com zone.
+/// - The answer might not be authoritative - the zone sub.example.com may have
+/// been delegated, so the authoritative answer should come from
+/// sub.example.com's nameservers.
+/// - The answer might be authoritative even though zone sub.example.com has
+/// been delegated, because the nameserver for example.com is the same as
+/// that for sub.example.com.
+///
+/// Unlike the previous case, it is not possible to err on the side of caution
+/// and drop such data. Any independent query for it will pass through the
+/// current bailiwick and the same question will be asked again. For this
+/// reason, any data in the response that has a QNAME equal to a subdomain of
+/// the bailiwick has to be accepted.
+///
+/// In summary then, data in a response that has a QNAME equal to or a subdomain
+/// of the bailiwick is considered in-bailiwick. Anything else is out of of
+/// bailiwick.
+///
+/// \subsection DataScrubbingCrossSection Cross-Section Scrubbing
+/// Even with the bailiwick checks above, there are some additional cleaning
+/// that can be done with the packet. In particular:
+///
+/// - The QNAMEs of the RRsets in the authority section must be equal to or
+/// superdomains of a QNAME of an RRset in the answer. Any that are not
+/// should be removed.
+/// - If there is no answer section, the QNAMES of RRsets in the authority
+/// section must be equal to or superdomains of the QNAME of the RRset in the
+/// question.
+///
+/// Although previous checks should have removed some inconsistencies, it
+/// will not trap obscure cases (e.g. bailiwick: "example.com", answer:
+/// "www.example.com", authority: sub.example.com). These checks do just that.
+///
+/// (Note that not included here is QNAME of question not equal to or a
+/// superdomain of the answer; that check is made in the ResponseClassifier
+/// class.)
+///
+/// \section DataScrubbingExample Examples
+/// Some examples should make this clear: they all use the notation
+/// Qu = Question, Zo = Zone being queried, An = Answer, Au = Authority,
+/// Ad = Additional.
+///
+/// \subsection DataScrubbingEx1 Example 1: Simple Query
+/// Querying a nameserver for the zone "example.com" for www.example.com and
+/// receiving the answer "www.example.com A 1.2.3.4" with two nameservers quoted
+/// as authority and both their addresses in the additional section:
+///
+/// Qu: www.example.com\n
+/// Zo: example.com
+///
+/// An: www.example.com A 192.0.2.1
+///
+/// Au(1): example.com NS ns0.example.com\n
+/// Au(2): example.com NS ns1.example.net
+///
+/// Ad(1): ns0.example.com A 192.0.2.100\n
+/// Ad(2): ns1.example.net A 192.0.2.200
+///
+/// This answer could be returned by a properly configured server. All resource
+/// records in the answer - with the exception of Ad(2) - are in bailiwick
+/// because the QNAME is equal to or a subdomain of the zone being queried.
+///
+/// It is permissible for Ad(2) to be returned by a properly configured server
+/// as a hint to resolvers. However the example.com nameservers are not
+/// authoritative for addresses of domains in example.net; that record could
+/// be out of date or incorrect. Indeed, it might even be a deliberate attempt
+/// at a spoof by getting us to cache an invalid address for ns1.example.net.
+/// The safest thing to do is to drop the A record and to get the address of
+/// ns1.example.net by querying for that name through the .net nameservers.
+///
+/// \subsection DataScrubbingEx2 Example 2: Multiple Zones on Same Nameserver
+/// Assume now that example.com and sub.example.com are hosted on the same
+/// nameserver and that from the .com zone the resolver has received a referral
+/// to example.com. Suppose that the query is for www.sub.example.com and that
+/// the following response is received:
+///
+/// Qu: www.sub.example.com\n
+/// Zo: example.com
+///
+/// An: (nothing)
+///
+/// Au(1): sub.example.com NS ns0.sub.example.com\n
+/// Au(2): sub.example.com NS ns1.example.net
+///
+/// Ad(1): ns0.sub.example.com A 192.0.2.101\n
+/// Ad(2): ns1.example.net A 192.0.2.201
+///
+/// Although we asked the example.com nameservers for information, we got the
+/// nameservers for sub.example.com in the authority section. This is valid
+/// because if BIND-10 hosts multiple zones, it will look up the data in the
+/// zone that most closely matches the query.
+///
+/// Using the criteria above, the data in the additional section can therefore
+/// be regarded as in bailiwick because sub.example.com is a subdomain of
+/// example.com. As before though, the address for ns1.example.net in the
+/// additional section is not in bailiwick because ns1.example.net is now a
+/// subdomain of example.com.
+///
+/// \subsection DataScrubbingEx3 Example 3: Deliberate Spoof Attempt
+/// Qu: www.example.com\n
+/// Zo: example.com
+///
+/// An: www.example.com A 192.0.2.1
+///
+/// Au(1): com NS ns0.example.com\n
+/// Au(2): com NS ns1.example.net
+///
+/// Ad(1): ns0.example.com A 192.0.2.100\n
+/// Ad(2): ns1.example.net A 192.0.2.200
+///
+/// This is a deliberately invalid response. The query is being sent to the
+/// nameservers for example.com (presumably because a referral to example.com
+/// was received from the com nameservers), but the response is an attempt
+/// to get the specified nameservers cached as the nameservers for com - for
+/// which example.com is not authoritative.
+///
+/// Note though that this response is only invalid because, due to the previous
+/// referral, the query was sent to the example.com nameservers. Had the
+/// referral been to the com nameservers, it would be a valid response; the com
+/// zone could well be serving all the data for example.com. Having said that,
+/// the A record for ns1.example.net would still be regarded as being out of
+/// bailiwick becase the nameserver is not authoritative for the .net zone.
+///
+/// \subsection DataScrubbingEx4 Example 4: Inconsistent Answer Section
+/// Qu: www.example.com\n
+/// Zo: example.com
+///
+/// An: www.example.com A 192.0.2.1
+///
+/// Au(1): alpha.example.com NS ns0.example.com\n
+/// Au(2): alpha.example.com NS ns1.example.net
+///
+/// Ad(1): ns0.example.com A 192.0.2.100\n
+/// Ad(2): ns1.example.net A 192.0.2.200
+///
+/// Here, everything in the answer and authority sections is in bailiwick for
+/// the example.com server. And although the zone example.com was queried, it
+/// is permissible for the authority section to contain nameservers with a
+/// qname that is a subdomain of example.com (e.g. see \ref DataScrubbingEx2).
+/// However, only servers with a qname that is equal to or a superdomain of
+/// the answer are authoritative for the answer. So in this case, both
+/// Au(1) and Au(2) (as well as Ad(2), for reasons given earlier) will be
+/// scrubbed.
+
+#include <config.h>
+#include <asiolink/io_endpoint.h>
+#include <dns/message.h>
+#include <dns/name.h>
+
+/// \brief Response Data Scrubbing
+///
+/// This is the class that implements the data scrubbing. Given a response
+/// message and some additional information, it checks the information using
+/// the rules given in \ref DataScrubbing and either rejects the packet or
+/// modifies it to remove non-conforming RRsets.
+///
+/// TODO: Examine the additional records and remove all cases where the
+/// QNAME does not match the RDATA of records in the authority section.
+
+class ResponseScrubber {
+public:
+
+ /// \brief Response Code for Address Check
+ enum Category {
+ SUCCESS = 0, ///< Packet is OK
+
+ // Error categories
+
+ ADDRESS = 1, ///< Mismatching IP address
+ PORT = 2, ///< Mismatching port
+ PROTOCOL = 3 ///< Mismatching protocol
+ };
+
+ /// \brief Check IP Address
+ ///
+ /// Compares the address to which the query was sent, the port it was
+ /// sent from, and the protocol used for communication with the (address,
+ /// port, protocol) from which the response was received.
+ ///
+ /// \param to Endpoint representing the address to which the query was sent.
+ /// \param from Endpoint from which the response was received.
+ ///
+ /// \return SUCCESS if the two endpoints match, otherwise an error status
+ /// indicating what was incorrect.
+ static Category addressCheck(const asiolink::IOEndpoint& to,
+ const asiolink::IOEndpoint& from);
+
+ /// \brief Check QID
+ ///
+ /// Compares the QID in the sent message with the QID in the response.
+ ///
+ /// \param sent Message sent to the authoritative server
+ /// \param received Message received from the authoritative server
+ ///
+ /// \return true if the QIDs match, false otherwise.
+ static bool qidCheck(const isc::dns::Message& sent,
+ const isc::dns::Message& received) {
+ return (sent.getQid() == received.getQid());
+ }
+
+ /// \brief Generalised Scrub Message Section
+ ///
+ /// When scrubbing a message given the bailiwick of the server, RRsets are
+ /// retained in the message section if the QNAME is equal to or a subdomain
+ /// of the bailiwick. However, when checking QNAME of RRsets in the
+ /// authority section against the QNAME of the question or answers, RRsets
+ /// are retained only if their QNAME is equal to or a superdomain of the
+ /// name in question.
+ ///
+ /// This method provides the generalised scrubbing whereby the RRsets in
+ /// a section are tested against a given name, and RRsets kept if their
+ /// QNAME is equal to or in the supplied relationship with the given name.
+ ///
+ /// \param section Section of the message to be scrubbed.
+ /// \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.
+ /// \param connection Relationship required for retention, i.e. the QNAME of
+ /// an RRset in the specified section must be equal to or a "connection"
+ /// (SUPERDOMAIN/SUBDOMAIN) of "name" for the RRset to be retained.
+ /// \param message Message to be scrubbed.
+ ///
+ /// \return Count of the number of RRsets removed from the section.
+ static unsigned int scrubSection(isc::dns::Message& message,
+ const std::vector<const isc::dns::Name*>& names,
+ const isc::dns::NameComparisonResult::NameRelation connection,
+ const isc::dns::Message::Section section);
+
+ /// \brief Scrub All Sections of a Message
+ ///
+ /// Scrubs each of the answer, authority and additional sections of the
+ /// message.
+ ///
+ /// No distinction is made between RRsets legitimately in the message (e.g.
+ /// glue for authorities that are not in bailiwick) and ones that could be
+ /// considered as attempts of spoofing (e.g. non-bailiwick RRsets in the
+ /// additional section that are not related to the query).
+ ///
+ /// The resultant packet returned to the caller may be invalid. If so, it
+ /// is up to the caller to detect that.
+ ///
+ /// \param message Message to be scrubbed.
+ /// \param bailiwick Name of the zone whose authoritative servers were
+ /// queried.
+ ///
+ /// \return Count of the number of RRsets removed from the message.
+ static unsigned int scrubAllSections(isc::dns::Message& message,
+ const isc::dns::Name& bailiwick);
+
+ /// \brief Scrub Across Message Sections
+ ///
+ /// Does some cross-section comparisons and removes inconsistent RRs. In
+ /// particular it:
+ ///
+ /// - If an answer is present, checks that the qname of the authority RRs
+ /// are equal to or superdomain of the qname answer RRsets. Any that are
+ /// not are removed.
+ /// - If an answer is not present, checks that the authority RRs are
+ /// equal to or superdomains of the question. If not, the authority RRs
+ /// are removed.
+ ///
+ /// Note that the scrubbing does not check:
+ ///
+ /// - that the question is in the bailiwick of the server; that check is
+ /// assumed to have been done prior to the query being sent (else why
+ /// was the query sent there in the first place?)
+ /// - that the qname of one of the RRsets in the answer (if present) is
+ /// equal to the qname of the question (that check is done in the
+ /// response classification code).
+ ///
+ /// \param message Message to be scrubbed.
+ ///
+ /// \return Count of the number of RRsets removed from the section.
+ static unsigned int scrubCrossSections(isc::dns::Message& message);
+
+ /// \brief Main Scrubbing Entry Point
+ ///
+ /// The single entry point to the module to sanitise the message. All
+ /// it does is call the various other scrubbing methods.
+ ///
+ /// \param message Pointer to the message to be scrubbed. (This is a
+ /// pointer - as opposed to a Message as in other methods in this class -
+ /// as the external code is expected to be mainly using message pointers
+ /// to access messages.)
+ /// \param bailiwick Name of the zone whose authoritative servers were
+ /// queried.
+ ///
+ /// \return Count of the number of RRsets removed from the message.
+ static unsigned int scrub(const isc::dns::MessagePtr& message,
+ const isc::dns::Name& bailiwick);
+
+ /// \brief Comparison Function for Sorting Name Pointers
+ ///
+ /// Utility method called to sorts pointers to names in lexical order.
+ ///
+ /// \param n1 Pointer to first Name object
+ /// \param n2 Pointer to second Name object
+ ///
+ /// \return true if n1 is less than n2, false otherwise.
+ static bool compareNameLt(const isc::dns::Name* n1,
+ const isc::dns::Name* n2)
+ {
+ return (*n1 < *n2);
+ }
+
+ /// \brief Function for Comparing Name Pointers
+ ///
+ /// Utility method called to sorts pointers to names in lexical order.
+ ///
+ /// \param n1 Pointer to first Name object
+ /// \param n2 Pointer to second Name object
+ ///
+ /// \return true if n1 is equal to n2, false otherwise.
+ static bool compareNameEq(const isc::dns::Name* n1,
+ const isc::dns::Name* n2)
+ {
+ return (*n1 == *n2);
+ }
+};
+
+#endif // __RESPONSE_SCRUBBER_H
diff --git a/src/bin/resolver/spec_config.h.pre.in b/src/bin/resolver/spec_config.h.pre.in
new file mode 100644
index 0000000..a361eb4
--- /dev/null
+++ b/src/bin/resolver/spec_config.h.pre.in
@@ -0,0 +1,15 @@
+// Copyright (C) 2009 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 RESOLVER_SPECFILE_LOCATION "@prefix@/share/@PACKAGE@/resolver.spec"
diff --git a/src/bin/resolver/tests/Makefile.am b/src/bin/resolver/tests/Makefile.am
new file mode 100644
index 0000000..b85c223
--- /dev/null
+++ b/src/bin/resolver/tests/Makefile.am
@@ -0,0 +1,55 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(top_srcdir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+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 += ../resolver.h ../resolver.cc
+run_unittests_SOURCES += ../response_scrubber.h ../response_scrubber.cc
+run_unittests_SOURCES += resolver_unittest.cc
+run_unittests_SOURCES += resolver_config_unittest.cc
+run_unittests_SOURCES += response_scrubber_unittest.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 += $(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/asiolink/libasiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
+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
+run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_GXX
+run_unittests_CXXFLAGS += -Wno-unused-parameter
+endif
+endif
+
+
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/resolver/tests/resolver_config_unittest.cc b/src/bin/resolver/tests/resolver_config_unittest.cc
new file mode 100644
index 0000000..2fa62e5
--- /dev/null
+++ b/src/bin/resolver/tests/resolver_config_unittest.cc
@@ -0,0 +1,227 @@
+// 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 <gtest/gtest.h>
+
+#include <cc/data.h>
+
+#include <asiolink/asiolink.h>
+
+#include <resolver/resolver.h>
+
+#include <dns/tests/unittest_util.h>
+#include <testutils/srv_test.h>
+#include <testutils/portconfig.h>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::testutils;
+using namespace asiolink;
+using isc::UnitTestUtil;
+
+namespace {
+class ResolverConfig : public ::testing::Test {
+ public:
+ IOService ios;
+ DNSService dnss;
+ Resolver server;
+ ResolverConfig() :
+ dnss(ios, NULL, NULL, NULL)
+ {
+ server.setDNSService(dnss);
+ server.setConfigured();
+ }
+ void invalidTest(const string &JSON, const string& name);
+};
+
+TEST_F(ResolverConfig, forwardAddresses) {
+ // Default value should be fully recursive
+ EXPECT_TRUE(server.getForwardAddresses().empty());
+ EXPECT_FALSE(server.isForwarding());
+
+ // Try putting there some addresses
+ vector<pair<string, uint16_t> > addresses;
+ addresses.push_back(pair<string, uint16_t>(DEFAULT_REMOTE_ADDRESS, 53));
+ addresses.push_back(pair<string, uint16_t>("::1", 53));
+ server.setForwardAddresses(addresses);
+ EXPECT_EQ(2, server.getForwardAddresses().size());
+ EXPECT_EQ("::1", server.getForwardAddresses()[1].first);
+ EXPECT_TRUE(server.isForwarding());
+
+ // Is it independent from what we do with the vector later?
+ addresses.clear();
+ EXPECT_EQ(2, server.getForwardAddresses().size());
+
+ // Did it return to fully recursive?
+ server.setForwardAddresses(addresses);
+ EXPECT_TRUE(server.getForwardAddresses().empty());
+ EXPECT_FALSE(server.isForwarding());
+}
+
+TEST_F(ResolverConfig, forwardAddressConfig) {
+ // Try putting there some address
+ ElementPtr config(Element::fromJSON("{"
+ "\"forward_addresses\": ["
+ " {"
+ " \"address\": \"192.0.2.1\","
+ " \"port\": 53"
+ " }"
+ "]"
+ "}"));
+ ConstElementPtr result(server.updateConfig(config));
+ EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+ EXPECT_TRUE(server.isForwarding());
+ ASSERT_EQ(1, server.getForwardAddresses().size());
+ EXPECT_EQ("192.0.2.1", server.getForwardAddresses()[0].first);
+ EXPECT_EQ(53, server.getForwardAddresses()[0].second);
+
+ // And then remove all addresses
+ config = Element::fromJSON("{"
+ "\"forward_addresses\": null"
+ "}");
+ result = server.updateConfig(config);
+ EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+ EXPECT_FALSE(server.isForwarding());
+ EXPECT_EQ(0, server.getForwardAddresses().size());
+}
+
+TEST_F(ResolverConfig, rootAddressConfig) {
+ // Try putting there some address
+ ElementPtr config(Element::fromJSON("{"
+ "\"root_addresses\": ["
+ " {"
+ " \"address\": \"192.0.2.1\","
+ " \"port\": 53"
+ " }"
+ "]"
+ "}"));
+ ConstElementPtr result(server.updateConfig(config));
+ EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+ ASSERT_EQ(1, server.getRootAddresses().size());
+ EXPECT_EQ("192.0.2.1", server.getRootAddresses()[0].first);
+ EXPECT_EQ(53, server.getRootAddresses()[0].second);
+
+ // And then remove all addresses
+ config = Element::fromJSON("{"
+ "\"root_addresses\": null"
+ "}");
+ result = server.updateConfig(config);
+ EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+ EXPECT_EQ(0, server.getRootAddresses().size());
+}
+
+void
+ResolverConfig::invalidTest(const string &JSON, const string& name) {
+ isc::testutils::portconfig::configRejected(server, JSON, name);
+}
+
+TEST_F(ResolverConfig, invalidForwardAddresses) {
+ // Try torturing it with some invalid inputs
+ invalidTest("{"
+ "\"forward_addresses\": \"error\""
+ "}", "Invalid type");
+ invalidTest("{"
+ "\"forward_addresses\": [{}]"
+ "}", "Empty element");
+ invalidTest("{"
+ "\"forward_addresses\": [{"
+ " \"port\": 1.5,"
+ " \"address\": \"192.0.2.1\""
+ "}]}", "Float port");
+ invalidTest("{"
+ "\"forward_addresses\": [{"
+ " \"port\": -5,"
+ " \"address\": \"192.0.2.1\""
+ "}]}", "Negative port");
+ invalidTest("{"
+ "\"forward_addresses\": [{"
+ " \"port\": 53,"
+ " \"address\": \"bad_address\""
+ "}]}", "Bad address");
+}
+
+// Try setting the addresses directly
+TEST_F(ResolverConfig, listenAddresses) {
+ isc::testutils::portconfig::listenAddresses(server);
+}
+
+// Try setting some addresses and a rollback
+TEST_F(ResolverConfig, listenAddressConfig) {
+ isc::testutils::portconfig::listenAddressConfig(server);
+}
+
+// Try some invalid configs are rejected
+TEST_F(ResolverConfig, invalidListenAddresses) {
+ isc::testutils::portconfig::invalidListenAddressConfig(server);
+}
+
+// Just test it sets and gets the values correctly
+TEST_F(ResolverConfig, timeouts) {
+ server.setTimeouts(0, 1, 2, 3);
+ EXPECT_EQ(0, server.getQueryTimeout());
+ EXPECT_EQ(1, server.getClientTimeout());
+ EXPECT_EQ(2, server.getLookupTimeout());
+ EXPECT_EQ(3, server.getRetries());
+ server.setTimeouts();
+ EXPECT_EQ(2000, server.getQueryTimeout());
+ EXPECT_EQ(4000, server.getClientTimeout());
+ EXPECT_EQ(30000, server.getLookupTimeout());
+ EXPECT_EQ(3, server.getRetries());
+}
+
+TEST_F(ResolverConfig, timeoutsConfig) {
+ ElementPtr config = Element::fromJSON("{"
+ "\"timeout_query\": 1000,"
+ "\"timeout_client\": 2000,"
+ "\"timeout_lookup\": 3000,"
+ "\"retries\": 4"
+ "}");
+ ConstElementPtr result(server.updateConfig(config));
+ EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+ EXPECT_EQ(1000, server.getQueryTimeout());
+ EXPECT_EQ(2000, server.getClientTimeout());
+ EXPECT_EQ(3000, server.getLookupTimeout());
+ EXPECT_EQ(4, server.getRetries());
+}
+
+TEST_F(ResolverConfig, invalidTimeoutsConfig) {
+ invalidTest("{"
+ "\"timeout_query\": \"error\""
+ "}", "Wrong query element type");
+ invalidTest("{"
+ "\"timeout_query\": -2"
+ "}", "Negative query timeout");
+ invalidTest("{"
+ "\"timeout_client\": \"error\""
+ "}", "Wrong client element type");
+ invalidTest("{"
+ "\"timeout_client\": -2"
+ "}", "Negative client timeout");
+ invalidTest("{"
+ "\"timeout_lookup\": \"error\""
+ "}", "Wrong lookup element type");
+ invalidTest("{"
+ "\"timeout_lookup\": -2"
+ "}", "Negative lookup timeout");
+ invalidTest("{"
+ "\"retries\": \"error\""
+ "}", "Wrong retries element type");
+ invalidTest("{"
+ "\"retries\": -1"
+ "}", "Negative number of retries");
+}
+
+}
diff --git a/src/bin/resolver/tests/resolver_unittest.cc b/src/bin/resolver/tests/resolver_unittest.cc
new file mode 100644
index 0000000..97edf12
--- /dev/null
+++ b/src/bin/resolver/tests/resolver_unittest.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 <dns/name.h>
+
+#include <resolver/resolver.h>
+#include <dns/tests/unittest_util.h>
+#include <testutils/dnsmessage_test.h>
+#include <testutils/srv_test.h>
+
+using namespace isc::dns;
+using namespace isc::testutils;
+using isc::UnitTestUtil;
+
+namespace {
+const char* const TEST_PORT = "53535";
+
+class ResolverTest : public SrvTestBase{
+protected:
+ ResolverTest() : server(){}
+ virtual void processMessage() {
+ server.processMessage(*io_message,
+ parse_message,
+ response_message,
+ response_obuffer,
+ &dnsserv);
+ }
+ Resolver server;
+};
+
+// Unsupported requests. Should result in NOTIMP.
+TEST_F(ResolverTest, unsupportedRequest) {
+ unsupportedRequest();
+}
+
+// Multiple questions. Should result in FORMERR.
+TEST_F(ResolverTest, multiQuestion) {
+ multiQuestion();
+}
+
+// Incoming data doesn't even contain the complete header. Must be silently
+// dropped.
+TEST_F(ResolverTest, shortMessage) {
+ shortMessage();
+}
+
+// Response messages. Must be silently dropped, whether it's a valid response
+// or malformed or could otherwise cause a protocol error.
+TEST_F(ResolverTest, response) {
+ response();
+}
+
+// Query with a broken question
+TEST_F(ResolverTest, shortQuestion) {
+ shortQuestion();
+}
+
+// Query with a broken answer section
+TEST_F(ResolverTest, shortAnswer) {
+ shortAnswer();
+}
+
+// Query with unsupported version of EDNS.
+TEST_F(ResolverTest, ednsBadVers) {
+ ednsBadVers();
+}
+
+TEST_F(ResolverTest, AXFROverUDP) {
+ axfrOverUDP();
+}
+
+TEST_F(ResolverTest, AXFRFail) {
+ UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+ Name("example.com"), RRClass::IN(),
+ RRType::AXFR());
+ createRequestPacket(request_message, IPPROTO_TCP);
+ // AXFR is not implemented and should always send NOTIMP.
+ server.processMessage(*io_message,
+ parse_message,
+ response_message,
+ response_obuffer,
+ &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::NOTIMP(), opcode.getCode(),
+ QR_FLAG, 1, 0, 0, 0);
+}
+
+TEST_F(ResolverTest, IXFRFail) {
+ UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+ Name("example.com"), RRClass::IN(),
+ RRType::IXFR());
+ createRequestPacket(request_message, IPPROTO_TCP);
+ // IXFR is not implemented and should always send NOTIMP.
+ server.processMessage(*io_message,
+ parse_message,
+ response_message,
+ response_obuffer,
+ &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ // the second check is what we'll need in the end (with the values
+ // from the first one), but right now the first one is for what
+ // will actually be returned to the client
+ headerCheck(*parse_message, default_qid, Rcode::NOTIMP(), opcode.getCode(),
+ QR_FLAG, 1, 0, 0, 0);
+ headerCheck(*response_message, default_qid, Rcode::NOTIMP(), opcode.getCode(),
+ 0, 0, 0, 0, 0);
+}
+
+TEST_F(ResolverTest, notifyFail) {
+ // Notify should always return NOTAUTH
+ request_message.clear(Message::RENDER);
+ request_message.setOpcode(Opcode::NOTIFY());
+ request_message.setRcode(Rcode::NOERROR());
+ request_message.setHeaderFlag(Message::HEADERFLAG_AA);
+ request_message.setQid(default_qid);
+ request_message.setHeaderFlag(Message::HEADERFLAG_AA);
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message,
+ parse_message,
+ response_message,
+ response_obuffer,
+ &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::NOTAUTH(),
+ Opcode::NOTIFY().getCode(), QR_FLAG, 0, 0, 0, 0);
+}
+
+}
diff --git a/src/bin/resolver/tests/response_scrubber_unittest.cc b/src/bin/resolver/tests/response_scrubber_unittest.cc
new file mode 100644
index 0000000..1dc6639
--- /dev/null
+++ b/src/bin/resolver/tests/response_scrubber_unittest.cc
@@ -0,0 +1,542 @@
+// 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 <string>
+#include <iostream>
+
+#include <gtest/gtest.h>
+
+#include <config.h>
+
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_address.h>
+#include <netinet/in.h>
+
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/question.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <resolver/response_scrubber.h>
+
+
+// Class for endpoint checks. The family of the endpoint is set in the
+// constructor; the address family by the string provided for the address.
+
+namespace asiolink {
+
+class GenericEndpoint : public IOEndpoint {
+public:
+ GenericEndpoint(const std::string& address, uint16_t port, short protocol) :
+ address_(address), port_(port), protocol_(protocol)
+ {}
+ virtual ~GenericEndpoint()
+ {}
+
+ virtual IOAddress getAddress() const {
+ return address_;
+ }
+
+ virtual uint16_t getPort() const {
+ return port_;
+ }
+
+ virtual short getProtocol() const {
+ return protocol_;
+ }
+
+ virtual short getFamily() const {
+ return address_.getFamily();
+ }
+
+private:
+ IOAddress address_; // Address of endpoint
+ uint16_t port_; // Port number of endpoint
+ short protocol_; // Protocol of the endpoint
+ };
+}
+
+using namespace asio::ip;
+using namespace isc::dns;
+using namespace rdata;
+using namespace isc::dns::rdata::generic;
+using namespace isc::dns::rdata::in;
+using namespace asiolink;
+
+// Test class
+
+namespace {
+class ResponseScrubberTest : public ::testing::Test {
+public:
+ ResponseScrubberTest() :
+ bailiwick("example.com"),
+
+ qu_in_any_www(Name("www.example.com"), RRClass::IN(), RRType::ANY()),
+ qu_in_a_www(Name("www.example.com"), RRClass::IN(), RRType::A()),
+ qu_in_ns(Name("example.com"), RRClass::IN(), RRType::NS()),
+ qu_in_txt_www(Name("www.example.com"), RRClass::IN(), RRType::TXT()),
+ rrs_in_a_org(new RRset(Name("mail.example.org"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+
+ rrs_in_a_net(new RRset(Name("mail.example.net"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_a_www(new RRset(Name("www.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_cname_www(new RRset(Name("www.example.com"), RRClass::IN(),
+ RRType::CNAME(), RRTTL(300))),
+ rrs_in_a_wwwnet(new RRset(Name("www.example.net"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_ns(new RRset(Name("example.com"), RRClass::IN(),
+ RRType::NS(), RRTTL(300))),
+ rrs_in_ns_com(new RRset(Name("com"), RRClass::IN(),
+ RRType::NS(), RRTTL(300))),
+ rrs_in_ns_net(new RRset(Name("example.net"), RRClass::IN(),
+ RRType::NS(), RRTTL(300))),
+ rrs_in_ns_sub(new RRset(Name("subdomain.example.com"), RRClass::IN(),
+ RRType::NS(), RRTTL(300))),
+ rrs_in_ns_sub2(new RRset(Name("subdomain2.example.com"), RRClass::IN(),
+ RRType::NS(), RRTTL(300))),
+ rrs_in_a_ns0(new RRset(Name("ns0.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_a_ns1(new RRset(Name("ns1.com"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_a_ns2(new RRset(Name("ns2.example.net"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_a_ns3(new RRset(Name("ns3.subdomain.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_txt_www(new RRset(Name("www.example.com"), RRClass::IN(),
+ RRType::TXT(), RRTTL(300)))
+ {}
+ Name bailiwick; // Bailiwick of the server queried
+ Question qu_in_any_www; // www.example.com IN ANY
+ Question qu_in_a_www; // www.example.com IN A
+ Question qu_in_ns; // example.com IN NS
+ Question qu_in_txt_www; // www.example.com IN TXT
+ RRsetPtr rrs_in_a_org; // mail.example.org IN A
+ RRsetPtr rrs_in_a_net; // mail.example.org IN A
+ RRsetPtr rrs_in_a_www; // www.example.com IN A
+ RRsetPtr rrs_in_cname_www; // www.example.com IN CNAME
+ RRsetPtr rrs_in_a_wwwnet; // www.example.net IN A
+ RRsetPtr rrs_in_ns; // example.com IN NS
+ RRsetPtr rrs_in_ns_com; // com IN NS
+ RRsetPtr rrs_in_ns_net; // example.net IN NS
+ RRsetPtr rrs_in_ns_sub; // subdomain.example.com IN NS
+ RRsetPtr rrs_in_ns_sub2; // subdomain2.example.com IN NS
+ RRsetPtr rrs_in_a_ns0; // ns0.example.com IN A
+ RRsetPtr rrs_in_a_ns1; // ns1.com IN A
+ RRsetPtr rrs_in_a_ns2; // ns2.example.net IN A
+ RRsetPtr rrs_in_a_ns3; // ns3.subdomain.example.net IN A
+ RRsetPtr rrs_in_txt_www; // www.example.com IN TXT
+};
+
+
+// Check that the IP addresses/ports/protocol for the packets sent and received
+// both match if both types are IP V4.
+
+TEST_F(ResponseScrubberTest, UDPv4) {
+
+ // Basic UDP Endpoint
+ GenericEndpoint udp_a("192.0.2.1", 12345, IPPROTO_UDP);
+
+ // Same address, port
+ GenericEndpoint udp_b("192.0.2.1", 12345, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::SUCCESS,
+ ResponseScrubber::addressCheck(udp_a, udp_b));
+
+ // Different address, same port
+ GenericEndpoint udp_c("192.0.2.2", 12345, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(udp_a, udp_c));
+
+ // Same address, different port
+ GenericEndpoint udp_d("192.0.2.1", 12346, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::PORT,
+ ResponseScrubber::addressCheck(udp_a, udp_d));
+
+ // Different address, different port
+ GenericEndpoint udp_e("192.0.2.3", 12347, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(udp_a, udp_e));
+
+}
+
+// Repeat the tests for TCP
+
+TEST_F(ResponseScrubberTest, TCPv4) {
+
+ // Basic TCP Endpoint
+ GenericEndpoint tcp_a("192.0.2.1", 12345, IPPROTO_TCP);
+
+ // Same address, port
+ GenericEndpoint tcp_b("192.0.2.1", 12345, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::SUCCESS,
+ ResponseScrubber::addressCheck(tcp_a, tcp_b));
+
+ // Different address, same port
+ GenericEndpoint tcp_c("192.0.2.2", 12345, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(tcp_a, tcp_c));
+
+ // Same address, different port
+ GenericEndpoint tcp_d("192.0.2.1", 12346, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::PORT,
+ ResponseScrubber::addressCheck(tcp_a, tcp_d));
+
+ // Different address, different port
+ GenericEndpoint tcp_e("192.0.2.3", 12347, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(tcp_a, tcp_e));
+
+}
+
+// Repeat the tests for UDP/IPv6
+
+TEST_F(ResponseScrubberTest, UDPv6) {
+
+ // Basic UDP Endpoint
+ GenericEndpoint udp_a("2001:db8::1", 12345, IPPROTO_UDP);
+
+ // Same address and port
+ GenericEndpoint udp_b("2001:db8::1", 12345, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::SUCCESS,
+ ResponseScrubber::addressCheck(udp_a, udp_b));
+
+ // Different address, same port
+ GenericEndpoint udp_c("2001:db8::3", 12345, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(udp_a, udp_c));
+
+ // Same address, different port
+ GenericEndpoint udp_d("2001:db8::1", 12346, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::PORT,
+ ResponseScrubber::addressCheck(udp_a, udp_d));
+
+ // Different address, different port
+ GenericEndpoint udp_e("2001:db8::3", 12347, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(udp_a, udp_e));
+
+}
+
+// Same again for TCP/IPv6
+
+TEST_F(ResponseScrubberTest, TCPv6) {
+
+ // Basic TCP Endpoint
+ GenericEndpoint tcp_a("2001:db8::1", 12345, IPPROTO_TCP);
+
+ // Same address and port
+ GenericEndpoint tcp_b("2001:db8::1", 12345, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::SUCCESS,
+ ResponseScrubber::addressCheck(tcp_a, tcp_b));
+
+ // Different address, same port
+ GenericEndpoint tcp_c("2001:db8::3", 12345, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(tcp_a, tcp_c));
+
+ // Same address, different port
+ GenericEndpoint tcp_d("2001:db8::1", 12346, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::PORT,
+ ResponseScrubber::addressCheck(tcp_a, tcp_d));
+
+ // Different address, different port
+ GenericEndpoint tcp_e("2001:db8::3", 12347, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(tcp_a, tcp_e));
+
+}
+
+// Ensure that mixed IPv4/6 addresses don't match.
+
+TEST_F(ResponseScrubberTest, v4v6) {
+
+ // UDP
+ GenericEndpoint udp_a("2001:db8::1", 12345, IPPROTO_UDP);
+ GenericEndpoint udp_b("192.0.2.1", 12345, IPPROTO_UDP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(udp_a, udp_b));
+
+ // TCP
+ GenericEndpoint tcp_a("2001:db8::1", 12345, IPPROTO_TCP);
+ GenericEndpoint tcp_b("192.0.2.1", 12345, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::ADDRESS,
+ ResponseScrubber::addressCheck(udp_a, udp_b));
+}
+
+// Check mixed protocols are detected
+
+TEST_F(ResponseScrubberTest, Protocol) {
+ GenericEndpoint udp_a("2001:db8::1", 12345, IPPROTO_UDP);
+ GenericEndpoint tcp_a("2001:db8::1", 12345, IPPROTO_TCP);
+ EXPECT_EQ(ResponseScrubber::PROTOCOL,
+ ResponseScrubber::addressCheck(udp_a, tcp_a));
+}
+
+// Check that the QIDs check OK
+
+TEST_F(ResponseScrubberTest, Qid) {
+ Message a(Message::RENDER);
+ a.setQid(27);
+
+ Message b(Message::RENDER);
+ b.setQid(27);
+ EXPECT_TRUE(ResponseScrubber::qidCheck(a, b));
+
+ Message c(Message::RENDER);
+ c.setQid(28);
+ EXPECT_FALSE(ResponseScrubber::qidCheck(a, c));
+}
+
+// Check the scrubAllSections() method. As this operates by calling the
+// scrubSection() method (with a SUBDOMAIN argument), this is also a check of
+// the latter.
+
+TEST_F(ResponseScrubberTest, ScrubAllSectionsValid) {
+ Message valid(Message::RENDER);
+
+ // Valid message with nothing out of bailiwick
+ valid.addQuestion(qu_in_a_www);
+ valid.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ valid.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns);
+ valid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns0);
+
+ // Scrub the message and expect nothing to have been removed.
+ int removed = ResponseScrubber::scrubAllSections(valid, bailiwick);
+ EXPECT_EQ(0, removed);
+
+ // ... and check that this is the case
+ EXPECT_TRUE(valid.hasRRset(Message::SECTION_ANSWER, rrs_in_a_www));
+ EXPECT_TRUE(valid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns));
+ EXPECT_TRUE(valid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns0));
+
+ // Add out-of-bailiwick glue to the additional section (pretend that the
+ // NS RRset contained an out-of-domain server.
+ valid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2);
+ EXPECT_TRUE(valid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2));
+
+ // ... and check that it is removed when scrubbed
+ removed = ResponseScrubber::scrubAllSections(valid, bailiwick);
+ EXPECT_EQ(1, removed);
+ EXPECT_TRUE(valid.hasRRset(Message::SECTION_ANSWER, rrs_in_a_www));
+ EXPECT_TRUE(valid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns));
+ EXPECT_TRUE(valid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns0));
+ EXPECT_FALSE(valid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2));
+ }
+
+TEST_F(ResponseScrubberTest, ScrubAllSectionsInvalid) {
+ Message invalid(Message::RENDER);
+
+ // Invalid message, with various things in and out of bailiwick.
+
+ invalid.addQuestion(qu_in_a_www);
+
+ // Answer section
+ //
+ // rrs_in_a_www - "www.example.com A", in bailiwick
+ // rrs_in_txt_www - "www.example.com TXT", in bailiwick
+ // rrs_in_a_org - "mail.example.org A", out of bailiwick - the qname is
+ // related to the bailiwick name by having a common ancestor at the root
+ // rrs_in_a_net - "mail.example.net A", out of bailiwick - the qname is
+ // related to the bailiwick name by having a common ancestor at the root
+ invalid.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ invalid.addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+ invalid.addRRset(Message::SECTION_ANSWER, rrs_in_a_org);
+ invalid.addRRset(Message::SECTION_ANSWER, rrs_in_a_net);
+
+ // Authority section
+ //
+ // rrs_in_ns - "example.com NS", in bailiwick (qname is bailiwick name)
+ // rrs_in_ns_com - "com NS", out of bailiwick as the qname is a superdomain
+ // (direct ancestor) of the bailiwick name
+ // rrs_in_ns_net - "example.net NS", out of bailiwick - the qname is related
+ // to the bailiwick name by having a common ancestor at the root
+ // rrs_in_ns_sub - "subdomain.example.com", in bailiwick as the qname is
+ // a subdomain of the bailiwick name
+ invalid.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns);
+ invalid.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_com);
+ invalid.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_net);
+ invalid.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub);
+
+ // Additional section
+ //
+ // rrs_in_a_ns0 - "ns0.example.com", in bailiwick because the qname is
+ // a subdomain of the bailiwick name
+ // rrs_in_a_ns1 - "ns1.com", out of bailiwick because the qname is a
+ // sibling to the bailiwick name
+ // rrs_in_a_ns2 - "ns2.example.net", out of bailiwick because qname is
+ // related by having a common ancestor and the root.
+ // rrs_in_a_ns3 - "ns3.subdomain.example.com", in bailiwick because the
+ // qname is a direct descendent of the bailiwick name.
+ invalid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns0);
+ invalid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns1);
+ invalid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2);
+ invalid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3);
+
+ // Scrub the message
+ int removed = ResponseScrubber::scrubAllSections(invalid, bailiwick);
+ EXPECT_EQ(6, removed);
+
+ // ... and check the sections. Answer...
+ EXPECT_TRUE(invalid.hasRRset(Message::SECTION_ANSWER, rrs_in_a_www));
+ EXPECT_TRUE(invalid.hasRRset(Message::SECTION_ANSWER, rrs_in_txt_www));
+ EXPECT_FALSE(invalid.hasRRset(Message::SECTION_ANSWER, rrs_in_a_org));
+ EXPECT_FALSE(invalid.hasRRset(Message::SECTION_ANSWER, rrs_in_a_net));
+
+ // ... authority...
+ EXPECT_TRUE(invalid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns));
+ EXPECT_FALSE(invalid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_com));
+ EXPECT_FALSE(invalid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_net));
+ EXPECT_TRUE(invalid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub));
+
+ // ... additional.
+ EXPECT_TRUE(invalid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns0));
+ EXPECT_FALSE(invalid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns1));
+ EXPECT_FALSE(invalid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2));
+ EXPECT_TRUE(invalid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3));
+}
+
+// An empty message
+
+TEST_F(ResponseScrubberTest, ScrubAllSectionsEmpty) {
+ Message empty(Message::RENDER);
+
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_ADDITIONAL));
+
+ int removed = ResponseScrubber::scrubAllSections(empty, bailiwick);
+ EXPECT_EQ(0, removed);
+
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, empty.getRRCount(Message::SECTION_ADDITIONAL));
+
+}
+
+// Check the cross-section scrubbing (checks the general scrubSection()
+// method with a SUPERDOMAIN argument.)
+
+// Empty message (apart from question)
+
+TEST_F(ResponseScrubberTest, CrossSectionEmpty) {
+
+ Message message1(Message::RENDER);
+ message1.addQuestion(qu_in_a_www);
+ int removed = ResponseScrubber::scrubCrossSections(message1);
+ EXPECT_EQ(0, removed);
+}
+
+// Valid answer section
+
+TEST_F(ResponseScrubberTest, CrossSectionAnswer) {
+
+ // Valid message with nothing out of bailiwick, but the authority
+ // (subdomain.example.com) is not authoritative for the answer.
+ //
+ // TODO: Test the case where the additional section does not match
+ // with something in the authority section.
+ Message message1(Message::RENDER);
+ message1.addQuestion(qu_in_a_www);
+ message1.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ message1.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub);
+ message1.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3);
+ int removed = ResponseScrubber::scrubCrossSections(message1);
+ EXPECT_EQ(1, removed);
+ EXPECT_TRUE(message1.hasRRset(Message::SECTION_ANSWER, rrs_in_a_www));
+ EXPECT_FALSE(message1.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub));
+ EXPECT_TRUE(message1.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3));
+
+ // A repeat of the test, this time with a mixture of incorrect and correct
+ // authorities.
+ Message message2(Message::RENDER);
+ message2.addQuestion(qu_in_a_www);
+ message2.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ message2.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub);
+ message2.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns);
+ message2.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub2);
+ message2.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3);
+ removed = ResponseScrubber::scrubCrossSections(message2);
+ EXPECT_EQ(2, removed);
+ EXPECT_TRUE(message2.hasRRset(Message::SECTION_ANSWER, rrs_in_a_www));
+ EXPECT_FALSE(message2.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub));
+ EXPECT_TRUE(message2.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns));
+ EXPECT_FALSE(message2.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub2));
+ EXPECT_TRUE(message2.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3));
+}
+
+// Test the main "scrub" method. This is a single to ensure that the
+// combination of methods
+
+TEST_F(ResponseScrubberTest, All) {
+ MessagePtr mptr(new Message(Message::RENDER));
+
+ // Question is "www.example.com IN A" sent to a nameserver with the
+ // bailiwick of "example.com".
+ mptr->addQuestion(qu_in_a_www);
+
+ // Answer section.
+
+ // "www.example.com IN CNAME www.example.net" - should be kept
+ mptr->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www);
+
+ // "www.example.net IN A a.b.c.d" - should be removed, out of bailiwick
+ mptr->addRRset(Message::SECTION_ANSWER, rrs_in_a_wwwnet);
+
+ // Authority section.
+
+ // "example.net IN NS xxxx" - should be removed, out of bailiwick.
+ mptr->addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_net);
+
+ // "example.com IN NS xxx" - kept
+ mptr->addRRset(Message::SECTION_AUTHORITY, rrs_in_ns);
+
+ // "com IN NS xxx" - removed, out of bailiwick
+ mptr->addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_com);
+
+ // "subdomain.example.com IN NS xxx" - removed, not a superdomain of the
+ // answer.
+ mptr->addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub);
+
+ // Additional section
+
+ // "ns2.example.net IN A a.b.c.d" - removed, out of bailiwick
+ mptr->addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2);
+
+ // "ns3.subdomain.example.com IN A a.b.c.d" - retained.
+ mptr->addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3);
+
+ unsigned int removed = ResponseScrubber::scrub(mptr, bailiwick);
+ EXPECT_EQ(5, removed);
+
+ EXPECT_TRUE(mptr->hasRRset(Message::SECTION_ANSWER, rrs_in_cname_www));
+ EXPECT_FALSE(mptr->hasRRset(Message::SECTION_ANSWER, rrs_in_a_wwwnet));
+ EXPECT_FALSE(mptr->hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_net));
+ EXPECT_TRUE(mptr->hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns));
+ EXPECT_FALSE(mptr->hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_com));
+ EXPECT_FALSE(mptr->hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub));
+ EXPECT_FALSE(mptr->hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2));
+ EXPECT_TRUE(mptr->hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3));
+
+}
+} // Anonymous namespace
diff --git a/src/bin/resolver/tests/run_unittests.cc b/src/bin/resolver/tests/run_unittests.cc
new file mode 100644
index 0000000..6ae848d
--- /dev/null
+++ b/src/bin/resolver/tests/run_unittests.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::UnitTestUtil::addDataPath(TEST_DATA_DIR);
+ isc::UnitTestUtil::addDataPath(TEST_DATA_BUILDDIR);
+
+ return (RUN_ALL_TESTS());
+}
diff --git a/src/bin/stats/Makefile.am b/src/bin/stats/Makefile.am
index b267479..b173813 100644
--- a/src/bin/stats/Makefile.am
+++ b/src/bin/stats/Makefile.am
@@ -5,7 +5,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-stats
noinst_SCRIPTS = b10-stats_stub
-b10_statsdir = $(DESTDIR)$(pkgdatadir)
+b10_statsdir = $(pkgdatadir)
b10_stats_DATA = stats.spec
CLEANFILES = stats.spec b10-stats stats.pyc stats.pyo b10-stats_stub stats_stub.pyc stats_stub.pyo
@@ -23,7 +23,6 @@ endif
stats.spec: stats.spec.pre
$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" stats.spec.pre >$@
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-stats: stats.py
$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
diff --git a/src/bin/stats/b10-stats.xml b/src/bin/stats/b10-stats.xml
index fb82ecd..f622439 100644
--- a/src/bin/stats/b10-stats.xml
+++ b/src/bin/stats/b10-stats.xml
@@ -17,7 +17,6 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
-<!-- $Id$ -->
<refentry>
<refentryinfo>
@@ -90,7 +89,8 @@
<para><filename>/usr/local/share/bind10-devel/stats.spec</filename>
— This is a spec file for <command>b10-stats</command>. It
contains definitions of statistics items of BIND 10 and commands
- received vi bindctl.
+ received via
+ <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>.
</para>
</refsect1>
diff --git a/src/bin/stats/run_b10-stats.sh.in b/src/bin/stats/run_b10-stats.sh.in
index c23df28..65b9737 100644
--- a/src/bin/stats/run_b10-stats.sh.in
+++ b/src/bin/stats/run_b10-stats.sh.in
@@ -21,10 +21,13 @@ export PYTHON_EXEC
PYTHONPATH=@abs_top_builddir@/src/lib/python
export PYTHONPATH
+BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
+export BIND10_MSGQ_SOCKET_FILE
+
B10_FROM_BUILD=@abs_top_builddir@
export B10_FROM_BUILD
STATS_PATH=@abs_top_builddir@/src/bin/stats
cd ${STATS_PATH}
-exec ${PYTHON_EXEC} -O b10-stats $*
+exec ${PYTHON_EXEC} -O b10-stats "$@"
diff --git a/src/bin/stats/run_b10-stats_stub.sh.in b/src/bin/stats/run_b10-stats_stub.sh.in
index 03c4584..19ade5c 100644
--- a/src/bin/stats/run_b10-stats_stub.sh.in
+++ b/src/bin/stats/run_b10-stats_stub.sh.in
@@ -24,7 +24,10 @@ export PYTHONPATH
B10_FROM_BUILD=@abs_top_srcdir@
export B10_FROM_BUILD
+BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
+export BIND10_MSGQ_SOCKET_FILE
+
STATS_PATH=@abs_top_builddir@/src/bin/stats
cd ${STATS_PATH}
-exec ${PYTHON_EXEC} -O b10-stats_stub $*
+exec ${PYTHON_EXEC} -O b10-stats_stub "$@"
diff --git a/src/bin/stats/stats.py.in b/src/bin/stats/stats.py.in
old mode 100644
new mode 100755
index 2d5ff36..15e2980
--- a/src/bin/stats/stats.py.in
+++ b/src/bin/stats/stats.py.in
@@ -15,7 +15,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
__version__ = "$Revision$"
import sys; sys.path.append ('@@PYTHONPATH@@')
diff --git a/src/bin/stats/stats_stub.py.in b/src/bin/stats/stats_stub.py.in
old mode 100644
new mode 100755
index fe63916..4a4a641
--- a/src/bin/stats/stats_stub.py.in
+++ b/src/bin/stats/stats_stub.py.in
@@ -15,7 +15,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
__version__ = "$Revision$"
import sys; sys.path.append ('@@PYTHONPATH@@')
diff --git a/src/bin/stats/tests/Makefile.am b/src/bin/stats/tests/Makefile.am
index 26debc5..9e94050 100644
--- a/src/bin/stats/tests/Makefile.am
+++ b/src/bin/stats/tests/Makefile.am
@@ -1,15 +1,19 @@
SUBDIRS = isc testdata
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = b10-stats_test.py b10-stats_stub_test.py
EXTRA_DIST = $(PYTESTS) fake_time.py
CLEANFILES = fake_time.pyc
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/stats:$(abs_top_builddir)/src/bin/stats/tests \
B10_FROM_BUILD=$(abs_top_builddir) \
- $(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/stats/tests/b10-stats_stub_test.py b/src/bin/stats/tests/b10-stats_stub_test.py
index 75c5fde..b8983c5 100644
--- a/src/bin/stats/tests/b10-stats_stub_test.py
+++ b/src/bin/stats/tests/b10-stats_stub_test.py
@@ -13,7 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
__version__ = "$Revision$"
#
diff --git a/src/bin/stats/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py
index 873c24d..e4e1a1e 100644
--- a/src/bin/stats/tests/b10-stats_test.py
+++ b/src/bin/stats/tests/b10-stats_test.py
@@ -13,7 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
__version__ = "$Revision$"
#
diff --git a/src/bin/stats/tests/fake_time.py b/src/bin/stats/tests/fake_time.py
index 964097f..65e0237 100644
--- a/src/bin/stats/tests/fake_time.py
+++ b/src/bin/stats/tests/fake_time.py
@@ -13,7 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
__version__ = "$Revision$"
# This is a dummy time class against a Python standard time class.
diff --git a/src/bin/stats/tests/isc/cc/session.py b/src/bin/stats/tests/isc/cc/session.py
index ab9c296..4d12adc 100644
--- a/src/bin/stats/tests/isc/cc/session.py
+++ b/src/bin/stats/tests/isc/cc/session.py
@@ -13,7 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
# This module is a mock-up class of isc.cc.session
__version__ = "$Revision$"
diff --git a/src/bin/stats/tests/isc/config/ccsession.py b/src/bin/stats/tests/isc/config/ccsession.py
index b563e3b..fc285a7 100644
--- a/src/bin/stats/tests/isc/config/ccsession.py
+++ b/src/bin/stats/tests/isc/config/ccsession.py
@@ -13,7 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
# This module is a mock-up class of isc.cc.session
__version__ = "$Revision$"
diff --git a/src/bin/stats/tests/isc/util/process.py b/src/bin/stats/tests/isc/util/process.py
index 768528c..7274a48 100644
--- a/src/bin/stats/tests/isc/util/process.py
+++ b/src/bin/stats/tests/isc/util/process.py
@@ -13,8 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
# A dummy function of isc.util.process.rename()
def rename(name=None):
pass
diff --git a/src/bin/stats/tests/isc/utils/process.py b/src/bin/stats/tests/isc/utils/process.py
index 806c2ae..4c0cf8c 100644
--- a/src/bin/stats/tests/isc/utils/process.py
+++ b/src/bin/stats/tests/isc/utils/process.py
@@ -13,8 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
# A dummy function of isc.utils.process.rename()
def rename(name=None):
pass
diff --git a/src/bin/tests/Makefile.am b/src/bin/tests/Makefile.am
index 0da058e..4340c64 100644
--- a/src/bin/tests/Makefile.am
+++ b/src/bin/tests/Makefile.am
@@ -1,13 +1,17 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = process_rename_test.py
# .py will be generated by configure, so we don't have to include it
# in EXTRA_DIST.
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
- $(PYCOVERAGE) $(abs_builddir)/$$pytest || exit ; \
+ $(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
done
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/usermgr/Makefile.am b/src/bin/usermgr/Makefile.am
index f830aad..7ebb1cd 100644
--- a/src/bin/usermgr/Makefile.am
+++ b/src/bin/usermgr/Makefile.am
@@ -1,6 +1,6 @@
sbin_SCRIPTS = b10-cmdctl-usermgr
-b10_cmdctl_usermgrdir = $(DESTDIR)$(pkgdatadir)
+b10_cmdctl_usermgrdir = $(pkgdatadir)
CLEANFILES= b10-cmdctl-usermgr
@@ -14,7 +14,6 @@ b10-cmdctl-usermgr.8: b10-cmdctl-usermgr.xml
endif
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-cmdctl-usermgr: b10-cmdctl-usermgr.py
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" b10-cmdctl-usermgr.py >$@
diff --git a/src/bin/usermgr/b10-cmdctl-usermgr.py.in b/src/bin/usermgr/b10-cmdctl-usermgr.py.in
index 645c053..d62ad72 100644
--- a/src/bin/usermgr/b10-cmdctl-usermgr.py.in
+++ b/src/bin/usermgr/b10-cmdctl-usermgr.py.in
@@ -24,7 +24,7 @@ from hashlib import sha1
import csv
import getpass
import getopt
-import sys
+import sys; sys.path.append ('@@PYTHONPATH@@')
import isc.util.process
isc.util.process.rename()
diff --git a/src/bin/usermgr/b10-cmdctl-usermgr.xml b/src/bin/usermgr/b10-cmdctl-usermgr.xml
index 7549fe1..529a8db 100644
--- a/src/bin/usermgr/b10-cmdctl-usermgr.xml
+++ b/src/bin/usermgr/b10-cmdctl-usermgr.xml
@@ -17,7 +17,6 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
-<!-- $Id$ -->
<refentry>
<refentryinfo>
diff --git a/src/bin/usermgr/run_b10-cmdctl-usermgr.sh.in b/src/bin/usermgr/run_b10-cmdctl-usermgr.sh.in
index b606a9e..5989205 100644
--- a/src/bin/usermgr/run_b10-cmdctl-usermgr.sh.in
+++ b/src/bin/usermgr/run_b10-cmdctl-usermgr.sh.in
@@ -20,6 +20,8 @@ export PYTHON_EXEC
MYPATH_PATH=@abs_top_builddir@/src/bin/usermgr
-cd ${MYPATH_PATH}
-${PYTHON_EXEC} b10-cmdctl-usermgr
+BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
+export BIND10_MSGQ_SOCKET_FILE
+cd ${MYPATH_PATH}
+exec ${PYTHON_EXEC} b10-cmdctl-usermgr "$@"
diff --git a/src/bin/xfrin/Makefile.am b/src/bin/xfrin/Makefile.am
index 7ed41e2..ee8505e 100644
--- a/src/bin/xfrin/Makefile.am
+++ b/src/bin/xfrin/Makefile.am
@@ -4,7 +4,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-xfrin
-b10_xfrindir = $(DESTDIR)$(pkgdatadir)
+b10_xfrindir = $(pkgdatadir)
b10_xfrin_DATA = xfrin.spec
CLEANFILES = b10-xfrin xfrin.pyc
@@ -20,7 +20,6 @@ b10-xfrin.8: b10-xfrin.xml
endif
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-xfrin: xfrin.py
$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
diff --git a/src/bin/xfrin/b10-xfrin.xml b/src/bin/xfrin/b10-xfrin.xml
index 68945d0..fdfe1ef 100644
--- a/src/bin/xfrin/b10-xfrin.xml
+++ b/src/bin/xfrin/b10-xfrin.xml
@@ -17,7 +17,6 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
-<!-- $Id$ -->
<refentry>
<refentryinfo>
@@ -64,7 +63,7 @@
</para>
<note><simpara>
- The Y1 prototype release only supports AXFR. IXFR is not implemented.
+ This prototype release only supports AXFR. IXFR is not implemented.
</simpara></note>
<para>
@@ -149,7 +148,6 @@
the authoritative server to transfer from,
and <varname>port</varname> to define the port number on the
authoritative server (defaults to 53).
-<!-- TODO: note: not documenting db_file since that will be removed. -->
</para>
<!-- TODO: later hostname for master? -->
diff --git a/src/bin/xfrin/run_b10-xfrin.sh.in b/src/bin/xfrin/run_b10-xfrin.sh.in
index dd28c37..4a22b53 100644
--- a/src/bin/xfrin/run_b10-xfrin.sh.in
+++ b/src/bin/xfrin/run_b10-xfrin.sh.in
@@ -22,6 +22,8 @@ MYPATH_PATH=@abs_top_builddir@/src/bin/xfrin
PYTHONPATH=@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/.libs
export PYTHONPATH
-cd ${MYPATH_PATH}
-${PYTHON_EXEC} b10-xfrin
+BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
+export BIND10_MSGQ_SOCKET_FILE
+cd ${MYPATH_PATH}
+exec ${PYTHON_EXEC} b10-xfrin "$@"
diff --git a/src/bin/xfrin/tests/Makefile.am b/src/bin/xfrin/tests/Makefile.am
index 9e1dde8..755d76e 100644
--- a/src/bin/xfrin/tests/Makefile.am
+++ b/src/bin/xfrin/tests/Makefile.am
@@ -1,3 +1,4 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
PYTESTS = xfrin_test.py
EXTRA_DIST = $(PYTESTS)
@@ -8,13 +9,16 @@ 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)
endif
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/bin/xfrin:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python \
$(LIBRARY_PATH_PLACEHOLDER) \
- $(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index d7e002a..04d04a6 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -13,8 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
import unittest
import socket
from xfrin import *
@@ -135,9 +133,9 @@ class MockXfrinConnection(XfrinConnection):
resp.set_opcode(Opcode.QUERY())
resp.set_rcode(rcode)
if response:
- resp.set_header_flag(MessageFlag.QR())
+ resp.set_header_flag(Message.HEADERFLAG_QR)
[resp.add_question(q) for q in questions]
- [resp.add_rrset(Section.ANSWER(), a) for a in answers]
+ [resp.add_rrset(Message.SECTION_ANSWER, a) for a in answers]
renderer = MessageRenderer()
resp.to_wire(renderer)
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
old mode 100644
new mode 100755
index 4bd205c..10a866e
--- 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
@@ -16,8 +15,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
import sys; sys.path.append ('@@PYTHONPATH@@')
import os
import signal
@@ -240,7 +237,7 @@ class XfrinConnection(asyncore.dispatcher):
if msg_rcode != Rcode.NOERROR():
raise XfrinException('error response: %s' % msg_rcode.to_text())
- if not msg.get_header_flag(MessageFlag.QR()):
+ if not msg.get_header_flag(Message.HEADERFLAG_QR):
raise XfrinException('response is not a response ')
if msg.get_qid() != self._query_id:
@@ -251,10 +248,10 @@ class XfrinConnection(asyncore.dispatcher):
self._check_response_header(msg)
- if msg.get_rr_count(Section.ANSWER()) == 0:
+ if msg.get_rr_count(Message.SECTION_ANSWER) == 0:
raise XfrinException('answer section is empty')
- if msg.get_rr_count(Section.QUESTION()) > 1:
+ if msg.get_rr_count(Message.SECTION_QUESTION) > 1:
raise XfrinException('query section count greater than 1')
def _handle_answer_section(self, answer_section):
@@ -294,7 +291,7 @@ class XfrinConnection(asyncore.dispatcher):
msg.from_wire(recvdata)
self._check_response_status(msg)
- answer_section = msg.get_section(Section.ANSWER())
+ answer_section = msg.get_section(Message.SECTION_ANSWER)
for rr in self._handle_answer_section(answer_section):
yield rr
@@ -521,8 +518,19 @@ class Xfrin:
msg = create_command(notify_out.ZONE_NEW_DATA_READY_CMD, param)
# catch the exception, in case msgq has been killed.
try:
- self._send_cc_session.group_sendmsg(msg, XFROUT_MODULE_NAME)
- self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
+ seq = self._send_cc_session.group_sendmsg(msg,
+ XFROUT_MODULE_NAME)
+ try:
+ answer, env = self._send_cc_session.group_recvmsg(False,
+ seq)
+ except isc.cc.session.SessionTimeout:
+ pass # for now we just ignore the failure
+ seq = self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
+ try:
+ answer, env = self._send_cc_session.group_recvmsg(False,
+ seq)
+ except isc.cc.session.SessionTimeout:
+ pass # for now we just ignore the failure
except socket.error as err:
log_error("Fail to send message to %s and %s, msgq may has been killed"
% (XFROUT_MODULE_NAME, ZONE_MANAGER_MODULE_NAME))
@@ -530,7 +538,12 @@ class Xfrin:
msg = create_command(ZONE_XFRIN_FAILED, param)
# catch the exception, in case msgq has been killed.
try:
- self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
+ seq = self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
+ try:
+ answer, env = self._send_cc_session.group_recvmsg(False,
+ seq)
+ except isc.cc.session.SessionTimeout:
+ pass # for now we just ignore the failure
except socket.error as err:
log_error("Fail to send message to %s, msgq may has been killed"
% ZONE_MANAGER_MODULE_NAME)
diff --git a/src/bin/xfrout/Makefile.am b/src/bin/xfrout/Makefile.am
index 4955b7a..d4f021e 100644
--- a/src/bin/xfrout/Makefile.am
+++ b/src/bin/xfrout/Makefile.am
@@ -4,7 +4,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-xfrout
-b10_xfroutdir = $(DESTDIR)$(pkgdatadir)
+b10_xfroutdir = $(pkgdatadir)
b10_xfrout_DATA = xfrout.spec
CLEANFILES= b10-xfrout xfrout.pyc xfrout.spec
@@ -23,7 +23,6 @@ endif
xfrout.spec: xfrout.spec.pre
$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" xfrout.spec.pre >$@
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-xfrout: xfrout.py
$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
diff --git a/src/bin/xfrout/b10-xfrout.8 b/src/bin/xfrout/b10-xfrout.8
index 4910723..c8b4b07 100644
--- a/src/bin/xfrout/b10-xfrout.8
+++ b/src/bin/xfrout/b10-xfrout.8
@@ -2,12 +2,12 @@
.\" Title: b10-xfrout
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: September 8, 2010
+.\" Date: December 1, 2010
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-XFROUT" "8" "September 8, 2010" "BIND10" "BIND10"
+.TH "B10\-XFROUT" "8" "December 1, 2010" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -67,13 +67,28 @@ receives its configurations from
The configurable settings are:
.PP
-\fIdb_file\fR
-defines the path to the SQLite3 data store file\&. The default is
-/usr/local/var/bind10\-devel/zone\&.sqlite3\&.
-.PP
-
\fItransfers_out\fR
defines the maximum number of outgoing zone transfers that can run concurrently\&. The default is 10\&.
+.PP
+
+\fIlog_name\fR
+.PP
+
+\fIlog_file\fR
+The location of the log file if using a file channel\&. If undefined, then the file channel is closed\&. The default is
+/usr/local/var/bind10\-devel/log/Xfrout\&.log\&.
+.PP
+
+\fIlog_severity\fR
+The default is "debug"\&.
+.PP
+
+\fIlog_versions\fR
+The default is 5\&.
+.PP
+
+\fIlog_max_bytes\fR
+The default is 1048576\&.
.if n \{\
.sp
.\}
diff --git a/src/bin/xfrout/b10-xfrout.xml b/src/bin/xfrout/b10-xfrout.xml
index 075f86a..ad71fe2 100644
--- a/src/bin/xfrout/b10-xfrout.xml
+++ b/src/bin/xfrout/b10-xfrout.xml
@@ -17,11 +17,10 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
-<!-- $Id$ -->
<refentry>
<refentryinfo>
- <date>September 8, 2010</date>
+ <date>December 1, 2010</date>
</refentryinfo>
<refmeta>
@@ -94,17 +93,37 @@
The configurable settings are:
</para>
<para>
- <varname>db_file</varname>
- defines the path to the SQLite3 data store file.
- The default is
- <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>.
-<!-- TODO: db_file will be removed -->
- </para>
- <para>
<varname>transfers_out</varname>
defines the maximum number of outgoing zone transfers
that can run concurrently. The default is 10.
</para>
+ <para>
+ <varname>log_name</varname>
+<!-- TODO -->
+ </para>
+ <para>
+ <varname>log_file</varname>
+<!-- TODO -->
+ The location of the log file if using a file channel.
+ If undefined, then the file channel is closed.
+ The default is
+ <filename>/usr/local/var/bind10-devel/log/Xfrout.log</filename>.
+ </para>
+ <para>
+ <varname>log_severity</varname>
+<!-- TODO -->
+ The default is "debug".
+ </para>
+ <para>
+ <varname>log_versions</varname>
+<!-- TODO -->
+ The default is 5.
+ </para>
+ <para>
+ <varname>log_max_bytes</varname>
+<!-- TODO -->
+ The default is 1048576.
+ </para>
<!-- TODO: log configurations not documented yet in here. jreed
has some but waiting on decisions ... -->
diff --git a/src/bin/xfrout/run_b10-xfrout.sh.in b/src/bin/xfrout/run_b10-xfrout.sh.in
index 2bbd208..cb3cc58 100644
--- a/src/bin/xfrout/run_b10-xfrout.sh.in
+++ b/src/bin/xfrout/run_b10-xfrout.sh.in
@@ -22,6 +22,8 @@ MYPATH_PATH=@abs_top_builddir@/src/bin/xfrout
PYTHONPATH=@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
-cd ${MYPATH_PATH}
-${PYTHON_EXEC} b10-xfrout
+BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
+export BIND10_MSGQ_SOCKET_FILE
+cd ${MYPATH_PATH}
+exec ${PYTHON_EXEC} b10-xfrout "$@"
diff --git a/src/bin/xfrout/tests/Makefile.am b/src/bin/xfrout/tests/Makefile.am
index 0840b55..01f2e40 100644
--- a/src/bin/xfrout/tests/Makefile.am
+++ b/src/bin/xfrout/tests/Makefile.am
@@ -1,3 +1,4 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
PYTESTS = xfrout_test.py
EXTRA_DIST = $(PYTESTS)
@@ -8,13 +9,16 @@ 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)
endif
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
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 \
$(LIBRARY_PATH_PLACEHOLDER) \
- $(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+ $(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
index fb92722..5aec072 100644
--- a/src/bin/xfrout/tests/xfrout_test.py
+++ b/src/bin/xfrout/tests/xfrout_test.py
@@ -47,22 +47,29 @@ class MySocket():
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 __init__() method, which wants a socket,
+# 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()
@@ -78,12 +85,10 @@ class TestXfroutSession(unittest.TestCase):
return msg
def setUp(self):
- request = MySocket(socket.AF_INET,socket.SOCK_STREAM)
+ self.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
self.log = isc.log.NSLogger('xfrout', '', severity = 'critical', log_to_console = False )
- self.xfrsess = MyXfroutSession(request, None, None, self.log)
- self.xfrsess.server = Dbserver()
+ 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.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
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):
@@ -93,7 +98,7 @@ class TestXfroutSession(unittest.TestCase):
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()
@@ -103,8 +108,31 @@ class TestXfroutSession(unittest.TestCase):
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")
-
+ 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()
@@ -115,10 +143,9 @@ class TestXfroutSession(unittest.TestCase):
self.assertEqual(msg.get_qid(), qid)
self.assertEqual(msg.get_opcode(), opcode)
self.assertEqual(msg.get_rcode(), rcode)
- self.assertTrue(msg.get_header_flag(MessageFlag.AA()))
+ 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()
@@ -140,12 +167,12 @@ class TestXfroutSession(unittest.TestCase):
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(Section.QUESTION()), 1)
- self.assertEqual(get_msg.get_rr_count(Section.ANSWER()), 1)
- self.assertEqual(get_msg.get_rr_count(Section.AUTHORITY()), 0)
+ 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(Section.ANSWER())[0]#answer_rrset_iter.get_rrset()
+ 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")
@@ -160,7 +187,7 @@ class TestXfroutSession(unittest.TestCase):
msg = self.getmsg()
msg.make_response()
- msg.add_rrset(Section.ANSWER(), rrset_a)
+ 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)
@@ -168,11 +195,11 @@ class TestXfroutSession(unittest.TestCase):
# (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(Section.QUESTION()), 1)
- self.assertEqual(get_msg.get_rr_count(Section.ANSWER()), 1)
- self.assertEqual(get_msg.get_rr_count(Section.AUTHORITY()), 0)
+ 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(Section.ANSWER())[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")
@@ -180,12 +207,12 @@ class TestXfroutSession(unittest.TestCase):
self.assertEqual(rdata[0].to_text(), "192.0.2.1")
get_msg = self.sock.read_msg()
- self.assertEqual(get_msg.get_rr_count(Section.QUESTION()), 0)
- self.assertEqual(get_msg.get_rr_count(Section.ANSWER()), 1)
- self.assertEqual(get_msg.get_rr_count(Section.AUTHORITY()), 0)
+ 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, section.ANSWER())
- answer = get_msg.get_section(Section.ANSWER())[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")
@@ -199,43 +226,45 @@ class TestXfroutSession(unittest.TestCase):
rrset_soa = self.xfrsess._create_rrset_from_db_record(self.soa_record)
self.assertEqual(82, get_rrset_len(rrset_soa))
- def test_zone_is_empty(self):
+ def test_zone_has_soa(self):
global sqlite3_ds
def mydb1(zone, file):
return True
sqlite3_ds.get_zone_soa = mydb1
- self.assertEqual(self.xfrsess._zone_is_empty(""), False)
+ self.assertTrue(self.xfrsess._zone_has_soa(""))
def mydb2(zone, file):
return False
sqlite3_ds.get_zone_soa = mydb2
- self.assertEqual(self.xfrsess._zone_is_empty(""), True)
+ self.assertFalse(self.xfrsess._zone_has_soa(""))
def test_zone_exist(self):
global sqlite3_ds
- def zone_soa(zone, file):
+ def zone_exist(zone, file):
return zone
- sqlite3_ds.get_zone_soa = zone_soa
- self.assertEqual(self.xfrsess._zone_exist(True), True)
- self.assertEqual(self.xfrsess._zone_exist(False), False)
-
+ 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_is_empty = 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 not zone
- self.xfrsess._zone_is_empty = zone_empty
+ return zone
+ self.xfrsess._zone_has_soa = zone_empty
def false_func():
return False
- self.xfrsess.server.increase_transfers_counter = false_func
+ 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.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):
@@ -243,7 +272,7 @@ class TestXfroutSession(unittest.TestCase):
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"
@@ -255,20 +284,20 @@ class TestXfroutSession(unittest.TestCase):
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()
+ 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):
@@ -281,7 +310,7 @@ class TestXfroutSession(unittest.TestCase):
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(Section.ANSWER()), 2)
+ self.assertEqual(reply_msg.get_rr_count(Message.SECTION_ANSWER), 2)
class MyCCSession():
def __init__(self):
@@ -292,7 +321,7 @@ class MyCCSession():
return "initdb.file", False
else:
return "unknown", False
-
+
class MyUnixSockServer(UnixSockServer):
def __init__(self):
@@ -305,8 +334,17 @@ class MyUnixSockServer(UnixSockServer):
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)
@@ -324,7 +362,7 @@ class TestUnixSockServer(unittest.TestCase):
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()
@@ -335,7 +373,7 @@ class TestUnixSockServer(unittest.TestCase):
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)
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
old mode 100644
new mode 100755
index ab4b875..b3f9e95
--- 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
@@ -50,7 +49,11 @@ 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"
- UNIX_SOCKET_FILE= os.environ["B10_FROM_BUILD"] + "/auth_xfrout_conn"
+ 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:
PREFIX = "@prefix@"
DATAROOTDIR = "@datarootdir@"
@@ -63,6 +66,7 @@ AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + os.sep + "auth.spec"
MAX_TRANSFERS_OUT = 10
VERBOSE_MODE = False
+
XFROUT_MAX_MESSAGE_SIZE = 65535
def get_rrset_len(rrset):
@@ -72,47 +76,29 @@ def get_rrset_len(rrset):
return len(bytes)
-class XfroutSession(BaseRequestHandler):
- def __init__(self, request, client_address, server, log):
+class XfroutSession():
+ def __init__(self, sock_fd, request_data, server, log):
# The initializer for the superclass may call functions
# that need _log to be set, so we set it first
+ self._sock_fd = sock_fd
+ self._request_data = request_data
+ self._server = server
self._log = log
- BaseRequestHandler.__init__(self, request, client_address, server)
+ self.handle()
def handle(self):
- fd = recv_fd(self.request.fileno())
-
- if fd < 0:
- # This may happen when one xfrout process try to connect to
- # xfrout unix socket server, to check whether there is another
- # xfrout running.
- self._log.log_message("error", "Failed to receive the file descriptor for XFR connection")
- return
-
- data_len = self.request.recv(2)
- msg_len = struct.unpack('!H', data_len)[0]
- msgdata = self.request.recv(msg_len)
- sock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
+ ''' Handle a xfrout query, send xfrout response '''
try:
- self.dns_xfrout_start(sock, msgdata)
+ self.dns_xfrout_start(self._sock_fd, self._request_data)
#TODO, avoid catching all exceptions
except Exception as e:
self._log.log_message("error", str(e))
- try:
- sock.shutdown(socket.SHUT_RDWR)
- except socket.error:
- # Avoid socket error caused by shutting down
- # one non-connected socket.
- pass
-
- sock.close()
- os.close(fd)
- pass
+ os.close(self._sock_fd)
def _parse_query_message(self, mdata):
''' parse query message to [socket,message]'''
- #TODO, need to add parseHeader() in case the message header is invalid
+ #TODO, need to add parseHeader() in case the message header is invalid
try:
msg = Message(Message.PARSE)
Message.from_wire(msg, mdata)
@@ -127,111 +113,124 @@ class XfroutSession(BaseRequestHandler):
return question.get_name().to_text()
- def _send_data(self, sock, data):
+ def _send_data(self, sock_fd, data):
size = len(data)
total_count = 0
while total_count < size:
- count = sock.send(data[total_count:])
+ count = os.write(sock_fd, data[total_count:])
total_count += count
- def _send_message(self, sock, msg):
+ def _send_message(self, sock_fd, msg):
render = MessageRenderer()
+ # As defined in RFC5936 section3.4, perform case-preserving name
+ # compression for AXFR message.
+ render.set_compress_mode(MessageRenderer.CASE_SENSITIVE)
render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
msg.to_wire(render)
header_len = struct.pack('H', socket.htons(render.get_length()))
- self._send_data(sock, header_len)
- self._send_data(sock, render.get_data())
+ self._send_data(sock_fd, header_len)
+ self._send_data(sock_fd, render.get_data())
- def _reply_query_with_error_rcode(self, msg, sock, rcode_):
+ def _reply_query_with_error_rcode(self, msg, sock_fd, rcode_):
msg.make_response()
msg.set_rcode(rcode_)
- self._send_message(sock, msg)
+ self._send_message(sock_fd, msg)
- def _reply_query_with_format_error(self, msg, sock):
+ def _reply_query_with_format_error(self, msg, sock_fd):
'''query message format isn't legal.'''
if not msg:
- return # query message is invalid. send nothing back.
+ return # query message is invalid. send nothing back.
msg.make_response()
msg.set_rcode(Rcode.FORMERR())
- self._send_message(sock, msg)
-
-
- def _zone_is_empty(self, zone):
- if sqlite3_ds.get_zone_soa(zone, self.server.get_db_file()):
- return False
+ self._send_message(sock_fd, msg)
+
+ def _zone_has_soa(self, zone):
+ '''Judge if the zone has an SOA record.'''
+ # In some sense, the SOA defines a zone.
+ # If the current name server has authority for the
+ # specific zone, we need to judge if the zone has an SOA record;
+ # if not, we consider the zone has incomplete data, so xfrout can't
+ # serve for it.
+ if sqlite3_ds.get_zone_soa(zone, self._server.get_db_file()):
+ return True
- return True
+ return False
def _zone_exist(self, zonename):
- # Find zone in datasource, should this works? maybe should ask
- # config manager.
- soa = sqlite3_ds.get_zone_soa(zonename, self.server.get_db_file())
- if soa:
- return True
- return False
+ '''Judge if the zone is configured by config manager.'''
+ # Currently, if we find the zone in datasource successfully, we
+ # consider the zone is configured, and the current name server has
+ # authority for the specific zone.
+ # TODO: should get zone's configuration from cfgmgr or other place
+ # in future.
+ return sqlite3_ds.zone_exist(zonename, self._server.get_db_file())
-
def _check_xfrout_available(self, zone_name):
'''Check if xfr request can be responsed.
TODO, Get zone's configuration from cfgmgr or some other place
- eg. check allow_transfer setting,
+ eg. check allow_transfer setting,
'''
+ # If the current name server does not have authority for the
+ # zone, xfrout can't serve for it, return rcode NOTAUTH.
if not self._zone_exist(zone_name):
return Rcode.NOTAUTH()
- if self._zone_is_empty(zone_name):
- return Rcode.SERVFAIL()
+ # If we are an authoritative name server for the zone, but fail
+ # to find the zone's SOA record in datasource, xfrout can't
+ # provide zone transfer for it.
+ if not self._zone_has_soa(zone_name):
+ return Rcode.SERVFAIL()
#TODO, check allow_transfer
- if not self.server.increase_transfers_counter():
+ if not self._server.increase_transfers_counter():
return Rcode.REFUSED()
return Rcode.NOERROR()
- def dns_xfrout_start(self, sock, msg_query):
+ def dns_xfrout_start(self, sock_fd, msg_query):
rcode_, msg = self._parse_query_message(msg_query)
#TODO. create query message and parse header
if rcode_ != Rcode.NOERROR():
- return self._reply_query_with_format_error(msg, sock)
+ return self._reply_query_with_format_error(msg, sock_fd)
zone_name = self._get_query_zone_name(msg)
rcode_ = self._check_xfrout_available(zone_name)
if rcode_ != Rcode.NOERROR():
self._log.log_message("info", "transfer of '%s/IN' failed: %s",
zone_name, rcode_.to_text())
- return self. _reply_query_with_error_rcode(msg, sock, rcode_)
+ return self. _reply_query_with_error_rcode(msg, sock_fd, rcode_)
try:
self._log.log_message("info", "transfer of '%s/IN': AXFR started" % zone_name)
- self._reply_xfrout_query(msg, sock, zone_name)
+ self._reply_xfrout_query(msg, sock_fd, zone_name)
self._log.log_message("info", "transfer of '%s/IN': AXFR end" % zone_name)
except Exception as err:
self._log.log_message("error", str(err))
- self.server.decrease_transfers_counter()
- return
+ self._server.decrease_transfers_counter()
+ return
def _clear_message(self, msg):
qid = msg.get_qid()
opcode = msg.get_opcode()
rcode = msg.get_rcode()
-
+
msg.clear(Message.RENDER)
msg.set_qid(qid)
msg.set_opcode(opcode)
msg.set_rcode(rcode)
- msg.set_header_flag(MessageFlag.AA())
- msg.set_header_flag(MessageFlag.QR())
+ msg.set_header_flag(Message.HEADERFLAG_AA)
+ msg.set_header_flag(Message.HEADERFLAG_QR)
return msg
def _create_rrset_from_db_record(self, record):
- '''Create one rrset from one record of datasource, if the schema of record is changed,
+ '''Create one rrset from one record of datasource, if the schema of record is changed,
This function should be updated first.
'''
rrtype_ = RRType(record[5])
@@ -239,35 +238,35 @@ class XfroutSession(BaseRequestHandler):
rrset_ = RRset(Name(record[2]), RRClass("IN"), rrtype_, RRTTL( int(record[4])))
rrset_.add_rdata(rdata_)
return rrset_
-
- def _send_message_with_last_soa(self, msg, sock, rrset_soa, message_upper_len):
+
+ def _send_message_with_last_soa(self, msg, sock_fd, rrset_soa, message_upper_len):
'''Add the SOA record to the end of message. If it can't be
added, a new message should be created to send out the last soa .
'''
rrset_len = get_rrset_len(rrset_soa)
if message_upper_len + rrset_len < XFROUT_MAX_MESSAGE_SIZE:
- msg.add_rrset(Section.ANSWER(), rrset_soa)
+ msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
else:
- self._send_message(sock, msg)
+ self._send_message(sock_fd, msg)
msg = self._clear_message(msg)
- msg.add_rrset(Section.ANSWER(), rrset_soa)
+ msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
- self._send_message(sock, msg)
+ self._send_message(sock_fd, msg)
- def _reply_xfrout_query(self, msg, sock, zone_name):
+ def _reply_xfrout_query(self, msg, sock_fd, zone_name):
#TODO, there should be a better way to insert rrset.
msg.make_response()
- msg.set_header_flag(MessageFlag.AA())
- soa_record = sqlite3_ds.get_zone_soa(zone_name, self.server.get_db_file())
+ msg.set_header_flag(Message.HEADERFLAG_AA)
+ soa_record = sqlite3_ds.get_zone_soa(zone_name, self._server.get_db_file())
rrset_soa = self._create_rrset_from_db_record(soa_record)
- msg.add_rrset(Section.ANSWER(), rrset_soa)
+ msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
message_upper_len = get_rrset_len(rrset_soa)
- for rr_data in sqlite3_ds.get_zone_datas(zone_name, self.server.get_db_file()):
- if self.server._shutdown_event.is_set(): # Check if xfrout is shutdown
+ for rr_data in sqlite3_ds.get_zone_datas(zone_name, self._server.get_db_file()):
+ if self._server._shutdown_event.is_set(): # Check if xfrout is shutdown
self._log.log_message("info", "xfrout process is being shutdown")
return
@@ -282,16 +281,16 @@ class XfroutSession(BaseRequestHandler):
# may have reached the limit
rrset_len = get_rrset_len(rrset_)
if message_upper_len + rrset_len < XFROUT_MAX_MESSAGE_SIZE:
- msg.add_rrset(Section.ANSWER(), rrset_)
+ msg.add_rrset(Message.SECTION_ANSWER, rrset_)
message_upper_len += rrset_len
continue
- self._send_message(sock, msg)
+ self._send_message(sock_fd, msg)
msg = self._clear_message(msg)
- msg.add_rrset(Section.ANSWER(), rrset_) # Add the rrset to the new message
+ msg.add_rrset(Message.SECTION_ANSWER, rrset_) # Add the rrset to the new message
message_upper_len = rrset_len
- self._send_message_with_last_soa(msg, sock, rrset_soa, message_upper_len)
+ self._send_message_with_last_soa(msg, sock_fd, rrset_soa, message_upper_len)
class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
'''The unix domain socket server which accept xfr query sent from auth server.'''
@@ -304,22 +303,107 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
self._lock = threading.Lock()
self._transfers_counter = 0
self._shutdown_event = shutdown_event
+ self._write_sock, self._read_sock = socket.socketpair()
self._log = log
self.update_config_data(config_data)
self._cc = cc
-
- def finish_request(self, request, client_address):
+
+ def _receive_query_message(self, sock):
+ ''' receive request message from sock'''
+ # receive data length
+ data_len = sock.recv(2)
+ if not data_len:
+ return None
+ msg_len = struct.unpack('!H', data_len)[0]
+ # receive data
+ recv_size = 0
+ msgdata = b''
+ while recv_size < msg_len:
+ data = sock.recv(msg_len - recv_size)
+ if not data:
+ return None
+ recv_size += len(data)
+ msgdata += data
+
+ return msgdata
+
+ def handle_request(self):
+ ''' Enable server handle a request until shutdown or auth is closed.'''
+ try:
+ request, client_address = self.get_request()
+ except socket.error:
+ self._log.log_message("error", "Failed to fetch request")
+ return
+
+ # Check self._shutdown_event to ensure the real shutdown comes.
+ # Linux could trigger a spurious readable event on the _read_sock
+ # due to a bug, so we need perform a double check.
+ while not self._shutdown_event.is_set(): # Check if xfrout is shutdown
+ try:
+ (rlist, wlist, xlist) = select.select([self._read_sock, request], [], [])
+ except select.error as e:
+ if e.args[0] == errno.EINTR:
+ (rlist, wlist, xlist) = ([], [], [])
+ continue
+ else:
+ self._log.log_message("error", "Error with select(): %s" %e)
+ break
+
+ # self.server._shutdown_event will be set by now, if it is not a false
+ # alarm
+ if self._read_sock in rlist:
+ continue
+
+ try:
+ self.process_request(request)
+ except:
+ self._log.log_message("error", "Exception happened during processing of %s"
+ % str(client_address))
+ break
+
+ def _handle_request_noblock(self):
+ """Override the function _handle_request_noblock(), it creates a new
+ thread to handle requests for each auth"""
+ td = threading.Thread(target=self.handle_request)
+ td.setDaemon(True)
+ td.start()
+
+ def process_request(self, request):
+ """Receive socket fd and query message from auth, then
+ start a new thread to process the request."""
+ sock_fd = recv_fd(request.fileno())
+ if sock_fd < 0:
+ # 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:
+ self._log.log_message("error", "Failed to receive the file descriptor for XFR connection")
+ return
+
+ # receive request msg
+ request_data = self._receive_query_message(request)
+ if not request_data:
+ return
+
+ t = threading.Thread(target = self.finish_request,
+ args = (sock_fd, request_data))
+ if self.daemon_threads:
+ t.daemon = True
+ t.start()
+
+
+ def finish_request(self, sock_fd, request_data):
'''Finish one request by instantiating RequestHandlerClass.'''
- self.RequestHandlerClass(request, client_address, self, self._log)
+ self.RequestHandlerClass(sock_fd, request_data, self, self._log)
def _remove_unused_sock_file(self, sock_file):
- '''Try to remove the socket file. If the file is being used
- by one running xfrout process, exit from python.
+ '''Try to remove the socket file. If the file is being used
+ by one running xfrout process, exit from python.
If it's not a socket file or nobody is listening
, it will be removed. If it can't be removed, exit from python. '''
if self._sock_file_in_use(sock_file):
- sys.stderr.write("[b10-xfrout] Fail to start xfrout process, unix socket"
- " file '%s' is being used by another xfrout process\n" % sock_file)
+ self._log.log_message("error", "Fail to start xfrout process, unix socket file '%s'"
+ " is being used by another xfrout process\n" % sock_file)
sys.exit(0)
else:
if not os.path.exists(sock_file):
@@ -328,12 +412,12 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
try:
os.unlink(sock_file)
except OSError as err:
- sys.stderr.write('[b10-xfrout] Fail to remove file %s: %s\n' % (sock_file, err))
+ self._log.log_message("error", "[b10-xfrout] Fail to remove file %s: %s\n" % (sock_file, err))
sys.exit(0)
-
+
def _sock_file_in_use(self, sock_file):
- '''Check whether the socket file 'sock_file' exists and
- is being used by one running xfrout process. If it is,
+ '''Check whether the socket file 'sock_file' exists and
+ is being used by one running xfrout process. If it is,
return True, or else return False. '''
try:
sock = socket.socket(socket.AF_UNIX)
@@ -341,14 +425,15 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
except socket.error as err:
return False
else:
- return True
+ return True
def shutdown(self):
+ self._write_sock.send(b"shutdown") #terminate the xfrout session thread
super().shutdown() # call the shutdown() of class socketserver_mixin.NoPollMixIn
try:
os.unlink(self._sock_file)
except Exception as e:
- self._log.log_message("error", str(e))
+ self._log.log_message('error', str(e))
def update_config_data(self, new_config):
'''Apply the new config setting of xfrout module. '''
@@ -390,7 +475,7 @@ class XfroutServer:
def __init__(self):
self._unix_socket_server = None
self._log = None
- self._listen_sock_file = UNIX_SOCKET_FILE
+ self._listen_sock_file = UNIX_SOCKET_FILE
self._shutdown_event = threading.Event()
self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
self._config_data = self._cc.get_full_config()
@@ -404,12 +489,12 @@ class XfroutServer:
def _start_xfr_query_listener(self):
'''Start a new thread to accept xfr query. '''
- self._unix_socket_server = UnixSockServer(self._listen_sock_file, XfroutSession,
+ self._unix_socket_server = UnixSockServer(self._listen_sock_file, XfroutSession,
self._shutdown_event, self._config_data,
self._cc, self._log);
listener = threading.Thread(target=self._unix_socket_server.serve_forever)
listener.start()
-
+
def _start_notifier(self):
datasrc = self._unix_socket_server.get_db_file()
self._notifier = notify_out.NotifyOut(datasrc, self._log)
@@ -472,7 +557,7 @@ class XfroutServer:
else:
answer = create_answer(1, "Bad command parameter:" + str(args))
- else:
+ else:
answer = create_answer(1, "Unknown command:" + str(cmd))
return answer
@@ -514,7 +599,7 @@ if '__main__' == __name__:
sys.stderr.write("[b10-xfrout] Error creating xfrout, "
"is the command channel daemon running?\n")
except SessionTimeout as e:
- sys.stderr.write("[b10-xfrout] Error creating xfrout, "
+ sys.stderr.write("[b10-xfrout] Error creating xfrout, "
"is the configuration manager running?\n")
except ModuleCCSessionError as e:
sys.stderr.write("[b10-xfrout] exit xfrout process:%s\n" % str(e))
diff --git a/src/bin/xfrout/xfrout.spec.pre.in b/src/bin/xfrout/xfrout.spec.pre.in
index dce8b67..941db72 100644
--- a/src/bin/xfrout/xfrout.spec.pre.in
+++ b/src/bin/xfrout/xfrout.spec.pre.in
@@ -9,12 +9,6 @@
"item_default": 10
},
{
- "item_name": "db_file",
- "item_type": "string",
- "item_optional": false,
- "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3"
- },
- {
"item_name": "log_name",
"item_type": "string",
"item_optional": false,
@@ -54,4 +48,4 @@
]
}
}
-
+
diff --git a/src/bin/zonemgr/Makefile.am b/src/bin/zonemgr/Makefile.am
index dd3e67a..410279a 100644
--- a/src/bin/zonemgr/Makefile.am
+++ b/src/bin/zonemgr/Makefile.am
@@ -4,7 +4,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-zonemgr
-b10_zonemgrdir = $(DESTDIR)$(pkgdatadir)
+b10_zonemgrdir = $(pkgdatadir)
b10_zonemgr_DATA = zonemgr.spec
CLEANFILES = b10-zonemgr zonemgr.pyc zonemgr.spec
diff --git a/src/bin/zonemgr/b10-zonemgr.8 b/src/bin/zonemgr/b10-zonemgr.8
index a53887c..fbd0602 100644
--- a/src/bin/zonemgr/b10-zonemgr.8
+++ b/src/bin/zonemgr/b10-zonemgr.8
@@ -2,12 +2,12 @@
.\" Title: b10-zonemgr
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: September 8, 2010
+.\" Date: October 18, 2010
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-ZONEMGR" "8" "September 8, 2010" "BIND10" "BIND10"
+.TH "B10\-ZONEMGR" "8" "October 18, 2010" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -43,6 +43,26 @@ receives its configurations from
\fBb10-cfgmgr\fR(8)\&.
.SH "CONFIGURATION AND COMMANDS"
.PP
+The configurable settings are:
+.PP
+
+\fIjitter_scope\fR
+defines the random jitter range subtracted from the refresh and retry timers to avoid many zones from refreshing at the same time\&. The refresh or retry time actually used is a random time between the defined refresh or retry time and it multiplied by the
+\fIjitter_scope\fR\&. This is re\-evaluated after each refresh or retry\&. This value is a real number and the maximum is 0\&.5 (half of the refresh or retry time)\&. The default is 0\&.25\&. Set to 0 to disable the jitter\&.
+.PP
+
+\fIlowerbound_refresh\fR
+defines the minimum SOA REFRESH time in seconds\&. The default is 10\&.
+.PP
+
+\fIlowerbound_retry\fR
+defines the minimum SOA RETRY time in seconds\&. The default is 5\&.
+.PP
+
+\fImax_transfer_timeout\fR
+defines the maximum amount of time in seconds for a transfer\&.
+The default is 14400 (4 hours)\&.
+.PP
The configuration commands are:
.PP
diff --git a/src/bin/zonemgr/b10-zonemgr.xml b/src/bin/zonemgr/b10-zonemgr.xml
index d0fe44b..4d796ee 100644
--- a/src/bin/zonemgr/b10-zonemgr.xml
+++ b/src/bin/zonemgr/b10-zonemgr.xml
@@ -17,11 +17,10 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
-<!-- $Id$ -->
<refentry>
<refentryinfo>
- <date>September 8, 2010</date>
+ <date>October 18, 2010</date>
</refentryinfo>
<refmeta>
@@ -90,11 +89,39 @@
<refsect1>
<title>CONFIGURATION AND COMMANDS</title>
-<!--
<para>
The configurable settings are:
</para>
--->
+ <para>
+ <varname>jitter_scope</varname>
+ defines the random jitter range subtracted from the refresh
+ and retry timers to avoid many zones from refreshing at the
+ same time.
+ The refresh or retry time actually used is a random time
+ between the defined refresh or retry time and it multiplied
+ by the <varname>jitter_scope</varname>.
+ This is re-evaluated after each refresh or retry.
+ This value is a real number and the maximum is 0.5 (half of the
+ refresh or retry time).
+ The default is 0.25.
+ Set to 0 to disable the jitter.
+ </para>
+ <para>
+ <varname>lowerbound_refresh</varname>
+ defines the minimum SOA REFRESH time in seconds.
+ The default is 10.
+ </para>
+ <para>
+ <varname>lowerbound_retry</varname>
+ defines the minimum SOA RETRY time in seconds.
+ The default is 5.
+ </para>
+ <para>
+ <varname>max_transfer_timeout</varname>
+ defines the maximum amount of time in seconds for a transfer.
+<!-- TODO: what is the purpose of this? -->
+ The default is 14400 (4 hours).
+ </para>
<!-- TODO: formating -->
<para>
diff --git a/src/bin/zonemgr/run_b10-zonemgr.sh.in b/src/bin/zonemgr/run_b10-zonemgr.sh.in
index e608b65..cd1f80c 100644
--- a/src/bin/zonemgr/run_b10-zonemgr.sh.in
+++ b/src/bin/zonemgr/run_b10-zonemgr.sh.in
@@ -22,6 +22,8 @@ MYPATH_PATH=@abs_top_builddir@/src/bin/zonemgr
PYTHONPATH=@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/.libs
export PYTHONPATH
-cd ${MYPATH_PATH}
-${PYTHON_EXEC} b10-zonemgr
+BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
+export BIND10_MSGQ_SOCKET_FILE
+cd ${MYPATH_PATH}
+exec ${PYTHON_EXEC} b10-zonemgr "$@"
diff --git a/src/bin/zonemgr/tests/Makefile.am b/src/bin/zonemgr/tests/Makefile.am
index dd961b5..496c1a4 100644
--- a/src/bin/zonemgr/tests/Makefile.am
+++ b/src/bin/zonemgr/tests/Makefile.am
@@ -1,14 +1,17 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
PYTESTS = zonemgr_test.py
EXTRA_DIST = $(PYTESTS)
-
CLEANFILES = initdb.file
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_builddir)/src/bin/zonemgr:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/xfr/.libs \
- $(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/zonemgr/tests/zonemgr_test.py b/src/bin/zonemgr/tests/zonemgr_test.py
index 88e8af4..70dc1b0 100644
--- a/src/bin/zonemgr/tests/zonemgr_test.py
+++ b/src/bin/zonemgr/tests/zonemgr_test.py
@@ -43,6 +43,9 @@ class MySession():
if module_name not in ("Auth", "Xfrin"):
raise ZonemgrTestException("module name not exist")
+ def group_recvmsg(self, nonblock, seq):
+ return None, None
+
class MyZonemgrRefresh(ZonemgrRefresh):
def __init__(self):
class FakeConfig:
diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
old mode 100644
new mode 100755
index 24b3f52..2cededf
--- 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
@@ -90,26 +89,26 @@ class ZonemgrException(Exception):
class ZonemgrRefresh:
"""This class will maintain and manage zone refresh info.
- It also provides methods to keep track of zone timers and
- do zone refresh.
- Zone timers can be started by calling run_timer(), and it
+ It also provides methods to keep track of zone timers and
+ do zone refresh.
+ Zone timers can be started by calling run_timer(), and it
can be stopped by calling shutdown() in another thread.
"""
def __init__(self, cc, db_file, slave_socket, config_data):
self._cc = cc
- self._check_sock = slave_socket
+ self._check_sock = slave_socket
self._db_file = db_file
self.update_config_data(config_data)
- self._zonemgr_refresh_info = {}
+ self._zonemgr_refresh_info = {}
self._build_zonemgr_refresh_info()
self._running = False
-
+
def _random_jitter(self, max, jitter):
"""Imposes some random jitters for refresh and
retry timers to avoid many zones need to do refresh
- at the same time.
+ at the same time.
The value should be between (max - jitter) and max.
"""
if 0 == jitter:
@@ -120,7 +119,7 @@ class ZonemgrRefresh:
return time.time()
def _set_zone_timer(self, zone_name_class, max, jitter):
- """Set zone next refresh time.
+ """Set zone next refresh time.
jitter should not be bigger than half the original value."""
self._set_zone_next_refresh_time(zone_name_class, self._get_current_time() + \
self._random_jitter(max, jitter))
@@ -143,7 +142,7 @@ class ZonemgrRefresh:
def _set_zone_notify_timer(self, zone_name_class):
"""Set zone next refresh time after receiving notify
- next_refresh_time = now
+ next_refresh_time = now
"""
self._set_zone_timer(zone_name_class, 0, 0)
@@ -199,7 +198,7 @@ class ZonemgrRefresh:
raise ZonemgrException("[b10-zonemgr] zone (%s, %s) doesn't have soa." % zone_name_class)
zone_info["zone_soa_rdata"] = zone_soa[7]
zone_info["zone_state"] = ZONE_OK
- zone_info["last_refresh_time"] = self._get_current_time()
+ zone_info["last_refresh_time"] = self._get_current_time()
zone_info["next_refresh_time"] = self._get_current_time() + \
float(zone_soa[7].split(" ")[REFRESH_OFFSET])
self._zonemgr_refresh_info[zone_name_class] = zone_info
@@ -233,7 +232,7 @@ class ZonemgrRefresh:
def _get_zone_notifier_master(self, zone_name_class):
if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()):
- return self._zonemgr_refresh_info[zone_name_class]["notify_master"]
+ return self._zonemgr_refresh_info[zone_name_class]["notify_master"]
return None
@@ -248,7 +247,7 @@ class ZonemgrRefresh:
return self._zonemgr_refresh_info[zone_name_class]["zone_state"]
def _set_zone_state(self, zone_name_class, zone_state):
- self._zonemgr_refresh_info[zone_name_class]["zone_state"] = zone_state
+ self._zonemgr_refresh_info[zone_name_class]["zone_state"] = zone_state
def _get_zone_refresh_timeout(self, zone_name_class):
return self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"]
@@ -266,9 +265,13 @@ class ZonemgrRefresh:
"""Send command between modules."""
msg = create_command(command_name, params)
try:
- self._cc.group_sendmsg(msg, module_name)
+ seq = self._cc.group_sendmsg(msg, module_name)
+ try:
+ answer, env = self._cc.group_recvmsg(False, seq)
+ except isc.cc.session.SessionTimeout:
+ pass # for now we just ignore the failure
except socket.error:
- sys.stderr.write("[b10-zonemgr] Failed to send to module %s, the session has been closed." % module_name)
+ sys.stderr.write("[b10-zonemgr] Failed to send to module %s, the session has been closed." % module_name)
def _find_need_do_refresh_zone(self):
"""Find the first zone need do refresh, if no zone need
@@ -281,10 +284,10 @@ class ZonemgrRefresh:
if (ZONE_REFRESHING == zone_state and
(self._get_zone_refresh_timeout(zone_name_class) > self._get_current_time())):
continue
-
- # Get the zone with minimum next_refresh_time
- if ((zone_need_refresh is None) or
- (self._get_zone_next_refresh_time(zone_name_class) <
+
+ # Get the zone with minimum next_refresh_time
+ if ((zone_need_refresh is None) or
+ (self._get_zone_next_refresh_time(zone_name_class) <
self._get_zone_next_refresh_time(zone_need_refresh))):
zone_need_refresh = zone_name_class
@@ -292,14 +295,14 @@ class ZonemgrRefresh:
if (self._get_zone_next_refresh_time(zone_need_refresh) < self._get_current_time()):
break
- return zone_need_refresh
+ return zone_need_refresh
+
-
def _do_refresh(self, zone_name_class):
"""Do zone refresh."""
log_msg("Do refresh for zone (%s, %s)." % zone_name_class)
self._set_zone_state(zone_name_class, ZONE_REFRESHING)
- self._set_zone_refresh_timeout(zone_name_class, self._get_current_time() + self._max_transfer_timeout)
+ self._set_zone_refresh_timeout(zone_name_class, self._get_current_time() + self._max_transfer_timeout)
notify_master = self._get_zone_notifier_master(zone_name_class)
# If the zone has notify master, send notify command to xfrin module
if notify_master:
@@ -307,7 +310,7 @@ class ZonemgrRefresh:
"zone_class" : zone_name_class[1],
"master" : notify_master
}
- self._send_command(XFRIN_MODULE_NAME, ZONE_NOTIFY_COMMAND, param)
+ self._send_command(XFRIN_MODULE_NAME, ZONE_NOTIFY_COMMAND, param)
self._clear_zone_notifier_master(zone_name_class)
# Send refresh command to xfrin module
else:
@@ -324,23 +327,29 @@ class ZonemgrRefresh:
return False
def _run_timer(self, start_event):
- start_event.set()
while self._running:
+ # Notify run_timer that we already started and are inside the loop.
+ # It is set only once, but when it was outside the loop, there was
+ # a race condition and _running could be set to false before we
+ # could enter it
+ if start_event:
+ start_event.set()
+ start_event = None
# If zonemgr has no zone, set timer timeout to self._lowerbound_retry.
if self._zone_mgr_is_empty():
- timeout = self._lowerbound_retry
+ timeout = self._lowerbound_retry
else:
zone_need_refresh = self._find_need_do_refresh_zone()
- # If don't get zone with minimum next refresh time, set timer timeout to self._lowerbound_retry
+ # If don't get zone with minimum next refresh time, set timer timeout to self._lowerbound_retry.
if not zone_need_refresh:
- timeout = self._lowerbound_retry
+ timeout = self._lowerbound_retry
else:
timeout = self._get_zone_next_refresh_time(zone_need_refresh) - self._get_current_time()
if (timeout < 0):
self._do_refresh(zone_need_refresh)
continue
- """ Wait for the socket notification for a maximum time of timeout
+ """ Wait for the socket notification for a maximum time of timeout
in seconds (as float)."""
try:
rlist, wlist, xlist = select.select([self._check_sock, self._read_sock], [], [], timeout)
@@ -352,9 +361,10 @@ class ZonemgrRefresh:
break
for fd in rlist:
- if fd == self._read_sock: # awaken by shutdown socket
+ if fd == self._read_sock: # awaken by shutdown socket
# self._running will be False by now, if it is not a false
- # alarm
+ # alarm (linux kernel is said to trigger spurious wakeup
+ # on a filehandle that is not really readable).
continue
if fd == self._check_sock: # awaken by check socket
self._check_sock.recv(32)
@@ -416,7 +426,7 @@ class Zonemgr:
self._zone_refresh = None
self._setup_session()
self._db_file = self.get_db_file()
- # Create socket pair for communicating between main thread and zonemgr timer thread
+ # Create socket pair for communicating between main thread and zonemgr timer thread
self._master_socket, self._slave_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
self._zone_refresh = ZonemgrRefresh(self._cc, self._db_file, self._slave_socket, self._config_data)
self._zone_refresh.run_timer()
@@ -426,7 +436,7 @@ class Zonemgr:
self.running = False
def _setup_session(self):
- """Setup two sessions for zonemgr, one(self._module_cc) is used for receiving
+ """Setup two sessions for zonemgr, one(self._module_cc) is used for receiving
commands and config data sent from other modules, another one (self._cc)
is used to send commands to proper modules."""
self._cc = isc.cc.Session()
@@ -450,7 +460,7 @@ class Zonemgr:
def shutdown(self):
"""Shutdown the zonemgr process. the thread which is keeping track of zone
timers should be terminated.
- """
+ """
self._zone_refresh.shutdown()
self._slave_socket.close()
@@ -503,7 +513,7 @@ class Zonemgr:
def command_handler(self, command, args):
"""Handle command receivd from command channel.
- ZONE_NOTIFY_COMMAND is issued by Auth process; ZONE_XFRIN_SUCCESS_COMMAND
+ ZONE_NOTIFY_COMMAND is issued by Auth process; ZONE_XFRIN_SUCCESS_COMMAND
and ZONE_XFRIN_FAILED_COMMAND are issued by Xfrin process; shutdown is issued
by a user or Boss process. """
answer = create_answer(0)
@@ -572,10 +582,10 @@ if '__main__' == __name__:
except KeyboardInterrupt:
sys.stderr.write("[b10-zonemgr] exit zonemgr process\n")
except isc.cc.session.SessionError as e:
- sys.stderr.write("[b10-zonemgr] Error creating zonemgr, "
+ sys.stderr.write("[b10-zonemgr] Error creating zonemgr, "
"is the command channel daemon running?\n")
except isc.cc.session.SessionTimeout as e:
- sys.stderr.write("[b10-zonemgr] Error creating zonemgr, "
+ sys.stderr.write("[b10-zonemgr] Error creating zonemgr, "
"is the configuration manager running?\n")
except isc.config.ModuleCCSessionError as e:
sys.stderr.write("[b10-zonemgr] exit zonemgr process: %s\n" % str(e))
diff --git a/src/cppcheck-suppress.lst b/src/cppcheck-suppress.lst
new file mode 100644
index 0000000..3e4dcd6
--- /dev/null
+++ b/src/cppcheck-suppress.lst
@@ -0,0 +1,15 @@
+// On some systems cppcheck produces false alarms about 'missing includes'.
+// the following two will suppress, depending on the cppcheck version
+debug
+missingInclude
+// This is a template, and should be excluded from the check
+unreadVariable:src/lib/dns/rdata/template.cc:59
+// These three trigger warnings due to the incomplete implementation. This is
+// our problem, but we need to suppress the warnings for now.
+functionConst:src/lib/cache/resolver_cache.h
+functionConst:src/lib/cache/message_cache.h
+functionConst:src/lib/cache/rrset_cache.h
+// Intentional self assignment tests. Suppress warning about them.
+selfAssignment:src/lib/dns/tests/name_unittest.cc:292
+selfAssignment:src/lib/dns/tests/rdata_unittest.cc:227
+selfAssignment:src/lib/dns/tests/tsigkey_unittest.cc:104
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index e18c437..8525b8d 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -1 +1,2 @@
-SUBDIRS = exceptions dns cc config datasrc python xfr bench
+SUBDIRS = exceptions dns cc config 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
new file mode 100644
index 0000000..2fda728
--- /dev/null
+++ b/src/lib/asiolink/Makefile.am
@@ -0,0 +1,51 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+# This is a wrapper library solely used for b10-auth. The ASIO header files
+# have some code fragments that would hit gcc's unused-parameter warning,
+# 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.cc dns_service.h
+libasiolink_la_SOURCES += dummy_io_cb.h
+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.cc io_endpoint.h
+libasiolink_la_SOURCES += io_error.h
+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 += simple_callback.h
+libasiolink_la_SOURCES += tcp_endpoint.h
+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.cc udp_server.h
+libasiolink_la_SOURCES += udp_socket.h
+
+EXTRA_DIST = asiodef.msg
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# B10_CXXFLAGS)
+libasiolink_la_CXXFLAGS = $(AM_CXXFLAGS)
+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
diff --git a/src/lib/asiolink/README b/src/lib/asiolink/README
new file mode 100644
index 0000000..6bd1a73
--- /dev/null
+++ b/src/lib/asiolink/README
@@ -0,0 +1,182 @@
+The asiolink library is intended to provide an abstraction layer between
+BIND10 modules and the socket I/O subsystem we are using (currently, the
+headers-only version of ASIO, release 1.43). This has several benefits,
+including:
+
+ - Simple interface
+
+ - Back-end flexibility: It would be easy to switch from using
+ ASIO to boost::asio, and even relatively straightforward to switch
+ to any other asynchronous I/O system.
+
+ - Cleaner compilation: The ASIO headers include code which can
+ generate warnings in some compilers due to unused parameters and
+ such. Including ASIO header files throughout the BIND 10 tree would
+ require us to relax the strictness of our error checking. Including
+ them in only one place allows us to relax strictness here, while
+ leaving it in place elsewhere.
+
+Currently, the asiolink library only supports DNS servers (i.e., b10-auth
+and b10-resolver). The plan is to make it more generic and allow it to
+support other modules as well.
+
+Some of the classes defined here--for example, IOSocket, IOEndpoint,
+and IOAddress--are to be used by BIND 10 modules as wrappers around
+ASIO-specific classes.
+
+Other classes implement the DNS protocol on behalf of BIND 10 modules.
+
+These DNS server and client routines are written using the "stackless
+coroutine" pattern invented by Chris Kohlhoff and described at
+http://blog.think-async.com/2010/03/potted-guide-to-stackless-coroutines.html.
+This is intended to simplify development a bit, since it allows the
+routines to be written in a straightfowrard step-step-step fashion rather
+than as a complex chain of separate handler functions.
+
+Coroutine objects (i.e., UDPServer, TCPServer and IOFetch) are objects
+with reenterable operator() members. When an instance of one of these
+classes is called as a function, it resumes at the position where it left
+off. Thus, a UDPServer can issue an asynchronous I/O call and specify
+itself as the handler object; when the call completes, the UDPServer
+carries on at the same position. As a result, the code can look as
+if it were using synchronous, not asynchronous, I/O, providing some of
+the benefit of threading but with minimal switching overhead.
+
+So, in simplified form, the behavior of a DNS Server is:
+
+ REENTER:
+ while true:
+ YIELD packet = read_packet
+ FORK
+ if not parent:
+ break
+
+ # This callback informs the caller that a packet has arrived, and
+ # gives it a chance to update configuration, etc
+ SimpleCallback(packet)
+ YIELD answer = DNSLookup(packet, this)
+ response = DNSAnswer(answer)
+ YIELD send(response)
+
+At each "YIELD" point, the coroutine initiates an asynchronous operation,
+then pauses and turns over control to some other task on the ASIO service
+queue. When the operation completes, the coroutine resumes.
+
+DNSLookup, DNSAnswer and SimpleCallback define callback methods
+used by a DNS Server to communicate with the module that called it.
+They are abstract-only classes whose concrete implementations
+are supplied by the calling module.
+
+The DNSLookup callback always runs asynchronously. Concrete
+implementations must be sure to call the server's "resume" method when
+it is finished.
+
+In an authoritative server, the DNSLookup implementation would examine
+the query, look up the answer, then call "resume". (See the diagram
+in doc/auth_process.jpg.)
+
+In a recursive server, the DNSLookup impelemtation would initiate a
+DNSQuery, which in turn would be responsible for calling the server's
+"resume" method. (See the diagram in doc/recursive_process.jpg.)
+
+A DNSQuery object is intended to handle resolution of a query over
+the network when the local authoritative data sources or cache are not
+sufficient. The plan is that it will make use of subsidiary DNSFetch
+calls to get data from particular authoritative servers, and when it has
+gotten a complete answer, it calls "resume".
+
+In current form, however, DNSQuery is much simpler; it forwards queries
+to a single upstream resolver and passes the answers back to the client.
+It is constructed with the address of the forward server. Queries are
+initiated with the question to ask the forward server, a buffer into
+which to write the answer, and a pointer to the coroutine to be resumed
+when the answer has arrived. In simplified form, the DNSQuery routine is:
+
+ REENTER:
+ render the question into a wire-format query packet
+ YIELD send(query)
+ YIELD response = read_packet
+ server->resume
+
+Currently, DNSQuery is only implemented for UDP queries. In future work
+it will be necessary to write code to fall back to TCP when circumstances
+require it.
+
+
+Upstream Fetches
+================
+Upstream fetches (queries by the resolver on behalf of a client) are made
+using a slightly-modified version of the pattern described above.
+
+Sockets
+-------
+First, it will be useful to understand the class hierarchy used in the
+fetch logic:
+
+ IOSocket
+ |
+ IOAsioSocket
+ |
+ +-----+-----+
+ | |
+UDPSocket TCPSocket
+
+IOSocket is a wrapper class for a socket and is used by the authoritative
+server code. It is an abstract base class, providing little more that the ability to hold the socket and to return the protocol in use.
+
+Built on this is IOAsioSocket, which adds the open, close, asyncSend and
+asyncReceive methods. This is a template class, which takes as template
+argument the class of the object that will be used as the callback when the
+asynchronous operation completes. This object can be of any type, but must
+include an operator() method with the signature:
+
+ operator()(asio::error_code ec, size_t length)
+
+... the two arguments being the status of the completed I/O operation and
+the number of bytes transferred. (In the case of the open method, the second
+argument will be zero.)
+
+Finally, the TCPSocket and UDPSocket classes provide the body of the
+asynchronous operations.
+
+Fetch Sequence
+--------------
+The fetch is implemented by the IOFetch class, which takes as argument the
+protocol to use. The sequence is:
+
+ REENTER:
+ render the question into a wire-format query packet
+ open() // Open socket and optionally connect
+ if (! synchronous) {
+ YIELD;
+ }
+ YIELD asyncSend(query) // Send query
+ do {
+ YIELD asyncReceive(response) // Read response
+ } while (! complete(response))
+ close() // Drop connection and close socket
+ server->resume
+
+The open() method opens a socket for use. On TCP, it also makes a
+connection to the remote end. So under UDP the operation will complete
+immediately, but under TCP it could take a long time. One solution would be
+for the open operation to post an event to the I/O queue; then both cases
+could be regarded as being equivalent, with the completion being signalled
+by the posting of the completion event. However UDP is the most common case
+and that would involve extra overhead. So the open() returns a status
+indicating whether the operation completed asynchronously. If it did, the
+code yields back to the coroutine; if not the yield is bypassed.
+
+The asynchronous send is straightforward, invoking the underlying ASIO
+function. (Note that the address/port is supplied to both the open() and
+asyncSend() methods - it is used by the TCPSocket in open() and by the
+UDPSocket in asyncSend().)
+
+The asyncReceive() method issues an asynchronous read and waits for completion.
+The fetch object keeps track of the amount of data received so far and when
+the receive completes it calls a method on the socket to determine if the
+entire message has been received. (This will always be the case for UDP. On
+TCP though, the message is preceded by a count field as several reads may be
+required to read all the data.) The fetch loops until all the data is read.
+
+Finally, the socket is closed and the server called to resume operation.
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
new file mode 100644
index 0000000..6e8fe84
--- /dev/null
+++ b/src/lib/asiolink/asiolink.h
@@ -0,0 +1,86 @@
+// 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 __ASIOLINK_H
+#define __ASIOLINK_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+
+#include <asiolink/io_service.h>
+#include <asiolink/dns_service.h>
+#include <asiolink/dns_server.h>
+#include <asiolink/dns_lookup.h>
+#include <asiolink/dns_answer.h>
+#include <asiolink/simple_callback.h>
+#include <asiolink/interval_timer.h>
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_message.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/io_error.h>
+
+/// \namespace asiolink
+/// \brief A wrapper interface for the ASIO library.
+///
+/// The \c asiolink namespace is used to define a set of wrapper interfaces
+/// for the ASIO library.
+///
+/// BIND 10 uses the non-Boost version of ASIO because it's header-only,
+/// i.e., does not require a separate library object to be linked, and thus
+/// lowers the bar for introduction.
+///
+/// But the advantage comes with its own costs: since the header-only version
+/// includes more definitions in public header files, it tends to trigger
+/// more compiler warnings for our own sources, and, depending on the
+/// compiler options, may make the build fail.
+///
+/// We also found it may be tricky to use ASIO and standard C++ libraries
+/// in a single translation unit, i.e., a .cc file: depending on the order
+/// of including header files, ASIO may or may not work on some platforms.
+///
+/// This wrapper interface is intended to centralize these
+/// problematic issues in a single sub module. Other BIND 10 modules should
+/// simply include \c asiolink.h and use the wrapper API instead of
+/// including ASIO header files and using ASIO-specific classes directly.
+///
+/// This wrapper may be used for other IO libraries if and when we want to
+/// switch, but generality for that purpose is not the primary goal of
+/// this module. The resulting interfaces are thus straightforward mapping
+/// to the ASIO counterparts.
+///
+/// Notes to developers:
+/// Currently the wrapper interface is fairly specific to use by a
+/// DNS server, i.e., b10-auth or b10-resolver. But the plan is to
+/// generalize it and have other modules use it as well.
+///
+/// One obvious drawback of this approach is performance overhead
+/// due to the additional layer. We should eventually evaluate the cost
+/// of the wrapper abstraction in benchmark tests. Another drawback is
+/// that the wrapper interfaces don't provide all features of ASIO
+/// (at least for the moment). We should also re-evaluate the
+/// maintenance overhead of providing necessary wrappers as we develop
+/// more.
+///
+/// On the other hand, we may be able to exploit the wrapper approach to
+/// simplify the interfaces (by limiting the usage) and unify performance
+/// optimization points.
+///
+/// As for optimization, we may want to provide a custom allocator for
+/// the placeholder of callback handlers:
+/// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html
+
+#endif // __ASIOLINK_H
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_answer.h b/src/lib/asiolink/dns_answer.h
new file mode 100644
index 0000000..84e1f6f
--- /dev/null
+++ b/src/lib/asiolink/dns_answer.h
@@ -0,0 +1,73 @@
+// 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_DNS_ANSWER_H
+#define __ASIOLINK_DNS_ANSWER_H 1
+
+#include <asiolink/io_message.h>
+
+namespace asiolink {
+
+/// \brief The \c DNSAnswer class is an abstract base class for a DNS
+/// Answer provider function.
+///
+/// Specific derived class implementations are hidden within the
+/// implementation. Instances of the derived classes can be called
+/// as functions via the operator() interface. Pointers to these
+/// instances can then be provided to the \c IOService class
+/// via its constructor.
+///
+/// A DNS Answer provider function takes answer data that has been obtained
+/// from a DNS Lookup provider functon and readies it to be sent to the
+/// client. After it has run, the OutputBuffer object passed to it should
+/// contain the answer to the query rendered into wire format.
+class DNSAnswer {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ DNSAnswer(const DNSAnswer& source);
+ DNSAnswer& operator=(const DNSAnswer& 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).
+ DNSAnswer() {}
+public:
+ /// \brief The destructor
+ virtual ~DNSAnswer() {}
+ //@}
+ /// \brief The function operator
+ ///
+ /// This makes its call indirectly via the "self" pointer, ensuring
+ /// that the function ultimately invoked will be the one in the derived
+ /// class.
+ ///
+ /// \param io_message The event message to handle
+ /// \param query_message The DNS MessagePtr of the original query
+ /// \param answer_message The DNS MessagePtr of the answer we are
+ /// building
+ /// \param buffer Intermediate data results are put here
+ virtual void operator()(const IOMessage& io_message,
+ isc::dns::MessagePtr query_message,
+ isc::dns::MessagePtr answer_message,
+ isc::dns::OutputBufferPtr buffer) const = 0;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_DNS_ANSWER_H
diff --git a/src/lib/asiolink/dns_lookup.h b/src/lib/asiolink/dns_lookup.h
new file mode 100644
index 0000000..a79976f
--- /dev/null
+++ b/src/lib/asiolink/dns_lookup.h
@@ -0,0 +1,83 @@
+// 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_DNS_LOOKUP_H
+#define __ASIOLINK_DNS_LOOKUP_H 1
+
+#include <asiolink/io_message.h>
+#include <asiolink/dns_server.h>
+#include <dns/buffer.h>
+#include <dns/message.h>
+
+namespace asiolink {
+
+/// \brief The \c DNSLookup class is an abstract base class for a DNS
+/// Lookup provider function.
+///
+/// Specific derived class implementations are hidden within the
+/// implementation. Instances of the derived classes can be called
+/// as functions via the operator() interface. Pointers to these
+/// instances can then be provided to the \c IOService class
+/// via its constructor.
+///
+/// A DNS Lookup provider function obtains the data needed to answer
+/// a DNS query (e.g., from authoritative data source, cache, or upstream
+/// query). After it has run, the OutputBuffer object passed to it
+/// should contain the answer to the query, in an internal representation.
+class DNSLookup {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ DNSLookup(const DNSLookup& source);
+ DNSLookup& operator=(const DNSLookup& 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).
+ DNSLookup() : self_(this) {}
+public:
+ /// \brief The destructor
+ virtual ~DNSLookup() {}
+ //@}
+ /// \brief The function operator
+ ///
+ /// This makes its call indirectly via the "self" pointer, ensuring
+ /// that the function ultimately invoked will be the one in the derived
+ /// class.
+ ///
+ /// \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 server DNSServer object to use
+ virtual void operator()(const IOMessage& io_message,
+ isc::dns::MessagePtr message,
+ isc::dns::MessagePtr answer_message,
+ isc::dns::OutputBufferPtr buffer,
+ DNSServer* server) const
+ {
+ (*self_)(io_message, message, answer_message, buffer, server);
+ }
+private:
+ DNSLookup* self_;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_DNS_LOOKUP_H
diff --git a/src/lib/asiolink/dns_server.h b/src/lib/asiolink/dns_server.h
new file mode 100644
index 0000000..f15f808
--- /dev/null
+++ b/src/lib/asiolink/dns_server.h
@@ -0,0 +1,155 @@
+// 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_DNS_SERVER_H
+#define __ASIOLINK_DNS_SERVER_H 1
+
+#include <asiolink/io_message.h>
+
+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.
+/// Those functions are described in more detail below under
+/// \c SimpleCallback, \c DNSLookup, and \c DNSAnswer.
+///
+/// Notes to developers:
+/// When constructed, this class (and its derived classes) will have its
+/// "self_" member set to point to "this". Objects of this class (as
+/// instantiated through a base class) are sometimes passed by
+/// reference (as this superclass); calls to methods in the base
+/// class are then rerouted via this pointer to methods in the derived
+/// class. This allows code from outside asiolink, with no specific
+/// knowledge of \c TCPServer or \c UDPServer, to access their methods.
+///
+/// This class is both assignable and copy-constructable. Its subclasses
+/// use the "stackless coroutine" pattern, meaning that it will copy itself
+/// 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
+/// that derived classes be kept small to reduce copy overhead.
+class DNSServer {
+protected:
+ ///
+ /// \name Constructors and destructors
+ ///
+ /// This is intentionally defined as \c protected, as this base class
+ /// should never be instantiated except as part of a derived class.
+ //@{
+ DNSServer() : self_(this) {}
+public:
+ /// \brief The destructor
+ virtual ~DNSServer() {}
+ //@}
+
+ ///
+ /// \name Class methods
+ ///
+ /// These methods all make their calls indirectly via the "self_"
+ /// pointer, ensuring that the functions ultimately invoked will be
+ /// 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(),
+ size_t length = 0)
+ {
+ (*self_)(ec, length);
+ }
+
+ /// \brief Stop current running server
+ virtual void stop() { self_->stop();}
+
+ /// \brief Resume processing of the server coroutine after an
+ /// asynchronous call (e.g., to the DNS Lookup provider) has completed.
+ ///
+ /// \param done If true, this signals the system there is an answer
+ /// to return.
+ virtual void resume(const bool done) { self_->resume(done); }
+
+ /// \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()); }
+
+ /// \brief Returns the current value of the 'coroutine' object
+ ///
+ /// This is a temporary method, intended to be used for debugging
+ /// purposes during development and removed later. It allows
+ /// callers from outside the coroutine object to retrieve information
+ /// about its current state.
+ ///
+ /// \return The value of the 'coroutine' object
+ virtual int value() { return (self_->value()); }
+
+ /// \brief Returns a pointer to a clone of this DNSServer object.
+ ///
+ /// When a \c DNSServer object is copied or assigned, the result will
+ /// normally be another \c DNSServer object containing a copy
+ /// of the original "self_" pointer. Calling clone() guarantees
+ /// that the underlying object is also correctly copied.
+ ///
+ /// \return A deep copy of this DNSServer object
+ virtual DNSServer* clone() { return (self_->clone()); }
+ //@}
+
+protected:
+ /// \brief Lookup handler object.
+ ///
+ /// This is a protected class; it can only be instantiated
+ /// from within a derived class of \c DNSServer.
+ ///
+ /// A server object that has received a query creates an instance
+ /// of this class and scheudles it on the ASIO service queue
+ /// using asio::io_service::post(). When the handler executes, it
+ /// calls the asyncLookup() method in the server object to start a
+ /// DNS lookup. When the lookup is complete, the server object is
+ /// scheduled to resume, again using io_service::post().
+ ///
+ /// Note that the calling object is copied into the handler object,
+ /// not referenced. This is because, once the calling object yields
+ /// control to the handler, it falls out of scope and may disappear
+ template <typename T>
+ class AsyncLookup {
+ public:
+ AsyncLookup(T& caller) : caller_(caller) {}
+ void operator()() { caller_.asyncLookup(); }
+ private:
+ T caller_;
+ };
+
+ /// \brief Carries out a DNS lookup.
+ ///
+ /// This function calls the \c DNSLookup object specified by the
+ /// DNS server when the \c IOService was created, passing along
+ /// the details of the query and a pointer back to the current
+ /// server object. It is called asynchronously via the AsyncLookup
+ /// handler class.
+ virtual void asyncLookup() { self_->asyncLookup(); }
+
+private:
+ DNSServer* self_;
+};
+
+
+} // asiolink
+#endif // __ASIOLINK_DNS_SERVER_H
diff --git a/src/lib/asiolink/dns_service.cc b/src/lib/asiolink/dns_service.cc
new file mode 100644
index 0000000..f17bb44
--- /dev/null
+++ b/src/lib/asiolink/dns_service.cc
@@ -0,0 +1,200 @@
+// 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 <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+
+#include <boost/lexical_cast.hpp>
+
+#include <config.h>
+
+#include <log/dummylog.h>
+
+#include <asio.hpp>
+#include <asiolink/dns_service.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_server.h>
+#include <asiolink/udp_server.h>
+
+#include <log/dummylog.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/foreach.hpp>
+
+using isc::log::dlog;
+
+namespace asiolink {
+
+class SimpleCallback;
+class DNSLookup;
+class DNSAnswer;
+
+namespace {
+
+asio::ip::address
+convertAddr(const std::string& address) {
+ asio::error_code err;
+ asio::ip::address addr = asio::ip::address::from_string(address, err);
+ if (err) {
+ isc_throw(IOError, "Invalid IP address '" << &address << "': "
+ << err.message());
+ }
+ return (addr);
+}
+
+}
+
+
+class DNSServiceImpl {
+public:
+ DNSServiceImpl(IOService& io_service, const char& port,
+ const asio::ip::address* v4addr,
+ const asio::ip::address* v6addr,
+ SimpleCallback* checkin, DNSLookup* lookup,
+ DNSAnswer* answer);
+
+ IOService& io_service_;
+
+ typedef boost::shared_ptr<UDPServer> UDPServerPtr;
+ typedef boost::shared_ptr<TCPServer> TCPServerPtr;
+ typedef boost::shared_ptr<DNSServer> DNSServerPtr;
+ std::vector<DNSServerPtr> servers_;
+ SimpleCallback *checkin_;
+ DNSLookup *lookup_;
+ DNSAnswer *answer_;
+
+ void addServer(uint16_t port, const asio::ip::address& address) {
+ try {
+ dlog(std::string("Initialize TCP server at ") + address.to_string() + ":" + boost::lexical_cast<std::string>(port));
+ TCPServerPtr tcpServer(new TCPServer(io_service_.get_io_service(),
+ address, port, checkin_, lookup_, answer_));
+ (*tcpServer)();
+ servers_.push_back(tcpServer);
+ dlog(std::string("Initialize UDP server at ") + address.to_string() + ":" + boost::lexical_cast<std::string>(port));
+ UDPServerPtr udpServer(new UDPServer(io_service_.get_io_service(),
+ address, port, checkin_, lookup_, answer_));
+ (*udpServer)();
+ servers_.push_back(udpServer);
+ }
+ catch (const asio::system_error& err) {
+ // We need to catch and convert any ASIO level exceptions.
+ // This can happen for unavailable address, binding a privilege port
+ // without the privilege, etc.
+ isc_throw(IOError, "Failed to initialize network servers: " <<
+ err.what());
+ }
+ }
+ void addServer(const char& port, const asio::ip::address& address) {
+ uint16_t portnum;
+ try {
+ // XXX: SunStudio with stlport4 doesn't reject some invalid
+ // representation such as "-1" by lexical_cast<uint16_t>, so
+ // we convert it into a signed integer of a larger size and perform
+ // range check ourselves.
+ const int32_t portnum32 = boost::lexical_cast<int32_t>(&port);
+ if (portnum32 < 0 || portnum32 > 65535) {
+ isc_throw(IOError, "Invalid port number '" << &port);
+ }
+ portnum = portnum32;
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(IOError, "Invalid port number '" << &port << "': " <<
+ ex.what());
+ }
+ addServer(portnum, address);
+ }
+};
+
+DNSServiceImpl::DNSServiceImpl(IOService& io_service,
+ const char& port,
+ const asio::ip::address* const v4addr,
+ const asio::ip::address* const v6addr,
+ SimpleCallback* checkin,
+ DNSLookup* lookup,
+ DNSAnswer* answer) :
+ io_service_(io_service),
+ checkin_(checkin),
+ lookup_(lookup),
+ answer_(answer)
+{
+
+ if (v4addr) {
+ addServer(port, *v4addr);
+ }
+ if (v6addr) {
+ addServer(port, *v6addr);
+ }
+}
+
+DNSService::DNSService(IOService& io_service,
+ const char& port, const char& address,
+ SimpleCallback* checkin,
+ DNSLookup* lookup,
+ DNSAnswer* answer) :
+ impl_(new DNSServiceImpl(io_service, port, NULL, NULL, checkin, lookup,
+ answer)), io_service_(io_service)
+{
+ addServer(port, &address);
+}
+
+DNSService::DNSService(IOService& io_service,
+ const char& port,
+ const bool use_ipv4, const bool use_ipv6,
+ SimpleCallback* checkin,
+ DNSLookup* lookup,
+ DNSAnswer* answer) :
+ impl_(NULL), io_service_(io_service)
+{
+ const asio::ip::address v4addr_any =
+ asio::ip::address(asio::ip::address_v4::any());
+ const asio::ip::address* const v4addrp = use_ipv4 ? &v4addr_any : NULL;
+ const asio::ip::address v6addr_any =
+ asio::ip::address(asio::ip::address_v6::any());
+ const asio::ip::address* const v6addrp = use_ipv6 ? &v6addr_any : NULL;
+ impl_ = new DNSServiceImpl(io_service, port, v4addrp, v6addrp, checkin, lookup, answer);
+}
+
+DNSService::DNSService(IOService& io_service, SimpleCallback* checkin,
+ DNSLookup* lookup, DNSAnswer *answer) :
+ impl_(new DNSServiceImpl(io_service, *"0", NULL, NULL, checkin, lookup,
+ answer)), io_service_(io_service)
+{
+}
+
+DNSService::~DNSService() {
+ delete impl_;
+}
+
+void
+DNSService::addServer(const char& port, const std::string& address) {
+ impl_->addServer(port, convertAddr(address));
+}
+
+void
+DNSService::addServer(uint16_t port, const std::string& address) {
+ impl_->addServer(port, convertAddr(address));
+}
+
+void
+DNSService::clearServers() {
+ BOOST_FOREACH(const DNSServiceImpl::DNSServerPtr& s, impl_->servers_) {
+ s->stop();
+ }
+ impl_->servers_.clear();
+}
+
+
+
+} // namespace asiolink
diff --git a/src/lib/asiolink/dns_service.h b/src/lib/asiolink/dns_service.h
new file mode 100644
index 0000000..9a3fb4c
--- /dev/null
+++ b/src/lib/asiolink/dns_service.h
@@ -0,0 +1,112 @@
+// 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_DNS_SERVICE_H
+#define __ASIOLINK_DNS_SERVICE_H 1
+
+#include <resolve/resolver_interface.h>
+
+#include <asiolink/io_service.h>
+
+namespace asiolink {
+
+class SimpleCallback;
+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
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ DNSService(const DNSService& source);
+ DNSService& operator=(const DNSService& source);
+
+public:
+ /// \brief The constructor with a specific IP address and port on which
+ /// the services listen on.
+ ///
+ /// \param io_service The IOService to work with
+ /// \param port the port to listen on
+ /// \param address the IP address to listen on
+ /// \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)
+ DNSService(IOService& io_service, const char& port,
+ const char& address, SimpleCallback* checkin,
+ DNSLookup* lookup, DNSAnswer* answer);
+ /// \brief The constructor with a specific port on which the services
+ /// listen on.
+ ///
+ /// It effectively listens on "any" IPv4 and/or IPv6 addresses.
+ /// IPv4/IPv6 services will be available if and only if \c use_ipv4
+ /// or \c use_ipv6 is \c true, respectively.
+ ///
+ /// \param io_service The IOService to work with
+ /// \param port the port to listen on
+ /// \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)
+ DNSService(IOService& io_service, const char& port,
+ const bool use_ipv4, const bool use_ipv6,
+ SimpleCallback* checkin, DNSLookup* lookup,
+ DNSAnswer* answer);
+ /// \brief The constructor without any servers.
+ ///
+ /// Use addServer() to add some servers.
+ DNSService(IOService& io_service, SimpleCallback* checkin,
+ DNSLookup* lookup, DNSAnswer* answer);
+ /// \brief The destructor.
+ ~DNSService();
+ //@}
+
+ /// \brief Add another server to the service
+ void addServer(uint16_t port, const std::string &address);
+ void addServer(const char &port, const std::string &address);
+ /// \brief Remove all servers from the service
+ void clearServers();
+
+ /// \brief Return the native \c io_service object used in this wrapper.
+ ///
+ /// This is a short term work around to support other BIND 10 modules
+ /// that share the same \c io_service with the authoritative server.
+ /// It will eventually be removed once the wrapper interface is
+ /// generalized.
+ asio::io_service& get_io_service() { return io_service_.get_io_service(); }
+
+ /// \brief Return the IO Service Object
+ ///
+ /// \return IOService object for this DNS service.
+ asiolink::IOService& getIOService() { return (io_service_);}
+
+private:
+ DNSServiceImpl* impl_;
+ IOService& io_service_;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_DNS_SERVICE_H
diff --git a/src/lib/asiolink/doc/auth_process.jpg b/src/lib/asiolink/doc/auth_process.jpg
new file mode 100644
index 0000000..a02111f
Binary files /dev/null and b/src/lib/asiolink/doc/auth_process.jpg differ
diff --git a/src/lib/asiolink/doc/recursive_process.jpg b/src/lib/asiolink/doc/recursive_process.jpg
new file mode 100644
index 0000000..fe2b137
Binary files /dev/null and b/src/lib/asiolink/doc/recursive_process.jpg differ
diff --git a/src/lib/asiolink/dummy_io_cb.h b/src/lib/asiolink/dummy_io_cb.h
new file mode 100644
index 0000000..0006b95
--- /dev/null
+++ b/src/lib/asiolink/dummy_io_cb.h
@@ -0,0 +1,59 @@
+// 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 __DUMMY_IO_CB_H
+#define __DUMMY_IO_CB_H
+
+#include <iostream>
+
+#include <asio/error.hpp>
+#include <asio/error_code.hpp>
+
+namespace asiolink {
+
+/// \brief Asynchronous I/O Completion Callback
+///
+/// The two socket classes (UDPSocket and TCPSocket) require that the I/O
+/// completion callback function have an operator() method with the appropriate
+/// signature. The classes are templates, any class with that method and
+/// signature can be passed as the callback object - there is no need for a
+/// base class defining the interface. However, some users of the socket
+/// classes do not use the asynchronous I/O operations, yet have to supply a
+/// template parameter. This is the reason for this class - it is the dummy
+/// template parameter.
+
+class DummyIOCallback {
+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)
+ {
+ // TODO: log an error if this method ever gets called.
+ }
+};
+
+} // namespace asiolink
+
+#endif // __DUMMY_IO_CB_H
diff --git a/src/lib/asiolink/interval_timer.cc b/src/lib/asiolink/interval_timer.cc
new file mode 100644
index 0000000..8efb102
--- /dev/null
+++ b/src/lib/asiolink/interval_timer.cc
@@ -0,0 +1,136 @@
+// 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 <unistd.h> // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <boost/bind.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <asio.hpp>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+
+namespace asiolink {
+
+class IntervalTimerImpl {
+private:
+ // prohibit copy
+ IntervalTimerImpl(const IntervalTimerImpl& source);
+ IntervalTimerImpl& operator=(const IntervalTimerImpl& source);
+public:
+ IntervalTimerImpl(IOService& io_service);
+ ~IntervalTimerImpl();
+ void setup(const IntervalTimer::Callback& cbfunc, const long interval);
+ void callback(const asio::error_code& error);
+ void cancel() {
+ timer_.cancel();
+ interval_ = 0;
+ }
+ long getInterval() const { return (interval_); }
+private:
+ // a function to update timer_ when it expires
+ void update();
+ // a function to call back when timer_ expires
+ IntervalTimer::Callback cbfunc_;
+ // interval in milliseconds
+ long interval_;
+ // asio timer
+ asio::deadline_timer timer_;
+};
+
+IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
+ interval_(0), timer_(io_service.get_io_service())
+{}
+
+IntervalTimerImpl::~IntervalTimerImpl()
+{}
+
+void
+IntervalTimerImpl::setup(const IntervalTimer::Callback& cbfunc,
+ const long interval)
+{
+ // Interval should not be less than or equal to 0.
+ if (interval <= 0) {
+ isc_throw(isc::BadValue, "Interval should not be less than or "
+ "equal to 0");
+ }
+ // Call back function should not be empty.
+ if (cbfunc.empty()) {
+ isc_throw(isc::InvalidParameter, "Callback function is empty");
+ }
+ cbfunc_ = cbfunc;
+ interval_ = interval;
+ // Set initial expire time.
+ // At this point the timer is not running yet and will not expire.
+ // After calling IOService::run(), the timer will expire.
+ update();
+ return;
+}
+
+void
+IntervalTimerImpl::update() {
+ if (interval_ == 0) {
+ // timer has been canceled. Do nothing.
+ return;
+ }
+ try {
+ // Update expire time to (current time + interval_).
+ timer_.expires_from_now(boost::posix_time::millisec(interval_));
+ } catch (const asio::system_error& e) {
+ isc_throw(isc::Unexpected, "Failed to update timer");
+ }
+ // Reset timer.
+ timer_.async_wait(boost::bind(&IntervalTimerImpl::callback, this, _1));
+}
+
+void
+IntervalTimerImpl::callback(const asio::error_code& cancelled) {
+ // Do not call cbfunc_ in case the timer was cancelled.
+ // The timer will be canelled in the destructor of asio::deadline_timer.
+ if (!cancelled) {
+ cbfunc_();
+ // Set next expire time.
+ update();
+ }
+}
+
+IntervalTimer::IntervalTimer(IOService& io_service) {
+ impl_ = new IntervalTimerImpl(io_service);
+}
+
+IntervalTimer::~IntervalTimer() {
+ delete impl_;
+}
+
+void
+IntervalTimer::setup(const Callback& cbfunc, const long interval) {
+ return (impl_->setup(cbfunc, interval));
+}
+
+void
+IntervalTimer::cancel() {
+ impl_->cancel();
+}
+
+long
+IntervalTimer::getInterval() const {
+ return (impl_->getInterval());
+}
+
+}
diff --git a/src/lib/asiolink/interval_timer.h b/src/lib/asiolink/interval_timer.h
new file mode 100644
index 0000000..6c43327
--- /dev/null
+++ b/src/lib/asiolink/interval_timer.h
@@ -0,0 +1,133 @@
+// 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_INTERVAL_TIMER_H
+#define __ASIOLINK_INTERVAL_TIMER_H 1
+
+#include <boost/function.hpp>
+
+#include <asiolink/io_service.h>
+
+namespace asiolink {
+
+struct IntervalTimerImpl;
+
+/// \brief The \c IntervalTimer class is a wrapper for the ASIO
+/// \c asio::deadline_timer class.
+///
+/// This class is implemented to use \c asio::deadline_timer as interval
+/// timer.
+///
+/// \c setup() sets a timer to expire on (now + interval) and a call back
+/// function.
+///
+/// \c IntervalTimerImpl::callback() is called by the timer when it expires.
+///
+/// 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.
+///
+/// Note: Destruction of an instance of this class while call back is pending
+/// causes throwing an exception from \c IOService.
+///
+/// Sample code:
+/// \code
+/// void function_to_call_back() {
+/// // this function will be called periodically
+/// }
+/// int interval_in_milliseconds = 1000;
+/// IOService io_service;
+///
+/// IntervalTimer intervalTimer(io_service);
+/// intervalTimer.setup(function_to_call_back, interval_in_milliseconds);
+/// io_service.run();
+/// \endcode
+class IntervalTimer {
+public:
+ /// \name The type of timer callback function
+ typedef boost::function<void()> Callback;
+
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IntervalTimer(const IntervalTimer& source);
+ IntervalTimer& operator=(const IntervalTimer& source);
+public:
+ /// \brief The constructor with \c IOService.
+ ///
+ /// This constructor may throw a standard exception if
+ /// memory allocation fails inside the method.
+ /// This constructor may also throw \c asio::system_error.
+ ///
+ /// \param io_service A reference to an instance of IOService
+ IntervalTimer(IOService& io_service);
+
+ /// \brief The destructor.
+ ///
+ /// This destructor never throws an exception.
+ ///
+ /// On the destruction of this class the timer will be canceled
+ /// inside \c asio::deadline_timer.
+ ~IntervalTimer();
+ //@}
+
+ /// \brief Register timer callback function and interval.
+ ///
+ /// This function sets callback function and interval in milliseconds.
+ /// Timer will actually start after calling \c IOService::run().
+ ///
+ /// \param cbfunc A reference to a function \c void(void) to call back
+ /// when the timer is expired (should not be an empty functor)
+ /// \param interval Interval in milliseconds (greater than 0)
+ ///
+ /// Note: IntervalTimer will not pass \c asio::error_code to
+ /// call back function. In case the timer is cancelled, the function
+ /// will not be called.
+ ///
+ /// \throw isc::InvalidParameter cbfunc is empty
+ /// \throw isc::BadValue interval is less than or equal to 0
+ /// \throw isc::Unexpected ASIO library error
+ void setup(const Callback& cbfunc, const long interval);
+
+ /// Cancel the timer.
+ ///
+ /// If the timer has been set up, this method cancels any asynchronous
+ /// events waiting on the timer and stops the timer itself.
+ /// If the timer has already been canceled, this method effectively does
+ /// nothing.
+ ///
+ /// This method never throws an exception.
+ void cancel();
+
+ /// Return the timer interval.
+ ///
+ /// This method returns the timer interval in milliseconds if it's running;
+ /// if the timer has been canceled it returns 0.
+ ///
+ /// This method never throws an exception.
+ long getInterval() const;
+
+private:
+ IntervalTimerImpl* impl_;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_INTERVAL_TIMER_H
diff --git a/src/lib/asiolink/io_address.cc b/src/lib/asiolink/io_address.cc
new file mode 100644
index 0000000..70e8374
--- /dev/null
+++ b/src/lib/asiolink/io_address.cc
@@ -0,0 +1,65 @@
+// 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 <config.h>
+
+#include <unistd.h> // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <asio.hpp>
+
+#include <exceptions/exceptions.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+
+
+using namespace asio;
+using asio::ip::udp;
+using asio::ip::tcp;
+
+using namespace std;
+
+namespace asiolink {
+
+// XXX: we cannot simply construct the address in the initialization list,
+// because we'd like to throw our own exception on failure.
+IOAddress::IOAddress(const string& address_str) {
+ error_code err;
+ asio_address_ = ip::address::from_string(address_str, err);
+ if (err) {
+ isc_throw(IOError, "Failed to convert string to address '"
+ << address_str << "': " << err.message());
+ }
+}
+
+IOAddress::IOAddress(const ip::address& asio_address) :
+ asio_address_(asio_address)
+{}
+
+string
+IOAddress::toText() const {
+ return (asio_address_.to_string());
+}
+
+short
+IOAddress::getFamily() const {
+ if (asio_address_.is_v4()) {
+ return (AF_INET);
+ } else {
+ return (AF_INET6);
+ }
+}
+
+}
diff --git a/src/lib/asiolink/io_address.h b/src/lib/asiolink/io_address.h
new file mode 100644
index 0000000..53c1a7a
--- /dev/null
+++ b/src/lib/asiolink/io_address.h
@@ -0,0 +1,123 @@
+// 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 __IO_ADDRESS_H
+#define __IO_ADDRESS_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h> // for some network system calls
+#include <asio/ip/address.hpp>
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+namespace asiolink {
+
+/// \brief The \c IOAddress class represents an IP addresses (version
+/// agnostic)
+///
+/// This class is a wrapper for the ASIO \c ip::address class.
+class IOAddress {
+public:
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// This class is copyable. We use default versions of copy constructor
+ /// and the assignment operator.
+ /// We use the default destructor.
+ //@{
+ /// \brief Constructor from string.
+ ///
+ /// This constructor converts a textual representation of IPv4 and IPv6
+ /// addresses into an IOAddress object.
+ /// If \c address_str is not a valid representation of any type of
+ /// address, an exception of class \c IOError will be thrown.
+ /// This constructor allocates memory for the object, and if that fails
+ /// a corresponding standard exception will be thrown.
+ ///
+ /// \param address_str Textual representation of address.
+ IOAddress(const std::string& address_str);
+
+ /// \brief Constructor from an ASIO \c ip::address object.
+ ///
+ /// This constructor is intended to be used within the wrapper
+ /// implementation; user applications of the wrapper API won't use it.
+ ///
+ /// This constructor never throws an exception.
+ ///
+ /// \param asio_address The ASIO \c ip::address to be converted.
+ IOAddress(const asio::ip::address& asio_address);
+ //@}
+
+ /// \brief Convert the address to a string.
+ ///
+ /// This method is basically expected to be exception free, but
+ /// generating the string will involve resource allocation,
+ /// and if it fails the corresponding standard exception will be thrown.
+ ///
+ /// \return A string representation of the address.
+ std::string toText() const;
+
+ /// \brief Returns the address family
+ ///
+ /// \return AF_INET for IPv4 or AF_INET6 for IPv6.
+ short getFamily() const;
+
+ /// \brief Compare addresses for equality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return true if addresses are equal, false if not.
+ bool equals(const IOAddress& other) const {
+ return (asio_address_ == other.asio_address_);
+ }
+
+ /// \brief Compare addresses for equality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return true if addresses are equal, false if not.
+ bool operator==(const IOAddress& other) const {
+ return equals(other);
+ }
+
+ // \brief Compare addresses for inequality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return false if addresses are equal, true if not.
+ bool nequals(const IOAddress& other) const {
+ return (!equals(other));
+ }
+
+ // \brief Compare addresses for inequality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return false if addresses are equal, true if not.
+ bool operator!=(const IOAddress& other) const {
+ return (nequals(other));
+ }
+
+
+private:
+ asio::ip::address asio_address_;
+};
+
+} // asiolink
+#endif // __IO_ADDRESS_H
diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h
new file mode 100644
index 0000000..ac793a6
--- /dev/null
+++ b/src/lib/asiolink/io_asio_socket.h
@@ -0,0 +1,399 @@
+// 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 __IO_ASIO_SOCKET_H
+#define __IO_ASIO_SOCKET_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h> // for some network system calls
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+#include <coroutine.h>
+
+#include <dns/buffer.h>
+
+#include <asiolink/io_error.h>
+#include <asiolink/io_socket.h>
+
+
+namespace asiolink {
+
+/// \brief Socket not open
+///
+/// Thrown on an attempt to do read/write to a socket that is not open.
+class SocketNotOpen : public IOError {
+public:
+ SocketNotOpen(const char* file, size_t line, const char* what) :
+ 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;
+
+
+/// \brief I/O Socket with asynchronous operations
+///
+/// This class is a wrapper for the ASIO socket classes such as
+/// \c ip::tcp::socket and \c ip::udp::socket.
+///
+/// This is the basic IOSocket with additional operations - open, send, receive
+/// and close. Depending on how the asiolink code develops, it may be a
+/// temporary class: its main use is to add the template parameter needed for
+/// the derived classes UDPSocket and TCPSocket but without changing the
+/// signature of the more basic IOSocket class.
+///
+/// We may revisit this decision when we generalize the wrapper and more
+/// modules use it. Also, at that point we may define a separate (visible)
+/// derived class for testing purposes rather than providing factory methods
+/// (i.e., getDummy variants below).
+///
+/// TODO: Check if IOAsioSocket class is still needed
+///
+/// \param C Template parameter identifying type of the callback object.
+
+template <typename C>
+class IOAsioSocket : public IOSocket {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IOAsioSocket(const IOAsioSocket<C>& source);
+ IOAsioSocket& operator=(const IOAsioSocket<C>& 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).
+ IOAsioSocket() {}
+public:
+ /// The destructor.
+ virtual ~IOAsioSocket() {}
+ //@}
+
+ /// \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 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.
+ virtual int getNative() const = 0;
+
+ /// \brief Return the transport protocol of the socket.
+ ///
+ /// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
+ /// \c IPPROTO_TCP for TCP sockets.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return \c IPPROTO_UDP for UDP sockets, \c IPPROTO_TCP for TCP sockets
+ virtual int getProtocol() const = 0;
+
+ /// \brief Is Open() synchronous?
+ ///
+ /// 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. 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.
+ /// \param callback I/O Completion callback, called when the operation has
+ /// 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
+ ///
+ /// 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.
+ ///
+ /// \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) = 0;
+
+ /// \brief Receive Asynchronously
+ ///
+ /// 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 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 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. 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;
+
+ /// \brief Close socket
+ virtual void close() = 0;
+};
+
+
+#include "io_socket.h"
+
+/// \brief The \c DummyAsioSocket class is a concrete derived class of
+/// \c IOAsioSocket that is not associated with any real socket.
+///
+/// This main purpose of this class is tests, where it may be desirable to
+/// instantiate an \c IOAsioSocket object without involving system resource
+/// allocation such as real network sockets.
+///
+/// \param C Template parameter identifying type of the callback object.
+
+template <typename C>
+class DummyAsioSocket : public IOAsioSocket<C> {
+private:
+ DummyAsioSocket(const DummyAsioSocket<C>& source);
+ DummyAsioSocket& operator=(const DummyAsioSocket<C>& source);
+public:
+ /// \brief Constructor from the protocol number.
+ ///
+ /// The protocol must validly identify a standard network protocol.
+ /// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
+ ///
+ /// \param protocol The network protocol number for the socket.
+ DummyAsioSocket(const int protocol) : protocol_(protocol) {}
+
+ /// \brief A dummy derived method of \c IOAsioSocket::getNative().
+ ///
+ /// \return Always returns -1 as the object is not associated with a real
+ /// (native) socket.
+ virtual int getNative() const { return (-1); }
+
+ /// \brief A dummy derived method of \c IOAsioSocket::getProtocol().
+ ///
+ /// \return Protocol socket was created with
+ 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
+ /// system identified by the given endpoint.
+ ///
+ /// \param endpoint Unused
+ /// \param callback Unused.
+ ///false indicating that the operation completed synchronously.
+ virtual bool open(const IOEndpoint*, C&) {
+ return (false);
+ }
+
+ /// \brief Send Asynchronously
+ ///
+ /// Must be supplied as it is abstract in the base class.
+ ///
+ /// \param data Unused
+ /// \param length Unused
+ /// \param endpoint Unused
+ /// \param callback Unused
+ virtual void asyncSend(const void*, size_t, const IOEndpoint*, C&) {
+ }
+
+ /// \brief Receive Asynchronously
+ ///
+ /// Must be supplied as it is abstract in the base class.
+ ///
+ /// \param data Unused
+ /// \param length Unused
+ /// \param offset Unused
+ /// \param endpoint Unused
+ /// \param callback Unused
+ virtual void asyncReceive(void* data, size_t, size_t, IOEndpoint*, C&) {
+ }
+
+ /// \brief Checks if the data received is complete.
+ ///
+ /// \param staging Unused
+ /// \param length Unused
+ /// \param cumulative Unused
+ /// \param offset Unused.
+ /// \param expected Unused.
+ /// \param outbuff Unused.
+ ///
+ /// \return Always true
+ 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.
+ virtual void cancel() {
+ }
+
+ /// \brief Close socket
+ ///
+ /// Must be supplied as it is abstract in the base class.
+ virtual void close() {
+ }
+
+private:
+ const int protocol_;
+};
+
+} // namespace asiolink
+
+#endif // __IO_ASIO_SOCKET_H
diff --git a/src/lib/asiolink/io_endpoint.cc b/src/lib/asiolink/io_endpoint.cc
new file mode 100644
index 0000000..e0b1a9e
--- /dev/null
+++ b/src/lib/asiolink/io_endpoint.cc
@@ -0,0 +1,60 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <unistd.h> // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <asio.hpp>
+
+#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>
+
+using namespace std;
+
+namespace asiolink {
+
+const IOEndpoint*
+IOEndpoint::create(const int protocol, const IOAddress& address,
+ const unsigned short port)
+{
+ if (protocol == IPPROTO_UDP) {
+ return (new UDPEndpoint(address, port));
+ } else if (protocol == IPPROTO_TCP) {
+ return (new TCPEndpoint(address, port));
+ }
+ isc_throw(IOError,
+ "IOEndpoint creation attempt for unsupported protocol: " <<
+ 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
new file mode 100644
index 0000000..d21da96
--- /dev/null
+++ b/src/lib/asiolink/io_endpoint.h
@@ -0,0 +1,121 @@
+// 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 __IO_ENDPOINT_H
+#define __IO_ENDPOINT_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h> // for some network system calls
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+#include <asiolink/io_address.h>
+
+namespace asiolink {
+
+/// \brief The \c IOEndpoint class is an abstract base class to represent
+/// a communication endpoint.
+///
+/// This class is a wrapper for the ASIO endpoint classes such as
+/// \c ip::tcp::endpoint and \c ip::udp::endpoint.
+///
+/// Derived class implementations are completely hidden within the
+/// implementation. User applications only get access to concrete
+/// \c IOEndpoint objects via the abstract interfaces.
+class IOEndpoint {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IOEndpoint(const IOEndpoint& source);
+ IOEndpoint& operator=(const IOEndpoint& 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).
+ IOEndpoint() {}
+public:
+ /// The destructor.
+ virtual ~IOEndpoint() {}
+ //@}
+
+ /// \brief Returns the address of the endpoint.
+ ///
+ /// This method returns an IOAddress object corresponding to \c this
+ /// endpoint.
+ ///
+ /// Note that the return value is a real object, not a reference or
+ /// a pointer.
+ ///
+ /// This is aligned with the interface of the ASIO counterpart:
+ /// the \c address() method of \c ip::xxx::endpoint classes returns
+ /// an \c ip::address object.
+ ///
+ /// This also means handling the address of an endpoint using this method
+ /// can be expensive. If the address information is necessary in a
+ /// performance sensitive context and there's a more efficient interface
+ /// for that purpose, it's probably better to avoid using this method.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return A copy of \c IOAddress object corresponding to the endpoint.
+ virtual IOAddress getAddress() const = 0;
+
+ /// \brief Returns the port of the endpoint.
+ virtual uint16_t getPort() const = 0;
+
+ /// \brief Returns the protocol number of the endpoint (TCP, UDP...)
+ virtual short getProtocol() const = 0;
+
+ /// \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)
+ /// \c IOEndpoint object that identifies the pair of given address
+ /// and port.
+ /// The appropriate derived class is chosen based on the specified
+ /// transport protocol. If the \c protocol doesn't specify a protocol
+ /// supported in this implementation, an exception of class \c IOError
+ /// will be thrown.
+ ///
+ /// Memory for the created object will be dynamically allocated. It's
+ /// the caller's responsibility to \c delete it later.
+ /// If resource allocation for the new object fails, a corresponding
+ /// standard exception will be thrown.
+ ///
+ /// \param protocol The transport protocol used for the endpoint.
+ /// Currently, only \c IPPROTO_UDP and \c IPPROTO_TCP can be specified.
+ /// \param address The (IP) address of the endpoint.
+ /// \param port The transport port number of the endpoint
+ /// \return A pointer to a newly created \c IOEndpoint object.
+ static const IOEndpoint* create(const int protocol,
+ const IOAddress& address,
+ const unsigned short port);
+};
+
+} // asiolink
+#endif // __IO_ENDPOINT_H
diff --git a/src/lib/asiolink/io_error.h b/src/lib/asiolink/io_error.h
new file mode 100644
index 0000000..2869e0b
--- /dev/null
+++ b/src/lib/asiolink/io_error.h
@@ -0,0 +1,35 @@
+// 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 __IO_ERROR_H
+#define __IO_ERROR_H
+
+#include <exceptions/exceptions.h>
+
+namespace asiolink {
+
+/// \brief An exception that is thrown if an error occurs within the IO
+/// module. This is mainly intended to be a wrapper exception class for
+/// ASIO specific exceptions.
+class IOError : public isc::Exception {
+public:
+ IOError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+} // asiolink
+
+#endif // __IO_ERROR_H
diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc
new file mode 100644
index 0000000..fdc1f2e
--- /dev/null
+++ b/src/lib/asiolink/io_fetch.cc
@@ -0,0 +1,380 @@
+// 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 <unistd.h> // for some IPC/network system calls
+#include <sys/socket.h>
+#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/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <log/logger.h>
+
+#include <asiolink/qid_gen.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 asiolink {
+
+/// 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::Question question; ///< Question to be asked
+ 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,
+ const isc::dns::Question& query, 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))
+ ),
+ question(query),
+ 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);
+ }
+};
+
+/// IOFetch Constructor - just initialize the private data
+
+IOFetch::IOFetch(Protocol protocol, IOService& service,
+ const isc::dns::Question& question, const IOAddress& address, uint16_t port,
+ OutputBufferPtr& buff, Callback* cb, int wait)
+ :
+ data_(new IOFetchData(protocol, service, question, address,
+ port, buff, cb, wait))
+{
+}
+
+// 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()(asio::error_code ec, size_t length) {
+
+ if (data_->stopped) {
+ return;
+ } else if (ec) {
+ logIOFailure(ec);
+ return;
+ }
+
+ CORO_REENTER (this) {
+
+ /// Generate the upstream query and render it to wire format
+ /// This is done in a different scope to allow inline variable
+ /// declarations.
+ {
+ Message msg(Message::RENDER);
+ msg.setQid(data_->qid);
+ msg.setOpcode(Opcode::QUERY());
+ msg.setRcode(Rcode::NOERROR());
+ msg.setHeaderFlag(Message::HEADERFLAG_RD);
+ msg.addQuestion(data_->question);
+ MessageRenderer renderer(*data_->msgbuf);
+ msg.toWire(renderer);
+ }
+
+ // 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));
+ data_->timer.async_wait(boost::bind(&IOFetch::stop, *this,
+ TIME_OUT));
+ }
+
+ // Open a connection to the target system. For speed, if the operation
+ // 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);
+ }
+
+ 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_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
+ // each I/O operation will be done asynchronously and between each one
+ // 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_->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
+ stop(SUCCESS);
+ }
+}
+
+// Function that stops the coroutine sequence. It is called either when the
+// 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, it checks
+// if the stopped_ flag is already set. If it is, the call is a no-op.
+
+void
+IOFetch::stop(Result result) {
+
+ if (!data_->stopped) {
+
+ // Mark the fetch as stopped to prevent other completion callbacks
+ // (invoked because of the calls to cancel()) from executing the
+ // cancel calls again.
+ //
+ // In a single threaded environment, the callbacks won't be invoked
+ // until this one completes. In a multi-threaded environment, they may
+ // well be, in which case the testing (and setting) of the stopped_
+ // 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:
+ 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:
+ // 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,
+ // and cancel the timer.
+ data_->socket->cancel();
+ data_->socket->close();
+
+ data_->timer.cancel();
+
+ // Execute the I/O completion callback (if present).
+ if (data_->callback) {
+ (*(data_->callback))(result);
+ }
+ }
+}
+
+// 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
new file mode 100644
index 0000000..0723777
--- /dev/null
+++ b/src/lib/asiolink/io_fetch.h
@@ -0,0 +1,179 @@
+// 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 __IO_FETCH_H
+#define __IO_FETCH_H 1
+
+#include <config.h>
+
+#include <boost/shared_array.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+#include <coroutine.h>
+
+#include <asio/error_code.hpp>
+
+#include <dns/buffer.h>
+#include <dns/question.h>
+
+namespace asiolink {
+
+// Forward declarations
+class IOAddress;
+class IOFetchData;
+class IOService;
+
+/// \brief Upstream Fetch Processing
+///
+/// IOFetch is the class used to send upstream fetches and to handle responses.
+///
+/// \param E Endpoint type to use.
+
+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
+ ///
+ /// Note that this applies to the status of I/Os in the fetch - a fetch
+ /// that resulted in a packet being received from the server is a SUCCESS,
+ /// even if the contents of the packet indicate that some error occurred.
+ enum Result {
+ SUCCESS = 0, ///< Success, fetch completed
+ 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
+ // declaration.
+
+ /// \brief Integer Constants
+ enum {
+ STAGING_LENGTH = 8192 ///< Size of staging buffer
+ };
+
+ /// \brief I/O Fetch Callback
+ ///
+ /// Class of callback object for when the fetch itself has completed - an
+ /// object of this class is passed to the IOFetch constructor and its
+ /// operator() method called when the fetch completes.
+ ///
+ /// Note the difference between the two operator() methods:
+ /// - IOFetch::operator() callback is called when an asynchronous I/O has
+ /// completed.
+ /// - IOFetch::Callback::operator() is called when an upstream fetch - which
+ /// may have involved several asynchronous I/O operations - has completed.
+ ///
+ /// This is an abstract class.
+ class Callback {
+ public:
+ /// \brief Default Constructor
+ Callback()
+ {}
+
+ /// \brief Virtual Destructor
+ virtual ~Callback()
+ {}
+
+ /// \brief Callback method
+ ///
+ /// This is the method called when the fetch completes.
+ ///
+ /// \param result Result of the fetch
+ virtual void operator()(Result result) = 0;
+ };
+
+ /// \brief Constructor.
+ ///
+ /// Creates the object that will handle the upstream fetch.
+ ///
+ /// TODO: Need to randomise the source port
+ ///
+ /// \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.
+ /// \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 address IP address of upstream server
+ /// \param port Port to which to connect on the upstream server
+ /// (default = 53)
+ /// \param wait Timeout for the fetch (in ms). The default value of
+ /// -1 indicates no timeout.
+ 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);
+
+ /// \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
+ /// this object when an operation has been completed.
+ ///
+ /// \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);
+
+ /// \brief Terminate query
+ ///
+ /// This method can be called at any point. It terminates the current
+ /// query with the specified reason.
+ ///
+ /// \param reason Reason for terminating the query
+ void stop(Result reason = STOPPED);
+
+private:
+ /// \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
+
+};
+
+} // namespace asiolink
+
+#endif // __IO_FETCH_H
diff --git a/src/lib/asiolink/io_message.h b/src/lib/asiolink/io_message.h
new file mode 100644
index 0000000..e857bd9
--- /dev/null
+++ b/src/lib/asiolink/io_message.h
@@ -0,0 +1,100 @@
+// 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 __IO_MESSAGE_H
+#define __IO_MESSAGE_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h> // for some network system calls
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_socket.h>
+
+namespace asiolink {
+
+/// \brief The \c IOMessage class encapsulates an incoming message received
+/// on a socket.
+///
+/// An \c IOMessage object represents a tuple of a chunk of data
+/// (a UDP packet or some segment of TCP stream), the socket over which the
+/// data is passed, the information about the other end point of the
+/// communication, and perhaps more.
+///
+/// The current design and interfaces of this class is tentative.
+/// It only provides a minimal level of support that is necessary for
+/// the current implementation of the authoritative server.
+/// A future version of this class will definitely support more.
+class IOMessage {
+ ///
+ /// \name Constructors and Destructor
+ ///
+
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IOMessage(const IOMessage& source);
+ IOMessage& operator=(const IOMessage& source);
+public:
+ /// \brief Constructor from message data
+ ///
+ /// This constructor needs to handle the ASIO \c ip::address class,
+ /// and is intended to be used within this wrapper implementation.
+ /// Once the \c IOMessage object is created, the application can
+ /// get access to the information via the wrapper interface such as
+ /// \c getRemoteAddress().
+ ///
+ /// This constructor never throws an exception.
+ ///
+ /// \param data A pointer to the message data.
+ /// \param data_size The size of the message data in bytes.
+ /// \param io_socket The socket over which the data is given.
+ /// \param remote_endpoint The other endpoint of the socket, that is,
+ /// the sender of the message.
+ IOMessage(const void* data, const size_t data_size,
+ const IOSocket& io_socket, const IOEndpoint& remote_endpoint) :
+ data_(data), data_size_(data_size), io_socket_(io_socket),
+ remote_endpoint_(remote_endpoint)
+ {}
+ //@}
+
+ /// \brief Returns a pointer to the received data.
+ const void* getData() const { return (data_); }
+
+ /// \brief Returns the size of the received data in bytes.
+ size_t getDataSize() const { return (data_size_); }
+
+ /// \brief Returns the socket on which the message arrives.
+ const IOSocket& getSocket() const { return (io_socket_); }
+
+ /// \brief Returns the endpoint that sends the message.
+ const IOEndpoint& getRemoteEndpoint() const { return (remote_endpoint_); }
+
+private:
+ const void* data_;
+ const size_t data_size_;
+ const IOSocket& io_socket_;
+ const IOEndpoint& remote_endpoint_;
+};
+
+
+} // asiolink
+#endif // __IO_MESSAGE_H
diff --git a/src/lib/asiolink/io_service.cc b/src/lib/asiolink/io_service.cc
new file mode 100644
index 0000000..55fc4b3
--- /dev/null
+++ b/src/lib/asiolink/io_service.cc
@@ -0,0 +1,98 @@
+// 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 <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+
+#include <config.h>
+
+#include <asio.hpp>
+#include <asiolink/io_service.h>
+
+namespace asiolink {
+
+class IOServiceImpl {
+private:
+ IOServiceImpl(const IOService& source);
+ IOServiceImpl& operator=(const IOService& source);
+public:
+ /// \brief The constructor
+ IOServiceImpl() :
+ io_service_(),
+ work_(io_service_)
+ {};
+ /// \brief The destructor.
+ ~IOServiceImpl() {};
+ //@}
+
+ /// \brief Start the underlying event loop.
+ ///
+ /// This method does not return control to the caller until
+ /// the \c stop() method is called via some handler.
+ void run() { io_service_.run(); };
+
+ /// \brief Run the underlying event loop for a single event.
+ ///
+ /// This method return control to the caller as soon as the
+ /// first handler has completed. (If no handlers are ready when
+ /// it is run, it will block until one is.)
+ void run_one() { io_service_.run_one();} ;
+
+ /// \brief Stop the underlying event loop.
+ ///
+ /// This will return the control to the caller of the \c run() method.
+ void stop() { io_service_.stop();} ;
+
+ /// \brief Return the native \c io_service object used in this wrapper.
+ ///
+ /// This is a short term work around to support other BIND 10 modules
+ /// that share the same \c io_service with the authoritative server.
+ /// It will eventually be removed once the wrapper interface is
+ /// generalized.
+ asio::io_service& get_io_service() { return io_service_; };
+private:
+ asio::io_service io_service_;
+ asio::io_service::work work_;
+};
+
+IOService::IOService() {
+ io_impl_ = new IOServiceImpl();
+}
+
+IOService::~IOService() {
+ delete io_impl_;
+}
+
+void
+IOService::run() {
+ io_impl_->run();
+}
+
+void
+IOService::run_one() {
+ io_impl_->run_one();
+}
+
+void
+IOService::stop() {
+ io_impl_->stop();
+}
+
+asio::io_service&
+IOService::get_io_service() {
+ return (io_impl_->get_io_service());
+}
+
+} // namepsace asiolink
diff --git a/src/lib/asiolink/io_service.h b/src/lib/asiolink/io_service.h
new file mode 100644
index 0000000..66558b7
--- /dev/null
+++ b/src/lib/asiolink/io_service.h
@@ -0,0 +1,77 @@
+// 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_IO_SERVICE_H
+#define __ASIOLINK_IO_SERVICE_H 1
+
+namespace asio {
+ class io_service;
+}
+
+namespace asiolink {
+
+struct IOServiceImpl;
+
+/// \brief The \c IOService class is a wrapper for the ASIO \c io_service
+/// class.
+///
+class IOService {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IOService(const IOService& source);
+ IOService& operator=(const IOService& source);
+public:
+ /// \brief The constructor
+ IOService();
+ /// \brief The destructor.
+ ~IOService();
+ //@}
+
+ /// \brief Start the underlying event loop.
+ ///
+ /// This method does not return control to the caller until
+ /// the \c stop() method is called via some handler.
+ void run();
+
+ /// \brief Run the underlying event loop for a single event.
+ ///
+ /// This method return control to the caller as soon as the
+ /// first handler has completed. (If no handlers are ready when
+ /// it is run, it will block until one is.)
+ void run_one();
+
+ /// \brief Stop the underlying event loop.
+ ///
+ /// This will return the control to the caller of the \c run() method.
+ void stop();
+
+ /// \brief Return the native \c io_service object used in this wrapper.
+ ///
+ /// This is a short term work around to support other BIND 10 modules
+ /// that share the same \c io_service with the authoritative server.
+ /// It will eventually be removed once the wrapper interface is
+ /// generalized.
+ asio::io_service& get_io_service();
+
+private:
+ IOServiceImpl* io_impl_;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_IO_SERVICE_H
diff --git a/src/lib/asiolink/io_socket.cc b/src/lib/asiolink/io_socket.cc
new file mode 100644
index 0000000..c386ca1
--- /dev/null
+++ b/src/lib/asiolink/io_socket.cc
@@ -0,0 +1,65 @@
+// 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 "io_socket.h"
+
+#include <asio.hpp>
+
+using namespace asio;
+
+namespace asiolink {
+
+/// \brief The \c DummySocket class is a concrete derived class of
+/// \c IOSocket that is not associated with any real socket.
+///
+/// This main purpose of this class is tests, where it may be desirable to
+/// instantiate an \c IOSocket object without involving system resource
+/// allocation such as real network sockets.
+class DummySocket : public IOSocket {
+private:
+ DummySocket(const DummySocket& source);
+ DummySocket& operator=(const DummySocket& source);
+public:
+ /// \brief Constructor from the protocol number.
+ ///
+ /// The protocol must validly identify a standard network protocol.
+ /// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
+ ///
+ /// \param protocol The network protocol number for the socket.
+ DummySocket(const int protocol) : protocol_(protocol) {}
+
+ /// \brief A dummy derived method of \c IOSocket::getNative().
+ ///
+ /// This version of method always returns -1 as the object is not
+ /// associated with a real (native) socket.
+ virtual int getNative() const { return (-1); }
+
+ virtual int getProtocol() const { return (protocol_); }
+private:
+ const int protocol_;
+};
+
+IOSocket&
+IOSocket::getDummyUDPSocket() {
+ static DummySocket socket(IPPROTO_UDP);
+ return (socket);
+}
+
+IOSocket&
+IOSocket::getDummyTCPSocket() {
+ static DummySocket socket(IPPROTO_TCP);
+ return (socket);
+}
+
+}
diff --git a/src/lib/asiolink/io_socket.h b/src/lib/asiolink/io_socket.h
new file mode 100644
index 0000000..bebc8b6
--- /dev/null
+++ b/src/lib/asiolink/io_socket.h
@@ -0,0 +1,124 @@
+// 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 __IO_SOCKET_H
+#define __IO_SOCKET_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h> // for some network system calls
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+namespace asiolink {
+
+/// \brief The \c IOSocket class is an abstract base class to represent
+/// various types of network sockets.
+///
+/// This class is a wrapper for the ASIO socket classes such as
+/// \c ip::tcp::socket and \c ip::udp::socket.
+///
+/// Derived class implementations are completely hidden within the
+/// implementation. User applications only get access to concrete
+/// \c IOSocket objects via the abstract interfaces.
+///
+/// We may revisit this decision when we generalize the wrapper and more
+/// modules use it. Also, at that point we may define a separate (visible)
+/// derived class for testing purposes rather than providing factory methods
+/// (i.e., getDummy variants below).
+class IOSocket {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IOSocket(const IOSocket& source);
+ IOSocket& operator=(const IOSocket& 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).
+ IOSocket() {}
+public:
+ /// The destructor.
+ virtual ~IOSocket() {}
+ //@}
+
+ /// \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 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.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return The native representation of the socket. This is the socket
+ /// file descriptor for UNIX-like systems.
+ virtual int getNative() const = 0;
+
+ /// \brief Return the transport protocol of the socket.
+ ///
+ /// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
+ /// \c IPPROTO_TCP for TCP sockets.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return IPPROTO_UDP for UDP sockets
+ /// \return IPPROTO_TCP for TCP sockets
+ virtual int getProtocol() const = 0;
+
+ /// \brief Return a non-usable "dummy" UDP socket for testing.
+ ///
+ /// This is a class method that returns a "mock" of UDP socket.
+ /// This is not associated with any actual socket, and its only
+ /// responsibility is to return \c IPPROTO_UDP from \c getProtocol().
+ /// The only feasible usage of this socket is for testing so that
+ /// the test code can prepare some "UDP data" even without opening any
+ /// actual socket.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return A reference to an \c IOSocket object whose \c getProtocol()
+ /// returns \c IPPROTO_UDP.
+ static IOSocket& getDummyUDPSocket();
+
+ /// \brief Return a non-usable "dummy" TCP socket for testing.
+ ///
+ /// See \c getDummyUDPSocket(). This method is its TCP version.
+ ///
+ /// \return A reference to an \c IOSocket object whose \c getProtocol()
+ /// returns \c IPPROTO_TCP.
+ static IOSocket& getDummyTCPSocket();
+};
+
+} // namespace asiolink
+
+#endif // __IO_SOCKET_H
diff --git a/src/lib/asiolink/qid_gen.cc b/src/lib/asiolink/qid_gen.cc
new file mode 100644
index 0000000..4063b39
--- /dev/null
+++ b/src/lib/asiolink/qid_gen.cc
@@ -0,0 +1,54 @@
+// 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.
+
+// qid_gen defines a generator for query id's
+//
+// We probably want to merge this with the weighted random in the nsas
+// (and other parts where we need randomness, perhaps another thing
+// for a general libutil?)
+
+#include <asiolink/qid_gen.h>
+
+#include <sys/time.h>
+
+namespace {
+ asiolink::QidGenerator qid_generator_instance;
+}
+
+namespace asiolink {
+
+QidGenerator&
+QidGenerator::getInstance() {
+ return (qid_generator_instance);
+}
+
+QidGenerator::QidGenerator() : dist_(0, 65535),
+ vgen_(generator_, dist_)
+{
+ seed();
+}
+
+void
+QidGenerator::seed() {
+ struct timeval tv;
+ gettimeofday(&tv, 0);
+ generator_.seed((tv.tv_sec * 1000000) + tv.tv_usec);
+}
+
+isc::dns::qid_t
+QidGenerator::generateQid() {
+ return (vgen_());
+}
+
+} // namespace asiolink
diff --git a/src/lib/asiolink/qid_gen.h b/src/lib/asiolink/qid_gen.h
new file mode 100644
index 0000000..a5caa17
--- /dev/null
+++ b/src/lib/asiolink/qid_gen.h
@@ -0,0 +1,85 @@
+// 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.
+
+// qid_gen defines a generator for query id's
+//
+// We probably want to merge this with the weighted random in the nsas
+// (and other parts where we need randomness, perhaps another thing
+// for a general libutil?)
+
+#ifndef __QID_GEN_H
+#define __QID_GEN_H
+
+#include <dns/message.h>
+#include <boost/random/mersenne_twister.hpp>
+#include <boost/random/uniform_int.hpp>
+#include <boost/random/variate_generator.hpp>
+
+
+namespace asiolink {
+
+/// This class generates Qids for outgoing queries
+///
+/// It is implemented as a singleton; the public way to access it
+/// is to call getInstance()->generateQid().
+///
+/// It automatically seeds it with the current time when it is first
+/// used.
+class QidGenerator {
+public:
+ /// \brief Returns the singleton instance of the QidGenerator
+ ///
+ /// Returns a reference to the singleton instance of the generator
+ static QidGenerator& getInstance();
+
+ /// \brief Default constructor
+ ///
+ /// It is recommended that getInstance is used rather than creating
+ /// separate instances of this class.
+ ///
+ /// The constructor automatically seeds the generator with the
+ /// current time.
+ QidGenerator();
+
+ /// Generate a Qid
+ ///
+ /// \return A random Qid
+ isc::dns::qid_t generateQid();
+
+ /// \brief Seeds the QidGenerator (based on the current time)
+ ///
+ /// This is automatically called by the constructor
+ void seed();
+
+private:
+ // "Mersenne Twister: A 623-dimensionally equidistributed
+ // uniform pseudo-random number generator", Makoto Matsumoto and
+ // Takuji Nishimura, ACM Transactions on Modeling and Computer
+ // Simulation: Special Issue on Uniform Random Number Generation,
+ // Vol. 8, No. 1, January 1998, pp. 3-30.
+ //
+ // mt19937 is an implementation of one of the pseudo random
+ // generators described in this paper.
+ boost::mt19937 generator_;
+
+ // For qid's we want a uniform distribution
+ boost::uniform_int<> dist_;
+
+ boost::variate_generator<boost::mt19937&, boost::uniform_int<> > vgen_;
+};
+
+
+} // namespace asiolink
+
+#endif // __QID_GEN_H
diff --git a/src/lib/asiolink/simple_callback.h b/src/lib/asiolink/simple_callback.h
new file mode 100644
index 0000000..ab5deaf
--- /dev/null
+++ b/src/lib/asiolink/simple_callback.h
@@ -0,0 +1,71 @@
+// 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_SIMPLE_CALLBACK_H
+#define __ASIOLINK_SIMPLE_CALLBACK_H 1
+
+#include <asiolink/io_message.h>
+
+namespace asiolink {
+
+/// \brief The \c SimpleCallback class is an abstract base class for a
+/// simple callback function with the signature:
+///
+/// void simpleCallback(const IOMessage& io_message) const;
+///
+/// Specific derived class implementations are hidden within the
+/// implementation. Instances of the derived classes can be called
+/// as functions via the operator() interface. Pointers to these
+/// instances can then be provided to the \c IOService class
+/// via its constructor.
+///
+/// The \c SimpleCallback is expected to be used for basic, generic
+/// tasks such as checking for configuration changes. It may also be
+/// used for testing purposes.
+class SimpleCallback {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ SimpleCallback(const SimpleCallback& source);
+ SimpleCallback& operator=(const SimpleCallback& 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).
+ SimpleCallback() : self_(this) {}
+public:
+ /// \brief The destructor
+ virtual ~SimpleCallback() {}
+ /// \brief The function operator
+ //@}
+ ///
+ /// This makes its call indirectly via the "self" pointer, ensuring
+ /// that the function ultimately invoked will be the one in the derived
+ /// class.
+ ///
+ /// \param io_message The event message to handle
+ virtual void operator()(const IOMessage& io_message) const {
+ (*self_)(io_message);
+ }
+private:
+ SimpleCallback* self_;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_SIMPLE_CALLBACK_H
diff --git a/src/lib/asiolink/tcp_endpoint.h b/src/lib/asiolink/tcp_endpoint.h
new file mode 100644
index 0000000..158ca4a
--- /dev/null
+++ b/src/lib/asiolink/tcp_endpoint.h
@@ -0,0 +1,113 @@
+// 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 __TCP_ENDPOINT_H
+#define __TCP_ENDPOINT_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_endpoint.h>
+
+namespace asiolink {
+
+/// \brief The \c TCPEndpoint class is a concrete derived class of
+/// \c IOEndpoint that represents an endpoint of a TCP packet.
+///
+/// Other notes about \c TCPEndpoint applies to this class, too.
+class TCPEndpoint : public IOEndpoint {
+public:
+ ///
+ /// \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)),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
+ /// \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(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.
+ virtual ~TCPEndpoint() { delete asio_endpoint_placeholder_; }
+ //@}
+
+ virtual IOAddress getAddress() const {
+ return (asio_endpoint_.address());
+ }
+
+ virtual uint16_t getPort() const {
+ return (asio_endpoint_.port());
+ }
+
+ virtual short getProtocol() const {
+ return (asio_endpoint_.protocol().protocol());
+ }
+
+ 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
+ inline const asio::ip::tcp::endpoint& getASIOEndpoint() const {
+ return (asio_endpoint_);
+ }
+ inline asio::ip::tcp::endpoint& getASIOEndpoint() {
+ return (asio_endpoint_);
+ }
+
+private:
+ asio::ip::tcp::endpoint* asio_endpoint_placeholder_;
+ asio::ip::tcp::endpoint& asio_endpoint_;
+};
+
+} // namespace asiolink
+#endif // __TCP_ENDPOINT_H
diff --git a/src/lib/asiolink/tcp_server.cc b/src/lib/asiolink/tcp_server.cc
new file mode 100644
index 0000000..db59551
--- /dev/null
+++ b/src/lib/asiolink/tcp_server.cc
@@ -0,0 +1,239 @@
+// 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 <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+#include <errno.h>
+
+#include <boost/shared_array.hpp>
+
+#include <log/dummylog.h>
+
+#include <asio.hpp>
+#include <asiolink/dummy_io_cb.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+#include <asiolink/tcp_server.h>
+
+
+using namespace asio;
+using asio::ip::udp;
+using asio::ip::tcp;
+
+using namespace std;
+using namespace isc::dns;
+
+namespace asiolink {
+
+/// The following functions implement the \c TCPServer class.
+///
+/// The constructor
+TCPServer::TCPServer(io_service& io_service,
+ const ip::address& addr, const uint16_t port,
+ const SimpleCallback* checkin,
+ const DNSLookup* lookup,
+ const DNSAnswer* answer) :
+ io_(io_service), done_(false),
+ checkin_callback_(checkin), lookup_callback_(lookup),
+ answer_callback_(answer)
+{
+ tcp::endpoint endpoint(addr, port);
+ acceptor_.reset(new tcp::acceptor(io_service));
+ acceptor_->open(endpoint.protocol());
+ // Set v6-only (we use a separate instantiation for v4,
+ // otherwise asio will bind to both v4 and v6
+ if (addr.is_v6()) {
+ acceptor_->set_option(ip::v6_only(true));
+ }
+ acceptor_->set_option(tcp::acceptor::reuse_address(true));
+ acceptor_->bind(endpoint);
+ acceptor_->listen();
+}
+
+void
+TCPServer::operator()(error_code ec, size_t length) {
+ /// Because the coroutine reentry block is implemented as
+ /// a switch statement, inline variable declarations are not
+ /// permitted. Certain variables used below can be declared here.
+
+ boost::array<const_buffer,2> bufs;
+ OutputBuffer lenbuf(TCP_MESSAGE_LENGTHSIZE);
+
+ CORO_REENTER (this) {
+ do {
+ /// Create a socket to listen for connections
+ socket_.reset(new tcp::socket(acceptor_->get_io_service()));
+
+ /// Wait for new connections. In the event of non-fatal error,
+ /// try again
+ do {
+ CORO_YIELD acceptor_->async_accept(*socket_, *this);
+
+ // Abort on fatal errors
+ // TODO: Log error?
+ if (ec) {
+ using namespace asio::error;
+ if (ec.value() != would_block && ec.value() != try_again &&
+ ec.value() != connection_aborted &&
+ ec.value() != interrupted) {
+ return;
+ }
+ }
+ } while (ec);
+
+ /// Fork the coroutine by creating a copy of this one and
+ /// scheduling it on the ASIO service queue. The parent
+ /// will continue listening for DNS connections while the
+ /// handles the one that has just arrived.
+ CORO_FORK io_.post(TCPServer(*this));
+ } while (is_parent());
+
+ /// Instantiate the data buffer that will be used by the
+ /// asynchronous read call.
+ data_.reset(new char[MAX_LENGTH]);
+
+ /// Read the message, in two parts. First, the message length:
+ CORO_YIELD async_read(*socket_, asio::buffer(data_.get(),
+ TCP_MESSAGE_LENGTHSIZE), *this);
+ if (ec) {
+ socket_->close();
+ CORO_YIELD return;
+ }
+
+ /// Now read the message itself. (This is done in a different scope
+ /// to allow inline variable declarations.)
+ CORO_YIELD {
+ InputBuffer dnsbuffer(data_.get(), length);
+ uint16_t msglen = dnsbuffer.readUint16();
+ async_read(*socket_, asio::buffer(data_.get(), msglen), *this);
+ }
+
+ 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
+ // that would quickly generate an IOMessage object without
+ // all these calls to "new".)
+ peer_.reset(new TCPEndpoint(socket_->remote_endpoint()));
+
+ // The TCP socket class has been extended with asynchronous functions
+ // and takes as a template parameter a completion callback class. As
+ // TCPServer does not use these extended functions (only those defined
+ // in the IOSocket base class) - but needs a TCPSocket to get hold of
+ // the underlying Boost TCP socket - DummyIOCallback is used. This
+ // provides the appropriate operator() but is otherwise functionless.
+ iosock_.reset(new TCPSocket<DummyIOCallback>(*socket_));
+ io_message_.reset(new IOMessage(data_.get(), length, *iosock_, *peer_));
+ bytes_ = length;
+
+ // Perform any necessary operations prior to processing the incoming
+ // packet (e.g., checking for queued configuration messages).
+ //
+ // (XXX: it may be a performance issue to have this called for
+ // every single incoming packet; we may wish to throttle it somehow
+ // in the future.)
+ if (checkin_callback_ != NULL) {
+ (*checkin_callback_)(*io_message_);
+ }
+
+ // 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;
+ }
+
+ // Reset or instantiate objects that will be needed by the
+ // DNS lookup and the write call.
+ respbuf_.reset(new OutputBuffer(0));
+ query_message_.reset(new Message(Message::PARSE));
+ answer_message_.reset(new Message(Message::RENDER));
+
+ // Schedule a DNS lookup, and yield. When the lookup is
+ // finished, the coroutine will resume immediately after
+ // this point.
+ CORO_YIELD io_.post(AsyncLookup<TCPServer>(*this));
+
+ // 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_,
+ answer_message_, respbuf_);
+
+ // Set up the response, beginning with two length bytes.
+ lenbuf.writeUint16(respbuf_->getLength());
+ bufs[0] = buffer(lenbuf.getData(), lenbuf.getLength());
+ bufs[1] = buffer(respbuf_->getData(), respbuf_->getLength());
+
+ // Begin an asynchronous send, and then yield. When the
+ // send completes, we will resume immediately after this point
+ // (though we have nothing further to do, so the coroutine
+ // will simply exit at that time).
+ 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();
+ }
+}
+
+/// Call the DNS lookup provider. (Expected to be called by the
+/// AsyncLookup<TCPServer> handler.)
+void
+TCPServer::asyncLookup() {
+ (*lookup_callback_)(*io_message_, query_message_,
+ answer_message_, respbuf_, this);
+}
+
+void TCPServer::stop() {
+ /// we use close instead of cancel, with the same reason
+ /// with udp server stop, refer to the udp server code
+
+ acceptor_->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
+/// whether there is an answer to return to the client.
+void
+TCPServer::resume(const bool done) {
+ done_ = done;
+ io_.post(*this);
+}
+
+} // namespace asiolink
+
diff --git a/src/lib/asiolink/tcp_server.h b/src/lib/asiolink/tcp_server.h
new file mode 100644
index 0000000..2fe0d37
--- /dev/null
+++ b/src/lib/asiolink/tcp_server.h
@@ -0,0 +1,120 @@
+// 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 __TCP_SERVER_H
+#define __TCP_SERVER_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <boost/shared_array.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <asiolink/asiolink.h>
+#include <coroutine.h>
+
+
+namespace asiolink {
+
+/// \brief A TCP-specific \c DNSServer object.
+///
+/// This class inherits from both \c DNSServer and from \c coroutine,
+/// defined in coroutine.h.
+class TCPServer : public virtual DNSServer, public virtual coroutine {
+public:
+ explicit TCPServer(asio::io_service& io_service,
+ const asio::ip::address& addr, const uint16_t port,
+ const SimpleCallback* checkin = NULL,
+ const DNSLookup* lookup = NULL,
+ const DNSAnswer* answer = NULL);
+
+ void operator()(asio::error_code ec = asio::error_code(),
+ size_t length = 0);
+ void asyncLookup();
+ void stop();
+ void resume(const bool done);
+ bool hasAnswer() { return (done_); }
+ int value() { return (get_value()); }
+
+ DNSServer* clone() {
+ TCPServer* s = new TCPServer(*this);
+ return (s);
+ }
+
+private:
+ enum { MAX_LENGTH = 65535 };
+ static const size_t TCP_MESSAGE_LENGTHSIZE = 2;
+
+ // The ASIO service object
+ asio::io_service& io_;
+
+ // Class member variables which are dynamic, and changes to which
+ // need to accessible from both sides of a coroutine fork or from
+ // outside of the coroutine (i.e., from an asynchronous I/O call),
+ // should be declared here as pointers and allocated in the
+ // constructor or in the coroutine. This allows state information
+ // to persist when an individual copy of the coroutine falls out
+ // scope while waiting for an event, *so long as* there is another
+ // object that is referencing the same data. As a side-benefit, using
+ // pointers also reduces copy overhead for coroutine objects.
+ //
+ // Note: Currently these objects are allocated by "new" in the
+ // constructor, or in the function operator while processing a query.
+ // Repeated allocations from the heap for every incoming query is
+ // clearly a performance issue; this must be optimized in the future.
+ // The plan is to have a structure pre-allocate several "server state"
+ // objects which can be pulled off a free list and placed on an in-use
+ // list whenever a query comes in. This will serve the dual purpose
+ // of improving performance and guaranteeing that state information
+ // will *not* be destroyed when any one instance of the coroutine
+ // falls out of scope while waiting for an event.
+ //
+ // An ASIO acceptor object to handle new connections. Created in
+ // the constructor.
+ boost::shared_ptr<asio::ip::tcp::acceptor> acceptor_;
+
+ // Socket used to for listen for queries. Created in the
+ // constructor and stored in a shared_ptr because socket objects
+ // are not copyable.
+ boost::shared_ptr<asio::ip::tcp::socket> socket_;
+
+ // The buffer into which the response is written
+ boost::shared_ptr<isc::dns::OutputBuffer> respbuf_;
+
+ // \c IOMessage and \c Message objects to be passed to the
+ // DNS lookup and answer providers
+ boost::shared_ptr<asiolink::IOMessage> io_message_;
+ isc::dns::MessagePtr query_message_;
+ isc::dns::MessagePtr answer_message_;
+
+ // The buffer into which the query packet is written
+ boost::shared_array<char>data_;
+
+ // State information that is entirely internal to a given instance
+ // of the coroutine can be declared here.
+ size_t bytes_;
+ bool done_;
+
+ // Callback functions provided by the caller
+ const SimpleCallback* checkin_callback_;
+ const DNSLookup* lookup_callback_;
+ const DNSAnswer* answer_callback_;
+
+ boost::shared_ptr<IOEndpoint> peer_;
+ boost::shared_ptr<IOSocket> iosock_;
+};
+
+} // namespace asiolink
+#endif // __TCP_SERVER_H
diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h
new file mode 100644
index 0000000..e6e0863
--- /dev/null
+++ b/src/lib/asiolink/tcp_socket.h
@@ -0,0 +1,416 @@
+// 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 __TCP_SOCKET_H
+#define __TCP_SOCKET_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <log/dummylog.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+
+#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>
+#include <asiolink/tcp_endpoint.h>
+
+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.
+///
+/// \param C Callback type
+template <typename C>
+class TCPSocket : public IOAsioSocket<C> {
+private:
+ /// \brief Class is non-copyable
+ TCPSocket(const TCPSocket&);
+ 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.
+ TCPSocket(asio::ip::tcp::socket& socket);
+
+ /// \brief Constructor
+ ///
+ /// Used when the TCPSocket is being asked to manage its own internal
+ /// 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);
+
+ /// \brief Destructor
+ virtual ~TCPSocket();
+
+ /// \brief Return file descriptor of underlying socket
+ virtual int getNative() const {
+ return (socket_.native());
+ }
+
+ /// \brief Return protocol of socket
+ virtual int getProtocol() const {
+ return (IPPROTO_TCP);
+ }
+
+ /// \brief Is "open()" synchronous?
+ ///
+ /// Indicates that the opening of a TCP socket is asynchronous.
+ virtual bool isOpenSynchronous() const {
+ return (false);
+ }
+
+ /// \brief Open Socket
+ ///
+ /// Opens the TCP socket. This is an asynchronous operation, completion of
+ /// which will be signalled via a call to the callback function.
+ ///
+ /// \param endpoint Endpoint to which the socket will connect.
+ /// \param callback Callback object.
+ virtual void open(const IOEndpoint* endpoint, C& callback);
+
+ /// \brief Send Asynchronously
+ ///
+ /// 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. (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);
+
+ /// \brief Receive Asynchronously
+ ///
+ /// 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 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 offset,
+ IOEndpoint* endpoint, C& callback);
+
+ /// \brief Process received data packet
+ ///
+ /// See the description of IOAsioSocket::receiveComplete for a complete
+ /// description of this method.
+ ///
+ /// \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 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();
+
+ /// \brief Close socket
+ virtual void close();
+
+
+private:
+ // Two variables to hold the socket - a socket and a pointer to it. This
+ // handles the case where a socket is passed to the TCPSocket on
+ // construction, or where it is asked to manage its own socket.
+ 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), send_buffer_()
+{
+}
+
+// Constructor - create socket on the fly
+
+template <typename C>
+TCPSocket<C>::TCPSocket(IOService& service) :
+ socket_ptr_(new asio::ip::tcp::socket(service.get_io_service())),
+ socket_(*socket_ptr_), isopen_(false)
+{
+}
+
+// Destructor. Only delete the socket if we are managing it.
+
+template <typename C>
+TCPSocket<C>::~TCPSocket()
+{
+ delete socket_ptr_;
+}
+
+// Open the socket.
+
+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());
+ }
+ else {
+ socket_.open(asio::ip::tcp::v6());
+ }
+ isopen_ = true;
+
+ // 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));
+ }
+
+ // 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
+// an exception if this is the case.
+
+template <typename C> void
+TCPSocket<C>::asyncSend(const void* data, size_t length,
+ const IOEndpoint*, C& callback)
+{
+ if (isopen_) {
+
+ // 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,
+ "attempt to send on a TCP socket that is not 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 offset,
+ IOEndpoint* endpoint, C& callback)
+{
+ if (isopen_) {
+ // 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);
+ 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);
+
+ // ... and kick off the read.
+ socket_.async_receive(asio::buffer(buffer_start, length - offset), callback);
+
+ } else {
+ isc_throw(SocketNotOpen,
+ "attempt to receive from a TCP socket that is not open");
+ }
+}
+
+// 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_) {
+ socket_.cancel();
+ }
+}
+
+// Close the socket down. Can only do this if the socket is open and we are
+// managing it ourself.
+
+template <typename C> void
+TCPSocket<C>::close() {
+ if (isopen_ && socket_ptr_) {
+ socket_.close();
+ isopen_ = false;
+ }
+}
+
+} // namespace asiolink
+
+#endif // __TCP_SOCKET_H
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
new file mode 100644
index 0000000..f67e547
--- /dev/null
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -0,0 +1,58 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+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 += 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/asiolink/libasiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# B10_CXXFLAGS)
+run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_GXX
+run_unittests_CXXFLAGS += -Wno-unused-parameter
+endif
+if USE_CLANGPP
+# Same for clang++, but we need to turn off -Werror completely.
+run_unittests_CXXFLAGS += -Wno-error
+endif
+endif
+
+noinst_PROGRAMS = $(TESTS)
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/interval_timer_unittest.cc b/src/lib/asiolink/tests/interval_timer_unittest.cc
new file mode 100644
index 0000000..7e0e7bc
--- /dev/null
+++ b/src/lib/asiolink/tests/interval_timer_unittest.cc
@@ -0,0 +1,296 @@
+// 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/asiolink.h>
+
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+namespace {
+// TODO: Consider this margin
+const boost::posix_time::time_duration TIMER_MARGIN_MSEC =
+ boost::posix_time::milliseconds(50);
+}
+
+using namespace asiolink;
+
+// This fixture is for testing IntervalTimer. Some callback functors are
+// registered as callback function of the timer to test if they are called
+// or not.
+class IntervalTimerTest : public ::testing::Test {
+protected:
+ IntervalTimerTest() :
+ io_service_(), timer_called_(false), timer_cancel_success_(false)
+ {}
+ ~IntervalTimerTest() {}
+ class TimerCallBack : public std::unary_function<void, void> {
+ public:
+ TimerCallBack(IntervalTimerTest* test_obj) : test_obj_(test_obj) {}
+ void operator()() const {
+ test_obj_->timer_called_ = true;
+ test_obj_->io_service_.stop();
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ };
+ class TimerCallBackCounter : public std::unary_function<void, void> {
+ public:
+ TimerCallBackCounter(IntervalTimerTest* test_obj) : test_obj_(test_obj) {
+ counter_ = 0;
+ }
+ void operator()() {
+ ++counter_;
+ return;
+ }
+ int counter_;
+ private:
+ IntervalTimerTest* test_obj_;
+ };
+ class TimerCallBackCancelDeleter : public std::unary_function<void, void> {
+ public:
+ TimerCallBackCancelDeleter(IntervalTimerTest* test_obj,
+ IntervalTimer* timer,
+ TimerCallBackCounter& counter)
+ : test_obj_(test_obj), timer_(timer), counter_(counter), count_(0),
+ prev_counter_(-1)
+ {}
+ void operator()() {
+ ++count_;
+ if (count_ == 1) {
+ // First time of call back.
+ // Store the value of counter_.counter_.
+ prev_counter_ = counter_.counter_;
+ delete timer_;
+ } else if (count_ == 2) {
+ // Second time of call back.
+ // Stop io_service to stop all timers.
+ test_obj_->io_service_.stop();
+ // Compare the value of counter_.counter_ with stored one.
+ // If TimerCallBackCounter was not called (expected behavior),
+ // they are same.
+ if (counter_.counter_ == prev_counter_) {
+ test_obj_->timer_cancel_success_ = true;
+ }
+ }
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ IntervalTimer* timer_;
+ TimerCallBackCounter& counter_;
+ int count_;
+ int prev_counter_;
+ };
+ class TimerCallBackCanceller {
+ public:
+ TimerCallBackCanceller(unsigned int& counter, IntervalTimer& itimer) :
+ counter_(counter), itimer_(itimer)
+ {}
+ void operator()() {
+ ++counter_;
+ itimer_.cancel();
+ }
+ private:
+ unsigned int& counter_;
+ IntervalTimer& itimer_;
+ };
+ class TimerCallBackOverwriter : public std::unary_function<void, void> {
+ public:
+ TimerCallBackOverwriter(IntervalTimerTest* test_obj,
+ IntervalTimer& timer)
+ : test_obj_(test_obj), timer_(timer), count_(0)
+ {}
+ void operator()() {
+ ++count_;
+ if (count_ == 1) {
+ // First time of call back.
+ // Call setup() to update callback function to TimerCallBack.
+ test_obj_->timer_called_ = false;
+ timer_.setup(TimerCallBack(test_obj_), 100);
+ } else if (count_ == 2) {
+ // Second time of call back.
+ // If it reaches here, re-setup() is failed (unexpected).
+ // We should stop here.
+ test_obj_->io_service_.stop();
+ }
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ IntervalTimer& timer_;
+ int count_;
+ };
+protected:
+ IOService io_service_;
+ bool timer_called_;
+ bool timer_cancel_success_;
+};
+
+TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) {
+ // Create asio_link::IntervalTimer and setup.
+ IntervalTimer itimer(io_service_);
+ // expect throw if call back function is empty
+ EXPECT_THROW(itimer.setup(IntervalTimer::Callback(), 1),
+ isc::InvalidParameter);
+ // expect throw if interval is not greater than 0
+ EXPECT_THROW(itimer.setup(TimerCallBack(this), 0), isc::BadValue);
+ EXPECT_THROW(itimer.setup(TimerCallBack(this), -1), isc::BadValue);
+}
+
+TEST_F(IntervalTimerTest, startIntervalTimer) {
+ // Create asio_link::IntervalTimer and setup.
+ // Then run IOService and test if the callback function is called.
+ IntervalTimer itimer(io_service_);
+ timer_called_ = false;
+ // store start time
+ boost::posix_time::ptime start;
+ start = boost::posix_time::microsec_clock::universal_time();
+ // setup timer
+ itimer.setup(TimerCallBack(this), 100);
+ EXPECT_EQ(100, itimer.getInterval());
+ io_service_.run();
+ // reaches here after timer expired
+ // delta: difference between elapsed time and 100 milliseconds.
+ boost::posix_time::time_duration delta =
+ (boost::posix_time::microsec_clock::universal_time() - start)
+ - boost::posix_time::millisec(100);
+ if (delta.is_negative()) {
+ delta.invert_sign();
+ }
+ // expect TimerCallBack is called; timer_called_ is true
+ EXPECT_TRUE(timer_called_);
+ // expect interval is 100 milliseconds +/- TIMER_MARGIN_MSEC.
+ EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+}
+
+TEST_F(IntervalTimerTest, destructIntervalTimer) {
+ // This code isn't exception safe, but we'd rather keep the code
+ // simpler and more readable as this is only for tests and if it throws
+ // the program would immediately terminate anyway.
+
+ // The call back function will not be called after the timer is
+ // destroyed.
+ //
+ // There are two timers:
+ // itimer_counter (A)
+ // (Calls TimerCallBackCounter)
+ // - increments internal counter in callback function
+ // itimer_canceller (B)
+ // (Calls TimerCallBackCancelDeleter)
+ // - first time of callback, it stores the counter value of
+ // callback_canceller and destroys itimer_counter
+ // - second time of callback, it compares the counter value of
+ // callback_canceller with stored value
+ // if they are same the timer was not called; expected result
+ // if they are different the timer was called after destroyed
+ //
+ // 0 100 200 300 400 500 600 (ms)
+ // (A) i--------+----x
+ // ^
+ // |destroy itimer_counter
+ // (B) i-------------+--------------s
+ // ^stop io_service
+ // and check if itimer_counter have been
+ // stopped
+
+ // itimer_counter will be deleted in TimerCallBackCancelDeleter
+ IntervalTimer* itimer_counter = new IntervalTimer(io_service_);
+ IntervalTimer itimer_canceller(io_service_);
+ timer_cancel_success_ = false;
+ TimerCallBackCounter callback_canceller(this);
+ itimer_counter->setup(callback_canceller, 200);
+ itimer_canceller.setup(
+ TimerCallBackCancelDeleter(this, itimer_counter, callback_canceller),
+ 300);
+ io_service_.run();
+ EXPECT_TRUE(timer_cancel_success_);
+}
+
+TEST_F(IntervalTimerTest, cancel) {
+ // Similar to destructIntervalTimer test, but the first timer explicitly
+ // cancels itself on first callback.
+ IntervalTimer itimer_counter(io_service_);
+ IntervalTimer itimer_watcher(io_service_);
+ unsigned int counter = 0;
+ itimer_counter.setup(TimerCallBackCanceller(counter, itimer_counter), 100);
+ itimer_watcher.setup(TimerCallBack(this), 200);
+ io_service_.run();
+ EXPECT_EQ(1, counter);
+ EXPECT_EQ(0, itimer_counter.getInterval());
+
+ // canceling an already canceled timer shouldn't cause any surprise.
+ EXPECT_NO_THROW(itimer_counter.cancel());
+}
+
+TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
+ // Calling setup() multiple times updates call back function and interval.
+ //
+ // There are two timers:
+ // itimer (A)
+ // (Calls TimerCallBackCounter / TimerCallBack)
+ // - increments internal counter in callback function
+ // (TimerCallBackCounter)
+ // interval: 300 milliseconds
+ // - io_service_.stop() (TimerCallBack)
+ // interval: 100 milliseconds
+ // itimer_overwriter (B)
+ // (Calls TimerCallBackOverwriter)
+ // - first time of callback, it calls setup() to change call back
+ // function to TimerCallBack and interval of itimer to 100
+ // milliseconds
+ // after 300 + 100 milliseconds from the beginning of this test,
+ // TimerCallBack() will be called and io_service_ stops.
+ // - second time of callback, it means the test fails.
+ //
+ // 0 100 200 300 400 500 600 700 800 (ms)
+ // (A) i-------------+----C----s
+ // ^ ^stop io_service
+ // |change call back function
+ // (B) i------------------+-------------------S
+ // ^(stop io_service on fail)
+ //
+
+ IntervalTimer itimer(io_service_);
+ IntervalTimer itimer_overwriter(io_service_);
+ // store start time
+ boost::posix_time::ptime start;
+ start = boost::posix_time::microsec_clock::universal_time();
+ itimer.setup(TimerCallBackCounter(this), 300);
+ itimer_overwriter.setup(TimerCallBackOverwriter(this, itimer), 400);
+ io_service_.run();
+ // reaches here after timer expired
+ // if interval is updated, it takes
+ // 400 milliseconds for TimerCallBackOverwriter
+ // + 100 milliseconds for TimerCallBack (stop)
+ // = 500 milliseconds.
+ // otherwise (test fails), it takes
+ // 400 milliseconds for TimerCallBackOverwriter
+ // + 400 milliseconds for TimerCallBackOverwriter (stop)
+ // = 800 milliseconds.
+ // delta: difference between elapsed time and 400 + 100 milliseconds
+ boost::posix_time::time_duration delta =
+ (boost::posix_time::microsec_clock::universal_time() - start)
+ - boost::posix_time::millisec(400 + 100);
+ if (delta.is_negative()) {
+ delta.invert_sign();
+ }
+ // expect callback function is updated: TimerCallBack is called
+ EXPECT_TRUE(timer_called_);
+ // expect interval is updated
+ EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+}
diff --git a/src/lib/asiolink/tests/io_address_unittest.cc b/src/lib/asiolink/tests/io_address_unittest.cc
new file mode 100644
index 0000000..894f143
--- /dev/null
+++ b/src/lib/asiolink/tests/io_address_unittest.cc
@@ -0,0 +1,63 @@
+// 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 <asiolink/io_error.h>
+#include <asiolink/io_address.h>
+
+using namespace asiolink;
+
+TEST(IOAddressTest, fromText) {
+ IOAddress io_address_v4("192.0.2.1");
+ EXPECT_EQ("192.0.2.1", io_address_v4.toText());
+
+ IOAddress io_address_v6("2001:db8::1234");
+ EXPECT_EQ("2001:db8::1234", io_address_v6.toText());
+
+ // bogus IPv4 address-like input
+ EXPECT_THROW(IOAddress("192.0.2.2.1"), IOError);
+
+ // bogus IPv4 address-like input: out-of-range octet
+ EXPECT_THROW(IOAddress("192.0.2.300"), IOError);
+
+ // bogus IPv6 address-like input
+ EXPECT_THROW(IOAddress("2001:db8:::1234"), IOError);
+
+ // bogus IPv6 address-like input
+ EXPECT_THROW(IOAddress("2001:db8::efgh"), IOError);
+}
+
+TEST(IOAddressTest, Equality) {
+ EXPECT_TRUE(IOAddress("192.0.2.1") == IOAddress("192.0.2.1"));
+ EXPECT_FALSE(IOAddress("192.0.2.1") != IOAddress("192.0.2.1"));
+
+ EXPECT_TRUE(IOAddress("192.0.2.1") != IOAddress("192.0.2.2"));
+ EXPECT_FALSE(IOAddress("192.0.2.1") == IOAddress("192.0.2.2"));
+
+ EXPECT_TRUE(IOAddress("2001:db8::12") == IOAddress("2001:0DB8:0:0::0012"));
+ EXPECT_FALSE(IOAddress("2001:db8::12") != IOAddress("2001:0DB8:0:0::0012"));
+
+ EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("2001:db8::1235"));
+ EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("2001:db8::1235"));
+
+ EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("192.0.2.3"));
+ EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("192.0.2.3"));
+}
+
+TEST(IOAddressTest, Family) {
+ EXPECT_EQ(AF_INET, IOAddress("192.0.2.1").getFamily());
+ EXPECT_EQ(AF_INET6, IOAddress("2001:0DB8:0:0::0012").getFamily());
+}
diff --git a/src/lib/asiolink/tests/io_endpoint_unittest.cc b/src/lib/asiolink/tests/io_endpoint_unittest.cc
new file mode 100644
index 0000000..5170f7d
--- /dev/null
+++ b/src/lib/asiolink/tests/io_endpoint_unittest.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 <config.h>
+#include <gtest/gtest.h>
+
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_error.h>
+
+using namespace asiolink;
+
+TEST(IOEndpointTest, createUDPv4) {
+ const IOEndpoint* ep;
+ ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 53210);
+ EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
+ EXPECT_EQ(53210, ep->getPort());
+ EXPECT_EQ(AF_INET, ep->getFamily());
+ EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
+ EXPECT_EQ(IPPROTO_UDP, ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createTCPv4) {
+ const IOEndpoint* ep;
+ ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5301);
+ EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
+ EXPECT_EQ(5301, ep->getPort());
+ EXPECT_EQ(AF_INET, ep->getFamily());
+ EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
+ EXPECT_EQ(IPPROTO_TCP, ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createUDPv6) {
+ const IOEndpoint* ep;
+ ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5302);
+ EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
+ EXPECT_EQ(5302, ep->getPort());
+ EXPECT_EQ(AF_INET6, ep->getFamily());
+ EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
+ EXPECT_EQ(IPPROTO_UDP, ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createTCPv6) {
+ const IOEndpoint* ep;
+ ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5303);
+ EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
+ EXPECT_EQ(5303, ep->getPort());
+ EXPECT_EQ(AF_INET6, ep->getFamily());
+ EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
+ 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(),
+ IOError);
+}
+
diff --git a/src/lib/asiolink/tests/io_fetch_unittest.cc b/src/lib/asiolink/tests/io_fetch_unittest.cc
new file mode 100644
index 0000000..2b258b8
--- /dev/null
+++ b/src/lib/asiolink/tests/io_fetch_unittest.cc
@@ -0,0 +1,723 @@
+// 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 <string>
+#include <iostream>
+#include <iomanip>
+#include <iterator>
+#include <vector>
+
+#include <gtest/gtest.h>
+#include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.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 <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 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);
+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
+{
+public:
+ IOService service_; ///< Service to run the query
+ IOFetch::Result expected_; ///< Expected result of the callback
+ bool run_; ///< Did the callback run already?
+ Question question_; ///< What to ask
+ 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
+ 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() :
+ service_(),
+ expected_(IOFetch::NOTSET),
+ run_(false),
+ question_(Name("example.net"), RRClass::IN(), RRType::A()),
+ 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. 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
+
+ // 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 Check for stop()
+ ///
+ /// 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.)
+ ///
+ /// \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_);
+ }
+
+ /// \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_);
+ }
+
+ /// \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_ = IOFetch::SUCCESS;
+ }
+
+ // 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());
+}
+
+// UDP Stop test - see IOFetchTest::stopTest() header.
+TEST_F(IOFetchTest, UdpStop) {
+ stopTest(IOFetch::UDP, udp_fetch_);
+}
+
+// UDP premature stop test - see IOFetchTest::prematureStopTest() header.
+TEST_F(IOFetchTest, UdpPrematureStop) {
+ prematureStopTest(IOFetch::UDP, udp_fetch_);
+}
+
+// UDP premature stop test - see IOFetchTest::timeoutTest() header.
+TEST_F(IOFetchTest, UdpTimeout) {
+ timeoutTest(IOFetch::UDP, udp_fetch_);
+}
+
+// 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;
+
+ udpSendReturnTest(false, false);
+
+ EXPECT_TRUE(run_);;
+}
+
+TEST_F(IOFetchTest, UdpSendReceiveBadQid) {
+ expected_ = IOFetch::TIME_OUT;
+
+ udpSendReturnTest(true, false);
+
+ EXPECT_TRUE(run_);;
+}
+
+TEST_F(IOFetchTest, UdpSendReceiveBadQidResend) {
+ expected_ = IOFetch::SUCCESS;
+
+ udpSendReturnTest(true, true);
+
+ EXPECT_TRUE(run_);;
+}
+
+// Do the same tests for TCP transport
+
+TEST_F(IOFetchTest, TcpStop) {
+ stopTest(IOFetch::TCP, tcp_fetch_);
+}
+
+TEST_F(IOFetchTest, TcpPrematureStop) {
+ prematureStopTest(IOFetch::TCP, tcp_fetch_);
+}
+
+TEST_F(IOFetchTest, TcpTimeout) {
+ timeoutTest(IOFetch::TCP, tcp_fetch_);
+}
+
+// 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));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive3) {
+ tcpSendReturnTest(test_data_.substr(0, 3));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive15) {
+ tcpSendReturnTest(test_data_.substr(0, 15));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive16) {
+ tcpSendReturnTest(test_data_.substr(0, 16));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive17) {
+ tcpSendReturnTest(test_data_.substr(0, 17));
+}
+
+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));
+}
+
+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);
+}
+
+TEST_F(IOFetchTest, TcpSendReceive8192ShortSend) {
+ tcpSendReturnTest(test_data_.substr(0, 8192), true);
+}
+
+
+} // namespace asiolink
diff --git a/src/lib/asiolink/tests/io_service_unittest.cc b/src/lib/asiolink/tests/io_service_unittest.cc
new file mode 100644
index 0000000..779d03e
--- /dev/null
+++ b/src/lib/asiolink/tests/io_service_unittest.cc
@@ -0,0 +1,116 @@
+// 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/asiolink.h>
+
+using namespace asiolink;
+
+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";
+
+TEST(IOServiceTest, badPort) {
+ IOService io_service;
+ EXPECT_THROW(DNSService(io_service, *"65536", true, false, NULL, NULL, NULL), IOError);
+ EXPECT_THROW(DNSService(io_service, *"53210.0", true, false, NULL, NULL, NULL), IOError);
+ EXPECT_THROW(DNSService(io_service, *"-1", true, false, NULL, NULL, NULL), IOError);
+ EXPECT_THROW(DNSService(io_service, *"domain", true, false, NULL, NULL, NULL), IOError);
+}
+
+TEST(IOServiceTest, badAddress) {
+ IOService io_service;
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.1.1", NULL, NULL, NULL), IOError);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"2001:db8:::1", NULL, NULL, NULL), IOError);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"localhost", NULL, NULL, NULL), IOError);
+}
+
+TEST(IOServiceTest, unavailableAddress) {
+ IOService io_service;
+ // These addresses should generally be unavailable as a valid local
+ // address, although there's no guarantee in theory.
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.0", NULL, NULL, NULL), IOError);
+
+ // Some OSes would simply reject binding attempt for an AF_INET6 socket
+ // to an IPv4-mapped IPv6 address. Even if those that allow it, since
+ // the corresponding IPv4 address is the same as the one used in the
+ // AF_INET socket case above, it should at least show the same result
+ // as the previous one.
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:192.0.2.0", NULL, NULL, NULL), IOError);
+}
+
+TEST(IOServiceTest, duplicateBind_v6) {
+ // In each sub test case, second attempt should fail due to duplicate bind
+ IOService io_service;
+
+ // IPv6, "any" address
+ DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL), IOError);
+ delete dns_service;
+
+}
+
+TEST(IOServiceTest, duplicateBind_v6_address) {
+ // In each sub test case, second attempt should fail due to duplicate bind
+ IOService io_service;
+
+ // IPv6, specific address
+ DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL), IOError);
+ delete dns_service;
+
+}
+
+TEST(IOServiceTest, duplicateBind_v4) {
+ // In each sub test case, second attempt should fail due to duplicate bind
+ IOService io_service;
+
+ // IPv4, "any" address
+ DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL), IOError);
+ delete dns_service;
+
+}
+
+TEST(IOServiceTest, duplicateBind_v4_address) {
+ // In each sub test case, second attempt should fail due to duplicate bind
+ IOService io_service;
+
+ // IPv4, specific address
+ DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL), IOError);
+ delete dns_service;
+}
+
+// Disabled because IPv4-mapped addresses don't seem to be working with
+// the IOService constructor
+TEST(IOServiceTest, DISABLED_IPv4MappedDuplicateBind) {
+ IOService io_service;
+ // Duplicate bind on IPv4-mapped IPv6 address
+ DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL), IOError);
+ delete dns_service;
+
+ // XXX:
+ // Currently, this throws an "invalid argument" exception. I have
+ // not been able to get IPv4-mapped addresses to work.
+ dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL), IOError);
+ delete dns_service;
+}
+
diff --git a/src/lib/asiolink/tests/io_socket_unittest.cc b/src/lib/asiolink/tests/io_socket_unittest.cc
new file mode 100644
index 0000000..6538550
--- /dev/null
+++ b/src/lib/asiolink/tests/io_socket_unittest.cc
@@ -0,0 +1,32 @@
+// 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 <netinet/in.h>
+
+#include <asio.hpp>
+#include <asiolink/io_socket.h>
+
+using namespace asiolink;
+
+TEST(IOSocketTest, dummySockets) {
+ EXPECT_EQ(IPPROTO_UDP, IOSocket::getDummyUDPSocket().getProtocol());
+ EXPECT_EQ(IPPROTO_TCP, IOSocket::getDummyTCPSocket().getProtocol());
+ EXPECT_EQ(-1, IOSocket::getDummyUDPSocket().getNative());
+ EXPECT_EQ(-1, IOSocket::getDummyTCPSocket().getNative());
+}
+
+
diff --git a/src/lib/asiolink/tests/qid_gen_unittest.cc b/src/lib/asiolink/tests/qid_gen_unittest.cc
new file mode 100644
index 0000000..3ad8a03
--- /dev/null
+++ b/src/lib/asiolink/tests/qid_gen_unittest.cc
@@ -0,0 +1,59 @@
+// 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.
+
+// 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 QidGenerator
+///
+
+#include <gtest/gtest.h>
+
+#include <asiolink/qid_gen.h>
+#include <dns/message.h>
+
+// Tests the operation of the Qid generator
+
+// Check that getInstance returns a singleton
+TEST(QidGenerator, singleton) {
+ asiolink::QidGenerator& g1 = asiolink::QidGenerator::getInstance();
+ asiolink::QidGenerator& g2 = asiolink::QidGenerator::getInstance();
+
+ EXPECT_TRUE(&g1 == &g2);
+}
+
+TEST(QidGenerator, generate) {
+ // We'll assume that boost's generator is 'good enough', and won't
+ // do full statistical checking here. Let's just call it the xkcd
+ // test (http://xkcd.com/221/), and check if three consecutive
+ // generates are not all the same.
+ isc::dns::qid_t one, two, three;
+ asiolink::QidGenerator& gen = asiolink::QidGenerator::getInstance();
+ one = gen.generateQid();
+ two = gen.generateQid();
+ three = gen.generateQid();
+ ASSERT_FALSE((one == two) && (one == three));
+}
diff --git a/src/lib/asiolink/tests/run_unittests.cc b/src/lib/asiolink/tests/run_unittests.cc
new file mode 100644
index 0000000..c285f9e
--- /dev/null
+++ b/src/lib/asiolink/tests/run_unittests.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2009 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 <log/root_logger_name.h>
+#include <dns/tests/unittest_util.h>
+
+int
+main(int argc, char* argv[])
+{
+ ::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_endpoint_unittest.cc b/src/lib/asiolink/tests/udp_endpoint_unittest.cc
new file mode 100644
index 0000000..18135ec
--- /dev/null
+++ b/src/lib/asiolink/tests/udp_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/udp_endpoint.h>
+
+using namespace asiolink;
+using namespace std;
+
+// This test checks that the endpoint can manage its own internal
+// asio::ip::udp::endpoint object.
+
+TEST(UDPEndpointTest, v4Address) {
+ const string test_address("192.0.2.1");
+ const unsigned short test_port = 5301;
+
+ IOAddress address(test_address);
+ UDPEndpoint endpoint(address, test_port);
+
+ EXPECT_TRUE(address == endpoint.getAddress());
+ EXPECT_EQ(test_port, endpoint.getPort());
+ EXPECT_EQ(IPPROTO_UDP, endpoint.getProtocol());
+ EXPECT_EQ(AF_INET, endpoint.getFamily());
+}
+
+TEST(UDPEndpointTest, v6Address) {
+ const string test_address("2001:db8::1235");
+ const unsigned short test_port = 5302;
+
+ IOAddress address(test_address);
+ UDPEndpoint endpoint(address, test_port);
+
+ EXPECT_TRUE(address == endpoint.getAddress());
+ EXPECT_EQ(test_port, endpoint.getPort());
+ EXPECT_EQ(IPPROTO_UDP, endpoint.getProtocol());
+ EXPECT_EQ(AF_INET6, endpoint.getFamily());
+}
diff --git a/src/lib/asiolink/tests/udp_socket_unittest.cc b/src/lib/asiolink/tests/udp_socket_unittest.cc
new file mode 100644
index 0000000..8563d22
--- /dev/null
+++ b/src/lib/asiolink/tests/udp_socket_unittest.cc
@@ -0,0 +1,333 @@
+// 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-
+/// 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/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+
+using namespace asio;
+using namespace asiolink;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+
+const char SERVER_ADDRESS[] = "127.0.0.1";
+const unsigned short SERVER_PORT = 5301;
+
+// 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 UDPCallback {
+public:
+
+ struct PrivateData {
+ PrivateData() :
+ error_code_(), length_(0), called_(false), name_("")
+ {}
+
+ asio::error_code error_code_; ///< Completion error code
+ size_t length_; ///< Number of bytes transferred
+ bool called_; ///< Set true when callback called
+ std::string name_; ///< Which of the objects this is
+ };
+
+ /// \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
+ UDPCallback(std::string which) : ptr_(new PrivateData())
+ {
+ setName(which);
+ }
+
+ /// \brief Destructor
+ ///
+ /// No code needed, destroying the shared pointer destroys the private data.
+ virtual ~UDPCallback()
+ {}
+
+ /// \brief Callback Function
+ ///
+ /// Called when an asynchronous I/O completes, this stores the
+ /// completion error code and the number of bytes transferred.
+ ///
+ /// \param ec I/O completion error code passed to callback function.
+ /// \param length Number of bytes transferred
+ virtual void operator()(asio::error_code ec, size_t length = 0) {
+ ptr_->error_code_ = ec;
+ setLength(length);
+ setCalled(true);
+ }
+
+ /// \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 getLength() const {
+ return (ptr_->length_);
+ }
+
+ /// \brief Set number of bytes transferred in I/O
+ ///
+ /// \param length New value of length parameter
+ void setLength(size_t length) {
+ ptr_->length_ = length;
+ }
+
+ /// \brief Get flag to say when callback was called
+ bool getCalled() const {
+ return (ptr_->called_);
+ }
+
+ /// \brief Set flag to say when callback was called
+ ///
+ /// \param called New value of called parameter
+ void setCalled(bool called) {
+ ptr_->called_ = called;
+ }
+
+ /// \brief Return instance of callback name
+ std::string getName() const {
+ return (ptr_->name_);
+ }
+
+ /// \brief Set callback name
+ ///
+ /// \param name New value of the callback name
+ void setName(const std::string& name) {
+ ptr_->name_ = name;
+ }
+
+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
+// message to a server, receiving an asynchronous message from the server and
+// closing.
+TEST(UDPSocket, SequenceTest) {
+
+ // Common objects.
+ IOService service; // Service object for async control
+
+ // Server
+ IOAddress server_address(SERVER_ADDRESS); // Address of target server
+ UDPCallback server_cb("Server"); // Server callback
+ UDPEndpoint server_endpoint( // Endpoint describing server
+ server_address, SERVER_PORT);
+ UDPEndpoint server_remote_endpoint; // Address where server received message from
+
+ // The client - the UDPSocket being tested
+ UDPSocket<UDPCallback> client(service);// Socket under test
+ 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
+ // the client to send to as the endpoint object in the constructor.
+ asio::ip::udp::socket server(service.get_io_service(),
+ server_endpoint.getASIOEndpoint());
+ server.set_option(socket_base::reuse_address(true));
+
+ // Assertion to ensure that the server buffer is large enough
+ char data[UDPSocket<UDPCallback>::MIN_SIZE];
+ ASSERT_GT(sizeof(data), sizeof(OUTBOUND_DATA));
+
+ // Open the client socket - the operation should be synchronous
+ 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);
+ server_cb.setCode(42); // Answer to Life, the Universe and Everything!
+ server.async_receive_from(buffer(data, sizeof(data)),
+ server_remote_endpoint.getASIOEndpoint(), server_cb);
+ EXPECT_FALSE(server_cb.getCalled());
+
+ // Write something to the server using the client - the callback should not
+ // be called until we call the io_service.run() method.
+ client_cb.setCalled(false);
+ client_cb.setCode(7); // Arbitrary number
+ client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb);
+ EXPECT_FALSE(client_cb.getCalled());
+
+ // Execute the two callbacks.
+ service.run_one();
+ service.run_one();
+
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_EQ(0, client_cb.getCode());
+ EXPECT_EQ(sizeof(OUTBOUND_DATA), client_cb.getLength());
+
+ EXPECT_TRUE(server_cb.getCalled());
+ EXPECT_EQ(0, server_cb.getCode());
+ EXPECT_EQ(sizeof(OUTBOUND_DATA), server_cb.getLength());
+
+ EXPECT_TRUE(equal(&data[0], &data[server_cb.getLength() - 1], OUTBOUND_DATA));
+
+ // Now return data from the server to the client. Issue the read on the
+ // client.
+ client_cb.setLength(12345); // Arbitrary number
+ client_cb.setCalled(false);
+ client_cb.setCode(32); // Arbitrary number
+ client.asyncReceive(data, sizeof(data), client_cumulative,
+ &client_remote_endpoint, client_cb);
+
+ // Issue the write on the server side to the source of the data it received.
+ server_cb.setLength(22345); // Arbitrary number
+ server_cb.setCalled(false);
+ server_cb.setCode(232); // Arbitrary number
+ server.async_send_to(buffer(INBOUND_DATA, sizeof(INBOUND_DATA)),
+ server_remote_endpoint.getASIOEndpoint(), server_cb);
+
+ // Expect two callbacks to run.
+ service.run_one();
+ service.run_one();
+
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_EQ(0, client_cb.getCode());
+ EXPECT_EQ(sizeof(INBOUND_DATA), client_cb.getLength());
+
+ EXPECT_TRUE(server_cb.getCalled());
+ EXPECT_EQ(0, server_cb.getCode());
+ EXPECT_EQ(sizeof(INBOUND_DATA), server_cb.getLength());
+
+ EXPECT_TRUE(equal(&data[0], &data[server_cb.getLength() - 1], INBOUND_DATA));
+
+ // Check that the address/port received by the client corresponds to the
+ // address and port the server is listening on.
+ EXPECT_TRUE(server_address == client_remote_endpoint.getAddress());
+ EXPECT_EQ(SERVER_PORT, client_remote_endpoint.getPort());
+
+ // 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());
+ EXPECT_NO_THROW(server.close());
+}
diff --git a/src/lib/asiolink/udp_endpoint.h b/src/lib/asiolink/udp_endpoint.h
new file mode 100644
index 0000000..99dc27f
--- /dev/null
+++ b/src/lib/asiolink/udp_endpoint.h
@@ -0,0 +1,113 @@
+// 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 __UDP_ENDPOINT_H
+#define __UDP_ENDPOINT_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_endpoint.h>
+
+namespace asiolink {
+
+/// \brief The \c UDPEndpoint class is a concrete derived class of
+/// \c IOEndpoint that represents an endpoint of a UDP packet.
+///
+/// Other notes about \c TCPEndpoint applies to this class, too.
+class UDPEndpoint : public IOEndpoint {
+public:
+ ///
+ /// \name Constructors and Destructor.
+ ///
+ //@{
+
+ /// \brief Default Constructor
+ ///
+ /// Creates an internal endpoint. This is expected to be set by some
+ /// external call.
+ UDPEndpoint() :
+ asio_endpoint_placeholder_(new asio::ip::udp::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 UDP port number of the endpoint.
+ UDPEndpoint(const IOAddress& address, const unsigned short port) :
+ asio_endpoint_placeholder_(
+ new asio::ip::udp::endpoint(asio::ip::address::from_string(address.toText()),
+ port)),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
+ /// \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 UDP endpoint.
+ UDPEndpoint(asio::ip::udp::endpoint& asio_endpoint) :
+ 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_; }
+ //@}
+
+ virtual IOAddress getAddress() const {
+ return (asio_endpoint_.address());
+ }
+
+ virtual uint16_t getPort() const {
+ return (asio_endpoint_.port());
+ }
+
+ virtual short getProtocol() const {
+ return (asio_endpoint_.protocol().protocol());
+ }
+
+ 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
+ inline const asio::ip::udp::endpoint& getASIOEndpoint() const {
+ return (asio_endpoint_);
+ }
+ inline asio::ip::udp::endpoint& getASIOEndpoint() {
+ return (asio_endpoint_);
+ }
+
+private:
+ asio::ip::udp::endpoint* asio_endpoint_placeholder_;
+ asio::ip::udp::endpoint& asio_endpoint_;
+};
+
+} // namespace asiolink
+#endif // __UDP_ENDPOINT_H
diff --git a/src/lib/asiolink/udp_server.cc b/src/lib/asiolink/udp_server.cc
new file mode 100644
index 0000000..5b48f28
--- /dev/null
+++ b/src/lib/asiolink/udp_server.cc
@@ -0,0 +1,321 @@
+// 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 <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+#include <errno.h>
+
+#include <boost/shared_array.hpp>
+
+#include <config.h>
+
+#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>
+#include <asiolink/udp_socket.h>
+
+#include <dns/opcode.h>
+
+using namespace asio;
+using asio::ip::udp;
+using isc::log::dlog;
+
+using namespace std;
+using namespace isc::dns;
+
+namespace asiolink {
+
+/*
+ * Some of the member variables here are shared_ptrs and some are
+ * auto_ptrs. There will be one instance of Data for the lifetime
+ * of packet. The variables that are state only for a single packet
+ * use auto_ptr, as it is more lightweight. In the case of shared
+ * configuration (eg. the callbacks, socket), we use shared_ptrs.
+ */
+struct UDPServer::Data {
+ /*
+ * Constructor from parameters passed to UDPServer constructor.
+ * This instance will not be used to retrieve and answer the actual
+ * query, it will only hold parameters until we wait for the
+ * first packet. But we do initialize the socket in here.
+ */
+ Data(io_service& io_service, const ip::address& addr, const uint16_t port,
+ SimpleCallback* checkin, DNSLookup* lookup, DNSAnswer* answer) :
+ io_(io_service), done_(false),
+ checkin_callback_(checkin),lookup_callback_(lookup),
+ answer_callback_(answer)
+ {
+ // We must use different instantiations for v4 and v6;
+ // otherwise ASIO will bind to both
+ udp proto = addr.is_v4() ? udp::v4() : udp::v6();
+ socket_.reset(new udp::socket(io_service, proto));
+ socket_->set_option(socket_base::reuse_address(true));
+ if (addr.is_v6()) {
+ socket_->set_option(asio::ip::v6_only(true));
+ }
+ socket_->bind(udp::endpoint(addr, port));
+ }
+
+ /*
+ * Copy constructor. Default one would probably do, but it is unnecessary
+ * to copy many of the member variables every time we fork to handle
+ * another packet.
+ *
+ * We also allocate data for receiving the packet here.
+ */
+ Data(const Data& other) :
+ io_(other.io_), socket_(other.socket_), done_(false),
+ checkin_callback_(other.checkin_callback_),
+ lookup_callback_(other.lookup_callback_),
+ answer_callback_(other.answer_callback_)
+ {
+ // Instantiate the data buffer and endpoint that will
+ // be used by the asynchronous receive call.
+ data_.reset(new char[MAX_LENGTH]);
+ sender_.reset(new udp::endpoint());
+ }
+
+ // The ASIO service object
+ asio::io_service& io_;
+
+ // Class member variables which are dynamic, and changes to which
+ // need to accessible from both sides of a coroutine fork or from
+ // outside of the coroutine (i.e., from an asynchronous I/O call),
+ // should be declared here as pointers and allocated in the
+ // constructor or in the coroutine. This allows state information
+ // to persist when an individual copy of the coroutine falls out
+ // scope while waiting for an event, *so long as* there is another
+ // object that is referencing the same data. As a side-benefit, using
+ // pointers also reduces copy overhead for coroutine objects.
+ //
+ // Note: Currently these objects are allocated by "new" in the
+ // constructor, or in the function operator while processing a query.
+ // Repeated allocations from the heap for every incoming query is
+ // clearly a performance issue; this must be optimized in the future.
+ // The plan is to have a structure pre-allocate several "Data"
+ // objects which can be pulled off a free list and placed on an in-use
+ // list whenever a query comes in. This will serve the dual purpose
+ // of improving performance and guaranteeing that state information
+ // will *not* be destroyed when any one instance of the coroutine
+ // falls out of scope while waiting for an event.
+ //
+ // Socket used to for listen for queries. Created in the
+ // constructor and stored in a shared_ptr because socket objects
+ // are not copyable.
+ boost::shared_ptr<asio::ip::udp::socket> socket_;
+
+ // The ASIO-internal endpoint object representing the client
+ std::auto_ptr<asio::ip::udp::endpoint> sender_;
+
+ // \c IOMessage and \c Message objects to be passed to the
+ // DNS lookup and answer providers
+ std::auto_ptr<asiolink::IOMessage> io_message_;
+
+ // The original query as sent by the client
+ isc::dns::MessagePtr query_message_;
+
+ // The response message we are building
+ isc::dns::MessagePtr answer_message_;
+
+ // The buffer into which the response is written
+ isc::dns::OutputBufferPtr respbuf_;
+
+ // The buffer into which the query packet is written
+ boost::shared_array<char> data_;
+
+ // State information that is entirely internal to a given instance
+ // of the coroutine can be declared here.
+ size_t bytes_;
+ bool done_;
+
+
+ // Callback functions provided by the caller
+ const SimpleCallback* checkin_callback_;
+ const DNSLookup* lookup_callback_;
+ const DNSAnswer* answer_callback_;
+
+ std::auto_ptr<IOEndpoint> peer_;
+ std::auto_ptr<IOSocket> iosock_;
+};
+
+/// The following functions implement the \c UDPServer class.
+///
+/// The constructor. It just creates new internal state object
+/// and lets it handle the initialization.
+UDPServer::UDPServer(io_service& io_service, const ip::address& addr,
+ const uint16_t port, SimpleCallback* checkin, DNSLookup* lookup,
+ DNSAnswer* answer) :
+ data_(new Data(io_service, addr, port, checkin, lookup, answer))
+{ }
+
+/// The function operator is implemented with the "stackless coroutine"
+/// pattern; see internal/coroutine.h for details.
+void
+UDPServer::operator()(error_code ec, size_t length) {
+ /// Because the coroutine reentry block is implemented as
+ /// a switch statement, inline variable declarations are not
+ /// permitted. Certain variables used below can be declared here.
+
+ CORO_REENTER (this) {
+ do {
+ /*
+ * This is preparation for receiving a packet. We get a new
+ * state object for the lifetime of the next packet to come.
+ * It allocates the buffers to receive data into.
+ */
+ data_.reset(new Data(*data_));
+
+ do {
+ // Begin an asynchronous receive, then yield.
+ // When the receive event is posted, the coroutine
+ // will resume immediately after this point.
+ 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 &&
+ ec.value() != interrupted) {
+ return;
+ }
+ }
+
+ } while (ec || length == 0);
+
+ data_->bytes_ = length;
+
+ /*
+ * We fork the coroutine now. One (the child) will keep
+ * the current state and handle the packet, then die and
+ * drop ownership of the state. The other (parent) will just
+ * go into the loop again and replace the current state with
+ * a new one for a new object.
+ *
+ * Actually, both of the coroutines will be a copy of this
+ * one, but that's just internal implementation detail.
+ */
+ CORO_FORK data_->io_.post(UDPServer(*this));
+ } while (is_parent());
+
+ // Create an \c IOMessage object to store the query.
+ //
+ // (XXX: It would be good to write a factory function
+ // that would quickly generate an IOMessage object without
+ // all these calls to "new".)
+ data_->peer_.reset(new UDPEndpoint(*data_->sender_));
+
+ // The UDP socket class has been extended with asynchronous functions
+ // and takes as a template parameter a completion callback class. As
+ // UDPServer does not use these extended functions (only those defined
+ // in the IOSocket base class) - but needs a UDPSocket to get hold of
+ // the underlying Boost UDP socket - DummyIOCallback is used. This
+ // provides the appropriate operator() but is otherwise functionless.
+ data_->iosock_.reset(
+ new UDPSocket<DummyIOCallback>(*data_->socket_));
+
+ data_->io_message_.reset(new IOMessage(data_->data_.get(),
+ data_->bytes_, *data_->iosock_, *data_->peer_));
+
+ // Perform any necessary operations prior to processing an incoming
+ // query (e.g., checking for queued configuration messages).
+ //
+ // (XXX: it may be a performance issue to check in for every single
+ // incoming query; we may wish to throttle this in the future.)
+ if (data_->checkin_callback_ != NULL) {
+ (*data_->checkin_callback_)(*data_->io_message_);
+ }
+
+ // If we don't have a DNS Lookup provider, there's no point in
+ // continuing; we exit the coroutine permanently.
+ if (data_->lookup_callback_ == NULL) {
+ CORO_YIELD return;
+ }
+
+ // Instantiate objects that will be needed by the
+ // asynchronous DNS lookup and/or by the send call.
+ data_->respbuf_.reset(new OutputBuffer(0));
+ data_->query_message_.reset(new Message(Message::PARSE));
+ data_->answer_message_.reset(new Message(Message::RENDER));
+
+ // Schedule a DNS lookup, and yield. When the lookup is
+ // finished, the coroutine will resume immediately after
+ // this point.
+ CORO_YIELD data_->io_.post(AsyncLookup<UDPServer>(*this));
+
+ // The 'done_' flag indicates whether we have an answer
+ // to send back. If not, exit the coroutine permanently.
+ if (!data_->done_) {
+ CORO_YIELD return;
+ }
+
+ // Call the DNS answer provider to render the answer into
+ // wire format
+ (*data_->answer_callback_)(*data_->io_message_, data_->query_message_,
+ data_->answer_message_, data_->respbuf_);
+
+ // Begin an asynchronous send, and then yield. When the
+ // send completes, we will resume immediately after this point
+ // (though we have nothing further to do, so the coroutine
+ // will simply exit at that time).
+ CORO_YIELD data_->socket_->async_send_to(
+ buffer(data_->respbuf_->getData(), data_->respbuf_->getLength()),
+ *data_->sender_, *this);
+ }
+}
+
+/// Call the DNS lookup provider. (Expected to be called by the
+/// AsyncLookup<UDPServer> handler.)
+void
+UDPServer::asyncLookup() {
+ (*data_->lookup_callback_)(*data_->io_message_,
+ data_->query_message_, data_->answer_message_, data_->respbuf_, this);
+}
+
+/// Stop the UDPServer
+void
+UDPServer::stop() {
+ /// 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();
+}
+
+/// Post this coroutine on the ASIO service queue so that it will
+/// resume processing where it left off. The 'done' parameter indicates
+/// whether there is an answer to return to the client.
+void
+UDPServer::resume(const bool done) {
+ data_->done_ = done;
+ data_->io_.post(*this);
+}
+
+bool
+UDPServer::hasAnswer() {
+ return (data_->done_);
+}
+
+} // namespace asiolink
diff --git a/src/lib/asiolink/udp_server.h b/src/lib/asiolink/udp_server.h
new file mode 100644
index 0000000..1d37471
--- /dev/null
+++ b/src/lib/asiolink/udp_server.h
@@ -0,0 +1,106 @@
+// 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 __UDP_SERVER_H
+#define __UDP_SERVER_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/dns_server.h>
+#include <asiolink/simple_callback.h>
+#include <asiolink/dns_lookup.h>
+#include <asiolink/dns_answer.h>
+
+#include <coroutine.h>
+
+namespace asiolink {
+
+//
+// Asynchronous UDP server coroutine
+//
+///
+/// \brief This class implements the coroutine to handle UDP
+/// DNS query event. As such, it is both a \c DNSServer and
+/// a \c coroutine
+///
+class UDPServer : public virtual DNSServer, public virtual coroutine {
+public:
+ /// \brief Constructor
+ /// \param io_service the asio::io_service to work with
+ /// \param addr the IP address to listen for queries on
+ /// \param port the port to listen for queries on
+ /// \param checkin the callbackprovider for non-DNS events
+ /// \param lookup the callbackprovider for DNS lookup events
+ /// \param answer the callbackprovider for DNS answer events
+ explicit UDPServer(asio::io_service& io_service,
+ const asio::ip::address& addr, const uint16_t port,
+ SimpleCallback* checkin = NULL,
+ DNSLookup* lookup = NULL,
+ DNSAnswer* answer = NULL);
+
+ /// \brief The function operator
+ void operator()(asio::error_code ec = asio::error_code(),
+ size_t length = 0);
+
+ /// \brief Calls the lookup callback
+ void asyncLookup();
+
+ /// \brief Stop the running server
+ /// \note once the server stopped, it can't restart
+ void stop();
+
+ /// \brief Resume operation
+ ///
+ /// \param done Set this to true if the lookup action is done and
+ /// we have an answer
+ void resume(const bool done);
+
+ /// \brief Check if we have an answer
+ ///
+ /// \return true if we have an answer
+ bool hasAnswer();
+
+ /// \brief Returns the coroutine state value
+ ///
+ /// \return the coroutine state value
+ int value() { return (get_value()); }
+
+ /// \brief Clones the object
+ ///
+ /// \return a newly allocated copy of this object
+ DNSServer* clone() {
+ UDPServer* s = new UDPServer(*this);
+ return (s);
+ }
+
+private:
+ enum { MAX_LENGTH = 4096 };
+
+ /**
+ * \brief Internal state and data.
+ *
+ * We use the pimple design pattern, but not because we need to hide
+ * internal data. This class and whole header is for private use anyway.
+ * It turned out that UDPServer is copied a lot, because it is a coroutine.
+ * This way the overhead of copying is lower, we copy only one shared
+ * pointer instead of about 10 of them.
+ */
+ class Data;
+ boost::shared_ptr<Data> data_;
+};
+
+} // namespace asiolink
+#endif // __UDP_SERVER_H
diff --git a/src/lib/asiolink/udp_socket.h b/src/lib/asiolink/udp_socket.h
new file mode 100644
index 0000000..35fc7b1
--- /dev/null
+++ b/src/lib/asiolink/udp_socket.h
@@ -0,0 +1,322 @@
+// 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 __UDP_SOCKET_H
+#define __UDP_SOCKET_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <log/dummylog.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+
+#include <cstddef>
+
+#include <config.h>
+
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_service.h>
+#include <asiolink/udp_endpoint.h>
+
+namespace asiolink {
+
+/// \brief The \c UDPSocket class is a concrete derived class of \c IOAsioSocket
+/// that represents a UDP socket.
+///
+/// \param C Callback type
+template <typename C>
+class UDPSocket : public IOAsioSocket<C> {
+private:
+ /// \brief Class is non-copyable
+ UDPSocket(const UDPSocket&);
+ UDPSocket& operator=(const UDPSocket&);
+
+public:
+ enum {
+ 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.
+ UDPSocket(asio::ip::udp::socket& socket);
+
+ /// \brief Constructor
+ ///
+ /// Used when the UDPSocket is being asked to manage its own internal
+ /// 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);
+
+ /// \brief Destructor
+ virtual ~UDPSocket();
+
+ /// \brief Return file descriptor of underlying socket
+ virtual int getNative() const {
+ return (socket_.native());
+ }
+
+ /// \brief Return protocol of socket
+ virtual int getProtocol() const {
+ return (IPPROTO_UDP);
+ }
+
+ /// \brief Is "open()" synchronous?
+ ///
+ /// Indicates that the opening of a UDP socket is synchronous.
+ virtual bool isOpenSynchronous() const {
+ return true;
+ }
+
+ /// \brief Open Socket
+ ///
+ /// Opens the UDP socket. This is a synchronous operation.
+ ///
+ /// \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
+ ///
+ /// 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);
+
+ /// \brief Receive Asynchronously
+ ///
+ /// 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 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 offset,
+ IOEndpoint* endpoint, C& callback);
+
+ /// \brief Process received data
+ ///
+ /// See the description of IOAsioSocket::receiveComplete for a complete
+ /// description of this method.
+ ///
+ /// \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 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();
+
+ /// \brief Close socket
+ virtual void close();
+
+
+private:
+ // Two variables to hold the socket - a socket and a pointer to it. This
+ // handles the case where a socket is passed to the UDPSocket on
+ // construction, or where it is asked to manage its own socket.
+ asio::ip::udp::socket* socket_ptr_; ///< Pointer to own socket
+ asio::ip::udp::socket& socket_; ///< Socket
+ bool isopen_; ///< true when socket is open
+};
+
+// Constructor - caller manages socket
+
+template <typename C>
+UDPSocket<C>::UDPSocket(asio::ip::udp::socket& socket) :
+ socket_ptr_(NULL), socket_(socket), isopen_(true)
+{
+}
+
+// Constructor - create socket on the fly
+
+template <typename C>
+UDPSocket<C>::UDPSocket(IOService& service) :
+ socket_ptr_(new asio::ip::udp::socket(service.get_io_service())),
+ socket_(*socket_ptr_), isopen_(false)
+{
+}
+
+// Destructor. Only delete the socket if we are managing it.
+
+template <typename C>
+UDPSocket<C>::~UDPSocket()
+{
+ delete socket_ptr_;
+}
+
+// Open the socket.
+
+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.)
+ // 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());
+ }
+ else {
+ socket_.open(asio::ip::udp::v6());
+ }
+ isopen_ = 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);
+ }
+ }
+}
+
+// Send 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>::asyncSend(const void* data, size_t length,
+ 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
+ // 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 {
+ isc_throw(SocketNotOpen,
+ "attempt to send on a UDP socket that is not 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 offset,
+ IOEndpoint* endpoint, C& callback)
+{
+ if (isopen_) {
+
+ // Upconvert the endpoint again.
+ assert(endpoint->getProtocol() == IPPROTO_UDP);
+ UDPEndpoint* udp_endpoint = static_cast<UDPEndpoint*>(endpoint);
+
+ // 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,
+ "attempt to receive from a UDP socket that is not open");
+ }
+}
+
+// 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_) {
+ socket_.cancel();
+ }
+}
+
+// Close the socket down. Can only do this if the socket is open and we are
+// managing it ourself.
+
+template <typename C> void
+UDPSocket<C>::close() {
+ if (isopen_ && socket_ptr_) {
+ socket_.close();
+ isopen_ = false;
+ }
+}
+
+} // namespace asiolink
+
+#endif // __UDP_SOCKET_H
diff --git a/src/lib/bench/benchmark.h b/src/lib/bench/benchmark.h
index 5f65c25..5eab1b7 100644
--- a/src/lib/bench/benchmark.h
+++ b/src/lib/bench/benchmark.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __BENCHMARK_H
#define __BENCHMARK_H 1
@@ -200,7 +198,7 @@ private:
BenchMark(const BenchMark& source);
BenchMark& operator=(const BenchMark& source);
public:
- /// \bench Constructor for immediate run.
+ /// \brief Constructor for immediate run.
///
/// This is the constructor that is expected to be used normally.
/// It runs the benchmark within the constructor and prints the result,
@@ -217,7 +215,7 @@ public:
initialize(true);
}
- /// \bench Constructor for finer-grained control.
+ /// \brief Constructor for finer-grained control.
///
/// This constructor takes the third parameter, \c immediate, to control
/// whether to run the benchmark within the constructor.
diff --git a/src/lib/bench/benchmark_util.cc b/src/lib/bench/benchmark_util.cc
index cdae6fe..ca2ca1b 100644
--- a/src/lib/bench/benchmark_util.cc
+++ b/src/lib/bench/benchmark_util.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <fstream>
#include <iostream>
#include <string>
diff --git a/src/lib/bench/benchmark_util.h b/src/lib/bench/benchmark_util.h
index 30d3a8f..3a373f2 100644
--- a/src/lib/bench/benchmark_util.h
+++ b/src/lib/bench/benchmark_util.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __BENCHMARK_UTIL_H
#define __BENCHMARK_UTIL_H 1
diff --git a/src/lib/bench/example/search_bench.cc b/src/lib/bench/example/search_bench.cc
index d830d2c..851d815 100644
--- a/src/lib/bench/example/search_bench.cc
+++ b/src/lib/bench/example/search_bench.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <unistd.h> // for getpid
#include <cassert>
diff --git a/src/lib/bench/tests/benchmark_unittest.cc b/src/lib/bench/tests/benchmark_unittest.cc
index ffd39b8..7bb8a60 100644
--- a/src/lib/bench/tests/benchmark_unittest.cc
+++ b/src/lib/bench/tests/benchmark_unittest.cc
@@ -12,9 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
-#include <unistd.h> // for usleep
+#include <time.h> // for nanosleep
#include <bench/benchmark.h>
@@ -28,16 +26,17 @@ namespace {
// number of iterations.
class TestBenchMark {
public:
- TestBenchMark(const int sub_iterations, const int sleep_time) :
+ TestBenchMark(const int sub_iterations,
+ const struct timespec& sleep_time) :
sub_iterations_(sub_iterations), sleep_time_(sleep_time),
setup_completed_(false), teardown_completed_(false)
{}
unsigned int run() {
- usleep(sleep_time_);
+ nanosleep(&sleep_time_, NULL);
return (sub_iterations_);
}
const int sub_iterations_;
- const int sleep_time_;
+ const struct timespec sleep_time_;
bool setup_completed_;
bool teardown_completed_;
};
@@ -69,6 +68,7 @@ TEST(BenchMarkTest, run) {
// use some uncommon iterations for testing purpose:
const int sub_iterations = 23;
const int sleep_time = 50000; // will sleep for 50ms
+ const struct timespec sleep_timespec = { 0, sleep_time * 1000 };
// we cannot expect particular accuracy on the measured duration, so
// we'll include some conservative margin (25%) and perform range
// comparison below.
@@ -77,12 +77,12 @@ TEST(BenchMarkTest, run) {
// Prerequisite check: since the tests in this case may depend on subtle
// timing, it may result in false positives. There are reportedly systems
- // where usleep() doesn't work as this test expects. So we check the
+ // where sleeping doesn't work as this test expects. So we check the
// conditions before the tests, and if it fails skip the tests at the
// risk of overlooking possible bugs.
struct timeval check_begin, check_end;
gettimeofday(&check_begin, NULL);
- usleep(sleep_time);
+ nanosleep(&sleep_timespec, 0);
gettimeofday(&check_end, NULL);
check_end.tv_sec -= check_begin.tv_sec;
if (check_end.tv_usec >= check_begin.tv_usec) {
@@ -99,7 +99,7 @@ TEST(BenchMarkTest, run) {
return;
}
- TestBenchMark test_bench(sub_iterations, sleep_time);
+ TestBenchMark test_bench(sub_iterations, sleep_timespec);
BenchMark<TestBenchMark> bench(1, test_bench, false);
// Check pre-test conditions.
EXPECT_FALSE(test_bench.setup_completed_);
@@ -132,7 +132,8 @@ TEST(BenchMarkTest, run) {
TEST(BenchMarkTest, runWithNoIteration) {
// we'll lie on the number of iteration (0). it will result in
// meaningless result, but at least it shouldn't crash.
- TestBenchMark test_bench(0, 0);
+ const struct timespec null_timespec = { 0, 0 };
+ TestBenchMark test_bench(0, null_timespec);
BenchMark<TestBenchMark> bench(1, test_bench, false);
bench.run();
EXPECT_EQ(0, bench.getIteration());
diff --git a/src/lib/bench/tests/loadquery_unittest.cc b/src/lib/bench/tests/loadquery_unittest.cc
index 073ee22..a53e191 100644
--- a/src/lib/bench/tests/loadquery_unittest.cc
+++ b/src/lib/bench/tests/loadquery_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <algorithm>
#include <utility>
#include <vector>
@@ -57,7 +55,7 @@ const char* const LoadQueryTest::DATA_DIR = TEST_DATA_DIR;
class QueryInserter {
public:
QueryInserter(stringstream& stream) : stream_(stream) {}
- void operator()(const QueryParam& query) {
+ void operator()(const QueryParam& query) const {
stream_ << query.first << " " << query.second << endl;
}
private:
@@ -82,12 +80,12 @@ public:
EXPECT_EQ(0, message.getQid());
EXPECT_EQ(Opcode::QUERY(), message.getOpcode());
EXPECT_EQ(Rcode::NOERROR(), message.getRcode());
- EXPECT_FALSE(message.getHeaderFlag(MessageFlag::QR()));
- EXPECT_FALSE(message.getHeaderFlag(MessageFlag::AA()));
- EXPECT_EQ(1, message.getRRCount(Section::QUESTION()));
- EXPECT_EQ(0, message.getRRCount(Section::ANSWER()));
- EXPECT_EQ(0, message.getRRCount(Section::AUTHORITY()));
- EXPECT_EQ(0, message.getRRCount(Section::ADDITIONAL()));
+ EXPECT_FALSE(message.getHeaderFlag(Message::HEADERFLAG_QR));
+ EXPECT_FALSE(message.getHeaderFlag(Message::HEADERFLAG_AA));
+ EXPECT_EQ(1, message.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(0, message.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, message.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, message.getRRCount(Message::SECTION_ADDITIONAL));
// Check if the question matches our original data, if the expected
// data is given.
diff --git a/src/lib/bench/tests/run_unittests.cc b/src/lib/bench/tests/run_unittests.cc
index ecc077f..85d4548 100644
--- a/src/lib/bench/tests/run_unittests.cc
+++ b/src/lib/bench/tests/run_unittests.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <gtest/gtest.h>
int
diff --git a/src/lib/cache/Makefile.am b/src/lib/cache/Makefile.am
new file mode 100644
index 0000000..107fc9a
--- /dev/null
+++ b/src/lib/cache/Makefile.am
@@ -0,0 +1,34 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/nsas -I$(top_builddir)/src/lib/nsas
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/cache -I$(top_builddir)/src/lib/cache
+AM_CPPFLAGS += $(SQLITE_CFLAGS)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_CLANGPP
+# clang++ complains about unused function parameters in some boost header
+# files.
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+lib_LTLIBRARIES = libcache.la
+libcache_la_SOURCES = resolver_cache.h resolver_cache.cc
+libcache_la_SOURCES += message_cache.h message_cache.cc
+libcache_la_SOURCES += message_entry.h message_entry.cc
+libcache_la_SOURCES += rrset_cache.h rrset_cache.cc
+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
new file mode 100644
index 0000000..aa7e3b0
--- /dev/null
+++ b/src/lib/cache/TODO
@@ -0,0 +1,18 @@
+* Revisit the algorithm used by getRRsetTrustLevel() in message_entry.cc.
+* Implement dump/load/resize interfaces of rrset/message/recursor cache.
+* Once LRU hash table is implemented, it should be used by message/rrset cache.
+* Once the hash/lrulist related files in /lib/nsas is moved to seperated
+ folder, the code of recursor cache has to be updated.
+* Set proper AD flags once DNSSEC is supported by the cache.
+* When the message or rrset entry has expired, it should be removed
+ from the cache, or just moved to the head of LRU list, so that it
+ can removed first.
+* Make resolver cache be smart to refetch the messages that are about
+ 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/cache_entry_key.cc b/src/lib/cache/cache_entry_key.cc
new file mode 100644
index 0000000..85c03a0
--- /dev/null
+++ b/src/lib/cache/cache_entry_key.cc
@@ -0,0 +1,42 @@
+// 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 <sstream>
+#include "cache_entry_key.h"
+
+using namespace std;
+
+namespace isc {
+namespace cache {
+const std::string
+genCacheEntryName(const isc::dns::Name& name, const isc::dns::RRType& type) {
+ std::string keystr = name.toText();
+ ostringstream stream;
+ stream << type.getCode();
+ keystr += stream.str();
+ return (keystr);
+}
+
+const std::string
+genCacheEntryName(const std::string& namestr, const uint16_t type) {
+ std::string keystr = namestr;
+ ostringstream stream;
+ stream << type;
+ keystr += stream.str();
+ return (keystr);
+}
+
+} // namespace cache
+} // namespace isc
+
diff --git a/src/lib/cache/cache_entry_key.h b/src/lib/cache/cache_entry_key.h
new file mode 100644
index 0000000..674deb0
--- /dev/null
+++ b/src/lib/cache/cache_entry_key.h
@@ -0,0 +1,54 @@
+// 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 __CACHE_ENTRY_KEY_H
+#define __CACHE_ENTRY_KEY_H
+
+#include <string>
+#include <dns/name.h>
+#include <dns/rrtype.h>
+
+namespace isc {
+namespace cache {
+
+/// \brief Entry Name Generation Functions
+///
+/// Generate the name for message/rrset entries.
+///
+/// Concatenates the string representation of the Name and the
+/// string representation of the type number.
+///
+/// Note: the returned name is a text string, not wire format.
+/// eg. if name is 'example.com.', type is 'A', the return
+/// value is 'example.com.1'
+///
+/// \param name The Name to create a text entry for
+/// \param type The RRType to create a text entry for
+/// \return return the entry name.
+const std::string
+genCacheEntryName(const isc::dns::Name& name, const isc::dns::RRType& type);
+
+///
+/// \overload
+///
+/// \param namestr A string representation of a DNS Name
+/// \param type The value of a DNS RRType
+const std::string
+genCacheEntryName(const std::string& namestr, const uint16_t type);
+
+} // namespace cache
+} // namespace isc
+
+#endif // __CACHE_ENTRY_KEY_H
+
diff --git a/src/lib/cache/local_zone_data.cc b/src/lib/cache/local_zone_data.cc
new file mode 100644
index 0000000..61ce35a
--- /dev/null
+++ b/src/lib/cache/local_zone_data.cc
@@ -0,0 +1,56 @@
+// 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 <dns/rrset.h>
+#include "local_zone_data.h"
+#include "cache_entry_key.h"
+#include "rrset_copy.h"
+
+using namespace std;
+using namespace isc::dns;
+
+namespace isc {
+namespace cache {
+
+typedef pair<std::string, RRsetPtr> RRsetMapPair;
+typedef map<std::string, RRsetPtr>::iterator RRsetMapIterator;
+
+isc::dns::RRsetPtr
+LocalZoneData::lookup(const isc::dns::Name& name,
+ const isc::dns::RRType& type)
+{
+ string key = genCacheEntryName(name, type);
+ RRsetMapIterator iter = rrsets_map_.find(key);
+ if (iter == rrsets_map_.end()) {
+ return (RRsetPtr());
+ } else {
+ return (iter->second);
+ }
+}
+
+void
+LocalZoneData::update(const isc::dns::RRset& rrset) {
+ //TODO Do we really need to recreate the rrset again?
+ string key = genCacheEntryName(rrset.getName(), rrset.getType());
+ RRset* rrset_copy = new RRset(rrset.getName(), rrset.getClass(),
+ rrset.getType(), rrset.getTTL());
+
+ rrsetCopy(rrset, *rrset_copy);
+ RRsetPtr rrset_ptr(rrset_copy);
+ rrsets_map_[key] = rrset_ptr;
+}
+
+} // namespace cache
+} // namespace isc
+
diff --git a/src/lib/cache/local_zone_data.h b/src/lib/cache/local_zone_data.h
new file mode 100644
index 0000000..3015847
--- /dev/null
+++ b/src/lib/cache/local_zone_data.h
@@ -0,0 +1,64 @@
+// 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 _LOCAL_ZONE_DATA
+#define _LOCAL_ZONE_DATA
+
+#include <map>
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include <dns/rrset.h>
+
+namespace isc {
+namespace cache {
+
+/// \brief Local Zone Data
+/// The object of LocalZoneData represents the data of one
+/// local zone. It provides the interface for lookup the rrsets
+/// in the zone.
+class LocalZoneData {
+public:
+ LocalZoneData(uint16_t rrset_class) : class_(rrset_class)
+ {}
+
+ /// \brief Look up one rrset.
+ ///
+ /// \param qname The query name to look up
+ /// \param qtype The query type to look up
+ /// \return return the shared_ptr of rrset if it is
+ /// found in the local zone, or else, return NULL.
+ isc::dns::RRsetPtr lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype);
+
+ /// \brief Update the rrset in the local zone.
+ ///
+ /// If the rrset doesn't exist, it will be added.
+ /// Otherwise, the existed one will be overwritten.
+ ///
+ /// \param rrset The rrset to update
+ void update(const isc::dns::RRset& rrset);
+
+private:
+ std::map<std::string, isc::dns::RRsetPtr> rrsets_map_; // RRsets of the zone
+ uint16_t class_; // The class of the zone
+};
+
+typedef boost::shared_ptr<LocalZoneData> LocalZoneDataPtr;
+typedef boost::shared_ptr<const LocalZoneData> ConstLocalZoneDataPtr;
+
+} // namespace cache
+} // namespace isc
+
+#endif // _LOCAL_ZONE_DATA
+
diff --git a/src/lib/cache/message_cache.cc b/src/lib/cache/message_cache.cc
new file mode 100644
index 0000000..0464f87
--- /dev/null
+++ b/src/lib/cache/message_cache.cc
@@ -0,0 +1,120 @@
+// 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 <config.h>
+
+#include <nsas/nsas_entry_compare.h>
+#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;
+
+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,
+ isc::dns::Message& response)
+{
+ std::string entry_name = genCacheEntryName(qname, qtype);
+ HashKey entry_key = HashKey(entry_name, RRClass(message_class_));
+ MessageEntryPtr msg_entry = message_table_.get(entry_key);
+ if(msg_entry) {
+ // 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);
+}
+
+bool
+MessageCache::update(const Message& msg) {
+ if (!canMessageBeCached(msg)){
+ return (false);
+ }
+
+ QuestionIterator iter = msg.beginQuestion();
+ 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.
+ // We have find the existed message entry, since we need to delete it
+ // from lru list too.
+ // TODO, but there should be a better way, since we here have to remove and
+ // add the message entry, maybe there is one way to touch it once.
+ MessageEntryPtr old_msg_entry = message_table_.get(entry_key);
+ if (old_msg_entry) {
+ message_lru_.remove(old_msg_entry);
+ }
+
+ 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));
+}
+
+#if 0
+void
+MessageCache::dump(const std::string&) {
+ //TODO
+}
+
+void
+MessageCache::load(const std::string&) {
+ //TODO
+}
+
+bool
+MessageCache::resize(uint32_t) {
+ //TODO
+ return (true);
+}
+#endif
+
+} // namespace cache
+} // namespace isc
+
diff --git a/src/lib/cache/message_cache.h b/src/lib/cache/message_cache.h
new file mode 100644
index 0000000..790ed83
--- /dev/null
+++ b/src/lib/cache/message_cache.h
@@ -0,0 +1,104 @@
+// 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 __MESSAGE_CACHE_H
+#define __MESSAGE_CACHE_H
+
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include <dns/message.h>
+#include "message_entry.h"
+#include <nsas/hash_table.h>
+#include <nsas/lru_list.h>
+#include "rrset_cache.h"
+
+namespace isc {
+namespace cache {
+
+/// \brief Message Cache
+/// The object of MessageCache represents the cache for class-specific
+/// messages.
+///
+class MessageCache {
+// Noncopyable
+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.
+ /// \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();
+
+ /// \brief Look up message in cache.
+ /// \param message generated response message if the message entry
+ /// can be found.
+ ///
+ /// \return return true if the message can be found in cache, or else,
+ /// return false.
+ //TODO Maybe some user just want to get the message_entry.
+ bool lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ isc::dns::Message& message);
+
+ /// \brief Update the message in the cache with the new one.
+ /// If the message doesn't exist in the cache, it will be added
+ /// directly.
+ bool update(const isc::dns::Message& msg);
+
+#if 0
+ /// \brief Dump the message cache to specified file.
+ /// \todo It should can be dumped to one configured database.
+ void dump(const std::string& file_name);
+
+ /// \brief Load the cache from one file.
+ /// \todo It should can be loaded from one configured database.
+ void load(const std::string& file_name);
+
+ /// \brief Resize the size of message cache in runtime.
+ bool resize(uint32_t size);
+#endif
+
+protected:
+ /// \brief Get the hash key for the message entry in the cache.
+ /// \param name query name of the message.
+ /// \param type query type of the message.
+ /// \return return the hash key.
+ HashKey getEntryHashKey(const isc::dns::Name& name,
+ const isc::dns::RRType& type) const;
+
+ // Make these variants be protected for easy unittest.
+protected:
+ uint16_t message_class_; // The class of the message cache.
+ RRsetCachePtr rrset_cache_;
+ RRsetCachePtr negative_soa_cache_;
+ isc::nsas::HashTable<MessageEntry> message_table_;
+ isc::nsas::LruList<MessageEntry> message_lru_;
+};
+
+typedef boost::shared_ptr<MessageCache> MessageCachePtr;
+
+} // namespace cache
+} // namespace isc
+
+#endif // __MESSAGE_CACHE_H
+
diff --git a/src/lib/cache/message_entry.cc b/src/lib/cache/message_entry.cc
new file mode 100644
index 0000000..de4ea89
--- /dev/null
+++ b/src/lib/cache/message_entry.cc
@@ -0,0 +1,345 @@
+// 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 <config.h>
+
+#include <limits>
+#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;
+using namespace std;
+
+// Put file scope functions in unnamed namespace.
+namespace {
+
+// Get the shortest existing ancestor which is the owner name of
+// one DNAME record for the given query name.
+// Note: there may be multiple DNAME records(DNAME chain) in answer
+// section. In most cases they are in order, but the code can't depend
+// on that, it has to find the starter by iterating the DNAME chain.
+Name
+getDNAMEChainStarter(const Message& message, const Name& query_name) {
+ Name dname = query_name;
+ RRsetIterator rrset_iter = message.beginSection(Message::SECTION_ANSWER);
+ while(rrset_iter != message.endSection(Message::SECTION_ANSWER)) {
+ if ((*rrset_iter)->getType() == RRType::DNAME()) {
+ const Name& rrname = (*rrset_iter)->getName();
+ if (NameComparisonResult::SUBDOMAIN ==
+ dname.compare(rrname).getRelation()) {
+ dname = rrname;
+ }
+ }
+ ++rrset_iter;
+ }
+
+ return (dname);
+}
+
+} // End of unnamed namespace
+
+namespace isc {
+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,
+ 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)
+{
+ initMessageEntry(msg);
+ entry_name_ = genCacheEntryName(query_name_, query_type_);
+ hash_key_ptr_ = new HashKey(entry_name_, RRClass(query_class_));
+}
+
+bool
+MessageEntry::getRRsetEntries(vector<RRsetEntryPtr>& rrset_entry_vec,
+ const time_t time_now)
+{
+ 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) {
+ 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);
+ } else {
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+void
+MessageEntry::addRRset(isc::dns::Message& message,
+ const vector<RRsetEntryPtr>& rrset_entry_vec,
+ const isc::dns::Message::Section& section,
+ bool dnssec_need)
+{
+ uint16_t start_index = 0;
+ uint16_t end_index = answer_count_;
+ assert(section != Message::SECTION_QUESTION);
+
+ if (section == Message::SECTION_AUTHORITY) {
+ start_index = answer_count_;
+ end_index = answer_count_ + authority_count_;
+ } else if (section == Message::SECTION_ADDITIONAL) {
+ start_index = answer_count_ + authority_count_;
+ 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);
+ }
+}
+
+bool
+MessageEntry::genMessage(const time_t& time_now,
+ isc::dns::Message& msg)
+{
+ if (time_now >= expire_time_) {
+ // The message entry has expired.
+ return (false);
+ } else {
+ // Before do any generation, we should check if some rrset
+ // has expired, if it is, return false.
+ vector<RRsetEntryPtr> rrset_entry_vec;
+ if (false == getRRsetEntries(rrset_entry_vec, time_now)) {
+ return (false);
+ }
+
+ // Begin message generation. We don't need to add question
+ // section, since it has been included in the message.
+ // Set cached header flags.
+ // 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();
+ addRRset(msg, rrset_entry_vec, Message::SECTION_ANSWER, dnssec_need);
+ addRRset(msg, rrset_entry_vec, Message::SECTION_AUTHORITY, dnssec_need);
+ addRRset(msg, rrset_entry_vec, Message::SECTION_ADDITIONAL, dnssec_need);
+
+ return (true);
+ }
+}
+
+RRsetTrustLevel
+MessageEntry::getRRsetTrustLevel(const Message& message,
+ const isc::dns::RRsetPtr& rrset,
+ const isc::dns::Message::Section& section)
+{
+ bool aa = message.getHeaderFlag(Message::HEADERFLAG_AA);
+ switch(section) {
+ case Message::SECTION_ANSWER: {
+ if (aa) {
+ // According RFC2181 section 5.4.1, only the record
+ // describing that ailas is necessarily authoritative.
+ // If there are CNAME(Not synchronized from DNAME)
+ // records in answer section, only the CNAME record
+ // whose owner name is same with qname is assumed as
+ // authoritative, all the left records are not authoritative.
+ //
+ // If there are DNAME records in answer section,
+ // Only the start DNAME and the synchronized CNAME record
+ // from it are authoritative, any other records in answer
+ // section are non-authoritative.
+ QuestionIterator quest_iter = message.beginQuestion();
+ // Make sure question section is not empty.
+ assert( quest_iter != message.endQuestion());
+
+ const Name& query_name = (*quest_iter)->getName();
+ const RRType& type = rrset->getType();
+ const Name& name = rrset->getName();
+ if ((type == RRType::CNAME() && name == query_name) ||
+ (type == RRType::DNAME() &&
+ name == getDNAMEChainStarter(message, query_name))) {
+ return (RRSET_TRUST_ANSWER_AA);
+ } else {
+ // If there is a CNAME record whose ower name is the same as
+ // the query name in answer section, the other records in answer
+ // section are non-authoritative, except the starter of DNAME
+ // chain (only checking CNAME is enough, because if the CNAME
+ // record is synthesized from a DNAME record, that DNAME
+ // record must be the starter of the DNAME chain).
+ RRsetIterator iter = message.beginSection(Message::SECTION_ANSWER);
+ while(iter != message.endSection(Message::SECTION_ANSWER)) {
+ if ((*iter)->getType() == RRType::CNAME() &&
+ (*iter)->getName() == query_name) {
+ return (RRSET_TRUST_ANSWER_NONAA);
+ }
+ ++iter;
+ }
+ }
+ return (RRSET_TRUST_ANSWER_AA);
+ } else {
+ return (RRSET_TRUST_ANSWER_NONAA);
+ }
+ break;
+ }
+
+ case Message::SECTION_AUTHORITY: {
+ if (aa) {
+ return (RRSET_TRUST_AUTHORITY_AA);
+ } else {
+ return (RRSET_TRUST_AUTHORITY_NONAA);
+ }
+ break;
+ }
+
+ case Message::SECTION_ADDITIONAL: {
+ if (aa) {
+ return (RRSET_TRUST_ADDITIONAL_AA);
+ } else {
+ return (RRSET_TRUST_ADDITIONAL_NONAA);
+ }
+ break;
+ }
+
+ default:
+ return (RRSET_TRUST_DEFAULT);
+ }
+}
+
+void
+MessageEntry::parseSection(const isc::dns::Message& msg,
+ const Message::Section& section,
+ uint32_t& smaller_ttl,
+ uint16_t& rrset_count)
+{
+ RRsetIterator iter;
+ int count = 0;
+ for (iter = msg.beginSection(section);
+ iter != msg.endSection(section);
+ ++iter) {
+ // Add the rrset entry to rrset_cache or update the existed
+ // rrset entry if the new one is more authoritative.
+ //TODO set proper rrset trust level.
+ 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(),
+ rrset_cache_.get()));
+
+ uint32_t rrset_ttl = rrset_entry->getTTL();
+ if (smaller_ttl > rrset_ttl) {
+ smaller_ttl = rrset_ttl;
+ }
+
+ count++;
+ }
+
+ rrset_count = count;
+}
+
+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);
+ headerflag_tc_ = msg.getHeaderFlag(Message::HEADERFLAG_TC);
+
+ // We only cache the first question in question section.
+ // TODO, do we need to support muptiple questions?
+ query_count_ = 1;
+ QuestionIterator iter = msg.beginQuestion();
+ query_name_ = (*iter)->getName().toText();
+ query_type_ = (*iter)->getType().getCode();
+ 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_);
+ 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
new file mode 100644
index 0000000..6775ff6
--- /dev/null
+++ b/src/lib/cache/message_entry.h
@@ -0,0 +1,205 @@
+// 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 __MESSAGE_ENTRY_H
+#define __MESSAGE_ENTRY_H
+
+#include <vector>
+#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;
+
+/// \brief Message Entry
+///
+/// The object of MessageEntry represents one response message
+/// answered to the resolver client.
+class MessageEntry : public NsasEntry<MessageEntry> {
+// Noncopyable
+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
+ /// message.
+ /// \param message The message used to initialize MessageEntry.
+ /// \param rrset_cache the pointer of RRsetCache. When one message
+ /// entry is created, rrset cache needs to be updated,
+ /// 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,
+ 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.
+ ///
+ /// \param time_now set the ttl of each rrset in the message
+ /// as "expire_time - time_now" (expire_time is the
+ /// expiration time of the rrset).
+ /// \param response generated dns message.
+ /// \return return true if the response message can be generated
+ /// from the cached information, or else, return false.
+ bool genMessage(const time_t& time_now, isc::dns::Message& response);
+
+ /// \brief Get the hash key of the message entry.
+ ///
+ /// \return return hash key
+ virtual HashKey hashKey() const {
+ 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:
+ /// \brief Initialize the message entry with dns message.
+ ///
+ /// \param message The Message to initialize the entry with
+ void initMessageEntry(const isc::dns::Message& message);
+
+ /// \brief Parse the rrsets in specified section.
+ ///
+ /// \param msg The message to parse the RRsets from
+ /// \param section The Section to parse the RRsets from
+ /// \param smaller_ttl Get the smallest ttl of rrsets in
+ /// specified section, if it's smaller than the given value.
+ /// \param rrset_count the rrset count of the section.
+ /// (TODO for Message, getRRsetCount() should be one
+ /// interface provided by Message.)
+ void parseSection(const isc::dns::Message& msg,
+ const isc::dns::Message::Section& section,
+ 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
+ /// with higher trust level.
+ ///
+ /// \param message Message that the rrset belongs to
+ /// \param rrset specified rrset which needs to get its
+ /// trust worthiness
+ /// \param section Section of the rrset
+ /// \return return rrset trust level.
+ RRsetTrustLevel getRRsetTrustLevel(const isc::dns::Message& message,
+ const isc::dns::RRsetPtr& rrset,
+ const isc::dns::Message::Section& section);
+
+ /// \brief Add rrset to one section of message.
+ ///
+ /// \param message The message to add rrsets to.
+ /// \param rrset_entry_vec vector for rrset entries in
+ /// different sections.
+ /// \param section The section to add to
+ /// \param dnssec_need need dnssec records or not.
+ void addRRset(isc::dns::Message& message,
+ const std::vector<RRsetEntryPtr>& rrset_entry_vec,
+ const isc::dns::Message::Section& section,
+ bool dnssec_need);
+
+ /// \brief Get the all the rrset entries for the message entry.
+ ///
+ /// \param rrset_entry_vec vector to add unexpired rrset entries to
+ /// \param time_now the time of now. Used to compare with rrset
+ /// entry's expire time.
+ /// \return return false if any rrset entry has expired, true
+ /// otherwise.
+ bool getRRsetEntries(std::vector<RRsetEntryPtr>& rrset_entry_vec,
+ const time_t time_now);
+
+ time_t expire_time_; // Expiration time of the message.
+ //@}
+
+private:
+ std::string entry_name_; // The name for this entry(name + type)
+ HashKey* hash_key_ptr_; // the key for messag entry in hash table.
+
+ std::vector<RRsetRef> rrsets_;
+ 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.
+ uint16_t query_type_; // query type of message.
+
+ uint16_t query_count_; // query count in query section.
+ uint16_t answer_count_; // rrset count in answer section.
+ uint16_t authority_count_; // rrset count in authority section.
+ uint16_t additional_count_; // rrset count in addition section.
+
+ //TODO, there should be a better way to cache these header flags
+ bool headerflag_aa_; // Whether AA bit is set.
+ bool headerflag_tc_; // Whether TC bit is set.
+};
+
+typedef boost::shared_ptr<MessageEntry> MessageEntryPtr;
+
+} // namespace cache
+} // namespace isc
+
+#endif // __MESSAGE_ENTRY_H
+
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
new file mode 100644
index 0000000..261db3c
--- /dev/null
+++ b/src/lib/cache/resolver_cache.cc
@@ -0,0 +1,252 @@
+// 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 <config.h>
+
+#include "resolver_cache.h"
+#include "dns/message.h"
+#include "rrset_cache.h"
+#include <string>
+#include <algorithm>
+
+using namespace isc::dns;
+using namespace std;
+
+namespace isc {
+namespace cache {
+
+ResolverClassCache::ResolverClassCache(const RRClass& cache_class) :
+ cache_class_(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(),
+ negative_soa_cache_));
+}
+
+ResolverClassCache::ResolverClassCache(const CacheSizeInfo& cache_info) :
+ cache_class_(cache_info.cclass)
+{
+ uint16_t klass = cache_class_.getCode();
+ // TODO We should find one way to load local zone data.
+ 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, negative_soa_cache_));
+}
+
+const RRClass&
+ResolverClassCache::getClass() const {
+ return (cache_class_);
+}
+
+bool
+ResolverClassCache::lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ isc::dns::Message& response) const
+{
+ // message response should has question section already.
+ if (response.beginQuestion() == response.endQuestion()) {
+ isc_throw(MessageNoQuestionSection, "Message has no question section");
+ }
+
+ // First, query in local zone, if the rrset(qname, qtype, qclass) can be
+ // found in local zone, generated reply message with only the rrset in
+ // answer section.
+ RRsetPtr rrset_ptr = local_zone_data_->lookup(qname, qtype);
+ if (rrset_ptr) {
+ response.addRRset(Message::SECTION_ANSWER, rrset_ptr);
+ return (true);
+ }
+
+ // Search in class-specific message cache.
+ return (messages_cache_->lookup(qname, qtype, response));
+}
+
+isc::dns::RRsetPtr
+ResolverClassCache::lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype) const
+{
+ // Algorithm:
+ // 1. Search in local zone data first,
+ // 2. Then do search in rrsets_cache_.
+ RRsetPtr rrset_ptr = local_zone_data_->lookup(qname, qtype);
+ if (rrset_ptr) {
+ return (rrset_ptr);
+ } else {
+ RRsetEntryPtr rrset_entry = rrsets_cache_->lookup(qname, qtype);
+ if (rrset_entry) {
+ return (rrset_entry->getRRset());
+ } else {
+ return (RRsetPtr());
+ }
+ }
+}
+
+bool
+ResolverClassCache::update(const isc::dns::Message& msg) {
+ return (messages_cache_->update(msg));
+}
+
+bool
+ResolverClassCache::updateRRsetCache(const isc::dns::ConstRRsetPtr& rrset_ptr,
+ RRsetCachePtr rrset_cache_ptr)
+{
+ RRsetTrustLevel level;
+ if (rrset_ptr->getType() == RRType::A() ||
+ rrset_ptr->getType() == RRType::AAAA()) {
+ level = RRSET_TRUST_PRIM_GLUE;
+ } else {
+ level = RRSET_TRUST_PRIM_ZONE_NONGLUE;
+ }
+
+ rrset_cache_ptr->update((*rrset_ptr.get()), level);
+ return (true);
+}
+
+bool
+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_);
+ return (true);
+}
+
+
+ResolverCache::ResolverCache()
+{
+ class_caches_.push_back(new ResolverClassCache(RRClass::IN()));
+}
+
+ResolverCache::ResolverCache(std::vector<CacheSizeInfo> caches_info)
+{
+ for (int i = 0; i < caches_info.size(); ++i) {
+ class_caches_.push_back(new ResolverClassCache(caches_info[i]));
+ }
+}
+
+ResolverCache::~ResolverCache()
+{
+ for (int i = 0; i < class_caches_.size(); ++i) {
+ delete class_caches_[i];
+ }
+}
+
+bool
+ResolverCache::lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ const isc::dns::RRClass& qclass,
+ isc::dns::Message& response) const
+{
+ ResolverClassCache* cc = getClassCache(qclass);
+ if (cc) {
+ return (cc->lookup(qname, qtype, response));
+ } else {
+ return (false);
+ }
+}
+
+isc::dns::RRsetPtr
+ResolverCache::lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ const isc::dns::RRClass& qclass) const
+{
+ ResolverClassCache* cc = getClassCache(qclass);
+ if (cc) {
+ return (cc->lookup(qname, qtype));
+ } else {
+ return (RRsetPtr());
+ }
+}
+
+isc::dns::RRsetPtr
+ResolverCache::lookupDeepestNS(const isc::dns::Name& qname,
+ const isc::dns::RRClass& qclass) const
+{
+ isc::dns::RRType qtype = RRType::NS();
+ ResolverClassCache* cc = getClassCache(qclass);
+ if (cc) {
+ unsigned int count = qname.getLabelCount();
+ unsigned int level = 0;
+ while(level < count) {
+ Name close_name = qname.split(level);
+ RRsetPtr rrset_ptr = cc->lookup(close_name, qtype);
+ if (rrset_ptr) {
+ return (rrset_ptr);
+ } else {
+ ++level;
+ }
+ }
+ }
+
+ return (RRsetPtr());
+}
+
+bool
+ResolverCache::update(const isc::dns::Message& msg) {
+ QuestionIterator iter = msg.beginQuestion();
+ ResolverClassCache* cc = getClassCache((*iter)->getClass());
+ if (cc) {
+ return (cc->update(msg));
+ } else {
+ return (false);
+ }
+}
+
+bool
+ResolverCache::update(const isc::dns::ConstRRsetPtr& rrset_ptr) {
+ ResolverClassCache* cc = getClassCache(rrset_ptr->getClass());
+ if (cc) {
+ return (cc->update(rrset_ptr));
+ } else {
+ return (false);
+ }
+}
+
+void
+ResolverCache::dump(const std::string&) {
+ //TODO
+}
+
+void
+ResolverCache::load(const std::string&) {
+ //TODO
+}
+
+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 (NULL);
+}
+
+} // namespace cache
+} // namespace isc
+
diff --git a/src/lib/cache/resolver_cache.h b/src/lib/cache/resolver_cache.h
new file mode 100644
index 0000000..49818b5
--- /dev/null
+++ b/src/lib/cache/resolver_cache.h
@@ -0,0 +1,337 @@
+// 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 __RESOLVER_CACHE_H
+#define __RESOLVER_CACHE_H
+
+#include <map>
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include <dns/rrclass.h>
+#include <dns/message.h>
+#include <exceptions/exceptions.h>
+#include "message_cache.h"
+#include "rrset_cache.h"
+#include "local_zone_data.h"
+
+namespace isc {
+namespace cache {
+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.
+///
+/// Used to initialize the size of class-specific rrset/message cache.
+struct CacheSizeInfo
+{
+public:
+ /// \brief Constructor
+ ///
+ /// \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,
+ uint32_t msg_cache_size,
+ uint32_t rst_cache_size):
+ cclass(cls),
+ message_cache_size(msg_cache_size),
+ rrset_cache_size(rst_cache_size)
+ {}
+
+ isc::dns::RRClass cclass; // class of the cache.
+ uint32_t message_cache_size; // the size for message cache.
+ uint32_t rrset_cache_size; // The size for rrset cache.
+};
+
+/// \brief Message has no question section.
+///
+/// Thrown if the given message has no question section when looking up
+/// the message in cache.
+class MessageNoQuestionSection : public isc::Exception {
+public:
+ MessageNoQuestionSection(const char*file, size_t line, const char*what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+/// \brief Class-specific Resolver Cache.
+///
+/// The object of ResolverCache represents the cache of the resolver. It may hold
+/// a list of message/rrset cache which are in different class.
+///
+/// \note Public interaction with the cache should be through ResolverCache,
+/// not directly with this one. (TODO: make this private/hidden/local to the .cc?)
+class ResolverClassCache {
+public:
+ /// \brief Default Constructor.
+ ///
+ /// Only support for class "IN", and message cache size is
+ /// MESSAGE_CACHE_DEFAULT_SIZE, rrset cache size is
+ /// RRSET_CACHE_DEFAULT_SIZE
+ ResolverClassCache(const isc::dns::RRClass& cache_class);
+
+ /// \brief Construct Function.
+ /// \param caches_size cache size information for each
+ /// messages/rrsets of different classes.
+ ResolverClassCache(const CacheSizeInfo& cache_info);
+
+ /// \name Lookup Interfaces
+ //@{
+ /// \brief Look up message in cache.
+ ///
+ /// \param qname The query name to look up
+ /// \param qtype The query type to look up
+ /// \param response the query message (must be in RENDER mode)
+ /// which has question section already (exception
+ /// MessageNoQeustionSection will be thrown if it has
+ /// no question section). If the message can be found
+ /// in cache, rrsets for the message will be added to
+ /// different sections(answer, authority, additional).
+ /// \return return true if the message can be found, or else,
+ /// return false.
+ bool lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ isc::dns::Message& response) const;
+
+ /// \brief Look up rrset in cache.
+ ///
+ /// \param qname The query name to look up
+ /// \param qtype The query type to look up
+ ///
+ /// \return return the shared_ptr of rrset if it can be found,
+ /// or else, return NULL. When looking up, local zone
+ /// data will be searched first, if not found, then
+ /// search in rrset cache.
+ ///
+ /// \overload
+ ///
+ isc::dns::RRsetPtr lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype) const;
+
+ /// \brief Update the message in the cache with the new one.
+ ///
+ /// \param msg The message to update
+ ///
+ /// \return return true if the message is updated successfully,
+ /// or else, return false.
+ ///
+ /// \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.
+ ///
+ /// local zone data and rrset cache will be updated together.
+ /// If the rrset doesn't exist in both of them, then the rrset
+ /// will be added into both of them.
+ ///
+ /// \param rrset_ptr The RRset to update
+ ///
+ /// \return return false, if the class of the parameter rrset is
+ /// allowed to be cached.
+ ///
+ /// \overload
+ ///
+ /// \note The class of the RRset must have been checked. It is not
+ /// here.
+ 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.
+ ///
+ /// \param rrset_ptr The rrset to update with
+ /// \param rrset_cache_ptr the rrset cache to update
+ ///
+ /// \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,
+ RRsetCachePtr rrset_cache_ptr);
+
+ /// \brief Class this cache is for.
+ const isc::dns::RRClass cache_class_;
+
+ /// \brief map of message caches for configured classes(each message
+ /// cache is class-specific)
+ MessageCachePtr messages_cache_;
+
+ /// \name rrset caches
+ //@{
+ /// \brief Local Zone data cache
+ /// 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 {
+public:
+ /// \brief Default Constructor.
+ ///
+ /// Right now, only support for class "IN", and message cache size is
+ /// MESSAGE_CACHE_DEFAULT_SIZE, rrset cache size is
+ /// RRSET_CACHE_DEFAULT_SIZE
+ ResolverCache();
+
+ /// \brief Construct Function.
+ /// \param caches_size cache size information for each
+ /// messages/rrsets of different classes.
+ ResolverCache(std::vector<CacheSizeInfo> caches_size);
+
+ /// \brief Destructor
+ ~ResolverCache();
+
+ /// \name Lookup Interfaces
+ //@{
+ /// \brief Look up message in cache.
+ ///
+ /// \param qname The query name to look up
+ /// \param qtype The query type to look up
+ /// \param qclass The query class to look up
+ /// \param response the query message (must be in RENDER mode)
+ /// which has question section already (exception
+ /// MessageNoQeustionSection will be thrown if it has
+ /// no question section). If the message can be found
+ /// in cache, rrsets for the message will be added to
+ /// different sections(answer, authority, additional).
+ /// \return return true if the message can be found, or else,
+ /// return false.
+ bool lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ const isc::dns::RRClass& qclass,
+ isc::dns::Message& response) const;
+
+ /// \brief Look up rrset in cache.
+ ///
+ /// \param qname The query name to look up
+ /// \param qtype The query type to look up
+ /// \param qclass The query class to look up
+ ///
+ /// \return return the shared_ptr of rrset if it can be found,
+ /// or else, return NULL. When looking up, local zone
+ /// data will be searched first, if not found, then
+ /// search in rrset cache.
+ ///
+ /// \overload
+ ///
+ isc::dns::RRsetPtr lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ const isc::dns::RRClass& qclass) const;
+
+ /// \brief Look up closest enclosing NS rrset in cache.
+ ///
+ /// \param qname The query name to look up
+ /// \param qclass The query class to look up
+ ///
+ /// \return return the shared_ptr of closest enclosing ns rrset
+ /// if it can be found in cache, or else return NULL.
+ ///
+ /// Currently the implementation is: search exact ns rrset
+ /// label by lable, If the ns rrset can't be found, remove the last
+ /// label, then search again. The efficiency may be very low when
+ /// the name is very long but it's closest rrset's name is very short.
+ ///
+ /// If a good perfermance is needed when looking up the closest
+ /// enclosing ns rrset, cache structure(HashTable) should be
+ /// redesigned. By using HashTable, it can only garantee the
+ /// performance for looking up exact rrset.
+ ///
+ /// So here there is another question, which rrset looking up interface
+ /// is used frequently? Exact or closest enclosing ns looking up.
+ isc::dns::RRsetPtr lookupDeepestNS(const isc::dns::Name& qname,
+ const isc::dns::RRClass& qclass) const;
+ //@}
+
+ /// \brief Update the message in the cache with the new one.
+ ///
+ /// \param msg The message to update
+ ///
+ /// \return return true if the message is updated successfully,
+ /// or else, return false.
+ ///
+ /// \note the function doesn't do any message validation check,
+ /// the user should make sure the message is valid.
+ bool update(const isc::dns::Message& msg);
+
+ /// \brief Update the rrset in the cache with the new one.
+ ///
+ /// local zone data and rrset cache will be updated together.
+ /// If the rrset doesn't exist in both of them, then the rrset
+ /// will be added into both of them.
+ ///
+ /// \param rrset_ptr The RRset to update
+ ///
+ /// \return return false, if the class of the parameter rrset is
+ /// allowed to be cached.
+ ///
+ /// \overload
+ ///
+ bool update(const isc::dns::ConstRRsetPtr& rrset_ptr);
+
+ /// \name Cache Serialization
+ //@{
+ /// \brief Dump the cache content to one file.
+ ///
+ /// \param file_name file to write to
+ ///
+ /// \todo It should can be dumped to one configured database.
+ void dump(const std::string& file_name);
+
+ /// \brief Load the cache from one file.
+ ///
+ /// \param file to load from
+ ///
+ /// \todo It should can be loaded from one configured database.
+ void load(const std::string& file_name);
+ //@}
+
+private:
+ /// \brief Returns the class-specific subcache
+ ///
+ /// \param cache_class the class to get the subcache for
+ /// \return The subcache, or NULL if there is no cache for this class
+ ResolverClassCache* getClassCache(const isc::dns::RRClass& cache_class) const;
+
+ /// The class-specific caches.
+ /// TODO: I think we can optimize for IN, and always have that
+ /// one directly available, use the vector for the rest?
+ std::vector<ResolverClassCache*> class_caches_;
+};
+
+} // namespace cache
+} // namespace isc
+
+#endif // __RESOLVER_CACHE_H
+
diff --git a/src/lib/cache/rrset_cache.cc b/src/lib/cache/rrset_cache.cc
new file mode 100644
index 0000000..f538320
--- /dev/null
+++ b/src/lib/cache/rrset_cache.cc
@@ -0,0 +1,102 @@
+// 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 <config.h>
+
+#include <string>
+#include "rrset_cache.h"
+#include <nsas/nsas_entry_compare.h>
+#include <nsas/hash_table.h>
+#include <nsas/hash_deleter.h>
+
+using namespace isc::nsas;
+using namespace isc::dns;
+using namespace std;
+
+namespace isc {
+namespace cache {
+
+RRsetCache::RRsetCache(uint32_t cache_size,
+ uint16_t rrset_class):
+ class_(rrset_class),
+ rrset_table_(new NsasEntryCompare<RRsetEntry>, cache_size),
+ rrset_lru_((3 * cache_size),
+ new HashDeleter<RRsetEntry>(rrset_table_))
+{
+}
+
+RRsetEntryPtr
+RRsetCache::lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype)
+{
+ const string entry_name = genCacheEntryName(qname, qtype);
+ RRsetEntryPtr entry_ptr = rrset_table_.get(HashKey(entry_name, RRClass(class_)));
+ 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 (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) {
+ if (entry_ptr->getTrustLevel() > level) {
+ // existed rrset entry is more authoritative, just return it
+ return (entry_ptr);
+ } else {
+ // 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
+void
+RRsetCache::dump(const std::string&) {
+ //TODO
+}
+
+void
+RRsetCache::load(const std::string&) {
+ //TODO
+}
+
+bool
+RRsetCache::resize(uint32_t) {
+ //TODO
+ return (true);
+}
+#endif
+
+} // namespace cache
+} // namespace isc
+
diff --git a/src/lib/cache/rrset_cache.h b/src/lib/cache/rrset_cache.h
new file mode 100644
index 0000000..104c2a5
--- /dev/null
+++ b/src/lib/cache/rrset_cache.h
@@ -0,0 +1,111 @@
+// 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 __RRSET_CACHE_H
+#define __RRSET_CACHE_H
+
+#include <cache/rrset_entry.h>
+#include <nsas/hash_table.h>
+#include <nsas/lru_list.h>
+
+using namespace isc::nsas;
+
+namespace isc {
+namespace cache {
+
+class RRsetEntry;
+
+/// \brief RRset Cache
+/// The object of RRsetCache represented the cache for class-specific
+/// RRsets.
+class RRsetCache{
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are intentionally
+ /// defined as private to make it uncopyable
+ //@{
+private:
+ RRsetCache(const RRsetCache&);
+ RRsetCache& operator=(const RRsetCache&);
+public:
+ /// \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() {
+ rrset_lru_.clear(); // Clear the rrset entries in the list.
+ }
+ //@}
+
+ /// \brief Look up rrset in cache.
+ ///
+ /// \param qname The query name to look up
+ /// \param qtype The query type
+ /// \return return the shared_ptr of rrset entry if it can be
+ /// found in the cache, or else, return NULL.
+ RRsetEntryPtr lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype);
+
+ /// \brief Update RRset Cache
+ /// Update the rrset entry in the cache with the new one.
+ /// If the rrset has expired or doesn't exist in the cache,
+ /// it will be added directly. It may be ingored if the new
+ /// rrset is not more authoritative than the old rrset in cache.
+ ///
+ /// \param rrset The new rrset used to update cache.
+ /// \param level trustworthiness of the rrset.
+ /// \return return the rrset entry in the cache, it may be the
+ /// new added rrset entry or existed one if it is not replaced.
+ RRsetEntryPtr update(const isc::dns::RRset& rrset,
+ const RRsetTrustLevel& level);
+
+#if 0
+ /// \brief Dump the rrset cache to specified file.
+ ///
+ /// \param file_name The file to write to
+ ///
+ /// \todo It should can be dumped to one configured database.
+ void dump(const std::string& file_name);
+
+ /// \brief Load the cache from one file.
+ ///
+ /// \param file_name The file to read from
+ ///
+ /// \todo It should can be loaded from one configured database.
+ void load(const std::string& file_name);
+
+ /// \brief Resize the size of rrset cache in runtime.
+ ///
+ /// \param The size to resize to
+ /// \return true
+ bool resize(uint32_t size);
+#endif
+
+ /// \short Protected memebers, so they can be accessed by tests.
+protected:
+ uint16_t class_; // The class of the rrset cache.
+ isc::nsas::HashTable<RRsetEntry> rrset_table_;
+ isc::nsas::LruList<RRsetEntry> rrset_lru_;
+};
+
+typedef boost::shared_ptr<RRsetCache> RRsetCachePtr;
+typedef boost::shared_ptr<const RRsetCache> ConstRRsetCachePtr;
+
+} // namespace cache
+} // namespace isc
+
+#endif // __RRSET_CACHE_H
+
diff --git a/src/lib/cache/rrset_copy.cc b/src/lib/cache/rrset_copy.cc
new file mode 100644
index 0000000..05b139a
--- /dev/null
+++ b/src/lib/cache/rrset_copy.cc
@@ -0,0 +1,38 @@
+// 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 "rrset_copy.h"
+
+using namespace isc::dns;
+
+namespace isc {
+namespace cache {
+
+void
+rrsetCopy(const isc::dns::RRset& src, isc::dns::RRset& dst) {
+ RdataIteratorPtr rdata_itor = src.getRdataIterator();
+ rdata_itor->first();
+ while(!rdata_itor->isLast()){
+ dst.addRdata(rdata_itor->getCurrent());
+ rdata_itor->next();
+ }
+
+ RRsetPtr rrsig = src.getRRsig();
+ if (rrsig != NULL){
+ dst.addRRsig(rrsig);
+ }
+}
+
+} // namespace cache
+} // namespace isc
diff --git a/src/lib/cache/rrset_copy.h b/src/lib/cache/rrset_copy.h
new file mode 100644
index 0000000..b6af8d6
--- /dev/null
+++ b/src/lib/cache/rrset_copy.h
@@ -0,0 +1,42 @@
+// 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 __RRSET_COPY_
+#define __RRSET_COPY_
+
+#include <dns/rrset.h>
+
+namespace isc {
+namespace cache {
+
+/// \brief RRset Copy Function
+///
+/// Adds all Rdatas and the RRsig in the source RRset to the target
+/// RRset
+///
+/// \param src RRset to copy from
+/// \param dst RRset to copy to
+///
+/// \note RRset class doesn't provide the interface for
+/// doing RRset copy. But in cache's code, sometime
+/// we have to do the copy.
+
+void
+rrsetCopy(const isc::dns::RRset& src, isc::dns::RRset& dst);
+
+} // namespace cache
+} // namespace isc
+
+#endif // __RRSET_COPY_
+
diff --git a/src/lib/cache/rrset_entry.cc b/src/lib/cache/rrset_entry.cc
new file mode 100644
index 0000000..c829956
--- /dev/null
+++ b/src/lib/cache/rrset_entry.cc
@@ -0,0 +1,66 @@
+// 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 <config.h>
+
+#include <dns/message.h>
+#include <nsas/nsas_entry.h>
+#include <nsas/fetchable.h>
+#include "rrset_entry.h"
+#include "rrset_copy.h"
+
+using namespace isc::dns;
+
+namespace isc {
+namespace cache {
+
+RRsetEntry::RRsetEntry(const isc::dns::RRset& rrset, const RRsetTrustLevel& level):
+ entry_name_(genCacheEntryName(rrset.getName(), rrset.getType())),
+ expire_time_(time(NULL) + rrset.getTTL().getValue()),
+ trust_level_(level),
+ rrset_(new RRset(rrset.getName(), rrset.getClass(), rrset.getType(), rrset.getTTL())),
+ hash_key_(HashKey(entry_name_, rrset_->getClass()))
+{
+ rrsetCopy(rrset, *(rrset_.get()));
+}
+
+isc::dns::RRsetPtr
+RRsetEntry::getRRset() {
+ updateTTL();
+ return (rrset_);
+}
+
+time_t
+RRsetEntry::getExpireTime() const {
+ return (expire_time_);
+}
+
+void
+RRsetEntry::updateTTL(){
+ uint32_t oldTTL = rrset_->getTTL().getValue();
+ if(oldTTL == 0) {
+ return;
+ }
+
+ uint32_t now = time(NULL);
+ uint32_t newTTL = now < expire_time_ ? (expire_time_ - now) : 0;
+
+ RRTTL ttl(newTTL);
+ rrset_->setTTL(ttl);
+}
+
+} // namespace cache
+} // namespace isc
+
+
diff --git a/src/lib/cache/rrset_entry.h b/src/lib/cache/rrset_entry.h
new file mode 100644
index 0000000..5fa8f2c
--- /dev/null
+++ b/src/lib/cache/rrset_entry.h
@@ -0,0 +1,135 @@
+// 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 __RRSET_ENTRY_H
+#define __RRSET_ENTRY_H
+
+#include <dns/rrset.h>
+#include <dns/message.h>
+#include <dns/rrttl.h>
+#include <nsas/nsas_entry.h>
+#include <nsas/fetchable.h>
+#include "cache_entry_key.h"
+
+using namespace isc::nsas;
+
+namespace isc {
+namespace cache {
+
+/// \enum RRset Trustworthiness
+/// For detail of RRset trustworthiness, please refer to
+/// RFC2181 section5.4.1.
+/// Bigger value is more trustworthy.
+enum RRsetTrustLevel {
+ /// Default trust for RRset.
+ RRSET_TRUST_DEFAULT = 0,
+ /// Additional information from non-authoritative answer.
+ RRSET_TRUST_ADDITIONAL_NONAA,
+ /// Data from the authority section of a non-authoritative answer
+ RRSET_TRUST_AUTHORITY_NONAA,
+ /// Additional information from an authoritative answer.
+ RRSET_TRUST_ADDITIONAL_AA,
+ /// Non-authoritative data from the answer section of authoritative
+ /// answers
+ RRSET_TRUST_NONAUTH_ANSWER_AA,
+ /// Data from the answer section of a non-authoritative answer.
+ RRSET_TRUST_ANSWER_NONAA,
+ /// Glue from a primary zone, or glue from a zone transfer.
+ RRSET_TRUST_PRIM_GLUE,
+ /// Data from the authority section of an authoritative answer.
+ RRSET_TRUST_AUTHORITY_AA,
+ /// Authoritative data included in the answer section of
+ /// an authoritative reply.
+ RRSET_TRUST_ANSWER_AA,
+ /// Data from a primary zone file, other than glue data.
+ RRSET_TRUST_PRIM_ZONE_NONGLUE
+};
+
+/// \brief RRset Entry
+/// The object of RRsetEntry represents one cached RRset.
+/// Each RRset entry may be refered using shared_ptr by several message
+/// entries.
+class RRsetEntry : public NsasEntry<RRsetEntry>
+{
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are intentionally
+ /// defined as private to make it uncopyable
+ //@{
+private:
+ RRsetEntry(const RRsetEntry&);
+ RRsetEntry& operator=(const RRsetEntry&);
+public:
+ /// \brief Constructor
+ /// \param rrset The RRset used to initialize the RRset entry.
+ /// \param level trustworthiness of the RRset.
+ RRsetEntry(const isc::dns::RRset& rrset, const RRsetTrustLevel& level);
+
+ /// The destructor.
+ ~RRsetEntry() {}
+ //@}
+
+ /// \brief Return a pointer to a generated RRset
+ ///
+ /// \return Pointer to the generated RRset
+ isc::dns::RRsetPtr getRRset();
+
+ /// \brief Get the expiration time of the RRset.
+ ///
+ /// \return The expiration time of the RRset
+ ///
+ /// \todo RRsig expiration processing
+ time_t getExpireTime() const;
+
+ /// \brief Get the ttl of the RRset.
+ ///
+ /// \return The TTL of the RRset
+ uint32_t getTTL() {
+ updateTTL();
+ return (rrset_->getTTL().getValue());
+ }
+
+ /// \brief Get the hash key
+ ///
+ /// \return return hash key
+ HashKey hashKey() const {
+ return (hash_key_);
+ }
+
+ /// \brief get RRset trustworthiness
+ ///
+ /// \return return the trust level
+ RRsetTrustLevel getTrustLevel() const {
+ return (trust_level_);
+ }
+private:
+ /// \brief Update TTL according to expiration time
+ void updateTTL();
+
+private:
+ std::string entry_name_; // The entry name for this rrset entry.
+ time_t expire_time_; // Expiration time of rrset.
+ RRsetTrustLevel trust_level_; // RRset trustworthiness.
+ boost::shared_ptr<isc::dns::RRset> rrset_;
+ HashKey hash_key_; // RRsetEntry hash key
+};
+
+typedef boost::shared_ptr<RRsetEntry> RRsetEntryPtr;
+
+} // namespace cache
+} // namespace isc
+
+#endif // __RRSET_ENTRY_H
+
diff --git a/src/lib/cache/tests/Makefile.am b/src/lib/cache/tests/Makefile.am
new file mode 100644
index 0000000..4763f55
--- /dev/null
+++ b/src/lib/cache/tests/Makefile.am
@@ -0,0 +1,80 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/cache -I$(top_builddir)/src/lib/cache
+AM_CPPFLAGS += -DTEST_DATA_SRCDIR=\"$(srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/cache/tests/testdata\"
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+
+AM_LDFLAGS = $(PTHREAD_LDFLAGS)
+if USE_STATIC_LINK
+AM_LDFLAGS += -static
+endif
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_CLANGPP
+# see ../Makefile.am
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
+run_unittests_SOURCES += rrset_entry_unittest.cc
+run_unittests_SOURCES += rrset_cache_unittest.cc
+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
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD = $(GTEST_LDADD)
+
+# NOTE: we may have to clean up this hack later (see the note in configure.ac)
+if NEED_LIBBOOST_THREAD
+run_unittests_LDADD += -lboost_thread
+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_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
+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/cache_test_messagefromfile.h b/src/lib/cache/tests/cache_test_messagefromfile.h
new file mode 100644
index 0000000..62e237c
--- /dev/null
+++ b/src/lib/cache/tests/cache_test_messagefromfile.h
@@ -0,0 +1,39 @@
+// 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 <vector>
+#include <dns/tests/unittest_util.h>
+#include <dns/buffer.h>
+#include <dns/message.h>
+
+using namespace isc;
+using namespace isc::dns;
+
+namespace {
+
+/// \brief Reads a Message from a data file
+///
+/// \param message Message to put the read data in
+/// \param datafile The file to read from
+void
+messageFromFile(Message& message, const char* datafile) {
+ std::vector<unsigned char> data;
+ UnitTestUtil::readWireData(datafile, data);
+
+ InputBuffer buffer(&data[0], data.size());
+ message.fromWire(buffer);
+}
+
+} // namespace
+
diff --git a/src/lib/cache/tests/cache_test_sectioncount.h b/src/lib/cache/tests/cache_test_sectioncount.h
new file mode 100644
index 0000000..537ca81
--- /dev/null
+++ b/src/lib/cache/tests/cache_test_sectioncount.h
@@ -0,0 +1,44 @@
+// 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 <vector>
+#include <dns/tests/unittest_util.h>
+#include <dns/buffer.h>
+#include <dns/message.h>
+
+using namespace isc;
+using namespace isc::dns;
+
+namespace {
+
+/// \brief Counts the number of rrsets in the given section
+///
+/// \param msg The message to count in
+/// \param section The section to count
+///
+/// \return The number of RRsets in the given section
+int
+sectionRRsetCount(Message& msg, Message::Section section) {
+ int count = 0;
+ for (RRsetIterator rrset_iter = msg.beginSection(section);
+ rrset_iter != msg.endSection(section);
+ ++rrset_iter) {
+ ++count;
+ }
+
+ return count;
+}
+
+} // namespace
+
diff --git a/src/lib/cache/tests/local_zone_data_unittest.cc b/src/lib/cache/tests/local_zone_data_unittest.cc
new file mode 100644
index 0000000..6877eae
--- /dev/null
+++ b/src/lib/cache/tests/local_zone_data_unittest.cc
@@ -0,0 +1,64 @@
+// 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 <config.h>
+#include <string>
+#include <gtest/gtest.h>
+#include <cache/local_zone_data.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include "cache_test_messagefromfile.h"
+
+using namespace isc::cache;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+
+class LocalZoneDataTest: public testing::Test {
+protected:
+ LocalZoneDataTest(): local_zone_data(1)
+ {
+ }
+
+ LocalZoneData local_zone_data;
+};
+
+TEST_F(LocalZoneDataTest, updateAndLookup) {
+ Message msg(Message::PARSE);
+ messageFromFile(msg, "message_fromWire3");
+ RRsetIterator rrset_iter = msg.beginSection(Message::SECTION_AUTHORITY);
+ Name name = (*rrset_iter)->getName();
+ RRType type = (*rrset_iter)->getType();
+
+ EXPECT_FALSE(local_zone_data.lookup(name, type));
+ local_zone_data.update((*(*rrset_iter).get()));
+ EXPECT_TRUE(local_zone_data.lookup(name, type));
+
+ // Test whether the old one is replaced
+ uint32_t ttl = (*rrset_iter)->getTTL().getValue();
+ // Make sure it is not zero
+ ASSERT_NE(ttl / 2, ttl);
+
+ RRsetPtr rrset_ptr = local_zone_data.lookup(name, type);
+ EXPECT_EQ(ttl, rrset_ptr->getTTL().getValue());
+
+ (*rrset_iter)->setTTL(RRTTL(ttl/2));
+
+ local_zone_data.update((*(*rrset_iter).get()));
+ rrset_ptr = local_zone_data.lookup(name, type);
+ EXPECT_EQ(ttl/2, rrset_ptr->getTTL().getValue());
+}
+
+}
diff --git a/src/lib/cache/tests/message_cache_unittest.cc b/src/lib/cache/tests/message_cache_unittest.cc
new file mode 100644
index 0000000..fc62e21
--- /dev/null
+++ b/src/lib/cache/tests/message_cache_unittest.cc
@@ -0,0 +1,162 @@
+// 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 <config.h>
+#include <string>
+#include <gtest/gtest.h>
+#include <dns/tests/unittest_util.h>
+#include <dns/buffer.h>
+#include "../message_cache.h"
+#include "../rrset_cache.h"
+#include "../resolver_cache.h"
+#include "cache_test_messagefromfile.h"
+
+using namespace isc::cache;
+using namespace isc;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+
+/// \brief Derived from base class to make it easy to test
+/// its internals.
+class DerivedMessageCache: public MessageCache {
+public:
+ 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() {
+ return message_lru_.size();
+ }
+};
+
+/// \brief Derived from base class to make it easy to test
+/// its internals.
+class DerivedRRsetCache: public RRsetCache {
+public:
+ DerivedRRsetCache(uint32_t cache_size, uint16_t rrset_class):
+ RRsetCache(cache_size, rrset_class)
+ {}
+
+ /// \brief Remove one rrset entry from rrset cache.
+ void removeRRsetEntry(Name& name, const RRType& type) {
+ const string entry_name = genCacheEntryName(name, type);
+ HashKey entry_key = HashKey(entry_name, RRClass(class_));
+ RRsetEntryPtr rrset_entry = rrset_table_.get(entry_key);
+ if (rrset_entry) {
+ rrset_lru_.remove(rrset_entry);
+ rrset_table_.remove(entry_key);
+ }
+ }
+};
+
+class MessageCacheTest: public testing::Test {
+public:
+ MessageCacheTest(): message_parse(Message::PARSE),
+ message_render(Message::RENDER)
+ {
+ uint16_t class_ = RRClass::IN().getCode();
+ rrset_cache_.reset(new DerivedRRsetCache(RRSET_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);
+
+ Message message_net(Message::PARSE);
+ messageFromFile(message_net, "message_fromWire2");
+ EXPECT_TRUE(message_cache_->update(message_net));
+ EXPECT_EQ(message_cache_->messages_count(), 2);
+
+ Name qname1("test.example.net.");
+ EXPECT_TRUE(message_cache_->lookup(qname1, RRType::A(), message_render));
+
+ // 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) {
+ messageFromFile(message_parse, "message_fromWire4");
+ EXPECT_TRUE(message_cache_->update(message_parse));
+
+ Name qname("example.com.");
+ EXPECT_TRUE(message_cache_->lookup(qname, RRType::SOA(), message_render));
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_AA));
+
+ Message new_msg(Message::PARSE);
+ messageFromFile(new_msg, "message_fromWire3");
+ 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_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
new file mode 100644
index 0000000..2ca33ec
--- /dev/null
+++ b/src/lib/cache/tests/message_entry_unittest.cc
@@ -0,0 +1,309 @@
+// 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 <dns/tests/unittest_util.h>
+#include <dns/message.h>
+#include <dns/buffer.h>
+#include "../message_entry.h"
+#include "../rrset_cache.h"
+#include "../resolver_cache.h"
+#include "cache_test_messagefromfile.h"
+#include "cache_test_sectioncount.h"
+
+using namespace isc::cache;
+using namespace isc;
+using namespace isc::dns;
+using namespace std;
+
+static uint32_t MAX_UINT32 = numeric_limits<uint32_t>::max();
+
+namespace {
+
+/// \brief Derived from base class to make it easy to test
+/// its internals.
+class DerivedMessageEntry: public MessageEntry {
+public:
+ DerivedMessageEntry(const isc::dns::Message& message,
+ 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.
+ void parseSectionForTest(const Message& msg,
+ const Message::Section& section,
+ uint32_t& smaller_ttl,
+ uint16_t& rrset_count)
+ {
+ parseSection(msg, section, smaller_ttl, rrset_count);
+ }
+
+ RRsetTrustLevel getRRsetTrustLevelForTest(const Message& message,
+ const RRsetPtr rrset,
+ const Message::Section& section)
+ {
+ return getRRsetTrustLevel(message, rrset, section);
+ }
+
+ bool getRRsetEntriesForTest(vector<RRsetEntryPtr> vec, time_t now) {
+ return getRRsetEntries(vec, now);
+ }
+
+ time_t getExpireTime() {
+ return expire_time_;
+ }
+
+};
+
+class MessageEntryTest: public testing::Test {
+public:
+ MessageEntryTest(): class_(1),
+ message_parse(Message::PARSE),
+ 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_, negative_soa_cache_);
+ uint32_t ttl = MAX_UINT32;
+ uint16_t rrset_count = 0;
+ message_entry.parseSectionForTest(message_parse, Message::SECTION_ANSWER, ttl, rrset_count);
+ EXPECT_EQ(ttl, 21600);
+ EXPECT_EQ(rrset_count, 1);
+
+ ttl = MAX_UINT32;
+ message_entry.parseSectionForTest(message_parse, Message::SECTION_AUTHORITY, ttl, rrset_count);
+ EXPECT_EQ(ttl, 21600);
+ EXPECT_EQ(rrset_count, 1);
+
+ ttl = MAX_UINT32;
+ message_entry.parseSectionForTest(message_parse, Message::SECTION_ADDITIONAL, ttl, rrset_count);
+ EXPECT_EQ(ttl, 10800);
+ EXPECT_EQ(rrset_count, 5);
+}
+
+TEST_F(MessageEntryTest, testGetRRsetTrustLevel_AA) {
+ messageFromFile(message_parse, "message_fromWire3");
+ 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,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA);
+
+ rrset_iter = message_parse.beginSection(Message::SECTION_AUTHORITY);
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_AUTHORITY);
+ EXPECT_EQ(level, RRSET_TRUST_AUTHORITY_AA);
+
+ rrset_iter = message_parse.beginSection(Message::SECTION_ADDITIONAL);
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ADDITIONAL);
+ EXPECT_EQ(level, RRSET_TRUST_ADDITIONAL_AA);
+}
+
+TEST_F(MessageEntryTest, testGetRRsetTrustLevel_NONAA) {
+ messageFromFile(message_parse, "message_fromWire4");
+ 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,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_NONAA);
+
+ rrset_iter = message_parse.beginSection(Message::SECTION_AUTHORITY);
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_AUTHORITY);
+ EXPECT_EQ(level, RRSET_TRUST_AUTHORITY_NONAA);
+
+ rrset_iter = message_parse.beginSection(Message::SECTION_ADDITIONAL);
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ADDITIONAL);
+ EXPECT_EQ(level, RRSET_TRUST_ADDITIONAL_NONAA);
+}
+
+TEST_F(MessageEntryTest, testGetRRsetTrustLevel_CNAME) {
+ messageFromFile(message_parse, "message_fromWire5");
+ 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,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA);
+
+ ++rrset_iter; // Get the rrset after the first cname rrset.
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_NONAA);
+}
+
+TEST_F(MessageEntryTest, testGetRRsetTrustLevel_CNAME_and_DNAME) {
+ messageFromFile(message_parse, "message_fromWire7");
+ 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,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA);
+ // All the left rrset are non-authoritative
+ ++rrset_iter;
+ while (rrset_iter != message_parse.endSection(Message::SECTION_ANSWER)) {
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ ++rrset_iter;
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_NONAA);
+ }
+}
+
+TEST_F(MessageEntryTest, testGetRRsetTrustLevel_DNAME_and_CNAME) {
+ messageFromFile(message_parse, "message_fromWire8");
+ 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,
+ Message::SECTION_ANSWER);
+ // Test the deepest DNAME
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA);
+ ++rrset_iter;
+ // Test the synchronized CNAME
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ ++rrset_iter;
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA);
+
+ ++rrset_iter;
+ // All the left rrset are non-authoritative
+ while (rrset_iter != message_parse.endSection(Message::SECTION_ANSWER)) {
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ ++rrset_iter;
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_NONAA);
+ }
+}
+
+TEST_F(MessageEntryTest, testGetRRsetTrustLevel_DNAME) {
+ messageFromFile(message_parse, "message_fromWire6");
+ 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,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA);
+
+ ++rrset_iter; // Get the rrset after the first dname rrset.
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA);
+
+ ++rrset_iter; // Get the second cname rrset
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_NONAA);
+}
+
+// We only test the expire_time of the message entry.
+// The test for genMessage() will make sure whether InitMessageEntry()
+// is right
+TEST_F(MessageEntryTest, testInitMessageEntry) {
+ messageFromFile(message_parse, "message_fromWire3");
+ 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);
+}
+
+TEST_F(MessageEntryTest, testGetRRsetEntries) {
+ messageFromFile(message_parse, "message_fromWire3");
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
+ vector<RRsetEntryPtr> vec;
+
+ // the time is bigger than the smallest expire time of
+ // the rrset in message.
+ time_t expire_time = time(NULL) + 10802;
+ EXPECT_FALSE(message_entry.getRRsetEntriesForTest(vec, expire_time));
+}
+
+TEST_F(MessageEntryTest, testGenMessage) {
+ messageFromFile(message_parse, "message_fromWire3");
+ 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_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));
+ EXPECT_EQ(5, sectionRRsetCount(msg, Message::SECTION_ADDITIONAL));
+
+ // Check the rrset in answer section.
+ EXPECT_EQ(1, msg.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(5, msg.getRRCount(Message::SECTION_AUTHORITY));
+ 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
new file mode 100644
index 0000000..7b686f5
--- /dev/null
+++ b/src/lib/cache/tests/resolver_cache_unittest.cc
@@ -0,0 +1,128 @@
+// 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 <config.h>
+#include <string>
+#include <gtest/gtest.h>
+#include <dns/rrset.h>
+#include "resolver_cache.h"
+#include "cache_test_messagefromfile.h"
+#include "cache_test_sectioncount.h"
+
+using namespace isc::cache;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+
+class ResolverCacheTest: public testing::Test {
+public:
+ ResolverCacheTest() {
+ vector<CacheSizeInfo> vec;
+ CacheSizeInfo class_in(RRClass::IN(), 100, 200);
+ CacheSizeInfo class_ch(RRClass::CH(), 100, 200);
+ vec.push_back(class_in);
+ vec.push_back(class_ch);
+ cache = new ResolverCache(vec);
+ }
+
+ ~ResolverCacheTest() {
+ delete cache;
+ }
+
+ ResolverCache* cache;
+};
+
+TEST_F(ResolverCacheTest, testUpdateMessage) {
+ Message msg(Message::PARSE);
+ messageFromFile(msg, "message_fromWire3");
+ cache->update(msg);
+
+ Name qname("example.com.");
+
+ msg.makeResponse();
+ EXPECT_TRUE(cache->lookup(qname, RRType::SOA(), RRClass::IN(), msg));
+ EXPECT_FALSE(msg.getHeaderFlag(Message::HEADERFLAG_AA));
+
+ // Test whether the old message can be updated
+ Message new_msg(Message::PARSE);
+ messageFromFile(new_msg, "message_fromWire4");
+ cache->update(new_msg);
+
+ new_msg.makeResponse();
+ EXPECT_TRUE(cache->lookup(qname, RRType::SOA(), RRClass::IN(), new_msg));
+ EXPECT_FALSE(new_msg.getHeaderFlag(Message::HEADERFLAG_AA));
+}
+
+TEST_F(ResolverCacheTest, testUpdateRRset) {
+ Message msg(Message::PARSE);
+ messageFromFile(msg, "message_fromWire3");
+ cache->update(msg);
+
+ Name qname("example.com.");
+
+ msg.makeResponse();
+ EXPECT_TRUE(cache->lookup(qname, RRType::SOA(), RRClass::IN(), msg));
+
+ Message except_msg(Message::RENDER);
+ EXPECT_THROW(cache->lookup(qname, RRType::SOA(), RRClass::IN(), except_msg),
+ MessageNoQuestionSection);
+
+ // Get one rrset in the message, then use it to
+ // update rrset cache-> Test whether the local zone
+ // data is updated.
+ RRsetIterator iter = msg.beginSection(Message::SECTION_AUTHORITY);
+ RRsetPtr rrset_ptr = *iter;
+ cache->update(rrset_ptr);
+
+ Message new_msg(Message::RENDER);
+ Question question(qname, RRClass::IN(), RRType::NS());
+ new_msg.addQuestion(question);
+ EXPECT_TRUE(cache->lookup(qname, RRType::NS(), RRClass::IN(), new_msg));
+ EXPECT_EQ(0, sectionRRsetCount(new_msg, Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, sectionRRsetCount(new_msg, Message::SECTION_ADDITIONAL));
+}
+
+TEST_F(ResolverCacheTest, testLookupUnsupportedClass) {
+ Message msg(Message::PARSE);
+ messageFromFile(msg, "message_fromWire3");
+ cache->update(msg);
+
+ Name qname("example.com.");
+
+ msg.makeResponse();
+ EXPECT_FALSE(cache->lookup(qname, RRType::SOA(), RRClass::CH(), msg));
+ EXPECT_FALSE(cache->lookup(qname, RRType::SOA(), RRClass::CH()));
+}
+
+TEST_F(ResolverCacheTest, testLookupClosestRRset) {
+ Message msg(Message::PARSE);
+ messageFromFile(msg, "message_fromWire3");
+ cache->update(msg);
+
+ Name qname("www.test.example.com.");
+
+ RRsetPtr rrset_ptr = cache->lookupDeepestNS(qname, RRClass::IN());
+ EXPECT_TRUE(rrset_ptr);
+ EXPECT_EQ(rrset_ptr->getName(), Name("example.com."));
+
+ rrset_ptr = cache->lookupDeepestNS(Name("example.com."), RRClass::IN());
+ EXPECT_TRUE(rrset_ptr);
+ EXPECT_EQ(rrset_ptr->getName(), Name("example.com."));
+
+ rrset_ptr = cache->lookupDeepestNS(Name("com."), RRClass::IN());
+ EXPECT_FALSE(rrset_ptr);
+}
+
+}
diff --git a/src/lib/cache/tests/rrset_cache_unittest.cc b/src/lib/cache/tests/rrset_cache_unittest.cc
new file mode 100644
index 0000000..b61f5c4
--- /dev/null
+++ b/src/lib/cache/tests/rrset_cache_unittest.cc
@@ -0,0 +1,127 @@
+// 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 <config.h>
+#include <string>
+#include <gtest/gtest.h>
+#include <cache/resolver_cache.h>
+#include <cache/cache_entry_key.h>
+#include <cache/rrset_entry.h>
+#include <cache/rrset_cache.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rrset.h>
+
+using namespace isc::cache;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+
+class RRsetCacheTest : public testing::Test {
+protected:
+ RRsetCacheTest():
+ 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_;
+};
+
+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());
+
+ // 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(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());
+
+ cache_.update(rrset1_, rrset_entry1_.getTrustLevel());
+ // The trust level should not be updated
+ 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/rrset_entry_unittest.cc b/src/lib/cache/tests/rrset_entry_unittest.cc
new file mode 100644
index 0000000..c7c3c6e
--- /dev/null
+++ b/src/lib/cache/tests/rrset_entry_unittest.cc
@@ -0,0 +1,106 @@
+// 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 <config.h>
+#include <string>
+#include <gtest/gtest.h>
+#include <cache/cache_entry_key.h>
+#include <cache/rrset_entry.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rrset.h>
+
+using namespace isc::cache;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+class GenCacheKeyTest: public testing::Test {
+};
+
+TEST_F(GenCacheKeyTest, genCacheEntryKey1) {
+ string name = "example.com.";
+ uint16_t type = 12;
+ string name_type = "example.com.12";
+
+ EXPECT_EQ(name_type, genCacheEntryName(name, type));
+}
+
+TEST_F(GenCacheKeyTest, genCacheEntryKey2) {
+ Name name("example.com");
+ RRType type(1234);
+ string keystr = "example.com.1234";
+ EXPECT_EQ(keystr, genCacheEntryName(name, type));
+}
+
+class DerivedRRsetEntry: public RRsetEntry {
+public:
+ DerivedRRsetEntry(const isc::dns::RRset& rrset, const RRsetTrustLevel& level) : RRsetEntry(rrset, level) {};
+
+ void updateTTLForTest() {
+
+ }
+};
+
+#define TEST_TTL 100
+class RRsetEntryTest : public ::testing::Test {
+protected:
+ RRsetEntryTest():
+ name("test.example.com"),
+ rrset(name, RRClass::IN(), RRType::A(), RRTTL(TEST_TTL)),
+ trust_level(RRSET_TRUST_ADDITIONAL_AA),
+ rrset_entry(rrset, trust_level)
+ {
+ }
+ Name name;
+ RRset rrset;
+ RRsetTrustLevel trust_level;
+ RRsetEntry rrset_entry;
+};
+
+TEST_F(RRsetEntryTest, constructor) {
+ EXPECT_EQ(trust_level, rrset_entry.getTrustLevel());
+ EXPECT_EQ(rrset.getName(), rrset_entry.getRRset()->getName());
+ EXPECT_EQ(rrset.getClass(), rrset_entry.getRRset()->getClass());
+ EXPECT_EQ(rrset.getType(), rrset_entry.getRRset()->getType());
+ EXPECT_EQ(rrset.getRdataCount(), rrset_entry.getRRset()->getRdataCount());
+}
+
+TEST_F(RRsetEntryTest, updateTTL) {
+ uint32_t ttl = rrset_entry.getTTL();
+ sleep(1);
+ // The TTL should be decreased
+ EXPECT_TRUE(rrset_entry.getTTL() < ttl);
+}
+
+TEST_F(RRsetEntryTest, TTLExpire) {
+ RRset exp_rrset(name, RRClass::IN(), RRType::A(), RRTTL(1));
+ RRsetEntry rrset_entry(exp_rrset, RRSET_TRUST_ANSWER_AA);
+ sleep(1);
+ uint32_t ttl = rrset_entry.getTTL();
+ EXPECT_LT(ttl, 1);
+ sleep(1);
+ ttl = rrset_entry.getTTL();
+ EXPECT_LT(ttl, 1);
+}
+
+TEST_F(RRsetEntryTest, getExpireTime){
+ uint32_t exp_time = time(NULL) + TEST_TTL;
+ EXPECT_EQ(exp_time, rrset_entry.getExpireTime());
+}
+
+} // namespace
+
diff --git a/src/lib/cache/tests/run_unittests.cc b/src/lib/cache/tests/run_unittests.cc
new file mode 100644
index 0000000..2c86581
--- /dev/null
+++ b/src/lib/cache/tests/run_unittests.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2009 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 <dns/tests/unittest_util.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::UnitTestUtil::addDataPath(TEST_DATA_SRCDIR);
+ isc::UnitTestUtil::addDataPath(TEST_DATA_BUILDDIR);
+
+ return (RUN_ALL_TESTS());
+}
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_fromWire1 b/src/lib/cache/tests/testdata/message_fromWire1
new file mode 100644
index 0000000..5b76e3f
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire1
@@ -0,0 +1,22 @@
+#
+# 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.com. IN A
+# Answer:
+# test.example.com. 3600 IN A 192.0.2.1
+# test.example.com. 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) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0001 0001
+# same name, fully compressed
+c0 0c
+# TTL=3600, A, IN, RDLENGTH=4, RDATA
+0001 0001 00000e10 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_fromWire2 b/src/lib/cache/tests/testdata/message_fromWire2
new file mode 100644
index 0000000..c8fddbd
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire2
@@ -0,0 +1,22 @@
+#
+# 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.net. IN A
+# Answer:
+# test.example.net. 3600 IN A 192.0.2.1
+# test.example.net. 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) n e t .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 6e 65 74 00
+0001 0001
+# same name, fully compressed
+c0 0c
+# TTL=3600, A, IN, RDLENGTH=4, RDATA
+0001 0001 00000e10 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_fromWire3 b/src/lib/cache/tests/testdata/message_fromWire3
new file mode 100644
index 0000000..f7b3a4a
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire3
@@ -0,0 +1,76 @@
+#
+# A simple DNS response message
+# ID = 0x0513
+# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=1, AUTHORITY COUNT=5, ADDITIONAL COUNT=7
+# Question: example.com. IN SOA
+# ANSWER:
+# ;; QUESTION SECTION:
+# ;example.com. IN SOA
+
+# ;; ANSWER SECTION:
+# example.com. 21600 IN SOA a.dns.example.com. root.example.com. 2009070811 7200 3600 2419200 21600
+
+# ;; AUTHORITY SECTION:
+# example.com. 21600 IN NS b.dns.example.com.
+# example.com. 21600 IN NS c.dns.example.com.
+# example.com. 21600 IN NS a.dns.example.com.
+# example.com. 21600 IN NS e.dns.example.com.
+# example.com. 21600 IN NS d.dns.example.com.
+
+# ;; ADDITIONAL SECTION:
+# a.dns.example.com. 21600 IN A 1.1.1.1
+# a.dns.example.com. 21600 IN A 2.2.2.2
+# b.dns.example.com. 21600 IN A 3.3.3.3
+# c.dns.example.com. 10800 IN A 4.4.4.4
+# d.dns.example.com. 43200 IN A 5.5.5.5
+# e.dns.example.com. 21600 IN A 7.7.7.7
+# e.dns.example.com. 21600 IN A 6.6.6.6
+
+0513 8500
+0001 0001 0005 0007
+#(7) e x a m p l e (3) c o m .
+ 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0006 0001
+# same name, fully compressed
+c0 0c
+# SOA IN TTL=6h RDLENGTH=35 rdata
+0006 0001 00005460 0023 01 61 03 64 6e 73 c0 0c 04 72 6f 6f 74 c0 0c 77 bf fc db 00 00 1c 20 00 00 0e 10 00 24 ea 00 00 00 54 60
+#Authority section begin
+c0 0c
+# NS IN TTL=6h RDLENGTH=4 b.dns.example.com.
+0002 0001 00005460 0004 01 62 c0 2b
+# NS IN TTL=6h c.dns.example.com.
+c0 0c
+0002 0001 00005460 00 04 01 63 c0 2b
+# NS IN a.dns.example.com.
+c0 0c
+0002 0001 00005460 00 02 c0 29
+# NS IN e.dns.example.com.
+c0 0c
+0002 0001 00005460 0004 01 65 c0 2b
+# NS IN d.dns.example.com.
+c0 0c
+0002 0001 00005460 0004 01 64 c0 2b
+# additional section begin
+# a.dns.example.com. A
+c0 29
+0001 0001 00005460 0004 01 01 01 01
+# a.dns.example.com. A
+c0 29
+0001 0001 00005460 0004 02 02 02 02
+#b.dns.example.com. A
+c0 58
+0001 0001 00002A30 0004 03 03 03 03
+#c.dns.example.com. A
+c0 68
+0001 0001 00005460 0004 04 04 04 04
+# d.dns.example.com. A
+c0 96
+0001 0001 0000A8C0 0004 05 05 05 05
+# e.dns.example.com. A
+c0 86
+0001 0001 00005460 0004 07 07 07 07
+# e.dns.example.com. A
+c0 86
+0001 0001 00005460 0004 06 06 06 06
diff --git a/src/lib/cache/tests/testdata/message_fromWire4 b/src/lib/cache/tests/testdata/message_fromWire4
new file mode 100644
index 0000000..251abd5
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire4
@@ -0,0 +1,80 @@
+# Note: This message is same with message_fromWire3, except
+# AA bit is not set. There should be a better way to
+# avoid the duplicated file by clear the AA bit flags
+# after reading the message from message_fromWire4.
+#
+# A simple DNS response message
+# ID = 0x0513
+# QR=1 (response), Opcode=0, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=1, AUTHORITY COUNT=5, ADDITIONAL COUNT=7
+# Question: example.com. IN SOA
+# ANSWER:
+# ;; QUESTION SECTION:
+# ;example.com. IN SOA
+
+# ;; ANSWER SECTION:
+# example.com. 21600 IN SOA a.dns.example.com. root.example.com. 2009070811 7200 3600 2419200 21600
+
+# ;; AUTHORITY SECTION:
+# example.com. 21600 IN NS b.dns.example.com.
+# example.com. 21600 IN NS c.dns.example.com.
+# example.com. 21600 IN NS a.dns.example.com.
+# example.com. 21600 IN NS e.dns.example.com.
+# example.com. 21600 IN NS d.dns.example.com.
+
+# ;; ADDITIONAL SECTION:
+# a.dns.example.com. 21600 IN A 1.1.1.1
+# a.dns.example.com. 21600 IN A 2.2.2.2
+# b.dns.example.com. 21600 IN A 3.3.3.3
+# c.dns.example.com. 10800 IN A 4.4.4.4
+# d.dns.example.com. 43200 IN A 5.5.5.5
+# e.dns.example.com. 21600 IN A 7.7.7.7
+# e.dns.example.com. 21600 IN A 6.6.6.6
+
+0513 8100
+0001 0001 0005 0007
+#(7) e x a m p l e (3) c o m .
+ 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0006 0001
+# same name, fully compressed
+c0 0c
+# SOA IN TTL=6h RDLENGTH=35 rdata
+0006 0001 00005460 0023 01 61 03 64 6e 73 c0 0c 04 72 6f 6f 74 c0 0c 77 bf fc db 00 00 1c 20 00 00 0e 10 00 24 ea 00 00 00 54 60
+#Authority section begin
+c0 0c
+# NS IN TTL=6h RDLENGTH=4 b.dns.example.com.
+0002 0001 00005460 0004 01 62 c0 2b
+# NS IN TTL=6h c.dns.example.com.
+c0 0c
+0002 0001 00005460 00 04 01 63 c0 2b
+# NS IN a.dns.example.com.
+c0 0c
+0002 0001 00005460 00 02 c0 29
+# NS IN e.dns.example.com.
+c0 0c
+0002 0001 00005460 0004 01 65 c0 2b
+# NS IN d.dns.example.com.
+c0 0c
+0002 0001 00005460 0004 01 64 c0 2b
+# additional section begin
+# a.dns.example.com. A
+c0 29
+0001 0001 00005460 0004 01 01 01 01
+# a.dns.example.com. A
+c0 29
+0001 0001 00005460 0004 02 02 02 02
+#b.dns.example.com. A
+c0 58
+0001 0001 00002A30 0004 03 03 03 03
+#c.dns.example.com. A
+c0 68
+0001 0001 00005460 0004 04 04 04 04
+# d.dns.example.com. A
+c0 96
+0001 0001 0000A8C0 0004 05 05 05 05
+# e.dns.example.com. A
+c0 86
+0001 0001 00005460 0004 07 07 07 07
+# e.dns.example.com. A
+c0 86
+0001 0001 00005460 0004 06 06 06 06
diff --git a/src/lib/cache/tests/testdata/message_fromWire5 b/src/lib/cache/tests/testdata/message_fromWire5
new file mode 100644
index 0000000..965f250
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire5
@@ -0,0 +1,36 @@
+#
+# A simple DNS response message
+# ID = 0x07b2
+# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=2, other COUNTS=0
+# Question: a.example.net. IN A
+# Answer:
+# ANSWER SECTION:
+# a.example.com. 21600 IN CNAME cname.example.com.
+# cname.example.com. 21600 IN A 1.1.1.1
+#
+# AUTHORITY SECTION:
+# example.com. 21600 IN NS a.dns.example.com.
+#
+# ADDITIONAL SECTION:
+# a.dns.example.com. 21600 IN A 1.1.1.1
+#
+07b2 8500
+0001 0002 0001 0001
+#(1) a (7) e x a m p l e (3) c o m .
+ 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# A IN
+0001 0001
+#
+c0 0c
+#CNAME IN TTL RDATA_LEN
+0005 0001 00005460 0008 05 63 6e 61 6d 65 c0 0e
+#
+c0 2b
+0001 0001 00005460 0004 01 01 01 01
+#
+c0 0e
+0002 0001 00005460 0008 01 61 03 64 6e 73 c0 0e
+#
+c0 4f
+0001 0001 00005460 0004 01 01 01 01
diff --git a/src/lib/cache/tests/testdata/message_fromWire6 b/src/lib/cache/tests/testdata/message_fromWire6
new file mode 100644
index 0000000..23684ba
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire6
@@ -0,0 +1,40 @@
+#
+# A simple DNS response message
+# ID = 0x005e
+# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=2, other COUNTS=0
+# Question: a.d.example.net. IN A
+# Answer:
+# ;; ANSWER SECTION:
+# d.example.com. 21600 IN DNAME dname.example.com.
+# a.d.example.com. 21600 IN CNAME a.dname.example.com.
+# a.dname.example.com. 21600 IN A 1.1.1.1
+#
+# ;; AUTHORITY SECTION:
+# example.com. 21600 IN NS a.dns.example.com.
+#
+# ;; ADDITIONAL SECTION:
+# a.dns.example.com. 21600 IN A 1.1.1.1
+#
+#
+005e 8500
+0001 0003 0001 0001
+#(1)a (1) b (7) e x a m p l e (3) c o m .
+ 01 61 01 64 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# A IN
+0001 0001
+#
+c0 0e
+0027 0001 00005460 0013 05 64 6e 61 6d 65 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+#
+c0 0c
+0005 0001 00005460 0004 01 61 c0 2d
+#
+c0 4c
+0001 0001 00005460 0004 01 01 01 01
+#
+c0 33
+0002 0001 00005460 0008 01 61 03 64 6e 73 c0 33
+#
+c0 6c
+0001 0001 00005460 0004 01 01 01 01
diff --git a/src/lib/cache/tests/testdata/message_fromWire7 b/src/lib/cache/tests/testdata/message_fromWire7
new file mode 100644
index 0000000..7b10b5d
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire7
@@ -0,0 +1,27 @@
+#
+# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1155
+# ;; flags: qr aa rd; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 0
+# ;; WARNING: recursion requested but not available
+#
+# ;; QUESTION SECTION:
+# ;test.example.com. IN A
+#
+# ;; ANSWER SECTION:
+# test.example.com. 21600 IN CNAME cname.a.dname.example.com.
+# dname.example.com. 21600 IN DNAME dname.example.org.
+# cname.a.dname.example.com. 21600 IN CNAME cname.a.dname.example.org.
+# dname.example.org. 21600 IN DNAME dname.example.org.
+# cname.a.dname.example.org. 21600 IN CNAME cname.a.dname.example.org.
+
+0424 8500
+ 00 01 00 05 00 00 00 00 04 74 65 73
+ 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 01
+ 00 01 c0 0c 00 05 00 01 00 00 54 60 00 10 05 63
+ 6e 61 6d 65 01 61 05 64 6e 61 6d 65 c0 11 c0 36
+ 00 27 00 01 00 00 54 60 00 13 05 64 6e 61 6d 65
+ 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 c0 2e 00
+ 05 00 01 00 00 54 60 00 0a 05 63 6e 61 6d 65 01
+ 61 c0 4a c0 4a 00 27 00 01 00 00 54 60 00 13 05
+ 64 6e 61 6d 65 07 65 78 61 6d 70 6c 65 03 6f 72
+ 67 00 c0 69 00 05 00 01 00 00 54 60 00 02 c0 69
+
diff --git a/src/lib/cache/tests/testdata/message_fromWire8 b/src/lib/cache/tests/testdata/message_fromWire8
new file mode 100644
index 0000000..bc9e144
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire8
@@ -0,0 +1,23 @@
+# A response includes multiple DNAME and synchronized CNAME records
+# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 900
+# ;; flags: qr aa rd; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 0
+# ;; WARNING: recursion requested but not available
+#
+# ;; QUESTION SECTION:
+# ;a.dname.example.com. IN NS
+#
+# ;; ANSWER SECTION:
+# dname.example.com. 21600 IN DNAME dname.example.org.
+# a.dname.example.com. 21600 IN CNAME a.dname.example.org.
+# dname.example.org. 21600 IN DNAME dname.example.org.
+# a.dname.example.org. 21600 IN CNAME a.dname.example.org.
+0384 8500
+ 00 01 00 04 00 00 00 00 01 61 05 64
+ 6e 61 6d 65 07 65 78 61 6d 70 6c 65 03 63 6f 6d
+ 00 00 02 00 01 c0 0e 00 27 00 01 00 00 54 60 00
+ 13 05 64 6e 61 6d 65 07 65 78 61 6d 70 6c 65 03
+ 6f 72 67 00 c0 0c 00 05 00 01 00 00 54 60 00 04
+ 01 61 c0 31 c0 31 00 27 00 01 00 00 54 60 00 13
+ 05 64 6e 61 6d 65 07 65 78 61 6d 70 6c 65 03 6f
+ 72 67 00 c0 50 00 05 00 01 00 00 54 60 00 02 c0
+ 50
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.cc b/src/lib/cc/data.cc
index f5e1cc5..6f7d4a2 100644
--- a/src/lib/cc/data.cc
+++ b/src/lib/cc/data.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config.h>
#include <cc/data.h>
@@ -62,84 +60,82 @@ Element::toWire(std::ostream& ss) const {
// installed files we define the methods here.
//
bool
-Element::getValue(long int& t UNUSED_PARAM) {
+Element::getValue(long int&) {
return (false);
}
bool
-Element::getValue(double& t UNUSED_PARAM) {
+Element::getValue(double&) {
return (false);
}
bool
-Element::getValue(bool& t UNUSED_PARAM) {
+Element::getValue(bool&) {
return (false);
}
bool
-Element::getValue(std::string& t UNUSED_PARAM) {
+Element::getValue(std::string&) {
return (false);
}
bool
-Element::getValue(std::vector<ConstElementPtr>& t UNUSED_PARAM) {
+Element::getValue(std::vector<ConstElementPtr>&) {
return (false);
}
bool
-Element::getValue(std::map<std::string, ConstElementPtr>& t UNUSED_PARAM) {
+Element::getValue(std::map<std::string, ConstElementPtr>&) {
return (false);
}
bool
-Element::setValue(const long int v UNUSED_PARAM) {
+Element::setValue(const long int) {
return (false);
}
bool
-Element::setValue(const double v UNUSED_PARAM) {
+Element::setValue(const double) {
return (false);
}
bool
-Element::setValue(const bool t UNUSED_PARAM) {
+Element::setValue(const bool) {
return (false);
}
bool
-Element::setValue(const std::string& v UNUSED_PARAM) {
+Element::setValue(const std::string&) {
return (false);
}
bool
-Element::setValue(const std::vector<ConstElementPtr>& v UNUSED_PARAM) {
+Element::setValue(const std::vector<ConstElementPtr>&) {
return (false);
}
bool
-Element::setValue(const std::map<std::string,
- ConstElementPtr>& v UNUSED_PARAM)
-{
+Element::setValue(const std::map<std::string, ConstElementPtr>&) {
return (false);
}
ConstElementPtr
-Element::get(const int i UNUSED_PARAM) const {
+Element::get(const int) const {
isc_throw(TypeError, "get(int) called on a non-list Element");
}
void
-Element::set(const size_t i UNUSED_PARAM, ConstElementPtr element UNUSED_PARAM) {
+Element::set(const size_t, ConstElementPtr) {
isc_throw(TypeError, "set(int, element) called on a non-list Element");
}
void
-Element::add(ConstElementPtr element UNUSED_PARAM) {
+Element::add(ConstElementPtr) {
isc_throw(TypeError, "add() called on a non-list Element");
}
void
-Element::remove(const int i UNUSED_PARAM) {
+Element::remove(const int) {
isc_throw(TypeError, "remove(int) called on a non-list Element");
}
@@ -149,36 +145,32 @@ Element::size() const {
}
ConstElementPtr
-Element::get(const std::string& name UNUSED_PARAM) const {
+Element::get(const std::string&) const {
isc_throw(TypeError, "get(string) called on a non-map Element");
}
void
-Element::set(const std::string& name UNUSED_PARAM,
- ConstElementPtr element UNUSED_PARAM)
-{
+Element::set(const std::string&, ConstElementPtr) {
isc_throw(TypeError, "set(name, element) called on a non-map Element");
}
void
-Element::remove(const std::string& name UNUSED_PARAM) {
+Element::remove(const std::string&) {
isc_throw(TypeError, "remove(string) called on a non-map Element");
}
bool
-Element::contains(const std::string& name UNUSED_PARAM) const {
+Element::contains(const std::string&) const {
isc_throw(TypeError, "contains(string) called on a non-map Element");
}
ConstElementPtr
-Element::find(const std::string& identifier UNUSED_PARAM) const {
+Element::find(const std::string&) const {
isc_throw(TypeError, "find(string) called on a non-map Element");
}
bool
-Element::find(const std::string& identifier UNUSED_PARAM,
- ConstElementPtr t UNUSED_PARAM) const
-{
+Element::find(const std::string&, ConstElementPtr) const {
return (false);
}
@@ -725,7 +717,7 @@ Element::fromWire(const std::string& s) {
}
ElementPtr
-Element::fromWire(std::stringstream& in, int length) {
+Element::fromWire(std::stringstream& in, int) {
//
// Check protocol version
//
diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h
index 1acfcad..0a363f4 100644
--- a/src/lib/cc/data.h
+++ b/src/lib/cc/data.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef _ISC_DATA_H
#define _ISC_DATA_H 1
@@ -224,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
@@ -317,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.
@@ -550,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.cc b/src/lib/cc/session.cc
index 7915862..e911a86 100644
--- a/src/lib/cc/session.cc
+++ b/src/lib/cc/session.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config.h>
#include <cc/session_config.h>
@@ -171,7 +169,7 @@ SessionImpl::readData(void* data, size_t datalen) {
asio::async_read(socket_, asio::buffer(data, datalen),
boost::bind(&setResult, &read_result, _1));
asio::deadline_timer timer(socket_.io_service());
-
+
if (getTimeout() != 0) {
timer.expires_from_now(boost::posix_time::milliseconds(getTimeout()));
timer.async_wait(boost::bind(&setResult, &timer_result, _1));
diff --git a/src/lib/cc/session.h b/src/lib/cc/session.h
index 37080cf..e91dd76 100644
--- a/src/lib/cc/session.h
+++ b/src/lib/cc/session.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef _ISC_SESSION_H
#define _ISC_SESSION_H 1
@@ -101,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/cc/tests/data_unittests.cc b/src/lib/cc/tests/data_unittests.cc
index cc3e121..2536682 100644
--- a/src/lib/cc/tests/data_unittests.cc
+++ b/src/lib/cc/tests/data_unittests.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <gtest/gtest.h>
#include <boost/foreach.hpp>
#include <boost/assign/std/vector.hpp>
diff --git a/src/lib/cc/tests/run_unittests.cc b/src/lib/cc/tests/run_unittests.cc
index 9aee35f..0908071 100644
--- a/src/lib/cc/tests/run_unittests.cc
+++ b/src/lib/cc/tests/run_unittests.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <gtest/gtest.h>
int
diff --git a/src/lib/cc/tests/session_unittests.cc b/src/lib/cc/tests/session_unittests.cc
index 45c7ad3..5f6e595 100644
--- a/src/lib/cc/tests/session_unittests.cc
+++ b/src/lib/cc/tests/session_unittests.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: data_unittests.cc 1899 2010-05-21 12:03:59Z jelte $
-
#include <config.h>
// for some IPC/network system calls in asio/detail/pipe_select_interrupter.hpp
@@ -76,7 +74,7 @@ public:
}
void
- acceptHandler(const asio::error_code& error UNUSED_PARAM) {
+ acceptHandler(const asio::error_code&) const {
}
void
diff --git a/src/lib/config/ccsession.cc b/src/lib/config/ccsession.cc
index 8d5edaf..69621a4 100644
--- a/src/lib/config/ccsession.cc
+++ b/src/lib/config/ccsession.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
//
// todo: generalize this and make it into a specific API for all modules
// to use (i.e. connect to cc, send config and commands, get config,
@@ -135,8 +133,6 @@ createCommand(const std::string& command, ConstElementPtr arg) {
return (cmd);
}
-/// Returns "" and empty ElementPtr() if this does not
-/// look like a command
std::string
parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
if (command &&
@@ -149,7 +145,7 @@ parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
if (cmd->size() > 1) {
arg = cmd->get(1);
} else {
- arg = ElementPtr();
+ arg = Element::createMap();
}
return (cmd->get(0)->stringValue());
} else {
@@ -174,10 +170,10 @@ ModuleCCSession::readModuleSpecification(const std::string& filename) {
try {
module_spec = moduleSpecFromFile(file, true);
- } catch (JSONError pe) {
+ } catch (const JSONError& pe) {
cout << "Error parsing module specification file: " << pe.what() << endl;
exit(1);
- } catch (ModuleSpecError dde) {
+ } catch (const ModuleSpecError& dde) {
cout << "Error reading module specification file: " << dde.what() << endl;
exit(1);
}
@@ -257,7 +253,7 @@ ModuleCCSession::handleConfigUpdate(ConstElementPtr new_config) {
ElementPtr errors = Element::createList();
if (!config_handler_) {
answer = createAnswer(1, module_name_ + " does not have a config handler");
- } else if (!module_specification_.validate_config(new_config, false,
+ } else if (!module_specification_.validateConfig(new_config, false,
errors)) {
std::stringstream ss;
ss << "Error in config validation: ";
@@ -286,6 +282,51 @@ ModuleCCSession::hasQueuedMsgs() const {
return (session_.hasQueuedMsgs());
}
+ConstElementPtr
+ModuleCCSession::checkConfigUpdateCommand(const std::string& target_module,
+ ConstElementPtr arg)
+{
+ if (target_module == module_name_) {
+ return (handleConfigUpdate(arg));
+ } else {
+ // ok this update is not for us, if we have this module
+ // in our remote config list, update that
+ updateRemoteConfig(target_module, arg);
+ // we're not supposed to answer to this, so return
+ return (ElementPtr());
+ }
+}
+
+ConstElementPtr
+ModuleCCSession::checkModuleCommand(const std::string& cmd_str,
+ const std::string& target_module,
+ ConstElementPtr arg) const
+{
+ if (target_module == module_name_) {
+ if (command_handler_) {
+ ElementPtr errors = Element::createList();
+ if (module_specification_.validateCommand(cmd_str,
+ arg,
+ errors)) {
+ return (command_handler_(cmd_str, arg));
+ } else {
+ std::stringstream ss;
+ ss << "Error in command validation: ";
+ BOOST_FOREACH(ConstElementPtr error,
+ errors->listValue()) {
+ ss << error->stringValue();
+ }
+ return (createAnswer(3, ss.str()));
+ }
+ } else {
+ return (createAnswer(1,
+ "Command given but no "
+ "command handler for module"));
+ }
+ }
+ return (ElementPtr());
+}
+
int
ModuleCCSession::checkCommand() {
ConstElementPtr cmd, routing, data;
@@ -302,23 +343,9 @@ ModuleCCSession::checkCommand() {
std::string cmd_str = parseCommand(arg, data);
std::string target_module = routing->get("group")->stringValue();
if (cmd_str == "config_update") {
- if (target_module == module_name_) {
- answer = handleConfigUpdate(arg);
- } else {
- // ok this update is not for us, if we have this module
- // in our remote config list, update that
- updateRemoteConfig(target_module, arg);
- // we're not supposed to answer to this, so return
- return (0);
- }
+ answer = checkConfigUpdateCommand(target_module, arg);
} else {
- if (target_module == module_name_) {
- if (command_handler_) {
- answer = command_handler_(cmd_str, arg);
- } else {
- answer = createAnswer(1, "Command given but no command handler for module");
- }
- }
+ answer = checkModuleCommand(cmd_str, target_module, arg);
}
} catch (const CCSessionError& re) {
// TODO: Once we have logging and timeouts, we should not
diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h
index be42da4..d8d1eeb 100644
--- a/src/lib/config/ccsession.h
+++ b/src/lib/config/ccsession.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __CCSESSION_H
#define __CCSESSION_H 1
@@ -91,11 +89,36 @@ isc::data::ConstElementPtr createCommand(const std::string& command,
/// \brief Parses the given command into a string containing the actual
/// command and an ElementPtr containing the optional argument.
///
+/// Raises a CCSessionError if this is not a well-formed command
+///
+/// Example code: (command_message is a ConstElementPtr that is
+/// passed here)
+/// \code
+/// ElementPtr command_message = Element::fromJSON("{ \"command\": [\"foo\", { \"bar\": 123 } ] }");
+/// try {
+/// ConstElementPtr args;
+/// std::string command_str = parseCommand(args, command_message);
+/// if (command_str == "foo") {
+/// std::cout << "The command 'foo' was given" << std::endl;
+/// if (args->contains("bar")) {
+/// std::cout << "It had argument name 'bar' set, which has"
+/// << "value "
+/// << args->get("bar")->intValue();
+/// }
+/// } else {
+/// std::cout << "Unknown command '" << command_str << std::endl;
+/// }
+/// } catch (CCSessionError cse) {
+/// std::cerr << "Bad command in CC Session: "
+/// << cse.what() << std::endl;
+/// }
+/// \endcode
+///
/// \param arg This value will be set to the ElementPtr pointing to
-/// the argument, or to an empty ElementPtr if there was none.
+/// the argument, or to an empty Map (ElementPtr) if there was none.
/// \param command The command message containing the command (as made
/// by createCommand()
-/// \return The command string
+/// \return The command name
std::string parseCommand(isc::data::ConstElementPtr& arg,
isc::data::ConstElementPtr command);
@@ -244,6 +267,15 @@ private:
isc::data::ConstElementPtr handleConfigUpdate(
isc::data::ConstElementPtr new_config);
+ isc::data::ConstElementPtr checkConfigUpdateCommand(
+ const std::string& target_module,
+ isc::data::ConstElementPtr arg);
+
+ isc::data::ConstElementPtr checkModuleCommand(
+ const std::string& cmd_str,
+ const std::string& target_module,
+ isc::data::ConstElementPtr arg) const;
+
isc::data::ConstElementPtr(*config_handler_)(
isc::data::ConstElementPtr new_config);
isc::data::ConstElementPtr(*command_handler_)(
diff --git a/src/lib/config/config_data.cc b/src/lib/config/config_data.cc
index 505a87e..1fa37c1 100644
--- a/src/lib/config/config_data.cc
+++ b/src/lib/config/config_data.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config/config_data.h>
#include <boost/foreach.hpp>
@@ -128,11 +126,6 @@ spec_name_list(ElementPtr result, ConstElementPtr spec_part,
if (recurse && list_el->get("item_type")->stringValue() == "map") {
spec_name_list(result, list_el->get("map_item_spec"), new_prefix, recurse);
} else {
- if (list_el->get("item_type")->stringValue() == "map" ||
- list_el->get("item_type")->stringValue() == "list"
- ) {
- new_prefix += "/";
- }
result->add(Element::create(new_prefix));
}
}
diff --git a/src/lib/config/config_data.h b/src/lib/config/config_data.h
index 423f0dd..29a5b5f 100644
--- a/src/lib/config/config_data.h
+++ b/src/lib/config/config_data.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __CONFIG_DATA_H
#define __CONFIG_DATA_H 1
diff --git a/src/lib/config/module_spec.cc b/src/lib/config/module_spec.cc
index e42cf32..1621fe3 100644
--- a/src/lib/config/module_spec.cc
+++ b/src/lib/config/module_spec.cc
@@ -121,7 +121,7 @@ void
check_module_specification(ConstElementPtr def) {
try {
check_data_specification(def);
- } catch (TypeError te) {
+ } catch (const TypeError& te) {
throw ModuleSpecError(te.what());
}
}
@@ -177,17 +177,47 @@ ModuleSpec::getModuleDescription() const {
}
bool
-ModuleSpec::validate_config(ConstElementPtr data, const bool full) const {
+ModuleSpec::validateConfig(ConstElementPtr data, const bool full) const {
ConstElementPtr spec = module_specification->find("config_data");
- return (validate_spec_list(spec, data, full, ElementPtr()));
+ return (validateSpecList(spec, data, full, ElementPtr()));
}
bool
-ModuleSpec::validate_config(ConstElementPtr data, const bool full,
+ModuleSpec::validateCommand(const std::string& command,
+ ConstElementPtr args,
+ ElementPtr errors) const
+{
+ if (args->getType() != Element::map) {
+ errors->add(Element::create("args for command " +
+ command + " is not a map"));
+ return (false);
+ }
+
+ ConstElementPtr commands_spec = module_specification->find("commands");
+ if (!commands_spec) {
+ // there are no commands according to the spec.
+ errors->add(Element::create("The given module has no commands"));
+ return (false);
+ }
+
+ BOOST_FOREACH(ConstElementPtr cur_command, commands_spec->listValue()) {
+ if (cur_command->get("command_name")->stringValue() == command) {
+ return (validateSpecList(cur_command->get("command_args"),
+ args, true, errors));
+ }
+ }
+
+ // this command is unknown
+ errors->add(Element::create("Unknown command " + command));
+ return (false);
+}
+
+bool
+ModuleSpec::validateConfig(ConstElementPtr data, const bool full,
ElementPtr errors) const
{
ConstElementPtr spec = module_specification->find("config_data");
- return (validate_spec_list(spec, data, full, errors));
+ return (validateSpecList(spec, data, full, errors));
}
ModuleSpec
@@ -264,7 +294,7 @@ check_type(ConstElementPtr spec, ConstElementPtr element) {
}
bool
-ModuleSpec::validate_item(ConstElementPtr spec, ConstElementPtr data,
+ModuleSpec::validateItem(ConstElementPtr spec, ConstElementPtr data,
const bool full, ElementPtr errors) const
{
if (!check_type(spec, data)) {
@@ -286,14 +316,14 @@ ModuleSpec::validate_item(ConstElementPtr spec, ConstElementPtr data,
return (false);
}
if (list_spec->get("item_type")->stringValue() == "map") {
- if (!validate_item(list_spec, list_el, full, errors)) {
+ if (!validateItem(list_spec, list_el, full, errors)) {
return (false);
}
}
}
}
if (data->getType() == Element::map) {
- if (!validate_spec_list(spec->get("map_item_spec"), data, full, errors)) {
+ if (!validateSpecList(spec->get("map_item_spec"), data, full, errors)) {
return (false);
}
}
@@ -302,7 +332,7 @@ ModuleSpec::validate_item(ConstElementPtr spec, ConstElementPtr data,
// spec is a map with item_name etc, data is a map
bool
-ModuleSpec::validate_spec(ConstElementPtr spec, ConstElementPtr data,
+ModuleSpec::validateSpec(ConstElementPtr spec, ConstElementPtr data,
const bool full, ElementPtr errors) const
{
std::string item_name = spec->get("item_name")->stringValue();
@@ -311,7 +341,7 @@ ModuleSpec::validate_spec(ConstElementPtr spec, ConstElementPtr data,
data_el = data->get(item_name);
if (data_el) {
- if (!validate_item(spec, data_el, full, errors)) {
+ if (!validateItem(spec, data_el, full, errors)) {
return (false);
}
} else {
@@ -327,16 +357,38 @@ ModuleSpec::validate_spec(ConstElementPtr spec, ConstElementPtr data,
// spec is a list of maps, data is a map
bool
-ModuleSpec::validate_spec_list(ConstElementPtr spec, ConstElementPtr data,
+ModuleSpec::validateSpecList(ConstElementPtr spec, ConstElementPtr data,
const bool full, ElementPtr errors) const
{
+ bool validated = true;
std::string cur_item_name;
BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
- if (!validate_spec(cur_spec_el, data, full, errors)) {
- return (false);
+ if (!validateSpec(cur_spec_el, data, full, errors)) {
+ validated = false;
}
}
- return (true);
+
+ typedef std::pair<std::string, ConstElementPtr> maptype;
+
+ BOOST_FOREACH(maptype m, data->mapValue()) {
+ bool found = false;
+ // 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));
+ }
+ }
+ }
+ }
+
+ return (validated);
}
}
diff --git a/src/lib/config/module_spec.h b/src/lib/config/module_spec.h
index 02445a6..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,25 +88,70 @@ 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 validate_config(isc::data::ConstElementPtr data,
+ bool validateConfig(isc::data::ConstElementPtr data,
const bool full = false) const;
+ /// Validates the arguments for the given command
+ ///
+ /// This checks the command and argument against the
+ /// specification in the module's .spec file.
+ ///
+ /// A command is considered valid if:
+ /// - it is known (the 'command' string must have an entry in
+ /// the specification)
+ /// - the args is a MapElement
+ /// - args contains all mandatory arguments
+ /// - args does not contain unknown arguments
+ /// - all arguments in args match their specification
+ /// If all of these are true, this function returns \c true
+ /// If not, this method returns \c false
+ ///
+ /// Example usage:
+ /// \code
+ /// ElementPtr errors = Element::createList();
+ /// if (module_specification_.validateCommand(cmd_str,
+ /// arg,
+ /// errors)) {
+ /// std::cout << "Command is valid" << std::endl;
+ /// } else {
+ /// std::cout << "Command is invalid: " << std::endl;
+ /// BOOST_FOREACH(ConstElementPtr error,
+ /// errors->listValue()) {
+ /// std::cout << error->stringValue() << std::endl;
+ /// }
+ /// }
+ /// \endcode
+ ///
+ /// \param command The command to validate the arguments for
+ /// \param args A dict containing the command parameters
+ /// \param errors An ElementPtr pointing to a ListElement. Any
+ /// errors that are found are added as
+ /// StringElements to this list
+ /// \return true if the command is known and the parameters are correct
+ /// false otherwise
+ bool validateCommand(const std::string& command,
+ isc::data::ConstElementPtr args,
+ isc::data::ElementPtr errors) const;
+
+
/// errors must be of type ListElement
- bool validate_config(isc::data::ConstElementPtr data, const bool full,
+ bool validateConfig(isc::data::ConstElementPtr data, const bool full,
isc::data::ElementPtr errors) const;
private:
- bool validate_item(isc::data::ConstElementPtr spec,
- isc::data::ConstElementPtr data,
- const bool full,
- isc::data::ElementPtr errors) const;
- bool validate_spec(isc::data::ConstElementPtr spec,
+ bool validateItem(isc::data::ConstElementPtr spec,
+ isc::data::ConstElementPtr data,
+ const bool full,
+ isc::data::ElementPtr errors) const;
+ bool validateSpec(isc::data::ConstElementPtr spec,
isc::data::ConstElementPtr data,
const bool full,
isc::data::ElementPtr errors) const;
- bool validate_spec_list(isc::data::ConstElementPtr spec,
+ bool validateSpecList(isc::data::ConstElementPtr spec,
isc::data::ConstElementPtr data,
const bool full,
isc::data::ElementPtr errors) const;
diff --git a/src/lib/config/tests/Makefile.am b/src/lib/config/tests/Makefile.am
index a149806..0cebdbf 100644
--- a/src/lib/config/tests/Makefile.am
+++ b/src/lib/config/tests/Makefile.am
@@ -4,10 +4,6 @@ AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
-# see src/lib/cc/Makefile.am for -Wno-unused-parameter
-if USE_GXX
-AM_CXXFLAGS += -Wno-unused-parameter
-endif
if USE_STATIC_LINK
AM_LDFLAGS = -static
diff --git a/src/lib/config/tests/ccsession_unittests.cc b/src/lib/config/tests/ccsession_unittests.cc
index de6949f..43663bd 100644
--- a/src/lib/config/tests/ccsession_unittests.cc
+++ b/src/lib/config/tests/ccsession_unittests.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: module_spec_unittests.cc 1321 2010-03-11 10:17:03Z jelte $
-
#include <config.h>
#include <gtest/gtest.h>
@@ -33,7 +31,7 @@ using namespace std;
namespace {
std::string
-ccspecfile(const std::string name) {
+ccspecfile(const std::string& name) {
return (std::string(TEST_DATA_PATH) + "/" + name);
}
@@ -137,7 +135,7 @@ TEST_F(CCSessionTest, parseCommand) {
cmd = parseCommand(arg, el("{ \"command\": [ \"my_command\" ] }"));
EXPECT_EQ("my_command", cmd);
- EXPECT_TRUE(isNull(arg));
+ EXPECT_EQ(*arg, *Element::createMap());
cmd = parseCommand(arg, el("{ \"command\": [ \"my_command\", 1 ] }"));
EXPECT_EQ("my_command", cmd);
@@ -188,13 +186,13 @@ ConstElementPtr my_config_handler(ConstElementPtr new_config) {
}
ConstElementPtr my_command_handler(const std::string& command,
- ConstElementPtr arg UNUSED_PARAM)
+ ConstElementPtr arg)
{
if (command == "good_command") {
return (createAnswer());
} else if (command == "command_with_arg") {
- if (arg) {
- if (arg->getType() == Element::integer) {
+ if (arg->contains("number")) {
+ if (arg->get("number")->getType() == Element::integer) {
return (createAnswer(0, el("2")));
} else {
return (createAnswer(1, "arg bad type"));
@@ -235,10 +233,10 @@ TEST_F(CCSessionTest, checkCommand) {
// client will ask for config
session.getMessages()->add(createAnswer(0, el("{}")));
- EXPECT_FALSE(session.haveSubscription("Spec2", "*"));
- ModuleCCSession mccs(ccspecfile("spec2.spec"), session, my_config_handler,
+ EXPECT_FALSE(session.haveSubscription("Spec29", "*"));
+ ModuleCCSession mccs(ccspecfile("spec29.spec"), session, my_config_handler,
my_command_handler);
- EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
+ EXPECT_TRUE(session.haveSubscription("Spec29", "*"));
EXPECT_EQ(2, session.getMsgQueue()->size());
ConstElementPtr msg;
@@ -252,19 +250,19 @@ TEST_F(CCSessionTest, checkCommand) {
EXPECT_EQ(0, result);
// not a command, should be ignored
- session.addMessage(el("1"), "Spec2", "*");
+ session.addMessage(el("1"), "Spec29", "*");
result = mccs.checkCommand();
EXPECT_EQ(0, result);
-
- session.addMessage(el("{ \"command\": [ \"good_command\" ] }"), "Spec2",
+ session.addMessage(el("{ \"command\": [ \"good_command\" ] }"), "Spec29",
"*");
+
result = mccs.checkCommand();
EXPECT_EQ(1, session.getMsgQueue()->size());
msg = session.getFirstMessage(group, to);
EXPECT_EQ("{ \"result\": [ 0 ] }", msg->str());
EXPECT_EQ(0, result);
- session.addMessage(el("{ \"command\": \"bad_command\" }"), "Spec2", "*");
+ session.addMessage(el("{ \"command\": \"bad_command\" }"), "Spec29", "*");
result = mccs.checkCommand();
EXPECT_EQ(1, session.getMsgQueue()->size());
msg = session.getFirstMessage(group, to);
@@ -272,53 +270,69 @@ TEST_F(CCSessionTest, checkCommand) {
EXPECT_EQ(0, result);
session.addMessage(el("{ \"command\": [ \"bad_command\" ] }"),
- "Spec2", "*");
+ "Spec29", "*");
result = mccs.checkCommand();
EXPECT_EQ(1, session.getMsgQueue()->size());
msg = session.getFirstMessage(group, to);
EXPECT_EQ("{ \"result\": [ 1, \"bad command\" ] }", msg->str());
EXPECT_EQ(0, result);
- session.addMessage(el("{ \"command\": [ \"command_with_arg\", 1 ] }"),
- "Spec2", "*");
+ session.addMessage(el("{ \"command\": [ \"command_with_arg\", {\"number\": 1} ] }"),
+ "Spec29", "*");
result = mccs.checkCommand();
EXPECT_EQ(1, session.getMsgQueue()->size());
msg = session.getFirstMessage(group, to);
EXPECT_EQ("{ \"result\": [ 0, 2 ] }", msg->str());
EXPECT_EQ(0, result);
- session.addMessage(el("{ \"command\": [ \"command_with_arg\" ] }"), "Spec2", "*");
+ session.addMessage(el("{ \"command\": [ \"command_with_arg\" ] }"), "Spec29", "*");
result = mccs.checkCommand();
EXPECT_EQ(1, session.getMsgQueue()->size());
msg = session.getFirstMessage(group, to);
EXPECT_EQ("{ \"result\": [ 1, \"arg missing\" ] }", msg->str());
EXPECT_EQ(0, result);
- session.addMessage(el("{ \"command\": [ \"command_with_arg\", \"asdf\" ] }"), "Spec2", "*");
+ session.addMessage(el("{ \"command\": [ \"command_with_arg\", \"asdf\" ] }"), "Spec29", "*");
result = mccs.checkCommand();
EXPECT_EQ(1, session.getMsgQueue()->size());
msg = session.getFirstMessage(group, to);
- EXPECT_EQ("{ \"result\": [ 1, \"arg bad type\" ] }", msg->str());
+ EXPECT_EQ("{ \"result\": [ 3, \"Error in command validation: args for command command_with_arg is not a map\" ] }", msg->str());
EXPECT_EQ(0, result);
mccs.setCommandHandler(NULL);
- session.addMessage(el("{ \"command\": [ \"whatever\" ] }"), "Spec2", "*");
+ session.addMessage(el("{ \"command\": [ \"whatever\" ] }"), "Spec29", "*");
result = mccs.checkCommand();
EXPECT_EQ(1, session.getMsgQueue()->size());
msg = session.getFirstMessage(group, to);
EXPECT_EQ("{ \"result\": [ 1, \"Command given but no command handler for module\" ] }", msg->str());
EXPECT_EQ(0, result);
+}
+
+// A heuristic workaround for clang++: It doesn't seem to compile the whole
+// test, probably due to its length. Dividing the tests into two separate
+// test cases seems to work.
+TEST_F(CCSessionTest, checkCommand2) {
+ session.getMessages()->add(createAnswer(0, el("{}")));
+ EXPECT_FALSE(session.haveSubscription("Spec29", "*"));
+ ModuleCCSession mccs(ccspecfile("spec29.spec"), session, my_config_handler,
+ my_command_handler);
+ EXPECT_TRUE(session.haveSubscription("Spec29", "*"));
+ ConstElementPtr msg;
+ std::string group, to;
+ // checked above, drop em
+ msg = session.getFirstMessage(group, to);
+ msg = session.getFirstMessage(group, to);
EXPECT_EQ(1, mccs.getValue("item1")->intValue());
- session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": 2 } ] }"), "Spec2", "*");
- result = mccs.checkCommand();
+ session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": 2 } ] }"), "Spec29", "*");
+ int result = mccs.checkCommand();
EXPECT_EQ(1, session.getMsgQueue()->size());
msg = session.getFirstMessage(group, to);
EXPECT_EQ("{ \"result\": [ 0 ] }", msg->str());
EXPECT_EQ(0, result);
EXPECT_EQ(2, mccs.getValue("item1")->intValue());
- session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": \"asdf\" } ] }"), "Spec2", "*");
+ session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": \"asdf\" } ] }"), "Spec29", "*");
result = mccs.checkCommand();
EXPECT_EQ(1, session.getMsgQueue()->size());
msg = session.getFirstMessage(group, to);
@@ -326,7 +340,7 @@ TEST_F(CCSessionTest, checkCommand) {
EXPECT_EQ(0, result);
EXPECT_EQ(2, mccs.getValue("item1")->intValue());
- session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": 5 } ] }"), "Spec2", "*");
+ session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": 5 } ] }"), "Spec29", "*");
result = mccs.checkCommand();
EXPECT_EQ(1, session.getMsgQueue()->size());
msg = session.getFirstMessage(group, to);
@@ -387,9 +401,9 @@ TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
// client will ask for config
session.getMessages()->add(createAnswer(0, el("{ }")));
- EXPECT_FALSE(session.haveSubscription("Spec2", "*"));
- ModuleCCSession mccs(ccspecfile("spec2.spec"), session, my_config_handler, my_command_handler);
- EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
+ EXPECT_FALSE(session.haveSubscription("Spec29", "*"));
+ ModuleCCSession mccs(ccspecfile("spec29.spec"), session, my_config_handler, my_command_handler);
+ EXPECT_TRUE(session.haveSubscription("Spec29", "*"));
EXPECT_EQ(2, session.getMsgQueue()->size());
ConstElementPtr msg;
@@ -404,7 +418,7 @@ TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
msg = session.getFirstMessage(group, to);
// Check if commands for the module are handled
- session.addMessage(el("{ \"command\": [ \"good_command\" ] }"), "Spec2", "*");
+ session.addMessage(el("{ \"command\": [ \"good_command\" ] }"), "Spec29", "*");
int result = mccs.checkCommand();
EXPECT_EQ(1, session.getMsgQueue()->size());
msg = session.getFirstMessage(group, to);
diff --git a/src/lib/config/tests/config_data_unittests.cc b/src/lib/config/tests/config_data_unittests.cc
index 30605a1..974812d 100644
--- a/src/lib/config/tests/config_data_unittests.cc
+++ b/src/lib/config/tests/config_data_unittests.cc
@@ -13,8 +13,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <gtest/gtest.h>
#include <config/tests/data_def_unittests_config.h>
@@ -120,8 +118,8 @@ TEST(ConfigData, getItemList) {
ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
ConfigData cd = ConfigData(spec2);
- EXPECT_EQ("[ \"item1\", \"item2\", \"item3\", \"item4\", \"item5/\", \"item6/\" ]", cd.getItemList()->str());
- EXPECT_EQ("[ \"item1\", \"item2\", \"item3\", \"item4\", \"item5/\", \"item6/value1\", \"item6/value2\" ]", cd.getItemList("", true)->str());
+ EXPECT_EQ("[ \"item1\", \"item2\", \"item3\", \"item4\", \"item5\", \"item6\" ]", cd.getItemList()->str());
+ EXPECT_EQ("[ \"item1\", \"item2\", \"item3\", \"item4\", \"item5\", \"item6/value1\", \"item6/value2\" ]", cd.getItemList("", true)->str());
EXPECT_EQ("[ \"item6/value1\", \"item6/value2\" ]", cd.getItemList("item6")->str());
}
@@ -129,12 +127,12 @@ TEST(ConfigData, getFullConfig) {
ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
ConfigData cd = ConfigData(spec2);
- EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5/\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
+ EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
ElementPtr my_config = Element::fromJSON("{ \"item1\": 2 }");
cd.setLocalConfig(my_config);
- EXPECT_EQ("{ \"item1\": 2, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5/\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
+ EXPECT_EQ("{ \"item1\": 2, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
ElementPtr my_config2 = Element::fromJSON("{ \"item6\": { \"value1\": \"a\" } }");
cd.setLocalConfig(my_config2);
- EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5/\": [ \"a\", \"b\" ], \"item6/value1\": \"a\", \"item6/value2\": None }", cd.getFullConfig()->str());
+ EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"a\", \"item6/value2\": None }", cd.getFullConfig()->str());
}
diff --git a/src/lib/config/tests/fake_session.cc b/src/lib/config/tests/fake_session.cc
index 51ce7a4..29744c7 100644
--- a/src/lib/config/tests/fake_session.cc
+++ b/src/lib/config/tests/fake_session.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: session.cc 1250 2010-03-09 22:52:15Z jinmei $
-
#include <config.h>
#include <stdint.h>
@@ -87,17 +85,15 @@ FakeSession::disconnect() {
}
void
-FakeSession::startRead(boost::function<void()> read_callback UNUSED_PARAM) {
+FakeSession::startRead(boost::function<void()>) {
}
void
-FakeSession::establish(const char* socket_file) {
+FakeSession::establish(const char*) {
}
bool
-FakeSession::recvmsg(ConstElementPtr& msg, bool nonblock UNUSED_PARAM,
- int seq UNUSED_PARAM)
-{
+FakeSession::recvmsg(ConstElementPtr& msg, bool, int) {
//cout << "[XX] client asks for message " << endl;
if (messages_ &&
messages_->getType() == Element::list &&
@@ -111,10 +107,7 @@ FakeSession::recvmsg(ConstElementPtr& msg, bool nonblock UNUSED_PARAM,
}
bool
-FakeSession::recvmsg(ConstElementPtr& env, ConstElementPtr& msg,
- bool nonblock UNUSED_PARAM,
- int seq UNUSED_PARAM)
-{
+FakeSession::recvmsg(ConstElementPtr& env, ConstElementPtr& msg, bool, int) {
//cout << "[XX] client asks for message and env" << endl;
env = ElementPtr();
if (messages_ &&
@@ -172,7 +165,7 @@ FakeSession::unsubscribe(std::string group, std::string instance) {
int
FakeSession::group_sendmsg(ConstElementPtr msg, std::string group,
- std::string to, std::string instance UNUSED_PARAM)
+ std::string to, std::string)
{
//cout << "[XX] client sends message: " << msg << endl;
//cout << "[XX] to: " << group << " . " << instance << "." << to << endl;
diff --git a/src/lib/config/tests/fake_session.h b/src/lib/config/tests/fake_session.h
index 1540294..ac8e291 100644
--- a/src/lib/config/tests/fake_session.h
+++ b/src/lib/config/tests/fake_session.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: session.h 1250 2010-03-09 22:52:15Z jinmei $
-
#ifndef _ISC_FAKESESSION_H
#define _ISC_FAKESESSION_H 1
@@ -63,7 +61,7 @@ public:
virtual int reply(isc::data::ConstElementPtr envelope,
isc::data::ConstElementPtr newmsg);
virtual bool hasQueuedMsgs() const;
- virtual void setTimeout(size_t milliseconds) {}
+ virtual void setTimeout(size_t) {}
virtual size_t getTimeout() const { return (0); }
isc::data::ConstElementPtr getFirstMessage(std::string& group,
std::string& to) const;
diff --git a/src/lib/config/tests/module_spec_unittests.cc b/src/lib/config/tests/module_spec_unittests.cc
index 070b783..1b43350 100644
--- a/src/lib/config/tests/module_spec_unittests.cc
+++ b/src/lib/config/tests/module_spec_unittests.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <gtest/gtest.h>
#include <config/module_spec.h>
@@ -25,12 +23,12 @@
using namespace isc::data;
using namespace isc::config;
-std::string specfile(const std::string name) {
+std::string specfile(const std::string& name) {
return (std::string(TEST_DATA_PATH) + "/" + name);
}
void
-module_spec_error(const std::string& file,
+moduleSpecError(const std::string& file,
const std::string& error1,
const std::string& error2 = "",
const std::string& error3 = "")
@@ -38,7 +36,7 @@ module_spec_error(const std::string& file,
EXPECT_THROW(moduleSpecFromFile(specfile(file)), ModuleSpecError);
try {
ModuleSpec dd = moduleSpecFromFile(specfile(file));
- } catch (ModuleSpecError dde) {
+ } catch (const ModuleSpecError& dde) {
std::string ddew = dde.what();
EXPECT_EQ(error1 + error2 + error3, ddew);
}
@@ -52,7 +50,7 @@ TEST(ModuleSpec, ReadingSpecfiles) {
->stringValue(), "Spec1");
dd = moduleSpecFromFile(specfile("spec2.spec"));
EXPECT_EQ(dd.getFullSpec()->get("config_data")->size(), 6);
- module_spec_error("doesnotexist",
+ moduleSpecError("doesnotexist",
"Error opening ",
specfile("doesnotexist"),
": No such file or directory");
@@ -81,69 +79,65 @@ TEST(ModuleSpec, ReadingSpecfiles) {
}
TEST(ModuleSpec, SpecfileItems) {
- module_spec_error("spec3.spec",
+ moduleSpecError("spec3.spec",
"item_name missing in { \"item_default\": 1, \"item_optional\": false, \"item_type\": \"integer\" }");
- module_spec_error("spec4.spec",
+ moduleSpecError("spec4.spec",
"item_type missing in { \"item_default\": 1, \"item_name\": \"item1\", \"item_optional\": false }");
- module_spec_error("spec5.spec",
+ moduleSpecError("spec5.spec",
"item_optional missing in { \"item_default\": 1, \"item_name\": \"item1\", \"item_type\": \"integer\" }");
- module_spec_error("spec6.spec",
+ moduleSpecError("spec6.spec",
"item_default missing in { \"item_name\": \"item1\", \"item_optional\": false, \"item_type\": \"integer\" }");
- module_spec_error("spec9.spec",
+ moduleSpecError("spec9.spec",
"item_default not of type integer");
- module_spec_error("spec10.spec",
+ moduleSpecError("spec10.spec",
"item_default not of type real");
- module_spec_error("spec11.spec",
+ moduleSpecError("spec11.spec",
"item_default not of type boolean");
- module_spec_error("spec12.spec",
+ moduleSpecError("spec12.spec",
"item_default not of type string");
- module_spec_error("spec13.spec",
+ moduleSpecError("spec13.spec",
"item_default not of type list");
- module_spec_error("spec14.spec",
+ moduleSpecError("spec14.spec",
"item_default not of type map");
- module_spec_error("spec15.spec",
+ moduleSpecError("spec15.spec",
"badname is not a valid type name");
}
TEST(ModuleSpec, SpecfileConfigData) {
- module_spec_error("spec7.spec",
+ moduleSpecError("spec7.spec",
"module_name missing in { }");
- module_spec_error("spec8.spec",
+ moduleSpecError("spec8.spec",
"No module_spec in specification");
- module_spec_error("spec16.spec",
+ moduleSpecError("spec16.spec",
"config_data is not a list of elements");
- module_spec_error("spec21.spec",
+ moduleSpecError("spec21.spec",
"commands is not a list of elements");
}
TEST(ModuleSpec, SpecfileCommands) {
- module_spec_error("spec17.spec",
+ moduleSpecError("spec17.spec",
"command_name missing in { \"command_args\": [ { \"item_default\": \"\", \"item_name\": \"message\", \"item_optional\": false, \"item_type\": \"string\" } ], \"command_description\": \"Print the given message to stdout\" }");
- module_spec_error("spec18.spec",
+ moduleSpecError("spec18.spec",
"command_args missing in { \"command_description\": \"Print the given message to stdout\", \"command_name\": \"print_message\" }");
- module_spec_error("spec19.spec",
+ moduleSpecError("spec19.spec",
"command_args not of type list");
- module_spec_error("spec20.spec",
+ moduleSpecError("spec20.spec",
"somethingbad is not a valid type name");
-/*
- module_spec_error("spec22.spec",
- "");
-*/
}
bool
-data_test(const ModuleSpec& dd, const std::string& data_file_name) {
+dataTest(const ModuleSpec& dd, const std::string& data_file_name) {
std::ifstream data_file;
data_file.open(specfile(data_file_name).c_str());
ConstElementPtr data = Element::fromJSON(data_file, data_file_name);
data_file.close();
- return (dd.validate_config(data));
+ return (dd.validateConfig(data));
}
bool
-data_test_with_errors(const ModuleSpec& dd, const std::string& data_file_name,
+dataTestWithErrors(const ModuleSpec& dd, const std::string& data_file_name,
ElementPtr errors)
{
std::ifstream data_file;
@@ -152,22 +146,68 @@ data_test_with_errors(const ModuleSpec& dd, const std::string& data_file_name,
ConstElementPtr data = Element::fromJSON(data_file, data_file_name);
data_file.close();
- return (dd.validate_config(data, true, errors));
+ return (dd.validateConfig(data, true, errors));
}
TEST(ModuleSpec, DataValidation) {
ModuleSpec dd = moduleSpecFromFile(specfile("spec22.spec"));
- EXPECT_TRUE(data_test(dd, "data22_1.data"));
- EXPECT_FALSE(data_test(dd, "data22_2.data"));
- EXPECT_FALSE(data_test(dd, "data22_3.data"));
- EXPECT_FALSE(data_test(dd, "data22_4.data"));
- EXPECT_FALSE(data_test(dd, "data22_5.data"));
- EXPECT_TRUE(data_test(dd, "data22_6.data"));
- EXPECT_TRUE(data_test(dd, "data22_7.data"));
- EXPECT_FALSE(data_test(dd, "data22_8.data"));
+ EXPECT_TRUE(dataTest(dd, "data22_1.data"));
+ EXPECT_FALSE(dataTest(dd, "data22_2.data"));
+ EXPECT_FALSE(dataTest(dd, "data22_3.data"));
+ EXPECT_FALSE(dataTest(dd, "data22_4.data"));
+ EXPECT_FALSE(dataTest(dd, "data22_5.data"));
+ EXPECT_TRUE(dataTest(dd, "data22_6.data"));
+ EXPECT_TRUE(dataTest(dd, "data22_7.data"));
+ 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(data_test_with_errors(dd, "data22_8.data", errors));
+ EXPECT_FALSE(dataTestWithErrors(dd, "data22_8.data", errors));
EXPECT_EQ("[ \"Type mismatch\" ]", errors->str());
+
+ errors = Element::createList();
+ EXPECT_FALSE(dataTestWithErrors(dd, "data22_9.data", errors));
+ EXPECT_EQ("[ \"Unknown item value_does_not_exist\" ]", errors->str());
+}
+
+TEST(ModuleSpec, CommandValidation) {
+ ModuleSpec dd = moduleSpecFromFile(specfile("spec2.spec"));
+ ConstElementPtr arg = Element::fromJSON("{}");
+ ElementPtr errors = Element::createList();
+
+ EXPECT_TRUE(dd.validateCommand("shutdown", arg, errors));
+ EXPECT_EQ(errors->size(), 0);
+
+ errors = Element::createList();
+ EXPECT_FALSE(dd.validateCommand("unknowncommand", arg, errors));
+ EXPECT_EQ(errors->size(), 1);
+ EXPECT_EQ(errors->get(0)->stringValue(), "Unknown command unknowncommand");
+
+ errors = Element::createList();
+ EXPECT_FALSE(dd.validateCommand("print_message", arg, errors));
+ EXPECT_EQ(errors->size(), 1);
+ EXPECT_EQ(errors->get(0)->stringValue(), "Non-optional value missing");
+
+ errors = Element::createList();
+ arg = Element::fromJSON("{ \"message\": \"Hello\" }");
+ EXPECT_TRUE(dd.validateCommand("print_message", arg, errors));
+ EXPECT_EQ(errors->size(), 0);
+
+ errors = Element::createList();
+ arg = Element::fromJSON("{ \"message\": \"Hello\", \"unknown_second_arg\": 1 }");
+ EXPECT_FALSE(dd.validateCommand("print_message", arg, errors));
+ EXPECT_EQ(errors->size(), 1);
+ EXPECT_EQ(errors->get(0)->stringValue(), "Unknown item unknown_second_arg");
+
+ errors = Element::createList();
+ arg = Element::fromJSON("{ \"message\": 1 }");
+ EXPECT_FALSE(dd.validateCommand("print_message", arg, errors));
+ EXPECT_EQ(errors->size(), 1);
+ EXPECT_EQ(errors->get(0)->stringValue(), "Type mismatch");
+
}
diff --git a/src/lib/config/tests/run_unittests.cc b/src/lib/config/tests/run_unittests.cc
index 9aee35f..0908071 100644
--- a/src/lib/config/tests/run_unittests.cc
+++ b/src/lib/config/tests/run_unittests.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <gtest/gtest.h>
int
diff --git a/src/lib/config/tests/testdata/Makefile.am b/src/lib/config/tests/testdata/Makefile.am
index b8217d7..94c087d 100644
--- a/src/lib/config/tests/testdata/Makefile.am
+++ b/src/lib/config/tests/testdata/Makefile.am
@@ -20,6 +20,8 @@ EXTRA_DIST += data22_5.data
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
@@ -48,3 +50,4 @@ EXTRA_DIST += spec25.spec
EXTRA_DIST += spec26.spec
EXTRA_DIST += spec27.spec
EXTRA_DIST += spec28.spec
+EXTRA_DIST += spec29.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/config/tests/testdata/data22_8.data b/src/lib/config/tests/testdata/data22_8.data
index 33a5d1a..8bc0aa9 100644
--- a/src/lib/config/tests/testdata/data22_8.data
+++ b/src/lib/config/tests/testdata/data22_8.data
@@ -5,5 +5,6 @@
"value4": "foo",
"value5": [ 1, 2, 3 ],
"value6": { "v61": "bar", "v62": true },
- "value8": [ { "a": "d" }, { "a": 1 } ]
+ "value8": [ { "a": "d" }, { "a": 1 } ],
+ "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
}
diff --git a/src/lib/config/tests/testdata/data22_9.data b/src/lib/config/tests/testdata/data22_9.data
new file mode 100644
index 0000000..f115194
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_9.data
@@ -0,0 +1,11 @@
+{
+ "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 } },
+ "value_does_not_exist": 1
+}
diff --git a/src/lib/config/tests/testdata/spec22.spec b/src/lib/config/tests/testdata/spec22.spec
index be6d51f..cccd77b 100644
--- a/src/lib/config/tests/testdata/spec22.spec
+++ b/src/lib/config/tests/testdata/spec22.spec
@@ -1,6 +1,6 @@
{
"module_spec": {
- "module_name": "Spec2",
+ "module_name": "Spec22",
"config_data": [
{ "item_name": "value1",
"item_type": "integer",
@@ -81,7 +81,7 @@
{ "item_name": "value9",
"item_type": "map",
"item_optional": false,
- "item_default": {},
+ "item_default": { "v91": "def", "v92": {} },
"map_item_spec": [
{ "item_name": "v91",
"item_type": "string",
diff --git a/src/lib/config/tests/testdata/spec29.spec b/src/lib/config/tests/testdata/spec29.spec
new file mode 100644
index 0000000..c7ae5f8
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec29.spec
@@ -0,0 +1,35 @@
+{
+ "module_spec": {
+ "module_name": "Spec29",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ],
+ "commands": [
+ {
+ "command_name": "good_command",
+ "command_description": "A good command",
+ "command_args": []
+ },
+ {
+ "command_name": "bad_command",
+ "command_description": "A bad command",
+ "command_args": []
+ },
+ {
+ "command_name": "command_with_arg",
+ "command_description": "A command with an (integer) argument",
+ "command_args": [ {
+ "item_name": "number",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 1
+ } ]
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index c164a87..adb1d41 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -15,3 +15,8 @@ libdatasrc_la_SOURCES += static_datasrc.h static_datasrc.cc
libdatasrc_la_SOURCES += sqlite3_datasrc.h sqlite3_datasrc.cc
libdatasrc_la_SOURCES += query.h query.cc
libdatasrc_la_SOURCES += cache.h cache.cc
+libdatasrc_la_SOURCES += rbtree.h
+libdatasrc_la_SOURCES += zonetable.h zonetable.cc
+libdatasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc
+libdatasrc_la_SOURCES += zone.h
+libdatasrc_la_SOURCES += result.h
diff --git a/src/lib/datasrc/cache.cc b/src/lib/datasrc/cache.cc
index 64bffb3..6fff754 100644
--- a/src/lib/datasrc/cache.cc
+++ b/src/lib/datasrc/cache.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <map>
diff --git a/src/lib/datasrc/cache.h b/src/lib/datasrc/cache.h
index a30d632..054912c 100644
--- a/src/lib/datasrc/cache.h
+++ b/src/lib/datasrc/cache.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __CACHE_H
#define __CACHE_H
@@ -81,7 +79,7 @@ class HotCacheImpl;
/// from the tail of the list. This operation is not locked. BIND 10
/// does not currently use threads, but if it ever does (or if libdatasrc
/// is ever used by a threaded application), this will need to be
-//revisited.
+/// revisited.
class HotCache {
private:
/// \name Static definitions
@@ -164,15 +162,15 @@ public:
///
/// Retrieves a record from the cache matching the given
/// query-tuple. Returns true if one is found. If it is a
- /// posiitve cache entry, then 'rrset' is set to the cached
+ /// positive cache entry, then 'rrset' is set to the cached
/// RRset. For both positive and negative cache entries, 'flags'
/// is set to the query response flags. The cache entry is
/// then promoted to the head of the LRU queue. (NOTE: Because
/// of this, "retrieve" cannot be implemented as a const method.)
///
- /// \param name The query name
- /// \param rrclass The query class
- /// \param rrtype The query type
+ /// \param qname The query name
+ /// \param qclass The query class
+ /// \param qtype The query type
/// \param rrset Returns the RRset found, if any, to the caller
/// \param flags Returns the flags, if any, to the caller
///
diff --git a/src/lib/datasrc/data_source.cc b/src/lib/datasrc/data_source.cc
index 6ac1850..c9819bb 100644
--- a/src/lib/datasrc/data_source.cc
+++ b/src/lib/datasrc/data_source.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config.h>
#include <cassert>
@@ -50,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 {
@@ -95,22 +115,22 @@ getAdditional(Query& q, ConstRRsetPtr rrset) {
}
RdataIteratorPtr it = rrset->getRdataIterator();
- for (it->first(); !it->isLast(); it->next()) {
+ for (; !it->isLast(); it->next()) {
const Rdata& rd(it->getCurrent());
if (rrset->getType() == RRType::NS()) {
const generic::NS& ns = dynamic_cast<const generic::NS&>(rd);
q.tasks().push(QueryTaskPtr(
new QueryTask(q, ns.getNSName(),
- Section::ADDITIONAL(),
+ Message::SECTION_ADDITIONAL,
QueryTask::GLUE_QUERY,
- QueryTask::GETADDITIONAL)));
+ QueryTask::GETADDITIONAL)));
} else if (rrset->getType() == RRType::MX()) {
const generic::MX& mx = dynamic_cast<const generic::MX&>(rd);
q.tasks().push(QueryTaskPtr(
new QueryTask(q, mx.getMXName(),
- Section::ADDITIONAL(),
+ Message::SECTION_ADDITIONAL,
QueryTask::NOGLUE_QUERY,
- QueryTask::GETADDITIONAL)));
+ QueryTask::GETADDITIONAL)));
}
}
}
@@ -123,7 +143,6 @@ synthesizeCname(QueryTaskPtr task, RRsetPtr rrset, RRsetList& target) {
// More than one DNAME RR in the RRset is illegal, so we only have
// to process the first one.
- it->first();
if (it->isLast()) {
return;
}
@@ -132,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();
@@ -152,7 +171,6 @@ chaseCname(Query& q, QueryTaskPtr task, RRsetPtr rrset) {
// More than one CNAME RR in the RRset is illegal, so we only have
// to process the first one.
- it->first();
if (it->isLast()) {
return;
}
@@ -165,7 +183,7 @@ chaseCname(Query& q, QueryTaskPtr task, RRsetPtr rrset) {
q.tasks().push(QueryTaskPtr(
new QueryTask(q, dynamic_cast<const generic::CNAME&>
(it->getCurrent()).getCname(),
- task->qtype, Section::ANSWER(),
+ task->qtype, Message::SECTION_ANSWER,
QueryTask::FOLLOWCNAME)));
}
@@ -193,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);
}
@@ -473,16 +504,19 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
// checking first to ensure that there isn't already an RRset with
// the same name and type.
inline void
-addToMessage(Query& q, const Section& sect, RRsetPtr rrset,
+addToMessage(Query& q, const Message::Section sect, RRsetPtr rrset,
bool no_dnssec = false)
{
Message& m = q.message();
if (no_dnssec) {
- if (rrset->getType() == RRType::RRSIG() || !m.hasRRset(sect, rrset)) {
+ if (rrset->getType() == RRType::RRSIG() ||
+ !m.hasRRset(sect, rrset->getName(), rrset->getClass(),
+ rrset->getType())) {
m.addRRset(sect, rrset, false);
}
} else {
- if (!m.hasRRset(sect, rrset)) {
+ if (!m.hasRRset(sect, rrset->getName(), rrset->getClass(),
+ rrset->getType())) {
m.addRRset(sect, rrset, q.wantDnssec());
}
}
@@ -498,7 +532,7 @@ copyAuth(Query& q, RRsetList& auth) {
if (rrset->getType() == RRType::DS() && !q.wantDnssec()) {
continue;
}
- addToMessage(q, Section::AUTHORITY(), rrset);
+ addToMessage(q, Message::SECTION_AUTHORITY, rrset);
getAdditional(q, rrset);
}
}
@@ -557,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, Section::ANSWER(), r);
- q.message().setHeaderFlag(MessageFlag::AA());
+ addToMessage(q, Message::SECTION_ANSWER, r);
+ q.message().setHeaderFlag(Message::HEADERFLAG_AA);
synthesizeCname(task, r, syn);
if (syn.size() == 1) {
- addToMessage(q, 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);
}
}
@@ -582,7 +616,7 @@ hasDelegation(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo) {
// at the actual qname node.)
if (task->op == QueryTask::AUTH_QUERY &&
task->state == QueryTask::GETANSWER) {
- q.message().setHeaderFlag(MessageFlag::AA());
+ q.message().setHeaderFlag(Message::HEADERFLAG_AA);
}
return (false);
@@ -599,8 +633,8 @@ addSOA(Query& q, ZoneInfo& zoneinfo) {
return (DataSrc::ERROR);
}
- addToMessage(q, Section::AUTHORITY(),
- soa.findRRset(RRType::SOA(), q.qclass()));
+ addToMessage(q, Message::SECTION_AUTHORITY,
+ findRRsetFromList(soa, RRType::SOA()));
return (DataSrc::SUCCESS);
}
@@ -611,8 +645,8 @@ addNSEC(Query& q, const Name& name, ZoneInfo& zoneinfo) {
QueryTask newtask(q, name, RRType::NSEC(), QueryTask::SIMPLE_QUERY);
RETERR(doQueryTask(newtask, zoneinfo, nsec));
if (newtask.flags == 0) {
- addToMessage(q, Section::AUTHORITY(),
- nsec.findRRset(RRType::NSEC(), q.qclass()));
+ addToMessage(q, Message::SECTION_AUTHORITY,
+ findRRsetFromList(nsec, RRType::NSEC()));
}
return (DataSrc::SUCCESS);
@@ -657,7 +691,6 @@ getNsec3Param(Query& q, ZoneInfo& zoneinfo) {
// XXX: currently only one NSEC3 chain per zone is supported;
// we will need to revisit this.
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
if (it->isLast()) {
return (ConstNsec3ParamPtr());
}
@@ -680,7 +713,7 @@ proveNX(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, const bool wildcard) {
RRsetPtr rrset;
string hash1(nsec3->getHash(task->qname));
RETERR(getNsec3(q, zoneinfo, hash1, rrset));
- addToMessage(q, Section::AUTHORITY(), rrset);
+ addToMessage(q, Message::SECTION_AUTHORITY, rrset);
// If this is an NXRRSET or NOERROR/NODATA, we're done
if ((task->flags & DataSrc::TYPE_NOT_FOUND) != 0) {
@@ -705,7 +738,7 @@ proveNX(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, const bool wildcard) {
// we don't want to use one until we find an exact match
RETERR(getNsec3(q, zoneinfo, hash2, rrset));
if (hash2 == nodehash) {
- addToMessage(q, Section::AUTHORITY(), rrset);
+ addToMessage(q, Message::SECTION_AUTHORITY, rrset);
break;
}
}
@@ -720,7 +753,7 @@ proveNX(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, const bool wildcard) {
string hash3(nsec3->getHash(Name("*").concatenate(enclosure)));
RETERR(getNsec3(q, zoneinfo, hash3, rrset));
if (hash3 != hash1 && hash3 != hash2) {
- addToMessage(q, Section::AUTHORITY(), rrset);
+ addToMessage(q, Message::SECTION_AUTHORITY, rrset);
}
} else {
Name nsecname(task->qname);
@@ -778,7 +811,7 @@ tryWildcard(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, bool& found) {
for (int i = 1; i <= diff; ++i) {
const Name& wname(star.concatenate(task->qname.split(i)));
- QueryTask newtask(q, wname, task->qtype, Section::ANSWER(),
+ QueryTask newtask(q, wname, task->qtype, Message::SECTION_ANSWER,
QueryTask::AUTH_QUERY);
result = doQueryTask(newtask, zoneinfo, wild);
if (result == DataSrc::SUCCESS) {
@@ -817,16 +850,16 @@ 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, Section::ANSWER(), rrset);
+ addToMessage(q, Message::SECTION_ANSWER, rrset);
chaseCname(q, task, rrset);
}
} else {
BOOST_FOREACH (RRsetPtr rrset, wild) {
rrset->setName(task->qname);
- addToMessage(q, Section::ANSWER(), rrset);
+ addToMessage(q, Message::SECTION_ANSWER, rrset);
}
RRsetList auth;
@@ -855,7 +888,7 @@ DataSrc::doQuery(Query& q) {
// Process the query task queue. (The queue is initialized
// and the first task placed on it by the Query constructor.)
- m.clearHeaderFlag(MessageFlag::AA());
+ m.setHeaderFlag(Message::HEADERFLAG_AA, false);
while (!q.tasks().empty()) {
QueryTaskPtr task = q.tasks().front();
q.tasks().pop();
@@ -912,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;
}
@@ -937,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());
@@ -972,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);
@@ -982,19 +1014,19 @@ DataSrc::doQuery(Query& q) {
// The qname node contains an out-of-zone referral.
if (task->state == QueryTask::GETANSWER) {
RRsetList auth;
- m.clearHeaderFlag(MessageFlag::AA());
+ m.setHeaderFlag(Message::HEADERFLAG_AA, false);
if (!refQuery(q, task->qname, zoneinfo, auth)) {
m.setRcode(Rcode::SERVFAIL());
return;
}
BOOST_FOREACH (RRsetPtr rrset, auth) {
if (rrset->getType() == RRType::NS()) {
- addToMessage(q, Section::AUTHORITY(), rrset);
+ addToMessage(q, Message::SECTION_AUTHORITY, rrset);
} else if (rrset->getType() == task->qtype) {
- addToMessage(q, Section::ANSWER(), rrset);
+ addToMessage(q, Message::SECTION_ANSWER, rrset);
} else if (rrset->getType() == RRType::DS() &&
q.wantDnssec()) {
- addToMessage(q, Section::AUTHORITY(), rrset);
+ addToMessage(q, Message::SECTION_AUTHORITY, rrset);
}
getAdditional(q, rrset);
}
@@ -1002,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.
@@ -1063,13 +1102,14 @@ DataSrc::doQuery(Query& q) {
// space, signatures in additional section are
// optional.)
BOOST_FOREACH(RRsetPtr rrset, additional) {
- addToMessage(q, Section::ADDITIONAL(), rrset, true);
+ addToMessage(q, Message::SECTION_ADDITIONAL, rrset, true);
}
if (q.wantDnssec()) {
BOOST_FOREACH(RRsetPtr rrset, additional) {
if (rrset->getRRsig()) {
- addToMessage(q, Section::ADDITIONAL(), rrset->getRRsig(), true);
+ addToMessage(q, Message::SECTION_ADDITIONAL, rrset->getRRsig(),
+ true);
}
}
}
@@ -1158,7 +1198,7 @@ MetaDataSrc::addDataSrc(ConstDataSrcPtr data_src) {
void
MetaDataSrc::removeDataSrc(ConstDataSrcPtr data_src) {
std::vector<ConstDataSrcPtr>::iterator it, itr;
- for (it = data_sources.begin(); it != data_sources.end(); it++) {
+ for (it = data_sources.begin(); it != data_sources.end(); ++it) {
if (*it == data_src) {
itr = it;
}
@@ -1243,72 +1283,65 @@ Nsec3Param::getHash(const Name& name) const {
return (encodeBase32Hex(vector<uint8_t>(digest, digest + SHA1_HASHSIZE)));
}
-//
-// The following methods are effectively empty, and their parameters are
-// unused. To silence compilers that warn unused function parameters,
-// we specify a (compiler dependent) special keyword when available.
-// It's defined in config.h, and to avoid including this header file from
-// installed files we define the methods here.
-//
DataSrc::Result
-DataSrc::init(isc::data::ConstElementPtr config UNUSED_PARAM) {
+DataSrc::init(isc::data::ConstElementPtr) {
return (NOT_IMPLEMENTED);
}
DataSrc::Result
-MetaDataSrc::findRRset(const isc::dns::Name& qname UNUSED_PARAM,
- const isc::dns::RRClass& qclass UNUSED_PARAM,
- const isc::dns::RRType& qtype UNUSED_PARAM,
- isc::dns::RRsetList& target UNUSED_PARAM,
- uint32_t& flags UNUSED_PARAM,
- const isc::dns::Name* zonename UNUSED_PARAM) const
+MetaDataSrc::findRRset(const isc::dns::Name&,
+ const isc::dns::RRClass&,
+ const isc::dns::RRType&,
+ isc::dns::RRsetList&,
+ uint32_t&,
+ const isc::dns::Name*) const
{
return (NOT_IMPLEMENTED);
}
DataSrc::Result
-MetaDataSrc::findExactRRset(const isc::dns::Name& qname UNUSED_PARAM,
- const isc::dns::RRClass& qclass UNUSED_PARAM,
- const isc::dns::RRType& qtype UNUSED_PARAM,
- isc::dns::RRsetList& target UNUSED_PARAM,
- uint32_t& flags UNUSED_PARAM,
- const isc::dns::Name* zonename UNUSED_PARAM) const
+MetaDataSrc::findExactRRset(const isc::dns::Name&,
+ const isc::dns::RRClass&,
+ const isc::dns::RRType&,
+ isc::dns::RRsetList&,
+ uint32_t&,
+ const isc::dns::Name*) const
{
return (NOT_IMPLEMENTED);
}
DataSrc::Result
-MetaDataSrc::findAddrs(const isc::dns::Name& qname UNUSED_PARAM,
- const isc::dns::RRClass& qclass UNUSED_PARAM,
- isc::dns::RRsetList& target UNUSED_PARAM,
- uint32_t& flags UNUSED_PARAM,
- const isc::dns::Name* zonename UNUSED_PARAM) const
+MetaDataSrc::findAddrs(const isc::dns::Name&,
+ const isc::dns::RRClass&,
+ isc::dns::RRsetList&,
+ uint32_t&,
+ const isc::dns::Name*) const
{
return (NOT_IMPLEMENTED);
}
DataSrc::Result
-MetaDataSrc::findReferral(const isc::dns::Name& qname UNUSED_PARAM,
- const isc::dns::RRClass& qclass UNUSED_PARAM,
- isc::dns::RRsetList& target UNUSED_PARAM,
- uint32_t& flags UNUSED_PARAM,
- const isc::dns::Name* zonename UNUSED_PARAM) const
+MetaDataSrc::findReferral(const isc::dns::Name&,
+ const isc::dns::RRClass&,
+ isc::dns::RRsetList&,
+ uint32_t&,
+ const isc::dns::Name*) const
{
return (NOT_IMPLEMENTED);
}
DataSrc::Result
-MetaDataSrc::findPreviousName(const isc::dns::Name& qname UNUSED_PARAM,
- isc::dns::Name& target UNUSED_PARAM,
- const isc::dns::Name* zonename UNUSED_PARAM) const
+MetaDataSrc::findPreviousName(const isc::dns::Name&,
+ isc::dns::Name&,
+ const isc::dns::Name*) const
{
return (NOT_IMPLEMENTED);
}
DataSrc::Result
-MetaDataSrc::findCoveringNSEC3(const isc::dns::Name& zonename UNUSED_PARAM,
- std::string& hash UNUSED_PARAM,
- isc::dns::RRsetList& target UNUSED_PARAM) const
+MetaDataSrc::findCoveringNSEC3(const isc::dns::Name&,
+ std::string&,
+ isc::dns::RRsetList&) const
{
return (NOT_IMPLEMENTED);
}
diff --git a/src/lib/datasrc/data_source.h b/src/lib/datasrc/data_source.h
index 14582d6..ff695da 100644
--- a/src/lib/datasrc/data_source.h
+++ b/src/lib/datasrc/data_source.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __DATA_SOURCE_H
#define __DATA_SOURCE_H
@@ -248,7 +246,7 @@ public:
void addDataSrc(ConstDataSrcPtr data_src);
void removeDataSrc(ConstDataSrcPtr data_src);
size_t dataSrcCount() { return (data_sources.size()); }
-
+
void findClosestEnclosure(DataSrcMatch& match) const;
// Actual queries for data should not be sent to a MetaDataSrc object,
diff --git a/src/lib/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
new file mode 100644
index 0000000..5230ced
--- /dev/null
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -0,0 +1,644 @@
+// 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 <map>
+#include <cassert>
+#include <boost/shared_ptr.hpp>
+#include <boost/bind.hpp>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrsetlist.h>
+#include <dns/masterload.h>
+
+#include <datasrc/memory_datasrc.h>
+#include <datasrc/rbtree.h>
+
+using namespace std;
+using namespace isc::dns;
+
+namespace isc {
+namespace datasrc {
+
+// Private data and hidden methods of MemoryZone
+struct MemoryZone::MemoryZoneImpl {
+ // Constructor
+ MemoryZoneImpl(const RRClass& zone_class, const Name& origin) :
+ zone_class_(zone_class), origin_(origin), origin_data_(NULL),
+ domains_(true)
+ {
+ // We create the node for origin (it needs to exist anyway in future)
+ domains_.insert(origin, &origin_data_);
+ DomainPtr origin_domain(new Domain);
+ origin_data_->setData(origin_domain);
+ }
+
+ // Some type aliases
+ /*
+ * Each domain consists of some RRsets. They will be looked up by the
+ * RRType.
+ *
+ * The use of map is questionable with regard to performance - there'll
+ * be usually only few RRsets in the domain, so the log n benefit isn't
+ * much and a vector/array might be faster due to its simplicity and
+ * continuous memory location. But this is unlikely to be a performance
+ * critical place and map has better interface for the lookups, so we use
+ * that.
+ */
+ typedef map<RRType, ConstRRsetPtr> Domain;
+ typedef Domain::value_type DomainPair;
+ typedef boost::shared_ptr<Domain> DomainPtr;
+ // The tree stores domains
+ typedef RBTree<Domain> DomainTree;
+ typedef RBNode<Domain> DomainNode;
+ static const DomainNode::Flags DOMAINFLAG_WILD = DomainNode::FLAG_USER1;
+
+ // Information about the zone
+ RRClass zone_class_;
+ Name origin_;
+ DomainNode* origin_data_;
+ string file_name_;
+
+ // The actual zone data
+ DomainTree domains_;
+
+ // Add the necessary magic for any wildcard contained in 'name'
+ // (including itself) to be found in the zone.
+ //
+ // In order for wildcard matching to work correctly in find(),
+ // we must ensure that a node for the wildcarding level exists in the
+ // backend RBTree.
+ // E.g. if the wildcard name is "*.sub.example." then we must ensure
+ // that "sub.example." exists and is marked as a wildcard level.
+ // Note: the "wildcarding level" is for the parent name of the wildcard
+ // name (such as "sub.example.").
+ //
+ // We also perform the same trick for empty wild card names possibly
+ // contained in 'name' (e.g., '*.foo.example' in 'bar.*.foo.example').
+ void addWildcards(DomainTree& domains, const Name& name) {
+ Name wname(name);
+ const unsigned int labels(wname.getLabelCount());
+ const unsigned int origin_labels(origin_.getLabelCount());
+ for (unsigned int l = labels;
+ l > origin_labels;
+ --l, wname = wname.split(1)) {
+ if (wname.isWildcard()) {
+ // Ensure a separate level exists for the "wildcarding" name,
+ // and mark the node as "wild".
+ DomainNode* node;
+ DomainTree::Result result(domains.insert(wname.split(1),
+ &node));
+ assert(result == DomainTree::SUCCESS ||
+ result == DomainTree::ALREADYEXISTS);
+ node->setFlag(DOMAINFLAG_WILD);
+
+ // Ensure a separate level exists for the wildcard name.
+ // Note: for 'name' itself we do this later anyway, but the
+ // overhead should be marginal because wildcard names should
+ // be rare.
+ result = domains.insert(wname, &node);
+ assert(result == DomainTree::SUCCESS ||
+ result == DomainTree::ALREADYEXISTS);
+ }
+ }
+ }
+
+ /*
+ * Does some checks in context of the data that are already in the zone.
+ * Currently checks for forbidden combinations of RRsets in the same
+ * domain (CNAME+anything, DNAME+NS).
+ *
+ * If such condition is found, it throws AddError.
+ */
+ void contextCheck(const ConstRRsetPtr& rrset,
+ const DomainPtr& domain) const {
+ // Ensure CNAME and other type of RR don't coexist for the same
+ // owner name.
+ if (rrset->getType() == RRType::CNAME()) {
+ // XXX: this check will become incorrect when we support DNSSEC
+ // (depending on how we support DNSSEC). We should revisit it
+ // at that point.
+ if (!domain->empty()) {
+ isc_throw(AddError, "CNAME can't be added with other data for "
+ << rrset->getName());
+ }
+ } else if (domain->find(RRType::CNAME()) != domain->end()) {
+ isc_throw(AddError, "CNAME and " << rrset->getType() <<
+ " can't coexist for " << rrset->getName());
+ }
+
+ /*
+ * Similar with DNAME, but it must not coexist only with NS and only in
+ * non-apex domains.
+ * RFC 2672 section 3 mentions that it is implied from it and RFC 2181
+ */
+ if (rrset->getName() != origin_ &&
+ // Adding DNAME, NS already there
+ ((rrset->getType() == RRType::DNAME() &&
+ domain->find(RRType::NS()) != domain->end()) ||
+ // Adding NS, DNAME already there
+ (rrset->getType() == RRType::NS() &&
+ domain->find(RRType::DNAME()) != domain->end())))
+ {
+ isc_throw(AddError, "DNAME can't coexist with NS in non-apex "
+ "domain " << rrset->getName());
+ }
+ }
+
+ // Validate rrset before adding it to the zone. If something is wrong
+ // it throws an exception. It doesn't modify the zone, and provides
+ // the strong exception guarantee.
+ void addValidation(const ConstRRsetPtr rrset) {
+ if (!rrset) {
+ isc_throw(NullRRset, "The rrset provided is NULL");
+ }
+ // Check for singleton RRs. It should probably handled at a different
+ // in future.
+ if ((rrset->getType() == RRType::CNAME() ||
+ rrset->getType() == RRType::DNAME()) &&
+ rrset->getRdataCount() > 1)
+ {
+ // XXX: this is not only for CNAME or DNAME. We should generalize
+ // this code for all other "singleton RR types" (such as SOA) in a
+ // separate task.
+ isc_throw(AddError, "multiple RRs of singleton type for "
+ << rrset->getName());
+ }
+
+ NameComparisonResult compare(origin_.compare(rrset->getName()));
+ if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN &&
+ compare.getRelation() != NameComparisonResult::EQUAL)
+ {
+ isc_throw(OutOfZone, "The name " << rrset->getName() <<
+ " is not contained in zone " << origin_);
+ }
+
+ // Some RR types do not really work well with a wildcard.
+ // Even though the protocol specifically doesn't completely ban such
+ // usage, we refuse to load a zone containing such RR in order to
+ // keep the lookup logic simpler and more predictable.
+ // See RFC4592 and (for DNAME) draft-ietf-dnsext-rfc2672bis-dname
+ // for more technical background. Note also that BIND 9 refuses
+ // NS at a wildcard, so in that sense we simply provide compatible
+ // behavior.
+ if (rrset->getName().isWildcard()) {
+ if (rrset->getType() == RRType::NS()) {
+ isc_throw(AddError, "Invalid NS owner name (wildcard): " <<
+ rrset->getName());
+ }
+ if (rrset->getType() == RRType::DNAME()) {
+ isc_throw(AddError, "Invalid DNAME owner name (wildcard): " <<
+ rrset->getName());
+ }
+ }
+ }
+
+ /*
+ * Implementation of longer methods. We put them here, because the
+ * access is without the impl_-> and it will get inlined anyway.
+ */
+ // Implementation of MemoryZone::add
+ result::Result add(const ConstRRsetPtr& rrset, DomainTree* domains) {
+ // Sanitize input
+ addValidation(rrset);
+
+ // Add wildcards possibly contained in the owner name to the domain
+ // tree.
+ // Note: this can throw an exception, breaking strong exception
+ // guarantee. (see also the note for contextCheck() below).
+ addWildcards(*domains, rrset->getName());
+
+ // Get the node
+ DomainNode* node;
+ DomainTree::Result result = domains->insert(rrset->getName(), &node);
+ // Just check it returns reasonable results
+ assert((result == DomainTree::SUCCESS ||
+ result == DomainTree::ALREADYEXISTS) && node!= NULL);
+
+ // Now get the domain
+ DomainPtr domain;
+ // It didn't exist yet, create it
+ if (node->isEmpty()) {
+ domain.reset(new Domain);
+ node->setData(domain);
+ } else { // Get existing one
+ domain = node->getData();
+ }
+
+ // Checks related to the surrounding data.
+ // Note: when the check fails and the exception is thrown, it may
+ // break strong exception guarantee. At the moment we prefer
+ // code simplicity and don't bother to introduce complicated
+ // recovery code.
+ contextCheck(rrset, domain);
+
+ // Try inserting the rrset there
+ if (domain->insert(DomainPair(rrset->getType(), rrset)).second) {
+ // Ok, we just put it in
+
+ // If this RRset creates a zone cut at this node, mark the node
+ // indicating the need for callback in find().
+ if (rrset->getType() == RRType::NS() &&
+ rrset->getName() != origin_) {
+ node->setFlag(DomainNode::FLAG_CALLBACK);
+ // If it is DNAME, we have a callback as well here
+ } else if (rrset->getType() == RRType::DNAME()) {
+ node->setFlag(DomainNode::FLAG_CALLBACK);
+ }
+
+ return (result::SUCCESS);
+ } else {
+ // The RRSet of given type was already there
+ return (result::EXIST);
+ }
+ }
+
+ /*
+ * Same as above, but it checks the return value and if it already exists,
+ * it throws.
+ */
+ void addFromLoad(const ConstRRsetPtr& set, DomainTree* domains) {
+ switch (add(set, domains)) {
+ case result::EXIST:
+ isc_throw(dns::MasterLoadError, "Duplicate rrset: " <<
+ set->toText());
+ case result::SUCCESS:
+ return;
+ default:
+ assert(0);
+ }
+ }
+
+ // Maintain intermediate data specific to the search context used in
+ /// \c find().
+ ///
+ /// It will be passed to \c zonecutCallback() and record a possible
+ /// zone cut node and related RRset (normally NS or DNAME).
+ struct FindState {
+ FindState(FindOptions options) :
+ zonecut_node_(NULL),
+ dname_node_(NULL),
+ options_(options)
+ {}
+ const DomainNode* zonecut_node_;
+ const DomainNode* dname_node_;
+ ConstRRsetPtr rrset_;
+ const FindOptions options_;
+ };
+
+ // A callback called from possible zone cut nodes and nodes with DNAME.
+ // This will be passed from the \c find() method to \c RBTree::find().
+ static bool cutCallback(const DomainNode& node, FindState* state) {
+ // We need to look for DNAME first, there's allowed case where
+ // DNAME and NS coexist in the apex. DNAME is the one to notice,
+ // the NS is authoritative, not delegation (corner case explicitly
+ // allowed by section 3 of 2672)
+ const Domain::const_iterator foundDNAME(node.getData()->find(
+ RRType::DNAME()));
+ if (foundDNAME != node.getData()->end()) {
+ state->dname_node_ = &node;
+ state->rrset_ = foundDNAME->second;
+ // No more processing below the DNAME (RFC 2672, section 3
+ // forbids anything to exist below it, so there's no need
+ // to actually search for it). This is strictly speaking
+ // a different way than described in 4.1 of that RFC,
+ // but because of the assumption in section 3, it has the
+ // same behaviour.
+ return (true);
+ }
+
+ // Look for NS
+ const Domain::const_iterator foundNS(node.getData()->find(
+ RRType::NS()));
+ if (foundNS != node.getData()->end()) {
+ // We perform callback check only for the highest zone cut in the
+ // rare case of nested zone cuts.
+ if (state->zonecut_node_ != NULL) {
+ return (false);
+ }
+
+ // BIND 9 checks if this node is not the origin. That's probably
+ // because it can support multiple versions for dynamic updates
+ // and IXFR, and it's possible that the callback is called at
+ // the apex and the DNAME doesn't exist for a particular version.
+ // It cannot happen for us (at least for now), so we don't do
+ // that check.
+ state->zonecut_node_ = &node;
+ state->rrset_ = foundNS->second;
+
+ // Unless glue is allowed the search stops here, so we return
+ // false; otherwise return true to continue the search.
+ return ((state->options_ & FIND_GLUE_OK) == 0);
+ }
+
+ // This case should not happen because we enable callback only
+ // when we add an RR searched for above.
+ assert(0);
+ // This is here to avoid warning (therefore compilation error)
+ // in case assert is turned off. Otherwise we could get "Control
+ // reached end of non-void function".
+ return (false);
+ }
+
+ /*
+ * Prepares a rrset to be return as a result.
+ *
+ * If rename is false, it returns the one provided. If it is true, it
+ * creates a new rrset with the same data but with provided name.
+ * It is designed for wildcard case, where we create the rrsets
+ * dynamically.
+ */
+ static ConstRRsetPtr prepareRRset(const Name& name, const ConstRRsetPtr&
+ rrset, bool rename)
+ {
+ if (rename) {
+ /*
+ * We lose a signature here. But it would be wrong anyway, because
+ * the name changed. This might turn out to be unimportant in
+ * future, because wildcards will probably be handled somehow
+ * by DNSSEC.
+ */
+ RRsetPtr result(new RRset(name, rrset->getClass(),
+ rrset->getType(), rrset->getTTL()));
+ for (RdataIteratorPtr i(rrset->getRdataIterator()); !i->isLast();
+ i->next()) {
+ result->addRdata(i->getCurrent());
+ }
+ return (result);
+ } else {
+ return (rrset);
+ }
+ }
+
+ // Implementation of MemoryZone::find
+ FindResult find(const Name& name, RRType type,
+ RRsetList* target, const FindOptions options) const
+ {
+ // Get the node
+ DomainNode* node(NULL);
+ FindState state(options);
+ RBTreeNodeChain<Domain> node_path;
+ bool rename(false);
+ switch (domains_.find(name, &node, node_path, cutCallback, &state)) {
+ case DomainTree::PARTIALMATCH:
+ /*
+ * In fact, we could use a single variable instead of
+ * dname_node_ and zonecut_node_. But then we would need
+ * to distinquish these two cases by something else and
+ * it seemed little more confusing to me when I wrote it.
+ *
+ * Usually at most one of them will be something else than
+ * NULL (it might happen both are NULL, in which case we
+ * consider it NOT FOUND). There's one corner case when
+ * both might be something else than NULL and it is in case
+ * there's a DNAME under a zone cut and we search in
+ * glue OK mode â in that case we don't stop on the domain
+ * with NS and ignore it for the answer, but it gets set
+ * anyway. Then we find the DNAME and we need to act by it,
+ * therefore we first check for DNAME and then for NS. In
+ * all other cases it doesn't matter, as at least one of them
+ * is NULL.
+ */
+ if (state.dname_node_ != NULL) {
+ // We were traversing a DNAME node (and wanted to go
+ // lower below it), so return the DNAME
+ return (FindResult(DNAME, prepareRRset(name, state.rrset_,
+ rename)));
+ }
+ if (state.zonecut_node_ != NULL) {
+ return (FindResult(DELEGATION, prepareRRset(name,
+ state.rrset_, rename)));
+ }
+
+ // If the RBTree search stopped at a node for a super domain
+ // of the search name, it means the search name exists in
+ // the zone but is empty. Treat it as NXRRSET.
+ if (node_path.getLastComparisonResult().getRelation() ==
+ NameComparisonResult::SUPERDOMAIN) {
+ return (FindResult(NXRRSET, ConstRRsetPtr()));
+ }
+
+ /*
+ * No redirection anywhere. Let's try if it is a wildcard.
+ *
+ * The wildcard is checked after the empty non-terminal domain
+ * case above, because if that one triggers, it means we should
+ * not match according to 4.3.3 of RFC 1034 (the query name
+ * is known to exist).
+ */
+ if (node->getFlag(DOMAINFLAG_WILD)) {
+ /* Should we cancel this match?
+ *
+ * If we compare with some node and get a common ancestor,
+ * it might mean we are comparing with a non-wildcard node.
+ * In that case, we check which part is common. If we have
+ * something in common that lives below the node we got
+ * (the one above *), then we should cancel the match
+ * according to section 4.3.3 of RFC 1034 (as the name
+ * between the wildcard domain and the query name is known
+ * to exist).
+ *
+ * Because the way the tree stores relative names, we will
+ * have exactly one common label (the ".") in case we have
+ * nothing common under the node we got and we will get
+ * more common labels otherwise (yes, this relies on the
+ * internal RBTree structure, which leaks out through this
+ * little bit).
+ *
+ * If the empty non-terminal node actually exists in the
+ * tree, then this cancellation is not needed, because we
+ * will not get here at all.
+ */
+ if (node_path.getLastComparisonResult().getRelation() ==
+ NameComparisonResult::COMMONANCESTOR && node_path.
+ getLastComparisonResult().getCommonLabels() > 1) {
+ return (FindResult(NXDOMAIN, ConstRRsetPtr()));
+ }
+ Name wildcard(Name("*").concatenate(
+ node_path.getAbsoluteName()));
+ DomainTree::Result result(domains_.find(wildcard, &node));
+ /*
+ * Otherwise, why would the DOMAINFLAG_WILD be there if
+ * there was no wildcard under it?
+ */
+ assert(result == DomainTree::EXACTMATCH);
+ /*
+ * We have the wildcard node now. Jump below the switch,
+ * where handling of the common (exact-match) case is.
+ *
+ * However, rename it to the searched name.
+ */
+ rename = true;
+ break;
+ }
+
+ // fall through
+ case DomainTree::NOTFOUND:
+ return (FindResult(NXDOMAIN, ConstRRsetPtr()));
+ case DomainTree::EXACTMATCH: // This one is OK, handle it
+ break;
+ default:
+ assert(0);
+ }
+ assert(node != NULL);
+
+ // If there is an exact match but the node is empty, it's equivalent
+ // to NXRRSET.
+ if (node->isEmpty()) {
+ return (FindResult(NXRRSET, ConstRRsetPtr()));
+ }
+
+ Domain::const_iterator found;
+
+ // If the node callback is enabled, this may be a zone cut. If it
+ // has a NS RR, we should return a delegation, but not in the apex.
+ if (node->getFlag(DomainNode::FLAG_CALLBACK) && node != origin_data_) {
+ found = node->getData()->find(RRType::NS());
+ if (found != node->getData()->end()) {
+ return (FindResult(DELEGATION, prepareRRset(name,
+ found->second, rename)));
+ }
+ }
+
+ // handle type any query
+ if (target != NULL && !node->getData()->empty()) {
+ // Empty domain will be handled as NXRRSET by normal processing
+ for (found = node->getData()->begin();
+ found != node->getData()->end(); ++found)
+ {
+ target->addRRset(
+ boost::const_pointer_cast<RRset>(prepareRRset(name,
+ found->second, rename)));
+ }
+ return (FindResult(SUCCESS, ConstRRsetPtr()));
+ }
+
+ found = node->getData()->find(type);
+ if (found != node->getData()->end()) {
+ // Good, it is here
+ return (FindResult(SUCCESS, prepareRRset(name, found->second,
+ rename)));
+ } else {
+ // Next, try CNAME.
+ found = node->getData()->find(RRType::CNAME());
+ if (found != node->getData()->end()) {
+ return (FindResult(CNAME, prepareRRset(name, found->second,
+ rename)));
+ }
+ }
+ // No exact match or CNAME. Return NXRRSET.
+ return (FindResult(NXRRSET, ConstRRsetPtr()));
+ }
+};
+
+MemoryZone::MemoryZone(const RRClass& zone_class, const Name& origin) :
+ impl_(new MemoryZoneImpl(zone_class, origin))
+{
+}
+
+MemoryZone::~MemoryZone() {
+ delete impl_;
+}
+
+const Name&
+MemoryZone::getOrigin() const {
+ return (impl_->origin_);
+}
+
+const RRClass&
+MemoryZone::getClass() const {
+ return (impl_->zone_class_);
+}
+
+Zone::FindResult
+MemoryZone::find(const Name& name, const RRType& type,
+ RRsetList* target, const FindOptions options) const
+{
+ return (impl_->find(name, type, target, options));
+}
+
+result::Result
+MemoryZone::add(const ConstRRsetPtr& rrset) {
+ return (impl_->add(rrset, &impl_->domains_));
+}
+
+
+void
+MemoryZone::load(const string& filename) {
+ // Load it into a temporary tree
+ MemoryZoneImpl::DomainTree tmp;
+ masterLoad(filename.c_str(), getOrigin(), getClass(),
+ boost::bind(&MemoryZoneImpl::addFromLoad, impl_, _1, &tmp));
+ // If it went well, put it inside
+ impl_->file_name_ = filename;
+ tmp.swap(impl_->domains_);
+ // And let the old data die with tmp
+}
+
+void
+MemoryZone::swap(MemoryZone& zone) {
+ std::swap(impl_, zone.impl_);
+}
+
+const string
+MemoryZone::getFileName() const {
+ return (impl_->file_name_);
+}
+
+/// Implementation details for \c MemoryDataSrc hidden from the public
+/// interface.
+///
+/// For now, \c MemoryDataSrc only contains a \c ZoneTable object, which
+/// consists of (pointers to) \c MemoryZone objects, we may add more
+/// member variables later for new features.
+class MemoryDataSrc::MemoryDataSrcImpl {
+public:
+ MemoryDataSrcImpl() : zone_count(0) {}
+ unsigned int zone_count;
+ ZoneTable zone_table;
+};
+
+MemoryDataSrc::MemoryDataSrc() : impl_(new MemoryDataSrcImpl)
+{}
+
+MemoryDataSrc::~MemoryDataSrc() {
+ delete impl_;
+}
+
+unsigned int
+MemoryDataSrc::getZoneCount() const {
+ return (impl_->zone_count);
+}
+
+result::Result
+MemoryDataSrc::addZone(ZonePtr zone) {
+ if (!zone) {
+ isc_throw(InvalidParameter,
+ "Null pointer is passed to MemoryDataSrc::addZone()");
+ }
+
+ const result::Result result = impl_->zone_table.addZone(zone);
+ if (result == result::SUCCESS) {
+ ++impl_->zone_count;
+ }
+ return (result);
+}
+
+MemoryDataSrc::FindResult
+MemoryDataSrc::findZone(const isc::dns::Name& name) const {
+ return (FindResult(impl_->zone_table.findZone(name).code,
+ impl_->zone_table.findZone(name).zone));
+}
+} // end of namespace datasrc
+} // end of namespace dns
diff --git a/src/lib/datasrc/memory_datasrc.h b/src/lib/datasrc/memory_datasrc.h
new file mode 100644
index 0000000..99bb4e8
--- /dev/null
+++ b/src/lib/datasrc/memory_datasrc.h
@@ -0,0 +1,310 @@
+// 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 __MEMORY_DATA_SOURCE_H
+#define __MEMORY_DATA_SOURCE_H 1
+
+#include <string>
+
+#include <datasrc/zonetable.h>
+
+namespace isc {
+namespace dns {
+class Name;
+class RRsetList;
+};
+
+namespace datasrc {
+
+/// A derived zone class intended to be used with the memory data source.
+class MemoryZone : public Zone {
+ ///
+ /// \name Constructors and Destructor.
+ ///
+ /// \b Note:
+ /// The copy constructor and the assignment operator are intentionally
+ /// defined as private, making this class non copyable.
+ //@{
+private:
+ MemoryZone(const MemoryZone& source);
+ MemoryZone& operator=(const MemoryZone& source);
+public:
+ /// \brief Constructor from zone parameters.
+ ///
+ /// This constructor internally involves resource allocation, and if
+ /// it fails, a corresponding standard exception will be thrown.
+ /// It never throws an exception otherwise.
+ ///
+ /// \param rrclass The RR class of the zone.
+ /// \param origin The origin name of the zone.
+ MemoryZone(const isc::dns::RRClass& rrclass, const isc::dns::Name& origin);
+
+ /// The destructor.
+ virtual ~MemoryZone();
+ //@}
+
+ /// \brief Returns the origin of the zone.
+ virtual const isc::dns::Name& getOrigin() const;
+
+ /// \brief Returns the class of the zone.
+ virtual const isc::dns::RRClass& getClass() const;
+
+ /// \brief Looks up an RRset in the zone.
+ ///
+ /// See documentation in \c Zone.
+ ///
+ /// It returns NULL pointer in case of NXDOMAIN and NXRRSET,
+ /// and also SUCCESS if target is not NULL(TYPE_ANY query).
+ /// (the base class documentation does not seem to require that).
+ virtual FindResult find(const isc::dns::Name& name,
+ const isc::dns::RRType& type,
+ isc::dns::RRsetList* target = NULL,
+ const FindOptions options = FIND_DEFAULT) const;
+
+ /// \brief Inserts an rrset into the zone.
+ ///
+ /// It puts another RRset into the zone.
+ ///
+ /// Except for NullRRset and OutOfZone, this method does not guarantee
+ /// strong exception safety (it is currently not needed, if it is needed
+ /// in future, it should be implemented).
+ ///
+ /// \throw NullRRset \c rrset is a NULL pointer.
+ /// \throw OutOfZone The owner name of \c rrset is outside of the
+ /// origin of the zone.
+ /// \throw AddError Other general errors.
+ /// \throw Others This method might throw standard allocation exceptions.
+ ///
+ /// \param rrset The set to add.
+ /// \return SUCCESS or EXIST (if an rrset for given name and type already
+ /// exists).
+ result::Result add(const isc::dns::ConstRRsetPtr& rrset);
+
+ /// \brief RRSet out of zone exception.
+ ///
+ /// This is thrown if addition of an RRset that doesn't belong under the
+ /// zone's origin is requested.
+ struct OutOfZone : public InvalidParameter {
+ OutOfZone(const char* file, size_t line, const char* what) :
+ InvalidParameter(file, line, what)
+ { }
+ };
+
+ /// \brief RRset is NULL exception.
+ ///
+ /// This is thrown if the provided RRset parameter is NULL.
+ struct NullRRset : public InvalidParameter {
+ NullRRset(const char* file, size_t line, const char* what) :
+ InvalidParameter(file, line, what)
+ { }
+ };
+
+ /// \brief General failure exception for \c add().
+ ///
+ /// This is thrown against general error cases in adding an RRset
+ /// to the zone.
+ ///
+ /// Note: this exception would cover cases for \c OutOfZone or
+ /// \c NullRRset. We'll need to clarify and unify the granularity
+ /// of exceptions eventually. For now, exceptions are added as
+ /// developers see the need for it.
+ struct AddError : public InvalidParameter {
+ AddError(const char* file, size_t line, const char* what) :
+ InvalidParameter(file, line, what)
+ { }
+ };
+
+ /// Return the master file name of the zone
+ ///
+ /// This method returns the name of the zone's master file to be loaded.
+ /// The returned string will be an empty unless the zone has successfully
+ /// loaded a zone.
+ ///
+ /// This method should normally not throw an exception. But the creation
+ /// of the return string may involve a resource allocation, and if it
+ /// fails, the corresponding standard exception will be thrown.
+ ///
+ /// \return The name of the zone file loaded in the zone, or an empty
+ /// string if the zone hasn't loaded any file.
+ const std::string getFileName() const;
+
+ /// \brief Load zone from masterfile.
+ ///
+ /// This loads data from masterfile specified by filename. It replaces
+ /// current content. The masterfile parsing ability is kind of limited,
+ /// see isc::dns::masterLoad.
+ ///
+ /// This throws isc::dns::MasterLoadError if there is problem with loading
+ /// (missing file, malformed, it contains different zone, etc - see
+ /// isc::dns::masterLoad for details).
+ ///
+ /// In case of internal problems, OutOfZone, NullRRset or AssertError could
+ /// be thrown, but they should not be expected. Exceptions caused by
+ /// allocation may be thrown as well.
+ ///
+ /// If anything is thrown, the previous content is preserved (so it can
+ /// be used to update the data, but if user makes a typo, the old one
+ /// is kept).
+ ///
+ /// \param filename The master file to load.
+ ///
+ /// \todo We may need to split it to some kind of build and commit/abort.
+ /// This will probably be needed when a better implementation of
+ /// configuration reloading is written.
+ void load(const std::string& filename);
+
+ /// Exchanges the content of \c this zone with that of the given \c zone.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param zone Another \c MemoryZone object which is to be swapped with
+ /// \c this zone.
+ void swap(MemoryZone& zone);
+
+private:
+ /// \name Hidden private data
+ //@{
+ struct MemoryZoneImpl;
+ MemoryZoneImpl* impl_;
+ //@}
+};
+
+/// \brief A data source that uses in memory dedicated backend.
+///
+/// The \c MemoryDataSrc class represents a data source and provides a
+/// basic interface to help DNS lookup processing. For a given domain
+/// name, its \c findZone() method searches the in memory dedicated backend
+/// for the zone that gives a longest match against that name.
+///
+/// The in memory dedicated backend are assumed to be of the same RR class,
+/// but the \c MemoryDataSrc class does not enforce the assumption through
+/// its interface.
+/// For example, the \c addZone() method does not check if the new zone is of
+/// the same RR class as that of the others already in the dedicated backend.
+/// It is caller's responsibility to ensure this assumption.
+///
+/// <b>Notes to developer:</b>
+///
+/// For now, 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).
+///
+/// The addZone() method takes a (Boost) shared pointer because it would be
+/// inconvenient to require the caller to maintain the ownership of zones,
+/// while it wouldn't be safe to delete unnecessary zones inside the dedicated
+/// backend.
+///
+/// The findZone() method takes a domain name and returns the best matching \c
+/// MemoryZone in the form of (Boost) shared pointer, so that it can provide
+/// the general interface for all data sources.
+class MemoryDataSrc {
+public:
+ /// \brief A helper structure to represent the search result of
+ /// <code>MemoryDataSrc::find()</code>.
+ ///
+ /// This is a straightforward pair of the result code and a share pointer
+ /// to the found zone to represent the result of \c find().
+ /// We use this in order to avoid overloading the return value for both
+ /// the result code ("success" or "not found") and the found object,
+ /// i.e., avoid using \c NULL to mean "not found", etc.
+ ///
+ /// This is a simple value class with no internal state, so for
+ /// convenience we allow the applications to refer to the members
+ /// directly.
+ ///
+ /// See the description of \c find() for the semantics of the member
+ /// variables.
+ struct FindResult {
+ FindResult(result::Result param_code, const ZonePtr param_zone) :
+ code(param_code), zone(param_zone)
+ {}
+ const result::Result code;
+ const ZonePtr zone;
+ };
+
+ ///
+ /// \name Constructors and Destructor.
+ ///
+ /// \b Note:
+ /// The copy constructor and the assignment operator are intentionally
+ /// defined as private, making this class non copyable.
+ //@{
+private:
+ MemoryDataSrc(const MemoryDataSrc& source);
+ MemoryDataSrc& operator=(const MemoryDataSrc& source);
+
+public:
+ /// Default constructor.
+ ///
+ /// This constructor internally involves resource allocation, and if
+ /// it fails, a corresponding standard exception will be thrown.
+ /// It never throws an exception otherwise.
+ MemoryDataSrc();
+
+ /// The destructor.
+ ~MemoryDataSrc();
+ //@}
+
+ /// Return the number of zones stored in the data source.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return The number of zones stored in the data source.
+ unsigned int getZoneCount() const;
+
+ /// Add a \c Zone to the \c MemoryDataSrc.
+ ///
+ /// \c Zone must not be associated with a NULL pointer; otherwise
+ /// an exception of class \c InvalidParameter will be thrown.
+ /// If internal resource allocation fails, a corresponding standard
+ /// exception will be thrown.
+ /// This method never throws an exception otherwise.
+ ///
+ /// \param zone A \c Zone object to be added.
+ /// \return \c result::SUCCESS If the zone is successfully
+ /// added to the memory data source.
+ /// \return \c result::EXIST The memory data source already
+ /// stores a zone that has the same origin.
+ result::Result addZone(ZonePtr zone);
+
+ /// Find a \c Zone that best matches the given name in the \c MemoryDataSrc.
+ ///
+ /// It searches the internal storage for a \c Zone that gives the
+ /// longest match against \c name, and returns the result in the
+ /// form of a \c FindResult object as follows:
+ /// - \c code: The result code of the operation.
+ /// - \c result::SUCCESS: A zone that gives an exact match
+ // is found
+ /// - \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
+ // is found; otherwise \c NULL.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param name A domain name for which the search is performed.
+ /// \return A \c FindResult object enclosing the search result (see above).
+ FindResult findZone(const isc::dns::Name& name) const;
+
+private:
+ class MemoryDataSrcImpl;
+ MemoryDataSrcImpl* impl_;
+};
+}
+}
+#endif // __DATA_SOURCE_MEMORY_H
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/query.cc b/src/lib/datasrc/query.cc
index 4b01f75..d3de5c7 100644
--- a/src/lib/datasrc/query.cc
+++ b/src/lib/datasrc/query.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/buffer.h>
#include <dns/name.h>
#include <dns/rrset.h>
@@ -29,27 +27,31 @@ namespace isc {
namespace datasrc {
QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n,
- const isc::dns::RRType& t, const isc::dns::Section& sect) :
+ const isc::dns::RRType& t,
+ const isc::dns::Message::Section sect) :
q(qry), qname(n), qclass(qry.qclass()), qtype(t), section(sect),
op(AUTH_QUERY), state(GETANSWER), flags(0)
{}
QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n,
- const isc::dns::RRType& t, const isc::dns::Section& sect,
+ const isc::dns::RRType& t,
+ const isc::dns::Message::Section sect,
const Op o) :
q(qry), qname(n), qclass(qry.qclass()), qtype(t), section(sect), op(o),
state(GETANSWER), flags(0)
{}
QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n,
- const isc::dns::RRType& t, const isc::dns::Section& sect,
+ const isc::dns::RRType& t,
+ const isc::dns::Message::Section sect,
const State st) :
q(qry), qname(n), qclass(qry.qclass()), qtype(t), section(sect),
op(AUTH_QUERY), state(st), flags(0)
{}
QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n,
- const isc::dns::RRType& t, const isc::dns::Section& sect,
+ const isc::dns::RRType& t,
+ const isc::dns::Message::Section sect,
const Op o, const State st) :
q(qry), qname(n), qclass(qry.qclass()), qtype(t), section(sect), op(o),
state(st), flags(0)
@@ -58,7 +60,7 @@ QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n,
QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n,
const isc::dns::RRType& t, const Op o) :
q(qry), qname(n), qclass(qry.qclass()), qtype(t),
- section(Section::ANSWER()), op(o), state(GETANSWER), flags(0)
+ section(Message::SECTION_ANSWER), op(o), state(GETANSWER), flags(0)
{
if (op != SIMPLE_QUERY) {
isc_throw(Unexpected, "invalid constructor for this task operation");
@@ -68,7 +70,7 @@ QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n,
// A referral query doesn't need to specify section, state, or type.
QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n, const Op o) :
q(qry), qname(n), qclass(qry.qclass()), qtype(RRType::ANY()),
- section(Section::ANSWER()), op(o), state(GETANSWER), flags(0)
+ section(Message::SECTION_ANSWER), op(o), state(GETANSWER), flags(0)
{
if (op != REF_QUERY) {
isc_throw(Unexpected, "invalid constructor for this task operation");
@@ -76,7 +78,7 @@ QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n, const Op o) :
}
QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n,
- const isc::dns::Section& sect, const Op o,
+ const isc::dns::Message::Section sect, const Op o,
const State st) :
q(qry), qname(n), qclass(qry.qclass()), qtype(RRType::ANY()),
section(sect), op(o), state(st), flags(0)
@@ -93,7 +95,7 @@ Query::Query(Message& m, HotCache& c, bool dnssec) :
cache_(&c), message_(&m), want_additional_(true), want_dnssec_(dnssec)
{
// Check message formatting
- if (message_->getRRCount(Section::QUESTION()) != 1) {
+ if (message_->getRRCount(Message::SECTION_QUESTION) != 1) {
isc_throw(Unexpected, "malformed message: too many questions");
}
@@ -105,7 +107,7 @@ Query::Query(Message& m, HotCache& c, bool dnssec) :
restarts_ = 0;
querytasks_.push(QueryTaskPtr(new QueryTask(*this, *qname_, *qtype_,
- Section::ANSWER())));
+ Message::SECTION_ANSWER)));
}
Query::~Query() {}
diff --git a/src/lib/datasrc/query.h b/src/lib/datasrc/query.h
index c9a3f4c..43b62cc 100644
--- a/src/lib/datasrc/query.h
+++ b/src/lib/datasrc/query.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __QUERY_H
#define __QUERY_H
@@ -57,7 +55,7 @@ public:
// The section of the reply into which the data should be
// written after it has been fetched from the data source.
- const isc::dns::Section section;
+ const isc::dns::Message::Section section;
// The op field indicates the operation to be carried out by
// this query task:
@@ -127,14 +125,18 @@ public:
// Constructors
QueryTask(const Query& q, const isc::dns::Name& n,
- const isc::dns::RRType& t, const isc::dns::Section& sect);
+ const isc::dns::RRType& t,
+ const isc::dns::Message::Section sect);
QueryTask(const Query& q, const isc::dns::Name& n,
- const isc::dns::RRType& t, const isc::dns::Section& sect, Op o);
+ const isc::dns::RRType& t,
+ const isc::dns::Message::Section sect, Op o);
QueryTask(const Query& q, const isc::dns::Name& n,
- const isc::dns::RRType& t, const isc::dns::Section& sect,
+ const isc::dns::RRType& t,
+ const isc::dns::Message::Section sect,
const State st);
QueryTask(const Query& q, const isc::dns::Name& n,
- const isc::dns::RRType& t, const isc::dns::Section& sect,
+ const isc::dns::RRType& t,
+ const isc::dns::Message::Section sect,
Op o, State st);
// These are special constructors for particular query task types,
@@ -147,7 +149,7 @@ public:
QueryTask(const Query& q, const isc::dns::Name& n, Op o);
// A glue (or noglue) query doesn't need to specify type.
QueryTask(const Query& q, const isc::dns::Name& n,
- const isc::dns::Section& sect, Op o, State st);
+ const isc::dns::Message::Section sect, Op o, State st);
~QueryTask();
};
diff --git a/src/lib/datasrc/rbtree.h b/src/lib/datasrc/rbtree.h
new file mode 100644
index 0000000..03a6967
--- /dev/null
+++ b/src/lib/datasrc/rbtree.h
@@ -0,0 +1,1323 @@
+// 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 _RBTREE_H
+#define _RBTREE_H 1
+
+//! \file datasrc/rbtree.h
+///
+/// \note The purpose of the RBTree is to provide a generic map with
+/// domain names as the key that can be used by various BIND 10 modules or
+/// even by other applications. However, because of some unresolved design
+/// issue, the design and interface are not fixed, and RBTree isn't ready
+/// to be used as a base data structure by other modules.
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <boost/utility.hpp>
+#include <boost/shared_ptr.hpp>
+#include <exceptions/exceptions.h>
+#include <ostream>
+#include <algorithm>
+#include <cassert>
+
+namespace isc {
+namespace datasrc {
+
+namespace helper {
+
+/// \brief Helper function to remove the base domain from super domain.
+///
+/// The precondition of this function is the super_name contains the
+/// sub_name so
+/// \code Name a("a.b.c");
+/// Name b("b.c");
+/// Name c = a - b;
+/// \endcode
+/// c will contain "a".
+///
+/// \note Functions in this namespace is not intended to be used outside of
+/// RBTree implementation.
+inline isc::dns::Name
+operator-(const isc::dns::Name& super_name, const isc::dns::Name& sub_name) {
+ return (super_name.split(0, super_name.getLabelCount() -
+ sub_name.getLabelCount()));
+}
+}
+
+/// Forward declare RBTree class here is convinent for following friend
+/// class declare inside RBNode and RBTreeNodeChain
+template <typename T>
+class RBTree;
+
+/// \brief \c RBNode is used by RBTree to store any data related to one domain
+/// name.
+///
+/// This is meant to be used only from RBTree. It is meaningless to inherit it
+/// or create instances of it from elsewhere. For that reason, the constructor
+/// is private.
+///
+/// It serves three roles. One is to keep structure of the \c RBTree as a
+/// red-black tree. For that purpose, it has left, right and parent pointers
+/// and color member. These are private and accessed only from within the tree.
+///
+/// The second one is to store data for one domain name. The data related
+/// functions can be used to access and set the data.
+///
+/// The third role is to keep the hierarchy of domains. The down pointer points
+/// to a subtree of subdomains. Note that we can traverse the hierarchy down,
+/// but not up.
+///
+/// One special kind of node is non-terminal node. It has subdomains with
+/// RRsets, but doesn't have any RRsets itself.
+template <typename T>
+class RBNode : public boost::noncopyable {
+private:
+ /// The RBNode is meant for use from within RBTree, so it has access to
+ /// it.
+ friend class RBTree<T>;
+
+ /// \name Constructors
+ ///
+ /// \note The existence of a RBNode without a RBTree is meaningless.
+ /// Therefore the constructors are private.
+ //@{
+
+ /// \brief Default constructor.
+ ///
+ /// This constructor is provided specifically for generating a special
+ /// "null" node.
+ RBNode();
+
+ /// \brief Constructor from the node name.
+ ///
+ /// \param name The *relative* domain name (if this will live inside
+ /// a.b.c and is called d.e.a.b.c, then you pass d.e).
+ RBNode(const isc::dns::Name& name);
+ //@}
+
+public:
+ /// \brief Alias for shared pointer to the data.
+ typedef boost::shared_ptr<T> NodeDataPtr;
+
+ /// Node flags.
+ ///
+ /// Each flag value defines a non default property for a specific node.
+ /// These are defined as bitmask type values for the convenience of
+ /// internal implementation, but applications are expected to use
+ /// each flag separately via the enum definitions.
+ ///
+ /// All (settable) flags are off by default; they must be explicitly
+ /// set to on by the \c setFlag() method.
+ enum Flags {
+ FLAG_CALLBACK = 1, ///< Callback enabled. See \ref callback
+ FLAG_USER1 = 0x80000000U ///< Application specific flag
+ };
+private:
+ // Some flag values are expected to be used for internal purposes
+ // (e.g., representing the node color) in future versions, so we
+ // limit the settable flags via the \c setFlag() method to those
+ // explicitly defined in \c Flags. This constant represents all
+ // such flags.
+ static const uint32_t SETTABLE_FLAGS = (FLAG_CALLBACK | FLAG_USER1);
+
+public:
+
+ /// \brief Destructor
+ ///
+ /// It might seem strange that constructors are private and destructor
+ /// public, but this is needed because of shared pointers need access
+ /// to the destructor.
+ ///
+ /// You should never call anything like:
+ /// \code delete pointer_to_node; \endcode
+ /// The RBTree handles both creation and destructoion of nodes.
+ ~RBNode();
+
+ /// \name Getter functions.
+ //@{
+ /// \brief Return the name of current node.
+ ///
+ /// It's relative to its containing node.
+ ///
+ /// To get the absolute name of one node, the node path from the top node
+ /// to current node has to be recorded.
+ const isc::dns::Name& getName() const { return (name_); }
+
+ /// \brief Return the data stored in this node.
+ ///
+ /// You should not delete the data, it is handled by shared pointers.
+ NodeDataPtr& getData() { return (data_); }
+ /// \brief Return the data stored in this node.
+ const NodeDataPtr& getData() const { return (data_); }
+
+ /// \brief return whether the node has related data.
+ ///
+ /// There can be empty nodes inside the RBTree. They are usually the
+ /// non-terminal domains, but it is possible (yet probably meaningless)
+ /// empty nodes anywhere.
+ bool isEmpty() const { return (data_.get() == NULL); }
+
+ //@}
+
+ /// \name Setter functions.
+ //@{
+ /// \brief Set the data stored in the node.
+ void setData(const NodeDataPtr& data) { data_ = data; }
+ //@}
+
+ /// \name Node flag manipulation methods
+ //@{
+ /// Get the status of a node flag.
+ ///
+ /// This method returns whether the given node flag is set (enabled)
+ /// on the node. The \c flag parameter is expected to be one of the
+ /// defined \c Flags constants. For simplicity, the method interface
+ /// does not prohibit passing an undefined flag or combined flags, but
+ /// the return value in such a case will be meaningless for the caller
+ /// (an application would have to use an ugly cast for such an unintended
+ /// form of call, which will hopefully avoid accidental misuse).
+ ///
+ /// \exception None
+ /// \param flag The flag to be tested.
+ /// \return \c true if the \c flag is set; \c false otherwise.
+ bool getFlag(Flags flag) const {
+ return ((flags_ & flag) != 0);
+ }
+
+ /// Set or clear a node flag.
+ ///
+ /// This method changes the status of the specified node flag to either
+ /// "on" (enabled) or "off" (disabled). The new status is specified by
+ /// the \c on parameter.
+ /// Like the \c getFlag() method, \c flag is expected to be one of the
+ /// defined \c Flags constants. If an undefined or unsettable flag is
+ /// specified, \c isc::InvalidParameter exception will be thrown.
+ ///
+ /// \exception isc::InvalidParameter Unsettable flag is specified
+ /// \exception None otherwise
+ /// \param flag The node flag to be changed.
+ /// \on If \c true, set the flag to on; otherwise set it to off.
+ void setFlag(Flags flag, bool on = true) {
+ if ((flag & ~SETTABLE_FLAGS) != 0) {
+ isc_throw(isc::InvalidParameter,
+ "Unsettable RBTree flag is being set");
+ }
+ if (on) {
+ flags_ |= flag;
+ } else {
+ flags_ &= ~flag;
+ }
+ }
+ //@}
+
+private:
+ /// \name Callback related methods
+ ///
+ /// See the description of \c RBTree<T>::find() about callbacks.
+ ///
+ /// These methods never throw an exception.
+ //@{
+ /// Return if callback is enabled at the node.
+ //@}
+
+private:
+ /// \brief Define rbnode color
+ enum RBNodeColor {BLACK, RED};
+ /// This is a factory class method of a special singleton null node.
+ static RBNode<T>* NULL_NODE() {
+ static RBNode<T> null_node;
+ return (&null_node);
+ }
+
+ /// \brief return the next node which is bigger than current node
+ /// in the same subtree
+ ///
+ /// The next successor for this node is the next bigger node in terms of
+ /// the DNSSEC order relation within the same single subtree.
+ /// Note that it may NOT be the next bigger node in the entire RBTree;
+ /// RBTree is a tree in tree, and the real next node may reside in
+ /// an upper or lower subtree of the subtree where this node belongs.
+ /// For example, if this node has a sub domain, the real next node is
+ /// the smallest node in the sub domain tree.
+ ///
+ /// If this node is the biggest node within the subtree, this method
+ /// returns \c NULL_NODE().
+ ///
+ /// This method never throws an exception.
+ const RBNode<T>* successor() const;
+
+ /// \name Data to maintain the rbtree structure.
+ //@{
+ RBNode<T>* parent_;
+ RBNode<T>* left_;
+ RBNode<T>* right_;
+ RBNodeColor color_;
+ //@}
+
+ /// \brief Relative name of the node.
+ isc::dns::Name name_;
+ /// \brief Data stored here.
+ NodeDataPtr data_;
+
+ /// \brief The subdomain tree.
+ ///
+ /// This points to the root node of trees of subdomains of this domain.
+ ///
+ /// \par Adding down pointer to \c RBNode has two purposes:
+ /// \li Accelerate the search process, with sub domain tree, it splits the
+ /// big flat tree into several hierarchy trees.
+ /// \li It saves memory useage as it allows storing only relative names,
+ /// avoiding storage of the same domain labels multiple times.
+ RBNode<T>* down_;
+
+ /// \brief If callback should be called when traversing this node in
+ /// RBTree::find().
+ ///
+ /// \todo It might be needed to put it into more general attributes field.
+ uint32_t flags_;
+};
+
+
+// This is only to support NULL nodes.
+template <typename T>
+RBNode<T>::RBNode() :
+ parent_(this),
+ left_(this),
+ right_(this),
+ color_(BLACK),
+ // dummy name, the value doesn't matter:
+ name_(isc::dns::Name::ROOT_NAME()),
+ down_(this),
+ flags_(0)
+{
+}
+
+template <typename T>
+RBNode<T>::RBNode(const isc::dns::Name& name) :
+ parent_(NULL_NODE()),
+ left_(NULL_NODE()),
+ right_(NULL_NODE()),
+ color_(RED),
+ name_(name),
+ down_(NULL_NODE()),
+ flags_(0)
+{
+}
+
+
+template <typename T>
+RBNode<T>::~RBNode() {
+}
+
+template <typename T>
+const RBNode<T>*
+RBNode<T>::successor() const {
+ const RBNode<T>* current = this;
+ // If it has right node, the successor is the left-most node of the right
+ // subtree.
+ if (right_ != NULL_NODE()) {
+ current = right_;
+ while (current->left_ != NULL_NODE()) {
+ current = current->left_;
+ }
+ return (current);
+ }
+
+
+ // Otherwise go up until we find the first left branch on our path to
+ // root. If found, the parent of the branch is the successor.
+ // Otherwise, we return the null node
+ const RBNode<T>* parent = current->parent_;
+ while (parent != NULL_NODE() && current == parent->right_) {
+ current = parent;
+ parent = parent->parent_;
+ }
+ return (parent);
+}
+
+
+/// \brief RBTreeNodeChain stores detailed information of \c RBTree::find()
+/// result.
+///
+/// - The \c RBNode that was last compared with the search name, and
+/// the comparison result at that point in the form of
+/// \c isc::dns::NameComparisonResult.
+/// - A sequence of nodes that forms a path to the found node (which is
+/// not yet implemented).
+///
+/// The comparison result can be used to handle some rare cases such as
+/// empty node processing.
+/// The node sequence keeps track of the nodes to reach any given node from
+/// the root of RBTree.
+///
+/// Currently, RBNode does not have "up" pointers in them (i.e., back pointers
+/// from the root of one level of tree of trees to the node in the parent
+/// tree whose down pointer points to that root node) for memory usage
+/// reasons, so there is no other way to find the path back to the root from
+/// any given RBNode.
+///
+/// \note This design may change in future versions. In particular, it's
+/// quite likely we want to have that pointer if we want to optimize name
+/// compression by exploiting the structure of the zone. If and when that
+/// happens we should also revisit the need for the chaining.
+/// Also, the class name may not be appropriate now that it contains other
+/// information than a node "chain", and the chain itself may even be
+/// deprecated. Something like "RBTreeFindContext" may be a better name.
+/// This point should be revisited later.
+///
+/// RBTreeNodeChain is constructed and manipulated only inside the \c RBTree
+/// class.
+/// \c RBTree uses it as an inner data structure to iterate over the whole
+/// RBTree.
+/// This is the reason why manipulation methods such as \c push() and \c pop()
+/// are private (and not shown in the doxygen document).
+template <typename T>
+class RBTreeNodeChain {
+ /// RBTreeNodeChain is initialized by RBTree, only RBTree has
+ /// knowledge to manipuate it.
+ friend class RBTree<T>;
+public:
+ /// \name Constructors and Assignment Operator.
+ ///
+ /// \note The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non copyable.
+ /// This may have to be changed in a future version with newer need.
+ /// For now we explicitly disable copy to avoid accidental copy happens
+ /// unintentionally.
+ //{@
+ /// The default constructor.
+ ///
+ /// \exception None
+ RBTreeNodeChain() : node_count_(0), last_compared_(NULL),
+ // XXX: meaningless initial values:
+ last_comparison_(0, 0,
+ isc::dns::NameComparisonResult::EQUAL)
+ {}
+
+private:
+ RBTreeNodeChain(const RBTreeNodeChain<T>&);
+ RBTreeNodeChain<T>& operator=(const RBTreeNodeChain<T>&);
+ //@}
+
+public:
+ /// Clear the state of the chain.
+ ///
+ /// This method re-initializes the internal state of the chain so that
+ /// it can be reused for subsequent operations.
+ ///
+ /// \exception None
+ void clear() {
+ node_count_ = 0;
+ last_compared_ = NULL;
+ }
+
+ /// Return the \c RBNode that was last compared in \c RBTree::find().
+ ///
+ /// If this chain has been passed to \c RBTree::find() and there has
+ /// been name comparison against the search name, the last compared
+ /// \c RBNode is recorded within the chain. This method returns that
+ /// node.
+ /// If \c RBTree::find() hasn't been called with this chain or name
+ /// comparison hasn't taken place (which is possible if the tree is empty),
+ /// this method returns \c NULL.
+ ///
+ /// \exception None
+ const RBNode<T>* getLastComparedNode() const {
+ return (last_compared_);
+ }
+
+ /// Return the result of last name comparison in \c RBTree::find().
+ ///
+ /// Like \c getLastComparedNode(), \c RBTree::find() records the result
+ /// of the last name comparison in the chain. This method returns the
+ /// result.
+ /// The return value of this method is only meaningful when comparison
+ /// has taken place, i.e, when \c getLastComparedNode() would return a
+ /// non \c NULL value.
+ ///
+ /// \exception None
+ const isc::dns::NameComparisonResult& getLastComparisonResult() const {
+ return (last_comparison_);
+ }
+
+ /// \brief Return the number of levels stored in the chain.
+ ///
+ /// It's equal to the number of nodes in the chain; for an empty
+ /// chain, 0 will be returned.
+ ///
+ /// \exception None
+ unsigned int getLevelCount() const { return (node_count_); }
+
+ /// \brief return the absolute name for the node which this
+ /// \c RBTreeNodeChain currently refers to.
+ ///
+ /// The chain must not be empty.
+ ///
+ /// \exception isc::BadValue the chain is empty.
+ /// \exception std::bad_alloc memory allocation for the new name fails.
+ isc::dns::Name getAbsoluteName() const {
+ if (isEmpty()) {
+ isc_throw(isc::BadValue,
+ "RBTreeNodeChain::getAbsoluteName is called on an empty "
+ "chain");
+ }
+
+ const RBNode<T>* top_node = top();
+ isc::dns::Name absolute_name = top_node->getName();
+ int node_count = node_count_ - 1;
+ while (node_count > 0) {
+ top_node = nodes_[node_count - 1];
+ absolute_name = absolute_name.concatenate(top_node->getName());
+ --node_count;
+ }
+ return (absolute_name);
+ }
+
+private:
+ // the following private functions check invariants about the internal
+ // state using assert() instead of exception. The state of a chain
+ // can only be modified operations within this file, so if any of the
+ // assumptions fails it means an internal bug.
+
+ /// \brief return whther node chain has node in it.
+ ///
+ /// \exception None
+ bool isEmpty() const { return (node_count_ == 0); }
+
+ /// \brief return the top node for the node chain
+ ///
+ /// RBTreeNodeChain store all the nodes along top node to
+ /// root node of RBTree
+ ///
+ /// \exception None
+ const RBNode<T>* top() const {
+ assert(!isEmpty());
+ return (nodes_[node_count_ - 1]);
+ }
+
+ /// \brief pop the top node from the node chain
+ ///
+ /// After pop, up/super node of original top node will be
+ /// the top node
+ ///
+ /// \exception None
+ void pop() {
+ assert(!isEmpty());
+ --node_count_;
+ }
+
+ /// \brief add the node into the node chain
+ ///
+ /// If the node chain isn't empty, the node should be
+ /// the sub domain of the original top node in node chain
+ /// otherwise the node should be the root node of RBTree.
+ ///
+ /// \exception None
+ void push(const RBNode<T>* node) {
+ assert(node_count_ < RBT_MAX_LEVEL);
+ nodes_[node_count_++] = node;
+ }
+
+private:
+ // The max label count for one domain name is Name::MAX_LABELS (128).
+ // 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];
+ const RBNode<T>* last_compared_;
+ isc::dns::NameComparisonResult last_comparison_;
+};
+
+
+// note: the following class description is documented using multiline comments
+// because the verbatim diagram contain a backslash, which could be interpreted
+// as escape of newline in singleline comment.
+/**
+ * \brief \c RBTree class represents all the domains with the same suffix.
+ * It can be used to store the domains in one zone, for example.
+ *
+ * RBTree is a generic map from domain names to any kind of data. Internally,
+ * it uses a red-black tree. However, it isn't one tree containing everything.
+ * Subdomains are trees, so this structure is recursive - trees inside trees.
+ * But, from the interface point of view, it is opaque data structure.
+ *
+ * \c RBTree splits the domain space into hierarchy red black trees; nodes
+ * in one tree has the same base name. The benefit of this struct is that:
+ * - Enhances the query performace compared with one big flat red black tree.
+ * - Decreases the memory footprint, as it doesn't store the suffix labels
+ * multiple times.
+ *
+ * Depending on different usage, rbtree will support different search policies.
+ * Whether to return an empty node to end user is one policy among them.
+ * The default policy is to NOT return an empty node to end user;
+ * to change the behavior, specify \c true for the constructor parameter
+ * \c returnEmptyNode.
+ * \note The search policy only affects the \c find() behavior of RBTree.
+ * When inserting one name into RBTree, if the node with the name already
+ * exists in the RBTree and it's an empty node which doesn't have any data,
+ * the \c insert() method will still return \c ALREADYEXISTS regardless of
+ * the search policy.
+ *
+ * \anchor diagram
+ *
+ * with the following names:
+ * - a
+ * - b
+ * - c
+ * - x.d.e.f
+ * - z.d.e.f
+ * - g.h
+ * - o.w.y.d.e.f
+ * - p.w.y.d.e.f
+ * - q.w.y.d.e.f
+ *
+ * the tree will look like:
+ * \verbatim
+ b
+ / \
+ a d.e.f
+ /|\
+ c | g.h
+ |
+ w.y
+ /|\
+ x | z
+ |
+ p
+ / \
+ o q
+ \endverbatim
+ * \todo
+ * - add remove interface
+ * - add iterator to iterate over the whole \c RBTree. This may be necessary,
+ * for example, to support AXFR.
+ */
+template <typename T>
+class RBTree : public boost::noncopyable {
+ friend class RBNode<T>;
+public:
+ /// \brief The return value for the \c find() and insert() methods
+ enum Result {
+ SUCCESS, ///< Insert was successful
+ /// \brief The node returned from find mathes exactly the name given
+ EXACTMATCH,
+ PARTIALMATCH, ///< A superdomain node was found
+ NOTFOUND, ///< Not even any superdomain was found
+ /// \brief Returned by insert() if a node of the name already exists
+ ALREADYEXISTS,
+ };
+
+ /// \name Constructor and Destructor
+ //@{
+ /// The constructor.
+ ///
+ /// It never throws an exception.
+ explicit RBTree(bool returnEmptyNode = false);
+
+ /// \b Note: RBTree is not intended to be inherited so the destructor
+ /// is not virtual
+ ~RBTree();
+ //@}
+
+ /// \name Find methods
+ ///
+ /// \brief Find the node that gives a longest match against the given name.
+ ///
+ /// \anchor find
+ ///
+ /// These methods search the RBTree for a node whose name is longest
+ /// against name. The found node, if any, is returned via the node pointer.
+ ///
+ /// By default, nodes that don't have data (see RBNode::isEmpty) are
+ /// ignored and the result can be NOTFOUND even if there's a node whose
+ /// name matches. If the \c RBTree is constructed with its
+ /// \c returnEmptyNode parameter being \c true, an empty node will also
+ /// be match candidates.
+ ///
+ /// \note Even when \c returnEmptyNode is \c true, not all empty nodes
+ /// in terms of the DNS protocol may necessarily be found by this method.
+ /// For example, in the \ref diagram shown in the class description,
+ /// the name y.d.e.f is logically contained in the tree as part of the
+ /// node w.y, but the \c find() variants cannot find the former for
+ /// the search key of y.d.e.f, no matter how the \c RBTree is constructed.
+ /// The caller of this method must use a different way to identify the
+ /// hidden match when necessary.
+ ///
+ /// These methods involve operations on names that can throw an exception.
+ /// If that happens the exception will be propagated to the caller.
+ /// The callback function should generally not throw an exception, but
+ /// if it throws, the exception will be propagated to the caller.
+ ///
+ /// The \c name parameter says what should be found. The node parameter
+ /// is output only and in case of EXACTMATCH and PARTIALMATCH, it is set
+ /// to a pointer to the found node.
+ ///
+ /// They return:
+ /// - EXACTMATCH when a node with the same name as requested exists.
+ /// - PARTIALMATCH when a node with the same name does not exist (or is
+ /// empty), but there's a (nonempty) superdomain of the requested one.
+ /// The superdomain with longest name is returned through the node
+ /// parameter. Beware that if you store a zone in the tree, you may get
+ /// PARTIALMATCH with zone apex when the given domain name is not there.
+ /// You should not try to delegate into another zone in that case.
+ /// - NOTFOUND if there's no node with the same name nor any superdomain
+ /// of it. In that case, node parameter is left intact.
+ //@{
+
+ /// \brief Simple find.
+ ///
+ /// Acts as described in the \ref find section.
+ Result find(const isc::dns::Name& name, RBNode<T>** node) const {
+ RBTreeNodeChain<T> node_path;
+ return (find<void*>(name, node, node_path, NULL, NULL));
+ }
+
+ /// \brief Simple find returning immutable node.
+ ///
+ /// Acts as described in the \ref find section, but returns immutable node
+ /// pointer.
+ Result find(const isc::dns::Name& name, const RBNode<T>** node) const {
+ RBTreeNodeChain<T> node_path;
+ RBNode<T> *target_node = NULL;
+ Result ret = (find<void*>(name, &target_node, node_path, NULL, NULL));
+ if (ret != NOTFOUND) {
+ *node = target_node;
+ }
+ return (ret);
+ }
+
+ /// \brief Find with callback and node chain.
+ ///
+ /// This version of \c find() is specifically designed for the backend
+ /// of the \c MemoryZone class, and implements all necessary features
+ /// for that purpose. Other applications shouldn't need these additional
+ /// features, and should normally use the simpler versions.
+ ///
+ /// This version of \c find() calls the callback whenever traversing (on
+ /// the way from root down the tree) a marked node on the way down through
+ /// the domain namespace (see \c RBNode::enableCallback and related
+ /// functions).
+ ///
+ /// If you return true from the callback, the search is stopped and a
+ /// PARTIALMATCH is returned with the given node. Note that this node
+ /// doesn't really need to be the one with longest possible match.
+ ///
+ /// This callback mechanism was designed with zone cut (delegation)
+ /// processing in mind. The marked nodes would be the ones at delegation
+ /// points. It is not expected that any other applications would need
+ /// callbacks; they should use the versions of find without callbacks.
+ /// The callbacks are not general functors for the same reason - we don't
+ /// expect it to be needed.
+ ///
+ /// Another special feature of this version is the ability to record
+ /// more detailed information regarding the search result.
+ ///
+ /// This information will be returned via the \c node_path parameter,
+ /// which is an object of class \c RBTreeNodeChain.
+ /// The passed parameter must be empty.
+ ///
+ /// \note The rest of the description isn't yet implemented. It will be
+ /// handled in Trac ticket #517.
+ ///
+ /// On success, the node sequence stoed in \c node_path will contain all
+ /// the ancestor nodes from the found node towards the root.
+ /// For example, if we look for o.w.y.d.e.f in the example \ref diagram,
+ /// \c node_path will contain w.y and d.e.f; the \c top() node of the
+ /// chain will be o, w.f and d.e.f will be stored below it.
+ ///
+ /// This feature can be used to get the absolute name for a node;
+ /// to do so, we need to travel upside from the node toward the root,
+ /// concatenating all ancestor names. With the current implementation
+ /// it's not possible without a node chain, because there is a no pointer
+ /// from the root of a subtree to the parent subtree (this may change
+ /// in a future version). A node chain can also be used to find the next
+ /// node of a given node in the entire RBTree; the \c nextNode() method
+ /// takes a node chain as a parameter.
+ ///
+ /// \exception isc::BadValue node_path is not empty (not yet implemented).
+ ///
+ /// \param name Target to be found
+ /// \param node On success (either \c EXACTMATCH or \c PARTIALMATCH)
+ /// it will store a pointer to the matching node
+ /// \param node_path Other search details will be stored (see the
+ /// description)
+ /// \param callback If non \c NULL, a call back function to be called
+ /// at marked nodes (see above).
+ /// \param callback_arg A caller supplied argument to be passed to
+ /// \c callback.
+ ///
+ /// \return As described above, but in case of callback returning true,
+ /// it returns immediately with the current node.
+ template <typename CBARG>
+ Result find(const isc::dns::Name& name,
+ RBNode<T>** node,
+ RBTreeNodeChain<T>& node_path,
+ bool (*callback)(const RBNode<T>&, CBARG),
+ CBARG callback_arg) const;
+
+ /// \brief Simple find returning immutable node.
+ ///
+ /// Acts as described in the \ref find section, but returns immutable
+ /// node pointer.
+ template <typename CBARG>
+ Result find(const isc::dns::Name& name,
+ const RBNode<T>** node,
+ RBTreeNodeChain<T>& node_path,
+ bool (*callback)(const RBNode<T>&, CBARG),
+ CBARG callback_arg) const
+ {
+ RBNode<T>* target_node = NULL;
+ Result ret = find(name, &target_node, node_path, callback,
+ callback_arg);
+ if (ret != NOTFOUND) {
+ *node = target_node;
+ }
+ return (ret);
+ }
+ //@}
+
+ /// \brief return the next bigger node in DNSSEC order from a given node
+ /// chain.
+ ///
+ /// This method identifies the next bigger node of the node currently
+ /// referenced in \c node_path and returns it.
+ /// This method also updates the passed \c node_path so that it will store
+ /// the path for the returned next node.
+ /// It will be convenient when we want to iterate over the all nodes
+ /// of \c RBTree; we can do this by calling this method repeatedly
+ /// starting from the root node.
+ ///
+ /// \note \c nextNode() will iterate over all the nodes in RBTree including
+ /// empty nodes. If empty node isn't desired, it's easy to add logic to
+ /// check return node and keep invoking \c nextNode() until the non-empty
+ /// node is retrieved.
+ ///
+ /// \exception isc::BadValue node_path is empty.
+ ///
+ /// \param node_path A node chain that stores all the nodes along the path
+ /// from root to node.
+ ///
+ /// \return An \c RBNode that is next bigger than \c node; if \c node is
+ /// the largest, \c NULL will be returned.
+ const RBNode<T>* nextNode(RBTreeNodeChain<T>& node_path) const;
+
+ /// \brief Get the total number of nodes in the tree
+ ///
+ /// It includes nodes internally created as a result of adding a domain
+ /// name that is a subdomain of an existing node of the tree.
+ /// This function is mainly intended to be used for debugging.
+ int getNodeCount() const { return (node_count_); }
+
+ /// \name Debug function
+ //@{
+ /// \brief Print the nodes in the trees.
+ ///
+ /// \param os A \c std::ostream object to which the tree is printed.
+ /// \param depth A factor of the initial indentation. Each line
+ /// will begin with space character repeating <code>5 * depth</code>
+ /// times.
+ void dumpTree(std::ostream& os, unsigned int depth = 0) const;
+ //@}
+
+ /// \name Modify functions
+ //@{
+ /// \brief Insert the domain name into the tree.
+ ///
+ /// It either finds an already existing node of the given name or inserts
+ /// a new one, if none exists yet. In any case, the inserted_node parameter
+ /// is set to point to that node. You can fill data into it or modify it.
+ /// So, if you don't know if a node exists or not and you need to modify
+ /// it, just call insert and act by the result.
+ ///
+ /// Please note that the tree can add some empty nodes by itself, so don't
+ /// assume that if you didn't insert a node of that name it doesn't exist.
+ ///
+ /// This method normally involves resource allocation. If it fails
+ /// the corresponding standard exception will be thrown.
+ ///
+ /// This method does not provide the strong exception guarantee in its
+ /// strict sense; if an exception is thrown in the middle of this
+ /// method, the internal structure may change. However, it should
+ /// still retain the same property as a mapping container before this
+ /// method is called. For example, the result of \c find() should be
+ /// the same. This method provides the weak exception guarantee in its
+ /// normal sense.
+ ///
+ /// \param name The name to be inserted into the tree.
+ /// \param inserted_node This is an output parameter and is set to the
+ /// node.
+ ///
+ /// \return
+ /// - SUCCESS The node was added.
+ /// - ALREADYEXISTS There was already a node of that name, so it was not
+ /// added.
+ Result insert(const isc::dns::Name& name, RBNode<T>** inserted_node);
+
+ /// \brief Swaps two tree's contents.
+ ///
+ /// This acts the same as many std::*.swap functions, exchanges the
+ /// contents. This doesn't throw anything.
+ void swap(RBTree<T>& other) {
+ std::swap(root_, other.root_);
+ std::swap(NULLNODE, other.NULLNODE);
+ std::swap(node_count_, other.node_count_);
+ }
+ //@}
+
+private:
+ /// \name RBTree balance functions
+ //@{
+ void insertRebalance(RBNode<T>** root, RBNode<T>* node);
+ RBNode<T>* rightRotate(RBNode<T>** root, RBNode<T>* node);
+ RBNode<T>* leftRotate(RBNode<T>** root, RBNode<T>* node);
+ //@}
+
+ /// \name Helper functions
+ //@{
+ /// \brief delete tree whose root is equal to node
+ void deleteHelper(RBNode<T> *node);
+
+ /// \brief Print the information of given RBNode.
+ void dumpTreeHelper(std::ostream& os, const RBNode<T>* node,
+ unsigned int depth) const;
+
+ /// \brief Indentation helper function for dumpTree
+ static void indent(std::ostream& os, unsigned int depth);
+
+ /// Split one node into two nodes, keep the old node and create one new
+ /// node, old node will hold the base name, new node will be the down node
+ /// of old node, new node will hold the sub_name, the data
+ /// of old node will be move into new node, and old node became non-terminal
+ void nodeFission(RBNode<T>& node, const isc::dns::Name& sub_name);
+ //@}
+
+ RBNode<T>* NULLNODE;
+ RBNode<T>* root_;
+ /// the node count of current tree
+ unsigned int node_count_;
+ /// search policy for rbtree
+ const bool needsReturnEmptyNode_;
+};
+
+template <typename T>
+RBTree<T>::RBTree(bool returnEmptyNode) :
+ NULLNODE(RBNode<T>::NULL_NODE()),
+ root_(NULLNODE),
+ node_count_(0),
+ needsReturnEmptyNode_(returnEmptyNode)
+{
+}
+
+template <typename T>
+RBTree<T>::~RBTree() {
+ deleteHelper(root_);
+ assert(node_count_ == 0);
+}
+
+template <typename T>
+void
+RBTree<T>::deleteHelper(RBNode<T>* root) {
+ if (root == NULLNODE) {
+ return;
+ }
+
+ RBNode<T>* node = root;
+ while (root->left_ != NULLNODE || root->right_ != NULLNODE) {
+ while (node->left_ != NULLNODE || node->right_ != NULLNODE) {
+ node = (node->left_ != NULLNODE) ? node->left_ : node->right_;
+ }
+
+ RBNode<T>* parent = node->parent_;
+ if (parent->left_ == node) {
+ parent->left_ = NULLNODE;
+ } else {
+ parent->right_ = NULLNODE;
+ }
+
+ deleteHelper(node->down_);
+ delete node;
+ --node_count_;
+ node = parent;
+ }
+
+ deleteHelper(root->down_);
+ delete root;
+ --node_count_;
+}
+
+template <typename T>
+template <typename CBARG>
+typename RBTree<T>::Result
+RBTree<T>::find(const isc::dns::Name& target_name,
+ RBNode<T>** target,
+ RBTreeNodeChain<T>& node_path,
+ bool (*callback)(const RBNode<T>&, CBARG),
+ CBARG callback_arg) const
+{
+ using namespace helper;
+
+ if (!node_path.isEmpty()) {
+ isc_throw(isc::BadValue, "RBTree::find is given a non empty chain");
+ }
+
+ RBNode<T>* node = root_;
+ Result ret = NOTFOUND;
+ isc::dns::Name name = target_name;
+
+ while (node != NULLNODE) {
+ node_path.last_compared_ = node;
+ node_path.last_comparison_ = name.compare(node->name_);
+ const isc::dns::NameComparisonResult::NameRelation relation =
+ node_path.last_comparison_.getRelation();
+
+ if (relation == isc::dns::NameComparisonResult::EQUAL) {
+ if (needsReturnEmptyNode_ || !node->isEmpty()) {
+ node_path.push(node);
+ *target = node;
+ ret = EXACTMATCH;
+ }
+ break;
+ } else {
+ 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". 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) {
+ if (needsReturnEmptyNode_ || !node->isEmpty()) {
+ ret = PARTIALMATCH;
+ *target = node;
+ if (callback != NULL &&
+ node->getFlag(RBNode<T>::FLAG_CALLBACK)) {
+ if ((callback)(*node, callback_arg)) {
+ break;
+ }
+ }
+ }
+ node_path.push(node);
+ name = name - node->name_;
+ node = node->down_;
+ } else {
+ break;
+ }
+ }
+ }
+
+ return (ret);
+}
+
+template <typename T>
+const RBNode<T>*
+RBTree<T>::nextNode(RBTreeNodeChain<T>& node_path) const {
+ if (node_path.isEmpty()) {
+ isc_throw(isc::BadValue, "RBTree::nextNode is given an empty chain");
+ }
+
+ const RBNode<T>* node = node_path.top();
+ // if node has sub domain, the next domain is the smallest
+ // domain in sub domain tree
+ if (node->down_ != NULLNODE) {
+ const RBNode<T>* left_most = node->down_;
+ while (left_most->left_ != NULLNODE) {
+ left_most = left_most->left_;
+ }
+ node_path.push(left_most);
+ return (left_most);
+ }
+
+ // node_path go to up level
+ node_path.pop();
+ // otherwise found the successor node in current level
+ const RBNode<T>* successor = node->successor();
+ if (successor != NULLNODE) {
+ node_path.push(successor);
+ return (successor);
+ }
+
+ // if no successor found move to up level, the next successor
+ // is the successor of up node in the up level tree, if
+ // up node doesn't have successor we gonna keep moving to up
+ // level
+ while (!node_path.isEmpty()) {
+ const RBNode<T>* up_node_successor = node_path.top()->successor();
+ node_path.pop();
+ if (up_node_successor != NULLNODE) {
+ node_path.push(up_node_successor);
+ return (up_node_successor);
+ }
+ }
+
+ return (NULL);
+}
+
+
+template <typename T>
+typename RBTree<T>::Result
+RBTree<T>::insert(const isc::dns::Name& target_name, RBNode<T>** new_node) {
+ using namespace helper;
+ RBNode<T>* parent = NULLNODE;
+ RBNode<T>* current = root_;
+ RBNode<T>* up_node = NULLNODE;
+ isc::dns::Name name = target_name;
+
+ int order = -1;
+ while (current != NULLNODE) {
+ const isc::dns::NameComparisonResult compare_result =
+ name.compare(current->name_);
+ const isc::dns::NameComparisonResult::NameRelation relation =
+ compare_result.getRelation();
+ if (relation == isc::dns::NameComparisonResult::EQUAL) {
+ if (new_node != NULL) {
+ *new_node = current;
+ }
+ return (ALREADYEXISTS);
+ } else {
+ const int common_label_count = compare_result.getCommonLabels();
+ // 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_;
+ } else {
+ // insert sub domain to sub tree
+ if (relation == isc::dns::NameComparisonResult::SUBDOMAIN) {
+ parent = NULLNODE;
+ up_node = current;
+ name = name - current->name_;
+ current = current->down_;
+ } else {
+ // The number of labels in common is fewer
+ // than the number of labels at the current
+ // node, so the current node must be adjusted
+ // to have just the common suffix, and a down
+ // pointer made to a new tree.
+ const isc::dns::Name common_ancestor = name.split(
+ name.getLabelCount() - common_label_count,
+ common_label_count);
+ nodeFission(*current, common_ancestor);
+ }
+ }
+ }
+ }
+
+ RBNode<T>** current_root = (up_node != NULLNODE) ?
+ &(up_node->down_) : &root_;
+ // using auto_ptr here is avoid memory leak in case of exceptoin raised
+ // after the RBNode creation, if we can make sure no exception will be
+ // raised until the end of the function, we can remove it for optimization
+ std::auto_ptr<RBNode<T> > node(new RBNode<T>(name));
+ node->parent_ = parent;
+ if (parent == NULLNODE) {
+ *current_root = node.get();
+ //node is the new root of sub tree, so its init color
+ // is BLACK
+ node->color_ = RBNode<T>::BLACK;
+ } else if (order < 0) {
+ parent->left_ = node.get();
+ } else {
+ parent->right_ = node.get();
+ }
+ insertRebalance(current_root, node.get());
+ if (new_node != NULL) {
+ *new_node = node.get();
+ }
+
+ ++node_count_;
+ node.release();
+ return (SUCCESS);
+}
+
+
+template <typename T>
+void
+RBTree<T>::nodeFission(RBNode<T>& node, const isc::dns::Name& base_name) {
+ using namespace helper;
+ const isc::dns::Name sub_name = node.name_ - base_name;
+ // using auto_ptr here is to avoid memory leak in case of exception raised
+ // after the RBNode creation
+ std::auto_ptr<RBNode<T> > down_node(new RBNode<T>(sub_name));
+ node.name_ = base_name;
+ // the rest of this function should be exception free so that it keeps
+ // consistent behavior (i.e., a weak form of strong exception guarantee)
+ // even if code after the call to this function throws an exception.
+ std::swap(node.data_, down_node->data_);
+ std::swap(node.flags_, down_node->flags_);
+ down_node->down_ = node.down_;
+ node.down_ = down_node.get();
+ // root node of sub tree, the initial color is BLACK
+ down_node->color_ = RBNode<T>::BLACK;
+ ++node_count_;
+ down_node.release();
+}
+
+
+template <typename T>
+void
+RBTree<T>::insertRebalance(RBNode<T>** root, RBNode<T>* node) {
+
+ RBNode<T>* uncle;
+ while (node != *root && node->parent_->color_ == RBNode<T>::RED) {
+ if (node->parent_ == node->parent_->parent_->left_) {
+ uncle = node->parent_->parent_->right_;
+
+ if (uncle->color_ == RBNode<T>::RED) {
+ node->parent_->color_ = RBNode<T>::BLACK;
+ uncle->color_ = RBNode<T>::BLACK;
+ node->parent_->parent_->color_ = RBNode<T>::RED;
+ node = node->parent_->parent_;
+ } else {
+ if (node == node->parent_->right_) {
+ node = node->parent_;
+ leftRotate(root, node);
+ }
+ node->parent_->color_ = RBNode<T>::BLACK;
+ node->parent_->parent_->color_ = RBNode<T>::RED;
+ rightRotate(root, node->parent_->parent_);
+ }
+ } else {
+ uncle = node->parent_->parent_->left_;
+ if (uncle->color_ == RBNode<T>::RED) {
+ node->parent_->color_ = RBNode<T>::BLACK;
+ uncle->color_ = RBNode<T>::BLACK;
+ node->parent_->parent_->color_ = RBNode<T>::RED;
+ node = node->parent_->parent_;
+ } else {
+ if (node == node->parent_->left_) {
+ node = node->parent_;
+ rightRotate(root, node);
+ }
+ node->parent_->color_ = RBNode<T>::BLACK;
+ node->parent_->parent_->color_ = RBNode<T>::RED;
+ leftRotate(root, node->parent_->parent_);
+ }
+ }
+ }
+
+ (*root)->color_ = RBNode<T>::BLACK;
+}
+
+
+template <typename T>
+RBNode<T>*
+RBTree<T>::leftRotate(RBNode<T>** root, RBNode<T>* node) {
+ RBNode<T>* right = node->right_;
+ node->right_ = right->left_;
+ if (right->left_ != NULLNODE)
+ right->left_->parent_ = node;
+
+ right->parent_ = node->parent_;
+
+ if (node->parent_ != NULLNODE) {
+ if (node == node->parent_->left_) {
+ node->parent_->left_ = right;
+ } else {
+ node->parent_->right_ = right;
+ }
+ } else {
+ *root = right;
+ }
+
+ right->left_ = node;
+ node->parent_ = right;
+ return (node);
+}
+
+template <typename T>
+RBNode<T>*
+RBTree<T>::rightRotate(RBNode<T>** root, RBNode<T>* node) {
+ RBNode<T>* left = node->left_;
+ node->left_ = left->right_;
+ if (left->right_ != NULLNODE)
+ left->right_->parent_ = node;
+
+ left->parent_ = node->parent_;
+
+ if (node->parent_ != NULLNODE) {
+ if (node == node->parent_->right_) {
+ node->parent_->right_ = left;
+ } else {
+ node->parent_->left_ = left;
+ }
+ } else {
+ *root = left;
+ }
+ left->right_ = node;
+ node->parent_ = left;
+ return (node);
+}
+
+
+template <typename T>
+void
+RBTree<T>::dumpTree(std::ostream& os, unsigned int depth) const {
+ indent(os, depth);
+ os << "tree has " << node_count_ << " node(s)\n";
+ dumpTreeHelper(os, root_, depth);
+}
+
+template <typename T>
+void
+RBTree<T>::dumpTreeHelper(std::ostream& os, const RBNode<T>* node,
+ unsigned int depth) const
+{
+ if (node == NULLNODE) {
+ indent(os, depth);
+ os << "NULL\n";
+ return;
+ }
+
+ indent(os, depth);
+ os << node->name_.toText() << " ("
+ << ((node->color_ == RBNode<T>::BLACK) ? "black" : "red") << ")";
+ os << ((node->isEmpty()) ? "[invisible] \n" : "\n");
+
+ if (node->down_ != NULLNODE) {
+ indent(os, depth + 1);
+ os << "begin down from " << node->name_.toText() << "\n";
+ dumpTreeHelper(os, node->down_, depth + 1);
+ indent(os, depth + 1);
+ os << "end down from " << node->name_.toText() << "\n";
+ }
+ dumpTreeHelper(os, node->left_, depth + 1);
+ dumpTreeHelper(os, node->right_, depth + 1);
+}
+
+template <typename T>
+void
+RBTree<T>::indent(std::ostream& os, unsigned int depth) {
+ static const unsigned int INDENT_FOR_EACH_DEPTH = 5;
+ os << std::string(depth * INDENT_FOR_EACH_DEPTH, ' ');
+}
+
+}
+}
+
+#endif // _RBTREE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/result.h b/src/lib/datasrc/result.h
new file mode 100644
index 0000000..f7ca363
--- /dev/null
+++ b/src/lib/datasrc/result.h
@@ -0,0 +1,39 @@
+// 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 __DATASRC_RESULT_H
+#define __DATASRC_RESULT_H 1
+
+namespace isc {
+namespace datasrc {
+namespace result {
+/// Result codes of various public methods of in memory data source
+///
+/// The detailed semantics may differ in different methods.
+/// See the description of specific methods for more details.
+///
+/// Note: this is intended to be used from other data sources eventually,
+/// but for now it's specific to in memory data source and its backend.
+enum Result {
+ SUCCESS, ///< The operation is successful.
+ EXIST, ///< The search key is already stored.
+ NOTFOUND, ///< The specified object is not found.
+ PARTIALMATCH ///< Only a partial match is found.
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/datasrc/sqlite3_datasrc.cc b/src/lib/datasrc/sqlite3_datasrc.cc
index 08e7482..ab910ba 100644
--- a/src/lib/datasrc/sqlite3_datasrc.cc
+++ b/src/lib/datasrc/sqlite3_datasrc.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <string>
#include <sstream>
diff --git a/src/lib/datasrc/sqlite3_datasrc.h b/src/lib/datasrc/sqlite3_datasrc.h
index 0261630..d4abef7 100644
--- a/src/lib/datasrc/sqlite3_datasrc.h
+++ b/src/lib/datasrc/sqlite3_datasrc.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __DATA_SOURCE_SQLITE3_H
#define __DATA_SOURCE_SQLITE3_H
diff --git a/src/lib/datasrc/static_datasrc.cc b/src/lib/datasrc/static_datasrc.cc
index 7229d07..025078a 100644
--- a/src/lib/datasrc/static_datasrc.cc
+++ b/src/lib/datasrc/static_datasrc.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config.h>
#include <cassert>
@@ -72,6 +70,7 @@ StaticDataSrcImpl::StaticDataSrcImpl() :
RRType::TXT(), RRTTL(0)));
authors->addRdata(generic::TXT("Chen Zhengzhang")); // Jerry
authors->addRdata(generic::TXT("Evan Hunt"));
+ authors->addRdata(generic::TXT("Haidong Wang")); // Ocean
authors->addRdata(generic::TXT("Han Feng"));
authors->addRdata(generic::TXT("Jelte Jansen"));
authors->addRdata(generic::TXT("Jeremy C. Reed"));
@@ -79,10 +78,12 @@ StaticDataSrcImpl::StaticDataSrcImpl() :
authors->addRdata(generic::TXT("JINMEI Tatuya"));
authors->addRdata(generic::TXT("Kazunori Fujiwara"));
authors->addRdata(generic::TXT("Michael Graff"));
+ authors->addRdata(generic::TXT("Michal Vaner"));
authors->addRdata(generic::TXT("Naoki Kambe"));
authors->addRdata(generic::TXT("Shane Kerr"));
authors->addRdata(generic::TXT("Shen Tingting"));
authors->addRdata(generic::TXT("Stephen Morris"));
+ authors->addRdata(generic::TXT("Yoshitaka Aharen"));
authors->addRdata(generic::TXT("Zhang Likun"));
authors_ns = RRsetPtr(new RRset(authors_name, RRClass::CH(),
@@ -235,18 +236,12 @@ StaticDataSrc::findExactRRset(const Name& qname,
}
DataSrc::Result
-StaticDataSrc::findPreviousName(const Name& qname UNUSED_PARAM,
- Name& target UNUSED_PARAM,
- const Name* zonename UNUSED_PARAM) const
-{
+StaticDataSrc::findPreviousName(const Name&, Name&, const Name*) const {
return (NOT_IMPLEMENTED);
}
DataSrc::Result
-StaticDataSrc::findCoveringNSEC3(const Name& zonename UNUSED_PARAM,
- string& hash UNUSED_PARAM,
- RRsetList& target UNUSED_PARAM) const
-{
+StaticDataSrc::findCoveringNSEC3(const Name&, string&, RRsetList&) const {
return (NOT_IMPLEMENTED);
}
@@ -258,7 +253,7 @@ StaticDataSrc::init() {
// Static data source is "configuration less", so the \c config parameter
// is intentionally ignored.
DataSrc::Result
-StaticDataSrc::init(isc::data::ConstElementPtr config UNUSED_PARAM) {
+StaticDataSrc::init(isc::data::ConstElementPtr) {
return (init());
}
diff --git a/src/lib/datasrc/static_datasrc.h b/src/lib/datasrc/static_datasrc.h
index 0cd14c1..4d212fe 100644
--- a/src/lib/datasrc/static_datasrc.h
+++ b/src/lib/datasrc/static_datasrc.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
//
// Sample Datasource implementation; this datasource only returns
// static content for the queries
@@ -76,9 +74,9 @@ public:
isc::dns::Name& target,
const isc::dns::Name* zonename) const;
- Result findCoveringNSEC3(const isc::dns::Name& zonename,
- std::string& hash,
- isc::dns::RRsetList& target) const;
+ Result findCoveringNSEC3(const isc::dns::Name& zonename,
+ std::string& hash,
+ isc::dns::RRsetList& target) const;
Result init();
Result init(isc::data::ConstElementPtr config);
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 62cc7e9..f09b4b7 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -24,11 +24,15 @@ run_unittests_SOURCES += static_unittest.cc
run_unittests_SOURCES += query_unittest.cc
run_unittests_SOURCES += cache_unittest.cc
run_unittests_SOURCES += test_datasrc.h test_datasrc.cc
+run_unittests_SOURCES += rbtree_unittest.cc
+run_unittests_SOURCES += zonetable_unittest.cc
+run_unittests_SOURCES += memory_datasrc_unittest.cc
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/datasrc/libdatasrc.la
+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
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
diff --git a/src/lib/datasrc/tests/cache_unittest.cc b/src/lib/datasrc/tests/cache_unittest.cc
index d7192f8..96beae0 100644
--- a/src/lib/datasrc/tests/cache_unittest.cc
+++ b/src/lib/datasrc/tests/cache_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdexcept>
#include <dns/name.h>
diff --git a/src/lib/datasrc/tests/datasrc_unittest.cc b/src/lib/datasrc/tests/datasrc_unittest.cc
index 8762f10..810aef9 100644
--- a/src/lib/datasrc/tests/datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/datasrc_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <iostream>
@@ -40,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>
@@ -49,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(
@@ -56,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);
@@ -68,77 +70,67 @@ 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) {
- message.setHeaderFlag(MessageFlag::AA());
+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(MessageFlag::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(MessageFlag::QR()));
- EXPECT_EQ(aaflag, message.getHeaderFlag(MessageFlag::AA()));
- EXPECT_EQ(rdflag, message.getHeaderFlag(MessageFlag::RD()));
-
- EXPECT_EQ(ancount, message.getRRCount(Section::ANSWER()));
- EXPECT_EQ(nscount, message.getRRCount(Section::AUTHORITY()));
- EXPECT_EQ(arcount, message.getRRCount(Section::ADDITIONAL()));
+ msg.setHeaderFlag(Message::HEADERFLAG_RD);
+ 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(Section::ANSWER());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("www.example.com"), rrset->getName());
EXPECT_EQ(RRType::A(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
// XXX: also check ANSWER RRSIG
- rit = msg.beginSection(Section::AUTHORITY());
+ rit = msg.beginSection(Message::SECTION_AUTHORITY);
rrset = *rit;
EXPECT_EQ(Name("example.com"), rrset->getName());
EXPECT_EQ(RRType::NS(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
it->next();
EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -147,14 +139,13 @@ DataSrcTest::QueryCommon(const RRClass& qclass) {
it->next();
EXPECT_TRUE(it->isLast());
- rit = msg.beginSection(Section::ADDITIONAL());
+ 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();
- it->first();
EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
@@ -168,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(MessageFlag::QR()));
- EXPECT_FALSE(msg.getHeaderFlag(MessageFlag::AA()));
- EXPECT_TRUE(msg.getHeaderFlag(MessageFlag::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.
@@ -181,19 +168,72 @@ 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(Section::ANSWER());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("example.com"), rrset->getName());
EXPECT_EQ(RRType::NS(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
it->next();
EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -207,16 +247,16 @@ 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(Section::ANSWER());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("example.com"), rrset->getName());
EXPECT_EQ(RRType::NS(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
it->next();
EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -228,16 +268,16 @@ 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(Section::ANSWER());
+ rit = msg.beginSection(Message::SECTION_ANSWER);
rrset = *rit;
EXPECT_EQ(Name("example.com"), rrset->getName());
EXPECT_EQ(RRType::NS(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
it->next();
EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -250,9 +290,10 @@ 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(Section::ANSWER());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("example.com"), rrset->getName());
EXPECT_EQ(RRType::DNSKEY(), rrset->getType());
@@ -265,9 +306,10 @@ 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(Section::ANSWER());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("example.com"), rrset->getName());
EXPECT_EQ(RRType::DNSKEY(), rrset->getType());
@@ -276,7 +318,7 @@ TEST_F(DataSrcTest, DNSKEYDuplicateQuery) {
msg.clear(Message::PARSE);
createAndProcessQuery(Name("example.com"), RRClass::IN(),
RRType::DNSKEY());
- rit = msg.beginSection(Section::ANSWER());
+ rit = msg.beginSection(Message::SECTION_ANSWER);
rrset = *rit;
EXPECT_EQ(Name("example.com"), rrset->getName());
EXPECT_EQ(RRType::DNSKEY(), rrset->getType());
@@ -287,9 +329,10 @@ 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(Section::AUTHORITY());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("example.com"), rrset->getName());
EXPECT_EQ(RRType::SOA(), rrset->getType());
@@ -299,9 +342,10 @@ 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(Section::AUTHORITY());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("example.com"), rrset->getName());
EXPECT_EQ(RRType::SOA(), rrset->getType());
@@ -309,37 +353,72 @@ 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(MessageFlag::QR()));
- EXPECT_FALSE(msg.getHeaderFlag(MessageFlag::AA()));
- EXPECT_TRUE(msg.getHeaderFlag(MessageFlag::RD()));
+ EXPECT_TRUE(msg.getHeaderFlag(Message::HEADERFLAG_QR));
+ EXPECT_FALSE(msg.getHeaderFlag(Message::HEADERFLAG_AA));
+ EXPECT_TRUE(msg.getHeaderFlag(Message::HEADERFLAG_RD));
}
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(Section::ANSWER());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("www.wild.example.com"), rrset->getName());
EXPECT_EQ(RRType::A(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("192.0.2.2", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
- rit = msg.beginSection(Section::AUTHORITY());
+ rit = msg.beginSection(Message::SECTION_AUTHORITY);
rrset = *rit;
EXPECT_EQ(Name("*.wild.example.com"), rrset->getName());
EXPECT_EQ(RRType::NSEC(), rrset->getType());
@@ -353,7 +432,6 @@ TEST_F(DataSrcTest, Wildcard) {
EXPECT_EQ(RRClass::IN(), rrset->getClass());
it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
it->next();
EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -362,14 +440,13 @@ TEST_F(DataSrcTest, Wildcard) {
it->next();
EXPECT_TRUE(it->isLast());
- rit = msg.beginSection(Section::ADDITIONAL());
+ 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();
- it->first();
EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
@@ -380,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) {
@@ -388,76 +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(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();
- it->first();
- 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();
- it->first();
- EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
- it->next();
- EXPECT_TRUE(it->isLast());
-
- rit = msg.beginSection(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();
- it->first();
- 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(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();
- it->first();
- 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) {
@@ -465,21 +509,21 @@ 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(Section::ANSWER());
+ 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();
- it->first();
EXPECT_EQ("www.example.com.", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
- rit = msg.beginSection(Section::AUTHORITY());
+ rit = msg.beginSection(Message::SECTION_AUTHORITY);
rrset = *rit;
EXPECT_EQ(Name("*.wild2.example.com"), rrset->getName());
EXPECT_EQ(RRType::NSEC(), rrset->getType());
@@ -497,21 +541,21 @@ 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(Section::ANSWER());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("www.wild3.example.com"), rrset->getName());
EXPECT_EQ(RRType::CNAME(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("spork.example.com.", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
- rit = msg.beginSection(Section::AUTHORITY());
+ rit = msg.beginSection(Message::SECTION_AUTHORITY);
rrset = *rit;
EXPECT_EQ(Name("*.wild3.example.com"), rrset->getName());
EXPECT_EQ(RRType::NSEC(), rrset->getType());
@@ -535,28 +579,27 @@ 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(Section::ANSWER());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("www.sql1.example.com"), rrset->getName());
EXPECT_EQ(RRType::A(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("192.0.2.2", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
- rit = msg.beginSection(Section::AUTHORITY());
+ rit = msg.beginSection(Message::SECTION_AUTHORITY);
rrset = *rit;
EXPECT_EQ(Name("sql1.example.com"), rrset->getName());
EXPECT_EQ(RRType::NS(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
it->next();
EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -565,14 +608,13 @@ TEST_F(DataSrcTest, AuthDelegation) {
it->next();
EXPECT_TRUE(it->isLast());
- rit = msg.beginSection(Section::ADDITIONAL());
+ 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();
- it->first();
EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
@@ -582,30 +624,29 @@ 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(Section::ANSWER());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("dname.example.com"), rrset->getName());
EXPECT_EQ(RRType::DNAME(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("sql1.example.com.", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
// XXX: check CNAME and A record too
- rit = msg.beginSection(Section::AUTHORITY());
+ rit = msg.beginSection(Message::SECTION_AUTHORITY);
rrset = *rit;
EXPECT_EQ(Name("sql1.example.com"), rrset->getName());
EXPECT_EQ(RRType::NS(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
it->next();
EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -614,14 +655,13 @@ TEST_F(DataSrcTest, Dname) {
it->next();
EXPECT_TRUE(it->isLast());
- rit = msg.beginSection(Section::ADDITIONAL());
+ 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();
- it->first();
EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
@@ -633,24 +673,25 @@ 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(Section::ANSWER());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("foo.example.com"), rrset->getName());
EXPECT_EQ(RRType::CNAME(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
- EXPECT_EQ("cnametest.flame.org.", it->getCurrent().toText());
+ EXPECT_EQ("cnametest.example.net.", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
}
@@ -659,23 +700,23 @@ 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(Section::ANSWER());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("cname-int.example.com"), rrset->getName());
EXPECT_EQ(RRType::CNAME(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("www.example.com.", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
// XXX: check a record as well
- rit = msg.beginSection(Section::AUTHORITY());
+ rit = msg.beginSection(Message::SECTION_AUTHORITY);
rrset = *rit;
EXPECT_EQ(Name("example.com"), rrset->getName());
EXPECT_EQ(RRType::NS(), rrset->getType());
@@ -686,21 +727,21 @@ 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(Section::ANSWER());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("cname-ext.example.com"), rrset->getName());
EXPECT_EQ(RRType::CNAME(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("www.sql1.example.com.", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
- rit = msg.beginSection(Section::AUTHORITY());
+ rit = msg.beginSection(Message::SECTION_AUTHORITY);
rrset = *rit;
EXPECT_EQ(Name("sql1.example.com"), rrset->getName());
EXPECT_EQ(RRType::NS(), rrset->getType());
@@ -711,28 +752,27 @@ 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(Section::AUTHORITY());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("subzone.example.com."), rrset->getName());
EXPECT_EQ(RRType::NS(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("ns1.subzone.example.com.", it->getCurrent().toText());
it->next();
EXPECT_FALSE(it->isLast());
- rit = msg.beginSection(Section::ADDITIONAL());
+ rit = msg.beginSection(Message::SECTION_ADDITIONAL);
rrset = *rit;
EXPECT_EQ(Name("ns1.subzone.example.com"), rrset->getName());
EXPECT_EQ(RRType::A(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
@@ -742,28 +782,27 @@ 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(Section::AUTHORITY());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("subzone.example.com."), rrset->getName());
EXPECT_EQ(RRType::NS(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("ns1.subzone.example.com.", it->getCurrent().toText());
it->next();
EXPECT_FALSE(it->isLast());
- rit = msg.beginSection(Section::ADDITIONAL());
+ rit = msg.beginSection(Message::SECTION_ADDITIONAL);
rrset = *rit;
EXPECT_EQ(Name("ns1.subzone.example.com"), rrset->getName());
EXPECT_EQ(RRType::A(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
@@ -780,22 +819,22 @@ 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(Section::ANSWER());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("subzone.example.com."), rrset->getName());
EXPECT_EQ(RRType::NSEC(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
- rit = msg.beginSection(Section::AUTHORITY());
+ rit = msg.beginSection(Message::SECTION_AUTHORITY);
rrset = *rit;
EXPECT_EQ(Name("example.com"), rrset->getName());
EXPECT_EQ(RRType::NS(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
it->next();
EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -809,27 +848,26 @@ TEST_F(DataSrcTest, DNAMEZonecut) {
createAndProcessQuery(Name("subzone.example.com"), RRClass::IN(),
RRType::DNAME());
- headerCheck(msg, Rcode::NOERROR(), true, false, true, 0, 5, 2);
- RRsetIterator rit = msg.beginSection(Section::AUTHORITY());
+ 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());
EXPECT_EQ(RRType::NS(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("ns1.subzone.example.com.", it->getCurrent().toText());
it->next();
EXPECT_FALSE(it->isLast());
- rit = msg.beginSection(Section::ADDITIONAL());
+ rit = msg.beginSection(Message::SECTION_ADDITIONAL);
rrset = *rit;
EXPECT_EQ(Name("ns1.subzone.example.com"), rrset->getName());
EXPECT_EQ(RRType::A(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
@@ -839,22 +877,22 @@ 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(Section::ANSWER());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("subzone.example.com."), rrset->getName());
EXPECT_EQ(RRType::DS(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
- rit = msg.beginSection(Section::AUTHORITY());
+ rit = msg.beginSection(Message::SECTION_AUTHORITY);
rrset = *rit;
EXPECT_EQ(Name("example.com"), rrset->getName());
EXPECT_EQ(RRType::NS(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
it->next();
EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -872,7 +910,7 @@ TEST_F(DataSrcTest, CNAMELoop) {
// one.loop.example points to two.loop.example, which points back
// to one.loop.example, so there should be exactly two CNAME records
// in the answer.
- EXPECT_EQ(2, msg.getRRCount(Section::ANSWER()));
+ EXPECT_EQ(2, msg.getRRCount(Message::SECTION_ANSWER));
}
// NSEC query for the name of a zone cut for non-secure delegation.
@@ -881,40 +919,61 @@ 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(Section::AUTHORITY());
+ RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
ConstRRsetPtr rrset = *rit;
EXPECT_EQ(Name("sub.example.org."), rrset->getName());
EXPECT_EQ(RRType::NS(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ(createRdata(RRType::NS(), RRClass::IN(),
"ns.sub.example.org.")->toText(),
it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
- rit = msg.beginSection(Section::ADDITIONAL());
+ rit = msg.beginSection(Message::SECTION_ADDITIONAL);
rrset = *rit;
EXPECT_EQ(Name("ns.sub.example.org."), rrset->getName());
EXPECT_EQ(RRType::A(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ(createRdata(RRType::A(), RRClass::IN(), "192.0.2.101")->toText(),
it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
}
-TEST_F(DataSrcTest, RootDSQuery) {
+// Test sending a DS query to root (nonsense, but it should survive)
+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
+// (which triggers rfc4035 section 3.1.4.1)
+TEST_F(DataSrcTest, RootDSQuery2) {
+ // The message
+ msg.makeResponse();
+ msg.setOpcode(Opcode::QUERY());
+ msg.addQuestion(Question(Name("."), RRClass::IN(), RRType::DS()));
+ msg.setHeaderFlag(Message::HEADERFLAG_RD);
+ // Prepare the source
+ DataSrcPtr sql3_source = DataSrcPtr(new Sqlite3DataSrc);
+ ConstElementPtr sqlite_root = Element::fromJSON(
+ "{ \"database_file\": \"" TEST_DATA_DIR "/test-root.sqlite3\"}");
+ EXPECT_NO_THROW(sql3_source->init(sqlite_root));
+ // Make the query
+ EXPECT_NO_THROW(performQuery(*sql3_source, cache, msg));
+
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 1, 0);
}
TEST_F(DataSrcTest, DSQueryFromCache) {
@@ -932,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
@@ -941,8 +1001,9 @@ 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);
- RRsetIterator rit = msg.beginSection(Section::AUTHORITY());
+ 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());
EXPECT_EQ(RRType::SOA(), rrset->getType());
@@ -989,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
new file mode 100644
index 0000000..83fbb58
--- /dev/null
+++ b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
@@ -0,0 +1,1036 @@
+// 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 <sstream>
+#include <vector>
+
+#include <boost/bind.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/masterload.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrsetlist.h>
+#include <dns/rrttl.h>
+#include <dns/masterload.h>
+
+#include <datasrc/memory_datasrc.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::datasrc;
+
+namespace {
+// Commonly used result codes (Who should write the prefix all the time)
+using result::SUCCESS;
+using result::EXIST;
+
+class MemoryDataSrcTest : public ::testing::Test {
+protected:
+ MemoryDataSrcTest() : rrclass(RRClass::IN())
+ {}
+ RRClass rrclass;
+ MemoryDataSrc memory_datasrc;
+};
+
+TEST_F(MemoryDataSrcTest, add_find_Zone) {
+ // test add zone
+ // Bogus zone (NULL)
+ EXPECT_THROW(memory_datasrc.addZone(ZonePtr()), isc::InvalidParameter);
+
+ // add zones with different names one by one
+ EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(RRClass::IN(), Name("a")))));
+ EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(RRClass::CH(), Name("b")))));
+ EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(RRClass::IN(), Name("c")))));
+ // add zones with the same name suffix
+ EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(RRClass::CH(),
+ Name("x.d.e.f")))));
+ EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(RRClass::CH(),
+ Name("o.w.y.d.e.f")))));
+ EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(RRClass::CH(),
+ Name("p.w.y.d.e.f")))));
+ EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(RRClass::IN(),
+ Name("q.w.y.d.e.f")))));
+ // add super zone and its subzone
+ EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(RRClass::CH(), Name("g.h")))));
+ EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(RRClass::IN(), Name("i.g.h")))));
+ EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(RRClass::IN(),
+ Name("z.d.e.f")))));
+ EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(RRClass::IN(),
+ Name("j.z.d.e.f")))));
+
+ // different zone class isn't allowed.
+ EXPECT_EQ(result::EXIST, memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(RRClass::CH(),
+ Name("q.w.y.d.e.f")))));
+
+ // names are compared in a case insensitive manner.
+ EXPECT_EQ(result::EXIST, memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(RRClass::IN(),
+ Name("Q.W.Y.d.E.f")))));
+
+ // test find zone
+ EXPECT_EQ(result::SUCCESS, memory_datasrc.findZone(Name("a")).code);
+ EXPECT_EQ(Name("a"),
+ memory_datasrc.findZone(Name("a")).zone->getOrigin());
+
+ EXPECT_EQ(result::SUCCESS,
+ memory_datasrc.findZone(Name("j.z.d.e.f")).code);
+ EXPECT_EQ(Name("j.z.d.e.f"),
+ memory_datasrc.findZone(Name("j.z.d.e.f")).zone->getOrigin());
+
+ // NOTFOUND
+ EXPECT_EQ(result::NOTFOUND, memory_datasrc.findZone(Name("d.e.f")).code);
+ EXPECT_EQ(ConstZonePtr(), memory_datasrc.findZone(Name("d.e.f")).zone);
+
+ EXPECT_EQ(result::NOTFOUND,
+ memory_datasrc.findZone(Name("w.y.d.e.f")).code);
+ EXPECT_EQ(ConstZonePtr(),
+ memory_datasrc.findZone(Name("w.y.d.e.f")).zone);
+
+ // there's no exact match. the result should be the longest match,
+ // and the code should be PARTIALMATCH.
+ EXPECT_EQ(result::PARTIALMATCH,
+ memory_datasrc.findZone(Name("j.g.h")).code);
+ EXPECT_EQ(Name("g.h"),
+ memory_datasrc.findZone(Name("g.h")).zone->getOrigin());
+
+ EXPECT_EQ(result::PARTIALMATCH,
+ memory_datasrc.findZone(Name("z.i.g.h")).code);
+ EXPECT_EQ(Name("i.g.h"),
+ memory_datasrc.findZone(Name("z.i.g.h")).zone->getOrigin());
+}
+
+TEST_F(MemoryDataSrcTest, getZoneCount) {
+ EXPECT_EQ(0, memory_datasrc.getZoneCount());
+ memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(rrclass, Name("example.com"))));
+ EXPECT_EQ(1, memory_datasrc.getZoneCount());
+
+ // duplicate add. counter shouldn't change
+ memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(rrclass, Name("example.com"))));
+ EXPECT_EQ(1, memory_datasrc.getZoneCount());
+
+ // add one more
+ memory_datasrc.addZone(
+ ZonePtr(new MemoryZone(rrclass, Name("example.org"))));
+ EXPECT_EQ(2, memory_datasrc.getZoneCount());
+}
+
+// A helper callback of masterLoad() used in MemoryZoneTest.
+void
+setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
+ *(*it) = rrset;
+ ++it;
+}
+
+/// \brief Test fixture for the MemoryZone class
+class MemoryZoneTest : public ::testing::Test {
+ // A straightforward pair of textual RR(set) and a RRsetPtr variable
+ // to store the RRset. Used to build test data below.
+ struct RRsetData {
+ const char* const text; // textual representation of an RRset
+ RRsetPtr* rrset;
+ };
+public:
+ MemoryZoneTest() :
+ class_(RRClass::IN()),
+ origin_("example.org"),
+ zone_(class_, origin_)
+ {
+ // Build test RRsets. Below, we construct an RRset for
+ // each textual RR(s) of zone_data, and assign it to the corresponding
+ // rr_xxx.
+ const RRsetData zone_data[] = {
+ {"example.org. 300 IN NS ns.example.org.", &rr_ns_},
+ {"example.org. 300 IN A 192.0.2.1", &rr_a_},
+ {"ns.example.org. 300 IN A 192.0.2.2", &rr_ns_a_},
+ {"ns.example.org. 300 IN AAAA 2001:db8::2", &rr_ns_aaaa_},
+ {"cname.example.org. 300 IN CNAME canonical.example.org",
+ &rr_cname_},
+ {"cname.example.org. 300 IN A 192.0.2.3", &rr_cname_a_},
+ {"dname.example.org. 300 IN DNAME target.example.org.",
+ &rr_dname_},
+ {"dname.example.org. 300 IN A 192.0.2.39", &rr_dname_a_},
+ {"dname.example.org. 300 IN NS ns.dname.example.org.",
+ &rr_dname_ns_},
+ {"example.org. 300 IN DNAME example.com.", &rr_dname_apex_},
+ {"child.example.org. 300 IN NS ns.child.example.org.",
+ &rr_child_ns_},
+ {"ns.child.example.org. 300 IN A 192.0.2.153",
+ &rr_child_glue_},
+ {"grand.child.example.org. 300 IN NS ns.grand.child.example.org.",
+ &rr_grandchild_ns_},
+ {"ns.grand.child.example.org. 300 IN AAAA 2001:db8::253",
+ &rr_grandchild_glue_},
+ {"dname.child.example.org. 300 IN DNAME example.com.",
+ &rr_child_dname_},
+ {"example.com. 300 IN A 192.0.2.10", &rr_out_},
+ {"*.wild.example.org. 300 IN A 192.0.2.1", &rr_wild_},
+ {"foo.wild.example.org. 300 IN A 192.0.2.3", &rr_under_wild_},
+ {"wild.*.foo.example.org. 300 IN A 192.0.2.1", &rr_emptywild_},
+ {"wild.*.foo.*.bar.example.org. 300 IN A 192.0.2.1",
+ &rr_nested_emptywild_},
+ {"*.nswild.example.org. 300 IN NS nswild.example.", &rr_nswild_},
+ {"*.dnamewild.example.org. 300 IN DNAME dnamewild.example.",
+ &rr_dnamewild_},
+ {"*.child.example.org. 300 IN A 192.0.2.1", &rr_child_wild_},
+ {"bar.foo.wild.example.org. 300 IN A 192.0.2.2", &rr_not_wild_},
+ {"baz.foo.wild.example.org. 300 IN A 192.0.2.3",
+ &rr_not_wild_another_},
+ {NULL, NULL}
+ };
+
+ stringstream zone_data_stream;
+ vector<RRsetPtr*> rrsets;
+ for (unsigned int i = 0; zone_data[i].text != NULL; ++i) {
+ zone_data_stream << zone_data[i].text << "\n";
+ rrsets.push_back(zone_data[i].rrset);
+ }
+
+ vector<RRsetPtr*>::iterator it = rrsets.begin();
+ masterLoad(zone_data_stream, Name::ROOT_NAME(), class_,
+ boost::bind(setRRset, _1, it));
+ }
+ // Some data to test with
+ const RRClass class_;
+ const Name origin_;
+ // The zone to torture by tests
+ MemoryZone zone_;
+
+ /*
+ * Some RRsets to put inside the zone.
+ */
+ RRsetPtr
+ // Out of zone RRset
+ rr_out_,
+ // NS of example.org
+ rr_ns_,
+ // A of ns.example.org
+ rr_ns_a_,
+ // AAAA of ns.example.org
+ rr_ns_aaaa_,
+ // A of example.org
+ rr_a_;
+ RRsetPtr rr_cname_; // CNAME in example.org (RDATA will be added)
+ RRsetPtr rr_cname_a_; // for mixed CNAME + A case
+ RRsetPtr rr_dname_; // DNAME in example.org (RDATA will be added)
+ RRsetPtr rr_dname_a_; // for mixed DNAME + A case
+ RRsetPtr rr_dname_ns_; // for mixed DNAME + NS case
+ RRsetPtr rr_dname_apex_; // for mixed DNAME + NS case in the apex
+ RRsetPtr rr_child_ns_; // NS of a child domain (for delegation)
+ RRsetPtr rr_child_glue_; // glue RR of the child domain
+ RRsetPtr rr_grandchild_ns_; // NS below a zone cut (unusual)
+ RRsetPtr rr_grandchild_glue_; // glue RR below a deeper zone cut
+ RRsetPtr rr_child_dname_; // A DNAME under NS
+ RRsetPtr rr_wild_;
+ RRsetPtr rr_emptywild_;
+ RRsetPtr rr_nested_emptywild_;
+ RRsetPtr rr_nswild_, rr_dnamewild_;
+ RRsetPtr rr_child_wild_;
+ RRsetPtr rr_under_wild_;
+ RRsetPtr rr_not_wild_;
+ RRsetPtr rr_not_wild_another_;
+
+ /**
+ * \brief Test one find query to the zone.
+ *
+ * Asks a query to the zone and checks it does not throw and returns
+ * expected results. It returns nothing, it just signals failures
+ * to GTEST.
+ *
+ * \param name The name to ask for.
+ * \param rrtype The RRType to ask of.
+ * \param result The expected code of the result.
+ * \param check_answer Should a check against equality of the answer be
+ * done?
+ * \param answer The expected rrset, if any should be returned.
+ * \param zone Check different MemoryZone object than zone_ (if NULL,
+ * uses zone_)
+ * \param check_wild_answer Checks that the answer has the same RRs, type
+ * class and TTL as the eqxpected answer and that the name corresponds
+ * to the one searched. It is meant for checking answers for wildcard
+ * queries.
+ */
+ void findTest(const Name& name, const RRType& rrtype, Zone::Result result,
+ bool check_answer = true,
+ const ConstRRsetPtr& answer = ConstRRsetPtr(),
+ RRsetList* target = NULL,
+ MemoryZone* zone = NULL,
+ Zone::FindOptions options = Zone::FIND_DEFAULT,
+ bool check_wild_answer = false)
+ {
+ if (!zone) {
+ zone = &zone_;
+ }
+ // The whole block is inside, because we need to check the result and
+ // we can't assign to FindResult
+ EXPECT_NO_THROW({
+ Zone::FindResult find_result(zone->find(name, rrtype, target,
+ options));
+ // Check it returns correct answers
+ EXPECT_EQ(result, find_result.code);
+ if (check_answer) {
+ EXPECT_EQ(answer, find_result.rrset);
+ } else if (check_wild_answer) {
+ ASSERT_NE(ConstRRsetPtr(), answer) <<
+ "Wrong test, don't check for wild names if you expect "
+ "empty answer";
+ ASSERT_NE(ConstRRsetPtr(), find_result.rrset) <<
+ "No answer found";
+ RdataIteratorPtr expectedIt(answer->getRdataIterator());
+ RdataIteratorPtr actualIt(
+ find_result.rrset->getRdataIterator());
+ while (!expectedIt->isLast() && !actualIt->isLast()) {
+ EXPECT_EQ(0, expectedIt->getCurrent().compare(
+ actualIt->getCurrent())) << "The RRs differ ('" <<
+ expectedIt->getCurrent().toText() << "', '" <<
+ actualIt->getCurrent().toText() << "')";
+ expectedIt->next();
+ actualIt->next();
+ }
+ EXPECT_TRUE(expectedIt->isLast()) <<
+ "Result has less RRs than expected";
+ EXPECT_TRUE(actualIt->isLast()) <<
+ "Result has more RRs than expected";
+ EXPECT_EQ(answer->getClass(),
+ find_result.rrset->getClass());
+ EXPECT_EQ(answer->getType(),
+ find_result.rrset->getType());
+ EXPECT_EQ(answer->getTTL(),
+ find_result.rrset->getTTL());
+ EXPECT_EQ(name, find_result.rrset->getName());
+ }
+ });
+ }
+ // Internal part of the cancelWildcard test that is multiple times
+ void doCancelWildcardTest();
+};
+
+/**
+ * \brief Test MemoryZone::MemoryZone constructor.
+ *
+ * Takes the created zone and checks its properties they are the same
+ * as passed parameters.
+ */
+TEST_F(MemoryZoneTest, constructor) {
+ ASSERT_EQ(class_, zone_.getClass());
+ ASSERT_EQ(origin_, zone_.getOrigin());
+}
+/**
+ * \brief Test adding.
+ *
+ * We test that it throws at the correct moments and the correct exceptions.
+ * And we test the return value.
+ */
+TEST_F(MemoryZoneTest, add) {
+ // This one does not belong to this zone
+ EXPECT_THROW(zone_.add(rr_out_), MemoryZone::OutOfZone);
+ // Test null pointer
+ EXPECT_THROW(zone_.add(ConstRRsetPtr()), MemoryZone::NullRRset);
+
+ // Now put all the data we have there. It should throw nothing
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_a_)));
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_aaaa_)));
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_a_)));
+
+ // Try putting there something twice, it should be rejected
+ EXPECT_NO_THROW(EXPECT_EQ(EXIST, zone_.add(rr_ns_)));
+ EXPECT_NO_THROW(EXPECT_EQ(EXIST, zone_.add(rr_ns_a_)));
+}
+
+TEST_F(MemoryZoneTest, addMultipleCNAMEs) {
+ rr_cname_->addRdata(generic::CNAME("canonical2.example.org."));
+ EXPECT_THROW(zone_.add(rr_cname_), MemoryZone::AddError);
+}
+
+TEST_F(MemoryZoneTest, addCNAMEThenOther) {
+ EXPECT_EQ(SUCCESS, zone_.add(rr_cname_));
+ EXPECT_THROW(zone_.add(rr_cname_a_), MemoryZone::AddError);
+}
+
+TEST_F(MemoryZoneTest, addOtherThenCNAME) {
+ EXPECT_EQ(SUCCESS, zone_.add(rr_cname_a_));
+ EXPECT_THROW(zone_.add(rr_cname_), MemoryZone::AddError);
+}
+
+TEST_F(MemoryZoneTest, findCNAME) {
+ // install CNAME RR
+ EXPECT_EQ(SUCCESS, zone_.add(rr_cname_));
+
+ // Find A RR of the same. Should match the CNAME
+ findTest(rr_cname_->getName(), RRType::NS(), Zone::CNAME, true, rr_cname_);
+
+ // Find the CNAME itself. Should result in normal SUCCESS
+ findTest(rr_cname_->getName(), RRType::CNAME(), Zone::SUCCESS, true,
+ rr_cname_);
+}
+
+TEST_F(MemoryZoneTest, findCNAMEUnderZoneCut) {
+ // There's nothing special when we find a CNAME under a zone cut
+ // (with FIND_GLUE_OK). The behavior is different from BIND 9,
+ // so we test this case explicitly.
+ EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_));
+ RRsetPtr rr_cname_under_cut_(new RRset(Name("cname.child.example.org"),
+ class_, RRType::CNAME(),
+ RRTTL(300)));
+ EXPECT_EQ(SUCCESS, zone_.add(rr_cname_under_cut_));
+ findTest(Name("cname.child.example.org"), RRType::AAAA(),
+ Zone::CNAME, true, rr_cname_under_cut_, NULL, NULL,
+ Zone::FIND_GLUE_OK);
+}
+
+// Two DNAMEs at single domain are disallowed by RFC 2672, section 3)
+// Having a CNAME there is disallowed too, but it is tested by
+// addOtherThenCNAME and addCNAMEThenOther.
+TEST_F(MemoryZoneTest, addMultipleDNAMEs) {
+ rr_dname_->addRdata(generic::DNAME("target2.example.org."));
+ EXPECT_THROW(zone_.add(rr_dname_), MemoryZone::AddError);
+}
+
+/*
+ * These two tests ensure that we can't have DNAME and NS at the same
+ * node with the exception of the apex of zone (forbidden by RFC 2672)
+ */
+TEST_F(MemoryZoneTest, addDNAMEThenNS) {
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_)));
+ EXPECT_THROW(zone_.add(rr_dname_ns_), MemoryZone::AddError);
+}
+
+TEST_F(MemoryZoneTest, addNSThenDNAME) {
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_ns_)));
+ EXPECT_THROW(zone_.add(rr_dname_), MemoryZone::AddError);
+}
+
+// It is allowed to have NS and DNAME at apex
+TEST_F(MemoryZoneTest, DNAMEAndNSAtApex) {
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_apex_)));
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
+
+ // The NS should be possible to be found, below should be DNAME, not
+ // delegation
+ findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
+ findTest(rr_child_ns_->getName(), RRType::A(), Zone::DNAME, true,
+ rr_dname_apex_);
+}
+
+TEST_F(MemoryZoneTest, NSAndDNAMEAtApex) {
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_apex_)));
+}
+
+// TODO: Test (and implement) adding data under DNAME. That is forbidden by
+// 2672 as well.
+
+// Search under a DNAME record. It should return the DNAME
+TEST_F(MemoryZoneTest, findBelowDNAME) {
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_)));
+ findTest(Name("below.dname.example.org"), RRType::A(), Zone::DNAME, true,
+ rr_dname_);
+}
+
+// Search at the domain with DNAME. It should act as DNAME isn't there, DNAME
+// influences only the data below (see RFC 2672, section 3)
+TEST_F(MemoryZoneTest, findAtDNAME) {
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_)));
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_a_)));
+
+ const Name dname_name(rr_dname_->getName());
+ findTest(dname_name, RRType::A(), Zone::SUCCESS, true, rr_dname_a_);
+ findTest(dname_name, RRType::DNAME(), Zone::SUCCESS, true, rr_dname_);
+ findTest(dname_name, RRType::TXT(), Zone::NXRRSET, true);
+}
+
+// Try searching something that is both under NS and DNAME, without and with
+// GLUE_OK mode (it should stop at the NS and DNAME respectively).
+TEST_F(MemoryZoneTest, DNAMEUnderNS) {
+ zone_.add(rr_child_ns_);
+ zone_.add(rr_child_dname_);
+
+ Name lowName("below.dname.child.example.org.");
+
+ findTest(lowName, RRType::A(), Zone::DELEGATION, true, rr_child_ns_);
+ findTest(lowName, RRType::A(), Zone::DNAME, true, rr_child_dname_, NULL,
+ NULL, Zone::FIND_GLUE_OK);
+}
+
+// Test adding child zones and zone cut handling
+TEST_F(MemoryZoneTest, delegationNS) {
+ // add in-zone data
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
+
+ // install a zone cut
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_)));
+
+ // below the zone cut
+ findTest(Name("www.child.example.org"), RRType::A(), Zone::DELEGATION,
+ true, rr_child_ns_);
+
+ // at the zone cut
+ findTest(Name("child.example.org"), RRType::A(), Zone::DELEGATION,
+ true, rr_child_ns_);
+ findTest(Name("child.example.org"), RRType::NS(), Zone::DELEGATION,
+ true, rr_child_ns_);
+
+ // finding NS for the apex (origin) node. This must not be confused
+ // with delegation due to the existence of an NS RR.
+ findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
+
+ // unusual case of "nested delegation": the highest cut should be used.
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_grandchild_ns_)));
+ findTest(Name("www.grand.child.example.org"), RRType::A(),
+ Zone::DELEGATION, true, rr_child_ns_); // note: !rr_grandchild_ns_
+}
+
+TEST_F(MemoryZoneTest, findAny) {
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_a_)));
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_glue_)));
+
+ // origin
+ RRsetList origin_rrsets;
+ findTest(origin_, RRType::ANY(), Zone::SUCCESS, true,
+ ConstRRsetPtr(), &origin_rrsets);
+ EXPECT_EQ(2, origin_rrsets.size());
+ EXPECT_EQ(rr_a_, origin_rrsets.findRRset(RRType::A(), RRClass::IN()));
+ EXPECT_EQ(rr_ns_, origin_rrsets.findRRset(RRType::NS(), RRClass::IN()));
+
+ // out zone name
+ RRsetList out_rrsets;
+ findTest(Name("example.com"), RRType::ANY(), Zone::NXDOMAIN, true,
+ ConstRRsetPtr(), &out_rrsets);
+ EXPECT_EQ(0, out_rrsets.size());
+
+ RRsetList glue_child_rrsets;
+ findTest(rr_child_glue_->getName(), RRType::ANY(), Zone::SUCCESS, true,
+ ConstRRsetPtr(), &glue_child_rrsets);
+ EXPECT_EQ(rr_child_glue_, glue_child_rrsets.findRRset(RRType::A(),
+ RRClass::IN()));
+ EXPECT_EQ(1, glue_child_rrsets.size());
+
+ // TODO: test NXRRSET case after rbtree non-terminal logic has
+ // been implemented
+
+ // add zone cut
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_)));
+
+ // zone cut
+ RRsetList child_rrsets;
+ findTest(rr_child_ns_->getName(), RRType::ANY(), Zone::DELEGATION, true,
+ rr_child_ns_, &child_rrsets);
+ EXPECT_EQ(0, child_rrsets.size());
+
+ // glue for this zone cut
+ RRsetList new_glue_child_rrsets;
+ findTest(rr_child_glue_->getName(), RRType::ANY(), Zone::DELEGATION, true,
+ rr_child_ns_, &new_glue_child_rrsets);
+ EXPECT_EQ(0, new_glue_child_rrsets.size());
+}
+
+TEST_F(MemoryZoneTest, glue) {
+ // install zone data:
+ // a zone cut
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_)));
+ // glue for this cut
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_glue_)));
+ // a nested zone cut (unusual)
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_grandchild_ns_)));
+ // glue under the deeper zone cut
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_grandchild_glue_)));
+
+ // by default glue is hidden due to the zone cut
+ findTest(rr_child_glue_->getName(), RRType::A(), Zone::DELEGATION, true,
+ rr_child_ns_);
+
+
+ // If we do it in the "glue OK" mode, we should find the exact match.
+ findTest(rr_child_glue_->getName(), RRType::A(), Zone::SUCCESS, true,
+ rr_child_glue_, NULL, NULL, Zone::FIND_GLUE_OK);
+
+ // glue OK + NXRRSET case
+ findTest(rr_child_glue_->getName(), RRType::AAAA(), Zone::NXRRSET, true,
+ ConstRRsetPtr(), NULL, NULL, Zone::FIND_GLUE_OK);
+
+ // glue OK + NXDOMAIN case
+ findTest(Name("www.child.example.org"), RRType::A(), Zone::DELEGATION,
+ true, rr_child_ns_, NULL, NULL, Zone::FIND_GLUE_OK);
+
+ // nested cut case. The glue should be found.
+ findTest(rr_grandchild_glue_->getName(), RRType::AAAA(),
+ Zone::SUCCESS,
+ true, rr_grandchild_glue_, NULL, NULL, Zone::FIND_GLUE_OK);
+
+ // A non-existent name in nested cut. This should result in delegation
+ // at the highest zone cut.
+ findTest(Name("www.grand.child.example.org"), RRType::TXT(),
+ Zone::DELEGATION, true, rr_child_ns_, NULL, NULL,
+ Zone::FIND_GLUE_OK);
+}
+
+/**
+ * \brief Test searching.
+ *
+ * Check it finds or does not find correctly and does not throw exceptions.
+ * \todo This doesn't do any kind of CNAME and so on. If it isn't
+ * directly there, it just tells it doesn't exist.
+ */
+TEST_F(MemoryZoneTest, find) {
+ // Fill some data inside
+ // Now put all the data we have there. It should throw nothing
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_a_)));
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_aaaa_)));
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_a_)));
+
+ // These two should be successful
+ findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
+ findTest(rr_ns_a_->getName(), RRType::A(), Zone::SUCCESS, true, rr_ns_a_);
+
+ // These domain exist but don't have the provided RRType
+ findTest(origin_, RRType::AAAA(), Zone::NXRRSET);
+ findTest(rr_ns_a_->getName(), RRType::NS(), Zone::NXRRSET);
+
+ // These domains don't exist (and one is out of the zone)
+ findTest(Name("nothere.example.org"), RRType::A(), Zone::NXDOMAIN);
+ findTest(Name("example.net"), RRType::A(), Zone::NXDOMAIN);
+}
+
+TEST_F(MemoryZoneTest, emptyNode) {
+ /*
+ * The backend RBTree for this test should look like as follows:
+ * example.org
+ * |
+ * baz (empty; easy case)
+ * / | \
+ * bar | x.foo ('foo' part is empty; a bit trickier)
+ * bbb
+ * /
+ * aaa
+ */
+
+ // Construct the test zone
+ const char* const names[] = {
+ "bar.example.org", "x.foo.example.org", "aaa.baz.example.org",
+ "bbb.baz.example.org.", NULL};
+ for (int i = 0; names[i] != NULL; ++i) {
+ ConstRRsetPtr rrset(new RRset(Name(names[i]), class_, RRType::A(),
+ RRTTL(300)));
+ EXPECT_EQ(SUCCESS, zone_.add(rrset));
+ }
+
+ // empty node matching, easy case: the node for 'baz' exists with
+ // no data.
+ findTest(Name("baz.example.org"), RRType::A(), Zone::NXRRSET);
+
+ // empty node matching, a trickier case: the node for 'foo' is part of
+ // "x.foo", which should be considered an empty node.
+ findTest(Name("foo.example.org"), RRType::A(), Zone::NXRRSET);
+
+ // "org" is contained in "example.org", but it shouldn't be treated as
+ // NXRRSET because it's out of zone.
+ // Note: basically we don't expect such a query to be performed (the common
+ // operation is to identify the best matching zone first then perform
+ // search it), but we shouldn't be confused even in the unexpected case.
+ findTest(Name("org"), RRType::A(), Zone::NXDOMAIN);
+}
+
+TEST_F(MemoryZoneTest, load) {
+ // Put some data inside the zone
+ EXPECT_NO_THROW(EXPECT_EQ(result::SUCCESS, zone_.add(rr_ns_)));
+ // Loading with different origin should fail
+ EXPECT_THROW(zone_.load(TEST_DATA_DIR "/root.zone"), MasterLoadError);
+ // See the original data is still there, survived the exception
+ findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
+ // Create correct zone
+ MemoryZone rootzone(class_, Name("."));
+ // Try putting something inside
+ EXPECT_NO_THROW(EXPECT_EQ(result::SUCCESS, rootzone.add(rr_ns_aaaa_)));
+ // Load the zone. It should overwrite/remove the above RRset
+ EXPECT_NO_THROW(rootzone.load(TEST_DATA_DIR "/root.zone"));
+
+ // Now see there are some rrsets (we don't look inside, though)
+ findTest(Name("."), RRType::SOA(), Zone::SUCCESS, false, ConstRRsetPtr(),
+ NULL, &rootzone);
+ findTest(Name("."), RRType::NS(), Zone::SUCCESS, false, ConstRRsetPtr(),
+ NULL, &rootzone);
+ findTest(Name("a.root-servers.net."), RRType::A(), Zone::SUCCESS, false,
+ ConstRRsetPtr(), NULL, &rootzone);
+ // But this should no longer be here
+ findTest(rr_ns_a_->getName(), RRType::AAAA(), Zone::NXDOMAIN, true,
+ ConstRRsetPtr(), NULL, &rootzone);
+
+ // Try loading zone that is wrong in a different way
+ EXPECT_THROW(zone_.load(TEST_DATA_DIR "/duplicate_rrset.zone"),
+ MasterLoadError);
+}
+
+/*
+ * Test that puts a (simple) wildcard into the zone and checks we can
+ * correctly find the data.
+ */
+TEST_F(MemoryZoneTest, wildcard) {
+ /*
+ * example.org.
+ * |
+ * wild (not *.wild, should have wild mark)
+ * |
+ * *
+ */
+ EXPECT_EQ(SUCCESS, zone_.add(rr_wild_));
+
+ // Search at the parent. The parent will not have the A, but it will
+ // be in the wildcard (so check the wildcard isn't matched at the parent)
+ {
+ SCOPED_TRACE("Search at parrent");
+ findTest(Name("wild.example.org"), RRType::A(), Zone::NXRRSET);
+ }
+
+ // Search the original name of wildcard
+ {
+ SCOPED_TRACE("Search directly at *");
+ findTest(Name("*.wild.example.org"), RRType::A(), Zone::SUCCESS, true,
+ rr_wild_);
+ }
+ // Search "created" name.
+ {
+ SCOPED_TRACE("Search at created child");
+ findTest(Name("a.wild.example.org"), RRType::A(), Zone::SUCCESS, false,
+ rr_wild_, NULL, NULL, Zone::FIND_DEFAULT, true);
+ }
+
+ // Search another created name, this time little bit lower
+ {
+ SCOPED_TRACE("Search at created grand-child");
+ findTest(Name("a.b.wild.example.org"), RRType::A(), Zone::SUCCESS,
+ false, rr_wild_, NULL, NULL, Zone::FIND_DEFAULT, true);
+ }
+
+ EXPECT_EQ(SUCCESS, zone_.add(rr_under_wild_));
+ {
+ SCOPED_TRACE("Search under non-wildcard");
+ findTest(Name("bar.foo.wild.example.org"), RRType::A(),
+ Zone::NXDOMAIN);
+ }
+}
+
+/*
+ * Test that we don't match a wildcard if we get under delegation.
+ * By 4.3.3 of RFC1034:
+ * "Wildcard RRs do not apply:
+ * - When the query is in another zone. That is, delegation cancels
+ * the wildcard defaults."
+ */
+TEST_F(MemoryZoneTest, delegatedWildcard) {
+ EXPECT_EQ(SUCCESS, zone_.add(rr_child_wild_));
+ EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_));
+
+ {
+ SCOPED_TRACE("Looking under delegation point");
+ findTest(Name("a.child.example.org"), RRType::A(), Zone::DELEGATION,
+ true, rr_child_ns_);
+ }
+
+ {
+ SCOPED_TRACE("Looking under delegation point in GLUE_OK mode");
+ findTest(Name("a.child.example.org"), RRType::A(), Zone::DELEGATION,
+ true, rr_child_ns_, NULL, NULL, Zone::FIND_GLUE_OK);
+ }
+}
+
+// Tests combination of wildcard and ANY.
+TEST_F(MemoryZoneTest, anyWildcard) {
+ EXPECT_EQ(SUCCESS, zone_.add(rr_wild_));
+
+ // First try directly the name (normal match)
+ {
+ SCOPED_TRACE("Asking direcly for *");
+ RRsetList target;
+ findTest(Name("*.wild.example.org"), RRType::ANY(), Zone::SUCCESS,
+ true, ConstRRsetPtr(), &target);
+ ASSERT_EQ(1, target.size());
+ EXPECT_EQ(RRType::A(), (*target.begin())->getType());
+ EXPECT_EQ(Name("*.wild.example.org"), (*target.begin())->getName());
+ }
+
+ // Then a wildcard match
+ {
+ SCOPED_TRACE("Asking in the wild way");
+ RRsetList target;
+ findTest(Name("a.wild.example.org"), RRType::ANY(), Zone::SUCCESS,
+ true, ConstRRsetPtr(), &target);
+ ASSERT_EQ(1, target.size());
+ EXPECT_EQ(RRType::A(), (*target.begin())->getType());
+ EXPECT_EQ(Name("a.wild.example.org"), (*target.begin())->getName());
+ }
+}
+
+// Test there's nothing in the wildcard in the middle if we load
+// wild.*.foo.example.org.
+TEST_F(MemoryZoneTest, emptyWildcard) {
+ /*
+ * example.org.
+ * foo
+ * *
+ * wild
+ */
+ EXPECT_EQ(SUCCESS, zone_.add(rr_emptywild_));
+
+ {
+ SCOPED_TRACE("Asking for the original record under wildcard");
+ findTest(Name("wild.*.foo.example.org"), RRType::A(), Zone::SUCCESS,
+ true, rr_emptywild_);
+ }
+
+ {
+ SCOPED_TRACE("Asking for A record");
+ findTest(Name("a.foo.example.org"), RRType::A(), Zone::NXRRSET);
+ findTest(Name("*.foo.example.org"), RRType::A(), Zone::NXRRSET);
+ findTest(Name("foo.example.org"), RRType::A(), Zone::NXRRSET);
+ }
+
+ {
+ SCOPED_TRACE("Asking for ANY record");
+ RRsetList normalTarget;
+ findTest(Name("*.foo.example.org"), RRType::ANY(), Zone::NXRRSET, true,
+ ConstRRsetPtr(), &normalTarget);
+ EXPECT_EQ(0, normalTarget.size());
+
+ RRsetList wildTarget;
+ findTest(Name("a.foo.example.org"), RRType::ANY(), Zone::NXRRSET, true,
+ ConstRRsetPtr(), &wildTarget);
+ EXPECT_EQ(0, wildTarget.size());
+ }
+
+ {
+ SCOPED_TRACE("Asking on the non-terminal");
+ findTest(Name("wild.bar.foo.example.org"), RRType::A(),
+ Zone::NXRRSET);
+ }
+}
+
+// Same as emptyWildcard, but with multiple * in the path.
+TEST_F(MemoryZoneTest, nestedEmptyWildcard) {
+ EXPECT_EQ(SUCCESS, zone_.add(rr_nested_emptywild_));
+
+ {
+ SCOPED_TRACE("Asking for the original record under wildcards");
+ findTest(Name("wild.*.foo.*.bar.example.org"), RRType::A(),
+ Zone::SUCCESS, true, rr_nested_emptywild_);
+ }
+
+ {
+ SCOPED_TRACE("Matching wildcard against empty nonterminal");
+
+ const char* names[] = {
+ "baz.foo.*.bar.example.org",
+ "baz.foo.baz.bar.example.org",
+ "*.foo.baz.bar.example.org",
+ NULL
+ };
+
+ for (const char** name(names); *name != NULL; ++ name) {
+ SCOPED_TRACE(string("Node ") + *name);
+ findTest(Name(*name), RRType::A(), Zone::NXRRSET);
+ }
+ }
+
+ // Domains to test
+ const char* names[] = {
+ "*.foo.*.bar.example.org",
+ "foo.*.bar.example.org",
+ "*.bar.example.org",
+ "bar.example.org",
+ NULL
+ };
+
+ {
+ SCOPED_TRACE("Asking directly for A on parent nodes");
+
+ for (const char** name(names); *name != NULL; ++ name) {
+ SCOPED_TRACE(string("Node ") + *name);
+ findTest(Name(*name), RRType::A(), Zone::NXRRSET);
+ }
+ }
+
+ {
+ SCOPED_TRACE("Asking for ANY on parent nodes");
+
+ for (const char** name(names); *name != NULL; ++ name) {
+ SCOPED_TRACE(string("Node ") + *name);
+
+ RRsetList target;
+ findTest(Name(*name), RRType::ANY(), Zone::NXRRSET, true,
+ ConstRRsetPtr(), &target);
+ EXPECT_EQ(0, target.size());
+ }
+ }
+}
+
+// We run this part twice from the below test, in two slightly different
+// situations
+void
+MemoryZoneTest::doCancelWildcardTest() {
+ // These should be canceled
+ {
+ SCOPED_TRACE("Canceled under foo.wild.example.org");
+ findTest(Name("aaa.foo.wild.example.org"), RRType::A(),
+ Zone::NXDOMAIN);
+ findTest(Name("zzz.foo.wild.example.org"), RRType::A(),
+ Zone::NXDOMAIN);
+ }
+
+ // This is existing, non-wildcard domain, shouldn't wildcard at all
+ {
+ SCOPED_TRACE("Existing domain under foo.wild.example.org");
+ findTest(Name("bar.foo.wild.example.org"), RRType::A(), Zone::SUCCESS,
+ true, rr_not_wild_);
+ }
+
+ // These should be caught by the wildcard
+ {
+ SCOPED_TRACE("Neighbor wildcards to foo.wild.example.org");
+
+ const char* names[] = {
+ "aaa.bbb.wild.example.org",
+ "aaa.zzz.wild.example.org",
+ "zzz.wild.example.org",
+ NULL
+ };
+
+ for (const char** name(names); *name != NULL; ++ name) {
+ SCOPED_TRACE(string("Node ") + *name);
+
+ findTest(Name(*name), RRType::A(), Zone::SUCCESS, false, rr_wild_,
+ NULL, NULL, Zone::FIND_DEFAULT, true);
+ }
+ }
+
+ // This shouldn't be wildcarded, it's an existing domain
+ {
+ SCOPED_TRACE("The foo.wild.example.org itself");
+ findTest(Name("foo.wild.example.org"), RRType::A(), Zone::NXRRSET);
+ }
+}
+
+/*
+ * This tests that if there's a name between the wildcard domain and the
+ * searched one, it will not trigger wildcard, for example, if we have
+ * *.wild.example.org and bar.foo.wild.example.org, then we know
+ * foo.wild.example.org exists and is not wildcard. Therefore, search for
+ * aaa.foo.wild.example.org should return NXDOMAIN.
+ *
+ * Tests few cases "around" the canceled wildcard match, to see something that
+ * shouldn't be canceled isn't.
+ */
+TEST_F(MemoryZoneTest, cancelWildcard) {
+ EXPECT_EQ(SUCCESS, zone_.add(rr_wild_));
+ EXPECT_EQ(SUCCESS, zone_.add(rr_not_wild_));
+
+ {
+ SCOPED_TRACE("Runnig with single entry under foo.wild.example.org");
+ doCancelWildcardTest();
+ }
+
+ // Try putting another one under foo.wild....
+ // The result should be the same but it will be done in another way in the
+ // code, because the foo.wild.example.org will exist in the tree.
+ EXPECT_EQ(SUCCESS, zone_.add(rr_not_wild_another_));
+ {
+ SCOPED_TRACE("Runnig with two entries under foo.wild.example.org");
+ doCancelWildcardTest();
+ }
+}
+
+TEST_F(MemoryZoneTest, loadBadWildcard) {
+ // We reject loading the zone if it contains a wildcard name for
+ // NS or DNAME.
+ EXPECT_THROW(zone_.add(rr_nswild_), MemoryZone::AddError);
+ EXPECT_THROW(zone_.add(rr_dnamewild_), MemoryZone::AddError);
+}
+
+TEST_F(MemoryZoneTest, swap) {
+ // build one zone with some data
+ MemoryZone zone1(class_, origin_);
+ EXPECT_EQ(result::SUCCESS, zone1.add(rr_ns_));
+ EXPECT_EQ(result::SUCCESS, zone1.add(rr_ns_aaaa_));
+
+ // build another zone of a different RR class with some other data
+ const Name other_origin("version.bind");
+ ASSERT_NE(origin_, other_origin); // make sure these two are different
+ MemoryZone zone2(RRClass::CH(), other_origin);
+ EXPECT_EQ(result::SUCCESS,
+ zone2.add(RRsetPtr(new RRset(Name("version.bind"),
+ RRClass::CH(), RRType::TXT(),
+ RRTTL(0)))));
+
+ zone1.swap(zone2);
+ EXPECT_EQ(other_origin, zone1.getOrigin());
+ EXPECT_EQ(origin_, zone2.getOrigin());
+ EXPECT_EQ(RRClass::CH(), zone1.getClass());
+ EXPECT_EQ(RRClass::IN(), zone2.getClass());
+ // make sure the zone data is swapped, too
+ findTest(origin_, RRType::NS(), Zone::NXDOMAIN, false, ConstRRsetPtr(),
+ NULL, &zone1);
+ findTest(other_origin, RRType::TXT(), Zone::SUCCESS, false,
+ ConstRRsetPtr(), NULL, &zone1);
+ findTest(origin_, RRType::NS(), Zone::SUCCESS, false, ConstRRsetPtr(),
+ NULL, &zone2);
+ findTest(other_origin, RRType::TXT(), Zone::NXDOMAIN, false,
+ ConstRRsetPtr(), NULL, &zone2);
+}
+
+TEST_F(MemoryZoneTest, getFileName) {
+ // for an empty zone the file name should also be empty.
+ EXPECT_TRUE(zone_.getFileName().empty());
+
+ // if loading a zone fails the file name shouldn't be set.
+ EXPECT_THROW(zone_.load(TEST_DATA_DIR "/root.zone"), MasterLoadError);
+ EXPECT_TRUE(zone_.getFileName().empty());
+
+ // after a successful load, the specified file name should be set
+ MemoryZone rootzone(class_, Name("."));
+ EXPECT_NO_THROW(rootzone.load(TEST_DATA_DIR "/root.zone"));
+ EXPECT_EQ(TEST_DATA_DIR "/root.zone", rootzone.getFileName());
+ // overriding load, which will fail
+ EXPECT_THROW(rootzone.load(TEST_DATA_DIR "/duplicate_rrset.zone"),
+ MasterLoadError);
+ // the file name should be intact.
+ EXPECT_EQ(TEST_DATA_DIR "/root.zone", rootzone.getFileName());
+
+ // After swap, file names should also be swapped.
+ zone_.swap(rootzone);
+ EXPECT_EQ(TEST_DATA_DIR "/root.zone", zone_.getFileName());
+ EXPECT_TRUE(rootzone.getFileName().empty());
+}
+
+}
diff --git a/src/lib/datasrc/tests/query_unittest.cc b/src/lib/datasrc/tests/query_unittest.cc
index 951a433..fa7216b 100644
--- a/src/lib/datasrc/tests/query_unittest.cc
+++ b/src/lib/datasrc/tests/query_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <gtest/gtest.h>
#include <dns/buffer.h>
@@ -38,7 +36,7 @@ createQuery(Message& m, const Name& qname, const RRClass& qclass,
const RRType& qtype)
{
m.setOpcode(Opcode::QUERY());
- m.setHeaderFlag(MessageFlag::RD());
+ m.setHeaderFlag(Message::HEADERFLAG_RD);
m.addQuestion(Question(qname, qclass, qtype));
}
diff --git a/src/lib/datasrc/tests/rbtree_unittest.cc b/src/lib/datasrc/tests/rbtree_unittest.cc
new file mode 100644
index 0000000..dd1b7fe
--- /dev/null
+++ b/src/lib/datasrc/tests/rbtree_unittest.cc
@@ -0,0 +1,566 @@
+// 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 <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+
+#include <datasrc/rbtree.h>
+
+#include <dns/tests/unittest_util.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using isc::UnitTestUtil;
+using namespace isc::datasrc;
+
+// XXX: some compilers cannot find class static constants used in
+// EXPECT_xxx macros, for which we need an explicit empty definition.
+const size_t Name::MAX_LABELS;
+
+/* The initial structure of rbtree
+ *
+ * b
+ * / \
+ * a d.e.f
+ * / | \
+ * c | g.h
+ * | |
+ * w.y i
+ * / | \
+ * x | z
+ * | |
+ * p j
+ * / \
+ * o q
+ */
+
+namespace {
+class RBTreeTest : public::testing::Test {
+protected:
+ RBTreeTest() : rbtree_expose_empty_node(true), crbtnode(NULL) {
+ const char* const domain_names[] = {
+ "c", "b", "a", "x.d.e.f", "z.d.e.f", "g.h", "i.g.h", "o.w.y.d.e.f",
+ "j.z.d.e.f", "p.w.y.d.e.f", "q.w.y.d.e.f"};
+ int name_count = sizeof(domain_names) / sizeof(domain_names[0]);
+ for (int i = 0; i < name_count; ++i) {
+ rbtree.insert(Name(domain_names[i]), &rbtnode);
+ rbtnode->setData(RBNode<int>::NodeDataPtr(new int(i + 1)));
+
+ rbtree_expose_empty_node.insert(Name(domain_names[i]), &rbtnode);
+ rbtnode->setData(RBNode<int>::NodeDataPtr(new int(i + 1)));
+
+ }
+ }
+
+ RBTree<int> rbtree;
+ RBTree<int> rbtree_expose_empty_node;
+ RBNode<int>* rbtnode;
+ const RBNode<int>* crbtnode;
+};
+
+
+TEST_F(RBTreeTest, getNodeCount) {
+ EXPECT_EQ(13, rbtree.getNodeCount());
+}
+
+TEST_F(RBTreeTest, setGetData) {
+ rbtnode->setData(RBNode<int>::NodeDataPtr(new int(11)));
+ EXPECT_EQ(11, *(rbtnode->getData()));
+}
+
+TEST_F(RBTreeTest, insertNames) {
+ EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree.insert(Name("d.e.f"),
+ &rbtnode));
+ EXPECT_EQ(Name("d.e.f"), rbtnode->getName());
+ EXPECT_EQ(13, rbtree.getNodeCount());
+
+ //insert not exist node
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("."), &rbtnode));
+ EXPECT_EQ(Name("."), rbtnode->getName());
+ EXPECT_EQ(14, rbtree.getNodeCount());
+
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("example.com"), &rbtnode));
+ EXPECT_EQ(15, rbtree.getNodeCount());
+ rbtnode->setData(RBNode<int>::NodeDataPtr(new int(12)));
+
+ // return ALREADYEXISTS, since node "example.com" already has been explicitly inserted
+ EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree.insert(Name("example.com"), &rbtnode));
+ EXPECT_EQ(15, rbtree.getNodeCount());
+
+ // split the node "d.e.f"
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("k.e.f"), &rbtnode));
+ EXPECT_EQ(Name("k"), rbtnode->getName());
+ EXPECT_EQ(17, rbtree.getNodeCount());
+
+ // split the node "g.h"
+ EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree.insert(Name("h"), &rbtnode));
+ EXPECT_EQ(Name("h"), rbtnode->getName());
+ EXPECT_EQ(18, rbtree.getNodeCount());
+
+ // add child domain
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("m.p.w.y.d.e.f"), &rbtnode));
+ EXPECT_EQ(Name("m"), rbtnode->getName());
+ EXPECT_EQ(19, rbtree.getNodeCount());
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("n.p.w.y.d.e.f"), &rbtnode));
+ EXPECT_EQ(Name("n"), rbtnode->getName());
+ EXPECT_EQ(20, rbtree.getNodeCount());
+
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("l.a"), &rbtnode));
+ EXPECT_EQ(Name("l"), rbtnode->getName());
+ EXPECT_EQ(21, rbtree.getNodeCount());
+
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("r.d.e.f"), &rbtnode));
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("s.d.e.f"), &rbtnode));
+ EXPECT_EQ(23, rbtree.getNodeCount());
+
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("h.w.y.d.e.f"), &rbtnode));
+
+ // add more nodes one by one to cover leftRotate and rightRotate
+ EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree.insert(Name("f"), &rbtnode));
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("m"), &rbtnode));
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("nm"), &rbtnode));
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("om"), &rbtnode));
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("k"), &rbtnode));
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("l"), &rbtnode));
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("fe"), &rbtnode));
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("ge"), &rbtnode));
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("i"), &rbtnode));
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("ae"), &rbtnode));
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("n"), &rbtnode));
+}
+
+TEST_F(RBTreeTest, findName) {
+ // find const rbtnode
+ // exact match
+ EXPECT_EQ(RBTree<int>::EXACTMATCH, rbtree.find(Name("a"), &crbtnode));
+ EXPECT_EQ(Name("a"), crbtnode->getName());
+
+ // not found
+ EXPECT_EQ(RBTree<int>::NOTFOUND, rbtree.find(Name("d.e.f"), &crbtnode));
+ EXPECT_EQ(RBTree<int>::NOTFOUND, rbtree.find(Name("y.d.e.f"), &crbtnode));
+ EXPECT_EQ(RBTree<int>::NOTFOUND, rbtree.find(Name("x"), &crbtnode));
+ EXPECT_EQ(RBTree<int>::NOTFOUND, rbtree.find(Name("m.n"), &crbtnode));
+
+ // if we expose empty node, we can get the empty node created during insert
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ rbtree_expose_empty_node.find(Name("d.e.f"), &crbtnode));
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ rbtree_expose_empty_node.find(Name("w.y.d.e.f"), &crbtnode));
+
+ // partial match
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH, rbtree.find(Name("m.b"), &crbtnode));
+ EXPECT_EQ(Name("b"), crbtnode->getName());
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ rbtree_expose_empty_node.find(Name("m.d.e.f"), &crbtnode));
+
+ // find rbtnode
+ EXPECT_EQ(RBTree<int>::EXACTMATCH, rbtree.find(Name("q.w.y.d.e.f"), &rbtnode));
+ EXPECT_EQ(Name("q"), rbtnode->getName());
+}
+
+TEST_F(RBTreeTest, findError) {
+ // For the version that takes a node chain, the chain must be empty.
+ RBTreeNodeChain<int> chain;
+ EXPECT_EQ(RBTree<int>::EXACTMATCH, rbtree.find<void*>(Name("a"), &crbtnode,
+ chain, NULL, NULL));
+ // trying to reuse the same chain. it should result in an exception.
+ EXPECT_THROW(rbtree.find<void*>(Name("a"), &crbtnode, chain, NULL, NULL),
+ BadValue);
+}
+
+TEST_F(RBTreeTest, flags) {
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("flags.example"),
+ &rbtnode));
+
+ // by default, flags are all off
+ EXPECT_FALSE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+
+ // set operation, by default it enables the flag
+ rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK);
+ EXPECT_TRUE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+
+ // try disable the flag explicitly
+ rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK, false);
+ EXPECT_FALSE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+
+ // try enable the flag explicitly
+ rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK, true);
+ EXPECT_TRUE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+
+ // setting an unknown flag will trigger an exception
+ EXPECT_THROW(rbtnode->setFlag(static_cast<RBNode<int>::Flags>(2), true),
+ isc::InvalidParameter);
+}
+
+bool
+testCallback(const RBNode<int>&, bool* callack_checker) {
+ *callack_checker = true;
+ return (false);
+}
+
+TEST_F(RBTreeTest, callback) {
+ // by default callback isn't enabled
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("callback.example"),
+ &rbtnode));
+ rbtnode->setData(RBNode<int>::NodeDataPtr(new int(1)));
+ EXPECT_FALSE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+
+ // enable/re-disable callback
+ rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK);
+ EXPECT_TRUE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+ rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK, false);
+ EXPECT_FALSE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+
+ // enable again for subsequent tests
+ rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK);
+ // add more levels below and above the callback node for partial match.
+ RBNode<int>* subrbtnode;
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("sub.callback.example"),
+ &subrbtnode));
+ subrbtnode->setData(RBNode<int>::NodeDataPtr(new int(2)));
+ RBNode<int>* parentrbtnode;
+ EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree.insert(Name("example"),
+ &parentrbtnode));
+ // the chilld/parent nodes shouldn't "inherit" the callback flag.
+ // "rbtnode" may be invalid due to the insertion, so we need to re-find
+ // it.
+ EXPECT_EQ(RBTree<int>::EXACTMATCH, rbtree.find(Name("callback.example"),
+ &rbtnode));
+ EXPECT_TRUE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+ EXPECT_FALSE(subrbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+ EXPECT_FALSE(parentrbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+
+ // check if the callback is called from find()
+ RBTreeNodeChain<int> node_path1;
+ bool callback_called = false;
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ rbtree.find(Name("sub.callback.example"), &crbtnode, node_path1,
+ testCallback, &callback_called));
+ EXPECT_TRUE(callback_called);
+
+ // enable callback at the parent node, but it doesn't have data so
+ // the callback shouldn't be called.
+ RBTreeNodeChain<int> node_path2;
+ parentrbtnode->setFlag(RBNode<int>::FLAG_CALLBACK);
+ callback_called = false;
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ rbtree.find(Name("callback.example"), &crbtnode, node_path2,
+ testCallback, &callback_called));
+ EXPECT_FALSE(callback_called);
+}
+
+TEST_F(RBTreeTest, chainLevel) {
+ RBTreeNodeChain<int> chain;
+
+ // by default there should be no level in the chain.
+ EXPECT_EQ(0, chain.getLevelCount());
+
+ // insert one node to the tree and find it. there should be exactly
+ // one level in the chain.
+ RBTree<int> tree(true);
+ Name node_name(Name::ROOT_NAME());
+ EXPECT_EQ(RBTree<int>::SUCCESS, tree.insert(node_name, &rbtnode));
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ tree.find<void*>(node_name, &crbtnode, chain, NULL, NULL));
+ EXPECT_EQ(1, chain.getLevelCount());
+
+ /*
+ * Now creating a possibly deepest tree with MAX_LABELS levels.
+ * it should look like:
+ * (.)
+ * |
+ * 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 the root name (".") solely belongs to a single level,
+ * so the levels begin with 2.
+ */
+ 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;
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ tree.find<void*>(node_name, &crbtnode, found_chain,
+ NULL, NULL));
+ EXPECT_EQ(i, found_chain.getLevelCount());
+ }
+
+ // Confirm the last inserted name has the possible maximum length with
+ // maximum label count. This confirms the rbtree and chain level cannot
+ // be larger.
+ EXPECT_EQ(Name::MAX_LABELS, node_name.getLabelCount());
+ EXPECT_THROW(node_name.concatenate(Name("a.")), TooLongName);
+}
+
+TEST_F(RBTreeTest, getAbsoluteNameError) {
+ // an empty chain isn't allowed.
+ RBTreeNodeChain<int> chain;
+ EXPECT_THROW(chain.getAbsoluteName(), BadValue);
+}
+
+/*
+ *the domain order should be:
+ * a, b, c, d.e.f, x.d.e.f, w.y.d.e.f, o.w.y.d.e.f, p.w.y.d.e.f, q.w.y.d.e.f,
+ * z.d.e.f, j.z.d.e.f, g.h, i.g.h
+ * b
+ * / \
+ * a d.e.f
+ * / | \
+ * c | g.h
+ * | |
+ * w.y i
+ * / | \
+ * x | z
+ * | |
+ * p j
+ * / \
+ * o q
+ */
+TEST_F(RBTreeTest, nextNode) {
+ const char* const names[] = {
+ "a", "b", "c", "d.e.f", "x.d.e.f", "w.y.d.e.f", "o.w.y.d.e.f",
+ "p.w.y.d.e.f", "q.w.y.d.e.f", "z.d.e.f", "j.z.d.e.f", "g.h", "i.g.h"};
+ const int name_count = sizeof(names) / sizeof(names[0]);
+ RBTreeNodeChain<int> node_path;
+ const RBNode<int>* node = NULL;
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ rbtree.find<void*>(Name(names[0]), &node, node_path, NULL,
+ NULL));
+ for (int i = 0; i < name_count; ++i) {
+ EXPECT_NE(static_cast<void*>(NULL), node);
+ EXPECT_EQ(Name(names[i]), node_path.getAbsoluteName());
+ node = rbtree.nextNode(node_path);
+ }
+
+ // We should have reached the end of the tree.
+ EXPECT_EQ(static_cast<void*>(NULL), node);
+}
+
+TEST_F(RBTreeTest, nextNodeError) {
+ // Empty chain for nextNode() is invalid.
+ RBTreeNodeChain<int> chain;
+ EXPECT_THROW(rbtree.nextNode(chain), BadValue);
+}
+
+// A helper function for getLastComparedNode() below.
+void
+comparisonChecks(const RBTreeNodeChain<int>& chain,
+ int expected_order, int expected_common_labels,
+ NameComparisonResult::NameRelation expected_reln)
+{
+ if (expected_order > 0) {
+ EXPECT_LT(0, chain.getLastComparisonResult().getOrder());
+ } else if (expected_order < 0) {
+ EXPECT_GT(0, chain.getLastComparisonResult().getOrder());
+ } else {
+ EXPECT_EQ(0, chain.getLastComparisonResult().getOrder());
+ }
+ EXPECT_EQ(expected_common_labels,
+ chain.getLastComparisonResult().getCommonLabels());
+ EXPECT_EQ(expected_reln,
+ chain.getLastComparisonResult().getRelation());
+}
+
+TEST_F(RBTreeTest, getLastComparedNode) {
+ RBTree<int>& tree = rbtree_expose_empty_node; // use the "empty OK" mode
+ RBTreeNodeChain<int> chain;
+
+ // initially there should be no 'last compared'.
+ EXPECT_EQ(static_cast<void*>(NULL), chain.getLastComparedNode());
+
+ // A search for an empty tree should result in no 'last compared', too.
+ RBTree<int> empty_tree;
+ EXPECT_EQ(RBTree<int>::NOTFOUND,
+ empty_tree.find<void*>(Name("a"), &crbtnode, chain, NULL, NULL));
+ EXPECT_EQ(static_cast<void*>(NULL), chain.getLastComparedNode());
+ chain.clear();
+
+ const RBNode<int>* expected_node;
+
+ // Exact match case. The returned node should be last compared.
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ tree.find<void*>(Name("x.d.e.f"), &expected_node, chain,
+ NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // 2 = # labels of "x."
+ comparisonChecks(chain, 0, 2, NameComparisonResult::EQUAL);
+ chain.clear();
+
+ // Partial match, search stopped at the matching node, which should be
+ // the last compared node.
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ tree.find(Name("i.g.h"), &expected_node));
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ tree.find<void*>(Name("x.i.g.h"), &crbtnode, chain,
+ NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // i.g.h < x.i.g.h, 2 = # labels of "i."
+ comparisonChecks(chain, 1, 2, NameComparisonResult::SUBDOMAIN);
+ chain.clear();
+
+ // Partial match, search stopped in the subtree below the matching node
+ // after following a left branch.
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ tree.find(Name("x.d.e.f"), &expected_node));
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ tree.find<void*>(Name("a.d.e.f"), &crbtnode, chain,
+ NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // a < x, 1 = # labels of "." (trailing dot)
+ comparisonChecks(chain, -1, 1, NameComparisonResult::COMMONANCESTOR);
+ chain.clear();
+
+ // Partial match, search stopped in the subtree below the matching node
+ // after following a right branch.
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ tree.find(Name("z.d.e.f"), &expected_node));
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ tree.find<void*>(Name("zz.d.e.f"), &crbtnode, chain,
+ NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // zz > z, 1 = # labels of "." (trailing dot)
+ comparisonChecks(chain, 1, 1, NameComparisonResult::COMMONANCESTOR);
+ chain.clear();
+
+ // Partial match, search stopped at a node for a super domain of the
+ // search name in the subtree below the matching node.
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ tree.find(Name("w.y.d.e.f"), &expected_node));
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ tree.find<void*>(Name("y.d.e.f"), &crbtnode, chain,
+ NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // y < w.y, 2 = # labels of "y."
+ comparisonChecks(chain, -1, 2, NameComparisonResult::SUPERDOMAIN);
+ chain.clear();
+
+ // Partial match, search stopped at a node that share a common ancestor
+ // with the search name in the subtree below the matching node.
+ // (the expected node is the same as the previous case)
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ tree.find<void*>(Name("z.y.d.e.f"), &crbtnode, chain,
+ NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // z.y > w.y, 2 = # labels of "y."
+ comparisonChecks(chain, 1, 2, NameComparisonResult::COMMONANCESTOR);
+ chain.clear();
+
+ // Search stops in the highest level after following a left branch.
+ EXPECT_EQ(RBTree<int>::EXACTMATCH, tree.find(Name("c"), &expected_node));
+ EXPECT_EQ(RBTree<int>::NOTFOUND,
+ tree.find<void*>(Name("bb"), &crbtnode, chain, NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // bb < c, 1 = # labels of "." (trailing dot)
+ comparisonChecks(chain, -1, 1, NameComparisonResult::COMMONANCESTOR);
+ chain.clear();
+
+ // Search stops in the highest level after following a right branch.
+ // (the expected node is the same as the previous case)
+ EXPECT_EQ(RBTree<int>::NOTFOUND,
+ tree.find<void*>(Name("d"), &crbtnode, chain, NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // d > c, 1 = # labels of "." (trailing dot)
+ comparisonChecks(chain, 1, 1, NameComparisonResult::COMMONANCESTOR);
+ chain.clear();
+}
+
+TEST_F(RBTreeTest, dumpTree) {
+ std::ostringstream str;
+ std::ostringstream str2;
+ rbtree.dumpTree(str);
+ str2 << "tree has 13 node(s)\nb. (black)\n a. (black)\n NULL\n NULL\n d.e.f. (black)[invisible] \n begin down from d.e.f.\n w.y. (black)[invisible] \n begin down from w.y.\n p. (black)\n o. (red)\n NULL\n NULL\n q. (red)\n NULL\n NULL\n end down from w.y.\n x. (red)\n NULL\n NULL\n z. (red)\n begin down from z.\n j. (black)\n NULL\n NULL\n end down from z.\n NULL\n NULL\n end down from d.e.f.\n c. (red)\n NULL\n NULL\n g.h. (red)\n begin down from g.h.\n i. (black)\n
NULL\n NULL\n end down from g.h.\n NULL\n NULL\n";
+ EXPECT_EQ(str.str(), str2.str());
+}
+
+TEST_F(RBTreeTest, swap) {
+ // Store info about the first tree
+ std::ostringstream str1;
+ rbtree.dumpTree(str1);
+ size_t count1(rbtree.getNodeCount());
+
+ // Create second one and store state
+ RBTree<int> tree2;
+ RBNode<int>* node;
+ tree2.insert(Name("second"), &node);
+ std::ostringstream str2;
+ tree2.dumpTree(str2);
+
+ // Swap them
+ ASSERT_NO_THROW(tree2.swap(rbtree));
+
+ // Check their sizes
+ ASSERT_EQ(1, rbtree.getNodeCount());
+ ASSERT_EQ(count1, tree2.getNodeCount());
+
+ // And content
+ std::ostringstream out;
+ rbtree.dumpTree(out);
+ ASSERT_EQ(str2.str(), out.str());
+ out.str("");
+ 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/run_unittests.cc b/src/lib/datasrc/tests/run_unittests.cc
index 20d5673..a35a646 100644
--- a/src/lib/datasrc/tests/run_unittests.cc
+++ b/src/lib/datasrc/tests/run_unittests.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <gtest/gtest.h>
#include <dns/tests/unittest_util.h>
diff --git a/src/lib/datasrc/tests/sqlite3_unittest.cc b/src/lib/datasrc/tests/sqlite3_unittest.cc
index 59cbd42..ce9413d 100644
--- a/src/lib/datasrc/tests/sqlite3_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <algorithm>
@@ -244,7 +242,6 @@ checkRRset(RRsetPtr rrset, const Name& expected_name,
EXPECT_EQ(expected_rrttl, rrset->getTTL());
RdataIteratorPtr rdata_iterator = rrset->getRdataIterator();
- rdata_iterator->first();
vector<string>::const_iterator data_it = expected_data.begin();
for (; data_it != expected_data.end(); ++data_it) {
EXPECT_FALSE(rdata_iterator->isLast());
diff --git a/src/lib/datasrc/tests/static_unittest.cc b/src/lib/datasrc/tests/static_unittest.cc
index 38ed933..a11e889 100644
--- a/src/lib/datasrc/tests/static_unittest.cc
+++ b/src/lib/datasrc/tests/static_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <string>
#include <vector>
@@ -53,9 +51,10 @@ protected:
// (defined as PACKAGE_STRING in config.h)
version_data.push_back(PACKAGE_STRING);
- // XXX: in addition, the order the following items matter.
+ // NOTE: in addition, the order of the following items matter.
authors_data.push_back("Chen Zhengzhang");
authors_data.push_back("Evan Hunt");
+ authors_data.push_back("Haidong Wang");
authors_data.push_back("Han Feng");
authors_data.push_back("Jelte Jansen");
authors_data.push_back("Jeremy C. Reed");
@@ -63,10 +62,12 @@ protected:
authors_data.push_back("JINMEI Tatuya");
authors_data.push_back("Kazunori Fujiwara");
authors_data.push_back("Michael Graff");
+ authors_data.push_back("Michal Vaner");
authors_data.push_back("Naoki Kambe");
authors_data.push_back("Shane Kerr");
authors_data.push_back("Shen Tingting");
authors_data.push_back("Stephen Morris");
+ authors_data.push_back("Yoshitaka Aharen");
authors_data.push_back("Zhang Likun");
version_ns_data.push_back("version.bind.");
@@ -109,7 +110,6 @@ checkRRset(ConstRRsetPtr rrset, const Name& expected_name,
EXPECT_EQ(rrttl, rrset->getTTL());
RdataIteratorPtr rdata_iterator = rrset->getRdataIterator();
- rdata_iterator->first();
vector<string>::const_iterator data_it = expected_data.begin();
for (; data_it != expected_data.end(); ++data_it) {
EXPECT_FALSE(rdata_iterator->isLast());
diff --git a/src/lib/datasrc/tests/test_datasrc.cc b/src/lib/datasrc/tests/test_datasrc.cc
index f4de1b0..c18f0bd 100644
--- a/src/lib/datasrc/tests/test_datasrc.cc
+++ b/src/lib/datasrc/tests/test_datasrc.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config.h>
#include <cassert>
@@ -156,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="},
@@ -201,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"},
@@ -249,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}
};
@@ -275,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[] = {
@@ -288,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]);
@@ -307,7 +333,7 @@ vector<Zone> zones;
}
DataSrc::Result
-TestDataSrc::init(isc::data::ConstElementPtr config UNUSED_PARAM) {
+TestDataSrc::init(isc::data::ConstElementPtr) {
return (init());
}
@@ -408,7 +434,7 @@ copyRRset(RRsetPtr const source) {
RRsetPtr rrset = RRsetPtr(new RRset(source->getName(), source->getClass(),
source->getType(), source->getTTL()));
RdataIteratorPtr it = source->getRdataIterator();
- for (it->first(); !it->isLast(); it->next()) {
+ for (; !it->isLast(); it->next()) {
rrset->addRdata(it->getCurrent());
}
if (source->getRRsig()) {
@@ -623,10 +649,7 @@ TestDataSrc::findPreviousName(const Name& qname,
}
DataSrc::Result
-TestDataSrc::findCoveringNSEC3(const Name& zonename UNUSED_PARAM,
- string& hash UNUSED_PARAM,
- RRsetList& target UNUSED_PARAM) const
-{
+TestDataSrc::findCoveringNSEC3(const Name&, string&, RRsetList&) const {
return (NOT_IMPLEMENTED);
}
diff --git a/src/lib/datasrc/tests/test_datasrc.h b/src/lib/datasrc/tests/test_datasrc.h
index 8335ca1..d0d67fc 100644
--- a/src/lib/datasrc/tests/test_datasrc.h
+++ b/src/lib/datasrc/tests/test_datasrc.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __TEST_DATA_SOURCE_H
#define __TEST_DATA_SOURCE_H
diff --git a/src/lib/datasrc/tests/testdata/duplicate_rrset.zone b/src/lib/datasrc/tests/testdata/duplicate_rrset.zone
new file mode 100644
index 0000000..91b8e27
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/duplicate_rrset.zone
@@ -0,0 +1,4 @@
+example.org. 3600 IN SOA ns1.example.org. admin.example.org. 1234 3600 1800 2419200 7200
+example.org. 3600 IN NS ns1.example.org.
+example.org. 3600 IN MX 10 mail.example.org.
+example.org. 3600 IN NS ns2.example.org.
diff --git a/src/lib/datasrc/tests/testdata/mkbrokendb.c b/src/lib/datasrc/tests/testdata/mkbrokendb.c
index aff4cd4..58f055c 100644
--- a/src/lib/datasrc/tests/testdata/mkbrokendb.c
+++ b/src/lib/datasrc/tests/testdata/mkbrokendb.c
@@ -14,8 +14,6 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
-/* $Id$ */
-
/*
* This file is provided for reference purpose only. A broken DB file, named
* "brokendb.sqlite3" in this directory was created using this program.
diff --git a/src/lib/datasrc/tests/zonetable_unittest.cc b/src/lib/datasrc/tests/zonetable_unittest.cc
new file mode 100644
index 0000000..a117176
--- /dev/null
+++ b/src/lib/datasrc/tests/zonetable_unittest.cc
@@ -0,0 +1,115 @@
+// 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 <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <datasrc/zonetable.h>
+// We use MemoryZone to put something into the table
+#include <datasrc/memory_datasrc.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc;
+
+namespace {
+TEST(ZoneTest, init) {
+ MemoryZone zone(RRClass::IN(), Name("example.com"));
+ EXPECT_EQ(Name("example.com"), zone.getOrigin());
+ EXPECT_EQ(RRClass::IN(), zone.getClass());
+
+ MemoryZone ch_zone(RRClass::CH(), Name("example"));
+ EXPECT_EQ(Name("example"), ch_zone.getOrigin());
+ EXPECT_EQ(RRClass::CH(), ch_zone.getClass());
+}
+
+TEST(ZoneTest, find) {
+ MemoryZone zone(RRClass::IN(), Name("example.com"));
+ EXPECT_EQ(Zone::NXDOMAIN,
+ zone.find(Name("www.example.com"), RRType::A()).code);
+}
+
+class ZoneTableTest : public ::testing::Test {
+protected:
+ ZoneTableTest() : zone1(new MemoryZone(RRClass::IN(),
+ Name("example.com"))),
+ zone2(new MemoryZone(RRClass::IN(),
+ Name("example.net"))),
+ zone3(new MemoryZone(RRClass::IN(), Name("example")))
+ {}
+ ZoneTable zone_table;
+ ZonePtr zone1, zone2, zone3;
+};
+
+TEST_F(ZoneTableTest, addZone) {
+ EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone1));
+ EXPECT_EQ(result::EXIST, zone_table.addZone(zone1));
+ // names are compared in a case insensitive manner.
+ EXPECT_EQ(result::EXIST, zone_table.addZone(
+ ZonePtr(new MemoryZone(RRClass::IN(), Name("EXAMPLE.COM")))));
+
+ EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone2));
+ EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone3));
+
+ // Zone table is indexed only by name. Duplicate origin name with
+ // different zone class isn't allowed.
+ EXPECT_EQ(result::EXIST, zone_table.addZone(
+ ZonePtr(new MemoryZone(RRClass::CH(),
+ Name("example.com")))));
+
+ /// Bogus zone (NULL)
+ EXPECT_THROW(zone_table.addZone(ZonePtr()), isc::InvalidParameter);
+}
+
+TEST_F(ZoneTableTest, DISABLED_removeZone) {
+ EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone1));
+ EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone2));
+ EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone3));
+
+ EXPECT_EQ(result::SUCCESS, zone_table.removeZone(Name("example.net")));
+ EXPECT_EQ(result::NOTFOUND, zone_table.removeZone(Name("example.net")));
+}
+
+TEST_F(ZoneTableTest, findZone) {
+ EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone1));
+ EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone2));
+ EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone3));
+
+ EXPECT_EQ(result::SUCCESS, zone_table.findZone(Name("example.com")).code);
+ EXPECT_EQ(Name("example.com"),
+ zone_table.findZone(Name("example.com")).zone->getOrigin());
+
+ EXPECT_EQ(result::NOTFOUND,
+ zone_table.findZone(Name("example.org")).code);
+ EXPECT_EQ(ConstZonePtr(),
+ zone_table.findZone(Name("example.org")).zone);
+
+ // there's no exact match. the result should be the longest match,
+ // and the code should be PARTIALMATCH.
+ EXPECT_EQ(result::PARTIALMATCH,
+ zone_table.findZone(Name("www.example.com")).code);
+ EXPECT_EQ(Name("example.com"),
+ zone_table.findZone(Name("www.example.com")).zone->getOrigin());
+
+ // make sure the partial match is indeed the longest match by adding
+ // a zone with a shorter origin and query again.
+ ZonePtr zone_com(new MemoryZone(RRClass::IN(), Name("com")));
+ EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone_com));
+ EXPECT_EQ(Name("example.com"),
+ zone_table.findZone(Name("www.example.com")).zone->getOrigin());
+}
+}
diff --git a/src/lib/datasrc/zone.h b/src/lib/datasrc/zone.h
new file mode 100644
index 0000000..1252c94
--- /dev/null
+++ b/src/lib/datasrc/zone.h
@@ -0,0 +1,217 @@
+// 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 __ZONE_H
+#define __ZONE_H 1
+
+#include <datasrc/result.h>
+#include <dns/rrsetlist.h>
+
+namespace isc {
+namespace datasrc {
+
+/// \brief The base class for a single authoritative zone
+///
+/// The \c Zone class is an abstract base class for representing
+/// a DNS zone as part of data source.
+///
+/// At the moment this is provided mainly for making the \c ZoneTable class
+/// and the authoritative query logic testable, and only provides a minimal
+/// set of features.
+/// This is why this class is defined in the same header file, but it may
+/// have to move to a separate header file when we understand what is
+/// necessary for this class for actual operation.
+///
+/// The idea is to provide a specific derived zone class for each data
+/// source, beginning with in memory one. At that point the derived classes
+/// will have more specific features. For example, they will maintain
+/// information about the location of a zone file, whether it's loaded in
+/// memory, etc.
+///
+/// It's not yet clear how the derived zone classes work with various other
+/// data sources when we integrate these components, but one possibility is
+/// something like this:
+/// - If the underlying database such as some variant of SQL doesn't have an
+/// explicit representation of zones (as part of public interface), we can
+/// probably use a "default" zone class that simply encapsulates the
+/// corresponding data source and calls a common "find" like method.
+/// - Some data source may want to specialize it by inheritance as an
+/// optimization. For example, in the current schema design of the sqlite3
+/// data source, its (derived) zone class would contain the information of
+/// the "zone ID".
+///
+/// <b>Note:</b> Unlike some other abstract base classes we don't name the
+/// class beginning with "Abstract". This is because we want to have
+/// commonly used definitions such as \c Result and \c ZonePtr, and we want
+/// to make them look more intuitive.
+class Zone {
+public:
+ /// Result codes of the \c find() method.
+ ///
+ /// Note: the codes are tentative. We may need more, or we may find
+ /// some of them unnecessary as we implement more details.
+ enum Result {
+ SUCCESS, ///< An exact match is found.
+ DELEGATION, ///< The search encounters a zone cut.
+ NXDOMAIN, ///< There is no domain name that matches the search name
+ NXRRSET, ///< There is a matching name but no RRset of the search type
+ CNAME, ///< The search encounters and returns a CNAME RR
+ DNAME ///< The search encounters and returns a DNAME RR
+ };
+
+ /// A helper structure to represent the search result of \c find().
+ ///
+ /// This is a straightforward tuple of the result code and a pointer
+ /// to the found RRset to represent the result of \c find()
+ /// (there will be more members in the future - see the class
+ /// description).
+ /// We use this in order to avoid overloading the return value for both
+ /// the result code ("success" or "not found") and the found object,
+ /// i.e., avoid using \c NULL to mean "not found", etc.
+ ///
+ /// This is a simple value class whose internal state never changes,
+ /// so for convenience we allow the applications to refer to the members
+ /// directly.
+ ///
+ /// Note: we should eventually include a notion of "zone node", which
+ /// corresponds to a particular domain name of the zone, so that we can
+ /// find RRsets of a different RR type for that name (e.g. for type ANY
+ /// query or to include DS RRs with delegation).
+ ///
+ /// Note: we may also want to include the closest enclosure "node" to
+ /// optimize including the NSEC for no-wildcard proof (FWIW NSD does that).
+ struct FindResult {
+ FindResult(Result param_code,
+ const isc::dns::ConstRRsetPtr param_rrset) :
+ code(param_code), rrset(param_rrset)
+ {}
+ const Result code;
+ const isc::dns::ConstRRsetPtr rrset;
+ };
+
+ /// Find options.
+ ///
+ /// The option values are used as a parameter for \c find().
+ /// These are values of a bitmask type. Bitwise operations can be
+ /// performed on these values to express compound options.
+ enum FindOptions {
+ FIND_DEFAULT = 0, ///< The default options
+ FIND_GLUE_OK = 1 ///< Allow search under a zone cut
+ };
+
+ ///
+ /// \name Constructors and Destructor.
+ ///
+ //@{
+protected:
+ /// 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).
+ Zone() {}
+public:
+ /// The destructor.
+ virtual ~Zone() {}
+ //@}
+
+ ///
+ /// \name Getter Methods
+ ///
+ /// These methods should never throw an exception.
+ //@{
+ /// Return the origin name of the zone.
+ virtual const isc::dns::Name& getOrigin() const = 0;
+
+ /// Return the RR class of the zone.
+ virtual const isc::dns::RRClass& getClass() const = 0;
+ //@}
+
+ ///
+ /// \name Search Method
+ ///
+ //@{
+ /// Search the zone for a given pair of domain name and RR type.
+ ///
+ /// Each derived version of this method searches the underlying backend
+ /// for the data that best matches the given name and type.
+ /// This method is expected to be "intelligent", and identifies the
+ /// best possible answer for the search key. Specifically,
+ /// - If the search name belongs under a zone cut, it returns the code
+ /// of \c DELEGATION and the NS RRset at the zone cut.
+ /// - If there is no matching name, it returns the code of \c NXDOMAIN,
+ /// and, if DNSSEC is requested, the NSEC RRset that proves the
+ /// non-existence.
+ /// - If there is a matching name but no RRset of the search type, it
+ /// returns the code of \c NXRRSET, and, if DNSSEC is required,
+ /// the NSEC RRset for that name.
+ /// - If there is a CNAME RR of the searched name but there is no
+ /// RR of the searched type of the name (so this type is different from
+ /// CNAME), it returns the code of \c CNAME and that CNAME RR.
+ /// Note that if the searched RR type is CNAME, it is considered
+ /// a successful match, and the code of \c SUCCESS will be returned.
+ /// - If the search name matches a delegation point of DNAME, it returns
+ /// the code of \c DNAME and that DNAME RR.
+ /// - If the target isn't NULL, all RRsets under the domain are inserted
+ /// there and SUCCESS (or NXDOMAIN, in case of empty domain) is returned
+ /// instead of normall processing. This is intended to handle ANY query.
+ /// \note: this behavior is controversial as we discussed in
+ /// https://lists.isc.org/pipermail/bind10-dev/2011-January/001918.html
+ /// We should revisit the interface before we heavily rely on it.
+ ///
+ /// The \c options parameter specifies customized behavior of the search.
+ /// Their semantics is as follows:
+ /// - \c GLUE_OK Allow search under a zone cut. By default the search
+ /// will stop once it encounters a zone cut. If this option is specified
+ /// it remembers information about the highest zone cut and continues
+ /// the search until it finds an exact match for the given name or it
+ /// detects there is no exact match. If an exact match is found,
+ /// RRsets for that name are searched just like the normal case;
+ /// otherwise, if the search has encountered a zone cut, \c DELEGATION
+ /// with the information of the highest zone cut will be returned.
+ ///
+ /// A derived version of this method may involve internal resource
+ /// allocation, especially for constructing the resulting RRset, and may
+ /// throw an exception if it fails.
+ /// It throws DuplicateRRset exception if there are duplicate rrsets under
+ /// the same domain.
+ /// It should not throw other types of exceptions.
+ ///
+ /// \param name The domain name to be searched for.
+ /// \param type The RR type to be searched for.
+ /// \param target If target is not NULL, insert all RRs under the domain
+ /// into it.
+ /// \param options The search options.
+ /// \return A \c FindResult object enclosing the search result (see above).
+ virtual FindResult find(const isc::dns::Name& name,
+ const isc::dns::RRType& type,
+ isc::dns::RRsetList* target = NULL,
+ const FindOptions options
+ = FIND_DEFAULT) const = 0;
+ //@}
+};
+
+/// \brief A pointer-like type pointing to a \c Zone object.
+typedef boost::shared_ptr<Zone> ZonePtr;
+
+/// \brief A pointer-like type pointing to a \c Zone object.
+typedef boost::shared_ptr<const Zone> ConstZonePtr;
+
+}
+}
+
+#endif // __ZONE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/zonetable.cc b/src/lib/datasrc/zonetable.cc
new file mode 100644
index 0000000..bc09286
--- /dev/null
+++ b/src/lib/datasrc/zonetable.cc
@@ -0,0 +1,129 @@
+// 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 <cassert>
+
+#include <dns/name.h>
+
+#include <datasrc/zonetable.h>
+#include <datasrc/rbtree.h>
+
+using namespace std;
+using namespace isc::dns;
+
+namespace isc {
+namespace datasrc {
+
+/// \short Private data and implementation of ZoneTable
+struct ZoneTable::ZoneTableImpl {
+ // Type aliases to make it shorter
+ typedef RBTree<Zone> ZoneTree;
+ typedef RBNode<Zone> ZoneNode;
+ // The actual storage
+ ZoneTree zones_;
+
+ /*
+ * The implementation methods are here and just wrap-called in the
+ * ZoneTable. We have variables locally (without impl_->), have
+ * type aliases, etc. And they will get inlined anyway.
+ */
+
+ // Implementation of ZoneTable::addZone
+ result::Result addZone(ZonePtr zone) {
+ // Sanity check
+ if (!zone) {
+ isc_throw(InvalidParameter,
+ "Null pointer is passed to ZoneTable::addZone()");
+ }
+
+ // Get the node where we put the zone
+ ZoneNode* node(NULL);
+ switch (zones_.insert(zone->getOrigin(), &node)) {
+ // This is OK
+ case ZoneTree::SUCCESS:
+ case ZoneTree::ALREADYEXISTS:
+ break;
+ // Can Not Happen
+ default:
+ assert(0);
+ }
+ // Can Not Happen
+ assert(node);
+
+ // Is it empty? We either just created it or it might be nonterminal
+ if (node->isEmpty()) {
+ node->setData(zone);
+ return (result::SUCCESS);
+ } else { // There's something there already
+ return (result::EXIST);
+ }
+ }
+
+ // Implementation of ZoneTable::findZone
+ ZoneTable::FindResult findZone(const Name& name) const {
+ ZoneNode *node(NULL);
+ result::Result my_result;
+
+ // Translate the return codes
+ switch (zones_.find(name, &node)) {
+ case ZoneTree::EXACTMATCH:
+ my_result = result::SUCCESS;
+ break;
+ case ZoneTree::PARTIALMATCH:
+ my_result = result::PARTIALMATCH;
+ break;
+ // We have no data there, so translate the pointer to NULL as well
+ case ZoneTree::NOTFOUND:
+ return (FindResult(result::NOTFOUND, ZonePtr()));
+ // Can Not Happen
+ default:
+ assert(0);
+ // Because of warning
+ return (FindResult(result::NOTFOUND, ZonePtr()));
+ }
+
+ // Can Not Happen (remember, NOTFOUND is handled)
+ assert(node);
+
+ return (FindResult(my_result, node->getData()));
+ }
+};
+
+ZoneTable::ZoneTable() : impl_(new ZoneTableImpl)
+{}
+
+ZoneTable::~ZoneTable() {
+ delete impl_;
+}
+
+result::Result
+ZoneTable::addZone(ZonePtr zone) {
+ return (impl_->addZone(zone));
+}
+
+result::Result
+ZoneTable::removeZone(const Name&) {
+ // TODO Implement
+ assert(0);
+ // This should not ever be returned, the assert should kill us by now
+ return (result::SUCCESS);
+}
+
+ZoneTable::FindResult
+ZoneTable::findZone(const Name& name) const {
+ return (impl_->findZone(name));
+}
+
+} // end of namespace datasrc
+} // end of namespace isc
diff --git a/src/lib/datasrc/zonetable.h b/src/lib/datasrc/zonetable.h
new file mode 100644
index 0000000..5b873d1
--- /dev/null
+++ b/src/lib/datasrc/zonetable.h
@@ -0,0 +1,129 @@
+// 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 __ZONETABLE_H
+#define __ZONETABLE_H 1
+
+#include <boost/shared_ptr.hpp>
+
+#include <dns/rrset.h>
+
+#include <datasrc/zone.h>
+
+namespace isc {
+namespace dns {
+class Name;
+class RRClass;
+}
+
+namespace datasrc {
+
+/// \brief A set of authoritative zones.
+///
+/// \c ZoneTable class is primarily intended to be used as a backend for the
+/// \c MemoryDataSrc class, but is exposed as a separate class in case some
+/// application wants to use it directly (e.g. for a customized data source
+/// implementation).
+///
+/// For more descriptions about its struct and interfaces, please refer to the
+/// corresponding struct and interfaces of \c MemoryDataSrc.
+class ZoneTable {
+public:
+ struct FindResult {
+ FindResult(result::Result param_code, const ZonePtr param_zone) :
+ code(param_code), zone(param_zone)
+ {}
+ const result::Result code;
+ const ZonePtr zone;
+ };
+ ///
+ /// \name Constructors and Destructor.
+ ///
+ /// \b Note:
+ /// The copy constructor and the assignment operator are intentionally
+ /// defined as private, making this class non copyable.
+ //@{
+private:
+ ZoneTable(const ZoneTable& source);
+ ZoneTable& operator=(const ZoneTable& source);
+
+public:
+ /// Default constructor.
+ ///
+ /// This constructor internally involves resource allocation, and if
+ /// it fails, a corresponding standard exception will be thrown.
+ /// It never throws an exception otherwise.
+ ZoneTable();
+
+ /// The destructor.
+ ~ZoneTable();
+ //@}
+
+ /// Add a \c Zone to the \c ZoneTable.
+ ///
+ /// \c Zone must not be associated with a NULL pointer; otherwise
+ /// an exception of class \c InvalidParameter will be thrown.
+ /// If internal resource allocation fails, a corresponding standard
+ /// exception will be thrown.
+ /// This method never throws an exception otherwise.
+ ///
+ /// \param zone A \c Zone object to be added.
+ /// \return \c result::SUCCESS If the zone is successfully
+ /// added to the zone table.
+ /// \return \c result::EXIST The zone table already contains
+ /// zone of the same origin.
+ result::Result addZone(ZonePtr zone);
+
+ /// Remove a \c Zone of the given origin name from the \c ZoneTable.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param origin The origin name of the zone to be removed.
+ /// \return \c result::SUCCESS If the zone is successfully
+ /// removed from the zone table.
+ /// \return \c result::NOTFOUND The zone table does not
+ /// store the zone that matches \c origin.
+ result::Result removeZone(const isc::dns::Name& origin);
+
+ /// Find a \c Zone that best matches the given name in the \c ZoneTable.
+ ///
+ /// It searches the internal storage for a \c Zone that gives the
+ /// longest match against \c name, and returns the result in the
+ /// form of a \c FindResult object as follows:
+ /// - \c code: The result code of the operation.
+ /// - \c result::SUCCESS: A zone that gives an exact match
+ /// is found
+ /// - \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
+ /// is found; otherwise \c NULL.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param name A domain name for which the search is performed.
+ /// \return A \c FindResult object enclosing the search result (see above).
+ FindResult findZone(const isc::dns::Name& name) const;
+
+private:
+ struct ZoneTableImpl;
+ ZoneTableImpl* impl_;
+};
+}
+}
+#endif // __ZONETABLE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index f74370a..7d01dc4 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -13,6 +13,8 @@ EXTRA_DIST += rrtype-placeholder.h
# TODO: double-check that this is the only way
# NOTE: when an rdata file is added, please also add to this list:
+EXTRA_DIST += rdata/any_255/tsig_250.cc
+EXTRA_DIST += rdata/any_255/tsig_250.h
EXTRA_DIST += rdata/in_1/aaaa_28.cc
EXTRA_DIST += rdata/in_1/aaaa_28.h
EXTRA_DIST += rdata/in_1/a_1.cc
@@ -67,6 +69,7 @@ libdns___la_SOURCES += dnssectime.h dnssectime.cc
libdns___la_SOURCES += edns.h edns.cc
libdns___la_SOURCES += exceptions.h exceptions.cc
libdns___la_SOURCES += util/hex.h
+libdns___la_SOURCES += masterload.h masterload.cc
libdns___la_SOURCES += message.h message.cc
libdns___la_SOURCES += messagerenderer.h messagerenderer.cc
libdns___la_SOURCES += name.h name.cc
@@ -82,7 +85,9 @@ libdns___la_SOURCES += rrttl.h rrttl.cc
libdns___la_SOURCES += rrtype.cc
libdns___la_SOURCES += question.h question.cc
libdns___la_SOURCES += util/sha1.h util/sha1.cc
-libdns___la_SOURCES += tsig.h tsig.cc
+libdns___la_SOURCES += tsigkey.h tsigkey.cc
+libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
+libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc
nodist_libdns___la_SOURCES = rdataclass.cc rrclass.h rrtype.h
nodist_libdns___la_SOURCES += rrparamregistry.cc
@@ -97,11 +102,13 @@ libdns___includedir = $(includedir)/dns
libdns___include_HEADERS = \
buffer.h \
dnssectime.h \
+ edns.h \
exceptions.h \
message.h \
messagerenderer.h \
name.h \
question.h \
+ rcode.h \
rdata.h \
rdataclass.h \
rrclass.h \
@@ -110,8 +117,9 @@ libdns___include_HEADERS = \
rrsetlist.h \
rrttl.h \
rrtype.h \
- tsig.h
+ tsigkey.h
# Purposely not installing these headers:
# util/*.h: used only internally, and not actually DNS specific
+# rdata/*/detail/*.h: these are internal use only
# rrclass-placeholder.h
# rrtype-placeholder.h
diff --git a/src/lib/dns/buffer.h b/src/lib/dns/buffer.h
index 2fb6e0b..c824154 100644
--- a/src/lib/dns/buffer.h
+++ b/src/lib/dns/buffer.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __BUFFER_H
#define __BUFFER_H 1
@@ -25,6 +23,8 @@
#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
+
namespace isc {
namespace dns {
@@ -356,6 +356,21 @@ public:
/// \param data The 8-bit integer to be written into the buffer.
void writeUint8(uint8_t data) { data_.push_back(data); }
+ /// \brief Write an unsigned 8-bit integer into the buffer.
+ ///
+ /// The position must be lower than the size of the buffer,
+ /// otherwise an exception of class \c isc::dns::InvalidBufferPosition
+ /// will be thrown.
+ ///
+ /// \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()) {
+ isc_throw(InvalidBufferPosition, "write at invalid position");
+ }
+ data_[pos] = data;
+ }
+
/// \brief Write an unsigned 16-bit integer in host byte order into the
/// buffer in network byte order.
///
@@ -412,6 +427,16 @@ public:
private:
std::vector<uint8_t> data_;
};
+
+/// \brief Pointer-like types pointing to \c InputBuffer or \c OutputBuffer
+///
+/// These types are expected to be used as an argument in asynchronous
+/// callback functions. The internal reference-counting will ensure that
+/// that ongoing state information will not be lost if the object
+/// that originated the asynchronous call falls out of scope.
+typedef boost::shared_ptr<InputBuffer> InputBufferPtr;
+typedef boost::shared_ptr<OutputBuffer> OutputBufferPtr;
+
}
}
#endif // __BUFFER_H
diff --git a/src/lib/dns/dnssectime.cc b/src/lib/dns/dnssectime.cc
index 856130a..c889178 100644
--- a/src/lib/dns/dnssectime.cc
+++ b/src/lib/dns/dnssectime.cc
@@ -12,7 +12,9 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
+#include <stdint.h>
+
+#include <sys/time.h>
#include <string>
#include <iomanip>
@@ -28,30 +30,121 @@
using namespace std;
+namespace {
+int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+inline bool
+isLeap(const int y) {
+ return ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0);
+}
+
+unsigned int
+yearSecs(const int year) {
+ return ((isLeap(year) ? 366 : 365 ) * 86400);
+}
+
+unsigned int
+monthSecs(const int month, const int year) {
+ return ((days[month] + ((month == 1 && isLeap(year)) ? 1 : 0 )) * 86400);
+}
+}
+
namespace isc {
namespace dns {
string
-timeToText(const time_t timeval) {
- struct tm* const t = gmtime(&timeval);
-
- // gmtime() will keep most values within range, but it can
- // produce a five-digit year; check for this.
- if ((t->tm_year + 1900) > 9999) {
- isc_throw(InvalidTime, "Time value out of range: year > 9999");
+timeToText64(uint64_t value) {
+ struct tm tm;
+ unsigned int secs;
+
+ // We cannot rely on gmtime() because time_t may not be of 64 bit
+ // integer. The following conversion logic is borrowed from BIND 9.
+ tm.tm_year = 70;
+ while ((secs = yearSecs(tm.tm_year + 1900)) <= value) {
+ value -= secs;
+ ++tm.tm_year;
+ if (tm.tm_year + 1900 > 9999) {
+ isc_throw(InvalidTime,
+ "Time value out of range (year > 9999): " <<
+ tm.tm_year + 1900);
+ }
+ }
+ tm.tm_mon = 0;
+ while ((secs = monthSecs(tm.tm_mon, tm.tm_year + 1900)) <= value) {
+ value -= secs;
+ tm.tm_mon++;
+ }
+ tm.tm_mday = 1;
+ while (86400 <= value) {
+ value -= 86400;
+ ++tm.tm_mday;
}
+ tm.tm_hour = 0;
+ while (3600 <= value) {
+ value -= 3600;
+ ++tm.tm_hour;
+ }
+ tm.tm_min = 0;
+ while (60 <= value) {
+ value -= 60;
+ ++tm.tm_min;
+ }
+ tm.tm_sec = value; // now t < 60, so this substitution is safe.
ostringstream oss;
oss << setfill('0')
- << setw(4) << t->tm_year + 1900
- << setw(2) << t->tm_mon + 1
- << setw(2) << t->tm_mday
- << setw(2) << t->tm_hour
- << setw(2) << t->tm_min
- << setw(2) << t->tm_sec;
+ << setw(4) << tm.tm_year + 1900
+ << setw(2) << tm.tm_mon + 1
+ << setw(2) << tm.tm_mday
+ << setw(2) << tm.tm_hour
+ << setw(2) << tm.tm_min
+ << setw(2) << tm.tm_sec;
return (oss.str());
}
+// timeToText32() below uses the current system time. To test it with
+// unusual current time values we introduce the following function pointer;
+// when it's non NULL, we call it to get the (normally faked) current time.
+// Otherwise we use the standard gettimeofday(2). This hook is specifically
+// intended for testing purposes, so, even if it's visible outside of this
+// library, it's not even declared in a header file.
+namespace dnssectime {
+namespace detail {
+int64_t (*gettimeFunction)() = NULL;
+}
+}
+
+namespace {
+int64_t
+gettimeofdayWrapper() {
+ using namespace dnssectime::detail;
+ if (gettimeFunction != NULL) {
+ return (gettimeFunction());
+ }
+
+ struct timeval now;
+ gettimeofday(&now, NULL);
+
+ return (static_cast<int64_t>(now.tv_sec));
+}
+}
+
+string
+timeToText32(const uint32_t value) {
+ // We first adjust the time to the closest epoch based on the current time.
+ // Note that the following variables must be signed in order to handle
+ // time until year 2038 correctly.
+ const int64_t start = gettimeofdayWrapper() - 0x7fffffff;
+ int64_t base = 0;
+ int64_t t;
+ while ((t = (base + value)) < start) {
+ base += 0x100000000LL;
+ }
+
+ // Then convert it to text.
+ return (timeToText64(t));
+}
+
namespace {
const size_t DATE_LEN = 14; // YYYYMMDDHHmmSS
@@ -64,27 +157,20 @@ checkRange(const int min, const int max, const int value,
}
isc_throw(InvalidTime, "Invalid " << valname << "value: " << value);
}
-
-int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
-
-inline bool
-isLeap(const int y) {
- return ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0);
-}
}
-time_t
-timeFromText(const string& time_txt) {
- // first try reading YYYYMMDDHHmmSS format
- int year, month, day, hour, minute, second;
-
+uint64_t
+timeFromText64(const string& time_txt) {
+ // Confirm the source only consists digits. sscanf() allows some
+ // minor exceptions.
for (int i = 0; i < time_txt.length(); ++i) {
if (!isdigit(time_txt.at(i))) {
- isc_throw(InvalidTime,
- "Couldn't convert non-numeric time value: " << time_txt);
+ isc_throw(InvalidTime, "Couldn't convert non-numeric time value: "
+ << time_txt);
}
}
+ int year, month, day, hour, minute, second;
if (time_txt.length() != DATE_LEN ||
sscanf(time_txt.c_str(), "%4d%2d%2d%2d%2d%2d",
&year, &month, &day, &hour, &minute, &second) != 6)
@@ -100,9 +186,9 @@ timeFromText(const string& time_txt) {
checkRange(0, 59, minute, "minute");
checkRange(0, 60, second, "second"); // 60 == leap second.
- time_t timeval = second + (60 * minute) + (3600 * hour) +
+ uint64_t timeval = second + (60 * minute) + (3600 * hour) +
((day - 1) * 86400);
- for (int m = 0; m < (month - 1); m++) {
+ for (int m = 0; m < (month - 1); ++m) {
timeval += days[m] * 86400;
}
if (isLeap(year) && month > 2) {
@@ -114,5 +200,12 @@ timeFromText(const string& time_txt) {
return (timeval);
}
+
+uint32_t
+timeFromText32(const string& time_txt) {
+ // The implicit conversion from uint64_t to uint32_t should just work here,
+ // because we only need to drop higher 32 bits.
+ return (timeFromText64(time_txt));
+}
}
}
diff --git a/src/lib/dns/dnssectime.h b/src/lib/dns/dnssectime.h
index 6999640..baf866f 100644
--- a/src/lib/dns/dnssectime.h
+++ b/src/lib/dns/dnssectime.h
@@ -12,14 +12,11 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __DNSSECTIME_H
#define __DNSSECTIME_H 1
#include <sys/types.h>
#include <stdint.h>
-#include <time.h>
#include <exceptions/exceptions.h>
@@ -42,11 +39,102 @@ public:
isc::Exception(file, line, what) {}
};
-time_t
-timeFromText(const std::string& time_txt);
+///
+/// \name DNSSEC time conversion functions.
+///
+/// These functions convert between times represented in seconds (in integer)
+/// since epoch and those in the textual form used in the RRSIG records.
+/// For integers we provide both 32-bit and 64-bit versions.
+/// The RRSIG expiration and inception fields are both 32-bit unsigned
+/// integers, so 32-bit versions would be more useful for protocol operations.
+/// However, with 32-bit integers we need to take into account wrap-around
+/// points and compare values using the serial number arithmetic as specified
+/// in RFC4034, which would be more error prone. We therefore provide 64-bit
+/// versions, too.
+///
+/// The timezone is always UTC for these functions.
+//@{
+/// Convert textual DNSSEC time to integer, 64-bit version.
+///
+/// The textual form must only consist of digits and be in the form of
+/// YYYYMMDDHHmmSS, where:
+/// - YYYY must be between 1970 and 9999
+/// - MM must be between 01 and 12
+/// - DD must be between 01 and 31 and must be a valid day for the month
+/// represented in 'MM'. For example, if MM is 04, DD cannot be 31.
+/// DD can be 29 when MM is 02 only when YYYY is a leap year.
+/// - HH must be between 00 and 23
+/// - mm must be between 00 and 59
+/// - SS must be between 00 and 60
+///
+/// For all fields the range includes the begin and end values. Note that
+/// 60 is allowed for 'SS', intending a leap second, although in real operation
+/// it's unlikely to be specified.
+///
+/// If the given text is valid, this function converts it to an unsigned
+/// 64-bit number of seconds since epoch (1 January 1970 00:00:00) and returns
+/// the converted value. 64 bits are sufficient to represent all possible
+/// values for the valid format uniquely, so there is no overflow.
+///
+/// \note RFC4034 also defines the textual form of an unsigned decimal integer
+/// for the corresponding time in seconds. This function doesn't support
+/// this form, and if given it throws an exception of class \c InvalidTime.
+///
+/// \exception InvalidTime The given textual representation is invalid.
+///
+/// \param time_txt Textual time in the form of YYYYMMDDHHmmSS
+/// \return Seconds since epoch corresponding to \c time_txt
+uint64_t
+timeFromText64(const std::string& time_txt);
+
+/// Convert textual DNSSEC time to integer, 32-bit version.
+///
+/// This version is the same as \c timeFromText64() except that the return
+/// value is wrapped around to an unsigned 32-bit integer, simply dropping
+/// the upper 32 bits.
+uint32_t
+timeFromText32(const std::string& time_txt);
+
+/// Convert integral DNSSEC time to textual form, 64-bit version.
+///
+/// This function takes an integer that would be seconds since epoch and
+/// converts it in the form of YYYYMMDDHHmmSS. For example, if \c value is
+/// 0, it returns "19700101000000". If the value corresponds to a point
+/// of time on and after year 10,000, which cannot be represented in the
+/// YYYY... form, an exception of class \c InvalidTime will be thrown.
+///
+/// \exception InvalidTime The given time specifies on or after year 10,000.
+/// \exception Other A standard exception, if resource allocation for the
+/// returned text fails.
+///
+/// \param value Seconds since epoch to be converted.
+/// \return Textual representation of \c value in the form of YYYYMMDDHHmmSS.
+std::string
+timeToText64(uint64_t value);
+/// Convert integral DNSSEC time to textual form, 32-bit version.
+///
+/// This version is the same as \c timeToText64(), but the time value
+/// is expected to be the lower 32 bits of the full 64-bit value.
+/// These two will be different on and after a certain point of time
+/// in year 2106, so this function internally resolves the ambiguity
+/// using the current system time at the time of function call;
+/// it first identifies the range of [N*2^32 - 2^31, N*2^32 + 2^31)
+/// that contains the current time, and interprets \c value in the context
+/// of that range. It then applies the same process as \c timeToText64().
+///
+/// There is one important exception in this processing, however.
+/// Until 19 Jan 2038 03:14:08 (2^31 seconds since epoch), this range
+/// would contain time before epoch. In order to ensure the returned
+/// value is also a valid input to \c timeFromText, this function uses
+/// a special range [0, 2^32) until that time. As a result, all upper
+/// half of the 32-bit values are treated as a future time. For example,
+/// 2^32-1 (the highest value in 32-bit unsigned integers) will be converted
+/// to "21060207062815", instead of "19691231235959".
std::string
-timeToText(const time_t timeval);
+timeToText32(const uint32_t value);
+
+//@}
}
}
diff --git a/src/lib/dns/edns.cc b/src/lib/dns/edns.cc
index d0c14cb..6e25624 100644
--- a/src/lib/dns/edns.cc
+++ b/src/lib/dns/edns.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config.h>
#include <stdint.h>
@@ -74,7 +72,7 @@ EDNS::EDNS(const uint8_t version) :
}
EDNS::EDNS(const Name& name, const RRClass& rrclass, const RRType& rrtype,
- const RRTTL& ttl, const Rdata& rdata UNUSED_PARAM) :
+ const RRTTL& ttl, const Rdata&) :
version_((ttl.getValue() & VERSION_MASK) >> VERSION_SHIFT)
{
if (rrtype != RRType::OPT()) {
@@ -152,7 +150,7 @@ EDNS::toWire(OutputBuffer& buffer, const uint8_t extended_rcode) const {
EDNS*
createEDNSFromRR(const Name& name, const RRClass& rrclass,
const RRType& rrtype, const RRTTL& ttl,
- const Rdata& rdata UNUSED_PARAM,
+ const Rdata& rdata,
uint8_t& extended_rcode)
{
// Create a new EDNS object first for exception guarantee.
diff --git a/src/lib/dns/edns.h b/src/lib/dns/edns.h
index 9ba9663..ca3db47 100644
--- a/src/lib/dns/edns.h
+++ b/src/lib/dns/edns.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __EDNS_H
#define __EDNS_H 1
@@ -215,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);
@@ -420,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/exceptions.cc b/src/lib/dns/exceptions.cc
index 61bbaa4..eb823b9 100644
--- a/src/lib/dns/exceptions.cc
+++ b/src/lib/dns/exceptions.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/exceptions.h>
#include <dns/rcode.h>
diff --git a/src/lib/dns/exceptions.h b/src/lib/dns/exceptions.h
index c540d05..bd696a5 100644
--- a/src/lib/dns/exceptions.h
+++ b/src/lib/dns/exceptions.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
// XXX: we have another exceptions.h, so we need to use a prefix "DNS_" in
// the include guard. More preferably, we should define a consistent naming
// style for the header guide (e.g. module-name_file-name_H) throughout the
diff --git a/src/lib/dns/gen-rdatacode.py.in b/src/lib/dns/gen-rdatacode.py.in
index 66fa4b7..69d4680 100755
--- a/src/lib/dns/gen-rdatacode.py.in
+++ b/src/lib/dns/gen-rdatacode.py.in
@@ -14,8 +14,6 @@
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
"""\
This is a supplemental script to (half) auto-generate DNS Rdata related
classes and constants.
diff --git a/src/lib/dns/masterload.cc b/src/lib/dns/masterload.cc
new file mode 100644
index 0000000..5f7d5a0
--- /dev/null
+++ b/src/lib/dns/masterload.cc
@@ -0,0 +1,162 @@
+// 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 <istream>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <cctype>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/masterload.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/rrtype.h>
+
+using namespace std;
+using namespace boost;
+using namespace isc::dns::rdata;
+
+namespace isc {
+namespace dns {
+void
+masterLoad(const char* const filename, const Name& origin,
+ const RRClass& zone_class, MasterLoadCallback callback)
+{
+ ifstream ifs;
+
+ ifs.open(filename, ios_base::in);
+ if (ifs.fail()) {
+ isc_throw(MasterLoadError, "Failed to open master file: " << filename);
+ }
+ masterLoad(ifs, origin, zone_class, callback);
+ ifs.close();
+}
+
+void
+masterLoad(istream& input, const Name& origin, const RRClass& zone_class,
+ MasterLoadCallback callback)
+{
+ RRsetPtr rrset;
+ string line;
+ unsigned int line_count = 1;
+
+ do {
+ getline(input, line);
+ if (input.bad() || (input.fail() && !input.eof())) {
+ isc_throw(MasterLoadError, "Unexpectedly failed to read a line");
+ }
+
+ // blank/comment lines should be simply skipped.
+ if (line.empty() || line[0] == ';') {
+ continue;
+ }
+
+ // The line shouldn't have leading space (which means omitting the
+ // owner name).
+ if (isspace(line[0])) {
+ isc_throw(MasterLoadError, "Leading space at line " << line_count);
+ }
+
+ // Parse a single RR
+ istringstream iss(line);
+ string owner_txt, ttl_txt, rrclass_txt, rrtype_txt;
+ stringbuf rdatabuf;
+ iss >> owner_txt >> ttl_txt >> rrclass_txt >> rrtype_txt >> &rdatabuf;
+ if (iss.bad() || iss.fail()) {
+ isc_throw(MasterLoadError, "Parse failure for a valid RR at line "
+ << line_count);
+ }
+
+ // This simple version doesn't support relative owner names with a
+ // separate origin.
+ if (owner_txt.empty() || *(owner_txt.end() - 1) != '.') {
+ isc_throw(MasterLoadError, "Owner name is not absolute at line "
+ << line_count);
+ }
+
+ // XXX: this part is a bit tricky (and less efficient). We are going
+ // to validate the text for the RR parameters, and throw an exception
+ // if any of them is invalid by converting an underlying exception
+ // to MasterLoadError. To do that, we need to define the corresponding
+ // variables used for RRset construction outside the try-catch block,
+ // but we don't like to use a temporary variable with a meaningless
+ // initial value. So we define pointers outside the try block
+ // and allocate/initialize the actual objects within the block.
+ // To make it exception safe we use Boost.scoped_ptr.
+ scoped_ptr<const Name> owner;
+ scoped_ptr<const RRTTL> ttl;
+ scoped_ptr<const RRClass> rrclass;
+ scoped_ptr<const RRType> rrtype;
+ ConstRdataPtr rdata;
+ try {
+ owner.reset(new Name(owner_txt));
+ ttl.reset(new RRTTL(ttl_txt));
+ rrclass.reset(new RRClass(rrclass_txt));
+ rrtype.reset(new RRType(rrtype_txt));
+ rdata = createRdata(*rrtype, *rrclass, rdatabuf.str());
+ } catch (const Exception& ex) {
+ isc_throw(MasterLoadError, "Invalid RR text at line " << line_count
+ << ": " << ex.what());
+ }
+
+ // Origin related validation:
+ // - reject out-of-zone data
+ // - reject SOA whose owner is not at the top of zone
+ const NameComparisonResult cmp_result = owner->compare(origin);
+ if (cmp_result.getRelation() != NameComparisonResult::EQUAL &&
+ cmp_result.getRelation() != NameComparisonResult::SUBDOMAIN) {
+ isc_throw(MasterLoadError, "Out-of-zone data at line "
+ << line_count);
+ }
+ if (*rrtype == RRType::SOA() &&
+ cmp_result.getRelation() != NameComparisonResult::EQUAL) {
+ isc_throw(MasterLoadError, "SOA not at top of zone at line "
+ << line_count);
+ }
+
+ // Reject RR class mismatching
+ if (*rrclass != zone_class) {
+ isc_throw(MasterLoadError, "RR class (" << rrclass_txt
+ << ") does not match the zone class (" << zone_class
+ << ") at line " << line_count);
+ }
+
+ // Everything is okay. Now create/update RRset with the new RR.
+ // If this is the first RR or the RR type/name is new, we are seeing
+ // a new RRset.
+ if (!rrset || rrset->getType() != *rrtype ||
+ rrset->getName() != *owner) {
+ // Commit the previous RRset, if any.
+ if (rrset) {
+ callback(rrset);
+ }
+ rrset = RRsetPtr(new RRset(*owner, *rrclass, *rrtype, *ttl));
+ }
+ rrset->addRdata(rdata);
+ } while (++line_count, !input.eof());
+
+ // Commit the last RRset, if any.
+ if (rrset) {
+ callback(rrset);
+ }
+}
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/masterload.h b/src/lib/dns/masterload.h
new file mode 100644
index 0000000..fe5c08f
--- /dev/null
+++ b/src/lib/dns/masterload.h
@@ -0,0 +1,246 @@
+// 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 __MASTERLOAD_H
+#define __MASTERLOAD_H 1
+
+#include <iosfwd>
+
+#include <boost/function.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rrset.h>
+
+namespace isc {
+namespace dns {
+class Name;
+class RRClass;
+
+/// \brief An exception that is thrown if an error occurs while loading a
+/// master zone data.
+class MasterLoadError : public isc::Exception {
+public:
+ MasterLoadError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// The type of the \c callback parameter of \c masterLoad().
+///
+/// This represents a functor object or a function that takes one parameter
+/// of type \c RRsetPtr and returns nothing.
+typedef boost::function<void(RRsetPtr)> MasterLoadCallback;
+
+///
+/// \name Master zone file loader functions.
+///
+//@{
+/// Master zone file loader from a file.
+///
+/// This function parses a given file as a master DNS zone file for
+/// the given origin name and RR class, constructs a sequence of \c RRset
+/// from the RRs containing in the file, and calls the given \c callback
+/// functor object or function with each \c RRset.
+///
+/// The \c callback parameter is a functor object or a function that
+/// takes one parameter of type \c RRsetPtr and returns nothing,
+/// i.e. \c void (see below for specific examples).
+/// More precisely, it can be anything that this form of boost::function
+/// can represent, but the caller normally doesn't have to care about
+/// that level of details.
+///
+/// The ownership of constructed RRsets is transferred to the callback
+/// and this function never uses it once it is called.
+/// The callback can freely modify the passed \c RRset.
+///
+/// This function performs minimum level of validation on the input:
+/// - Each RR is a valid textual representation per the DNS protocol.
+/// - The class of each RR must be identical to the specified RR class.
+/// - The owner name of each RR must be a subdomain of the origin name
+/// (that can be equal to the origin).
+/// - If an SOA RR is included, its owner name must be the origin name.
+/// If any of these validation checks fails, this function throws an
+/// exception of class \c MasterLoadError.
+///
+/// It does not perform other semantical checks, however. For example,
+/// it doesn't check if an NS RR of the origin name is included or if
+/// there is more than one SOA RR. Such further checks are the caller's
+/// (or the callback's) responsibility.
+///
+/// <b>Acceptable Format</b>
+///
+/// The current implementation only supports a restricted form of master files
+/// for simplicity. One easy way to ensure that a handwritten zone file is
+/// acceptable to this implementation is to preprocess it with BIND 9's
+/// named-compilezone tool with both the input and output formats being
+/// "text".
+/// Here is an example:
+/// \code % named-compilezone -f text -F text -o example.com.norm
+/// example.com example.com.zone
+/// \endcode
+/// where example.com.zone is the original zone file for the "example.com"
+/// zone. The output file is example.com.norm, which should be acceptable
+/// by this implementation.
+///
+/// Below are specific restrictions that this implementation assumes.
+/// Basically, each RR must consist of exactly one line
+/// (so there shouldn't be a multi-line RR) in the following format:
+/// \code <owner name> <TTL> <RRCLASS> <RRTYPE> <RDATA (single line)>
+/// \endcode
+/// Here are some more details about the restrictions:
+/// - No special directives such as $TTL are supported.
+/// - The owner name must be absolute, that is, it must end with a period.
+/// - "@" is not recognized as a valid owner name.
+/// - Owner names, TTL and RRCLASS cannot be omitted.
+/// - As a corollary, a non blank line must not begin with a space character.
+/// - The order of the RR parameters is fixed, for example, this is acceptable:
+/// \code example.com. 3600 IN A 192.0.2.1
+/// \endcode
+/// 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,
+/// 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
+/// classes and RR types are supported yet, so the mnemonics of them will
+/// be rejected, too.
+/// - RR TTLs of the same RRset must be the same; even if they are different,
+/// this implementation simply uses the TTL of the first RR.
+///
+/// Blank lines and lines beginning with a semi-colon are allowed, and will
+/// be simply ignored. Comments cannot coexist with an RR line, however.
+/// For example, this will be rejected:
+/// \code example.com. 3600 IN A 192.0.2.1 ; this is a comment
+/// \endcode
+///
+/// This implementation assumes that RRs of a single RRset are not
+/// interleaved with RRs of a different RRset.
+/// That is, the following sequence shouldn't happen:
+/// \code example.com. 3600 IN A 192.0.2.1
+/// example.com. 3600 IN AAAA 2001:db8::1
+/// example.com. 3600 IN A 192.0.2.2
+/// \endcode
+/// But it does not consider this an error; it will simply regard each RR
+/// as a separate RRset and call the callback with them separately.
+/// It is up to the callback to merge multiple RRsets into one if possible
+/// and necessary.
+///
+/// <b>Exceptions</b>
+///
+/// This function throws an exception of class \c MasterLoadError in the
+/// following cases:
+/// - Any of the validation checks fails (see the class description).
+/// - The input data is not in the acceptable format (see the details of
+/// the format above).
+/// - The specified file cannot be opened for loading.
+/// - An I/O error occurs during the loading.
+///
+/// In addition, this function requires resource allocation for parsing and
+/// constructing RRsets. If it fails, the corresponding standard exception
+/// will be thrown.
+///
+/// The callback may throw its own function. This function doesn't catch it
+/// and will simply propagate it towards the caller.
+///
+/// <b>Usage Examples</b>
+///
+/// A simplest example usage of this function would be to parse a zone
+/// file and (after validation) dump the content to the standard output.
+/// This is an example functor object and a call to \c masterLoad
+/// that implements this scenario:
+/// \code struct ZoneDumper {
+/// void operator()(ConstRRsetPtr rrset) const {
+/// std::cout << *rrset;
+/// }
+/// };
+/// ...
+/// masterLoad(zone_file, Name("example.com"), RRClass::IN(), ZoneDumper());
+/// \endcode
+/// Alternatively, you can use a normal function instead of a functor:
+/// \code void zoneDumper(ConstRRsetPtr rrset) {
+/// std::cout << *rrset;
+/// }
+/// ...
+/// masterLoad(zone_file, Name("example.com"), RRClass::IN(), zoneDumper);
+/// \endcode
+/// Or, if you want to use it with a member function of some other class,
+/// wrapping things with \c boost::bind would be handy:
+/// \code class ZoneDumper {
+/// public:
+/// void dump(ConstRRsetPtr rrset) const {
+/// std::cout << *rrset;
+/// }
+/// };
+/// ...
+/// ZoneDumper dumper;
+/// masterLoad(rr_stream, Name("example.com"), RRClass::IN(),
+/// boost::bind(&ZoneDumper::dump, &dumper, _1));
+/// \endcode
+/// You can find a bit more complicated examples in the unit tests code for
+/// this function.
+///
+/// <b>Implementation Notes</b>
+///
+/// The current implementation is in a preliminary level and needs further
+/// extensions. Some design decisions may also have to be reconsidered as
+/// we gain experiences. Those include:
+/// - We should be more flexible about the input format.
+/// - We may want to allow optional conditions. For example, we may want to
+/// be generous about some validation failures and be able to continue
+/// parsing.
+/// - Especially if we allow to be generous, we may also want to support
+/// returning an error code instead of throwing an exception when we
+/// encounter validation failure.
+/// - We may want to support incremental loading.
+/// - If we add these optional features we may want to introduce a class
+/// that encapsulates loading status and options.
+/// - RRSIGs are currently identified as their owner name and RR type (RRSIG).
+/// In practice it should be sufficient, but technically we should also
+/// consider the Type Covered field.
+///
+/// \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 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);
+
+/// Master zone file loader from input stream.
+///
+/// This function is same as the other version
+/// (\c masterLoad(const char* const, const Name&, const RRClass&, MasterLoadCallback))
+/// except that it takes a \c std::istream instead of a file.
+/// It extracts lines from the stream and handles each line just as a line
+/// of a file for the other version of function.
+/// All descriptions of the other version apply to this version except those
+/// specific to file I/O.
+///
+/// \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 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);
+}
+//@}
+}
+
+#endif // __MASTERLOAD_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/message.cc b/src/lib/dns/message.cc
index 5ee11bd..9eae605 100644
--- a/src/lib/dns/message.cc
+++ b/src/lib/dns/message.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <algorithm>
@@ -44,32 +42,42 @@
using namespace std;
using namespace boost;
-using namespace isc::dns;
using namespace isc::dns::rdata;
namespace isc {
namespace dns {
namespace {
-typedef uint16_t flags_t;
-
// protocol constants
const size_t HEADERLEN = 12;
-const flags_t FLAG_QR = 0x8000;
-const flags_t FLAG_AA = 0x0400;
-const flags_t FLAG_TC = 0x0200;
-const flags_t FLAG_RD = 0x0100;
-const flags_t FLAG_RA = 0x0080;
-const flags_t FLAG_AD = 0x0020;
-const flags_t FLAG_CD = 0x0010;
-
const unsigned int OPCODE_MASK = 0x7800;
const unsigned int OPCODE_SHIFT = 11;
const unsigned int RCODE_MASK = 0x000f;
-const unsigned int FLAG_MASK = 0x8ff0;
-const unsigned int MESSAGE_REPLYPRESERVE = (FLAG_RD | FLAG_CD);
+// This diagram shows the wire-format representation of the 2nd 16 bits of
+// the DNS header section, which contain all defined flag bits.
+//
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// |QR| Opcode |AA|TC|RD|RA| |AD|CD| RCODE |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// 1 0 0 0| 0 1 1 1| 1 0 1 1| 0 0 0 0|
+// 0x8 0x7 0xb 0x0
+//
+// This mask covers all the flag bits, and those bits only.
+// Note: we reject a "flag" the is not covered by this mask in some of the
+// public methods. This means our current definition is not fully extendable;
+// applications cannot introduce a new flag bit temporarily without modifying
+// the source code.
+const unsigned int HEADERFLAG_MASK = 0x87b0;
+
+// This is a set of flag bits that should be preserved when building a reply
+// from a request.
+// Note: we assume the specific definition of HEADERFLAG_xx. We may change
+// the definition in future, in which case we need to adjust this definition,
+// too (see also the description about the Message::HeaderFlag type).
+const uint16_t MESSAGE_REPLYPRESERVE = (Message::HEADERFLAG_RD |
+ Message::HEADERFLAG_CD);
const char *sectiontext[] = {
"QUESTION",
@@ -79,15 +87,6 @@ const char *sectiontext[] = {
};
}
-namespace {
-inline unsigned int
-sectionCodeToId(const Section& section) {
- unsigned int code = section.getCode();
- assert(code > 0);
- return (section.getCode() - 1);
-}
-}
-
class MessageImpl {
public:
MessageImpl(Message::Mode mode);
@@ -105,25 +104,23 @@ public:
const Opcode* opcode_;
Opcode opcode_placeholder_;
- flags_t flags_;
+ uint16_t flags_; // wire-format representation of header flags.
bool header_parsed_;
- static const unsigned int SECTION_MAX = 4; // TODO: revisit this design
- int counts_[SECTION_MAX]; // TODO: revisit this definition
+ static const unsigned int NUM_SECTIONS = 4; // TODO: revisit this design
+ int counts_[NUM_SECTIONS]; // TODO: revisit this definition
vector<QuestionPtr> questions_;
- vector<RRsetPtr> rrsets_[SECTION_MAX];
+ vector<RRsetPtr> rrsets_[NUM_SECTIONS];
ConstEDNSPtr edns_;
-#ifdef notyet
// tsig/sig0: TODO
- RRsetsSorter* sorter_;
-#endif
+ // RRsetsSorter* sorter_; : TODO
void init();
void setOpcode(const Opcode& opcode);
void setRcode(const Rcode& rcode);
int parseQuestion(InputBuffer& buffer);
- int parseSection(const Section& section, InputBuffer& buffer);
+ int parseSection(const Message::Section section, InputBuffer& buffer);
};
MessageImpl::MessageImpl(Message::Mode mode) :
@@ -142,15 +139,15 @@ MessageImpl::init() {
opcode_ = NULL;
edns_ = EDNSPtr();
- for (int i = 0; i < SECTION_MAX; ++i) {
+ for (int i = 0; i < NUM_SECTIONS; ++i) {
counts_[i] = 0;
}
header_parsed_ = false;
questions_.clear();
- rrsets_[sectionCodeToId(Section::ANSWER())].clear();
- rrsets_[sectionCodeToId(Section::AUTHORITY())].clear();
- rrsets_[sectionCodeToId(Section::ADDITIONAL())].clear();
+ rrsets_[Message::SECTION_ANSWER].clear();
+ rrsets_[Message::SECTION_AUTHORITY].clear();
+ rrsets_[Message::SECTION_ADDITIONAL].clear();
}
void
@@ -174,26 +171,31 @@ Message::~Message() {
}
bool
-Message::getHeaderFlag(const MessageFlag& flag) const {
- return ((impl_->flags_ & flag.getBit()) != 0);
+Message::getHeaderFlag(const HeaderFlag flag) const {
+ if (flag == 0 || (flag & ~HEADERFLAG_MASK) != 0) {
+ isc_throw(InvalidParameter,
+ "Message::getHeaderFlag:: Invalid flag is specified: " <<
+ flag);
+ }
+ return ((impl_->flags_ & flag) != 0);
}
void
-Message::setHeaderFlag(const MessageFlag& flag) {
+Message::setHeaderFlag(const HeaderFlag flag, const bool on) {
if (impl_->mode_ != Message::RENDER) {
isc_throw(InvalidMessageOperation,
"setHeaderFlag performed in non-render mode");
}
- impl_->flags_ |= flag.getBit();
-}
-
-void
-Message::clearHeaderFlag(const MessageFlag& flag) {
- if (impl_->mode_ != Message::RENDER) {
- isc_throw(InvalidMessageOperation,
- "clearHeaderFlag performed in non-render mode");
+ if (flag == 0 || (flag & ~HEADERFLAG_MASK) != 0) {
+ isc_throw(InvalidParameter,
+ "Message::getHeaderFlag:: Invalid flag is specified: " <<
+ flag);
+ }
+ if (on) {
+ impl_->flags_ |= flag;
+ } else {
+ impl_->flags_ &= ~flag;
}
- impl_->flags_ &= ~flag.getBit();
}
qid_t
@@ -259,41 +261,90 @@ Message::setEDNS(ConstEDNSPtr edns) {
}
unsigned int
-Message::getRRCount(const Section& section) const {
- return (impl_->counts_[section.getCode()]);
+Message::getRRCount(const Section section) const {
+ if (section >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << section);
+ }
+ return (impl_->counts_[section]);
}
void
-Message::addRRset(const Section& section, RRsetPtr rrset, const bool sign) {
+Message::addRRset(const Section section, RRsetPtr rrset, const bool sign) {
if (impl_->mode_ != Message::RENDER) {
isc_throw(InvalidMessageOperation,
"addRRset performed in non-render mode");
}
+ if (section >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << section);
+ }
- impl_->rrsets_[sectionCodeToId(section)].push_back(rrset);
- impl_->counts_[section.getCode()] += rrset->getRdataCount();
+ impl_->rrsets_[section].push_back(rrset);
+ impl_->counts_[section] += rrset->getRdataCount();
RRsetPtr sp = rrset->getRRsig();
if (sign && sp != NULL) {
- impl_->rrsets_[sectionCodeToId(section)].push_back(sp);
- impl_->counts_[section.getCode()] += sp->getRdataCount();
+ impl_->rrsets_[section].push_back(sp);
+ impl_->counts_[section] += sp->getRdataCount();
}
}
bool
-Message::hasRRset(const Section& section, RRsetPtr rrset) {
- BOOST_FOREACH(RRsetPtr r, impl_->rrsets_[sectionCodeToId(section)]) {
- if (r->getType() == rrset->getType() &&
- r->getName() == rrset->getName())
- {
- return (true);
+Message::hasRRset(const Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype)
+{
+ if (section >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << section);
+ }
+ BOOST_FOREACH(ConstRRsetPtr r, impl_->rrsets_[section]) {
+ if (r->getClass() == rrclass &&
+ r->getType() == rrtype &&
+ r->getName() == name) {
+ return (true);
}
}
return (false);
}
+bool
+Message::hasRRset(const Section section, const RRsetPtr& rrset) {
+ return (hasRRset(section, rrset->getName(), rrset->getClass(), rrset->getType()));
+}
+
+bool
+Message::removeRRset(const Section section, RRsetIterator& iterator) {
+ if (section >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << section);
+ }
+
+ bool removed = false;
+ for (vector<RRsetPtr>::iterator i = impl_->rrsets_[section].begin();
+ i != impl_->rrsets_[section].end(); ++i) {
+ if (((*i)->getName() == (*iterator)->getName()) &&
+ ((*i)->getClass() == (*iterator)->getClass()) &&
+ ((*i)->getType() == (*iterator)->getType())) {
+
+ // Found the matching RRset so remove it & ignore rest
+ impl_->counts_[section] -= (*iterator)->getRdataCount();
+ impl_->rrsets_[section].erase(i);
+ removed = true;
+ break;
+ }
+ }
+
+ return (removed);
+}
+
+void
+Message::clearSection(const Section section) {
+ if (section >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << section);
+ }
+ impl_->rrsets_[section].clear();
+ impl_->counts_[section] = 0;
+}
+
void
Message::addQuestion(const QuestionPtr question) {
if (impl_->mode_ != Message::RENDER) {
@@ -302,7 +353,7 @@ Message::addQuestion(const QuestionPtr question) {
}
impl_->questions_.push_back(question);
- ++impl_->counts_[Section::QUESTION().getCode()];
+ ++impl_->counts_[SECTION_QUESTION];
}
void
@@ -366,24 +417,24 @@ Message::toWire(MessageRenderer& renderer) {
uint16_t ancount = 0;
if (!renderer.isTruncated()) {
ancount =
- for_each(impl_->rrsets_[sectionCodeToId(Section::ANSWER())].begin(),
- impl_->rrsets_[sectionCodeToId(Section::ANSWER())].end(),
+ for_each(impl_->rrsets_[SECTION_ANSWER].begin(),
+ impl_->rrsets_[SECTION_ANSWER].end(),
RenderSection<RRsetPtr>(renderer, true)).getTotalCount();
}
uint16_t nscount = 0;
if (!renderer.isTruncated()) {
nscount =
- for_each(impl_->rrsets_[sectionCodeToId(Section::AUTHORITY())].begin(),
- impl_->rrsets_[sectionCodeToId(Section::AUTHORITY())].end(),
+ for_each(impl_->rrsets_[SECTION_AUTHORITY].begin(),
+ impl_->rrsets_[SECTION_AUTHORITY].end(),
RenderSection<RRsetPtr>(renderer, true)).getTotalCount();
}
uint16_t arcount = 0;
if (renderer.isTruncated()) {
- setHeaderFlag(MessageFlag::TC());
+ setHeaderFlag(HEADERFLAG_TC, true);
} else {
arcount =
- for_each(impl_->rrsets_[sectionCodeToId(Section::ADDITIONAL())].begin(),
- impl_->rrsets_[sectionCodeToId(Section::ADDITIONAL())].end(),
+ for_each(impl_->rrsets_[SECTION_ADDITIONAL].begin(),
+ impl_->rrsets_[SECTION_ADDITIONAL].end(),
RenderSection<RRsetPtr>(renderer, false)).getTotalCount();
}
@@ -406,10 +457,10 @@ Message::toWire(MessageRenderer& renderer) {
// in rrsets_[] or questions_ if truncation occurred or an EDNS OPT RR
// was inserted. This is not good, and we should revisit the entire
// design.
- impl_->counts_[Section::QUESTION().getCode()] = qdcount;
- impl_->counts_[Section::ANSWER().getCode()] = ancount;
- impl_->counts_[Section::AUTHORITY().getCode()] = nscount;
- impl_->counts_[Section::ADDITIONAL().getCode()] = arcount;
+ impl_->counts_[SECTION_QUESTION] = qdcount;
+ impl_->counts_[SECTION_ANSWER] = ancount;
+ impl_->counts_[SECTION_AUTHORITY] = nscount;
+ impl_->counts_[SECTION_ADDITIONAL] = arcount;
// TBD: TSIG, SIG(0) etc.
@@ -421,7 +472,7 @@ Message::toWire(MessageRenderer& renderer) {
uint16_t codes_and_flags =
(impl_->opcode_->getCode() << OPCODE_SHIFT) & OPCODE_MASK;
codes_and_flags |= (impl_->rcode_->getCode() & RCODE_MASK);
- codes_and_flags |= (impl_->flags_ & FLAG_MASK);
+ codes_and_flags |= (impl_->flags_ & HEADERFLAG_MASK);
renderer.writeUint16At(codes_and_flags, header_pos);
header_pos += sizeof(uint16_t);
// XXX: should avoid repeated pattern (TODO)
@@ -451,11 +502,11 @@ Message::parseHeader(InputBuffer& buffer) {
const uint16_t codes_and_flags = buffer.readUint16();
impl_->setOpcode(Opcode((codes_and_flags & OPCODE_MASK) >> OPCODE_SHIFT));
impl_->setRcode(Rcode(codes_and_flags & RCODE_MASK));
- impl_->flags_ = (codes_and_flags & FLAG_MASK);
- impl_->counts_[Section::QUESTION().getCode()] = buffer.readUint16();
- impl_->counts_[Section::ANSWER().getCode()] = buffer.readUint16();
- impl_->counts_[Section::AUTHORITY().getCode()] = buffer.readUint16();
- impl_->counts_[Section::ADDITIONAL().getCode()] = buffer.readUint16();
+ impl_->flags_ = (codes_and_flags & HEADERFLAG_MASK);
+ impl_->counts_[SECTION_QUESTION] = buffer.readUint16();
+ impl_->counts_[SECTION_ANSWER] = buffer.readUint16();
+ impl_->counts_[SECTION_AUTHORITY] = buffer.readUint16();
+ impl_->counts_[SECTION_ADDITIONAL] = buffer.readUint16();
impl_->header_parsed_ = true;
}
@@ -471,14 +522,13 @@ Message::fromWire(InputBuffer& buffer) {
parseHeader(buffer);
}
- impl_->counts_[Section::QUESTION().getCode()] =
- impl_->parseQuestion(buffer);
- impl_->counts_[Section::ANSWER().getCode()] =
- impl_->parseSection(Section::ANSWER(), buffer);
- impl_->counts_[Section::AUTHORITY().getCode()] =
- impl_->parseSection(Section::AUTHORITY(), buffer);
- impl_->counts_[Section::ADDITIONAL().getCode()] =
- impl_->parseSection(Section::ADDITIONAL(), buffer);
+ impl_->counts_[SECTION_QUESTION] = impl_->parseQuestion(buffer);
+ impl_->counts_[SECTION_ANSWER] =
+ impl_->parseSection(SECTION_ANSWER, buffer);
+ impl_->counts_[SECTION_AUTHORITY] =
+ impl_->parseSection(SECTION_AUTHORITY, buffer);
+ impl_->counts_[SECTION_ADDITIONAL] =
+ impl_->parseSection(SECTION_ADDITIONAL, buffer);
}
int
@@ -486,7 +536,7 @@ MessageImpl::parseQuestion(InputBuffer& buffer) {
unsigned int added = 0;
for (unsigned int count = 0;
- count < counts_[Section::QUESTION().getCode()];
+ count < counts_[Message::SECTION_QUESTION];
++count) {
const Name name(buffer);
@@ -498,9 +548,9 @@ MessageImpl::parseQuestion(InputBuffer& buffer) {
const RRType rrtype(buffer.readUint16());
const RRClass rrclass(buffer.readUint16());
- // XXX: need a duplicate check. We might also want to have an optimized
- // algorithm that requires the question section contain exactly one
- // RR.
+ // XXX: need a duplicate check. We might also want to have an
+ // optimized algorithm that requires the question section contain
+ // exactly one RR.
questions_.push_back(QuestionPtr(new Question(name, rrclass, rrtype)));
++added;
@@ -553,16 +603,20 @@ struct MatchRR : public unary_function<RRsetPtr, bool> {
// logic to that class; processing logic dependent on parse context
// is hardcoded here.
int
-MessageImpl::parseSection(const Section& section, InputBuffer& buffer) {
+MessageImpl::parseSection(const Message::Section section,
+ InputBuffer& buffer)
+{
+ assert(section < MessageImpl::NUM_SECTIONS);
+
unsigned int added = 0;
- for (unsigned int count = 0; count < counts_[section.getCode()]; ++count) {
+ for (unsigned int count = 0; count < counts_[section]; ++count) {
const Name name(buffer);
// buffer must store at least RR TYPE, RR CLASS, TTL, and RDLEN.
if ((buffer.getLength() - buffer.getPosition()) <
3 * sizeof(uint16_t) + sizeof(uint32_t)) {
- isc_throw(DNSMessageFORMERR, sectiontext[section.getCode()] <<
+ isc_throw(DNSMessageFORMERR, sectiontext[section] <<
" section too short: " <<
(buffer.getLength() - buffer.getPosition()) << " bytes");
}
@@ -574,7 +628,7 @@ MessageImpl::parseSection(const Section& section, InputBuffer& buffer) {
ConstRdataPtr rdata = createRdata(rrtype, rrclass, buffer, rdlen);
if (rrtype == RRType::OPT()) {
- if (section != Section::ADDITIONAL()) {
+ if (section != Message::SECTION_ADDITIONAL) {
isc_throw(DNSMessageFORMERR,
"EDNS OPT RR found in an invalid section");
}
@@ -589,17 +643,16 @@ MessageImpl::parseSection(const Section& section, InputBuffer& buffer) {
continue;
} else {
vector<RRsetPtr>::iterator it =
- find_if(rrsets_[sectionCodeToId(section)].begin(),
- rrsets_[sectionCodeToId(section)].end(),
+ find_if(rrsets_[section].begin(), rrsets_[section].end(),
MatchRR(name, rrtype, rrclass));
- if (it != rrsets_[sectionCodeToId(section)].end()) {
+ if (it != rrsets_[section].end()) {
(*it)->setTTL(min((*it)->getTTL(), ttl));
(*it)->addRdata(rdata);
} else {
RRsetPtr rrset =
RRsetPtr(new RRset(name, rrclass, rrtype, ttl));
rrset->addRdata(rdata);
- rrsets_[sectionCodeToId(section)].push_back(rrset);
+ rrsets_[section].push_back(rrset);
}
++added;
}
@@ -611,15 +664,15 @@ MessageImpl::parseSection(const Section& section, InputBuffer& buffer) {
namespace {
template <typename T>
struct SectionFormatter {
- SectionFormatter(const Section& section, string& output) :
+ SectionFormatter(const Message::Section section, string& output) :
section_(section), output_(output) {}
void operator()(const T& entry) {
- if (section_ == Section::QUESTION()) {
+ if (section_ == Message::SECTION_QUESTION) {
output_ += ";";
}
output_ += entry->toText();
}
- const Section& section_;
+ const Message::Section section_;
string& output_;
};
}
@@ -642,30 +695,37 @@ Message::toText() const {
s += ", status: " + impl_->rcode_->toText();
s += ", id: " + boost::lexical_cast<string>(impl_->qid_);
s += "\n;; flags: ";
- if (getHeaderFlag(MessageFlag::QR()))
+ if (getHeaderFlag(HEADERFLAG_QR)) {
s += "qr ";
- if (getHeaderFlag(MessageFlag::AA()))
+ }
+ if (getHeaderFlag(HEADERFLAG_AA)) {
s += "aa ";
- if (getHeaderFlag(MessageFlag::TC()))
+ }
+ if (getHeaderFlag(HEADERFLAG_TC)) {
s += "tc ";
- if (getHeaderFlag(MessageFlag::RD()))
+ }
+ if (getHeaderFlag(HEADERFLAG_RD)) {
s += "rd ";
- if (getHeaderFlag(MessageFlag::RA()))
+ }
+ if (getHeaderFlag(HEADERFLAG_RA)) {
s += "ra ";
- if (getHeaderFlag(MessageFlag::AD()))
+ }
+ if (getHeaderFlag(HEADERFLAG_AD)) {
s += "ad ";
- if (getHeaderFlag(MessageFlag::CD()))
+ }
+ if (getHeaderFlag(HEADERFLAG_CD)) {
s += "cd ";
+ }
// for simplicity, don't consider the update case for now
s += "; QUESTION: " +
- lexical_cast<string>(impl_->counts_[Section::QUESTION().getCode()]);
+ lexical_cast<string>(impl_->counts_[SECTION_QUESTION]);
s += ", ANSWER: " +
- lexical_cast<string>(impl_->counts_[Section::ANSWER().getCode()]);
+ lexical_cast<string>(impl_->counts_[SECTION_ANSWER]);
s += ", AUTHORITY: " +
- lexical_cast<string>(impl_->counts_[Section::AUTHORITY().getCode()]);
+ lexical_cast<string>(impl_->counts_[SECTION_AUTHORITY]);
- unsigned int arcount = impl_->counts_[Section::ADDITIONAL().getCode()];
+ unsigned int arcount = impl_->counts_[SECTION_ADDITIONAL];
if (impl_->edns_ != NULL) {
++arcount;
}
@@ -678,31 +738,31 @@ Message::toText() const {
if (!impl_->questions_.empty()) {
s += "\n;; " +
- string(sectiontext[Section::QUESTION().getCode()]) + " SECTION:\n";
+ string(sectiontext[SECTION_QUESTION]) + " SECTION:\n";
for_each(impl_->questions_.begin(), impl_->questions_.end(),
- SectionFormatter<QuestionPtr>(Section::QUESTION(), s));
+ SectionFormatter<QuestionPtr>(SECTION_QUESTION, s));
}
- if (!impl_->rrsets_[sectionCodeToId(Section::ANSWER())].empty()) {
+ if (!impl_->rrsets_[SECTION_ANSWER].empty()) {
s += "\n;; " +
- string(sectiontext[Section::ANSWER().getCode()]) + " SECTION:\n";
- for_each(impl_->rrsets_[sectionCodeToId(Section::ANSWER())].begin(),
- impl_->rrsets_[sectionCodeToId(Section::ANSWER())].end(),
- SectionFormatter<RRsetPtr>(Section::ANSWER(), s));
+ string(sectiontext[SECTION_ANSWER]) + " SECTION:\n";
+ for_each(impl_->rrsets_[SECTION_ANSWER].begin(),
+ impl_->rrsets_[SECTION_ANSWER].end(),
+ SectionFormatter<RRsetPtr>(SECTION_ANSWER, s));
}
- if (!impl_->rrsets_[sectionCodeToId(Section::AUTHORITY())].empty()) {
+ if (!impl_->rrsets_[SECTION_AUTHORITY].empty()) {
s += "\n;; " +
- string(sectiontext[Section::AUTHORITY().getCode()]) + " SECTION:\n";
- for_each(impl_->rrsets_[sectionCodeToId(Section::AUTHORITY())].begin(),
- impl_->rrsets_[sectionCodeToId(Section::AUTHORITY())].end(),
- SectionFormatter<RRsetPtr>(Section::AUTHORITY(), s));
+ string(sectiontext[SECTION_AUTHORITY]) + " SECTION:\n";
+ for_each(impl_->rrsets_[SECTION_AUTHORITY].begin(),
+ impl_->rrsets_[SECTION_AUTHORITY].end(),
+ SectionFormatter<RRsetPtr>(SECTION_AUTHORITY, s));
}
- if (!impl_->rrsets_[sectionCodeToId(Section::ADDITIONAL())].empty()) {
+ if (!impl_->rrsets_[SECTION_ADDITIONAL].empty()) {
s += "\n;; " +
- string(sectiontext[Section::ADDITIONAL().getCode()]) +
+ string(sectiontext[SECTION_ADDITIONAL]) +
" SECTION:\n";
- for_each(impl_->rrsets_[sectionCodeToId(Section::ADDITIONAL())].begin(),
- impl_->rrsets_[sectionCodeToId(Section::ADDITIONAL())].end(),
- SectionFormatter<RRsetPtr>(Section::ADDITIONAL(), s));
+ for_each(impl_->rrsets_[SECTION_ADDITIONAL].begin(),
+ impl_->rrsets_[SECTION_ADDITIONAL].end(),
+ SectionFormatter<RRsetPtr>(SECTION_ADDITIONAL, s));
}
return (s);
@@ -715,6 +775,27 @@ Message::clear(Mode mode) {
}
void
+Message::appendSection(const Section section, const Message& source) {
+ if (section >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << section);
+ }
+
+ if (section == SECTION_QUESTION) {
+ for (QuestionIterator qi = source.beginQuestion();
+ qi != source.endQuestion();
+ ++qi) {
+ addQuestion(*qi);
+ }
+ } else {
+ for (RRsetIterator rrsi = source.beginSection(section);
+ rrsi != source.endSection(section);
+ ++rrsi) {
+ addRRset(section, *rrsi);
+ }
+ }
+}
+
+void
Message::makeResponse() {
if (impl_->mode_ != Message::PARSE) {
isc_throw(InvalidMessageOperation,
@@ -725,14 +806,14 @@ Message::makeResponse() {
impl_->edns_ = EDNSPtr();
impl_->flags_ &= MESSAGE_REPLYPRESERVE;
- setHeaderFlag(MessageFlag::QR());
+ setHeaderFlag(HEADERFLAG_QR, true);
- impl_->rrsets_[sectionCodeToId(Section::ANSWER())].clear();
- impl_->counts_[Section::ANSWER().getCode()] = 0;
- impl_->rrsets_[sectionCodeToId(Section::AUTHORITY())].clear();
- impl_->counts_[Section::AUTHORITY().getCode()] = 0;
- impl_->rrsets_[sectionCodeToId(Section::ADDITIONAL())].clear();
- impl_->counts_[Section::ADDITIONAL().getCode()] = 0;
+ impl_->rrsets_[SECTION_ANSWER].clear();
+ impl_->counts_[SECTION_ANSWER] = 0;
+ impl_->rrsets_[SECTION_AUTHORITY].clear();
+ impl_->counts_[SECTION_AUTHORITY] = 0;
+ impl_->rrsets_[SECTION_ADDITIONAL].clear();
+ impl_->counts_[SECTION_ADDITIONAL] = 0;
}
///
@@ -840,33 +921,34 @@ Message::endQuestion() const {
/// RRsets iterators
///
const SectionIterator<RRsetPtr>
-Message::beginSection(const Section& section) const {
- if (section == Section::QUESTION()) {
+Message::beginSection(const Section section) const {
+ if (section >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << section);
+ }
+ if (section == SECTION_QUESTION) {
isc_throw(InvalidMessageSection,
"RRset iterator is requested for question");
}
- return (RRsetIterator(
- RRsetIteratorImpl(
- impl_->rrsets_[sectionCodeToId(section)].begin())));
+ return (RRsetIterator(RRsetIteratorImpl(impl_->rrsets_[section].begin())));
}
const SectionIterator<RRsetPtr>
-Message::endSection(const Section& section) const {
- if (section == Section::QUESTION()) {
+Message::endSection(const Section section) const {
+ if (section >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << section);
+ }
+ if (section == SECTION_QUESTION) {
isc_throw(InvalidMessageSection,
"RRset iterator is requested for question");
}
- return (RRsetIterator(
- RRsetIteratorImpl(
- impl_->rrsets_[sectionCodeToId(section)].end())));
+ return (RRsetIterator(RRsetIteratorImpl(impl_->rrsets_[section].end())));
}
ostream&
operator<<(ostream& os, const Message& message) {
return (os << message.toText());
}
-
} // end of namespace dns
} // end of namespace isc
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h
index a148a00..0bbb9ea 100644
--- a/src/lib/dns/message.h
+++ b/src/lib/dns/message.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __MESSAGE_H
#define __MESSAGE_H 1
@@ -88,133 +86,6 @@ class Rcode;
template <typename T>
struct SectionIteratorImpl;
-/// \brief The \c MessageFlag class objects represent standard flag bits
-/// of the header section of DNS messages.
-///
-/// Constant objects are defined for standard flags.
-class MessageFlag {
-public:
- /// \brief Returns the corresponding bit of the MessageFlag.
- ///
- /// Note: this value is intended to be used for rendering or parsing
- /// low level wire-format data. Applications should use abstract
- /// interfaces. This also means the interface is not well sophisticated,
- /// and we should revisit the design.
- uint16_t getBit() const { return (flagbit_); }
- static const MessageFlag& QR();
- static const MessageFlag& AA();
- static const MessageFlag& TC();
- static const MessageFlag& RD();
- static const MessageFlag& RA();
- static const MessageFlag& AD();
- static const MessageFlag& CD();
-private:
- MessageFlag(uint16_t flagbit) : flagbit_(flagbit) {}
- uint16_t flagbit_;
-};
-
-inline const MessageFlag&
-MessageFlag::QR() {
- static MessageFlag f(0x8000);
- return (f);
-}
-
-inline const MessageFlag&
-MessageFlag::AA() {
- static MessageFlag f(0x0400);
- return (f);
-}
-
-inline const MessageFlag&
-MessageFlag::TC() {
- static MessageFlag f(0x0200);
- return (f);
-}
-
-inline const MessageFlag&
-MessageFlag::RD() {
- static MessageFlag f(0x0100);
- return (f);
-}
-
-inline const MessageFlag&
-MessageFlag::RA() {
- static MessageFlag f(0x0080);
- return (f);
-}
-
-inline const MessageFlag&
-MessageFlag::AD() {
- static MessageFlag f(0x0020);
- return (f);
-}
-
-inline const MessageFlag&
-MessageFlag::CD() {
- static MessageFlag f(0x0010);
- return (f);
-}
-
-/// \brief The \c Section class objects represent DNS message sections such
-/// as the header, question, or answer.
-///
-/// Note: this class doesn't seem to be very useful. We should probably
-/// revisit this design.
-///
-/// Note: whether or not it's represented as a class, we'll need a way
-/// to represent more advanced sections such as those used in dynamic updates.
-/// This is a TODO item.
-///
-/// Constant objects are defined for standard flags.
-class Section {
-public:
- /// \brief Returns the relative position of the \c Section in DNS messages.
- unsigned int getCode() const { return (code_); }
- bool operator==(const Section& other) const
- { return (code_ == other.code_); }
- bool operator!=(const Section& other) const
- { return (code_ != other.code_); }
-
- static const Section& QUESTION();
- static const Section& ANSWER();
- static const Section& AUTHORITY();
- static const Section& ADDITIONAL();
-private:
- enum {
- SECTION_QUESTION = 0,
- SECTION_ANSWER = 1,
- SECTION_AUTHORITY = 2,
- SECTION_ADDITIONAL = 3
- };
-
- Section(int code) : code_(code) {}
- unsigned int code_;
-};
-
-inline const Section&
-Section::QUESTION() {
- static Section s(SECTION_QUESTION);
- return (s);
-}
-
-inline const Section&
-Section::ANSWER() {
- static Section s(SECTION_ANSWER);
- return (s);
-}
-
-inline const Section&
-Section::AUTHORITY() {
- static Section s(SECTION_AUTHORITY);
- return (s);
-}
-
-inline const Section&
-Section::ADDITIONAL() {
- static Section s(SECTION_ADDITIONAL);
- return (s);
-}
-
/// \c SectionIterator is a templated class to provide standard-compatible
/// iterators for Questions and RRsets for a given DNS message section.
/// The template parameter is either \c QuestionPtr (for the question section)
@@ -270,23 +141,113 @@ 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:
+ /// Constants to specify the operation mode of the \c Message.
enum Mode {
- PARSE = 0,
- RENDER = 1
+ PARSE = 0, ///< Parse mode (handling an incoming message)
+ RENDER = 1 ///< Render mode (building an outgoing message)
+ };
+
+ /// \brief Constants for flag bit fields of a DNS message header.
+ ///
+ /// 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, 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
+ /// callee does not perform proper validation; the result in such usage
+ /// is undefined.
+ ///
+ /// In the current implementation, the defined values happen to be
+ /// a 16-bit integer with one bit being set corresponding to the
+ /// 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 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
+ /// and the result is undefined.
+ ///
+ /// Likewise, bit wise operations such as AND or OR on the flag values
+ /// are invalid and are not guaranteed to work, even if it could compile
+ /// with casting.
+ /// For example, the following code will compile:
+ /// \code const uint16_t combined_flags =
+ /// static_cast<uint16_t>(Message::HEADERFLAG_AA) |
+ /// static_cast<uint16_t>(Message::HEADERFLAG_CD);
+ /// message->setHeaderFlag(static_cast<Message::HeaderFlag>(combined_flags));
+ /// \endcode
+ /// and (with the current definition) happens to work as if it were
+ /// validly written as follows:
+ /// \code message->setHeaderFlag(Message::HEADERFLAG_AA);
+ /// message->setHeaderFlag(Message::HEADERFLAG_CD);
+ /// \endcode
+ /// But the former notation is invalid and may not work in future versions.
+ /// We did not try to prohibit such usage at compilation time, e.g., by
+ /// introducing a separately defined class considering the balance
+ /// between the complexity and advantage, but hopefully the cast notation
+ /// is sufficiently ugly to prevent proliferation of the usage.
+ enum HeaderFlag {
+ HEADERFLAG_QR = 0x8000, ///< Query (if cleared) or response (if set)
+ HEADERFLAG_AA = 0x0400, ///< Authoritative answer
+ HEADERFLAG_TC = 0x0200, ///< Truncation
+ HEADERFLAG_RD = 0x0100, ///< Recursion desired
+ HEADERFLAG_RA = 0x0080, ///< Recursion available
+ HEADERFLAG_AD = 0x0020, ///< Authentic %data (RFC4035)
+ HEADERFLAG_CD = 0x0010 ///< DNSSEC checking disabled (RFC4035)
+ };
+
+ /// \brief Constants to specify sections of a DNS message.
+ ///
+ /// The sections are those defined in RFC 1035 excluding the Header
+ /// section; the fields of the Header section are accessed via specific
+ /// methods of the \c Message class (e.g., \c getQid()).
+ ///
+ /// <b>Open Design Issue:</b>
+ /// In the current implementation the values for the constants are
+ /// sorted in the order of appearance in DNS messages, i.e.,
+ /// from %Question to Additional.
+ /// So, for example,
+ /// code <code>section >= Message::SECTION_AUTHORITY</code> can be
+ /// used to do something in or after the Authority section.
+ /// This would be convenient, but it is not clear if it's really a good
+ /// idea to rely on relationship between the underlying values of enum
+ /// constants. At the moment, applications are discouraged to rely on
+ /// this implementation detail. We will see if such usage is sufficiently
+ /// common to officially support it.
+ ///
+ /// Note also that since we don't define \c operator++ for this enum,
+ /// the following code intending to iterate over all sections will
+ /// \b not compile:
+ /// \code for (Section s; s <= SECTION_ADDITIONAL; ++s) { // ++s undefined
+ /// // do something
+ /// } \endcode
+ /// This is intentional at this moment, and we'll see if we need to allow
+ /// that as we have more experiences with this library.
+ ///
+ /// <b>Future Extension:</b> We'll probably also define constants for
+ /// the section names used in dynamic updates in future versions.
+ enum Section {
+ SECTION_QUESTION = 0, ///< %Question section
+ SECTION_ANSWER = 1, ///< Answer section
+ SECTION_AUTHORITY = 2, ///< Authority section
+ SECTION_ADDITIONAL = 3 ///< Additional section
};
///
/// \name Constructors and Destructor
///
- /// Note: The copy constructor and the assignment operator are intentionally
- /// defined as private. The intended use case wouldn't require copies of
- /// a \c Message object; once created, it would normally be expected to
- /// be reused, changing the mode from \c PARSE to \c RENDER, and vice
- /// versa.
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private.
+ /// The intended use case wouldn't require copies of a \c Message object;
+ /// once created, it would normally be expected to be reused, changing the
+ /// mode from \c PARSE to \c RENDER, and vice versa.
//@{
public:
/// \brief The constructor.
@@ -301,26 +262,51 @@ private:
public:
/// \brief Return whether the specified header flag bit is set in the
/// header section.
- bool getHeaderFlag(const MessageFlag& flag) const;
-
- /// \brief Set the specified header flag bit is set in the header section.
///
- /// Only allowed in the \c RENDER mode.
- void setHeaderFlag(const MessageFlag& flag);
+ /// This method is basically exception free, but if
+ /// \c flag is not a valid constant of the \c HeaderFlag type,
+ /// an exception of class \c InvalidParameter will be thrown.
+ ///
+ /// \param flag The header flag constant to test.
+ /// \return \c true if the specified flag is set; otherwise \c false.
+ bool getHeaderFlag(const HeaderFlag flag) const;
- /// \brief Clear the specified header flag bit is set in the header section.
+ /// \brief Set or clear the specified header flag bit in the header
+ /// section.
+ ///
+ /// The optional parameter \c on indicates the operation mode,
+ /// set or clear; if it's \c true the corresponding flag will be set;
+ /// otherwise the flag will be cleared.
+ /// In either case the original state of the flag does not affect the
+ /// operation; for example, if a flag is already set and the "set"
+ /// operation is attempted, it effectively results in no operation.
+ ///
+ /// The parameter \c on can be omitted, in which case a value of \c true
+ /// (i.e., set operation) will be assumed.
+ /// This is based on the observation that the flag would have to be set
+ /// in the vast majority of the cases where an application needs to
+ /// use this method.
+ ///
+ /// This method is only allowed in the \c RENDER mode;
+ /// if the \c Message is in other mode, an exception of class
+ /// InvalidMessageOperation will be thrown.
+ ///
+ /// If \c flag is not a valid constant of the \c HeaderFlag type,
+ /// an exception of class \c InvalidParameter will be thrown.
///
- /// Only allowed in the \c RENDER mode.
- /// Note: it may make more sense to integrate this method into \c
- /// \c setHeaderFlag() with an additional argument.
- void clearHeaderFlag(const MessageFlag& flag);
+ /// \param flag The header flag constant to set or clear.
+ /// \param on If \c true the flag will be set; otherwise the flag will be
+ /// cleared.
+ void setHeaderFlag(const HeaderFlag flag, const bool on = true);
/// \brief Return the query ID given in the header section of the message.
qid_t getQid() const;
/// \brief Set the query ID of the header section of the message.
///
- /// Only allowed in the \c RENDER mode.
+ /// This method is only allowed in the \c RENDER mode;
+ /// if the \c Message is in other mode, an exception of class
+ /// InvalidMessageOperation will be thrown.
void setQid(qid_t qid);
/// \brief Return the Response Code of the message.
@@ -338,7 +324,9 @@ public:
/// \brief Set the Response Code of the message.
///
- /// Only allowed in the \c RENDER mode.
+ /// This method is only allowed in the \c RENDER mode;
+ /// if the \c Message is in other mode, an exception of class
+ /// InvalidMessageOperation will be thrown.
///
/// If the specified code is an EDNS extended RCODE, an EDNS OPT RR will be
/// included in the message.
@@ -354,7 +342,9 @@ public:
/// \brief Set the OPCODE of the header section of the message.
///
- /// Only allowed in the \c RENDER mode.
+ /// This method is only allowed in the \c RENDER mode;
+ /// if the \c Message is in other mode, an exception of class
+ /// InvalidMessageOperation will be thrown.
void setOpcode(const Opcode& opcode);
/// \brief Return, if any, the EDNS associated with the message.
@@ -367,15 +357,38 @@ public:
/// \brief Set EDNS for the message.
///
- /// Only allowed in the \c RENDER mode; otherwise an exception of class
- /// \c InvalidMessageOperation will be thrown.
+ /// This method is only allowed in the \c RENDER mode;
+ /// if the \c Message is in other mode, an exception of class
+ /// InvalidMessageOperation will be thrown.
///
/// \param edns A shared pointer to an \c EDNS object to be set in
/// \c Message.
void setEDNS(ConstEDNSPtr edns);
/// \brief Returns the number of RRs contained in the given section.
- unsigned int getRRCount(const Section& section) const;
+ ///
+ /// In the \c PARSE mode, the returned value may not be identical to
+ /// the actual number of RRs of the incoming message that is parsed.
+ /// The \c Message class handles some "meta" RRs such as EDNS OPT RR
+ /// separately. This method doesn't include such RRs.
+ /// Also, a future version of the parser will detect and unify duplicate
+ /// RRs (which should be rare in practice though), in which case
+ /// the stored RRs in the \c Message object will be fewer than the RRs
+ /// originally contained in the incoming message.
+ ///
+ /// Likewise, in the \c RENDER mode, even if \c EDNS is set in the
+ /// \c Message, this method doesn't count the corresponding OPT RR
+ /// in the Additional section.
+ ///
+ /// This method is basically exception free, but if
+ /// \c section is not a valid constant of the \c Section type,
+ /// an exception of class \c OutOfRange will be thrown.
+ ///
+ /// \param section The section in the message where RRs should be
+ /// counted.
+ /// \return The number of RRs stored in the specified section of the
+ /// message.
+ unsigned int getRRCount(const Section section) const;
/// \brief Return an iterator corresponding to the beginning of the
/// Question section of the message.
@@ -387,15 +400,23 @@ public:
/// \brief Return an iterator corresponding to the beginning of the
/// given section (other than Question) of the message.
- const RRsetIterator beginSection(const Section& section) const;
+ ///
+ /// \c section must be a valid constant of the \c Section type;
+ /// otherwise, an exception of class \c OutOfRange will be thrown.
+ const RRsetIterator beginSection(const Section section) const;
/// \brief Return an iterator corresponding to the end of the
/// given section (other than Question) of the message.
- const RRsetIterator endSection(const Section& section) const;
+ ///
+ /// \c section must be a valid constant of the \c Section type;
+ /// otherwise, an exception of class \c OutOfRange will be thrown.
+ const RRsetIterator endSection(const Section section) const;
/// \brief Add a (pointer like object of) Question to the message.
///
- /// Only allowed in the \c RENDER mode.
+ /// This method is only allowed in the \c RENDER mode;
+ /// if the \c Message is in other mode, an exception of class
+ /// InvalidMessageOperation will be thrown.
void addQuestion(QuestionPtr question);
/// \brief Add a (pointer like object of) Question to the message.
@@ -406,7 +427,9 @@ public:
/// form may be more intuitive and may make more sense for performance
/// insensitive applications.
///
- /// Only allowed in the \c RENDER mode.
+ /// This method is only allowed in the \c RENDER mode;
+ /// if the \c Message is in other mode, an exception of class
+ /// InvalidMessageOperation will be thrown.
void addQuestion(const Question& question);
/// \brief Add a (pointer like object of) RRset to the given section
@@ -415,28 +438,73 @@ public:
/// This interface takes into account the RRSIG possibly attached to
/// \c rrset. This interface design needs to be revisited later.
///
- /// Only allowed in the \c RENDER mode.
+ /// This method is only allowed in the \c RENDER mode;
+ /// if the \c Message is in other mode, an exception of class
+ /// InvalidMessageOperation will be thrown.
+ /// \c section must be a valid constant of the \c Section type;
+ /// otherwise, an exception of class \c OutOfRange will be thrown.
///
- /// Note that addRRset() does not currently check for duplicate
+ /// Note that \c addRRset() does not currently check for duplicate
/// data before inserting RRsets. The caller is responsible for
- /// checking for these (see hasRRset() below).
- void addRRset(const Section& section, RRsetPtr rrset, bool sign = false);
+ /// checking for these (see \c hasRRset() below).
+ void addRRset(const Section section, RRsetPtr rrset, bool sign = false);
+
+ /// \brief Determine whether the given section already has an RRset
+ /// matching the given name, RR class and RR type.
+ ///
+ /// \c section must be a valid constant of the \c Section type;
+ /// otherwise, an exception of class \c OutOfRange will be thrown.
+ ///
+ /// This should probably be extended to be a "find" method that returns
+ /// a matching RRset if found.
+ bool hasRRset(const Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype);
/// \brief Determine whether the given section already has an RRset
- /// matching the name and type of this one
- bool hasRRset(const Section& section, RRsetPtr rrset);
+ /// matching the one pointed to by the argumet
+ ///
+ /// \c section must be a valid constant of the \c Section type;
+ /// otherwise, an exception of class \c OutOfRange will be thrown.
+ bool hasRRset(const Section section, const RRsetPtr& rrset);
+
+ /// \brief Remove RRSet from Message
+ ///
+ /// Removes the RRset identified by the section iterator from the message.
+ /// Note: if,.for some reason, the RRset is duplicated in the section, only
+ /// one occurrence is removed.
+ ///
+ /// If the operation is successful, all iterators into the section are
+ /// invalidated.
+ ///
+ /// \param section Section to which the iterator belongs
+ /// \param iterator Iterator pointing to the element to be removed
+ ///
+ /// \return true if the element was removed, false if the iterator was not
+ /// found in the specified section.
+ bool removeRRset(const Section section, RRsetIterator& iterator);
+
+ /// \brief Remove all RRSets from the given Section
+ ///
+ /// \param section Section to remove all rrsets from
+ void clearSection(const Section section);
// The following methods are not currently implemented.
//void removeQuestion(QuestionPtr question);
- //void removeRRset(const Section& section, RRsetPtr rrset);
// notyet:
- //void addRR(const Section& section, const RR& rr);
- //void removeRR(const Section& section, const RR& rr);
+ //void addRR(const Section section, const RR& rr);
+ //void removeRR(const Section section, const RR& rr);
/// \brief Clear the message content (if any) and reinitialize it in the
/// specified mode.
void clear(Mode mode);
+ /// \brief Adds all rrsets from the source the given section in the
+ /// source message to the same section of this message
+ ///
+ /// \param section the section to append
+ /// \param target The source Message
+ void appendSection(const Section section, const Message& source);
+
/// \brief Prepare for making a response from a request.
///
/// This will clear the DNS header except those fields that should be kept
@@ -476,14 +544,22 @@ public:
/// With EDNS the maximum size can be increased per message.
static const uint16_t DEFAULT_MAX_UDPSIZE = 512;
- /// \brief The highest EDNS version this implementation supports.
- static const uint8_t EDNS_SUPPORTED_VERSION = 0;
+ /// \brief The default maximum size of UDP DNS messages we can handle
+ static const uint16_t DEFAULT_MAX_EDNS0_UDPSIZE = 4096;
//@}
private:
MessageImpl* impl_;
};
+/// \brief Pointer-like type pointing to a \c Message
+///
+/// This type is expected to be used as an argument in asynchronous
+/// callback functions. The internal reference-counting will ensure that
+/// that ongoing state information will not be lost if the object
+/// that originated the asynchronous call falls out of scope.
+typedef boost::shared_ptr<Message> MessagePtr;
+
std::ostream& operator<<(std::ostream& os, const Message& message);
}
}
diff --git a/src/lib/dns/messagerenderer.cc b/src/lib/dns/messagerenderer.cc
index 1ff4c25..212411a 100644
--- a/src/lib/dns/messagerenderer.cc
+++ b/src/lib/dns/messagerenderer.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <cctype>
#include <cassert>
#include <set>
diff --git a/src/lib/dns/messagerenderer.h b/src/lib/dns/messagerenderer.h
index e27b35f..b698d22 100644
--- a/src/lib/dns/messagerenderer.h
+++ b/src/lib/dns/messagerenderer.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __MESSAGERENDERER_H
#define __MESSAGERENDERER_H 1
diff --git a/src/lib/dns/name.cc b/src/lib/dns/name.cc
index 4395267..103bca5 100644
--- a/src/lib/dns/name.cc
+++ b/src/lib/dns/name.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <cctype>
#include <cassert>
#include <iterator>
diff --git a/src/lib/dns/name.h b/src/lib/dns/name.h
index 5032372..47408fa 100644
--- a/src/lib/dns/name.h
+++ b/src/lib/dns/name.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __NAME_H
#define __NAME_H 1
diff --git a/src/lib/dns/opcode.cc b/src/lib/dns/opcode.cc
index 8141123..570dd81 100644
--- a/src/lib/dns/opcode.cc
+++ b/src/lib/dns/opcode.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <string>
#include <ostream>
diff --git a/src/lib/dns/opcode.h b/src/lib/dns/opcode.h
index c5499c1..dd88062 100644
--- a/src/lib/dns/opcode.h
+++ b/src/lib/dns/opcode.h
@@ -14,8 +14,6 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
-/* $Id$ */
-
#include <stdint.h>
#include <ostream>
diff --git a/src/lib/dns/python/Makefile.am b/src/lib/dns/python/Makefile.am
index 435f034..9d171cd 100644
--- a/src/lib/dns/python/Makefile.am
+++ b/src/lib/dns/python/Makefile.am
@@ -24,6 +24,7 @@ EXTRA_DIST += question_python.cc
EXTRA_DIST += rrttl_python.cc
EXTRA_DIST += rdata_python.cc
EXTRA_DIST += rrtype_python.cc
+EXTRA_DIST += tsigkey_python.cc
# Python prefers .so, while some OSes (specifically MacOS) use a different
# suffix for dynamic objects. -module is necessary to work this around.
diff --git a/src/lib/dns/python/edns_python.cc b/src/lib/dns/python/edns_python.cc
index dac2fb8..e54dba0 100644
--- a/src/lib/dns/python/edns_python.cc
+++ b/src/lib/dns/python/edns_python.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <cassert>
#include <dns/edns.h>
diff --git a/src/lib/dns/python/message_python.cc b/src/lib/dns/python/message_python.cc
index 8d09857..e19547e 100644
--- a/src/lib/dns/python/message_python.cc
+++ b/src/lib/dns/python/message_python.cc
@@ -12,8 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
+#include <exceptions/exceptions.h>
#include <dns/message.h>
using namespace isc::dns;
@@ -35,341 +34,10 @@ static PyObject* po_InvalidMessageUDPSize;
// and static wrappers around the methods we export), a list of methods,
// and a type description
-
-//
-// MessageFlag
-//
-class s_MessageFlag : public PyObject {
-public:
- const MessageFlag* messageflag;
-};
-
-static int MessageFlag_init(s_MessageFlag* self, PyObject* args);
-static void MessageFlag_destroy(s_MessageFlag* self);
-
-static PyObject* MessageFlag_getBit(s_MessageFlag* self);
-static PyObject* MessageFlag_QR(s_MessageFlag* self);
-static PyObject* MessageFlag_AA(s_MessageFlag* self);
-static PyObject* MessageFlag_TC(s_MessageFlag* self);
-static PyObject* MessageFlag_RD(s_MessageFlag* self);
-static PyObject* MessageFlag_RA(s_MessageFlag* self);
-static PyObject* MessageFlag_AD(s_MessageFlag* self);
-static PyObject* MessageFlag_CD(s_MessageFlag* self);
-
-static PyMethodDef MessageFlag_methods[] = {
- { "get_bit", reinterpret_cast<PyCFunction>(MessageFlag_getBit), METH_NOARGS, "Returns the flag bit" },
- { "QR", reinterpret_cast<PyCFunction>(MessageFlag_QR), METH_NOARGS | METH_STATIC, "Creates a QR MessageFlag" },
- { "AA", reinterpret_cast<PyCFunction>(MessageFlag_AA), METH_NOARGS | METH_STATIC, "Creates a AA MessageFlag" },
- { "TC", reinterpret_cast<PyCFunction>(MessageFlag_TC), METH_NOARGS | METH_STATIC, "Creates a TC MessageFlag" },
- { "RD", reinterpret_cast<PyCFunction>(MessageFlag_RD), METH_NOARGS | METH_STATIC, "Creates a RD MessageFlag" },
- { "RA", reinterpret_cast<PyCFunction>(MessageFlag_RA), METH_NOARGS | METH_STATIC, "Creates a RA MessageFlag" },
- { "AD", reinterpret_cast<PyCFunction>(MessageFlag_AD), METH_NOARGS | METH_STATIC, "Creates a AD MessageFlag" },
- { "CD", reinterpret_cast<PyCFunction>(MessageFlag_CD), METH_NOARGS | METH_STATIC, "Creates a CD MessageFlag" },
- { NULL, NULL, 0, NULL }
-};
-
-static PyTypeObject messageflag_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "pydnspp.MessageFlag",
- sizeof(s_MessageFlag), // tp_basicsize
- 0, // tp_itemsize
- (destructor)MessageFlag_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- NULL, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "The MessageFlag class objects represent standard "
- "flag bits of the header section of DNS messages.",
- NULL, // tp_traverse
- NULL, // tp_clear
- NULL, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- MessageFlag_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)MessageFlag_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
-static int
-MessageFlag_init(s_MessageFlag* self UNUSED_PARAM,
- PyObject* args UNUSED_PARAM)
-{
- PyErr_SetString(PyExc_NotImplementedError,
- "MessageFlag can't be built directly");
- return (-1);
-}
-
-static void
-MessageFlag_destroy(s_MessageFlag* self) {
- // We only use the consts from MessageFlag, so don't
- // delete self->messageflag here
- self->messageflag = NULL;
- Py_TYPE(self)->tp_free(self);
-}
-
-static PyObject*
-MessageFlag_getBit(s_MessageFlag* self) {
- return (Py_BuildValue("I", self->messageflag->getBit()));
-}
-
-static PyObject*
-MessageFlag_createStatic(const MessageFlag& flag) {
- s_MessageFlag* ret = PyObject_New(s_MessageFlag, &messageflag_type);
- if (ret != NULL) {
- ret->messageflag = &flag;
- }
- return (ret);
-}
-
-static PyObject*
-MessageFlag_QR(s_MessageFlag* self UNUSED_PARAM) {
- return (MessageFlag_createStatic(MessageFlag::QR()));
-}
-
-static PyObject*
-MessageFlag_AA(s_MessageFlag* self UNUSED_PARAM) {
- return (MessageFlag_createStatic(MessageFlag::AA()));
-}
-
-static PyObject*
-MessageFlag_TC(s_MessageFlag* self UNUSED_PARAM) {
- return (MessageFlag_createStatic(MessageFlag::TC()));
-}
-
-static PyObject*
-MessageFlag_RD(s_MessageFlag* self UNUSED_PARAM) {
- return (MessageFlag_createStatic(MessageFlag::RD()));
-}
-
-static PyObject*
-MessageFlag_RA(s_MessageFlag* self UNUSED_PARAM) {
- return (MessageFlag_createStatic(MessageFlag::RA()));
-}
-
-static PyObject*
-MessageFlag_AD(s_MessageFlag* self UNUSED_PARAM) {
- return (MessageFlag_createStatic(MessageFlag::AD()));
-}
-
-static PyObject*
-MessageFlag_CD(s_MessageFlag* self UNUSED_PARAM) {
- return (MessageFlag_createStatic(MessageFlag::CD()));
-}
-
-//
-// End of MessageFlag wrapper
-//
-
//
// Section
//
-// TODO: iterator?
-
-class s_Section : public PyObject {
-public:
- const Section* section;
-};
-
-static int Section_init(s_Section* self, PyObject* args);
-static void Section_destroy(s_Section* self);
-
-static PyObject* Section_getCode(s_Section* self);
-static PyObject* Section_QUESTION(s_Section* self);
-static PyObject* Section_ANSWER(s_Section* self);
-static PyObject* Section_AUTHORITY(s_Section* self);
-static PyObject* Section_ADDITIONAL(s_Section* self);
-static PyObject* Section_richcmp(s_Section* self, s_Section* other, int op);
-
-static PyMethodDef Section_methods[] = {
- { "get_code", reinterpret_cast<PyCFunction>(Section_getCode), METH_NOARGS, "Returns the code value" },
- { "QUESTION", reinterpret_cast<PyCFunction>(Section_QUESTION), METH_NOARGS | METH_STATIC, "Creates a QUESTION Section" },
- { "ANSWER", reinterpret_cast<PyCFunction>(Section_ANSWER), METH_NOARGS | METH_STATIC, "Creates an ANSWER Section" },
- { "AUTHORITY", reinterpret_cast<PyCFunction>(Section_AUTHORITY), METH_NOARGS | METH_STATIC, "Creates an AUTHORITY Section" },
- { "ADDITIONAL", reinterpret_cast<PyCFunction>(Section_ADDITIONAL), METH_NOARGS | METH_STATIC, "Creates an ADDITIONAL Section" },
- { NULL, NULL, 0, NULL }
-};
-
-static PyTypeObject section_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "pydnspp.Section",
- sizeof(s_Section), // tp_basicsize
- 0, // tp_itemsize
- (destructor)Section_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- NULL, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "The Section class objects represent DNS message sections such "
- "as the header, question, or answer.",
- NULL, // tp_traverse
- NULL, // tp_clear
- (richcmpfunc)Section_richcmp, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- Section_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)Section_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
-
-static int
-Section_init(s_Section* self UNUSED_PARAM,
- PyObject* args UNUSED_PARAM)
-{
- PyErr_SetString(PyExc_NotImplementedError,
- "Section can't be built directly");
- return (-1);
-}
-
-static void
-Section_destroy(s_Section* self) {
- // We only use the consts from Section, so don't
- // delete self->section here
- self->section = NULL;
- Py_TYPE(self)->tp_free(self);
-}
-
-static PyObject*
-Section_getCode(s_Section* self) {
- return (Py_BuildValue("I", self->section->getCode()));
-}
-
-static PyObject*
-Section_createStatic(const Section& section) {
- s_Section* ret = PyObject_New(s_Section, §ion_type);
- if (ret != NULL) {
- ret->section = §ion;
- }
- return (ret);
-}
-
-
-static PyObject*
-Section_QUESTION(s_Section* self UNUSED_PARAM) {
- return (Section_createStatic(Section::QUESTION()));
-}
-
-static PyObject*
-Section_ANSWER(s_Section* self UNUSED_PARAM) {
- return (Section_createStatic(Section::ANSWER()));
-}
-
-static PyObject*
-Section_AUTHORITY(s_Section* self UNUSED_PARAM) {
- return (Section_createStatic(Section::AUTHORITY()));
-}
-
-static PyObject*
-Section_ADDITIONAL(s_Section* self UNUSED_PARAM) {
- return (Section_createStatic(Section::ADDITIONAL()));
-}
-
-static PyObject*
-Section_richcmp(s_Section* self, s_Section* other, int op) {
- bool c = false;
-
- // Check for null and if the types match. If different type,
- // simply return False
- if (!other || (self->ob_type != other->ob_type)) {
- Py_RETURN_FALSE;
- }
-
- // Only equals and not equals here, unorderable type
- switch (op) {
- case Py_LT:
- PyErr_SetString(PyExc_TypeError, "Unorderable type; Section");
- return (NULL);
- break;
- case Py_LE:
- PyErr_SetString(PyExc_TypeError, "Unorderable type; Section");
- return (NULL);
- break;
- case Py_EQ:
- c = (*self->section == *other->section);
- break;
- case Py_NE:
- c = (*self->section != *other->section);
- break;
- case Py_GT:
- PyErr_SetString(PyExc_TypeError, "Unorderable type; Section");
- return (NULL);
- break;
- case Py_GE:
- PyErr_SetString(PyExc_TypeError, "Unorderable type; Section");
- return (NULL);
- break;
- }
- if (c)
- Py_RETURN_TRUE;
- else
- Py_RETURN_FALSE;
-}
-
-//
-// End of Section wrapper
-//
-
-
-
//
// Message
//
@@ -391,7 +59,6 @@ static void Message_destroy(s_Message* self);
static PyObject* Message_getHeaderFlag(s_Message* self, PyObject* args);
static PyObject* Message_setHeaderFlag(s_Message* self, PyObject* args);
-static PyObject* Message_clearHeaderFlag(s_Message* self, PyObject* args);
static PyObject* Message_getQid(s_Message* self);
static PyObject* Message_setQid(s_Message* self, PyObject* args);
static PyObject* Message_getRcode(s_Message* self);
@@ -425,17 +92,15 @@ static PyObject* Message_fromWire(s_Message* self, PyObject* args);
// 3. Argument type
// 4. Documentation
static PyMethodDef Message_methods[] = {
- { "get_header_flag", reinterpret_cast<PyCFunction>(Message_getHeaderFlag), METH_VARARGS,
+ { "get_header_flag", reinterpret_cast<PyCFunction>(Message_getHeaderFlag),
+ METH_VARARGS,
"Return whether the specified header flag bit is set in the "
"header section. Takes a MessageFlag object as the only argument." },
- { "set_header_flag", reinterpret_cast<PyCFunction>(Message_setHeaderFlag), METH_VARARGS,
+ { "set_header_flag",
+ reinterpret_cast<PyCFunction>(Message_setHeaderFlag), METH_VARARGS,
"Sets the specified header flag bit to 1. The message must be in "
"RENDER mode. If not, an InvalidMessageOperation is raised. "
"Takes a MessageFlag object as the only argument." },
- { "clear_header_flag", reinterpret_cast<PyCFunction>(Message_clearHeaderFlag), METH_VARARGS,
- "Sets the specified header flag bit to 0. The message must be in "
- "RENDER mode. If not, an InvalidMessageOperation is raised. "
- "Takes a MessageFlag object as the only argument." },
{ "get_qid", reinterpret_cast<PyCFunction>(Message_getQid), METH_NOARGS,
"Returns the query id" },
{ "set_qid", reinterpret_cast<PyCFunction>(Message_setQid), METH_VARARGS,
@@ -562,8 +227,6 @@ static PyTypeObject message_type = {
static int
Message_init(s_Message* self, PyObject* args) {
unsigned int i;
- // The constructor argument can be a string ("IN"), an integer (1),
- // or a sequence of numbers between 0 and 255 (wire code)
if (PyArg_ParseTuple(args, "I", &i)) {
PyErr_Clear();
@@ -593,12 +256,16 @@ Message_destroy(s_Message* self) {
static PyObject*
Message_getHeaderFlag(s_Message* self, PyObject* args) {
- s_MessageFlag* messageflag;
- if (!PyArg_ParseTuple(args, "O!", &messageflag_type, &messageflag)) {
+ unsigned int messageflag;
+ if (!PyArg_ParseTuple(args, "I", &messageflag)) {
+ PyErr_Clear();
+ PyErr_SetString(PyExc_TypeError,
+ "no valid type in get_header_flag argument");
return (NULL);
}
-
- if (self->message->getHeaderFlag(*messageflag->messageflag)) {
+
+ if (self->message->getHeaderFlag(
+ static_cast<Message::HeaderFlag>(messageflag))) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
@@ -607,38 +274,33 @@ Message_getHeaderFlag(s_Message* self, PyObject* args) {
static PyObject*
Message_setHeaderFlag(s_Message* self, PyObject* args) {
- s_MessageFlag* messageflag;
- if (!PyArg_ParseTuple(args, "O!", &messageflag_type, &messageflag)) {
- return (NULL);
- }
+ int messageflag;
+ PyObject *on = Py_True;
- try {
- self->message->setHeaderFlag(*messageflag->messageflag);
- Py_RETURN_NONE;
- } catch (const InvalidMessageOperation& imo) {
+ if (!PyArg_ParseTuple(args, "i|O!", &messageflag, &PyBool_Type, &on)) {
PyErr_Clear();
- PyErr_SetString(po_InvalidMessageOperation, imo.what());
+ PyErr_SetString(PyExc_TypeError,
+ "no valid type in set_header_flag argument");
return (NULL);
}
-}
-
-static PyObject*
-Message_clearHeaderFlag(s_Message* self, PyObject* args) {
- s_MessageFlag* messageflag;
- if (!PyArg_ParseTuple(args, "O!", &messageflag_type, &messageflag)) {
+ if (messageflag < 0) {
+ PyErr_SetString(PyExc_TypeError, "invalid Message header flag");
return (NULL);
}
try {
- self->message->clearHeaderFlag(*messageflag->messageflag);
+ self->message->setHeaderFlag(
+ static_cast<Message::HeaderFlag>(messageflag), on == Py_True);
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_Clear();
PyErr_SetString(po_InvalidMessageOperation, imo.what());
return (NULL);
+ } catch (const isc::InvalidParameter& ip) {
+ PyErr_Clear();
+ PyErr_SetString(po_InvalidParameter, ip.what());
+ return (NULL);
}
-
- Py_RETURN_NONE;
}
static PyObject*
@@ -774,58 +436,107 @@ Message_setEDNS(s_Message* self, PyObject* args) {
static PyObject*
Message_getRRCount(s_Message* self, PyObject* args) {
- s_Section *section;
- if (!PyArg_ParseTuple(args, "O!", §ion_type, §ion)) {
+ unsigned int section;
+ if (!PyArg_ParseTuple(args, "I", §ion)) {
+ PyErr_Clear();
+ PyErr_SetString(PyExc_TypeError,
+ "no valid type in get_rr_count argument");
+ return (NULL);
+ }
+ try {
+ return (Py_BuildValue("I", self->message->getRRCount(
+ static_cast<Message::Section>(section))));
+ } catch (const isc::OutOfRange& ex) {
+ PyErr_SetString(PyExc_OverflowError, ex.what());
return (NULL);
}
- return (Py_BuildValue("I", self->message->getRRCount(*section->section)));
}
// TODO use direct iterators for these? (or simply lists for now?)
static PyObject*
Message_getQuestion(s_Message* self) {
+ QuestionIterator qi, qi_end;
+ try {
+ qi = self->message->beginQuestion();
+ qi_end = self->message->endQuestion();
+ } catch (const InvalidMessageSection& ex) {
+ PyErr_SetString(po_InvalidMessageSection, ex.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in getting section iterators");
+ return (NULL);
+ }
+
PyObject* list = PyList_New(0);
-
- for (QuestionIterator qi = self->message->beginQuestion();
- qi != self->message->endQuestion();
- ++qi) {
- s_Question *question = static_cast<s_Question*>(question_type.tp_alloc(&question_type, 0));
- if (question != NULL) {
- question->question = *qi;
- if (question->question == NULL)
- {
- Py_DECREF(question);
- return (NULL);
- }
+ if (list == NULL) {
+ return (NULL);
+ }
+
+ for (; qi != qi_end; ++qi) {
+ s_Question *question = static_cast<s_Question*>(
+ question_type.tp_alloc(&question_type, 0));
+ if (question == NULL) {
+ Py_DECREF(question);
+ Py_DECREF(list);
+ return (NULL);
+ }
+ question->question = *qi;
+ if (PyList_Append(list, question) == -1) {
+ Py_DECREF(question);
+ Py_DECREF(list);
+ return (NULL);
}
- PyList_Append(list, question);
+ Py_DECREF(question);
}
return (list);
}
static PyObject*
Message_getSection(s_Message* self, PyObject* args) {
- s_Section *section;
- if (!PyArg_ParseTuple(args, "O!", §ion_type, §ion)) {
+ unsigned int section;
+ if (!PyArg_ParseTuple(args, "I", §ion)) {
+ PyErr_Clear();
+ PyErr_SetString(PyExc_TypeError,
+ "no valid type in get_section argument");
+ return (NULL);
+ }
+ RRsetIterator rrsi, rrsi_end;
+ try {
+ rrsi = self->message->beginSection(
+ static_cast<Message::Section>(section));
+ rrsi_end = self->message->endSection(
+ static_cast<Message::Section>(section));
+ } catch (const isc::OutOfRange& ex) {
+ PyErr_SetString(PyExc_OverflowError, ex.what());
+ return (NULL);
+ } catch (const InvalidMessageSection& ex) {
+ PyErr_SetString(po_InvalidMessageSection, ex.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in getting section iterators");
return (NULL);
}
- PyObject* list = PyList_New(0);
-
- for (RRsetIterator rrsi = self->message->beginSection(*section->section);
- rrsi != self->message->endSection(*section->section);
- ++rrsi) {
- s_RRset *rrset = static_cast<s_RRset*>(rrset_type.tp_alloc(&rrset_type, 0));
- if (rrset != NULL) {
- rrset->rrset = *rrsi;
- if (rrset->rrset == NULL)
- {
+ PyObject* list = PyList_New(0);
+ if (list == NULL) {
+ return (NULL);
+ }
+ for (; rrsi != rrsi_end; ++rrsi) {
+ s_RRset *rrset = static_cast<s_RRset*>(
+ rrset_type.tp_alloc(&rrset_type, 0));
+ if (rrset == NULL) {
+ Py_DECREF(rrset);
+ Py_DECREF(list);
+ return (NULL);
+ }
+ rrset->rrset = *rrsi;
+ if (PyList_Append(list, rrset) == -1) {
Py_DECREF(rrset);
Py_DECREF(list);
return (NULL);
- }
}
- PyList_Append(list, rrset);
// PyList_Append increases refcount, so we remove ours since
// we don't need it anymore
Py_DECREF(rrset);
@@ -854,33 +565,33 @@ Message_addQuestion(s_Message* self, PyObject* args) {
static PyObject*
Message_addRRset(s_Message* self, PyObject* args) {
PyObject *sign = Py_False;
- s_Section* section;
+ unsigned int section;
s_RRset* rrset;
- if (!PyArg_ParseTuple(args, "O!O!|O!", §ion_type, §ion,
- &rrset_type, &rrset,
- &PyBool_Type, &sign)) {
+ if (!PyArg_ParseTuple(args, "IO!|O!", §ion, &rrset_type, &rrset,
+ &PyBool_Type, &sign)) {
return (NULL);
}
try {
- if (sign == Py_True) {
- self->message->addRRset(*section->section, rrset->rrset, true);
- } else {
- self->message->addRRset(*section->section, rrset->rrset, false);
- }
+ self->message->addRRset(static_cast<Message::Section>(section),
+ rrset->rrset, sign == Py_True);
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
return (NULL);
+ } catch (const isc::OutOfRange& ex) {
+ PyErr_SetString(PyExc_OverflowError, ex.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in adding RRset");
+ return (NULL);
}
}
static PyObject*
Message_clear(s_Message* self, PyObject* args) {
unsigned int i;
- // The constructor argument can be a string ("IN"), an integer (1),
- // or a sequence of numbers between 0 and 255 (wire code)
-
if (PyArg_ParseTuple(args, "I", &i)) {
PyErr_Clear();
if (i == Message::PARSE) {
@@ -890,7 +601,8 @@ Message_clear(s_Message* self, PyObject* args) {
self->message->clear(Message::RENDER);
Py_RETURN_NONE;
} else {
- PyErr_SetString(PyExc_TypeError, "Message mode must be Message.PARSE or Message.RENDER");
+ PyErr_SetString(PyExc_TypeError,
+ "Message mode must be Message.PARSE or Message.RENDER");
return (NULL);
}
} else {
@@ -979,62 +691,64 @@ Message_fromWire(s_Message* self, PyObject* args) {
// Module Initialization, all statics are initialized here
bool
initModulePart_Message(PyObject* mod) {
-
- // add methods to class
- if (PyType_Ready(&messageflag_type) < 0) {
- return (false);
- }
- Py_INCREF(&messageflag_type);
- PyModule_AddObject(mod, "MessageFlag",
- reinterpret_cast<PyObject*>(&messageflag_type));
-
-
- if (PyType_Ready(&opcode_type) < 0) {
- return (false);
- }
- Py_INCREF(&opcode_type);
- PyModule_AddObject(mod, "Opcode",
- reinterpret_cast<PyObject*>(&opcode_type));
-
- if (PyType_Ready(&rcode_type) < 0) {
- return (false);
- }
- Py_INCREF(&rcode_type);
- PyModule_AddObject(mod, "Rcode",
- reinterpret_cast<PyObject*>(&rcode_type));
-
- if (PyType_Ready(§ion_type) < 0) {
- return (false);
- }
- Py_INCREF(§ion_type);
- PyModule_AddObject(mod, "Section",
- reinterpret_cast<PyObject*>(§ion_type));
-
-
if (PyType_Ready(&message_type) < 0) {
return (false);
}
+ Py_INCREF(&message_type);
// Class variables
// These are added to the tp_dict of the type object
//
- addClassVariable(message_type, "PARSE", Py_BuildValue("I", Message::PARSE));
- addClassVariable(message_type, "RENDER", Py_BuildValue("I", Message::RENDER));
- addClassVariable(message_type, "DEFAULT_MAX_UDPSIZE", Py_BuildValue("I", Message::DEFAULT_MAX_UDPSIZE));
+ addClassVariable(message_type, "PARSE",
+ Py_BuildValue("I", Message::PARSE));
+ addClassVariable(message_type, "RENDER",
+ Py_BuildValue("I", Message::RENDER));
+
+ addClassVariable(message_type, "HEADERFLAG_QR",
+ Py_BuildValue("I", Message::HEADERFLAG_QR));
+ addClassVariable(message_type, "HEADERFLAG_AA",
+ Py_BuildValue("I", Message::HEADERFLAG_AA));
+ addClassVariable(message_type, "HEADERFLAG_TC",
+ Py_BuildValue("I", Message::HEADERFLAG_TC));
+ addClassVariable(message_type, "HEADERFLAG_RD",
+ Py_BuildValue("I", Message::HEADERFLAG_RD));
+ addClassVariable(message_type, "HEADERFLAG_RA",
+ Py_BuildValue("I", Message::HEADERFLAG_RA));
+ addClassVariable(message_type, "HEADERFLAG_AD",
+ Py_BuildValue("I", Message::HEADERFLAG_AD));
+ addClassVariable(message_type, "HEADERFLAG_CD",
+ Py_BuildValue("I", Message::HEADERFLAG_CD));
+
+ addClassVariable(message_type, "SECTION_QUESTION",
+ Py_BuildValue("I", Message::SECTION_QUESTION));
+ addClassVariable(message_type, "SECTION_ANSWER",
+ Py_BuildValue("I", Message::SECTION_ANSWER));
+ addClassVariable(message_type, "SECTION_AUTHORITY",
+ Py_BuildValue("I", Message::SECTION_AUTHORITY));
+ addClassVariable(message_type, "SECTION_ADDITIONAL",
+ Py_BuildValue("I", Message::SECTION_ADDITIONAL));
+
+ addClassVariable(message_type, "DEFAULT_MAX_UDPSIZE",
+ Py_BuildValue("I", Message::DEFAULT_MAX_UDPSIZE));
/* Class-specific exceptions */
- po_MessageTooShort = PyErr_NewException("pydnspp.MessageTooShort", NULL, NULL);
+ po_MessageTooShort = PyErr_NewException("pydnspp.MessageTooShort", NULL,
+ NULL);
PyModule_AddObject(mod, "MessageTooShort", po_MessageTooShort);
- po_InvalidMessageSection = PyErr_NewException("pydnspp.InvalidMessageSection", NULL, NULL);
+ po_InvalidMessageSection =
+ PyErr_NewException("pydnspp.InvalidMessageSection", NULL, NULL);
PyModule_AddObject(mod, "InvalidMessageSection", po_InvalidMessageSection);
- po_InvalidMessageOperation = PyErr_NewException("pydnspp.InvalidMessageOperation", NULL, NULL);
- PyModule_AddObject(mod, "InvalidMessageOperation", po_InvalidMessageOperation);
- po_InvalidMessageUDPSize = PyErr_NewException("pydnspp.InvalidMessageUDPSize", NULL, NULL);
+ po_InvalidMessageOperation =
+ PyErr_NewException("pydnspp.InvalidMessageOperation", NULL, NULL);
+ PyModule_AddObject(mod, "InvalidMessageOperation",
+ po_InvalidMessageOperation);
+ po_InvalidMessageUDPSize =
+ PyErr_NewException("pydnspp.InvalidMessageUDPSize", NULL, NULL);
PyModule_AddObject(mod, "InvalidMessageUDPSize", po_InvalidMessageUDPSize);
- po_DNSMessageBADVERS = PyErr_NewException("pydnspp.DNSMessageBADVERS", NULL, NULL);
+ po_DNSMessageBADVERS = PyErr_NewException("pydnspp.DNSMessageBADVERS",
+ NULL, NULL);
PyModule_AddObject(mod, "DNSMessageBADVERS", po_DNSMessageBADVERS);
- Py_INCREF(&message_type);
PyModule_AddObject(mod, "Message",
reinterpret_cast<PyObject*>(&message_type));
diff --git a/src/lib/dns/python/messagerenderer_python.cc b/src/lib/dns/python/messagerenderer_python.cc
index 3d154bd..a00d8d4 100644
--- a/src/lib/dns/python/messagerenderer_python.cc
+++ b/src/lib/dns/python/messagerenderer_python.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/messagerenderer.h>
// For each class, we need a struct, a helper functions (init, destroy,
@@ -39,9 +37,10 @@ static PyObject* MessageRenderer_getData(s_MessageRenderer* self);
static PyObject* MessageRenderer_getLength(s_MessageRenderer* self);
static PyObject* MessageRenderer_isTruncated(s_MessageRenderer* self);
static PyObject* MessageRenderer_getLengthLimit(s_MessageRenderer* self);
-// TODO: set/get compressmode
+static PyObject* MessageRenderer_getCompressMode(s_MessageRenderer* self);
static PyObject* MessageRenderer_setTruncated(s_MessageRenderer* self);
static PyObject* MessageRenderer_setLengthLimit(s_MessageRenderer* self, PyObject* args);
+static PyObject* MessageRenderer_setCompressMode(s_MessageRenderer* self, PyObject* args);
static PyObject* MessageRenderer_clear(s_MessageRenderer* self);
static PyMethodDef MessageRenderer_methods[] = {
@@ -53,10 +52,14 @@ static PyMethodDef MessageRenderer_methods[] = {
"Returns True if the data is truncated" },
{ "get_length_limit", reinterpret_cast<PyCFunction>(MessageRenderer_getLengthLimit), METH_NOARGS,
"Returns the length limit of the data" },
+ { "get_compress_mode", reinterpret_cast<PyCFunction>(MessageRenderer_getCompressMode), METH_NOARGS,
+ "Returns the current compression mode" },
{ "set_truncated", reinterpret_cast<PyCFunction>(MessageRenderer_setTruncated), METH_NOARGS,
"Sets truncated to true" },
{ "set_length_limit", reinterpret_cast<PyCFunction>(MessageRenderer_setLengthLimit), METH_VARARGS,
"Sets the length limit of the data to the given number" },
+ { "set_compress_mode", reinterpret_cast<PyCFunction>(MessageRenderer_setCompressMode), METH_VARARGS,
+ "Sets the compression mode of the MessageRenderer" },
{ "clear", reinterpret_cast<PyCFunction>(MessageRenderer_clear),
METH_NOARGS,
"Clear the internal buffer and other internal resources." },
@@ -161,6 +164,11 @@ MessageRenderer_getLengthLimit(s_MessageRenderer* self) {
}
static PyObject*
+MessageRenderer_getCompressMode(s_MessageRenderer* self) {
+ return (Py_BuildValue("I", self->messagerenderer->getCompressMode()));
+}
+
+static PyObject*
MessageRenderer_setTruncated(s_MessageRenderer* self) {
self->messagerenderer->setTruncated();
Py_RETURN_NONE;
@@ -179,6 +187,31 @@ MessageRenderer_setLengthLimit(s_MessageRenderer* self,
}
static PyObject*
+MessageRenderer_setCompressMode(s_MessageRenderer* self,
+ PyObject* args)
+{
+ unsigned int mode;
+ if (!PyArg_ParseTuple(args, "I", &mode)) {
+ return (NULL);
+ }
+
+ if (mode == MessageRenderer::CASE_INSENSITIVE) {
+ self->messagerenderer->setCompressMode(MessageRenderer::CASE_INSENSITIVE);
+ // If we return NULL it is seen as an error, so use this for
+ // None returns, it also applies to CASE_SENSITIVE.
+ Py_RETURN_NONE;
+ } else if (mode == MessageRenderer::CASE_SENSITIVE) {
+ self->messagerenderer->setCompressMode(MessageRenderer::CASE_SENSITIVE);
+ Py_RETURN_NONE;
+ } else {
+ PyErr_SetString(PyExc_TypeError,
+ "MessageRenderer compress mode must be MessageRenderer.CASE_INSENSITIVE"
+ "or MessageRenderer.CASE_SENSITIVE");
+ return (NULL);
+ }
+}
+
+static PyObject*
MessageRenderer_clear(s_MessageRenderer* self) {
self->messagerenderer->clear();
Py_RETURN_NONE;
@@ -205,6 +238,14 @@ initModulePart_MessageRenderer(PyObject* mod) {
return (false);
}
Py_INCREF(&messagerenderer_type);
+
+ // Class variables
+ // These are added to the tp_dict of the type object
+ addClassVariable(messagerenderer_type, "CASE_INSENSITIVE",
+ Py_BuildValue("I", MessageRenderer::CASE_INSENSITIVE));
+ addClassVariable(messagerenderer_type, "CASE_SENSITIVE",
+ Py_BuildValue("I", MessageRenderer::CASE_SENSITIVE));
+
PyModule_AddObject(mod, "MessageRenderer",
reinterpret_cast<PyObject*>(&messagerenderer_type));
diff --git a/src/lib/dns/python/name_python.cc b/src/lib/dns/python/name_python.cc
index ae8b25b..3d7a196 100644
--- a/src/lib/dns/python/name_python.cc
+++ b/src/lib/dns/python/name_python.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
//
// Declaration of the custom exceptions
// Initialization and addition of these go in the module init at the
@@ -50,7 +48,7 @@ public:
isc::dns::NameComparisonResult* ncr;
};
-static int NameComparisonResult_init(s_NameComparisonResult* self UNUSED_PARAM, PyObject* args UNUSED_PARAM);
+static int NameComparisonResult_init(s_NameComparisonResult*, PyObject*);
static void NameComparisonResult_destroy(s_NameComparisonResult* self);
static PyObject* NameComparisonResult_getOrder(s_NameComparisonResult* self);
static PyObject* NameComparisonResult_getCommonLabels(s_NameComparisonResult* self);
@@ -123,9 +121,7 @@ static PyTypeObject name_comparison_result_type = {
};
static int
-NameComparisonResult_init(s_NameComparisonResult* self UNUSED_PARAM,
- PyObject* args UNUSED_PARAM)
-{
+NameComparisonResult_init(s_NameComparisonResult*, PyObject*) {
PyErr_SetString(PyExc_NotImplementedError,
"NameComparisonResult can't be built directly");
return (-1);
diff --git a/src/lib/dns/python/opcode_python.cc b/src/lib/dns/python/opcode_python.cc
index a1a2e1b..0e2a30b 100644
--- a/src/lib/dns/python/opcode_python.cc
+++ b/src/lib/dns/python/opcode_python.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/opcode.h>
using namespace isc::dns;
@@ -220,82 +218,82 @@ Opcode_createStatic(const Opcode& opcode) {
}
PyObject*
-Opcode_QUERY(const s_Opcode* self UNUSED_PARAM) {
+Opcode_QUERY(const s_Opcode*) {
return (Opcode_createStatic(Opcode::QUERY()));
}
PyObject*
-Opcode_IQUERY(const s_Opcode* self UNUSED_PARAM) {
+Opcode_IQUERY(const s_Opcode*) {
return (Opcode_createStatic(Opcode::IQUERY()));
}
PyObject*
-Opcode_STATUS(const s_Opcode* self UNUSED_PARAM) {
+Opcode_STATUS(const s_Opcode*) {
return (Opcode_createStatic(Opcode::STATUS()));
}
PyObject*
-Opcode_RESERVED3(const s_Opcode* self UNUSED_PARAM) {
+Opcode_RESERVED3(const s_Opcode*) {
return (Opcode_createStatic(Opcode::RESERVED3()));
}
PyObject*
-Opcode_NOTIFY(const s_Opcode* self UNUSED_PARAM) {
+Opcode_NOTIFY(const s_Opcode*) {
return (Opcode_createStatic(Opcode::NOTIFY()));
}
PyObject*
-Opcode_UPDATE(const s_Opcode* self UNUSED_PARAM) {
+Opcode_UPDATE(const s_Opcode*) {
return (Opcode_createStatic(Opcode::UPDATE()));
}
PyObject*
-Opcode_RESERVED6(const s_Opcode* self UNUSED_PARAM) {
+Opcode_RESERVED6(const s_Opcode*) {
return (Opcode_createStatic(Opcode::RESERVED6()));
}
PyObject*
-Opcode_RESERVED7(const s_Opcode* self UNUSED_PARAM) {
+Opcode_RESERVED7(const s_Opcode*) {
return (Opcode_createStatic(Opcode::RESERVED7()));
}
PyObject*
-Opcode_RESERVED8(const s_Opcode* self UNUSED_PARAM) {
+Opcode_RESERVED8(const s_Opcode*) {
return (Opcode_createStatic(Opcode::RESERVED8()));
}
PyObject*
-Opcode_RESERVED9(const s_Opcode* self UNUSED_PARAM) {
+Opcode_RESERVED9(const s_Opcode*) {
return (Opcode_createStatic(Opcode::RESERVED9()));
}
PyObject*
-Opcode_RESERVED10(const s_Opcode* self UNUSED_PARAM) {
+Opcode_RESERVED10(const s_Opcode*) {
return (Opcode_createStatic(Opcode::RESERVED10()));
}
PyObject*
-Opcode_RESERVED11(const s_Opcode* self UNUSED_PARAM) {
+Opcode_RESERVED11(const s_Opcode*) {
return (Opcode_createStatic(Opcode::RESERVED11()));
}
PyObject*
-Opcode_RESERVED12(const s_Opcode* self UNUSED_PARAM) {
+Opcode_RESERVED12(const s_Opcode*) {
return (Opcode_createStatic(Opcode::RESERVED12()));
}
PyObject*
-Opcode_RESERVED13(const s_Opcode* self UNUSED_PARAM) {
+Opcode_RESERVED13(const s_Opcode*) {
return (Opcode_createStatic(Opcode::RESERVED13()));
}
PyObject*
-Opcode_RESERVED14(const s_Opcode* self UNUSED_PARAM) {
+Opcode_RESERVED14(const s_Opcode*) {
return (Opcode_createStatic(Opcode::RESERVED14()));
}
PyObject*
-Opcode_RESERVED15(const s_Opcode* self UNUSED_PARAM) {
+Opcode_RESERVED15(const s_Opcode*) {
return (Opcode_createStatic(Opcode::RESERVED15()));
}
diff --git a/src/lib/dns/python/pydnspp.cc b/src/lib/dns/python/pydnspp.cc
index 3729e59..7b41598 100644
--- a/src/lib/dns/python/pydnspp.cc
+++ b/src/lib/dns/python/pydnspp.cc
@@ -23,8 +23,6 @@
//
// And of course care has to be taken that all identifiers be unique
-// $Id$
-
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <structmember.h>
@@ -57,6 +55,7 @@ static PyObject* po_DNSMessageBADVERS;
#include <dns/python/rrset_python.cc> // needs Rdata, RRTTL
#include <dns/python/question_python.cc> // needs RRClass, RRType, RRTTL,
// Name
+#include <dns/python/tsigkey_python.cc> // needs Name
#include <dns/python/opcode_python.cc>
#include <dns/python/rcode_python.cc>
#include <dns/python/edns_python.cc> // needs Messagerenderer, Rcode
@@ -146,6 +145,14 @@ PyInit_pydnspp(void) {
return (NULL);
}
+ if (!initModulePart_TSIGKey(mod)) {
+ return (NULL);
+ }
+
+ if (!initModulePart_TSIGKeyRing(mod)) {
+ return (NULL);
+ }
+
return (mod);
}
diff --git a/src/lib/dns/python/pydnspp_common.cc b/src/lib/dns/python/pydnspp_common.cc
index 1b10d6f..6c26367 100644
--- a/src/lib/dns/python/pydnspp_common.cc
+++ b/src/lib/dns/python/pydnspp_common.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <Python.h>
#include <pydnspp_common.h>
diff --git a/src/lib/dns/python/pydnspp_common.h b/src/lib/dns/python/pydnspp_common.h
index 750e344..32e2b78 100644
--- a/src/lib/dns/python/pydnspp_common.h
+++ b/src/lib/dns/python/pydnspp_common.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __LIBDNS_PYTHON_COMMON_H
#define __LIBDNS_PYTHON_COMMON_H 1
diff --git a/src/lib/dns/python/question_python.cc b/src/lib/dns/python/question_python.cc
index 378fc5d..a039284 100644
--- a/src/lib/dns/python/question_python.cc
+++ b/src/lib/dns/python/question_python.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: question_python.cc 1711 2010-04-14 15:14:53Z jelte $
-
#include <dns/question.h>
using namespace isc::dns;
diff --git a/src/lib/dns/python/rcode_python.cc b/src/lib/dns/python/rcode_python.cc
index 2d324f0..fce8eef 100644
--- a/src/lib/dns/python/rcode_python.cc
+++ b/src/lib/dns/python/rcode_python.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/rcode.h>
using namespace isc::dns;
@@ -254,87 +252,87 @@ Rcode_createStatic(const Rcode& rcode) {
}
PyObject*
-Rcode_NOERROR(const s_Rcode* self UNUSED_PARAM) {
+Rcode_NOERROR(const s_Rcode*) {
return (Rcode_createStatic(Rcode::NOERROR()));
}
PyObject*
-Rcode_FORMERR(const s_Rcode* self UNUSED_PARAM) {
+Rcode_FORMERR(const s_Rcode*) {
return (Rcode_createStatic(Rcode::FORMERR()));
}
PyObject*
-Rcode_SERVFAIL(const s_Rcode* self UNUSED_PARAM) {
+Rcode_SERVFAIL(const s_Rcode*) {
return (Rcode_createStatic(Rcode::SERVFAIL()));
}
PyObject*
-Rcode_NXDOMAIN(const s_Rcode* self UNUSED_PARAM) {
+Rcode_NXDOMAIN(const s_Rcode*) {
return (Rcode_createStatic(Rcode::NXDOMAIN()));
}
PyObject*
-Rcode_NOTIMP(const s_Rcode* self UNUSED_PARAM) {
+Rcode_NOTIMP(const s_Rcode*) {
return (Rcode_createStatic(Rcode::NOTIMP()));
}
PyObject*
-Rcode_REFUSED(const s_Rcode* self UNUSED_PARAM) {
+Rcode_REFUSED(const s_Rcode*) {
return (Rcode_createStatic(Rcode::REFUSED()));
}
PyObject*
-Rcode_YXDOMAIN(const s_Rcode* self UNUSED_PARAM) {
+Rcode_YXDOMAIN(const s_Rcode*) {
return (Rcode_createStatic(Rcode::YXDOMAIN()));
}
PyObject*
-Rcode_YXRRSET(const s_Rcode* self UNUSED_PARAM) {
+Rcode_YXRRSET(const s_Rcode*) {
return (Rcode_createStatic(Rcode::YXRRSET()));
}
PyObject*
-Rcode_NXRRSET(const s_Rcode* self UNUSED_PARAM) {
+Rcode_NXRRSET(const s_Rcode*) {
return (Rcode_createStatic(Rcode::NXRRSET()));
}
PyObject*
-Rcode_NOTAUTH(const s_Rcode* self UNUSED_PARAM) {
+Rcode_NOTAUTH(const s_Rcode*) {
return (Rcode_createStatic(Rcode::NOTAUTH()));
}
PyObject*
-Rcode_NOTZONE(const s_Rcode* self UNUSED_PARAM) {
+Rcode_NOTZONE(const s_Rcode*) {
return (Rcode_createStatic(Rcode::NOTZONE()));
}
PyObject*
-Rcode_RESERVED11(const s_Rcode* self UNUSED_PARAM) {
+Rcode_RESERVED11(const s_Rcode*) {
return (Rcode_createStatic(Rcode::RESERVED11()));
}
PyObject*
-Rcode_RESERVED12(const s_Rcode* self UNUSED_PARAM) {
+Rcode_RESERVED12(const s_Rcode*) {
return (Rcode_createStatic(Rcode::RESERVED12()));
}
PyObject*
-Rcode_RESERVED13(const s_Rcode* self UNUSED_PARAM) {
+Rcode_RESERVED13(const s_Rcode*) {
return (Rcode_createStatic(Rcode::RESERVED13()));
}
PyObject*
-Rcode_RESERVED14(const s_Rcode* self UNUSED_PARAM) {
+Rcode_RESERVED14(const s_Rcode*) {
return (Rcode_createStatic(Rcode::RESERVED14()));
}
PyObject*
-Rcode_RESERVED15(const s_Rcode* self UNUSED_PARAM) {
+Rcode_RESERVED15(const s_Rcode*) {
return (Rcode_createStatic(Rcode::RESERVED15()));
}
PyObject*
-Rcode_BADVERS(const s_Rcode* self UNUSED_PARAM) {
+Rcode_BADVERS(const s_Rcode*) {
return (Rcode_createStatic(Rcode::BADVERS()));
}
diff --git a/src/lib/dns/python/rdata_python.cc b/src/lib/dns/python/rdata_python.cc
index 3c9c45c..8579b7e 100644
--- a/src/lib/dns/python/rdata_python.cc
+++ b/src/lib/dns/python/rdata_python.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/rdata.h>
using namespace isc::dns;
using namespace isc::dns::rdata;
diff --git a/src/lib/dns/python/rrclass_python.cc b/src/lib/dns/python/rrclass_python.cc
index b0bce88..3cfed4c 100644
--- a/src/lib/dns/python/rrclass_python.cc
+++ b/src/lib/dns/python/rrclass_python.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/rrclass.h>
using namespace isc::dns;
@@ -306,23 +304,23 @@ static PyObject* RRClass_createStatic(RRClass stc) {
return (ret);
}
-static PyObject* RRClass_IN(s_RRClass *self UNUSED_PARAM) {
+static PyObject* RRClass_IN(s_RRClass*) {
return (RRClass_createStatic(RRClass::IN()));
}
-static PyObject* RRClass_CH(s_RRClass *self UNUSED_PARAM) {
+static PyObject* RRClass_CH(s_RRClass*) {
return (RRClass_createStatic(RRClass::CH()));
}
-static PyObject* RRClass_HS(s_RRClass *self UNUSED_PARAM) {
+static PyObject* RRClass_HS(s_RRClass*) {
return (RRClass_createStatic(RRClass::HS()));
}
-static PyObject* RRClass_NONE(s_RRClass *self UNUSED_PARAM) {
+static PyObject* RRClass_NONE(s_RRClass*) {
return (RRClass_createStatic(RRClass::NONE()));
}
-static PyObject* RRClass_ANY(s_RRClass *self UNUSED_PARAM) {
+static PyObject* RRClass_ANY(s_RRClass*) {
return (RRClass_createStatic(RRClass::ANY()));
}
// end of RRClass
diff --git a/src/lib/dns/python/rrset_python.cc b/src/lib/dns/python/rrset_python.cc
index 4a970f0..2292784 100644
--- a/src/lib/dns/python/rrset_python.cc
+++ b/src/lib/dns/python/rrset_python.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/rrset.h>
//
@@ -158,7 +156,7 @@ static PyTypeObject rrset_type = {
};
static int
-RRset_init(s_RRset* self, PyObject* args UNUSED_PARAM) {
+RRset_init(s_RRset* self, PyObject* args) {
s_Name* name;
s_RRClass* rrclass;
s_RRType* rrtype;
@@ -355,7 +353,7 @@ RRset_getRdata(s_RRset* self) {
RdataIteratorPtr it = self->rrset->getRdataIterator();
- for (it->first(); !it->isLast(); it->next()) {
+ for (; !it->isLast(); it->next()) {
s_Rdata *rds = static_cast<s_Rdata*>(rdata_type.tp_alloc(&rdata_type, 0));
if (rds != NULL) {
// hmz them iterators/shared_ptrs and private constructors
diff --git a/src/lib/dns/python/rrttl_python.cc b/src/lib/dns/python/rrttl_python.cc
index 6885ea5..e7391d0 100644
--- a/src/lib/dns/python/rrttl_python.cc
+++ b/src/lib/dns/python/rrttl_python.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <vector>
#include <dns/rrttl.h>
diff --git a/src/lib/dns/python/rrtype_python.cc b/src/lib/dns/python/rrtype_python.cc
index cac0043..012f251 100644
--- a/src/lib/dns/python/rrtype_python.cc
+++ b/src/lib/dns/python/rrtype_python.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <vector>
#include <dns/rrtype.h>
@@ -344,97 +342,97 @@ static PyObject* RRType_createStatic(RRType stc) {
}
static PyObject*
-RRType_NSEC3PARAM(s_RRType *self UNUSED_PARAM) {
+RRType_NSEC3PARAM(s_RRType*) {
return (RRType_createStatic(RRType::NSEC3PARAM()));
}
static PyObject*
-RRType_DNAME(s_RRType *self UNUSED_PARAM) {
+RRType_DNAME(s_RRType*) {
return (RRType_createStatic(RRType::DNAME()));
}
static PyObject*
-RRType_PTR(s_RRType *self UNUSED_PARAM) {
+RRType_PTR(s_RRType*) {
return (RRType_createStatic(RRType::PTR()));
}
static PyObject*
-RRType_MX(s_RRType *self UNUSED_PARAM) {
+RRType_MX(s_RRType*) {
return (RRType_createStatic(RRType::MX()));
}
static PyObject*
-RRType_DNSKEY(s_RRType *self UNUSED_PARAM) {
+RRType_DNSKEY(s_RRType*) {
return (RRType_createStatic(RRType::DNSKEY()));
}
static PyObject*
-RRType_TXT(s_RRType *self UNUSED_PARAM) {
+RRType_TXT(s_RRType*) {
return (RRType_createStatic(RRType::TXT()));
}
static PyObject*
-RRType_RRSIG(s_RRType *self UNUSED_PARAM) {
+RRType_RRSIG(s_RRType*) {
return (RRType_createStatic(RRType::RRSIG()));
}
static PyObject*
-RRType_NSEC(s_RRType *self UNUSED_PARAM) {
+RRType_NSEC(s_RRType*) {
return (RRType_createStatic(RRType::NSEC()));
}
static PyObject*
-RRType_AAAA(s_RRType *self UNUSED_PARAM) {
+RRType_AAAA(s_RRType*) {
return (RRType_createStatic(RRType::AAAA()));
}
static PyObject*
-RRType_DS(s_RRType *self UNUSED_PARAM) {
+RRType_DS(s_RRType*) {
return (RRType_createStatic(RRType::DS()));
}
static PyObject*
-RRType_OPT(s_RRType *self UNUSED_PARAM) {
+RRType_OPT(s_RRType*) {
return (RRType_createStatic(RRType::OPT()));
}
static PyObject*
-RRType_A(s_RRType *self UNUSED_PARAM) {
+RRType_A(s_RRType*) {
return (RRType_createStatic(RRType::A()));
}
static PyObject*
-RRType_NS(s_RRType *self UNUSED_PARAM) {
+RRType_NS(s_RRType*) {
return (RRType_createStatic(RRType::NS()));
}
static PyObject*
-RRType_CNAME(s_RRType *self UNUSED_PARAM) {
+RRType_CNAME(s_RRType*) {
return (RRType_createStatic(RRType::CNAME()));
}
static PyObject*
-RRType_SOA(s_RRType *self UNUSED_PARAM) {
+RRType_SOA(s_RRType*) {
return (RRType_createStatic(RRType::SOA()));
}
static PyObject*
-RRType_NSEC3(s_RRType *self UNUSED_PARAM) {
+RRType_NSEC3(s_RRType*) {
return (RRType_createStatic(RRType::NSEC3()));
}
static PyObject*
-RRType_IXFR(s_RRType *self UNUSED_PARAM) {
+RRType_IXFR(s_RRType*) {
return (RRType_createStatic(RRType::IXFR()));
}
static PyObject*
-RRType_AXFR(s_RRType *self UNUSED_PARAM) {
+RRType_AXFR(s_RRType*) {
return (RRType_createStatic(RRType::AXFR()));
}
static PyObject*
-RRType_ANY(s_RRType *self UNUSED_PARAM) {
+RRType_ANY(s_RRType*) {
return (RRType_createStatic(RRType::ANY()));
}
diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am
index 2fa16dd..7f3c213 100644
--- a/src/lib/dns/python/tests/Makefile.am
+++ b/src/lib/dns/python/tests/Makefile.am
@@ -1,3 +1,4 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = edns_python_test.py
PYTESTS += message_python_test.py
PYTESTS += messagerenderer_python_test.py
@@ -10,6 +11,7 @@ PYTESTS += rrclass_python_test.py
PYTESTS += rrset_python_test.py
PYTESTS += rrttl_python_test.py
PYTESTS += rrtype_python_test.py
+PYTESTS += tsigkey_python_test.py
EXTRA_DIST = $(PYTESTS)
EXTRA_DIST += testutil.py
@@ -21,14 +23,17 @@ 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:$$$(ENV_LIBRARY_PATH)
endif
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/dns/.libs:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
TESTDATA_PATH=$(abs_top_srcdir)/src/lib/dns/tests/testdata:$(abs_top_builddir)/src/lib/dns/tests/testdata \
$(LIBRARY_PATH_PLACEHOLDER) \
- $(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/lib/dns/python/tests/edns_python_test.py b/src/lib/dns/python/tests/edns_python_test.py
index a68342c..3f03c90 100644
--- a/src/lib/dns/python/tests/edns_python_test.py
+++ b/src/lib/dns/python/tests/edns_python_test.py
@@ -13,8 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
import unittest
import os
from pydnspp import *
diff --git a/src/lib/dns/python/tests/message_python_test.py b/src/lib/dns/python/tests/message_python_test.py
index 2b0f2de..6ef42d4 100644
--- a/src/lib/dns/python/tests/message_python_test.py
+++ b/src/lib/dns/python/tests/message_python_test.py
@@ -22,61 +22,6 @@ import os
from pydnspp import *
from testutil import *
-class MessageFlagTest(unittest.TestCase):
- def test_init(self):
- self.assertRaises(NotImplementedError, MessageFlag)
-
- def test_get_bit(self):
- self.assertEqual(0x8000, MessageFlag.QR().get_bit())
- self.assertEqual(0x0400, MessageFlag.AA().get_bit())
- self.assertEqual(0x0200, MessageFlag.TC().get_bit())
- self.assertEqual(0x0100, MessageFlag.RD().get_bit())
- self.assertEqual(0x0080, MessageFlag.RA().get_bit())
- self.assertEqual(0x0020, MessageFlag.AD().get_bit())
- self.assertEqual(0x0010, MessageFlag.CD().get_bit())
-
-class SectionTest(unittest.TestCase):
-
- def test_init(self):
- self.assertRaises(NotImplementedError, Section)
-
- def test_get_code(self):
- self.assertEqual(0, Section.QUESTION().get_code())
- self.assertEqual(1, Section.ANSWER().get_code())
- self.assertEqual(2, Section.AUTHORITY().get_code())
- self.assertEqual(3, Section.ADDITIONAL().get_code())
-
- def test_richcmp(self):
- s1 = Section.QUESTION()
- s2 = Section.ANSWER()
- s3 = Section.ANSWER()
- self.assertTrue(s2 == s3)
- self.assertTrue(s1 != s2)
- self.assertFalse(s1 == s2)
- self.assertFalse(s1 == 1)
- # can't use assertRaises here...
- try:
- s1 < s2
- self.fail("operation that should have raised an error unexpectedly succeeded")
- except Exception as err:
- self.assertEqual(TypeError, type(err))
- try:
- s1 <= s2
- self.fail("operation that should have raised an error unexpectedly succeeded")
- except Exception as err:
- self.assertEqual(TypeError, type(err))
- try:
- s1 > s2
- self.fail("operation that should have raised an error unexpectedly succeeded")
- except Exception as err:
- self.assertEqual(TypeError, type(err))
- try:
- s1 >= s2
- self.fail("operation that should have raised an error unexpectedly succeeded")
- except Exception as err:
- self.assertEqual(TypeError, type(err))
-
-
# helper functions for tests taken from c++ unittests
if "TESTDATA_PATH" in os.environ:
testdata_path = os.environ["TESTDATA_PATH"]
@@ -105,15 +50,16 @@ def create_message():
message_render.set_qid(0x1035)
message_render.set_opcode(Opcode.QUERY())
message_render.set_rcode(Rcode.NOERROR())
- message_render.set_header_flag(MessageFlag.QR())
- message_render.set_header_flag(MessageFlag.RD())
- message_render.set_header_flag(MessageFlag.AA())
- message_render.add_question(Question(Name("test.example.com"), RRClass("IN"), RRType("A")))
+ message_render.set_header_flag(Message.HEADERFLAG_QR)
+ message_render.set_header_flag(Message.HEADERFLAG_RD)
+ message_render.set_header_flag(Message.HEADERFLAG_AA)
+ message_render.add_question(Question(Name("test.example.com"),
+ RRClass("IN"), RRType("A")))
rrset = RRset(Name("test.example.com"), RRClass("IN"),
RRType("A"), RRTTL(3600))
rrset.add_rdata(Rdata(RRType("A"), RRClass("IN"), "192.0.2.1"))
rrset.add_rdata(Rdata(RRType("A"), RRClass("IN"), "192.0.2.2"))
- message_render.add_rrset(Section.ANSWER(), rrset)
+ message_render.add_rrset(Message.SECTION_ANSWER, rrset)
return message_render
@@ -122,29 +68,56 @@ class MessageTest(unittest.TestCase):
def setUp(self):
self.p = Message(Message.PARSE)
self.r = Message(Message.RENDER)
-
+
+ self.rrset_a = RRset(Name("example.com"), RRClass("IN"), RRType("A"),
+ RRTTL(3600))
+ self.rrset_a.add_rdata(Rdata(RRType("A"), RRClass("IN"), "192.0.2.1"))
+ self.rrset_a.add_rdata(Rdata(RRType("A"), RRClass("IN"), "192.0.2.2"))
+
+ self.rrset_aaaa = RRset(Name("example.com"), RRClass("IN"),
+ RRType("AAAA"), RRTTL(3600))
+ self.rrset_aaaa.add_rdata(Rdata(RRType("AAAA"), RRClass("IN"),
+ "2001:db8::134"))
+
+ self.bogus_section = Message.SECTION_ADDITIONAL + 1
+
def test_init(self):
self.assertRaises(TypeError, Message, 3)
self.assertRaises(TypeError, Message, "wrong")
- def test_get_header_flag(self):
+ def test_header_flag(self): # set and get methods
self.assertRaises(TypeError, self.p.get_header_flag, "wrong")
- self.assertFalse(self.p.get_header_flag(MessageFlag.AA()))
-
- def test_set_clear_header_flag(self):
self.assertRaises(TypeError, self.r.set_header_flag, "wrong")
- self.assertRaises(TypeError, self.r.clear_header_flag, "wrong")
- self.assertFalse(self.r.get_header_flag(MessageFlag.AA()))
- self.r.set_header_flag(MessageFlag.AA())
- self.assertTrue(self.r.get_header_flag(MessageFlag.AA()))
- self.r.clear_header_flag(MessageFlag.AA())
- self.assertFalse(self.r.get_header_flag(MessageFlag.AA()))
+ self.assertFalse(self.r.get_header_flag(Message.HEADERFLAG_QR))
+ self.assertFalse(self.r.get_header_flag(Message.HEADERFLAG_AA))
+ self.assertFalse(self.r.get_header_flag(Message.HEADERFLAG_TC))
+ self.assertFalse(self.r.get_header_flag(Message.HEADERFLAG_RD))
+ self.assertFalse(self.r.get_header_flag(Message.HEADERFLAG_RA))
+ self.assertFalse(self.r.get_header_flag(Message.HEADERFLAG_AD))
+ self.assertFalse(self.r.get_header_flag(Message.HEADERFLAG_CD))
+
+ self.r.set_header_flag(Message.HEADERFLAG_QR)
+ self.assertTrue(self.r.get_header_flag(Message.HEADERFLAG_QR))
+
+ self.r.set_header_flag(Message.HEADERFLAG_AA, True)
+ self.assertTrue(self.r.get_header_flag(Message.HEADERFLAG_AA))
+
+ self.r.set_header_flag(Message.HEADERFLAG_AA, False)
+ self.assertFalse(self.r.get_header_flag(Message.HEADERFLAG_AA))
+
+ 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, MessageFlag.AA())
- self.assertRaises(InvalidMessageOperation,
- self.p.clear_header_flag, MessageFlag.AA())
+ self.p.set_header_flag, Message.HEADERFLAG_AA)
def test_set_qid(self):
self.assertRaises(TypeError, self.r.set_qid, "wrong")
@@ -195,52 +168,77 @@ class MessageTest(unittest.TestCase):
self.r.set_edns(edns)
self.assertEqual(1024, self.r.get_edns().get_udp_size())
+ def test_get_rr_count(self):
+ # counts also tested in add_section
+ self.assertEqual(0, self.r.get_rr_count(Message.SECTION_QUESTION))
+ self.assertEqual(0, self.r.get_rr_count(Message.SECTION_ANSWER))
+ self.assertEqual(0, self.r.get_rr_count(Message.SECTION_AUTHORITY))
+ self.assertEqual(0, self.r.get_rr_count(Message.SECTION_ADDITIONAL))
+
+ self.r.add_question(Question(Name("example.com"), RRClass("IN"),
+ RRType("A")))
+ self.assertEqual(1, self.r.get_rr_count(Message.SECTION_QUESTION))
+
+ self.r.add_rrset(Message.SECTION_ANSWER, self.rrset_a)
+ self.assertEqual(2, self.r.get_rr_count(Message.SECTION_ANSWER))
+
+ factoryFromFile(self.p, "message_fromWire11.wire")
+ self.assertEqual(1, self.r.get_rr_count(Message.SECTION_QUESTION))
+ self.assertEqual(0, self.r.get_rr_count(Message.SECTION_ADDITIONAL))
+
+ self.assertRaises(OverflowError, self.r.get_rr_count,
+ self.bogus_section)
+ self.assertRaises(TypeError, self.r.get_rr_count, "wrong")
+
def test_get_section(self):
self.assertRaises(TypeError, self.r.get_section, "wrong")
- rrset = RRset(Name("example.com"), RRClass("IN"), RRType("A"), RRTTL(3600))
- rrset.add_rdata(Rdata(RRType("A"), RRClass("IN"), "192.0.2.1"))
- rrset.add_rdata(Rdata(RRType("A"), RRClass("IN"), "192.0.2.2"))
- section_rrset = [rrset]
+ section_rrset = [self.rrset_a]
self.assertRaises(InvalidMessageOperation, self.p.add_rrset,
- Section.ANSWER(), rrset)
+ Message.SECTION_ANSWER, self.rrset_a)
- self.assertFalse(compare_rrset_list(section_rrset, self.r.get_section(Section.ANSWER())))
- self.assertEqual(0, self.r.get_rr_count(Section.ANSWER()))
- self.r.add_rrset(Section.ANSWER(), rrset)
- self.assertTrue(compare_rrset_list(section_rrset, self.r.get_section(Section.ANSWER())))
- self.assertEqual(2, self.r.get_rr_count(Section.ANSWER()))
-
- self.assertFalse(compare_rrset_list(section_rrset, self.r.get_section(Section.AUTHORITY())))
- self.assertEqual(0, self.r.get_rr_count(Section.AUTHORITY()))
- self.r.add_rrset(Section.AUTHORITY(), rrset)
- self.assertTrue(compare_rrset_list(section_rrset, self.r.get_section(Section.AUTHORITY())))
- self.assertEqual(2, self.r.get_rr_count(Section.AUTHORITY()))
-
- self.assertFalse(compare_rrset_list(section_rrset, self.r.get_section(Section.ADDITIONAL())))
- self.assertEqual(0, self.r.get_rr_count(Section.ADDITIONAL()))
- self.r.add_rrset(Section.ADDITIONAL(), rrset)
- self.assertTrue(compare_rrset_list(section_rrset, self.r.get_section(Section.ADDITIONAL())))
- self.assertEqual(2, self.r.get_rr_count(Section.ADDITIONAL()))
-
- def test_get_rr_count(self):
- self.assertRaises(TypeError, self.r.get_rr_count, "wrong")
- # counts also tested in add_section
+ 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)
+ self.assertTrue(compare_rrset_list(section_rrset, self.r.get_section(Message.SECTION_ANSWER)))
+ self.assertEqual(2, self.r.get_rr_count(Message.SECTION_ANSWER))
+
+ self.assertFalse(compare_rrset_list(section_rrset, self.r.get_section(Message.SECTION_AUTHORITY)))
+ self.assertEqual(0, self.r.get_rr_count(Message.SECTION_AUTHORITY))
+ self.r.add_rrset(Message.SECTION_AUTHORITY, self.rrset_a)
+ self.assertTrue(compare_rrset_list(section_rrset, self.r.get_section(Message.SECTION_AUTHORITY)))
+ self.assertEqual(2, self.r.get_rr_count(Message.SECTION_AUTHORITY))
+
+ self.assertFalse(compare_rrset_list(section_rrset, self.r.get_section(Message.SECTION_ADDITIONAL)))
+ self.assertEqual(0, self.r.get_rr_count(Message.SECTION_ADDITIONAL))
+ self.r.add_rrset(Message.SECTION_ADDITIONAL, self.rrset_a)
+ self.assertTrue(compare_rrset_list(section_rrset, self.r.get_section(Message.SECTION_ADDITIONAL)))
+ self.assertEqual(2, self.r.get_rr_count(Message.SECTION_ADDITIONAL))
def test_add_question(self):
self.assertRaises(TypeError, self.r.add_question, "wrong", "wrong")
q = Question(Name("example.com"), RRClass("IN"), RRType("A"))
qs = [q]
self.assertFalse(compare_rrset_list(qs, self.r.get_question()))
- self.assertEqual(0, self.r.get_rr_count(Section.QUESTION()))
+ self.assertEqual(0, self.r.get_rr_count(Message.SECTION_QUESTION))
self.r.add_question(q)
self.assertTrue(compare_rrset_list(qs, self.r.get_question()))
- self.assertEqual(1, self.r.get_rr_count(Section.QUESTION()))
+ self.assertEqual(1, self.r.get_rr_count(Message.SECTION_QUESTION))
def test_add_rrset(self):
self.assertRaises(TypeError, self.r.add_rrset, "wrong")
- # actual addition already tested in get_section
+ self.assertRaises(TypeError, self.r.add_rrset)
+
+ # we can currently only test the no-sign case.
+ self.r.add_rrset(Message.SECTION_ANSWER, self.rrset_a)
+ self.assertEqual(2, self.r.get_rr_count(Message.SECTION_ANSWER))
+
+ def test_bad_add_rrset(self):
+ self.assertRaises(InvalidMessageOperation, self.p.add_rrset,
+ Message.SECTION_ANSWER, self.rrset_a)
+ self.assertRaises(OverflowError, self.r.add_rrset,
+ self.bogus_section, self.rrset_a)
def test_clear(self):
self.assertEqual(None, self.r.clear(Message.PARSE))
@@ -308,22 +306,22 @@ test.example.com. 3600 IN A 192.0.2.2
self.assertEqual(0x1035, message_parse.get_qid())
self.assertEqual(Opcode.QUERY(), message_parse.get_opcode())
self.assertEqual(Rcode.NOERROR(), message_parse.get_rcode())
- self.assertTrue(message_parse.get_header_flag(MessageFlag.QR()))
- self.assertTrue(message_parse.get_header_flag(MessageFlag.RD()))
- self.assertTrue(message_parse.get_header_flag(MessageFlag.AA()))
+ self.assertTrue(message_parse.get_header_flag(Message.HEADERFLAG_QR))
+ self.assertTrue(message_parse.get_header_flag(Message.HEADERFLAG_RD))
+ self.assertTrue(message_parse.get_header_flag(Message.HEADERFLAG_AA))
#QuestionPtr q = *message_parse.beginQuestion()
q = message_parse.get_question()[0]
self.assertEqual(test_name, q.get_name())
self.assertEqual(RRType("A"), q.get_type())
self.assertEqual(RRClass("IN"), q.get_class())
- self.assertEqual(1, message_parse.get_rr_count(Section.QUESTION()))
- self.assertEqual(2, message_parse.get_rr_count(Section.ANSWER()))
- self.assertEqual(0, message_parse.get_rr_count(Section.AUTHORITY()))
- self.assertEqual(0, message_parse.get_rr_count(Section.ADDITIONAL()))
+ self.assertEqual(1, message_parse.get_rr_count(Message.SECTION_QUESTION))
+ self.assertEqual(2, message_parse.get_rr_count(Message.SECTION_ANSWER))
+ self.assertEqual(0, message_parse.get_rr_count(Message.SECTION_AUTHORITY))
+ self.assertEqual(0, message_parse.get_rr_count(Message.SECTION_ADDITIONAL))
- #RRsetPtr rrset = *message_parse.beginSection(Section.ANSWER())
- rrset = message_parse.get_section(Section.ANSWER())[0]
+ #RRsetPtr rrset = *message_parse.beginSection(Message.SECTION_ANSWER)
+ rrset = message_parse.get_section(Message.SECTION_ANSWER)[0]
self.assertEqual(test_name, rrset.get_name())
self.assertEqual(RRType("A"), rrset.get_type())
self.assertEqual(RRClass("IN"), rrset.get_class())
diff --git a/src/lib/dns/python/tests/messagerenderer_python_test.py b/src/lib/dns/python/tests/messagerenderer_python_test.py
index d3648df..544ad23 100644
--- a/src/lib/dns/python/tests/messagerenderer_python_test.py
+++ b/src/lib/dns/python/tests/messagerenderer_python_test.py
@@ -28,7 +28,7 @@ class MessageRendererTest(unittest.TestCase):
c = RRClass("IN")
t = RRType("A")
ttl = RRTTL("3600")
-
+
message = Message(Message.RENDER)
message.set_qid(123)
message.set_opcode(Opcode.QUERY())
@@ -38,15 +38,15 @@ class MessageRendererTest(unittest.TestCase):
self.message1 = message
message = Message(Message.RENDER)
message.set_qid(123)
- message.set_header_flag(MessageFlag.AA())
- message.set_header_flag(MessageFlag.QR())
+ message.set_header_flag(Message.HEADERFLAG_AA, True)
+ message.set_header_flag(Message.HEADERFLAG_QR, True)
message.set_opcode(Opcode.QUERY())
message.set_rcode(Rcode.NOERROR())
message.add_question(Question(name, c, t))
rrset = RRset(name, c, t, ttl)
rrset.add_rdata(Rdata(t, c, "192.0.2.98"))
rrset.add_rdata(Rdata(t, c, "192.0.2.99"))
- message.add_rrset(Section.AUTHORITY(), rrset)
+ message.add_rrset(Message.SECTION_AUTHORITY, rrset)
self.message2 = message
self.renderer1 = MessageRenderer()
@@ -56,14 +56,14 @@ class MessageRendererTest(unittest.TestCase):
self.message1.to_wire(self.renderer1)
self.message2.to_wire(self.renderer2)
self.message2.to_wire(self.renderer3)
-
-
+
+
def test_messagerenderer_get_data(self):
data1 = b'\x00{\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\x01\x00\x01'
self.assertEqual(data1, self.renderer1.get_data())
data2 = b'\x00{\x84\x00\x00\x01\x00\x00\x00\x02\x00\x00\x07example\x03com\x00\x00\x01\x00\x01\xc0\x0c\x00\x01\x00\x01\x00\x00\x0e\x10\x00\x04\xc0\x00\x02b\xc0\x0c\x00\x01\x00\x01\x00\x00\x0e\x10\x00\x04\xc0\x00\x02c'
self.assertEqual(data2, self.renderer2.get_data())
-
+
def test_messagerenderer_get_length(self):
self.assertEqual(29, self.renderer1.get_length())
self.assertEqual(61, self.renderer2.get_length())
@@ -79,6 +79,14 @@ class MessageRendererTest(unittest.TestCase):
self.assertEqual(512, self.renderer2.get_length_limit())
self.assertEqual(50, self.renderer3.get_length_limit())
+ def test_messagerenderer_get_compress_mode(self):
+ self.assertEqual(MessageRenderer.CASE_INSENSITIVE,
+ self.renderer1.get_compress_mode())
+ self.assertEqual(MessageRenderer.CASE_INSENSITIVE,
+ self.renderer2.get_compress_mode())
+ self.assertEqual(MessageRenderer.CASE_INSENSITIVE,
+ self.renderer3.get_compress_mode())
+
def test_messagerenderer_set_truncated(self):
self.assertFalse(self.renderer1.is_truncated())
self.renderer1.set_truncated()
@@ -91,5 +99,17 @@ class MessageRendererTest(unittest.TestCase):
self.assertEqual(1024, renderer.get_length_limit())
self.assertRaises(TypeError, renderer.set_length_limit, "wrong")
+ def test_messagerenderer_set_compress_mode(self):
+ renderer = MessageRenderer()
+ self.assertEqual(MessageRenderer.CASE_INSENSITIVE,
+ renderer.get_compress_mode())
+ renderer.set_compress_mode(MessageRenderer.CASE_SENSITIVE)
+ self.assertEqual(MessageRenderer.CASE_SENSITIVE,
+ renderer.get_compress_mode())
+ renderer.set_compress_mode(MessageRenderer.CASE_INSENSITIVE)
+ self.assertEqual(MessageRenderer.CASE_INSENSITIVE,
+ renderer.get_compress_mode())
+ self.assertRaises(TypeError, renderer.set_compress_mode, "wrong")
+
if __name__ == '__main__':
unittest.main()
diff --git a/src/lib/dns/python/tests/testutil.py b/src/lib/dns/python/tests/testutil.py
index 38e9762..679f827 100644
--- a/src/lib/dns/python/tests/testutil.py
+++ b/src/lib/dns/python/tests/testutil.py
@@ -13,8 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
#
# helper functions for tests taken from C++ unittests
#
diff --git a/src/lib/dns/python/tests/tsigkey_python_test.py b/src/lib/dns/python/tests/tsigkey_python_test.py
new file mode 100644
index 0000000..06e6868
--- /dev/null
+++ b/src/lib/dns/python/tests/tsigkey_python_test.py
@@ -0,0 +1,172 @@
+# Copyright (C) 2010 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import unittest
+from pydnspp import *
+
+class TSIGKeyTest(unittest.TestCase):
+ key_name = Name('example.com')
+ secret = b'anotherRandomData'
+
+ def test_algorithm_names(self):
+ self.assertEqual(Name('hmac-md5.sig-alg.reg.int'),
+ TSIGKey.HMACMD5_NAME)
+ self.assertEqual(Name('hmac-sha1'), TSIGKey.HMACSHA1_NAME)
+ self.assertEqual(Name('hmac-sha256'), TSIGKey.HMACSHA256_NAME)
+
+ def test_init(self):
+ key = TSIGKey(self.key_name, TSIGKey.HMACMD5_NAME, self.secret)
+ self.assertEqual(self.key_name, key.get_key_name())
+ self.assertEqual(Name('hmac-md5.sig-alg.reg.int'),
+ key.get_algorithm_name())
+ self.assertEqual(self.secret, key.get_secret())
+
+ self.assertRaises(InvalidParameter, TSIGKey, self.key_name,
+ Name('unknown-alg'), self.secret)
+
+ self.assertEqual('hmac-sha1.',
+ TSIGKey(self.key_name, TSIGKey.HMACSHA1_NAME,
+ self.secret).get_algorithm_name().to_text())
+
+ self.assertRaises(TypeError, TSIGKey, self.key_name,
+ TSIGKey.HMACMD5_NAME,
+ 'should be binary') # signature mismatch
+
+class TSIGKeyRingTest(unittest.TestCase):
+ key_name = Name('example.com')
+ secret = b'someRandomData'
+
+ def setUp(self):
+ self.keyring = TSIGKeyRing()
+
+ def test_init(self):
+ self.assertEqual(0, self.keyring.size())
+ self.assertRaises(TypeError, TSIGKeyRing, 1)
+ self.assertRaises(TypeError, TSIGKeyRing, 'there should not be arg')
+
+ def test_add(self):
+ self.assertEqual(TSIGKeyRing.SUCCESS,
+ self.keyring.add(TSIGKey(self.key_name,
+ TSIGKey.HMACSHA256_NAME,
+ self.secret)))
+ self.assertEqual(1, self.keyring.size())
+ self.assertEqual(TSIGKeyRing.EXIST,
+ self.keyring.add(TSIGKey(self.key_name,
+ TSIGKey.HMACSHA256_NAME,
+ self.secret)))
+ self.assertEqual(TSIGKeyRing.EXIST,
+ self.keyring.add(TSIGKey(self.key_name,
+ TSIGKey.HMACSHA1_NAME,
+ self.secret)))
+ self.assertEqual(TSIGKeyRing.EXIST,
+ self.keyring.add(TSIGKey(Name('EXAMPLE.COM'),
+ TSIGKey.HMACSHA1_NAME,
+ self.secret)))
+ self.assertEqual(1, self.keyring.size())
+
+ def test_add_more(self):
+ self.assertEqual(TSIGKeyRing.SUCCESS,
+ self.keyring.add(TSIGKey(self.key_name,
+ TSIGKey.HMACSHA256_NAME,
+ self.secret)))
+ self.assertEqual(TSIGKeyRing.SUCCESS,
+ self.keyring.add(TSIGKey(Name('another.example'),
+ TSIGKey.HMACMD5_NAME,
+ self.secret)))
+ self.assertEqual(TSIGKeyRing.SUCCESS,
+ self.keyring.add(TSIGKey(Name('more.example'),
+ TSIGKey.HMACSHA1_NAME,
+ self.secret)))
+ self.assertEqual(3, self.keyring.size())
+
+ self.assertRaises(TypeError, self.keyring.add, 1)
+ self.assertRaises(TypeError, self.keyring.add, 'invalid arg')
+
+ def test_remove(self):
+ self.assertEqual(TSIGKeyRing.SUCCESS,
+ self.keyring.add(TSIGKey(self.key_name,
+ TSIGKey.HMACSHA256_NAME,
+ self.secret)))
+ self.assertEqual(TSIGKeyRing.SUCCESS,
+ self.keyring.remove(self.key_name))
+ self.assertEqual(TSIGKeyRing.NOTFOUND,
+ self.keyring.remove(self.key_name))
+
+ self.assertRaises(TypeError, self.keyring.add, 1)
+ self.assertRaises(TypeError, self.keyring.add, 'invalid arg')
+ self.assertRaises(TypeError, self.keyring.add, self.key_name, 0)
+
+ def test_remove_from_some(self):
+ self.assertEqual(TSIGKeyRing.SUCCESS,
+ self.keyring.add(TSIGKey(self.key_name,
+ TSIGKey.HMACSHA256_NAME,
+ self.secret)))
+ self.assertEqual(TSIGKeyRing.SUCCESS,
+ self.keyring.add(TSIGKey(Name('another.example'),
+ TSIGKey.HMACMD5_NAME,
+ self.secret)))
+ self.assertEqual(TSIGKeyRing.SUCCESS,
+ self.keyring.add(TSIGKey(Name('more.example'),
+ TSIGKey.HMACSHA1_NAME,
+ self.secret)))
+
+ self.assertEqual(TSIGKeyRing.SUCCESS,
+ self.keyring.remove(Name('another.example')))
+ self.assertEqual(TSIGKeyRing.NOTFOUND,
+ self.keyring.remove(Name('noexist.example')))
+ self.assertEqual(2, self.keyring.size())
+
+ def test_find(self):
+ self.assertEqual((TSIGKeyRing.NOTFOUND, None),
+ self.keyring.find(self.key_name))
+
+ self.assertEqual(TSIGKeyRing.SUCCESS,
+ self.keyring.add(TSIGKey(self.key_name,
+ TSIGKey.HMACSHA256_NAME,
+ self.secret)))
+ (code, key) = self.keyring.find(self.key_name)
+ self.assertEqual(TSIGKeyRing.SUCCESS, code)
+ self.assertEqual(self.key_name, key.get_key_name())
+ self.assertEqual(TSIGKey.HMACSHA256_NAME, key.get_algorithm_name())
+ self.assertEqual(self.secret, key.get_secret())
+
+ self.assertRaises(TypeError, self.keyring.find, 1)
+ self.assertRaises(TypeError, self.keyring.find, 'should be a name')
+ self.assertRaises(TypeError, self.keyring.find, self.key_name, 0)
+
+ def test_find_from_some(self):
+ self.assertEqual(TSIGKeyRing.SUCCESS,
+ self.keyring.add(TSIGKey(self.key_name,
+ TSIGKey.HMACSHA256_NAME,
+ self.secret)))
+ self.assertEqual(TSIGKeyRing.SUCCESS,
+ self.keyring.add(TSIGKey(Name('another.example'),
+ TSIGKey.HMACMD5_NAME,
+ self.secret)))
+ self.assertEqual(TSIGKeyRing.SUCCESS,
+ self.keyring.add(TSIGKey(Name('more.example'),
+ TSIGKey.HMACSHA1_NAME,
+ self.secret)))
+
+ (code, key) = self.keyring.find(Name('another.example'))
+ self.assertEqual(TSIGKeyRing.SUCCESS, code)
+ self.assertEqual(Name('another.example'), key.get_key_name())
+ self.assertEqual(TSIGKey.HMACMD5_NAME, key.get_algorithm_name())
+
+ self.assertEqual((TSIGKeyRing.NOTFOUND, None),
+ self.keyring.find(Name('noexist.example')))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/lib/dns/python/tsigkey_python.cc b/src/lib/dns/python/tsigkey_python.cc
new file mode 100644
index 0000000..aa87909
--- /dev/null
+++ b/src/lib/dns/python/tsigkey_python.cc
@@ -0,0 +1,453 @@
+// 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 <new>
+
+#include <dns/tsigkey.h>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+//
+// Definition of the classes
+//
+
+// For each class, we need a struct, a helper functions (init, destroy,
+// and static wrappers around the methods we export), a list of methods,
+// and a type description
+
+namespace {
+//
+// TSIGKey
+//
+
+// The s_* Class simply covers one instantiation of the object
+
+class s_TSIGKey : public PyObject {
+public:
+ s_TSIGKey() : tsigkey(NULL) {}
+ TSIGKey* tsigkey;
+};
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+
+// General creation and destruction
+int TSIGKey_init(s_TSIGKey* self, PyObject* args);
+void TSIGKey_destroy(s_TSIGKey* self);
+
+// These are the functions we export
+// This is a second version of toText, we need one where the argument
+// is a PyObject*, for the str() function in python.
+PyObject* TSIGKey_getKeyName(const s_TSIGKey* self);
+PyObject* TSIGKey_getAlgorithmName(const s_TSIGKey* self);
+PyObject* TSIGKey_getSecret(const s_TSIGKey* self);
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef TSIGKey_methods[] = {
+ { "get_key_name",
+ reinterpret_cast<PyCFunction>(TSIGKey_getKeyName), METH_NOARGS,
+ "Return the key name." },
+ { "get_algorithm_name",
+ reinterpret_cast<PyCFunction>(TSIGKey_getAlgorithmName), METH_NOARGS,
+ "Return the algorithm name." },
+ { "get_secret",
+ reinterpret_cast<PyCFunction>(TSIGKey_getSecret), METH_NOARGS,
+ "Return the value of the TSIG secret." },
+ { NULL, NULL, 0, NULL }
+};
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_EDNS
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject tsigkey_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "libdns_python.TSIGKey",
+ sizeof(s_TSIGKey), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)TSIGKey_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The TSIGKey class holds a TSIG key along with some related attributes as "
+ "defined in RFC2845.",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ TSIGKey_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)TSIGKey_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+// A helper function to build a python "Name" object with error handling
+// encapsulated.
+s_Name*
+createNameObject(const Name& source) {
+ s_Name* name = PyObject_New(s_Name, &name_type);
+ if (name == NULL) {
+ return (NULL);
+ }
+ name->name = new(nothrow) Name(source);
+ if (name->name == NULL) {
+ Py_DECREF(name);
+ PyErr_SetString(po_IscException, "Allocating Name object failed");
+ return (NULL);
+ }
+ return (name);
+}
+
+int
+TSIGKey_init(s_TSIGKey* self, PyObject* args) {
+ const s_Name* key_name;
+ const s_Name* algorithm_name;
+ PyObject* bytes_obj;
+ const char* secret;
+ Py_ssize_t secret_len;
+
+ if (PyArg_ParseTuple(args, "O!O!O", &name_type, &key_name,
+ &name_type, &algorithm_name, &bytes_obj) &&
+ PyObject_AsCharBuffer(bytes_obj, &secret, &secret_len) != -1) {
+ try {
+ self->tsigkey = new TSIGKey(*key_name->name,
+ *algorithm_name->name,
+ secret, secret_len);
+ } catch (const isc::InvalidParameter& ex) {
+ PyErr_SetString(po_InvalidParameter, ex.what());
+ return (-1);
+ } catch (...) {
+ PyErr_SetString(po_IscException, "Unexpected exception");
+ return (-1);
+ }
+ return (0);
+ }
+
+ PyErr_Clear();
+ PyErr_SetString(PyExc_TypeError,
+ "Invalid arguments to TSIGKey constructor");
+
+ return (-1);
+}
+
+void
+TSIGKey_destroy(s_TSIGKey* const self) {
+ delete self->tsigkey;
+ self->tsigkey = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+TSIGKey_getKeyName(const s_TSIGKey* const self) {
+ return (createNameObject(self->tsigkey->getKeyName()));
+}
+
+PyObject*
+TSIGKey_getAlgorithmName(const s_TSIGKey* const self) {
+ return (createNameObject(self->tsigkey->getAlgorithmName()));
+}
+
+PyObject*
+TSIGKey_getSecret(const s_TSIGKey* const self) {
+ return (Py_BuildValue("y#", self->tsigkey->getSecret(),
+ self->tsigkey->getSecretLength()));
+}
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_TSIGKey(PyObject* mod) {
+ // We initialize the static description object with PyType_Ready(),
+ // then add it to the module. This is not just a check! (leaving
+ // this out results in segmentation faults)
+ if (PyType_Ready(&tsigkey_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&tsigkey_type);
+ void* p = &tsigkey_type;
+ if (PyModule_AddObject(mod, "TSIGKey", static_cast<PyObject*>(p)) != 0) {
+ Py_DECREF(&tsigkey_type);
+ return (false);
+ }
+
+ s_Name* name;
+ if ((name = createNameObject(TSIGKey::HMACMD5_NAME())) == NULL) {
+ goto cleanup;
+ }
+ addClassVariable(tsigkey_type, "HMACMD5_NAME", name);
+ if ((name = createNameObject(TSIGKey::HMACSHA1_NAME())) == NULL) {
+ goto cleanup;
+ }
+ addClassVariable(tsigkey_type, "HMACSHA1_NAME", name);
+ if ((name = createNameObject(TSIGKey::HMACSHA256_NAME())) == NULL) {
+ goto cleanup;
+ }
+ addClassVariable(tsigkey_type, "HMACSHA256_NAME", name);
+
+ return (true);
+
+ cleanup:
+ Py_DECREF(&tsigkey_type);
+ return (false);
+}
+//
+// End of TSIGKey
+//
+
+//
+// TSIGKeyRing
+//
+
+// The s_* Class simply covers one instantiation of the object
+
+// The s_* Class simply covers one instantiation of the object
+
+class s_TSIGKeyRing : public PyObject {
+public:
+ s_TSIGKeyRing() : keyring(NULL) {}
+ TSIGKeyRing* keyring;
+};
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+
+int TSIGKeyRing_init(s_TSIGKeyRing* self, PyObject* args);
+void TSIGKeyRing_destroy(s_TSIGKeyRing* self);
+
+PyObject* TSIGKeyRing_size(const s_TSIGKeyRing* self);
+PyObject* TSIGKeyRing_add(const s_TSIGKeyRing* self, PyObject* args);
+PyObject* TSIGKeyRing_remove(const s_TSIGKeyRing* self, PyObject* args);
+PyObject* TSIGKeyRing_find(const s_TSIGKeyRing* self, PyObject* args);
+
+PyMethodDef TSIGKeyRing_methods[] = {
+ { "size", reinterpret_cast<PyCFunction>(TSIGKeyRing_size), METH_NOARGS,
+ "Return the number of keys stored in the TSIGKeyRing." },
+ { "add", reinterpret_cast<PyCFunction>(TSIGKeyRing_add), METH_VARARGS,
+ "Add a TSIGKey to the TSIGKeyRing." },
+ { "remove", reinterpret_cast<PyCFunction>(TSIGKeyRing_remove),
+ METH_VARARGS,
+ "Remove a TSIGKey for the given name from the TSIGKeyRing." },
+ { "find", reinterpret_cast<PyCFunction>(TSIGKeyRing_find), METH_VARARGS,
+ "Find a TSIGKey for the given name in the TSIGKeyRing. "
+ "It returns a tuple of (result_code, key)." },
+ { NULL, NULL, 0, NULL }
+};
+
+PyTypeObject tsigkeyring_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "libdns_python.TSIGKeyRing",
+ sizeof(s_TSIGKeyRing), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)TSIGKeyRing_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "A simple repository of a set of TSIGKey objects.",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ TSIGKeyRing_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)TSIGKeyRing_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+int
+TSIGKeyRing_init(s_TSIGKeyRing* self, PyObject* args) {
+ if (!PyArg_ParseTuple(args, "")) {
+ PyErr_Clear();
+ PyErr_SetString(PyExc_TypeError,
+ "Invalid arguments to TSIGKeyRing constructor");
+ return (-1);
+ }
+
+ self->keyring = new(nothrow) TSIGKeyRing();
+ if (self->keyring == NULL) {
+ PyErr_SetString(po_IscException, "Allocating TSIGKeyRing failed");
+ return (-1);
+ }
+
+ return (0);
+}
+
+void
+TSIGKeyRing_destroy(s_TSIGKeyRing* self) {
+ delete self->keyring;
+ self->keyring = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+TSIGKeyRing_size(const s_TSIGKeyRing* const self) {
+ return (Py_BuildValue("I", self->keyring->size()));
+}
+
+PyObject*
+TSIGKeyRing_add(const s_TSIGKeyRing* const self, PyObject* args) {
+ s_TSIGKey* tsigkey;
+
+ if (PyArg_ParseTuple(args, "O!", &tsigkey_type, &tsigkey)) {
+ try {
+ const TSIGKeyRing::Result result =
+ self->keyring->add(*tsigkey->tsigkey);
+ return (Py_BuildValue("I", result));
+ } catch (...) {
+ PyErr_SetString(po_IscException, "Unexpected exception");
+ return (NULL);
+ }
+ }
+
+ PyErr_Clear();
+ PyErr_SetString(PyExc_TypeError, "Invalid arguments to TSIGKeyRing.add");
+
+ return (NULL);
+}
+
+PyObject*
+TSIGKeyRing_remove(const s_TSIGKeyRing* self, PyObject* args) {
+ s_Name* key_name;
+
+ if (PyArg_ParseTuple(args, "O!", &name_type, &key_name)) {
+ const TSIGKeyRing::Result result =
+ self->keyring->remove(*key_name->name);
+ return (Py_BuildValue("I", result));
+ }
+
+ PyErr_Clear();
+ PyErr_SetString(PyExc_TypeError, "Invalid arguments to TSIGKeyRing.add");
+
+ return (NULL);
+}
+
+PyObject*
+TSIGKeyRing_find(const s_TSIGKeyRing* self, PyObject* args) {
+ s_Name* key_name;
+
+ if (PyArg_ParseTuple(args, "O!", &name_type, &key_name)) {
+ const TSIGKeyRing::FindResult result =
+ self->keyring->find(*key_name->name);
+ if (result.key != NULL) {
+ s_TSIGKey* key = PyObject_New(s_TSIGKey, &tsigkey_type);
+ if (key == NULL) {
+ return (NULL);
+ }
+ key->tsigkey = new(nothrow) TSIGKey(*result.key);
+ if (key->tsigkey == NULL) {
+ Py_DECREF(key);
+ PyErr_SetString(po_IscException,
+ "Allocating TSIGKey object failed");
+ return (NULL);
+ }
+ return (Py_BuildValue("IN", result.code, key));
+ } else {
+ return (Py_BuildValue("Is", result.code, NULL));
+ }
+ }
+
+ return (NULL);
+}
+
+bool
+initModulePart_TSIGKeyRing(PyObject* mod) {
+ if (PyType_Ready(&tsigkeyring_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&tsigkeyring_type);
+ void* p = &tsigkeyring_type;
+ if (PyModule_AddObject(mod, "TSIGKeyRing",
+ static_cast<PyObject*>(p)) != 0) {
+ Py_DECREF(&tsigkeyring_type);
+ return (false);
+ }
+
+ addClassVariable(tsigkeyring_type, "SUCCESS",
+ Py_BuildValue("I", TSIGKeyRing::SUCCESS));
+ addClassVariable(tsigkeyring_type, "EXIST",
+ Py_BuildValue("I", TSIGKeyRing::EXIST));
+ addClassVariable(tsigkeyring_type, "NOTFOUND",
+ Py_BuildValue("I", TSIGKeyRing::NOTFOUND));
+
+ return (true);
+}
+
+} // end of unnamed namespace
diff --git a/src/lib/dns/question.cc b/src/lib/dns/question.cc
index 54a4262..3e14c78 100644
--- a/src/lib/dns/question.cc
+++ b/src/lib/dns/question.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <iostream>
#include <string>
diff --git a/src/lib/dns/question.h b/src/lib/dns/question.h
index 3d72173..520207b 100644
--- a/src/lib/dns/question.h
+++ b/src/lib/dns/question.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __QUESTION_H
#define __QUESTION_H 1
@@ -56,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
@@ -76,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.
@@ -230,10 +228,10 @@ public:
//@}
///
- /// \name Comparison Operator
+ /// \name Comparison Operators
///
//@{
- /// A comparison operator is needed for this class so it can
+ /// A "less than" operator is needed for this class so it can
/// function as an index to std::map.
bool operator <(const Question& rhs) const {
return (rrclass_ < rhs.rrclass_ ||
@@ -241,6 +239,26 @@ public:
(rrtype_ < rhs.rrtype_ ||
(rrtype_ == rhs.rrtype_ && (name_ < rhs.name_)))));
}
+
+ /// Equality operator. Primarily used to compare the question section in
+ /// a response to that in the query.
+ ///
+ /// \param rhs Question to compare against
+ /// \return true if name, class and type are equal, false otherwise
+ bool operator==(const Question& rhs) const {
+ return ((rrclass_ == rhs.rrclass_) && (rrtype_ == rhs.rrtype_) &&
+ (name_ == rhs.name_));
+ }
+
+ /// Inequality operator. Primarily used to compare the question section in
+ /// a response to that in the query.
+ ///
+ /// \param rhs Question to compare against
+ /// \return true if one or more of the name, class and type do not match,
+ /// false otherwise.
+ bool operator!=(const Question& rhs) const {
+ return (!operator==(rhs));
+ }
//@}
private:
diff --git a/src/lib/dns/rcode.cc b/src/lib/dns/rcode.cc
index c02ab6a..ee41b1e 100644
--- a/src/lib/dns/rcode.cc
+++ b/src/lib/dns/rcode.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <string>
#include <sstream>
#include <ostream>
diff --git a/src/lib/dns/rcode.h b/src/lib/dns/rcode.h
index 8b88b1b..0c63285 100644
--- a/src/lib/dns/rcode.h
+++ b/src/lib/dns/rcode.h
@@ -14,8 +14,6 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
-/* $Id$ */
-
#include <stdint.h>
#include <ostream>
diff --git a/src/lib/dns/rdata.cc b/src/lib/dns/rdata.cc
index be9d735..12beacc 100644
--- a/src/lib/dns/rdata.cc
+++ b/src/lib/dns/rdata.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <algorithm>
#include <cctype>
#include <string>
diff --git a/src/lib/dns/rdata.h b/src/lib/dns/rdata.h
index 836e926..9ce9735 100644
--- a/src/lib/dns/rdata.h
+++ b/src/lib/dns/rdata.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __RDATA_H
#define __RDATA_H 1
diff --git a/src/lib/dns/rdata/any_255/tsig_250.cc b/src/lib/dns/rdata/any_255/tsig_250.cc
new file mode 100644
index 0000000..f7c8d83
--- /dev/null
+++ b/src/lib/dns/rdata/any_255/tsig_250.cc
@@ -0,0 +1,499 @@
+// 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 <sstream>
+#include <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <dns/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/util/base64.h>
+
+using namespace std;
+using namespace boost;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// This is a straightforward representation of TSIG RDATA fields.
+struct TSIG::TSIGImpl {
+ TSIGImpl(const Name& algorithm, uint64_t time_signed, uint16_t fudge,
+ vector<uint8_t>& mac, uint16_t original_id, uint16_t error,
+ vector<uint8_t>& other_data) :
+ algorithm_(algorithm), time_signed_(time_signed), fudge_(fudge),
+ mac_(mac), original_id_(original_id), error_(error),
+ other_data_(other_data)
+ {}
+ TSIGImpl(const Name& algorithm, uint64_t time_signed, uint16_t fudge,
+ size_t macsize, const void* mac, uint16_t original_id,
+ uint16_t error, size_t other_len, const void* other_data) :
+ algorithm_(algorithm), time_signed_(time_signed), fudge_(fudge),
+ mac_(static_cast<const uint8_t*>(mac),
+ static_cast<const uint8_t*>(mac) + macsize),
+ original_id_(original_id), error_(error),
+ other_data_(static_cast<const uint8_t*>(other_data),
+ static_cast<const uint8_t*>(other_data) + other_len)
+ {}
+ template <typename Output>
+ void toWireCommon(Output& output) const;
+
+ const Name algorithm_;
+ const uint64_t time_signed_;
+ const uint16_t fudge_;
+ const vector<uint8_t> mac_;
+ const uint16_t original_id_;
+ const uint16_t error_;
+ const vector<uint8_t> other_data_;
+};
+
+namespace {
+string
+getToken(istringstream& iss, const string& full_input) {
+ string token;
+ iss >> token;
+ if (iss.bad() || iss.fail()) {
+ isc_throw(InvalidRdataText, "Invalid TSIG text: parse error" <<
+ full_input);
+ }
+ return (token);
+}
+
+// This helper function converts a string token to an *unsigned* integer.
+// NumType is a *signed* integral type (e.g. int32_t) that is sufficiently
+// wide to store resulting integers.
+// BitSize is the maximum number of bits that the resulting integer can take.
+// This function first checks whether the given token can be converted to
+// an integer of NumType type. It then confirms the conversion result is
+// within the valid range, i.e., [0, 2^NumType - 1]. The second check is
+// necessary because lexical_cast<T> where T is an unsigned integer type
+// doesn't correctly reject negative numbers when compiled with SunStudio.
+template <typename NumType, int BitSize>
+NumType
+tokenToNum(const string& num_token) {
+ NumType num;
+ try {
+ num = lexical_cast<NumType>(num_token);
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(InvalidRdataText, "Invalid TSIG numeric parameter: " <<
+ num_token);
+ }
+ if (num < 0 || num >= (static_cast<NumType>(1) << BitSize)) {
+ isc_throw(InvalidRdataText, "Numeric TSIG parameter out of range: " <<
+ num);
+ }
+ return (num);
+}
+}
+
+/// \brief Constructor from string.
+///
+/// \c tsig_str must be formatted as follows:
+/// \code <Alg> <Time> <Fudge> <MACsize> [<MAC>] <OrigID> <Error> <OtherLen> [<OtherData>]
+/// \endcode
+/// where
+/// - <Alg> is a valid textual representation of domain name.
+/// - <Time> is an unsigned 48-bit decimal integer.
+/// - <MACSize>, <OrigID>, and <OtherLen> are an unsigned 16-bit decimal
+/// integer.
+/// - <Error> is an unsigned 16-bit decimal integer or a valid mnemonic for
+/// the Error field specified in RFC2845. Currently, "BADSIG", "BADKEY",
+/// and "BADTIME" are supported (case sensitive). In future versions
+/// other representations that are compatible with the DNS RCODE will be
+/// supported.
+/// - <MAC> and <OtherData> is a BASE-64 encoded string that does not contain
+/// space characters.
+/// When <MACSize> and <OtherLen> is 0, <MAC> and <OtherData> must not
+/// appear in \c tsgi_str, respectively.
+/// - The decoded data of <MAC> is <MACSize> bytes of binary stream.
+/// - The decoded data of <OtherData> is <OtherLen> bytes of binary stream.
+///
+/// An example of valid string is:
+/// \code "hmac-sha256. 853804800 300 3 AAAA 2845 0 0" \endcode
+/// In this example <OtherData> is missing because <OtherLen> is 0.
+///
+/// Note that RFC2845 does not define the standard presentation format
+/// of %TSIG RR, so the above syntax is implementation specific.
+/// This is, however, compatible with the format acceptable to BIND 9's
+/// RDATA parser.
+///
+/// <b>Exceptions</b>
+///
+/// If <Alg> is not a valid domain name, a corresponding exception from
+/// the \c Name class will be thrown;
+/// if <MAC> or <OtherData> is not validly encoded in BASE-64, an exception
+/// of class \c isc::BadValue will be thrown;
+/// if %any of the other bullet points above is not met, an exception of
+/// class \c InvalidRdataText will be thrown.
+/// This constructor internally involves resource allocation, and if it fails
+/// a corresponding standard exception will be thrown.
+TSIG::TSIG(const std::string& tsig_str) : impl_(NULL) {
+ istringstream iss(tsig_str);
+
+ const Name algorithm(getToken(iss, tsig_str));
+ const int64_t time_signed = tokenToNum<int64_t, 48>(getToken(iss,
+ tsig_str));
+ const int32_t fudge = tokenToNum<int32_t, 16>(getToken(iss, tsig_str));
+ const int32_t macsize = tokenToNum<int32_t, 16>(getToken(iss, tsig_str));
+
+ const string mac_txt = (macsize > 0) ? getToken(iss, tsig_str) : "";
+ vector<uint8_t> mac;
+ decodeBase64(mac_txt, mac);
+ if (mac.size() != macsize) {
+ isc_throw(InvalidRdataText, "TSIG MAC size and data are inconsistent");
+ }
+
+ const int32_t orig_id = tokenToNum<int32_t, 16>(getToken(iss, tsig_str));
+
+ const string error_txt = getToken(iss, tsig_str);
+ int32_t error = 0;
+ // XXX: In the initial implementation we hardcode the mnemonics.
+ // We'll soon generalize this.
+ if (error_txt == "BADSIG") {
+ error = 16;
+ } else if (error_txt == "BADKEY") {
+ error = 17;
+ } else if (error_txt == "BADTIME") {
+ error = 18;
+ } else {
+ error = tokenToNum<int32_t, 16>(error_txt);
+ }
+
+ const int32_t otherlen = tokenToNum<int32_t, 16>(getToken(iss, tsig_str));
+ const string otherdata_txt = (otherlen > 0) ? getToken(iss, tsig_str) : "";
+ vector<uint8_t> other_data;
+ decodeBase64(otherdata_txt, other_data);
+
+ if (!iss.eof()) {
+ isc_throw(InvalidRdataText, "Unexpected input for TSIG RDATA: " <<
+ tsig_str);
+ }
+
+ impl_ = new TSIGImpl(algorithm, time_signed, fudge, mac, orig_id,
+ error, other_data);
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// When a read operation on \c buffer fails (e.g., due to a corrupted
+/// message) a corresponding exception from the \c InputBuffer class will
+/// be thrown.
+/// If the wire-format data does not begin with a valid domain name,
+/// a corresponding exception from the \c Name class will be thrown.
+/// In addition, this constructor internally involves resource allocation,
+/// and if it fails a corresponding standard exception will be thrown.
+///
+/// According to RFC3597, the Algorithm field must be a non compressed form
+/// of domain name. But this implementation accepts a %TSIG RR even if that
+/// field is compressed.
+///
+/// \param buffer A buffer storing the wire format data.
+/// \param rdata_len The length of the RDATA in bytes, normally expected
+/// to be the value of the RDLENGTH field of the corresponding RR.
+/// But this constructor does not use this parameter; if necessary, the caller
+/// must check consistency between the length parameter and the actual
+/// RDATA length.
+TSIG::TSIG(InputBuffer& buffer, size_t) : impl_(NULL) {
+ Name algorithm(buffer);
+
+ uint8_t time_signed_buf[6];
+ buffer.readData(time_signed_buf, sizeof(time_signed_buf));
+ const uint64_t time_signed =
+ (static_cast<uint64_t>(time_signed_buf[0]) << 40 |
+ static_cast<uint64_t>(time_signed_buf[1]) << 32 |
+ static_cast<uint64_t>(time_signed_buf[2]) << 24 |
+ static_cast<uint64_t>(time_signed_buf[3]) << 16 |
+ static_cast<uint64_t>(time_signed_buf[4]) << 8 |
+ static_cast<uint64_t>(time_signed_buf[5]));
+
+ const uint16_t fudge = buffer.readUint16();
+
+ const uint16_t mac_size = buffer.readUint16();
+ vector<uint8_t> mac(mac_size);
+ if (mac_size > 0) {
+ buffer.readData(&mac[0], mac_size);
+ }
+
+ const uint16_t original_id = buffer.readUint16();
+ const uint16_t error = buffer.readUint16();
+
+ const uint16_t other_len = buffer.readUint16();
+ vector<uint8_t> other_data(other_len);
+ if (other_len > 0) {
+ buffer.readData(&other_data[0], other_len);
+ }
+
+ impl_ = new TSIGImpl(algorithm, time_signed, fudge, mac, original_id,
+ error, other_data);
+}
+
+TSIG::TSIG(const Name& algorithm, uint64_t time_signed, uint16_t fudge,
+ uint16_t mac_size, const void* mac, uint16_t original_id,
+ uint16_t error, uint16_t other_len, const void* other_data) :
+ impl_(NULL)
+{
+ // Time Signed is a 48-bit value.
+ if ((time_signed >> 48) != 0) {
+ isc_throw(OutOfRange, "TSIG Time Signed is too large: " <<
+ time_signed);
+ }
+ if ((mac_size == 0 && mac != NULL) || (mac_size > 0 && mac == NULL)) {
+ isc_throw(InvalidParameter, "TSIG MAC size and data inconsistent");
+ }
+ if ((other_len == 0 && other_data != NULL) ||
+ (other_len > 0 && other_data == NULL)) {
+ isc_throw(InvalidParameter,
+ "TSIG Other data length and data inconsistent");
+ }
+ impl_ = new TSIGImpl(algorithm, time_signed, fudge, mac_size, mac,
+ original_id, error, other_len, other_data);
+}
+
+/// \brief The copy constructor.
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
+/// This constructor never throws an exception otherwise.
+TSIG::TSIG(const TSIG& source) : Rdata(), impl_(new TSIGImpl(*source.impl_))
+{}
+
+TSIG&
+TSIG::operator=(const TSIG& source) {
+ if (impl_ == source.impl_) {
+ return (*this);
+ }
+
+ TSIGImpl* newimpl = new TSIGImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+TSIG::~TSIG() {
+ delete impl_;
+}
+
+/// \brief Convert the \c TSIG to a string.
+///
+/// The output of this method is formatted as described in the "from string"
+/// constructor (\c TSIG(const std::string&))).
+///
+/// If internal resource allocation fails, a corresponding
+/// standard exception will be thrown.
+///
+/// \return A \c string object that represents the \c TSIG object.
+std::string
+TSIG::toText() const {
+ string result;
+
+ result += impl_->algorithm_.toText() + " " +
+ lexical_cast<string>(impl_->time_signed_) + " " +
+ lexical_cast<string>(impl_->fudge_) + " " +
+ lexical_cast<string>(impl_->mac_.size()) + " ";
+ if (impl_->mac_.size() > 0) {
+ result += encodeBase64(impl_->mac_) + " ";
+ }
+ result += lexical_cast<string>(impl_->original_id_) + " ";
+ if (impl_->error_ == 16) { // XXX: we'll soon introduce generic converter.
+ result += "BADSIG ";
+ } else if (impl_->error_ == 17) {
+ result += "BADKEY ";
+ } else if (impl_->error_ == 18) {
+ result += "BADTIME ";
+ } else {
+ result += lexical_cast<string>(impl_->error_) + " ";
+ }
+ result += lexical_cast<string>(impl_->other_data_.size());
+ if (impl_->other_data_.size() > 0) {
+ result += " " + encodeBase64(impl_->other_data_);
+ }
+
+ return (result);
+}
+
+// Common sequence of toWire() operations used for the two versions of
+// toWire().
+template <typename Output>
+void
+TSIG::TSIGImpl::toWireCommon(Output& output) const {
+ output.writeUint16(time_signed_ >> 32);
+ output.writeUint32(time_signed_ & 0xffffffff);
+ output.writeUint16(fudge_);
+ const uint16_t mac_size = mac_.size();
+ output.writeUint16(mac_size);
+ if (mac_size > 0) {
+ output.writeData(&mac_[0], mac_size);
+ }
+ output.writeUint16(original_id_);
+ output.writeUint16(error_);
+ const uint16_t other_len = other_data_.size();
+ output.writeUint16(other_len);
+ if (other_len > 0) {
+ output.writeData(&other_data_[0], other_len);
+ }
+}
+
+/// \brief Render the \c TSIG in the wire format without name compression.
+///
+/// If internal resource allocation fails, a corresponding
+/// standard exception will be thrown.
+/// This method never throws an exception otherwise.
+///
+/// \param buffer An output buffer to store the wire data.
+void
+TSIG::toWire(OutputBuffer& buffer) const {
+ impl_->algorithm_.toWire(buffer);
+ impl_->toWireCommon<OutputBuffer>(buffer);
+}
+
+/// \brief Render the \c TSIG in the wire format with taking into account
+/// compression.
+///
+/// As specified in RFC3597, the Algorithm field (a domain name) will not
+/// be compressed. However, the domain name could be a target of compression
+/// of other compressible names (though pretty unlikely), the offset
+/// information of the algorithm name may be recorded in \c renderer.
+///
+/// If internal resource allocation fails, a corresponding
+/// standard exception will be thrown.
+/// This method never throws an exception otherwise.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer and name compression information.
+void
+TSIG::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeName(impl_->algorithm_, false);
+ impl_->toWireCommon<AbstractMessageRenderer>(renderer);
+}
+
+// A helper function commonly used for TSIG::compare().
+int
+vectorComp(const vector<uint8_t>& v1, const vector<uint8_t>& v2) {
+ const size_t this_size = v1.size();
+ const size_t other_size = v2.size();
+ if (this_size != other_size) {
+ return (this_size < other_size ? -1 : 1);
+ }
+ if (this_size > 0) {
+ return (memcmp(&v1[0], &v2[0], this_size));
+ }
+ return (0);
+}
+
+/// \brief Compare two instances of \c TSIG RDATA.
+///
+/// This method compares \c this and the \c other \c TSIG objects
+/// in terms of the DNSSEC sorting order as defined in RFC4034, and returns
+/// the result as an integer.
+///
+/// This method is expected to be used in a polymorphic way, and the
+/// parameter to compare against is therefore of the abstract \c Rdata class.
+/// However, comparing two \c Rdata objects of different RR types
+/// is meaningless, and \c other must point to a \c TSIG object;
+/// otherwise, the standard \c bad_cast exception will be thrown.
+/// This method never throws an exception otherwise.
+///
+/// \param other the right-hand operand to compare against.
+/// \return < 0 if \c this would be sorted before \c other.
+/// \return 0 if \c this is identical to \c other in terms of sorting order.
+/// \return > 0 if \c this would be sorted after \c other.
+int
+TSIG::compare(const Rdata& other) const {
+ const TSIG& other_tsig = dynamic_cast<const TSIG&>(other);
+
+ const int ncmp = compareNames(impl_->algorithm_,
+ other_tsig.impl_->algorithm_);
+ if (ncmp != 0) {
+ return (ncmp);
+ }
+
+ if (impl_->time_signed_ != other_tsig.impl_->time_signed_) {
+ return (impl_->time_signed_ < other_tsig.impl_->time_signed_ ? -1 : 1);
+ }
+ if (impl_->fudge_ != other_tsig.impl_->fudge_) {
+ return (impl_->fudge_ < other_tsig.impl_->fudge_ ? -1 : 1);
+ }
+ const int vcmp = vectorComp(impl_->mac_, other_tsig.impl_->mac_);
+ if (vcmp != 0) {
+ return (vcmp);
+ }
+ if (impl_->original_id_ != other_tsig.impl_->original_id_) {
+ return (impl_->original_id_ < other_tsig.impl_->original_id_ ? -1 : 1);
+ }
+ if (impl_->error_ != other_tsig.impl_->error_) {
+ return (impl_->error_ < other_tsig.impl_->error_ ? -1 : 1);
+ }
+ return (vectorComp(impl_->other_data_, other_tsig.impl_->other_data_));
+}
+
+const Name&
+TSIG::getAlgorithm() const {
+ return (impl_->algorithm_);
+}
+
+uint64_t
+TSIG::getTimeSigned() const {
+ return (impl_->time_signed_);
+}
+
+uint16_t
+TSIG::getFudge() const {
+ return (impl_->fudge_);
+}
+
+uint16_t
+TSIG::getMACSize() const {
+ return (impl_->mac_.size());
+}
+
+const void*
+TSIG::getMAC() const {
+ if (impl_->mac_.size() > 0) {
+ return (&impl_->mac_[0]);
+ } else {
+ return (NULL);
+ }
+}
+
+uint16_t
+TSIG::getOriginalID() const {
+ return (impl_->original_id_);
+}
+
+uint16_t
+TSIG::getError() const {
+ return (impl_->error_);
+}
+
+uint16_t
+TSIG::getOtherLen() const {
+ return (impl_->other_data_.size());
+}
+
+const void*
+TSIG::getOtherData() const {
+ if (impl_->other_data_.size() > 0) {
+ return (&impl_->other_data_[0]);
+ } else {
+ return (NULL);
+ }
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/any_255/tsig_250.h b/src/lib/dns/rdata/any_255/tsig_250.h
new file mode 100644
index 0000000..ff24925
--- /dev/null
+++ b/src/lib/dns/rdata/any_255/tsig_250.h
@@ -0,0 +1,158 @@
+// 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.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/rdata.h>
+
+namespace isc {
+namespace dns {
+class Name;
+}
+}
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief \c rdata::TSIG class represents the TSIG RDATA as defined %in
+/// RFC2845.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// TSIG RDATA.
+class TSIG : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ /// \brief Constructor from RDATA field parameters.
+ ///
+ /// The parameters are a straightforward mapping of %TSIG RDATA
+ /// fields as defined %in RFC2845, but there are some implementation
+ /// specific notes as follows.
+ ///
+ /// \c algorithm is a \c Name object that specifies the algorithm.
+ /// For example, if the algorithm is HMAC-SHA256, \c algorithm would be
+ /// \c Name("hmac-sha256").
+ ///
+ /// \c time_signed corresponds to the Time Signed field, which is of
+ /// 48-bit unsigned integer type, and therefore cannot exceed 2^48-1;
+ /// otherwise, an exception of type \c OutOfRange will be thrown.
+ ///
+ /// \c mac_size and \c mac correspond to the MAC Size and MAC fields,
+ /// respectively. When the MAC field is empty, \c mac must be NULL.
+ /// \c mac_size and \c mac must be consistent %in that \c mac_size is 0 if
+ /// and only if \c mac is NULL; otherwise an exception of type
+ /// InvalidParameter will be thrown.
+ ///
+ /// The same restriction applies to \c other_len and \c other_data,
+ /// which correspond to the Other Len and Other Data fields, respectively.
+ ///
+ /// This constructor internally involves resource allocation, and if
+ /// it fails, a corresponding standard exception will be thrown.
+ TSIG(const Name& algorithm, uint64_t time_signed, uint16_t fudge,
+ uint16_t mac_size, const void* mac, uint16_t original_id,
+ uint16_t error, uint16_t other_len, const void* other_data);
+
+ /// \brief Assignment operator.
+ ///
+ /// It internally allocates a resource, and if it fails a corresponding
+ /// standard exception will be thrown.
+ /// This operator never throws an exception otherwise.
+ ///
+ /// This operator provides the strong exception guarantee: When an
+ /// exception is thrown the content of the assignment target will be
+ /// intact.
+ TSIG& operator=(const TSIG& source);
+
+ /// \brief The destructor.
+ ~TSIG();
+
+ /// \brief Return the algorithm name.
+ ///
+ /// This method never throws an exception.
+ const Name& getAlgorithm() const;
+
+ /// \brief Return the value of the Time Signed field.
+ ///
+ /// The returned value does not exceed 2^48-1.
+ ///
+ /// This method never throws an exception.
+ uint64_t getTimeSigned() const;
+
+ /// \brief Return the value of the Fudge field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getFudge() const;
+
+ /// \brief Return the value of the MAC Size field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getMACSize() const;
+
+ /// \brief Return the value of the MAC field.
+ ///
+ /// If the MAC field is empty, it returns NULL.
+ /// Otherwise, the memory region beginning at the address returned by
+ /// this method is valid up to the bytes specified by the return value
+ /// of \c getMACSize().
+ /// The memory region is only valid while the corresponding \c TSIG
+ /// object is valid. The caller must hold the \c TSIG object while
+ /// it needs to refer to the region or it must make a local copy of the
+ /// region.
+ ///
+ /// This method never throws an exception.
+ const void* getMAC() const;
+
+ /// \brief Return the value of the Original ID field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getOriginalID() const;
+
+ /// \brief Return the value of the Error field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getError() const;
+
+ /// \brief Return the value of the Other Len field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getOtherLen() const;
+
+ /// \brief Return the value of the Other Data field.
+ ///
+ /// The same note as \c getMAC() applies.
+ ///
+ /// This method never throws an exception.
+ const void* getOtherData() const;
+private:
+ struct TSIGImpl;
+ TSIGImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/ch_3/a_1.cc b/src/lib/dns/rdata/ch_3/a_1.cc
index 9daa9e3..d0b097a 100644
--- a/src/lib/dns/rdata/ch_3/a_1.cc
+++ b/src/lib/dns/rdata/ch_3/a_1.cc
@@ -12,10 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
-#include <config.h> // for UNUSED_PARAM
-
#include <string>
#include <exceptions/exceptions.h>
@@ -30,25 +26,25 @@ using namespace std;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-A::A(const string& addrstr UNUSED_PARAM) {
+A::A(const string&) {
// TBD
}
-A::A(InputBuffer& buffer UNUSED_PARAM, size_t rdata_len UNUSED_PARAM) {
+A::A(InputBuffer&, size_t) {
// TBD
}
-A::A(const A& source UNUSED_PARAM) : Rdata() {
+A::A(const A&) : Rdata() {
// TBD
}
void
-A::toWire(OutputBuffer& buffer UNUSED_PARAM) const {
+A::toWire(OutputBuffer&) const {
// TBD
}
void
-A::toWire(AbstractMessageRenderer& renderer UNUSED_PARAM) const {
+A::toWire(AbstractMessageRenderer&) const {
// TBD
}
@@ -59,7 +55,7 @@ A::toText() const {
}
int
-A::compare(const Rdata& other UNUSED_PARAM) const {
+A::compare(const Rdata&) const {
return (0); // dummy. TBD
}
diff --git a/src/lib/dns/rdata/ch_3/a_1.h b/src/lib/dns/rdata/ch_3/a_1.h
index 9e827bb..6d75952 100644
--- a/src/lib/dns/rdata/ch_3/a_1.h
+++ b/src/lib/dns/rdata/ch_3/a_1.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
// BEGIN_HEADER_GUARD
#include <string>
diff --git a/src/lib/dns/rdata/generic/cname_5.cc b/src/lib/dns/rdata/generic/cname_5.cc
index feb39cf..3634a43 100644
--- a/src/lib/dns/rdata/generic/cname_5.cc
+++ b/src/lib/dns/rdata/generic/cname_5.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config.h>
#include <string>
@@ -33,7 +31,7 @@ CNAME::CNAME(const std::string& namestr) :
cname_(namestr)
{}
-CNAME::CNAME(InputBuffer& buffer, size_t rdata_len UNUSED_PARAM) :
+CNAME::CNAME(InputBuffer& buffer, size_t) :
Rdata(), cname_(buffer)
{
// we don't need rdata_len for parsing. if necessary, the caller will
diff --git a/src/lib/dns/rdata/generic/cname_5.h b/src/lib/dns/rdata/generic/cname_5.h
index 9630cea..92dffad 100644
--- a/src/lib/dns/rdata/generic/cname_5.h
+++ b/src/lib/dns/rdata/generic/cname_5.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
// BEGIN_HEADER_GUARD
#include <string>
diff --git a/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc b/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
new file mode 100644
index 0000000..a72058f
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
@@ -0,0 +1,78 @@
+// 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 <stdint.h>
+
+#include <vector>
+
+#include <dns/exceptions.h>
+
+using namespace std;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+namespace nsec {
+void
+checkRRTypeBitmaps(const char* const rrtype_name,
+ const vector<uint8_t>& typebits)
+{
+ bool first = true;
+ unsigned int lastblock = 0;
+ const size_t total_len = typebits.size();
+ size_t i = 0;
+
+ while (i < total_len) {
+ if (i + 2 > total_len) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " RDATA from wire: incomplete bit map field");
+ }
+ const unsigned int block = typebits[i];
+ const size_t len = typebits[i + 1];
+ // Check that bitmap window blocks are in the correct order.
+ if (!first && block <= lastblock) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " RDATA from wire: Disordered window blocks found: "
+ << lastblock << " then " << block);
+ }
+ // Check for legal length
+ if (len < 1 || len > 32) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " RDATA from wire: Invalid bitmap length: " << len);
+ }
+ // Check for overflow.
+ i += 2;
+ if (i + len > total_len) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " RDATA from wire: bitmap length too large: " << len);
+ }
+ // The last octet of the bitmap must be non zero.
+ if (typebits[i + len - 1] == 0) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " RDATA from wire: bitmap ending an all-zero byte");
+ }
+
+ i += len;
+ lastblock = block;
+ first = false;
+ }
+}
+}
+}
+}
+}
+}
+}
diff --git a/src/lib/dns/rdata/generic/detail/nsec_bitmap.h b/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
new file mode 100644
index 0000000..6431e10
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
@@ -0,0 +1,51 @@
+// 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 <stdint.h>
+
+#include <vector>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+namespace nsec {
+/// Check if a given "type bitmap" for NSEC/NSEC3 is valid.
+///
+/// This helper function checks given wire format data (stored in a
+/// \c std::vector) is a valid type bitmaps used for the NSEC and NSEC3 RRs
+/// according to RFC4034 and RFC5155. The validation logic is the same
+/// for these two RRs, so a unified check function is provided.
+/// This function is essentially private and is only expected to be called
+/// from the \c NSEC and \c NSEC3 class implementations.
+///
+/// \exception DNSMessageFORMERR The bitmap is not valid.
+///
+/// \param rrtype_name Either "NSEC" or "NSEC3"; used as part of exception
+/// messages.
+/// \param typebits The type bitmaps in wire format. The size of vector
+/// is the total length of the bitmaps.
+void checkRRTypeBitmaps(const char* const rrtype_name,
+ const std::vector<uint8_t>& typebits);
+}
+}
+}
+}
+}
+}
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/dname_39.cc b/src/lib/dns/rdata/generic/dname_39.cc
index 2d64830..d69dedb 100644
--- a/src/lib/dns/rdata/generic/dname_39.cc
+++ b/src/lib/dns/rdata/generic/dname_39.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config.h>
#include <string>
@@ -33,7 +31,7 @@ DNAME::DNAME(const std::string& namestr) :
dname_(namestr)
{}
-DNAME::DNAME(InputBuffer& buffer, size_t rdata_len UNUSED_PARAM) :
+DNAME::DNAME(InputBuffer& buffer, size_t) :
dname_(buffer)
{
// we don't need rdata_len for parsing. if necessary, the caller will
diff --git a/src/lib/dns/rdata/generic/dname_39.h b/src/lib/dns/rdata/generic/dname_39.h
index 7a12abd..5e12167 100644
--- a/src/lib/dns/rdata/generic/dname_39.h
+++ b/src/lib/dns/rdata/generic/dname_39.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
// BEGIN_HEADER_GUARD
#include <string>
diff --git a/src/lib/dns/rdata/generic/dnskey_48.cc b/src/lib/dns/rdata/generic/dnskey_48.cc
index b519cfa..5e826c3 100644
--- a/src/lib/dns/rdata/generic/dnskey_48.cc
+++ b/src/lib/dns/rdata/generic/dnskey_48.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <iostream>
#include <string>
#include <sstream>
diff --git a/src/lib/dns/rdata/generic/dnskey_48.h b/src/lib/dns/rdata/generic/dnskey_48.h
index e676227..14fad9f 100644
--- a/src/lib/dns/rdata/generic/dnskey_48.h
+++ b/src/lib/dns/rdata/generic/dnskey_48.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <string>
diff --git a/src/lib/dns/rdata/generic/ds_43.cc b/src/lib/dns/rdata/generic/ds_43.cc
index 7c84023..5037402 100644
--- a/src/lib/dns/rdata/generic/ds_43.cc
+++ b/src/lib/dns/rdata/generic/ds_43.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <iostream>
#include <string>
#include <sstream>
diff --git a/src/lib/dns/rdata/generic/ds_43.h b/src/lib/dns/rdata/generic/ds_43.h
index e88c339..03b19a0 100644
--- a/src/lib/dns/rdata/generic/ds_43.h
+++ b/src/lib/dns/rdata/generic/ds_43.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <string>
diff --git a/src/lib/dns/rdata/generic/mx_15.cc b/src/lib/dns/rdata/generic/mx_15.cc
index cfd0b7a..52f1fc7 100644
--- a/src/lib/dns/rdata/generic/mx_15.cc
+++ b/src/lib/dns/rdata/generic/mx_15.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config.h>
#include <string>
@@ -34,7 +32,7 @@ using namespace boost;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-MX::MX(InputBuffer& buffer, size_t rdata_len UNUSED_PARAM) :
+MX::MX(InputBuffer& buffer, size_t) :
preference_(buffer.readUint16()), mxname_(buffer)
{
// we don't need rdata_len for parsing. if necessary, the caller will
diff --git a/src/lib/dns/rdata/generic/mx_15.h b/src/lib/dns/rdata/generic/mx_15.h
index 50c8c98..1381f18 100644
--- a/src/lib/dns/rdata/generic/mx_15.h
+++ b/src/lib/dns/rdata/generic/mx_15.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
// BEGIN_HEADER_GUARD
#include <stdint.h>
diff --git a/src/lib/dns/rdata/generic/ns_2.cc b/src/lib/dns/rdata/generic/ns_2.cc
index b93d739..d51cfc4 100644
--- a/src/lib/dns/rdata/generic/ns_2.cc
+++ b/src/lib/dns/rdata/generic/ns_2.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config.h>
#include <string>
@@ -33,7 +31,7 @@ NS::NS(const std::string& namestr) :
nsname_(namestr)
{}
-NS::NS(InputBuffer& buffer, size_t rdata_len UNUSED_PARAM) :
+NS::NS(InputBuffer& buffer, size_t) :
nsname_(buffer)
{
// we don't need rdata_len for parsing. if necessary, the caller will
diff --git a/src/lib/dns/rdata/generic/ns_2.h b/src/lib/dns/rdata/generic/ns_2.h
index 4e7d0b7..fa44f22 100644
--- a/src/lib/dns/rdata/generic/ns_2.h
+++ b/src/lib/dns/rdata/generic/ns_2.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: rdata.h 545 2010-01-27 00:33:28Z jinmei $
-
// BEGIN_HEADER_GUARD
#include <string>
diff --git a/src/lib/dns/rdata/generic/nsec3_50.cc b/src/lib/dns/rdata/generic/nsec3_50.cc
index 1e52996..0deeea2 100644
--- a/src/lib/dns/rdata/generic/nsec3_50.cc
+++ b/src/lib/dns/rdata/generic/nsec3_50.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <iostream>
#include <iomanip>
#include <string>
@@ -32,11 +30,13 @@
#include <dns/rrttl.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/nsec_bitmap.h>
#include <stdio.h>
#include <time.h>
using namespace std;
+using namespace isc::dns::rdata::generic::detail::nsec;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -63,50 +63,70 @@ NSEC3::NSEC3(const string& nsec3_str) :
{
istringstream iss(nsec3_str);
unsigned int hashalg, flags, iterations;
- string salthex;
+ string iterations_str, salthex, nexthash;
- iss >> hashalg >> flags >> iterations >> salthex;
+ iss >> hashalg >> flags >> iterations_str >> salthex >> nexthash;
if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid NSEC3 text");
+ isc_throw(InvalidRdataText, "Invalid NSEC3 text: " << nsec3_str);
}
- if (hashalg > 0xf) {
- isc_throw(InvalidRdataText, "NSEC3 hash algorithm out of range");
+ if (hashalg > 0xff) {
+ isc_throw(InvalidRdataText,
+ "NSEC3 hash algorithm out of range: " << hashalg);
}
if (flags > 0xff) {
- isc_throw(InvalidRdataText, "NSEC3 flags out of range");
+ isc_throw(InvalidRdataText, "NSEC3 flags out of range: " << flags);
+ }
+ // Convert iteration. To reject an invalid case where there's no space
+ // between iteration and salt, we extract this field as string and convert
+ // to integer.
+ try {
+ iterations = boost::lexical_cast<unsigned int>(iterations_str);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(InvalidRdataText, "Bad NSEC3 iteration: " << iterations_str);
}
if (iterations > 0xffff) {
- isc_throw(InvalidRdataText, "NSEC3 iterations out of range");
+ isc_throw(InvalidRdataText, "NSEC3 iterations out of range: " <<
+ iterations);
}
vector<uint8_t> salt;
- decodeHex(salthex, salt);
+ if (salthex != "-") { // "-" means a 0-length salt
+ decodeHex(salthex, salt);
+ }
+ if (salt.size() > 255) {
+ isc_throw(InvalidRdataText, "NSEC3 salt is too long: "
+ << salt.size() << " bytes");
+ }
- string nextstr;
- iss >> setw(32) >> nextstr;
vector<uint8_t> next;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid NSEC3 hash algorithm");
+ decodeBase32Hex(nexthash, next);
+ if (next.size() > 255) {
+ isc_throw(InvalidRdataText, "NSEC3 hash is too long: "
+ << next.size() << " bytes");
}
- decodeBase32Hex(nextstr, next);
- uint8_t bitmap[8 * 1024]; // 64k bits
- vector<uint8_t> typebits;
+ // For NSEC3 empty bitmap is possible and allowed.
+ if (iss.eof()) {
+ impl_ = new NSEC3Impl(hashalg, flags, iterations, salt, next,
+ vector<uint8_t>());
+ return;
+ }
+ vector<uint8_t> typebits;
+ uint8_t bitmap[8 * 1024]; // 64k bits
memset(bitmap, 0, sizeof(bitmap));
do {
string type;
- int code;
iss >> type;
if (type.length() != 0) {
try {
- code = RRType(type).getCode();
+ const int code = RRType(type).getCode();
bitmap[code / 8] |= (0x80 >> (code % 8));
} catch (...) {
isc_throw(InvalidRdataText, "Invalid RRtype in NSEC3");
}
}
- } while(!iss.eof());
+ } while (!iss.eof());
for (int window = 0; window < 256; window++) {
int octet;
@@ -128,56 +148,46 @@ NSEC3::NSEC3(const string& nsec3_str) :
}
NSEC3::NSEC3(InputBuffer& buffer, size_t rdata_len) {
+ // NSEC3 RR must have at least 5 octets:
+ // hash algorithm(1), flags(1), iteration(2), saltlen(1)
if (rdata_len < 5) {
- isc_throw(InvalidRdataLength, "NSEC3 too short");
+ isc_throw(DNSMessageFORMERR, "NSEC3 too short, length: " << rdata_len);
}
- uint8_t hashalg = buffer.readUint8();
- uint8_t flags = buffer.readUint8();
- uint16_t iterations = buffer.readUint16();
- rdata_len -= 4;
-
- uint8_t saltlen = buffer.readUint8();
- --rdata_len;
+ const uint8_t hashalg = buffer.readUint8();
+ const uint8_t flags = buffer.readUint8();
+ const uint16_t iterations = buffer.readUint16();
+ const uint8_t saltlen = buffer.readUint8();
+ rdata_len -= 5;
if (rdata_len < saltlen) {
- isc_throw(InvalidRdataLength, "NSEC3 salt too short");
+ isc_throw(DNSMessageFORMERR, "NSEC3 salt length is too large: " <<
+ static_cast<unsigned int>(saltlen));
}
vector<uint8_t> salt(saltlen);
- buffer.readData(&salt[0], saltlen);
- rdata_len -= saltlen;
+ if (saltlen > 0) {
+ buffer.readData(&salt[0], saltlen);
+ rdata_len -= saltlen;
+ }
- uint8_t nextlen = buffer.readUint8();
+ const uint8_t nextlen = buffer.readUint8();
--rdata_len;
-
- if (rdata_len < nextlen) {
- isc_throw(InvalidRdataLength, "NSEC3 next hash too short");
+ if (nextlen == 0 || rdata_len < nextlen) {
+ isc_throw(DNSMessageFORMERR, "NSEC3 invalid hash length: " <<
+ static_cast<unsigned int>(nextlen));
}
vector<uint8_t> next(nextlen);
buffer.readData(&next[0], nextlen);
rdata_len -= nextlen;
- if (rdata_len == 0) {
- isc_throw(InvalidRdataLength, "NSEC3 type bitmap too short");
- }
-
vector<uint8_t> typebits(rdata_len);
- buffer.readData(&typebits[0], rdata_len);
-
- int len = 0;
- for (int i = 0; i < typebits.size(); i += len) {
- if (i + 2 > typebits.size()) {
- isc_throw(DNSMessageFORMERR, "Invalid rdata: "
- "bad NSEC3 type bitmap");
- }
- len = typebits[i + 1];
- if (len > 31) {
- isc_throw(DNSMessageFORMERR, "Invalid rdata: "
- "bad NSEC3 type bitmap");
- }
- i += 2;
+ if (rdata_len > 0) {
+ // Read and parse the bitmaps only when they exist; empty bitmap
+ // is possible for NSEC3.
+ buffer.readData(&typebits[0], rdata_len);
+ checkRRTypeBitmaps("NSEC3", typebits);
}
impl_ = new NSEC3Impl(hashalg, flags, iterations, salt, next, typebits);
@@ -329,10 +339,15 @@ NSEC3::getIterations() const {
return (impl_->iterations_);
}
-vector<uint8_t>&
+const vector<uint8_t>&
NSEC3::getSalt() const {
return (impl_->salt_);
}
+const vector<uint8_t>&
+NSEC3::getNext() const {
+ return (impl_->next_);
+}
+
// END_RDATA_NAMESPACE
// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/nsec3_50.h b/src/lib/dns/rdata/generic/nsec3_50.h
index eeec8bb..c766ade 100644
--- a/src/lib/dns/rdata/generic/nsec3_50.h
+++ b/src/lib/dns/rdata/generic/nsec3_50.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: nsec_47.h 991 2010-02-26 08:53:26Z jinmei $
-
#include <stdint.h>
#include <string>
@@ -45,7 +43,8 @@ public:
uint8_t getHashalg() const;
uint8_t getFlags() const;
uint16_t getIterations() const;
- std::vector<uint8_t>& getSalt() const;
+ const std::vector<uint8_t>& getSalt() const;
+ const std::vector<uint8_t>& getNext() const;
private:
NSEC3Impl* impl_;
diff --git a/src/lib/dns/rdata/generic/nsec3param_51.cc b/src/lib/dns/rdata/generic/nsec3param_51.cc
index 98b4dde..d81221c 100644
--- a/src/lib/dns/rdata/generic/nsec3param_51.cc
+++ b/src/lib/dns/rdata/generic/nsec3param_51.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <iostream>
#include <string>
#include <sstream>
diff --git a/src/lib/dns/rdata/generic/nsec3param_51.h b/src/lib/dns/rdata/generic/nsec3param_51.h
index cad236f..130c759 100644
--- a/src/lib/dns/rdata/generic/nsec3param_51.h
+++ b/src/lib/dns/rdata/generic/nsec3param_51.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <string>
diff --git a/src/lib/dns/rdata/generic/nsec_47.cc b/src/lib/dns/rdata/generic/nsec_47.cc
index b47d7e9..affd99b 100644
--- a/src/lib/dns/rdata/generic/nsec_47.cc
+++ b/src/lib/dns/rdata/generic/nsec_47.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <iostream>
#include <string>
#include <sstream>
@@ -28,11 +26,13 @@
#include <dns/rrttl.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/nsec_bitmap.h>
#include <stdio.h>
#include <time.h>
using namespace std;
+using namespace isc::dns::rdata::generic::detail::nsec;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -63,10 +63,9 @@ NSEC::NSEC(const string& nsec_str) :
memset(bitmap, 0, sizeof(bitmap));
do {
string type;
- int code;
iss >> type;
try {
- code = RRType(type).getCode();
+ const int code = RRType(type).getCode();
bitmap[code / 8] |= (0x80 >> (code % 8));
} catch (...) {
isc_throw(InvalidRdataText, "Invalid RRtype in NSEC");
@@ -105,43 +104,7 @@ NSEC::NSEC(InputBuffer& buffer, size_t rdata_len) {
vector<uint8_t> typebits(rdata_len);
buffer.readData(&typebits[0], rdata_len);
-
- int len = 0;
- bool first = true;
- unsigned int block, lastblock = 0;
- for (int i = 0; i < rdata_len; i += len) {
- if (i + 2 > rdata_len) {
- isc_throw(DNSMessageFORMERR, "NSEC RDATA from wire: "
- "incomplete bit map field");
- }
- block = typebits[i];
- len = typebits[i + 1];
- // Check that bitmap window blocks are in the correct order.
- if (!first && block <= lastblock) {
- isc_throw(DNSMessageFORMERR, "NSEC RDATA from wire: Disordered "
- "window blocks found: " << lastblock <<
- " then " << block);
- }
- // Check for legal length
- if (len < 1 || len > 32) {
- isc_throw(DNSMessageFORMERR, "NSEC RDATA from wire: Invalid bitmap "
- "length: " << len);
- }
- // Check for overflow.
- i += 2;
- if (i + len > rdata_len) {
- isc_throw(DNSMessageFORMERR, "NSEC RDATA from wire: bitmap length "
- "too large: " << len);
- }
- // The last octet of the bitmap must be non zero.
- if (typebits[i + len - 1] == 0) {
- isc_throw(DNSMessageFORMERR, "NSEC RDATA from wire: bitmap ending "
- "an all-zero byte");
- }
-
- lastblock = block;
- first = false;
- }
+ checkRRTypeBitmaps("NSEC", typebits);
impl_ = new NSECImpl(nextname, typebits);
}
diff --git a/src/lib/dns/rdata/generic/nsec_47.h b/src/lib/dns/rdata/generic/nsec_47.h
index 0140bca..b86a25b 100644
--- a/src/lib/dns/rdata/generic/nsec_47.h
+++ b/src/lib/dns/rdata/generic/nsec_47.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <string>
diff --git a/src/lib/dns/rdata/generic/opt_41.cc b/src/lib/dns/rdata/generic/opt_41.cc
index 761fe75..54d7c96 100644
--- a/src/lib/dns/rdata/generic/opt_41.cc
+++ b/src/lib/dns/rdata/generic/opt_41.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config.h>
#include <string>
@@ -28,7 +26,7 @@ using namespace std;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-OPT::OPT(const string& type_str UNUSED_PARAM) {
+OPT::OPT(const string&) {
isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
}
@@ -43,7 +41,7 @@ OPT::OPT(InputBuffer& buffer, size_t rdata_len) {
buffer.setPosition(buffer.getPosition() + rdata_len);
}
-OPT::OPT(const OPT& source UNUSED_PARAM) : Rdata() {
+OPT::OPT(const OPT&) : Rdata() {
// there's nothing to copy in this simple implementation.
}
@@ -53,12 +51,12 @@ OPT::toText() const {
}
void
-OPT::toWire(OutputBuffer& buffer UNUSED_PARAM) const {
+OPT::toWire(OutputBuffer&) const {
// nothing to do, as this simple version doesn't support any options.
}
void
-OPT::toWire(AbstractMessageRenderer& renderer UNUSED_PARAM) const {
+OPT::toWire(AbstractMessageRenderer&) const {
// nothing to do, as this simple version doesn't support any options.
}
diff --git a/src/lib/dns/rdata/generic/opt_41.h b/src/lib/dns/rdata/generic/opt_41.h
index d62d870..0cb7043 100644
--- a/src/lib/dns/rdata/generic/opt_41.h
+++ b/src/lib/dns/rdata/generic/opt_41.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
// BEGIN_HEADER_GUARD
#include <string>
diff --git a/src/lib/dns/rdata/generic/ptr_12.cc b/src/lib/dns/rdata/generic/ptr_12.cc
index 2a5423f..657252b 100644
--- a/src/lib/dns/rdata/generic/ptr_12.cc
+++ b/src/lib/dns/rdata/generic/ptr_12.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config.h>
#include <string>
@@ -33,7 +31,7 @@ PTR::PTR(const string& type_str) :
ptr_name_(type_str)
{}
-PTR::PTR(InputBuffer& buffer, size_t rdata_len UNUSED_PARAM) :
+PTR::PTR(InputBuffer& buffer, size_t) :
ptr_name_(buffer)
{
// we don't need rdata_len for parsing. if necessary, the caller will
diff --git a/src/lib/dns/rdata/generic/ptr_12.h b/src/lib/dns/rdata/generic/ptr_12.h
index 8fc42db..5d2d048 100644
--- a/src/lib/dns/rdata/generic/ptr_12.h
+++ b/src/lib/dns/rdata/generic/ptr_12.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
// BEGIN_HEADER_GUARD
#include <string>
diff --git a/src/lib/dns/rdata/generic/rrsig_46.cc b/src/lib/dns/rdata/generic/rrsig_46.cc
index e00f37b..7fb3419 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.cc
+++ b/src/lib/dns/rdata/generic/rrsig_46.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <string>
#include <iomanip>
#include <iostream>
@@ -95,8 +93,8 @@ RRSIG::RRSIG(const string& rrsig_str) :
isc_throw(InvalidRdataText, "RRSIG labels out of range");
}
- uint32_t timeexpire = timeFromText(expire_txt);
- uint32_t timeinception = timeFromText(inception_txt);
+ const uint32_t timeexpire = timeFromText32(expire_txt);
+ const uint32_t timeinception = timeFromText32(inception_txt);
vector<uint8_t> signature;
decodeBase64(signaturebuf.str(), signature);
@@ -159,15 +157,12 @@ RRSIG::~RRSIG() {
string
RRSIG::toText() const {
- string expire = timeToText(impl_->timeexpire_);
- string inception = timeToText(impl_->timeinception_);
-
return (impl_->covered_.toText() +
" " + boost::lexical_cast<string>(static_cast<int>(impl_->algorithm_))
+ " " + boost::lexical_cast<string>(static_cast<int>(impl_->labels_))
+ " " + boost::lexical_cast<string>(impl_->originalttl_)
- + " " + expire
- + " " + inception
+ + " " + timeToText32(impl_->timeexpire_)
+ + " " + timeToText32(impl_->timeinception_)
+ " " + boost::lexical_cast<string>(impl_->tag_)
+ " " + impl_->signer_.toText()
+ " " + encodeBase64(impl_->signature_));
diff --git a/src/lib/dns/rdata/generic/rrsig_46.h b/src/lib/dns/rdata/generic/rrsig_46.h
index 3cc254a..19acc40 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.h
+++ b/src/lib/dns/rdata/generic/rrsig_46.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <string>
diff --git a/src/lib/dns/rdata/generic/soa_6.cc b/src/lib/dns/rdata/generic/soa_6.cc
index c167a24..6225819 100644
--- a/src/lib/dns/rdata/generic/soa_6.cc
+++ b/src/lib/dns/rdata/generic/soa_6.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config.h>
#include <string>
@@ -34,7 +32,7 @@ using namespace boost;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-SOA::SOA(InputBuffer& buffer, size_t rdata_len UNUSED_PARAM) :
+SOA::SOA(InputBuffer& buffer, size_t) :
mname_(buffer), rname_(buffer)
{
// we don't need rdata_len for parsing. if necessary, the caller will
diff --git a/src/lib/dns/rdata/generic/soa_6.h b/src/lib/dns/rdata/generic/soa_6.h
index fc847ff..3f6185e 100644
--- a/src/lib/dns/rdata/generic/soa_6.h
+++ b/src/lib/dns/rdata/generic/soa_6.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
// BEGIN_HEADER_GUARD
#include <string>
diff --git a/src/lib/dns/rdata/generic/txt_16.cc b/src/lib/dns/rdata/generic/txt_16.cc
index 437ec99..f9ce86c 100644
--- a/src/lib/dns/rdata/generic/txt_16.cc
+++ b/src/lib/dns/rdata/generic/txt_16.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <string.h>
diff --git a/src/lib/dns/rdata/generic/txt_16.h b/src/lib/dns/rdata/generic/txt_16.h
index 33a1ee2..b4c791f 100644
--- a/src/lib/dns/rdata/generic/txt_16.h
+++ b/src/lib/dns/rdata/generic/txt_16.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
// BEGIN_HEADER_GUARD
#include <stdint.h>
diff --git a/src/lib/dns/rdata/hs_4/a_1.cc b/src/lib/dns/rdata/hs_4/a_1.cc
index 9daa9e3..d0b097a 100644
--- a/src/lib/dns/rdata/hs_4/a_1.cc
+++ b/src/lib/dns/rdata/hs_4/a_1.cc
@@ -12,10 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
-#include <config.h> // for UNUSED_PARAM
-
#include <string>
#include <exceptions/exceptions.h>
@@ -30,25 +26,25 @@ using namespace std;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-A::A(const string& addrstr UNUSED_PARAM) {
+A::A(const string&) {
// TBD
}
-A::A(InputBuffer& buffer UNUSED_PARAM, size_t rdata_len UNUSED_PARAM) {
+A::A(InputBuffer&, size_t) {
// TBD
}
-A::A(const A& source UNUSED_PARAM) : Rdata() {
+A::A(const A&) : Rdata() {
// TBD
}
void
-A::toWire(OutputBuffer& buffer UNUSED_PARAM) const {
+A::toWire(OutputBuffer&) const {
// TBD
}
void
-A::toWire(AbstractMessageRenderer& renderer UNUSED_PARAM) const {
+A::toWire(AbstractMessageRenderer&) const {
// TBD
}
@@ -59,7 +55,7 @@ A::toText() const {
}
int
-A::compare(const Rdata& other UNUSED_PARAM) const {
+A::compare(const Rdata&) const {
return (0); // dummy. TBD
}
diff --git a/src/lib/dns/rdata/hs_4/a_1.h b/src/lib/dns/rdata/hs_4/a_1.h
index 9e827bb..6d75952 100644
--- a/src/lib/dns/rdata/hs_4/a_1.h
+++ b/src/lib/dns/rdata/hs_4/a_1.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
// BEGIN_HEADER_GUARD
#include <string>
diff --git a/src/lib/dns/rdata/in_1/a_1.cc b/src/lib/dns/rdata/in_1/a_1.cc
index c6a7166..509146f 100644
--- a/src/lib/dns/rdata/in_1/a_1.cc
+++ b/src/lib/dns/rdata/in_1/a_1.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: rdata.cc 545 2010-01-27 00:33:28Z jinmei $
-
#include <stdint.h>
#include <string.h>
diff --git a/src/lib/dns/rdata/in_1/a_1.h b/src/lib/dns/rdata/in_1/a_1.h
index d684267..6e98f0d 100644
--- a/src/lib/dns/rdata/in_1/a_1.h
+++ b/src/lib/dns/rdata/in_1/a_1.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: rdata.h 545 2010-01-27 00:33:28Z jinmei $
-
// BEGIN_HEADER_GUARD
#include <string>
diff --git a/src/lib/dns/rdata/in_1/aaaa_28.cc b/src/lib/dns/rdata/in_1/aaaa_28.cc
index 7654c3e..4487eb6 100644
--- a/src/lib/dns/rdata/in_1/aaaa_28.cc
+++ b/src/lib/dns/rdata/in_1/aaaa_28.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <string.h>
diff --git a/src/lib/dns/rdata/in_1/aaaa_28.h b/src/lib/dns/rdata/in_1/aaaa_28.h
index cf4f450..3093017 100644
--- a/src/lib/dns/rdata/in_1/aaaa_28.h
+++ b/src/lib/dns/rdata/in_1/aaaa_28.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
// BEGIN_HEADER_GUARD
#include <stdint.h>
diff --git a/src/lib/dns/rdata/template.cc b/src/lib/dns/rdata/template.cc
index 3c84203..3434503 100644
--- a/src/lib/dns/rdata/template.cc
+++ b/src/lib/dns/rdata/template.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <string>
#include <dns/buffer.h>
diff --git a/src/lib/dns/rdata/template.h b/src/lib/dns/rdata/template.h
index 9541af2..e85a839 100644
--- a/src/lib/dns/rdata/template.h
+++ b/src/lib/dns/rdata/template.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
// BEGIN_HEADER_GUARD
#include <string>
diff --git a/src/lib/dns/rrclass-placeholder.h b/src/lib/dns/rrclass-placeholder.h
index 2128196..ce9a141 100644
--- a/src/lib/dns/rrclass-placeholder.h
+++ b/src/lib/dns/rrclass-placeholder.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: rrclass.h 530 2010-01-26 22:15:42Z jinmei $
-
#ifndef __RRCLASS_H
#define __RRCLASS_H 1
@@ -244,14 +242,12 @@ public:
// END_WELL_KNOWN_CLASS_DECLARATIONS
static const RRClass& NONE();
- static const RRClass& ANY();
private:
// \brief Meta-classes
enum {
RRCLASS_RESERVED0 = 0,
- RRCLASS_NONE = 254,
- RRCLASS_ANY = 255
+ RRCLASS_NONE = 254
};
uint16_t classcode_;
};
@@ -266,13 +262,6 @@ RRClass::NONE() {
return (rrclass);
}
-inline const RRClass&
-RRClass::ANY() {
- static RRClass rrclass(RRCLASS_ANY);
-
- return (rrclass);
-}
-
///
/// \brief Insert the \c RRClass as a string into stream.
///
diff --git a/src/lib/dns/rrclass.cc b/src/lib/dns/rrclass.cc
index 27e82cf..04ff59c 100644
--- a/src/lib/dns/rrclass.cc
+++ b/src/lib/dns/rrclass.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <string>
diff --git a/src/lib/dns/rrparamregistry-placeholder.cc b/src/lib/dns/rrparamregistry-placeholder.cc
index 37a676e..19363a3 100644
--- a/src/lib/dns/rrparamregistry-placeholder.cc
+++ b/src/lib/dns/rrparamregistry-placeholder.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <cassert>
#include <algorithm>
#include <cctype>
diff --git a/src/lib/dns/rrparamregistry.h b/src/lib/dns/rrparamregistry.h
index 408738a..a856423 100644
--- a/src/lib/dns/rrparamregistry.h
+++ b/src/lib/dns/rrparamregistry.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __RRPARAMREGISTRY_H
#define __RRPARAMREGISTRY_H 1
diff --git a/src/lib/dns/rrset.cc b/src/lib/dns/rrset.cc
index a7eb20c..b931bec 100644
--- a/src/lib/dns/rrset.cc
+++ b/src/lib/dns/rrset.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <algorithm>
#include <string>
#include <vector>
@@ -44,7 +42,6 @@ AbstractRRset::toText() const {
string s;
RdataIteratorPtr it = getRdataIterator();
- it->first();
if (it->isLast()) {
isc_throw(EmptyRRset, "ToText() is attempted for an empty RRset");
}
@@ -66,7 +63,6 @@ rrsetToWire(const AbstractRRset& rrset, T& output, const size_t limit) {
unsigned int n = 0;
RdataIteratorPtr it = rrset.getRdataIterator();
- it->first();
if (it->isLast()) {
isc_throw(EmptyRRset, "ToWire() is attempted for an empty RRset");
}
@@ -224,7 +220,8 @@ private:
BasicRdataIterator() {}
public:
BasicRdataIterator(const std::vector<rdata::ConstRdataPtr>& datavector) :
- datavector_(&datavector) {}
+ datavector_(&datavector), it_(datavector_->begin())
+ {}
~BasicRdataIterator() {}
virtual void first() { it_ = datavector_->begin(); }
virtual void next() { ++it_; }
diff --git a/src/lib/dns/rrset.h b/src/lib/dns/rrset.h
index 06a9050..926a58f 100644
--- a/src/lib/dns/rrset.h
+++ b/src/lib/dns/rrset.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __RRSET_H
#define __RRSET_H 1
@@ -148,7 +146,7 @@ typedef boost::shared_ptr<RdataIterator> RdataIteratorPtr;
/// "sort" and "search(find)" method?
/// - what about comparing two RRsets of the same type? If we need this,
/// should it compare rdata's as a set or as a list (i.e. compare
-/// each %rdata one by one or as a whole)? c.f. NLnet Labs' ldns
+/// each rdata one by one or as a whole)? c.f. NLnet Labs' ldns
/// (http://www.nlnetlabs.nl/projects/ldns/doc/index.html)
/// has \c ldns_rr_list_compare(), which takes the latter approach
/// (seemingly assuming the caller sorts the lists beforehand).
@@ -231,8 +229,8 @@ public:
/// \brief Updates the owner name of the \c RRset.
///
- /// \param name A reference to a \c RRTTL class object to be copied as the
- /// new TTL.
+ /// \param name A reference to a \c Name class object to be copied as the
+ /// new name.
virtual void setName(const Name& name) = 0;
/// \brief Updates the TTL of the \c RRset.
@@ -280,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;
@@ -357,7 +353,7 @@ public:
/// \endcode
///
/// This method is more strictly typed than the pointer version:
- /// If \c %rdata does not refer to the appropriate derived
+ /// If \c rdata does not refer to the appropriate derived
/// \c Rdata class
/// for the \c RRType for this \c RRset, it throws an exception of class
/// \c std::bad_cast.
@@ -385,6 +381,10 @@ public:
/// \brief Return an iterator to go through all RDATA stored in the
/// \c RRset.
///
+ /// The rdata cursor of the returned iterator will point to the first
+ /// RDATA, that is, it effectively calls \c RdataIterator::first()
+ /// internally.
+ ///
/// Using the design pattern terminology, \c getRdataIterator()
/// is an example of a <em>factory method</em>.
///
@@ -417,10 +417,10 @@ public:
/// The RDATA objects stored in the \c RRset are considered to form
/// a unidirectional list from the \c RdataIterator point of view (while
/// the actual implementation in the derived \c RRset may not use a list).
-/// We call this unidirectional list the <em>%rdata list</em>.
+/// We call this unidirectional list the <em>rdata list</em>.
///
/// An \c RdataIterator object internally (and conceptually) holds a
-/// <em>%rdata cursor</em>, which points to a specific item of the %rdata list.
+/// <em>rdata cursor</em>, which points to a specific item of the rdata list.
///
/// Note about design choice: as is clear from the interface, \c RdataIterator
/// is not compatible with the standard iterator classes.
@@ -458,29 +458,29 @@ private:
//@}
public:
- /// \brief Move the %rdata cursor to the first RDATA in the %rdata list
+ /// \brief Move the rdata cursor to the first RDATA in the rdata list
/// (if any).
///
/// This method can safely be called multiple times, even after moving
- /// the %rdata cursor forward by the \c next() method.
+ /// the rdata cursor forward by the \c next() method.
///
/// This method should never throw an exception.
virtual void first() = 0;
- /// \brief Move the %rdata cursor to the next RDATA in the %rdata list
+ /// \brief Move the rdata cursor to the next RDATA in the rdata list
/// (if any).
///
/// This method should never throw an exception.
virtual void next() = 0;
- /// \brief Return the current \c Rdata corresponding to the %rdata cursor.
+ /// \brief Return the current \c Rdata corresponding to the rdata cursor.
///
/// \return A reference to an \c rdata::::Rdata object corresponding
- /// to the %rdata cursor.
+ /// to the rdata cursor.
virtual const rdata::Rdata& getCurrent() const = 0;
- /// \brief Return true iff the %rdata cursor has reached the end of the
- /// %rdata list.
+ /// \brief Return true iff the rdata cursor has reached the end of the
+ /// rdata list.
///
/// Once this method returns \c true, the behavior of any subsequent
/// call to \c next() or \c getCurrent() is undefined.
@@ -489,8 +489,8 @@ public:
///
/// This method should never throw an exception.
///
- /// \return \c true if the %rdata cursor has reached the end of the
- /// %rdata list; otherwise \c false.
+ /// \return \c true if the rdata cursor has reached the end of the
+ /// rdata list; otherwise \c false.
virtual bool isLast() const = 0;
};
@@ -586,8 +586,8 @@ public:
/// internal copy of the \c name involves resource allocation and it
/// fails.
///
- /// \param name A reference to a \c RRTTL class object to be copied as the
- /// new TTL.
+ /// \param name A reference to a \c Name class object to be copied as the
+ /// new name.
virtual void setName(const Name& name);
/// \brief Updates the TTL of the \c RRset.
@@ -718,7 +718,7 @@ public:
void removeRRsig() { rrsig_ = RRsetPtr(); }
/// \brief Return a pointer to this RRset's RRSIG RRset
- RRsetPtr getRRsig() { return (rrsig_); }
+ RRsetPtr getRRsig() const { return (rrsig_); }
private:
RRsetPtr rrsig_;
};
diff --git a/src/lib/dns/rrsetlist.cc b/src/lib/dns/rrsetlist.cc
index 8c2637c..fcdcfbb 100644
--- a/src/lib/dns/rrsetlist.cc
+++ b/src/lib/dns/rrsetlist.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <vector>
#include <boost/foreach.hpp>
diff --git a/src/lib/dns/rrsetlist.h b/src/lib/dns/rrsetlist.h
index a07f0fc..0e05b5b 100644
--- a/src/lib/dns/rrsetlist.h
+++ b/src/lib/dns/rrsetlist.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __RRSETLIST_H
#define __RRSETLIST_H 1
@@ -75,6 +73,27 @@ private:
T it_;
};
+/// A set of RRsets.
+///
+/// \note Do not use this class unless you really understand what
+/// you're doing and you're 100% sure that this class is the best choice
+/// for your purpose.
+///
+/// Counter intuitively, this class is not a "list" of RRsets but a
+/// "set" of them; it doesn't allow multiple RRsets of the same RR
+/// type and RR class to be added at the same time. And, for that
+/// reason, adding an RRset is more expensive than you'd expect. The
+/// class name is confusing, but was named so as a result of
+/// compromise: "RRsetset" would look awkward; RRsets would be
+/// confusing (with RRset).
+///
+/// In any case, if you want a list like container of RRsets, your best choice
+/// would be \c std::vector<RRset> or \c std::list<RRset>, not this class.
+/// In fact, in many cases \c RRsetList will be a suboptimal choice.
+/// This class is defined publicly as part of libdns++ for a historical
+/// reason and is actually quite specific to a particular need for libdatasrc.
+/// If you are tempted to use it, think twice to assess if this class
+/// is really what you want. Again, in many cases the answer will be no.
class RRsetList {
private:
RRsetList(const RRsetList& source);
diff --git a/src/lib/dns/rrttl.cc b/src/lib/dns/rrttl.cc
index 2e57f61..78bb355 100644
--- a/src/lib/dns/rrttl.cc
+++ b/src/lib/dns/rrttl.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <sstream>
diff --git a/src/lib/dns/rrttl.h b/src/lib/dns/rrttl.h
index a3c07ea..695306a 100644
--- a/src/lib/dns/rrttl.h
+++ b/src/lib/dns/rrttl.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __RRTTL_H
#define __RRTTL_H 1
@@ -120,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.
///
@@ -130,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/rrtype-placeholder.h b/src/lib/dns/rrtype-placeholder.h
index fa502eb..0feccaf 100644
--- a/src/lib/dns/rrtype-placeholder.h
+++ b/src/lib/dns/rrtype-placeholder.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: rrtype.h 534 2010-01-26 23:08:23Z jinmei $
-
#ifndef __RRTYPE_H
#define __RRTYPE_H 1
diff --git a/src/lib/dns/rrtype.cc b/src/lib/dns/rrtype.cc
index 664bebd..f8f1400 100644
--- a/src/lib/dns/rrtype.cc
+++ b/src/lib/dns/rrtype.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <string>
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index b7ff7fa..2af7e10 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -37,17 +37,20 @@ run_unittests_SOURCES += rdata_dnskey_unittest.cc
run_unittests_SOURCES += rdata_ds_unittest.cc
run_unittests_SOURCES += rdata_nsec_unittest.cc
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_tsig_unittest.cc
run_unittests_SOURCES += rrset_unittest.cc rrsetlist_unittest.cc
run_unittests_SOURCES += question_unittest.cc
run_unittests_SOURCES += rrparamregistry_unittest.cc
+run_unittests_SOURCES += masterload_unittest.cc
run_unittests_SOURCES += message_unittest.cc
run_unittests_SOURCES += base32hex_unittest.cc
run_unittests_SOURCES += base64_unittest.cc
run_unittests_SOURCES += hex_unittest.cc
run_unittests_SOURCES += sha1_unittest.cc
-run_unittests_SOURCES += tsig_unittest.cc
+run_unittests_SOURCES += tsigkey_unittest.cc
run_unittests_SOURCES += run_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
diff --git a/src/lib/dns/tests/base32hex_unittest.cc b/src/lib/dns/tests/base32hex_unittest.cc
index b106533..253d310 100644
--- a/src/lib/dns/tests/base32hex_unittest.cc
+++ b/src/lib/dns/tests/base32hex_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <cctype>
diff --git a/src/lib/dns/tests/base64_unittest.cc b/src/lib/dns/tests/base64_unittest.cc
index d3d5491..7333793 100644
--- a/src/lib/dns/tests/base64_unittest.cc
+++ b/src/lib/dns/tests/base64_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <string>
#include <utility>
#include <vector>
diff --git a/src/lib/dns/tests/buffer_unittest.cc b/src/lib/dns/tests/buffer_unittest.cc
index 1459ac9..2fe5a10 100644
--- a/src/lib/dns/tests/buffer_unittest.cc
+++ b/src/lib/dns/tests/buffer_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <exceptions/exceptions.h>
#include <dns/buffer.h>
@@ -126,10 +124,16 @@ TEST_F(BufferTest, outputBufferWriteat) {
obuffer.writeUint32(data32);
expected_size += sizeof(data32);
+ // overwrite 2nd byte
+ obuffer.writeUint8At(4, 1);
+ EXPECT_EQ(expected_size, obuffer.getLength()); // length shouldn't change
+ const uint8_t* cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_EQ(4, *(cp + 1));
+
// overwrite 2nd and 3rd bytes
obuffer.writeUint16At(data16, 1);
EXPECT_EQ(expected_size, obuffer.getLength()); // length shouldn't change
- const uint8_t* cp = static_cast<const uint8_t*>(obuffer.getData());
+ cp = static_cast<const uint8_t*>(obuffer.getData());
EXPECT_EQ(2, *(cp + 1));
EXPECT_EQ(3, *(cp + 2));
@@ -140,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),
diff --git a/src/lib/dns/tests/dnssectime_unittest.cc b/src/lib/dns/tests/dnssectime_unittest.cc
index 68b4f85..b2708cc 100644
--- a/src/lib/dns/tests/dnssectime_unittest.cc
+++ b/src/lib/dns/tests/dnssectime_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <string>
#include <time.h>
@@ -25,48 +23,141 @@
using namespace std;
using namespace isc::dns;
+// See dnssectime.cc
+namespace isc {
+namespace dns {
+namespace dnssectime {
+namespace detail {
+extern int64_t (*gettimeFunction)();
+}
+}
+}
+}
+
namespace {
-TEST(DNSSECTimeTest, fromText) {
+class DNSSECTimeTest : public ::testing::Test {
+protected:
+ ~DNSSECTimeTest() {
+ dnssectime::detail::gettimeFunction = NULL;
+ }
+};
+
+TEST_F(DNSSECTimeTest, fromText) {
+ // In most cases (in practice) the 32-bit and 64-bit versions should
+ // behave identically, so we'll mainly test the 32-bit version, which
+ // will be more commonly used in actual code (because many of the wire
+ // format time field are 32-bit). The subtle cases where these two
+ // return different values will be tested at the end of this test case.
+
// These are bogus and should be rejected
- EXPECT_THROW(timeFromText("2011 101120000"), InvalidTime);
- EXPECT_THROW(timeFromText("201101011200-0"), InvalidTime);
+ EXPECT_THROW(timeFromText32("2011 101120000"), InvalidTime);
+ EXPECT_THROW(timeFromText32("201101011200-0"), InvalidTime);
- // Short length
- EXPECT_THROW(timeFromText("20100223"), InvalidTime);
+ // Short length (or "decimal integer" version of representation;
+ // it's valid per RFC4034, but is not supported in this implementation)
+ EXPECT_THROW(timeFromText32("20100223"), InvalidTime);
// Leap year checks
- EXPECT_THROW(timeFromText("20110229120000"), InvalidTime);
- EXPECT_THROW(timeFromText("21000229120000"), InvalidTime);
- EXPECT_NO_THROW(timeFromText("20000229120000"));
- EXPECT_NO_THROW(timeFromText("20120229120000"));
+ EXPECT_THROW(timeFromText32("20110229120000"), InvalidTime);
+ EXPECT_THROW(timeFromText32("21000229120000"), InvalidTime);
+ EXPECT_NO_THROW(timeFromText32("20000229120000"));
+ EXPECT_NO_THROW(timeFromText32("20120229120000"));
// unusual case: this implementation allows SS=60 for "leap seconds"
- EXPECT_NO_THROW(timeFromText("20110101120060"));
+ EXPECT_NO_THROW(timeFromText32("20110101120060"));
// Out of range parameters
- EXPECT_THROW(timeFromText("19100223214617"), InvalidTime); // YY<1970
- EXPECT_THROW(timeFromText("20110001120000"), InvalidTime); // MM=00
- EXPECT_THROW(timeFromText("20111301120000"), InvalidTime); // MM=13
- EXPECT_THROW(timeFromText("20110100120000"), InvalidTime); // DD=00
- EXPECT_THROW(timeFromText("20110132120000"), InvalidTime); // DD=32
- EXPECT_THROW(timeFromText("20110431120000"), InvalidTime); // 'Apr31'
- EXPECT_THROW(timeFromText("20110101250000"), InvalidTime); // HH=25
- EXPECT_THROW(timeFromText("20110101126000"), InvalidTime); // mm=60
- EXPECT_THROW(timeFromText("20110101120061"), InvalidTime); // SS=61
+ EXPECT_THROW(timeFromText32("19100223214617"), InvalidTime); // YY<1970
+ EXPECT_THROW(timeFromText32("20110001120000"), InvalidTime); // MM=00
+ EXPECT_THROW(timeFromText32("20111301120000"), InvalidTime); // MM=13
+ EXPECT_THROW(timeFromText32("20110100120000"), InvalidTime); // DD=00
+ EXPECT_THROW(timeFromText32("20110132120000"), InvalidTime); // DD=32
+ EXPECT_THROW(timeFromText32("20110431120000"), InvalidTime); // 'Apr31'
+ EXPECT_THROW(timeFromText32("20110101250000"), InvalidTime); // HH=25
+ EXPECT_THROW(timeFromText32("20110101126000"), InvalidTime); // mm=60
+ EXPECT_THROW(timeFromText32("20110101120061"), InvalidTime); // SS=61
+
+ // Feb 7, 06:28:15 UTC 2106 is the possible maximum time that can be
+ // represented as an unsigned 32bit integer without overflow.
+ EXPECT_EQ(4294967295LU, timeFromText32("21060207062815"));
+
+ // After that, timeFromText32() should start returning the second count
+ // modulo 2^32.
+ EXPECT_EQ(0, timeFromText32("21060207062816"));
+ EXPECT_EQ(10, timeFromText32("21060207062826"));
+
+ // On the other hand, the 64-bit version should return monotonically
+ // increasing counters.
+ EXPECT_EQ(4294967296LL, timeFromText64("21060207062816"));
+ EXPECT_EQ(4294967306LL, timeFromText64("21060207062826"));
}
-TEST(DNSSECTimeTest, toText) {
- EXPECT_EQ("19700101000000", timeToText(0));
- EXPECT_EQ("20100311233000", timeToText(1268350200));
+// This helper templated function tells timeToText32 a faked current time.
+// The template parameter is that faked time in the form of int64_t seconds
+// since epoch.
+template <int64_t NOW>
+int64_t
+testGetTime() {
+ return (NOW);
}
-TEST(DNSSECTimeTest, overflow) {
+// Seconds since epoch for the year 10K eve. Commonly used in some tests
+// below.
+const uint64_t YEAR10K_EVE = 253402300799LL;
+
+TEST_F(DNSSECTimeTest, toText) {
+ // Check a basic case with the default (normal) gettimeFunction
+ // based on the "real current time".
+ // Note: this will fail after year 2078, but at that point we won't use
+ // this program anyway:-)
+ EXPECT_EQ("20100311233000", timeToText32(1268350200));
+
+ // Set the current time to: Feb 18 09:04:14 UTC 2012 (an arbitrary choice
+ // in the range of the first half of uint32 since epoch).
+ dnssectime::detail::gettimeFunction = testGetTime<1329555854LL>;
+
+ // Test the "year 2038" problem.
+ // Check the result of toText() for "INT_MIN" in int32_t. It's in the
+ // 68-year range from the faked current time, so the result should be
+ // in year 2038, instead of 1901.
+ EXPECT_EQ("20380119031408", timeToText64(0x80000000L));
+ EXPECT_EQ("20380119031408", timeToText32(0x80000000L));
+
+ // A controversial case: what should we do with "-1"? It's out of range
+ // in future, but according to RFC time before epoch doesn't seem to be
+ // considered "in-range" either. Our toText() implementation handles
+ // this range as a special case and always treats them as future time
+ // until year 2038. This won't be a real issue in practice, though,
+ // since such too large values won't be used in actual deployment by then.
+ EXPECT_EQ("21060207062815", timeToText32(0xffffffffL));
+
+ // After the singular point of year 2038, the first half of uint32 can
+ // point to a future time.
+ // Set the current time to: Apr 1 00:00:00 UTC 2038:
+ dnssectime::detail::gettimeFunction = testGetTime<2153692800LL>;
+ // then time "10" is Feb 7 06:28:26 UTC 2106
+ EXPECT_EQ("21060207062826", timeToText32(10));
+ // in 64-bit, it's 2^32 + 10
+ EXPECT_EQ("21060207062826", timeToText64(0x10000000aLL));
+
+ // After year 2106, the upper half of uint32 can point to past time
+ // (as it should).
+ dnssectime::detail::gettimeFunction = testGetTime<0x10000000aLL>;
+ EXPECT_EQ("21060207062815", timeToText32(0xffffffffL));
+
+ // Try very large time value. Actually it's the possible farthest time
+ // that can be represented in the form of YYYYMMDDHHmmSS.
+ EXPECT_EQ("99991231235959", timeToText64(YEAR10K_EVE));
+ dnssectime::detail::gettimeFunction = testGetTime<YEAR10K_EVE - 10>;
+ EXPECT_EQ("99991231235959", timeToText32(4294197631LU));
+}
+
+TEST_F(DNSSECTimeTest, overflow) {
// Jan 1, Year 10,000.
- if (sizeof(time_t) > 4) {
- EXPECT_THROW(timeToText(static_cast<time_t>(253402300800LL)),
- InvalidTime);
- }
+ EXPECT_THROW(timeToText64(253402300800LL), InvalidTime);
+ dnssectime::detail::gettimeFunction = testGetTime<YEAR10K_EVE - 10>;
+ EXPECT_THROW(timeToText32(4294197632LU), InvalidTime);
}
}
diff --git a/src/lib/dns/tests/edns_unittest.cc b/src/lib/dns/tests/edns_unittest.cc
index 2663e24..fb9a7c2 100644
--- a/src/lib/dns/tests/edns_unittest.cc
+++ b/src/lib/dns/tests/edns_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <sstream>
#include <exceptions/exceptions.h>
diff --git a/src/lib/dns/tests/hex_unittest.cc b/src/lib/dns/tests/hex_unittest.cc
index 3237940..6f82b17 100644
--- a/src/lib/dns/tests/hex_unittest.cc
+++ b/src/lib/dns/tests/hex_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: rrtype_unittest.cc 476 2010-01-19 00:29:28Z jinmei $
-
#include <stdint.h>
#include <vector>
diff --git a/src/lib/dns/tests/masterload_unittest.cc b/src/lib/dns/tests/masterload_unittest.cc
new file mode 100644
index 0000000..c47debd
--- /dev/null
+++ b/src/lib/dns/tests/masterload_unittest.cc
@@ -0,0 +1,268 @@
+// 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 <functional>
+#include <ios>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <boost/bind.hpp>
+
+#include <gtest/gtest.h>
+
+#include <dns/masterload.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+using namespace std;
+using namespace isc::dns;
+
+namespace {
+// A callback functor for masterLoad() commonly used for the following tests.
+class TestCallback : public unary_function<ConstRRsetPtr, void> {
+public:
+ TestCallback(vector<ConstRRsetPtr>& rrsets) : rrsets_(rrsets) {}
+ void operator()(ConstRRsetPtr rrset) {
+ rrsets_.push_back(rrset);
+ }
+private:
+ vector<ConstRRsetPtr>& rrsets_;
+};
+
+// A function version of TestCallback.
+void
+testCallback(ConstRRsetPtr rrset, vector<ConstRRsetPtr>* rrsets) {
+ rrsets->push_back(rrset);
+}
+
+class MasterLoadTest : public ::testing::Test {
+protected:
+ MasterLoadTest() : origin("example.com"), zclass(RRClass::IN()),
+ callback(results) {}
+public:
+ void rrsetCallback(ConstRRsetPtr rrset) {
+ results.push_back(rrset);
+ }
+protected:
+ Name origin;
+ RRClass zclass;
+ stringstream rr_stream;
+ vector<ConstRRsetPtr> results;
+ TestCallback callback;
+};
+
+// Commonly used test RRs
+const char* const txt_rr = "example.com. 3600 IN TXT \"test data\"\n";
+const char* const a_rr1 = "www.example.com. 60 IN A 192.0.2.1\n";
+const char* const a_rr2 = "www.example.com. 60 IN A 192.0.2.2\n";
+const char* const a_rr3 = "ftp.example.com. 60 IN A 192.0.2.3\n";
+// multi-field RR case
+const char* const soa_rr = "example.com. 7200 IN SOA . . 0 0 0 0 0\n";
+
+TEST_F(MasterLoadTest, loadRRs) {
+ // a simple case: loading 3 RRs, each consists of a single RRset.
+ rr_stream << txt_rr << a_rr1 << soa_rr;
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(3, results.size());
+ EXPECT_EQ(txt_rr, results[0]->toText());
+ EXPECT_EQ(a_rr1, results[1]->toText());
+ EXPECT_EQ(soa_rr, results[2]->toText());
+}
+
+TEST_F(MasterLoadTest, loadWithFunctionCallback) {
+ // The same test as loadRRs but using a normal function (not a functor
+ // object)
+ rr_stream << txt_rr << a_rr1 << soa_rr;
+ masterLoad(rr_stream, origin, zclass,
+ bind2nd(ptr_fun(testCallback), &results));
+ ASSERT_EQ(3, results.size());
+ EXPECT_EQ(txt_rr, results[0]->toText());
+ EXPECT_EQ(a_rr1, results[1]->toText());
+ EXPECT_EQ(soa_rr, results[2]->toText());
+}
+
+TEST_F(MasterLoadTest, loadWithMemFunctionCallback) {
+ // The same test as loadRRs but using a class member function (with a
+ // help of Boost.bind)
+ rr_stream << txt_rr << a_rr1 << soa_rr;
+ masterLoad(rr_stream, origin, zclass,
+ boost::bind(&MasterLoadTest::rrsetCallback, this, _1));
+ ASSERT_EQ(3, results.size());
+ EXPECT_EQ(txt_rr, results[0]->toText());
+ EXPECT_EQ(a_rr1, results[1]->toText());
+ EXPECT_EQ(soa_rr, results[2]->toText());
+}
+
+TEST_F(MasterLoadTest, loadComments) {
+ rr_stream << ";; comment line, should be skipped\n"
+ << "\n" // blank line (should be skipped)
+ << txt_rr;
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(txt_rr, results[0]->toText());
+}
+
+TEST_F(MasterLoadTest, loadRRset) {
+ // load an RRset containing two RRs
+ rr_stream << a_rr1 << a_rr2;
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(string(a_rr1) + string(a_rr2), results[0]->toText());
+}
+
+TEST_F(MasterLoadTest, loadRRsetsOfSameType) {
+ // load two RRsets with the same RR type and different owner names.
+ // the loader must distinguish them as separate RRsets.
+ rr_stream << a_rr1 << a_rr3;
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(2, results.size());
+ EXPECT_EQ(a_rr1, results[0]->toText());
+ EXPECT_EQ(a_rr3, results[1]->toText());
+}
+
+TEST_F(MasterLoadTest, loadRRsetsInterleaved) {
+ // two RRs that belongs to the same RRset (rr1 and rr2) are interleaved
+ // by another. This is an unexpected case for this loader, but it's
+ // not considered an error. The loader will simply treat them separate
+ // RRsets.
+ rr_stream << a_rr1 << a_rr3 << a_rr2;
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(3, results.size());
+ EXPECT_EQ(a_rr1, results[0]->toText());
+ EXPECT_EQ(a_rr3, results[1]->toText());
+ EXPECT_EQ(a_rr2, results[2]->toText());
+}
+
+TEST_F(MasterLoadTest, loadWithNoEOF) {
+ // the input stream doesn't end with a new line (and the following blank
+ // line). It should be accepted.
+ string rr_string(a_rr1);
+ rr_string.erase(rr_string.end() - 1);
+ rr_stream << rr_string;
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(a_rr1, results[0]->toText());
+}
+
+TEST_F(MasterLoadTest, loadEmpty) {
+ // an unusual case: empty input. load must succeed with an empty result.
+ masterLoad(rr_stream, origin, zclass, callback);
+ EXPECT_EQ(0, results.size());
+}
+
+TEST_F(MasterLoadTest, loadWithBeginningSpace) {
+ rr_stream << " " << a_rr1;
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+ MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadWithBeginningTab) {
+ rr_stream << "\t" << a_rr1;
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+ MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadInvalidRRClass) {
+ rr_stream << "example.com. 3600 CH TXT \"test text\"";
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+ MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadOutOfZoneData) {
+ rr_stream << "example.org. 3600 IN A 192.0.2.255";
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+ MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadNonAtopSOA) {
+ // SOA's owner name must be zone's origin.
+ rr_stream << "soa.example.com. 3600 IN SOA . . 0 0 0 0 0";
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+ MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadBadRRText) {
+ rr_stream << "example..com. 3600 IN A 192.0.2.1"; // bad owner name
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+ MasterLoadError);
+
+ // currently we only support numeric TTLs
+ stringstream rr_stream2("example.com. 1D IN A 192.0.2.1");
+ EXPECT_THROW(masterLoad(rr_stream2, origin, zclass, callback),
+ MasterLoadError);
+
+ // bad RR class text
+ stringstream rr_stream3("example.com. 3600 BAD A 192.0.2.1");
+ EXPECT_THROW(masterLoad(rr_stream3, origin, zclass, callback),
+ MasterLoadError);
+
+ // bad RR type text
+ stringstream rr_stream4("example.com. 3600 IN BAD 192.0.2.1");
+ EXPECT_THROW(masterLoad(rr_stream4, origin, zclass, callback),
+ MasterLoadError);
+
+ // bad RDATA text
+ stringstream rr_stream5("example.com. 3600 IN A 2001:db8::1");
+ EXPECT_THROW(masterLoad(rr_stream5, origin, zclass, callback),
+ MasterLoadError);
+
+ // incomplete RR text
+ stringstream rr_stream6("example.com. 3600 IN A");
+ EXPECT_THROW(masterLoad(rr_stream6, origin, zclass, callback),
+ MasterLoadError);
+
+ // owner name is not absolute
+ stringstream rr_stream7("example.com 3600 IN A 192.0.2.1");
+ EXPECT_THROW(masterLoad(rr_stream7, origin, zclass, callback),
+ MasterLoadError);
+}
+
+// This is a helper callback to test the case the input stream becomes bad
+// in the middle of processing.
+class StreamInvalidator : public unary_function<ConstRRsetPtr, void> {
+public:
+ StreamInvalidator(stringstream& ss) : ss_(ss) {}
+ void operator()(ConstRRsetPtr) {
+ ss_.setstate(ios::badbit);
+ }
+private:
+ stringstream& ss_;
+};
+
+TEST_F(MasterLoadTest, loadBadStream) {
+ rr_stream << txt_rr << a_rr1;
+ StreamInvalidator invalidator(rr_stream);
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, invalidator),
+ MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadFromFile) {
+ // The main parser is shared with the stream version, so we simply test
+ // file I/O specific parts.
+ masterLoad(TEST_DATA_SRCDIR "/masterload.txt", origin, zclass, callback);
+ ASSERT_EQ(2, results.size());
+ EXPECT_EQ(txt_rr, results[0]->toText());
+ EXPECT_EQ(string(a_rr1) + string(a_rr2), results[1]->toText());
+
+ // NULL file name. Should result in exception.
+ EXPECT_THROW(masterLoad(NULL, origin, zclass, callback), MasterLoadError);
+
+ // Non existent file name. Ditto.
+ EXPECT_THROW(masterLoad(TEST_DATA_BUILDDIR "/notexistent.txt", origin,
+ zclass, callback), MasterLoadError);
+}
+} // end namespace
diff --git a/src/lib/dns/tests/message_unittest.cc b/src/lib/dns/tests/message_unittest.cc
index b59403e..92adbc9 100644
--- a/src/lib/dns/tests/message_unittest.cc
+++ b/src/lib/dns/tests/message_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <exceptions/exceptions.h>
#include <dns/buffer.h>
@@ -35,6 +33,7 @@
using isc::UnitTestUtil;
using namespace std;
+using namespace isc;
using namespace isc::dns;
using namespace isc::dns::rdata;
@@ -51,25 +50,46 @@ using namespace isc::dns::rdata;
//
const uint16_t Message::DEFAULT_MAX_UDPSIZE;
+const Name test_name("test.example.com");
namespace {
class MessageTest : public ::testing::Test {
protected:
MessageTest() : obuffer(0), renderer(obuffer),
message_parse(Message::PARSE),
- message_render(Message::RENDER)
- {}
+ message_render(Message::RENDER),
+ bogus_section(static_cast<Message::Section>(
+ Message::SECTION_ADDITIONAL + 1))
+ {
+ rrset_a = RRsetPtr(new RRset(test_name, RRClass::IN(),
+ RRType::A(), RRTTL(3600)));
+ rrset_a->addRdata(in::A("192.0.2.1"));
+ rrset_a->addRdata(in::A("192.0.2.2"));
+
+ rrset_aaaa = RRsetPtr(new RRset(test_name, RRClass::IN(),
+ RRType::AAAA(), RRTTL(3600)));
+ rrset_aaaa->addRdata(in::AAAA("2001:db8::1234"));
+
+ rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(),
+ RRType::RRSIG(), RRTTL(3600)));
+ rrset_rrsig->addRdata(generic::RRSIG("AAAA 5 3 7200 20100322084538 "
+ "20100220084538 1 example.com "
+ "FAKEFAKEFAKEFAKE"));
+ rrset_aaaa->addRRsig(rrset_rrsig);
+ }
static Question factoryFromFile(const char* datafile);
OutputBuffer obuffer;
MessageRenderer renderer;
Message message_parse;
Message message_render;
+ const Message::Section bogus_section;
+ RRsetPtr rrset_a; // A RRset with two RDATAs
+ RRsetPtr rrset_aaaa; // AAAA RRset with one RDATA with RRSIG
+ RRsetPtr rrset_rrsig; // RRSIG for the AAAA RRset
static void factoryFromFile(Message& message, const char* datafile);
};
-const Name test_name("test.example.com");
-
void
MessageTest::factoryFromFile(Message& message, const char* datafile) {
std::vector<unsigned char> data;
@@ -79,6 +99,52 @@ MessageTest::factoryFromFile(Message& message, const char* datafile) {
message.fromWire(buffer);
}
+TEST_F(MessageTest, headerFlag) {
+ // by default no flag is set
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_QR));
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_AA));
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_TC));
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_RD));
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_RA));
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_AD));
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_CD));
+
+ // set operation: by default it will be on
+ message_render.setHeaderFlag(Message::HEADERFLAG_QR);
+ EXPECT_TRUE(message_render.getHeaderFlag(Message::HEADERFLAG_QR));
+
+ // it can be set to on explicitly, too
+ message_render.setHeaderFlag(Message::HEADERFLAG_AA, true);
+ EXPECT_TRUE(message_render.getHeaderFlag(Message::HEADERFLAG_AA));
+
+ // the bit can also be cleared
+ message_render.setHeaderFlag(Message::HEADERFLAG_AA, false);
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_AA));
+
+ // Invalid flag values
+ EXPECT_THROW(message_render.setHeaderFlag(
+ static_cast<Message::HeaderFlag>(0)), InvalidParameter);
+ EXPECT_THROW(message_render.setHeaderFlag(
+ static_cast<Message::HeaderFlag>(0x7000)),
+ InvalidParameter);
+ EXPECT_THROW(message_render.setHeaderFlag(
+ static_cast<Message::HeaderFlag>(0x0800)),
+ InvalidParameter);
+ EXPECT_THROW(message_render.setHeaderFlag(
+ static_cast<Message::HeaderFlag>(0x0040)),
+ InvalidParameter);
+ EXPECT_THROW(message_render.setHeaderFlag(
+ static_cast<Message::HeaderFlag>(0x10000)),
+ InvalidParameter);
+ EXPECT_THROW(message_render.setHeaderFlag(
+ static_cast<Message::HeaderFlag>(0x80000000)),
+ InvalidParameter);
+
+ // set operation isn't allowed in the parse mode.
+ EXPECT_THROW(message_parse.setHeaderFlag(Message::HEADERFLAG_QR),
+ InvalidMessageOperation);
+}
+
TEST_F(MessageTest, getEDNS) {
EXPECT_FALSE(message_parse.getEDNS()); // by default EDNS isn't set
@@ -99,32 +165,303 @@ TEST_F(MessageTest, setEDNS) {
EXPECT_EQ(edns, message_render.getEDNS());
}
+TEST_F(MessageTest, getRRCount) {
+ // by default all counters should be 0
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL));
+
+ message_render.addQuestion(Question(Name("test.example.com"),
+ RRClass::IN(), RRType::A()));
+ EXPECT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION));
+
+ // rrset_a contains two RRs
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+ EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER));
+
+ // parse a message containing a Question and EDNS OPT RR.
+ // OPT shouldn't be counted as normal RR, so result of getRRCount
+ // shouldn't change.
+ factoryFromFile(message_parse, "message_fromWire11.wire");
+ EXPECT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL));
+
+ // out-of-band section ID
+ EXPECT_THROW(message_parse.getRRCount(bogus_section), OutOfRange);
+}
+
+TEST_F(MessageTest, addRRset) {
+ // default case
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+ EXPECT_EQ(rrset_a,
+ *message_render.beginSection(Message::SECTION_ANSWER));
+ EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER));
+
+ // signed RRset, default case
+ message_render.clear(Message::RENDER);
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_aaaa);
+ EXPECT_EQ(rrset_aaaa,
+ *message_render.beginSection(Message::SECTION_ANSWER));
+ EXPECT_EQ(1, message_render.getRRCount(Message::SECTION_ANSWER));
+
+ // signed RRset, add with the RRSIG. getRRCount() should return 2
+ message_render.clear(Message::RENDER);
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_aaaa, true);
+ EXPECT_EQ(rrset_aaaa,
+ *message_render.beginSection(Message::SECTION_ANSWER));
+ EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER));
+
+ // signed RRset, add explicitly without RRSIG.
+ message_render.clear(Message::RENDER);
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_aaaa, false);
+ EXPECT_EQ(rrset_aaaa,
+ *message_render.beginSection(Message::SECTION_ANSWER));
+ EXPECT_EQ(1, message_render.getRRCount(Message::SECTION_ANSWER));
+}
+
+TEST_F(MessageTest, badAddRRset) {
+ // addRRset() isn't allowed in the parse mode.
+ EXPECT_THROW(message_parse.addRRset(Message::SECTION_ANSWER,
+ rrset_a), InvalidMessageOperation);
+ // out-of-band section ID
+ EXPECT_THROW(message_render.addRRset(bogus_section, rrset_a), OutOfRange);
+}
+
+TEST_F(MessageTest, hasRRset) {
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+ EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+ // section doesn't match
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::A()));
+ // name doesn't match
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER,
+ Name("nomatch.example"),
+ RRClass::IN(), RRType::A()));
+ // RR class doesn't match
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::CH(), RRType::A()));
+ // RR type doesn't match
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::AAAA()));
+
+ // out-of-band section ID
+ EXPECT_THROW(message_render.hasRRset(bogus_section, test_name,
+ RRClass::IN(), RRType::A()),
+ OutOfRange);
+
+ // Repeat the checks having created an RRset of the appropriate type.
+
+ RRsetPtr rrs1(new RRset(test_name, RRClass::IN(), RRType::A(), RRTTL(60)));
+ EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, rrs1));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, rrs1));
+
+ RRsetPtr rrs2(new RRset(Name("nomatch.example"), RRClass::IN(), RRType::A(),
+ RRTTL(5)));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs2));
+
+ RRsetPtr rrs3(new RRset(test_name, RRClass::CH(), RRType::A(), RRTTL(60)));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs3));
+
+ RRsetPtr rrs4(new RRset(test_name, RRClass::IN(), RRType::AAAA(), RRTTL(5)));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs4));
+
+ RRsetPtr rrs5(new RRset(test_name, RRClass::IN(), RRType::AAAA(), RRTTL(5)));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs4));
+
+ EXPECT_THROW(message_render.hasRRset(bogus_section, rrs1), OutOfRange);
+}
+
+TEST_F(MessageTest, removeRRset) {
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_aaaa);
+ EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ EXPECT_EQ(3, message_render.getRRCount(Message::SECTION_ANSWER));
+
+ // Locate the AAAA RRset and remove it; this has one RR in it.
+ RRsetIterator i = message_render.beginSection(Message::SECTION_ANSWER);
+ if ((*i)->getType() == RRType::A()) {
+ ++i;
+ }
+ EXPECT_EQ(RRType::AAAA(), (*i)->getType());
+ message_render.removeRRset(Message::SECTION_ANSWER, i);
+
+ EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER));
+}
+
+TEST_F(MessageTest, clearQuestionSection) {
+ QuestionPtr q(new Question(Name("www.example.com"), RRClass::IN(),
+ RRType::A()));
+ message_render.addQuestion(q);
+ ASSERT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION));
+
+ message_render.clearSection(Message::SECTION_QUESTION);
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_QUESTION));
+}
+
+
+TEST_F(MessageTest, clearAnswerSection) {
+ // Add two RRsets, check they are present, clear the section,
+ // check if they are gone.
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_aaaa);
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ ASSERT_EQ(3, message_render.getRRCount(Message::SECTION_ANSWER));
+
+ message_render.clearSection(Message::SECTION_ANSWER);
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ANSWER));
+}
+
+TEST_F(MessageTest, clearAuthoritySection) {
+ // Add two RRsets, check they are present, clear the section,
+ // check if they are gone.
+ message_render.addRRset(Message::SECTION_AUTHORITY, rrset_a);
+ message_render.addRRset(Message::SECTION_AUTHORITY, rrset_aaaa);
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+ RRClass::IN(), RRType::A()));
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ ASSERT_EQ(3, message_render.getRRCount(Message::SECTION_AUTHORITY));
+
+ message_render.clearSection(Message::SECTION_AUTHORITY);
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_AUTHORITY));
+}
+
+TEST_F(MessageTest, clearAdditionalSection) {
+ // Add two RRsets, check they are present, clear the section,
+ // check if they are gone.
+ message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_a);
+ message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_aaaa);
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::A()));
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ ASSERT_EQ(3, message_render.getRRCount(Message::SECTION_ADDITIONAL));
+
+ message_render.clearSection(Message::SECTION_ADDITIONAL);
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL));
+}
+
+
+TEST_F(MessageTest, badBeginSection) {
+ // valid cases are tested via other tests
+ EXPECT_THROW(message_render.beginSection(Message::SECTION_QUESTION),
+ InvalidMessageSection);
+ EXPECT_THROW(message_render.beginSection(bogus_section), OutOfRange);
+}
+
+TEST_F(MessageTest, badEndSection) {
+ // valid cases are tested via other tests
+ EXPECT_THROW(message_render.endSection(Message::SECTION_QUESTION),
+ InvalidMessageSection);
+ EXPECT_THROW(message_render.endSection(bogus_section), OutOfRange);
+}
+
+TEST_F(MessageTest, appendSection) {
+ Message target(Message::RENDER);
+
+ // Section check
+ EXPECT_THROW(target.appendSection(bogus_section, message_render),
+ OutOfRange);
+
+ // Make sure nothing is copied if there is nothing to copy
+ target.appendSection(Message::SECTION_QUESTION, message_render);
+ EXPECT_EQ(0, target.getRRCount(Message::SECTION_QUESTION));
+ target.appendSection(Message::SECTION_ANSWER, message_render);
+ EXPECT_EQ(0, target.getRRCount(Message::SECTION_ANSWER));
+ target.appendSection(Message::SECTION_AUTHORITY, message_render);
+ EXPECT_EQ(0, target.getRRCount(Message::SECTION_AUTHORITY));
+ target.appendSection(Message::SECTION_ADDITIONAL, message_render);
+ EXPECT_EQ(0, target.getRRCount(Message::SECTION_ADDITIONAL));
+
+ // Now add some data, copy again, and see if it got added
+ message_render.addQuestion(Question(Name("test.example.com"),
+ RRClass::IN(), RRType::A()));
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+ message_render.addRRset(Message::SECTION_AUTHORITY, rrset_a);
+ message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_a);
+ message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_aaaa);
+
+ target.appendSection(Message::SECTION_QUESTION, message_render);
+ EXPECT_EQ(1, target.getRRCount(Message::SECTION_QUESTION));
+
+ target.appendSection(Message::SECTION_ANSWER, message_render);
+ EXPECT_EQ(2, target.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+
+ target.appendSection(Message::SECTION_AUTHORITY, message_render);
+ EXPECT_EQ(2, target.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_AUTHORITY, test_name,
+ RRClass::IN(), RRType::A()));
+
+ target.appendSection(Message::SECTION_ADDITIONAL, message_render);
+ EXPECT_EQ(3, target.getRRCount(Message::SECTION_ADDITIONAL));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::AAAA()));
+
+ // One more test, test to see if the section gets added, not replaced
+ Message source2(Message::RENDER);
+ source2.addRRset(Message::SECTION_ANSWER, rrset_aaaa);
+ target.appendSection(Message::SECTION_ANSWER, source2);
+ EXPECT_EQ(3, target.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::AAAA()));
+
+}
+
TEST_F(MessageTest, fromWire) {
factoryFromFile(message_parse, "message_fromWire1");
EXPECT_EQ(0x1035, message_parse.getQid());
EXPECT_EQ(Opcode::QUERY(), message_parse.getOpcode());
EXPECT_EQ(Rcode::NOERROR(), message_parse.getRcode());
- EXPECT_TRUE(message_parse.getHeaderFlag(MessageFlag::QR()));
- EXPECT_TRUE(message_parse.getHeaderFlag(MessageFlag::RD()));
- EXPECT_TRUE(message_parse.getHeaderFlag(MessageFlag::AA()));
+ EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_QR));
+ EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_RD));
+ EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_AA));
QuestionPtr q = *message_parse.beginQuestion();
EXPECT_EQ(test_name, q->getName());
EXPECT_EQ(RRType::A(), q->getType());
EXPECT_EQ(RRClass::IN(), q->getClass());
- EXPECT_EQ(1, message_parse.getRRCount(Section::QUESTION()));
- EXPECT_EQ(2, message_parse.getRRCount(Section::ANSWER()));
- EXPECT_EQ(0, message_parse.getRRCount(Section::AUTHORITY()));
- EXPECT_EQ(0, message_parse.getRRCount(Section::ADDITIONAL()));
+ EXPECT_EQ(1, message_parse.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(2, message_parse.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, message_parse.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, message_parse.getRRCount(Message::SECTION_ADDITIONAL));
- RRsetPtr rrset = *message_parse.beginSection(Section::ANSWER());
+ RRsetPtr rrset = *message_parse.beginSection(Message::SECTION_ANSWER);
EXPECT_EQ(test_name, rrset->getName());
EXPECT_EQ(RRType::A(), rrset->getType());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
// TTL should be 3600, even though that of the 2nd RR is 7200
EXPECT_EQ(RRTTL(3600), rrset->getTTL());
RdataIteratorPtr it = rrset->getRdataIterator();
- it->first();
EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
it->next();
EXPECT_EQ("192.0.2.2", it->getCurrent().toText());
@@ -157,21 +494,17 @@ TEST_F(MessageTest, toWire) {
message_render.setQid(0x1035);
message_render.setOpcode(Opcode::QUERY());
message_render.setRcode(Rcode::NOERROR());
- message_render.setHeaderFlag(MessageFlag::QR());
- message_render.setHeaderFlag(MessageFlag::RD());
- message_render.setHeaderFlag(MessageFlag::AA());
+ message_render.setHeaderFlag(Message::HEADERFLAG_QR, true);
+ message_render.setHeaderFlag(Message::HEADERFLAG_RD, true);
+ message_render.setHeaderFlag(Message::HEADERFLAG_AA, true);
message_render.addQuestion(Question(Name("test.example.com"), RRClass::IN(),
RRType::A()));
- RRsetPtr rrset = RRsetPtr(new RRset(Name("test.example.com"), RRClass::IN(),
- RRType::A(), RRTTL(3600)));
- rrset->addRdata(in::A("192.0.2.1"));
- rrset->addRdata(in::A("192.0.2.2"));
- message_render.addRRset(Section::ANSWER(), rrset);
-
- EXPECT_EQ(1, message_render.getRRCount(Section::QUESTION()));
- EXPECT_EQ(2, message_render.getRRCount(Section::ANSWER()));
- EXPECT_EQ(0, message_render.getRRCount(Section::AUTHORITY()));
- EXPECT_EQ(0, message_render.getRRCount(Section::ADDITIONAL()));
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+
+ EXPECT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL));
message_render.toWire(renderer);
vector<unsigned char> data;
diff --git a/src/lib/dns/tests/messagerenderer_unittest.cc b/src/lib/dns/tests/messagerenderer_unittest.cc
index 056304a..c3d3edb 100644
--- a/src/lib/dns/tests/messagerenderer_unittest.cc
+++ b/src/lib/dns/tests/messagerenderer_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <vector>
#include <dns/buffer.h>
diff --git a/src/lib/dns/tests/name_unittest.cc b/src/lib/dns/tests/name_unittest.cc
index 3972f64..5daba9c 100644
--- a/src/lib/dns/tests/name_unittest.cc
+++ b/src/lib/dns/tests/name_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <vector>
#include <string>
#include <sstream>
@@ -292,7 +290,7 @@ TEST_F(NameTest, assignment) {
// Self assignment
copy = copy;
- EXPECT_EQ(copy, example_name);
+ EXPECT_EQ(example_name, copy);
}
TEST_F(NameTest, toText) {
diff --git a/src/lib/dns/tests/opcode_unittest.cc b/src/lib/dns/tests/opcode_unittest.cc
index 1cf8af1..a7db654 100644
--- a/src/lib/dns/tests/opcode_unittest.cc
+++ b/src/lib/dns/tests/opcode_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <vector>
#include <sstream>
diff --git a/src/lib/dns/tests/question_unittest.cc b/src/lib/dns/tests/question_unittest.cc
index d2fb1f7..59a4815 100644
--- a/src/lib/dns/tests/question_unittest.cc
+++ b/src/lib/dns/tests/question_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <vector>
#include <sstream>
@@ -141,6 +139,39 @@ TEST_F(QuestionTest, comparison) {
EXPECT_FALSE(Question(a, ch, ns) < Question(a, ch, ns));
EXPECT_FALSE(Question(b, in, ns) < Question(b, in, ns));
EXPECT_FALSE(Question(b, in, aaaa) < Question(b, in, aaaa));
+
+ // Identical questions are equal
+
+ EXPECT_TRUE(Question(a, in, ns) == Question(a, in, ns));
+ EXPECT_FALSE(Question(a, in, ns) != Question(a, in, ns));
+
+ // Components differing by one component are unequal...
+
+ EXPECT_FALSE(Question(b, in, ns) == Question(a, in, ns));
+ EXPECT_TRUE(Question(b, in, ns) != Question(a, in, ns));
+
+ EXPECT_FALSE(Question(a, ch, ns) == Question(a, in, ns));
+ EXPECT_TRUE(Question(a, ch, ns) != Question(a, in, ns));
+
+ EXPECT_FALSE(Question(a, in, aaaa) == Question(a, in, ns));
+ EXPECT_TRUE(Question(a, in, aaaa) != Question(a, in, ns));
+
+ // ... as are those differing by two components
+
+ EXPECT_FALSE(Question(b, ch, ns) == Question(a, in, ns));
+ EXPECT_TRUE(Question(b, ch, ns) != Question(a, in, ns));
+
+ EXPECT_FALSE(Question(b, in, aaaa) == Question(a, in, ns));
+ EXPECT_TRUE(Question(b, in, aaaa) != Question(a, in, ns));
+
+ EXPECT_FALSE(Question(a, ch, aaaa) == Question(a, in, ns));
+ EXPECT_TRUE(Question(a, ch, aaaa) != Question(a, in, ns));
+
+ // ... and question differing by all three
+
+ EXPECT_FALSE(Question(b, ch, aaaa) == Question(a, in, ns));
+ EXPECT_TRUE(Question(b, ch, aaaa) != Question(a, in, ns));
+
}
}
diff --git a/src/lib/dns/tests/rcode_unittest.cc b/src/lib/dns/tests/rcode_unittest.cc
index 4ccb7b0..b8d7c73 100644
--- a/src/lib/dns/tests/rcode_unittest.cc
+++ b/src/lib/dns/tests/rcode_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <vector>
#include <sstream>
diff --git a/src/lib/dns/tests/rdata_cname_unittest.cc b/src/lib/dns/tests/rdata_cname_unittest.cc
index 2e46cd7..e3137a7 100644
--- a/src/lib/dns/tests/rdata_cname_unittest.cc
+++ b/src/lib/dns/tests/rdata_cname_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/buffer.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
diff --git a/src/lib/dns/tests/rdata_dname_unittest.cc b/src/lib/dns/tests/rdata_dname_unittest.cc
index be2990f..c2384b6 100644
--- a/src/lib/dns/tests/rdata_dname_unittest.cc
+++ b/src/lib/dns/tests/rdata_dname_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/buffer.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
diff --git a/src/lib/dns/tests/rdata_dnskey_unittest.cc b/src/lib/dns/tests/rdata_dnskey_unittest.cc
index 38d51b9..e26bf57 100644
--- a/src/lib/dns/tests/rdata_dnskey_unittest.cc
+++ b/src/lib/dns/tests/rdata_dnskey_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <string>
#include <exceptions/exceptions.h>
diff --git a/src/lib/dns/tests/rdata_ds_unittest.cc b/src/lib/dns/tests/rdata_ds_unittest.cc
index 87e05f1..d7e3f88 100644
--- a/src/lib/dns/tests/rdata_ds_unittest.cc
+++ b/src/lib/dns/tests/rdata_ds_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <string>
#include <dns/buffer.h>
diff --git a/src/lib/dns/tests/rdata_in_a_unittest.cc b/src/lib/dns/tests/rdata_in_a_unittest.cc
index 8c0388a..7302881 100644
--- a/src/lib/dns/tests/rdata_in_a_unittest.cc
+++ b/src/lib/dns/tests/rdata_in_a_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/buffer.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
diff --git a/src/lib/dns/tests/rdata_in_aaaa_unittest.cc b/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
index c1b46cf..c1953d6 100644
--- a/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
+++ b/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/buffer.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
diff --git a/src/lib/dns/tests/rdata_mx_unittest.cc b/src/lib/dns/tests/rdata_mx_unittest.cc
index e385361..dd7677d 100644
--- a/src/lib/dns/tests/rdata_mx_unittest.cc
+++ b/src/lib/dns/tests/rdata_mx_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/buffer.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
@@ -76,12 +74,9 @@ TEST_F(Rdata_MX_Test, toWireRenderer) {
TEST_F(Rdata_MX_Test, toWireBuffer) {
renderer.writeName(Name("example.com"));
rdata_mx.toWire(obuffer);
-}
-TEST_F(Rdata_MX_Test, DISABLED_toWireBuffer) {
-// XXX: does not pass
vector<unsigned char> data;
- UnitTestUtil::readWireData("rdata_mx_toWire1", data);
+ UnitTestUtil::readWireData("rdata_mx_toWire2", data);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
obuffer.getLength(), &data[0], data.size());
}
diff --git a/src/lib/dns/tests/rdata_ns_unittest.cc b/src/lib/dns/tests/rdata_ns_unittest.cc
index 8aee40a..6d4a69e 100644
--- a/src/lib/dns/tests/rdata_ns_unittest.cc
+++ b/src/lib/dns/tests/rdata_ns_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/buffer.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
diff --git a/src/lib/dns/tests/rdata_nsec3_unittest.cc b/src/lib/dns/tests/rdata_nsec3_unittest.cc
index 3636640..749e262 100644
--- a/src/lib/dns/tests/rdata_nsec3_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <string>
#include <exceptions/exceptions.h>
@@ -48,59 +46,136 @@ public:
string nsec3_txt;
};
+TEST_F(Rdata_NSEC3_Test, fromText) {
+ // A normal case: the test constructor should successfully parse the
+ // text and construct nsec3_txt. It will be tested against the wire format
+ // representation in the createFromWire test.
+
+ // Numeric parameters have possible maximum values. Unusual, but must
+ // be accepted.
+ EXPECT_NO_THROW(generic::NSEC3("255 255 65535 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
+ "NS SOA RRSIG DNSKEY NSEC3PARAM"));
+
+ // 0-length salt
+ EXPECT_EQ(0, generic::NSEC3("1 1 1 - H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
+ "A").getSalt().size());
+
+ // salt that has the possible max length
+ EXPECT_EQ(255, generic::NSEC3("1 1 1 " + string(255 * 2, '0') +
+ " H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
+ "NS").getSalt().size());
+
+ // hash that has the possible max length (see badText about the magic
+ // numbers)
+ EXPECT_EQ(255, generic::NSEC3("1 1 1 D399EAAB " +
+ string((255 * 8) / 5, '0') +
+ " NS").getNext().size());
+
+ // type bitmap is empty. it's possible and allowed for NSEC3.
+ EXPECT_NO_THROW(generic::NSEC3(
+ "1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6"));
+}
+
TEST_F(Rdata_NSEC3_Test, toText) {
const generic::NSEC3 rdata_nsec3(nsec3_txt);
EXPECT_EQ(nsec3_txt, rdata_nsec3.toText());
}
TEST_F(Rdata_NSEC3_Test, badText) {
- EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1 1 ADDAFEEE "
- "0123456789ABCDEFGHIJKLMNOPQRSTUV "
- "BIFF POW SPOON"),
+ EXPECT_THROW(generic::NSEC3("1 1 1 ADDAFEEE "
+ "0123456789ABCDEFGHIJKLMNOPQRSTUV "
+ "BIFF POW SPOON"),
InvalidRdataText);
- EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1 1 ADDAFEE "
- "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW "
- "A NS SOA"),
+ EXPECT_THROW(generic::NSEC3("1 1 1 ADDAFEE "
+ "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW A NS SOA"),
BadValue); // bad hex
- EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1 1 ADDAFEEE "
- "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW "
- "A NS SOA"),
+ EXPECT_THROW(generic::NSEC3("1 1 1 -- H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
+ "A"),
+ BadValue); // this shouldn't be confused a valid empty salt
+ EXPECT_THROW(generic::NSEC3("1 1 1 ADDAFEEE "
+ "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW A NS SOA"),
BadValue); // bad base32hex
- EXPECT_THROW(generic::NSEC3 rdata_nsec3("1000000 1 1 ADDAFEEE "
- "0123456789ABCDEFGHIJKLMNOPQRSTUV "
- "A NS SOA"),
+ EXPECT_THROW(generic::NSEC3("1000000 1 1 ADDAFEEE "
+ "0123456789ABCDEFGHIJKLMNOPQRSTUV A NS SOA"),
InvalidRdataText);
- EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1000000 1 ADDAFEEE "
- "0123456789ABCDEFGHIJKLMNOPQRSTUV "
- "A NS SOA"),
+ EXPECT_THROW(generic::NSEC3("1 1000000 1 ADDAFEEE "
+ "0123456789ABCDEFGHIJKLMNOPQRSTUV A NS SOA"),
InvalidRdataText);
- EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1 1000000 ADDAFEEE "
- "0123456789ABCDEFGHIJKLMNOPQRSTUV "
- "A NS SOA"),
+ EXPECT_THROW(generic::NSEC3("1 1 1000000 ADDAFEEE "
+ "0123456789ABCDEFGHIJKLMNOPQRSTUV A NS SOA"),
InvalidRdataText);
-}
-TEST_F(Rdata_NSEC3_Test, DISABLED_badText) { // this currently fails
+ // There should be a space between "1" and "D399EAAB" (salt)
EXPECT_THROW(generic::NSEC3(
"1 1 1D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
"NS SOA RRSIG DNSKEY NSEC3PARAM"), InvalidRdataText);
+
+ // Salt is too long (possible max + 1 bytes)
+ EXPECT_THROW(generic::NSEC3("1 1 1 " + string(256 * 2, '0') +
+ " H9RSFB7FPF2L8HG35CMPC765TDK23RP6 NS"),
+ InvalidRdataText);
+
+ // Hash is too long. Max = 255 bytes, base32-hex converts each 5 bytes
+ // of the original to 8 characters, so 260 * 8 / 5 is the smallest length
+ // of the encoded string that exceeds the max and doesn't require padding.
+ EXPECT_THROW(generic::NSEC3("1 1 1 D399EAAB " + string((260 * 8) / 5, '0') +
+ " NS"),
+ InvalidRdataText);
}
TEST_F(Rdata_NSEC3_Test, createFromWire) {
+ // Normal case
const generic::NSEC3 rdata_nsec3(nsec3_txt);
EXPECT_EQ(0, rdata_nsec3.compare(
*rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
"rdata_nsec3_fromWire1")));
- // Too short RDLENGTH
+ // A valid NSEC3 RR with empty type bitmap.
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire15.wire"));
+
+ // Too short RDLENGTH: it doesn't even contain the first 5 octets.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire2.wire"),
+ DNSMessageFORMERR);
+
+ // Invalid bitmap cases are tested in Rdata_NSECBITMAP_Test.
+
+ // salt length is too large
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire11.wire"),
+ DNSMessageFORMERR);
+
+ // empty salt. unusual, but valid.
+ ConstRdataPtr rdata =
+ rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire13.wire");
+ EXPECT_EQ(0, dynamic_cast<const generic::NSEC3&>(*rdata).getSalt().size());
+
+ // hash length is too large
EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
- "rdata_nsec3_fromWire2"),
- InvalidRdataLength);
+ "rdata_nsec3_fromWire12.wire"),
+ DNSMessageFORMERR);
- // Invalid type bits
+ // empty hash. invalid.
EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
- "rdata_nsec3_fromWire3"),
+ "rdata_nsec3_fromWire14.wire"),
DNSMessageFORMERR);
+
+ //
+ // Short buffer cases. The data is valid NSEC3 RDATA, but the buffer
+ // is trimmed at the end. All cases should result in an exception from
+ // the buffer class.
+ vector<uint8_t> data;
+ UnitTestUtil::readWireData("rdata_nsec3_fromWire1", data);
+ const uint16_t rdlen = (data.at(0) << 8) + data.at(1);
+ for (int i = 0; i < rdlen; ++i) {
+ // intentionally construct a short buffer
+ InputBuffer b(&data[0] + 2, i);
+ EXPECT_THROW(createRdata(RRType::NSEC3(), RRClass::IN(), b, 39),
+ InvalidBufferPosition);
+ }
}
TEST_F(Rdata_NSEC3_Test, toWireRenderer) {
diff --git a/src/lib/dns/tests/rdata_nsec3param_unittest.cc b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
index 777d6a3..53e9126 100644
--- a/src/lib/dns/tests/rdata_nsec3param_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <string>
#include <exceptions/exceptions.h>
diff --git a/src/lib/dns/tests/rdata_nsec_unittest.cc b/src/lib/dns/tests/rdata_nsec_unittest.cc
index 63e80ff..8286dee 100644
--- a/src/lib/dns/tests/rdata_nsec_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <string>
#include <dns/buffer.h>
@@ -64,46 +62,7 @@ TEST_F(Rdata_NSEC_Test, createFromWire_NSEC) {
"rdata_nsec_fromWire2"),
DNSMessageFORMERR);
- EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire3"),
- DNSMessageFORMERR);
-
- // A malformed NSEC bitmap length field that could cause overflow.
- EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire4.wire"),
- DNSMessageFORMERR);
-
- // The bitmap field is incomplete (only the first byte is included)
- EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire5.wire"),
- DNSMessageFORMERR);
-
- // Bitmap length is 0, which is invalid.
- EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire6.wire"),
- DNSMessageFORMERR);
-
- // A boundary case: longest possible bitmaps (32 maps). This should be
- // accepted.
- EXPECT_NO_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire7.wire"));
-
- // Another boundary condition: 33 bitmaps, which should be rejected.
- EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire8.wire"),
- DNSMessageFORMERR);
-
- // Disordered bitmap window blocks.
- EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire9.wire"),
- DNSMessageFORMERR);
-
- // Bitmap ending with all-zero bytes. Not necessarily harmful except
- // the additional overhead of parsing, but invalid according to the
- // spec anyway.
- EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire10.wire"),
- DNSMessageFORMERR);
+ // Invalid bitmap cases are tested in Rdata_NSECBITMAP_Test.
}
TEST_F(Rdata_NSEC_Test, toWireRenderer_NSEC) {
diff --git a/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc b/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
new file mode 100644
index 0000000..8a90878
--- /dev/null
+++ b/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
@@ -0,0 +1,103 @@
+// 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 <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/rdata_unittest.h>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+class Rdata_NSECBITMAP_Test : public RdataTest {
+ // there's nothing to specialize
+};
+
+// Tests against various types of bogus NSEC/NSEC3 type bitmaps.
+// The syntax and semantics are common for both RR types, and our
+// implementation of that part is shared, so in theory it should be sufficient
+// to test for only one RR type. But we check for both just in case.
+TEST_F(Rdata_NSECBITMAP_Test, createFromWire_NSEC) {
+ // A malformed NSEC bitmap length field that could cause overflow.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire4.wire"),
+ DNSMessageFORMERR);
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire4.wire"),
+ DNSMessageFORMERR);
+
+ // The bitmap field is incomplete (only the first byte is included)
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire5.wire"),
+ DNSMessageFORMERR);
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire5.wire"),
+ DNSMessageFORMERR);
+
+ // Bitmap length is 0, which is invalid.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire6.wire"),
+ DNSMessageFORMERR);
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire6.wire"),
+ DNSMessageFORMERR);
+
+ // Too large bitmap length with a short buffer.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire3"),
+ DNSMessageFORMERR);
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire3"),
+ DNSMessageFORMERR);
+
+ // A boundary case: longest possible bitmaps (32 maps). This should be
+ // accepted.
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire7.wire"));
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire7.wire"));
+
+ // Another boundary condition: 33 bitmaps, which should be rejected.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire8.wire"),
+ DNSMessageFORMERR);
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire8.wire"),
+ DNSMessageFORMERR);
+
+ // Disordered bitmap window blocks.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire9.wire"),
+ DNSMessageFORMERR);
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire9.wire"),
+ DNSMessageFORMERR);
+
+ // Bitmap ending with all-zero bytes. Not necessarily harmful except
+ // the additional overhead of parsing, but invalid according to the
+ // spec anyway.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire10.wire"),
+ DNSMessageFORMERR);
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire10.wire"),
+ DNSMessageFORMERR);
+}
+}
diff --git a/src/lib/dns/tests/rdata_opt_unittest.cc b/src/lib/dns/tests/rdata_opt_unittest.cc
index d957ba8..be92b91 100644
--- a/src/lib/dns/tests/rdata_opt_unittest.cc
+++ b/src/lib/dns/tests/rdata_opt_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/buffer.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
diff --git a/src/lib/dns/tests/rdata_ptr_unittest.cc b/src/lib/dns/tests/rdata_ptr_unittest.cc
index cb96b7f..da13dcb 100644
--- a/src/lib/dns/tests/rdata_ptr_unittest.cc
+++ b/src/lib/dns/tests/rdata_ptr_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/buffer.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
diff --git a/src/lib/dns/tests/rdata_rrsig_unittest.cc b/src/lib/dns/tests/rdata_rrsig_unittest.cc
index d691a88..04d9469 100644
--- a/src/lib/dns/tests/rdata_rrsig_unittest.cc
+++ b/src/lib/dns/tests/rdata_rrsig_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <exceptions/exceptions.h>
#include <dns/buffer.h>
diff --git a/src/lib/dns/tests/rdata_soa_unittest.cc b/src/lib/dns/tests/rdata_soa_unittest.cc
index 999ecd1..6858a0b 100644
--- a/src/lib/dns/tests/rdata_soa_unittest.cc
+++ b/src/lib/dns/tests/rdata_soa_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/buffer.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
diff --git a/src/lib/dns/tests/rdata_tsig_unittest.cc b/src/lib/dns/tests/rdata_tsig_unittest.cc
new file mode 100644
index 0000000..5c9a14f
--- /dev/null
+++ b/src/lib/dns/tests/rdata_tsig_unittest.cc
@@ -0,0 +1,366 @@
+// 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 <dns/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.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_TSIG_Test : public RdataTest {
+protected:
+ vector<uint8_t> expect_data;
+};
+
+const char* const valid_text1 = "hmac-md5.sig-alg.reg.int. 1286779327 300 "
+ "0 16020 BADKEY 0";
+const char* const valid_text2 = "hmac-sha256. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 BADSIG 0";
+
+const char* const valid_text3 = "hmac-sha1. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 BADTIME 6 FAKEFAKE";
+const char* const valid_text4 = "hmac-sha1. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 BADSIG 6 FAKEFAKE";
+const char* const valid_text5 = "hmac-sha256. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 2845 0"; // using numeric error code
+const char* const too_long_label = "012345678901234567890123456789"
+ "0123456789012345678901234567890123";
+
+// commonly used test RDATA
+const any::TSIG rdata_tsig((string(valid_text1)));
+
+TEST_F(Rdata_TSIG_Test, createFromText) {
+ // normal case. it also tests getter methods.
+ EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), rdata_tsig.getAlgorithm());
+ EXPECT_EQ(1286779327, rdata_tsig.getTimeSigned());
+ EXPECT_EQ(300, rdata_tsig.getFudge());
+ EXPECT_EQ(0, rdata_tsig.getMACSize());
+ EXPECT_EQ(static_cast<void*>(NULL), rdata_tsig.getMAC());
+ EXPECT_EQ(16020, rdata_tsig.getOriginalID());
+ EXPECT_EQ(17, rdata_tsig.getError()); // TODO: use constant
+ EXPECT_EQ(0, rdata_tsig.getOtherLen());
+ EXPECT_EQ(static_cast<void*>(NULL), rdata_tsig.getOtherData());
+
+ any::TSIG tsig2((string(valid_text2)));
+ EXPECT_EQ(12, tsig2.getMACSize());
+ EXPECT_EQ(16, tsig2.getError()); // TODO: use constant
+
+ any::TSIG tsig3((string(valid_text3)));
+ EXPECT_EQ(6, tsig3.getOtherLen());
+
+ // The other data is unusual, but we don't reject it.
+ EXPECT_NO_THROW(any::TSIG(string(valid_text4)));
+
+ // numeric representation of TSIG error
+ any::TSIG tsig5((string(valid_text5)));
+ EXPECT_EQ(2845, tsig5.getError());
+
+ //
+ // invalid cases
+ //
+ // there's a garbage parameter at the end
+ EXPECT_THROW(any::TSIG("foo 0 0 0 0 BADKEY 0 0"), InvalidRdataText);
+ // input is too short
+ EXPECT_THROW(any::TSIG("foo 0 0 0 0 BADKEY"), InvalidRdataText);
+ // bad domain name
+ EXPECT_THROW(any::TSIG(string(too_long_label) + "0 0 0 0 BADKEY 0"),
+ TooLongLabel);
+ // time is too large (2814...6 is 2^48)
+ EXPECT_THROW(any::TSIG("foo 281474976710656 0 0 0 BADKEY 0"),
+ InvalidRdataText);
+ // invalid time (negative)
+ EXPECT_THROW(any::TSIG("foo -1 0 0 0 BADKEY 0"), InvalidRdataText);
+ // fudge is too large
+ EXPECT_THROW(any::TSIG("foo 0 65536 0 0 BADKEY 0"), InvalidRdataText);
+ // invalid fudge (negative)
+ EXPECT_THROW(any::TSIG("foo 0 -1 0 0 BADKEY 0"), InvalidRdataText);
+ // MAC size is too large
+ EXPECT_THROW(any::TSIG("foo 0 0 65536 0 BADKEY 0"), InvalidRdataText);
+ // MAC size and MAC mismatch
+ EXPECT_THROW(any::TSIG("foo 0 0 9 FAKE 0 BADKEY 0"), InvalidRdataText);
+ EXPECT_THROW(any::TSIG("foo 0 0 0 FAKE 0 BADKEY 0"), InvalidRdataText);
+ // MAC is bad base64
+ EXPECT_THROW(any::TSIG("foo 0 0 3 FAK= 0 BADKEY 0"), isc::BadValue);
+ // Unknown error code
+ EXPECT_THROW(any::TSIG("foo 0 0 0 0 TEST 0"), InvalidRdataText);
+ // Numeric error code is too large
+ EXPECT_THROW(any::TSIG("foo 0 0 0 0 65536 0"), InvalidRdataText);
+ // Other len is too large
+ EXPECT_THROW(any::TSIG("foo 0 0 0 0 NOERROR 65536 FAKE"), InvalidRdataText);
+ // Other len and data mismatch
+ EXPECT_THROW(any::TSIG("foo 0 0 0 0 NOERROR 9 FAKE"), InvalidRdataText);
+ EXPECT_THROW(any::TSIG("foo 0 0 0 0 NOERROR 0 FAKE"), InvalidRdataText);
+}
+
+void
+fromWireCommonChecks(const any::TSIG& tsig) {
+ EXPECT_EQ(Name("hmac-sha256"), tsig.getAlgorithm());
+ EXPECT_EQ(1286978795, tsig.getTimeSigned());
+ EXPECT_EQ(300, tsig.getFudge());
+
+ vector<uint8_t> expect_mac(32, 'x');
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ &expect_mac[0], expect_mac.size(),
+ tsig.getMAC(), tsig.getMACSize());
+
+ EXPECT_EQ(2845, tsig.getOriginalID());
+
+ EXPECT_EQ(0, tsig.getOtherLen());
+ EXPECT_EQ(static_cast<const void*>(NULL), tsig.getOtherData());
+}
+
+TEST_F(Rdata_TSIG_Test, createFromWire) {
+ RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire1.wire"));
+ fromWireCommonChecks(dynamic_cast<any::TSIG&>(*rdata));
+}
+
+TEST_F(Rdata_TSIG_Test, createFromWireWithOtherData) {
+ RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire2.wire"));
+ const any::TSIG& tsig(dynamic_cast<any::TSIG&>(*rdata));
+
+ EXPECT_EQ(18, tsig.getError());
+ const uint64_t otherdata = 1286978795 + 300 + 1; // time-signed + fudge + 1
+ expect_data.resize(6);
+ expect_data[0] = (otherdata >> 40);
+ expect_data[1] = ((otherdata >> 32) & 0xff);
+ expect_data[2] = ((otherdata >> 24) & 0xff);
+ expect_data[3] = ((otherdata >> 16) & 0xff);
+ expect_data[4] = ((otherdata >> 8) & 0xff);
+ expect_data[5] = (otherdata & 0xff);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ &expect_data[0], expect_data.size(),
+ tsig.getOtherData(), tsig.getOtherLen());
+}
+
+TEST_F(Rdata_TSIG_Test, createFromWireWithoutMAC) {
+ RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire3.wire"));
+ const any::TSIG& tsig(dynamic_cast<any::TSIG&>(*rdata));
+ EXPECT_EQ(16, tsig.getError());
+ EXPECT_EQ(0, tsig.getMACSize());
+ EXPECT_EQ(static_cast<const void*>(NULL), tsig.getMAC());
+}
+
+TEST_F(Rdata_TSIG_Test, createFromWireWithCompression) {
+ RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire4.wire",
+ // we need to skip the dummy name:
+ Name("hmac-sha256").getLength()));
+ fromWireCommonChecks(dynamic_cast<any::TSIG&>(*rdata));
+}
+
+TEST_F(Rdata_TSIG_Test, badFromWire) {
+ // RDLENGTH is too short:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire5.wire"),
+ InvalidRdataLength);
+ // RDLENGTH is too long:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire6.wire"),
+ InvalidRdataLength);
+ // Algorithm name is broken:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire7.wire"),
+ DNSMessageFORMERR);
+ // MAC size is bogus:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire8.wire"),
+ InvalidBufferPosition);
+ // Other-data length is bogus:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire9.wire"),
+ InvalidBufferPosition);
+}
+
+TEST_F(Rdata_TSIG_Test, copyConstruct) {
+ const any::TSIG copy(rdata_tsig);
+ EXPECT_EQ(0, copy.compare(rdata_tsig));
+
+ // Check the copied data is valid even after the original is deleted
+ any::TSIG* copy2 = new any::TSIG(rdata_tsig);
+ any::TSIG copy3(*copy2);
+ delete copy2;
+ EXPECT_EQ(0, copy3.compare(rdata_tsig));
+}
+
+TEST_F(Rdata_TSIG_Test, createFromParams) {
+ EXPECT_EQ(0, rdata_tsig.compare(any::TSIG(Name("hmac-md5.sig-alg.reg.int"),
+ 1286779327, 300, 0, NULL, 16020,
+ 17, 0, NULL)));
+
+ const uint8_t fake_data[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84,
+ 0x14, 0x02, 0x84, 0x14, 0x02, 0x84 };
+ EXPECT_EQ(0, any::TSIG((string(valid_text2))).compare(
+ any::TSIG(Name("hmac-sha256"), 1286779327, 300, 12,
+ fake_data, 16020, 16, 0, NULL)));
+
+ const uint8_t fake_data2[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84 };
+ EXPECT_EQ(0, any::TSIG((string(valid_text3))).compare(
+ any::TSIG(Name("hmac-sha1"), 1286779327, 300, 12,
+ fake_data, 16020, 18, 6, fake_data2)));
+
+ EXPECT_THROW(any::TSIG(Name("hmac-sha256"), 1LLU << 48, 300, 12,
+ fake_data, 16020, 18, 6, fake_data2),
+ isc::OutOfRange);
+ EXPECT_THROW(any::TSIG(Name("hmac-sha256"), 0, 300, 0, fake_data, 16020,
+ 18, 0, NULL),
+ isc::InvalidParameter);
+ EXPECT_THROW(any::TSIG(Name("hmac-sha256"), 0, 300, 12, NULL, 16020,
+ 18, 0, NULL),
+ isc::InvalidParameter);
+ EXPECT_THROW(any::TSIG(Name("hmac-sha256"), 0, 300, 0, NULL, 16020,
+ 18, 0, fake_data),
+ isc::InvalidParameter);
+ EXPECT_THROW(any::TSIG(Name("hmac-sha256"), 0, 300, 0, NULL, 16020,
+ 18, 6, NULL),
+ isc::InvalidParameter);
+}
+
+TEST_F(Rdata_TSIG_Test, assignment) {
+ any::TSIG copy((string(valid_text2)));
+ copy = rdata_tsig;
+ EXPECT_EQ(0, copy.compare(rdata_tsig));
+
+ // Check if the copied data is valid even after the original is deleted
+ any::TSIG* copy2 = new any::TSIG(rdata_tsig);
+ any::TSIG copy3((string(valid_text2)));
+ copy3 = *copy2;
+ delete copy2;
+ EXPECT_EQ(0, copy3.compare(rdata_tsig));
+
+ // Self assignment
+ copy = copy;
+ EXPECT_EQ(0, copy.compare(rdata_tsig));
+}
+
+template <typename Output>
+void
+toWireCommonChecks(Output& output) {
+ vector<uint8_t> expect_data;
+
+ output.clear();
+ expect_data.clear();
+ rdata_tsig.toWire(output);
+ // read the expected wire format data and trim the RDLEN part.
+ UnitTestUtil::readWireData("rdata_tsig_toWire1.wire", expect_data);
+ expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ &expect_data[0], expect_data.size(),
+ output.getData(), output.getLength());
+
+ expect_data.clear();
+ output.clear();
+ any::TSIG(string(valid_text2)).toWire(output);
+ UnitTestUtil::readWireData("rdata_tsig_toWire2.wire", expect_data);
+ expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ &expect_data[0], expect_data.size(),
+ output.getData(), output.getLength());
+
+ expect_data.clear();
+ output.clear();
+ any::TSIG(string(valid_text3)).toWire(output);
+ UnitTestUtil::readWireData("rdata_tsig_toWire3.wire", expect_data);
+ expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ &expect_data[0], expect_data.size(),
+ output.getData(), output.getLength());
+}
+
+TEST_F(Rdata_TSIG_Test, toWireBuffer) {
+ toWireCommonChecks<OutputBuffer>(obuffer);
+}
+
+TEST_F(Rdata_TSIG_Test, toWireRenderer) {
+ toWireCommonChecks<MessageRenderer>(renderer);
+
+ // check algorithm name won't compressed when it would otherwise.
+ expect_data.clear();
+ renderer.clear();
+ renderer.writeName(Name("hmac-md5.sig-alg.reg.int"));
+ renderer.writeUint16(42); // RDLEN
+ rdata_tsig.toWire(renderer);
+ UnitTestUtil::readWireData("rdata_tsig_toWire4.wire", expect_data);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ &expect_data[0], expect_data.size(),
+ renderer.getData(), renderer.getLength());
+
+ // check algorithm can be used as a compression target.
+ expect_data.clear();
+ renderer.clear();
+ renderer.writeUint16(42);
+ rdata_tsig.toWire(renderer);
+ renderer.writeName(Name("hmac-md5.sig-alg.reg.int"));
+ UnitTestUtil::readWireData("rdata_tsig_toWire5.wire", expect_data);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ &expect_data[0], expect_data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_TSIG_Test, toText) {
+ EXPECT_EQ(string(valid_text1), rdata_tsig.toText());
+ EXPECT_EQ(string(valid_text2), any::TSIG(string(valid_text2)).toText());
+ EXPECT_EQ(string(valid_text3), any::TSIG(string(valid_text3)).toText());
+ EXPECT_EQ(string(valid_text5), any::TSIG(string(valid_text5)).toText());
+}
+
+TEST_F(Rdata_TSIG_Test, compare) {
+ // test RDATAs, sorted in the ascendent order.
+ // "AAAA" encoded in BASE64 corresponds to 0x000000, so it should be the
+ // smallest data of the same length.
+ vector<any::TSIG> compare_set;
+ compare_set.push_back(any::TSIG("a.example 0 300 0 16020 0 0"));
+ compare_set.push_back(any::TSIG("example 0 300 0 16020 0 0"));
+ compare_set.push_back(any::TSIG("example 1 300 0 16020 0 0"));
+ compare_set.push_back(any::TSIG("example 1 600 0 16020 0 0"));
+ compare_set.push_back(any::TSIG("example 1 600 3 AAAA 16020 0 0"));
+ compare_set.push_back(any::TSIG("example 1 600 3 FAKE 16020 0 0"));
+ compare_set.push_back(any::TSIG("example 1 600 3 FAKE 16021 0 0"));
+ compare_set.push_back(any::TSIG("example 1 600 3 FAKE 16021 1 0"));
+ compare_set.push_back(any::TSIG("example 1 600 3 FAKE 16021 1 3 AAAA"));
+ compare_set.push_back(any::TSIG("example 1 600 3 FAKE 16021 1 3 FAKE"));
+
+ EXPECT_EQ(0, compare_set[0].compare(
+ any::TSIG("A.EXAMPLE 0 300 0 16020 0 0")));
+
+ vector<any::TSIG>::const_iterator it;
+ vector<any::TSIG>::const_iterator it_end = compare_set.end();
+ for (it = compare_set.begin(); it != it_end - 1; ++it) {
+ EXPECT_GT(0, (*it).compare(*(it + 1)));
+ EXPECT_LT(0, (*(it + 1)).compare(*it));
+ }
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(rdata_tsig.compare(*RdataTest::rdata_nomatch), bad_cast);
+}
+}
diff --git a/src/lib/dns/tests/rdata_txt_unittest.cc b/src/lib/dns/tests/rdata_txt_unittest.cc
index 3271d88..54993e1 100644
--- a/src/lib/dns/tests/rdata_txt_unittest.cc
+++ b/src/lib/dns/tests/rdata_txt_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <dns/buffer.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
diff --git a/src/lib/dns/tests/rdata_unittest.cc b/src/lib/dns/tests/rdata_unittest.cc
index 81dfe10..5ce4c03 100644
--- a/src/lib/dns/tests/rdata_unittest.cc
+++ b/src/lib/dns/tests/rdata_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <vector>
#include <string>
#include <sstream>
diff --git a/src/lib/dns/tests/rdata_unittest.h b/src/lib/dns/tests/rdata_unittest.h
index 40b1eb2..748c8d3 100644
--- a/src/lib/dns/tests/rdata_unittest.h
+++ b/src/lib/dns/tests/rdata_unittest.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __RDATA_UNITTEST_H
#define __RDATA_UNITTEST_H 1
diff --git a/src/lib/dns/tests/rdatafields_unittest.cc b/src/lib/dns/tests/rdatafields_unittest.cc
index 212a8a3..7c17b54 100644
--- a/src/lib/dns/tests/rdatafields_unittest.cc
+++ b/src/lib/dns/tests/rdatafields_unittest.cc
@@ -277,7 +277,7 @@ void
RdataFieldsTest::constructCommonTestsOPT(const RdataFields& fields) {
EXPECT_EQ(0, fields.getFieldCount());
EXPECT_EQ(0, fields.getDataLength());
- EXPECT_EQ(NULL, fields.getData());
+ EXPECT_EQ((const uint8_t*) NULL, fields.getData());
fields.toWire(obuffer);
EXPECT_EQ(0, obuffer.getLength());
fields.toWire(renderer);
diff --git a/src/lib/dns/tests/rrclass_unittest.cc b/src/lib/dns/tests/rrclass_unittest.cc
index 871909a..4eeb1e0 100644
--- a/src/lib/dns/tests/rrclass_unittest.cc
+++ b/src/lib/dns/tests/rrclass_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <gtest/gtest.h>
#include <dns/buffer.h>
diff --git a/src/lib/dns/tests/rrparamregistry_unittest.cc b/src/lib/dns/tests/rrparamregistry_unittest.cc
index ac57ae7..a75eed5 100644
--- a/src/lib/dns/tests/rrparamregistry_unittest.cc
+++ b/src/lib/dns/tests/rrparamregistry_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <string>
#include <sstream>
diff --git a/src/lib/dns/tests/rrset_unittest.cc b/src/lib/dns/tests/rrset_unittest.cc
index 91a98ec..c704cc8 100644
--- a/src/lib/dns/tests/rrset_unittest.cc
+++ b/src/lib/dns/tests/rrset_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdexcept>
#include <dns/buffer.h>
@@ -117,8 +115,7 @@ void
addRdataTestCommon(const RRset& rrset) {
EXPECT_EQ(2, rrset.getRdataCount());
- RdataIteratorPtr it = rrset.getRdataIterator();
- it->first();
+ RdataIteratorPtr it = rrset.getRdataIterator(); // cursor is set to the 1st
EXPECT_FALSE(it->isLast());
EXPECT_EQ(0, it->getCurrent().compare(in::A("192.0.2.1")));
it->next();
@@ -156,7 +153,6 @@ TEST_F(RRsetTest, addRdataPtr) {
TEST_F(RRsetTest, iterator) {
// Iterator for an empty RRset.
RdataIteratorPtr it = rrset_a_empty.getRdataIterator();
- it->first();
EXPECT_TRUE(it->isLast());
// Normal case (already tested, but do it again just in case)
diff --git a/src/lib/dns/tests/rrsetlist_unittest.cc b/src/lib/dns/tests/rrsetlist_unittest.cc
index 51da4df..080f888 100644
--- a/src/lib/dns/tests/rrsetlist_unittest.cc
+++ b/src/lib/dns/tests/rrsetlist_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <vector>
#include <boost/foreach.hpp>
@@ -150,7 +148,6 @@ TEST_F(RRsetListTest, checkData) {
RdataIteratorPtr it =
list.findRRset(RRType::A(), RRClass::IN())->getRdataIterator();
- it->first();
EXPECT_FALSE(it->isLast());
EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
}
diff --git a/src/lib/dns/tests/rrttl_unittest.cc b/src/lib/dns/tests/rrttl_unittest.cc
index c359ce3..b8f5ac2 100644
--- a/src/lib/dns/tests/rrttl_unittest.cc
+++ b/src/lib/dns/tests/rrttl_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <gtest/gtest.h>
#include <dns/buffer.h>
diff --git a/src/lib/dns/tests/rrtype_unittest.cc b/src/lib/dns/tests/rrtype_unittest.cc
index 25aa1d7..6da7381 100644
--- a/src/lib/dns/tests/rrtype_unittest.cc
+++ b/src/lib/dns/tests/rrtype_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <gtest/gtest.h>
#include <dns/buffer.h>
diff --git a/src/lib/dns/tests/run_unittests.cc b/src/lib/dns/tests/run_unittests.cc
index 1a6f8ee..3cdc61d 100644
--- a/src/lib/dns/tests/run_unittests.cc
+++ b/src/lib/dns/tests/run_unittests.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <gtest/gtest.h>
#include <dns/tests/unittest_util.h>
diff --git a/src/lib/dns/tests/sha1_unittest.cc b/src/lib/dns/tests/sha1_unittest.cc
index 658be0d..79bc37d 100644
--- a/src/lib/dns/tests/sha1_unittest.cc
+++ b/src/lib/dns/tests/sha1_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <string>
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index 4889e17..4d5c17c 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -10,16 +10,32 @@ BUILT_SOURCES += rdata_nsec_fromWire4.wire rdata_nsec_fromWire5.wire
BUILT_SOURCES += rdata_nsec_fromWire6.wire rdata_nsec_fromWire7.wire
BUILT_SOURCES += rdata_nsec_fromWire8.wire rdata_nsec_fromWire9.wire
BUILT_SOURCES += rdata_nsec_fromWire10.wire
+BUILT_SOURCES += rdata_nsec3_fromWire2.wire
+BUILT_SOURCES += rdata_nsec3_fromWire4.wire rdata_nsec3_fromWire5.wire
+BUILT_SOURCES += rdata_nsec3_fromWire6.wire rdata_nsec3_fromWire7.wire
+BUILT_SOURCES += rdata_nsec3_fromWire8.wire rdata_nsec3_fromWire9.wire
+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_soa_toWireUncompressed.wire
-BUILT_SOURCES += rdata_txt_fromWire2.wire rdata_txt_fromWire3.wire
+BUILT_SOURCES += rdata_txt_fromWire2.wire rdata_txt_fromWire3.wire
BUILT_SOURCES += rdata_txt_fromWire4.wire rdata_txt_fromWire5.wire
+BUILT_SOURCES += rdata_tsig_fromWire1.wire rdata_tsig_fromWire2.wire
+BUILT_SOURCES += rdata_tsig_fromWire3.wire rdata_tsig_fromWire4.wire
+BUILT_SOURCES += rdata_tsig_fromWire5.wire rdata_tsig_fromWire6.wire
+BUILT_SOURCES += rdata_tsig_fromWire7.wire rdata_tsig_fromWire8.wire
+BUILT_SOURCES += rdata_tsig_fromWire9.wire
+BUILT_SOURCES += rdata_tsig_toWire1.wire rdata_tsig_toWire2.wire
+BUILT_SOURCES += rdata_tsig_toWire3.wire rdata_tsig_toWire4.wire
+BUILT_SOURCES += rdata_tsig_toWire5.wire
# NOTE: keep this in sync with real file listing
# so is included in tarball
EXTRA_DIST = gen-wiredata.py.in
EXTRA_DIST += edns_toWire1.spec edns_toWire2.spec
EXTRA_DIST += edns_toWire3.spec edns_toWire4.spec
+EXTRA_DIST += masterload.txt
EXTRA_DIST += message_fromWire1 message_fromWire2
EXTRA_DIST += message_fromWire3 message_fromWire4
EXTRA_DIST += message_fromWire5 message_fromWire6
@@ -38,14 +54,21 @@ EXTRA_DIST += rdatafields1.spec rdatafields2.spec rdatafields3.spec
EXTRA_DIST += rdatafields4.spec rdatafields5.spec rdatafields6.spec
EXTRA_DIST += rdata_cname_fromWire rdata_dname_fromWire rdata_dnskey_fromWire
EXTRA_DIST += rdata_ds_fromWire rdata_in_a_fromWire rdata_in_aaaa_fromWire
-EXTRA_DIST += rdata_mx_fromWire rdata_mx_toWire1 rdata_ns_fromWire
-EXTRA_DIST += rdata_nsec3_fromWire1 rdata_nsec3_fromWire2 rdata_nsec3_fromWire3
-EXTRA_DIST += rdata_nsec3param_fromWire1 rdata_nsec_fromWire1
-EXTRA_DIST += rdata_nsec_fromWire2 rdata_nsec_fromWire3
+EXTRA_DIST += rdata_mx_fromWire rdata_mx_toWire1 rdata_mx_toWire2
+EXTRA_DIST += rdata_ns_fromWire
+EXTRA_DIST += rdata_nsec_fromWire1 rdata_nsec_fromWire2 rdata_nsec_fromWire3
EXTRA_DIST += rdata_nsec_fromWire4.spec rdata_nsec_fromWire5.spec
EXTRA_DIST += rdata_nsec_fromWire6.spec rdata_nsec_fromWire7.spec
EXTRA_DIST += rdata_nsec_fromWire8.spec rdata_nsec_fromWire9.spec
EXTRA_DIST += rdata_nsec_fromWire10.spec
+EXTRA_DIST += rdata_nsec3param_fromWire1
+EXTRA_DIST += rdata_nsec3_fromWire1 rdata_nsec3_fromWire3
+EXTRA_DIST += rdata_nsec3_fromWire4.spec rdata_nsec3_fromWire5.spec
+EXTRA_DIST += rdata_nsec3_fromWire6.spec rdata_nsec3_fromWire7.spec
+EXTRA_DIST += rdata_nsec3_fromWire8.spec rdata_nsec3_fromWire9.spec
+EXTRA_DIST += rdata_nsec3_fromWire10.spec rdata_nsec3_fromWire11.spec
+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_soa_fromWire rdata_soa_toWireUncompressed.spec
@@ -55,6 +78,15 @@ EXTRA_DIST += rdata_txt_fromWire5.spec rdata_unknown_fromWire
EXTRA_DIST += rrcode16_fromWire1 rrcode16_fromWire2
EXTRA_DIST += rrcode32_fromWire1 rrcode32_fromWire2
EXTRA_DIST += rrset_toWire1 rrset_toWire2
+EXTRA_DIST += rdata_tsig_fromWire1.spec rdata_tsig_fromWire2.spec
+EXTRA_DIST += rdata_tsig_fromWire3.spec rdata_tsig_fromWire4.spec
+EXTRA_DIST += rdata_tsig_fromWire5.spec rdata_tsig_fromWire6.spec
+EXTRA_DIST += rdata_tsig_fromWire7.spec rdata_tsig_fromWire8.spec
+EXTRA_DIST += rdata_tsig_fromWire9.spec
+EXTRA_DIST += rdata_tsig_toWire1.spec rdata_tsig_toWire2.spec
+EXTRA_DIST += rdata_tsig_toWire3.spec rdata_tsig_toWire4.spec
+EXTRA_DIST += rdata_tsig_toWire5.spec
+EXTRA_DIST += rdata_nsec3_fromWire2.spec
.spec.wire:
./gen-wiredata.py -o $@ $<
diff --git a/src/lib/dns/tests/testdata/edns_toWire4.spec b/src/lib/dns/tests/testdata/edns_toWire4.spec
index 772f937..ea1f5e3 100644
--- a/src/lib/dns/tests/testdata/edns_toWire4.spec
+++ b/src/lib/dns/tests/testdata/edns_toWire4.spec
@@ -1,5 +1,6 @@
#
-# Same as edns_toWire1 but setting the DO bit
+# Same as edns_toWire1 but setting the DO bit, and using an unusual
+# UDP payload size
#
[edns]
do: 1
diff --git a/src/lib/dns/tests/testdata/gen-wiredata.py.in b/src/lib/dns/tests/testdata/gen-wiredata.py.in
index f599d01..645430c 100755
--- a/src/lib/dns/tests/testdata/gen-wiredata.py.in
+++ b/src/lib/dns/tests/testdata/gen-wiredata.py.in
@@ -19,8 +19,8 @@ import configparser, re, time, sys
from datetime import datetime
from optparse import OptionParser
-re_hex = re.compile(r'0x[0-9a-fA-F]+')
-re_decimal = re.compile(r'\d+$')
+re_hex = re.compile(r'^0x[0-9a-fA-F]+')
+re_decimal = re.compile(r'^\d+$')
re_string = re.compile(r"\'(.*)\'$")
dnssec_timefmt = '%Y%m%d%H%M%S'
@@ -48,9 +48,15 @@ dict_rrtype = { 'none' : 0, 'a' : 1, 'ns' : 2, 'md' : 3, 'mf' : 4, 'cname' : 5,
'maila' : 254, 'any' : 255 }
rdict_rrtype = dict([(dict_rrtype[k], k.upper()) for k in dict_rrtype.keys()])
dict_rrclass = { 'in' : 1, 'ch' : 3, 'hs' : 4, 'any' : 255 }
-rdict_rrclass = dict([(dict_rrclass[k], k.upper()) for k in dict_rrclass.keys()])
-dict_algorithm = { 'rsamd5' : 1, 'dh' : 2, 'dsa' : 3, 'ecc' : 4, 'rsasha1' : 5 }
-rdict_algorithm = dict([(dict_algorithm[k], k.upper()) for k in dict_algorithm.keys()])
+rdict_rrclass = dict([(dict_rrclass[k], k.upper()) for k in \
+ dict_rrclass.keys()])
+dict_algorithm = { 'rsamd5' : 1, 'dh' : 2, 'dsa' : 3, 'ecc' : 4,
+ 'rsasha1' : 5 }
+dict_nsec3_algorithm = { 'reserved' : 0, 'sha1' : 1 }
+rdict_algorithm = dict([(dict_algorithm[k], k.upper()) for k in \
+ dict_algorithm.keys()])
+rdict_nsec3_algorithm = dict([(dict_nsec3_algorithm[k], k.upper()) for k in \
+ dict_nsec3_algorithm.keys()])
header_xtables = { 'qr' : dict_qr, 'opcode' : dict_opcode,
'rcode' : dict_rcode }
@@ -75,13 +81,17 @@ def code_totext(code, dict):
return dict[code] + '(' + str(code) + ')'
return str(code)
-def encode_name(name, absolute = True):
+def encode_name(name, absolute=True):
# make sure the name is dot-terminated. duplicate dots will be ignored
# below.
name += '.'
labels = name.split('.')
wire = ''
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:]))
+ break
if absolute or len(l) > 0:
wire += '%02x' % len(l)
wire += ''.join(['%02x' % ord(ch) for ch in l])
@@ -89,7 +99,9 @@ def encode_name(name, absolute = True):
break
return wire
-def encode_string(name):
+def encode_string(name, len=None):
+ if type(name) is int and len is not None:
+ return '%0.*x' % (len * 2, name)
return ''.join(['%02x' % ord(ch) for ch in name])
def count_namelabels(name):
@@ -121,17 +133,19 @@ def print_header(f, input_file):
class Name:
name = 'example.com'
- pointer = -1 # no compression by default
+ pointer = None # no compression by default
def dump(self, f):
- name_wire = encode_name(self.name,
- True if self.pointer == -1 else False)
+ name = self.name
+ if self.pointer is not None:
+ if len(name) > 0 and name[-1] != '.':
+ name += '.'
+ name += 'ptr=%d' % self.pointer
+ name_wire = encode_name(name)
f.write('\n# DNS Name: %s' % self.name)
- if self.pointer >= 0:
+ if self.pointer is not None:
f.write(' + compression pointer: %d' % self.pointer)
f.write('\n')
f.write('%s' % name_wire)
- if self.pointer >= 0:
- f.write(' %04x' % (0xc000 | self.pointer))
f.write('\n')
class DNSHeader:
@@ -263,14 +277,16 @@ class TXT:
' ' if len(wirestring_list[i]) > 0 else '',
wirestring_list[i]))
-class NSEC:
- rdlen = -1 # auto-calculate
- nextname = 'next.example.com'
+class NSECBASE:
+ '''Implements rendering NSEC/NSEC3 type bitmaps commonly used for
+ these RRs. The NSEC and NSEC3 classes will be inherited from this
+ class.'''
nbitmap = 1 # number of bitmaps
block = 0
- maplen = -1 # default bitmap length, auto-calculate
+ maplen = None # default bitmap length, auto-calculate
bitmap = '040000000003' # an arbtrarily chosen bitmap sample
def dump(self, f):
+ # first, construct the bitmpa data
block_list = []
maplen_list = []
bitmap_list = []
@@ -285,30 +301,72 @@ class NSEC:
maplen_list.append(self.__dict__[key_maplen])
else:
maplen_list.append(self.maplen)
- if maplen_list[-1] < 0:
+ if maplen_list[-1] is None: # calculate it if not specified
maplen_list[-1] = int(len(bitmap_list[-1]) / 2)
key_block = 'block' + str(i)
if key_block in self.__dict__:
block_list.append(self.__dict__[key_block])
else:
block_list.append(self.block)
+
+ # dump RR-type specific part (NSEC or NSEC3)
+ self.dump_fixedpart(f, 2 * self.nbitmap + \
+ int(len(''.join(bitmap_list)) / 2))
+
+ # dump the bitmap
+ for i in range(0, self.nbitmap):
+ f.write('# Bitmap: Block=%d, Length=%d\n' %
+ (block_list[i], maplen_list[i]))
+ f.write('%02x %02x %s\n' %
+ (block_list[i], maplen_list[i], bitmap_list[i]))
+
+class NSEC(NSECBASE):
+ rdlen = None # auto-calculate
+ nextname = 'next.example.com'
+ def dump_fixedpart(self, f, bitmap_totallen):
name_wire = encode_name(self.nextname)
- rdlen = self.rdlen
- if rdlen < 0:
+ if self.rdlen is None:
# if rdlen needs to be calculated, it must be based on the bitmap
# length, because the configured maplen can be fake.
- rdlen = int(len(name_wire) / 2) + 2 * self.nbitmap
- rdlen = rdlen + int(len(''.join(bitmap_list)) / 2)
- f.write('\n# NSEC RDATA (RDLEN=%d)\n' % rdlen)
- f.write('%04x\n' % rdlen);
+ self.rdlen = int(len(name_wire) / 2) + bitmap_totallen
+ f.write('\n# NSEC RDATA (RDLEN=%d)\n' % self.rdlen)
+ f.write('%04x\n' % self.rdlen);
f.write('# Next Name=%s (%d bytes)\n' % (self.nextname,
int(len(name_wire) / 2)))
f.write('%s\n' % name_wire)
- for i in range(0, self.nbitmap):
- f.write('# Bitmap: Block=%d, Length=%d\n' %
- (block_list[i], maplen_list[i]))
- f.write('%02x %02x %s\n' %
- (block_list[i], maplen_list[i], bitmap_list[i]))
+
+class NSEC3(NSECBASE):
+ rdlen = None # auto-calculate
+ hashalg = 1 # SHA-1
+ optout = False # opt-out flag
+ mbz = 0 # other flag fields (none defined yet)
+ iterations = 1
+ saltlen = 5
+ salt = 's' * saltlen
+ hashlen = 20
+ hash = 'h' * hashlen
+ def dump_fixedpart(self, f, bitmap_totallen):
+ if self.rdlen is None:
+ # if rdlen needs to be calculated, it must be based on the bitmap
+ # length, because the configured maplen can be fake.
+ self.rdlen = 4 + 1 + len(self.salt) + 1 + len(self.hash) \
+ + bitmap_totallen
+ f.write('\n# NSEC3 RDATA (RDLEN=%d)\n' % self.rdlen)
+ f.write('%04x\n' % self.rdlen)
+ optout_val = 1 if self.optout else 0
+ f.write('# Hash Alg=%s, Opt-Out=%d, Other Flags=%0x, Iterations=%d\n' %
+ (code_totext(self.hashalg, rdict_nsec3_algorithm),
+ optout_val, self.mbz, self.iterations))
+ f.write('%02x %02x %04x\n' %
+ (self.hashalg, (self.mbz << 1) | optout_val, self.iterations))
+ f.write("# Salt Len=%d, Salt='%s'\n" % (self.saltlen, self.salt))
+ f.write('%02x%s%s\n' % (self.saltlen,
+ ' ' if len(self.salt) > 0 else '',
+ encode_string(self.salt)))
+ f.write("# Hash Len=%d, Hash='%s'\n" % (self.hashlen, self.hash))
+ f.write('%02x%s%s\n' % (self.hashlen,
+ ' ' if len(self.hash) > 0 else '',
+ encode_string(self.hash)))
class RRSIG:
rdlen = -1 # auto-calculate
@@ -338,20 +396,73 @@ class RRSIG:
(code_totext(self.covered, rdict_rrtype),
code_totext(self.algorithm, rdict_algorithm), labels,
self.originalttl))
- f.write('%04x %02x %02x %08x\n' % (self.covered, self.algorithm, labels,
- self.originalttl))
+ f.write('%04x %02x %02x %08x\n' % (self.covered, self.algorithm,
+ labels, self.originalttl))
f.write('# Expiration=%s, Inception=%s\n' %
(str(self.expiration), str(self.inception)))
f.write('%08x %08x\n' % (self.expiration, self.inception))
f.write('# Tag=%d Signer=%s and Signature\n' % (self.tag, self.signer))
f.write('%04x %s %s\n' % (self.tag, name_wire, sig_wire))
+class TSIG:
+ rdlen = None # auto-calculate
+ algorithm = 'hmac-sha256'
+ time_signed = 1286978795 # arbitrarily chosen default
+ fudge = 300
+ mac_size = None # use a common value for the algorithm
+ mac = None # use 'x' * mac_size
+ original_id = 2845 # arbitrarily chosen default
+ error = 0
+ other_len = None # 6 if error is BADTIME; otherwise 0
+ other_data = None # use time_signed + fudge + 1 for BADTIME
+ dict_macsize = { 'hmac-md5' : 16, 'hmac-sha1' : 20, 'hmac-sha256' : 32 }
+ def dump(self, f):
+ if str(self.algorithm) == 'hmac-md5':
+ name_wire = encode_name('hmac-md5.sig-alg.reg.int')
+ else:
+ name_wire = encode_name(self.algorithm)
+ rdlen = self.rdlen
+ mac_size = self.mac_size
+ if mac_size is None:
+ if self.algorithm in self.dict_macsize.keys():
+ mac_size = self.dict_macsize[self.algorithm]
+ else:
+ raise RuntimeError('TSIG Mac Size cannot be determined')
+ mac = encode_string('x' * mac_size) if self.mac is None else \
+ encode_string(self.mac, mac_size)
+ other_len = self.other_len
+ if other_len is None:
+ # 18 = BADTIME
+ other_len = 6 if self.error == 18 else 0
+ other_data = self.other_data
+ if other_data is None:
+ other_data = '%012x' % (self.time_signed + self.fudge + 1) \
+ if self.error == 18 else ''
+ else:
+ other_data = encode_string(self.other_data, other_len)
+ if rdlen is None:
+ rdlen = int(len(name_wire) / 2 + 16 + len(mac) / 2 + \
+ len(other_data) / 2)
+ f.write('\n# TSIG RDATA (RDLEN=%d)\n' % rdlen)
+ f.write('%04x\n' % rdlen);
+ f.write('# Algorithm=%s Time-Signed=%d Fudge=%d\n' %
+ (self.algorithm, self.time_signed, self.fudge))
+ f.write('%s %012x %04x\n' % (name_wire, self.time_signed, self.fudge))
+ f.write('# MAC Size=%d MAC=(see hex)\n' % mac_size)
+ f.write('%04x%s\n' % (mac_size, ' ' + mac if len(mac) > 0 else ''))
+ f.write('# Original-ID=%d Error=%d\n' % (self.original_id, self.error))
+ f.write('%04x %04x\n' % (self.original_id, self.error))
+ f.write('# Other-Len=%d Other-Data=(see hex)\n' % other_len)
+ f.write('%04x%s\n' % (other_len,
+ ' ' + other_data if len(other_data) > 0 else ''))
+
def get_config_param(section):
config_param = {'name' : (Name, {}),
'header' : (DNSHeader, header_xtables),
'question' : (DNSQuestion, question_xtables),
'edns' : (EDNS, {}), 'soa' : (SOA, {}), 'txt' : (TXT, {}),
- 'rrsig' : (RRSIG, {}), 'nsec' : (NSEC, {})}
+ '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/masterload.txt b/src/lib/dns/tests/testdata/masterload.txt
new file mode 100644
index 0000000..0d2f942
--- /dev/null
+++ b/src/lib/dns/tests/testdata/masterload.txt
@@ -0,0 +1,5 @@
+;; a simple (incomplete) zone file
+
+example.com. 3600 IN TXT "test data"
+www.example.com. 60 IN A 192.0.2.1
+www.example.com. 60 IN A 192.0.2.2
diff --git a/src/lib/dns/tests/testdata/rdata_mx_toWire2 b/src/lib/dns/tests/testdata/rdata_mx_toWire2
new file mode 100644
index 0000000..ebd2f27
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_mx_toWire2
@@ -0,0 +1,12 @@
+#
+# compressed MX RDATA stored in an output buffer
+#
+# sentinel name: example.com.
+# 0 1 2 3 4 5 6 7 8 9 10 1 2 (bytes)
+#(7) e x a m p l e (3) c o m .
+ 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# PREFERENCE: 10
+ 00 0a
+# EXCHANGE: not compressed
+#(4) m x ptr=0
+ 02 6d 78 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire1.spec
new file mode 100644
index 0000000..39a78d7
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire1.spec
@@ -0,0 +1,7 @@
+#
+# A malformed NSEC3 RDATA: bit map length is too large, causing overflow
+#
+
+[custom]
+sections: nsec3
+[nsec3]
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.spec
new file mode 100644
index 0000000..30417f5
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.spec
@@ -0,0 +1,8 @@
+#
+# An invalid NSEC3 RDATA: a bitmap block containing empty bytes
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+bitmap: '01000000'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.spec
new file mode 100644
index 0000000..80ec59f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.spec
@@ -0,0 +1,8 @@
+#
+# An invalid NSEC3 RDATA: Saltlen is too large
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+rdlen: 7
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.spec
new file mode 100644
index 0000000..1e01655
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.spec
@@ -0,0 +1,9 @@
+#
+# An invalid NSEC3 RDATA: Hash length is too large
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+# only contains the first byte of hash
+rdlen: 12
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.spec
new file mode 100644
index 0000000..fcc9d53
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.spec
@@ -0,0 +1,9 @@
+#
+# A valid (but unusual) NSEC3 RDATA: salt is empty.
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+saltlen: 0
+salt: ''
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.spec
new file mode 100644
index 0000000..a0550d5
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.spec
@@ -0,0 +1,9 @@
+#
+# An invalid NSEC3 RDATA: empty hash
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+hashlen: 0
+hash: ''
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.spec
new file mode 100644
index 0000000..4993e03
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.spec
@@ -0,0 +1,10 @@
+#
+# NSEC3 RDATA with empty type bitmap. It's okay.
+# The test data includes bytes for a bitmap field, but RDLEN indicates
+# it's not part of the RDATA and so it will be ignored.
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+rdlen: 31
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2 b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2
deleted file mode 100644
index 0965a27..0000000
--- a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2
+++ /dev/null
@@ -1,12 +0,0 @@
-#
-# NSEC3 RDATA with a bogus RDLEN (too short)
-#
-
-# RDLENGTH, 29 bytes (should be 39)
-00 1e
-
-# NSEC3 record:
-# 1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 NS SOA RRSIG DNSKEY NSEC3PARAM
-01 01 00 01 04 d3 99 ea ab 14 8a 77 c7 ac ef cb
-c5 54 46 03 2b 2d 96 1c c5 eb 68 21 ef 26 00 07
-22 00 00 00 00 02 90
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec
new file mode 100644
index 0000000..0b6a5af
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec
@@ -0,0 +1,9 @@
+#
+# A malformed NSEC3 RDATA: RDLEN indicates it doesn't even contain the fixed
+# 5 octects
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+rdlen: 4
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.spec
new file mode 100644
index 0000000..06d6eb4
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.spec
@@ -0,0 +1,9 @@
+#
+# A malformed NSEC3 RDATA: bit map length is too large, causing overflow
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+maplen: 31
+bitmap: '01'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.spec
new file mode 100644
index 0000000..2d5713c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.spec
@@ -0,0 +1,13 @@
+#
+# A malformed NSEC3 RDATA: incomplete bit map field
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+# only containing the block field of the bitmap
+rdlen: 32
+#dummy data
+maplen: 31
+#dummy data
+bitmap: '00'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.spec
new file mode 100644
index 0000000..36e9e59
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.spec
@@ -0,0 +1,11 @@
+#
+# A malformed NSEC3 RDATA: bit map length being 0
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+rdlen: 33
+maplen: 0
+# dummy data:
+bitmap: '01'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.spec
new file mode 100644
index 0000000..338c0c9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.spec
@@ -0,0 +1,9 @@
+#
+# NSEC3 RDATA with a longest bitmap field (32 bitmap bytes)
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+maplen: 32
+bitmap: '0101010101010101010101010101010101010101010101010101010101010101'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.spec
new file mode 100644
index 0000000..041714e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.spec
@@ -0,0 +1,9 @@
+#
+# An invalid NSEC3 RDATA with an oversized bitmap field (33 bitmap bytes)
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+maplen: 33
+bitmap: '010101010101010101010101010101010101010101010101010101010101010101'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.spec
new file mode 100644
index 0000000..b04c84f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.spec
@@ -0,0 +1,10 @@
+#
+# An invalid NSEC3 RDATA: disordered bitmap blocks
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+nbitmap: 2
+block0: 2
+block1: 1
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire1.spec
new file mode 100644
index 0000000..a30c371
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire1.spec
@@ -0,0 +1,6 @@
+#
+# A simplest form of TSIG: all default parameters
+#
+[custom]
+sections: tsig
+[tsig]
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire2.spec
new file mode 100644
index 0000000..d1e49a5
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire2.spec
@@ -0,0 +1,8 @@
+#
+# TSIG with other data (error = BADTIME(18))
+#
+[custom]
+sections: tsig
+[tsig]
+mac_size: 0
+error: 18
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire3.spec
new file mode 100644
index 0000000..57f8e83
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire3.spec
@@ -0,0 +1,8 @@
+#
+# TSIG without MAC (error = BADSIG(16))
+#
+[custom]
+sections: tsig
+[tsig]
+mac_size: 0
+error: 16
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire4.spec
new file mode 100644
index 0000000..8c38e9e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire4.spec
@@ -0,0 +1,11 @@
+#
+# A simplest form of TSIG, but the algorithm name is compressed (quite
+# pathological, but we accept it)
+#
+[custom]
+sections: name:tsig
+[name]
+name: hmac-sha256
+[tsig]
+algorithm: ptr=0
+mac_size: 32
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire5.spec
new file mode 100644
index 0000000..da90b18
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire5.spec
@@ -0,0 +1,7 @@
+#
+# TSIG-like RDATA but RDLEN is too short.
+#
+[custom]
+sections: tsig
+[tsig]
+rdlen: 60
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire6.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire6.spec
new file mode 100644
index 0000000..9d2f627
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire6.spec
@@ -0,0 +1,7 @@
+#
+# TSIG-like RDATA but RDLEN is too long.
+#
+[custom]
+sections: tsig
+[tsig]
+rdlen: 63
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire7.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire7.spec
new file mode 100644
index 0000000..ed7a81c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire7.spec
@@ -0,0 +1,8 @@
+#
+# TSIG-like RDATA but algorithm name is broken.
+#
+[custom]
+sections: tsig
+[tsig]
+algorithm: "01234567890123456789012345678901234567890123456789012345678901234"
+mac_size: 32
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire8.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire8.spec
new file mode 100644
index 0000000..0b44f87
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire8.spec
@@ -0,0 +1,8 @@
+#
+# TSIG-like RDATA but MAC size is bogus
+#
+[custom]
+sections: tsig
+[tsig]
+mac_size: 65535
+mac: "dummy data"
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire9.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire9.spec
new file mode 100644
index 0000000..f512fb4
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire9.spec
@@ -0,0 +1,8 @@
+#
+# TSIG-like RDATA but Other-Data length is bogus
+#
+[custom]
+sections: tsig
+[tsig]
+other_len: 65535
+otherdata: "dummy data"
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire1.spec b/src/lib/dns/tests/testdata/rdata_tsig_toWire1.spec
new file mode 100644
index 0000000..eb74000
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire1.spec
@@ -0,0 +1,11 @@
+#
+# An artificial TSIG RDATA for toWire test.
+#
+[custom]
+sections: tsig
+[tsig]
+algorithm: hmac-md5
+time_signed: 1286779327
+mac_size: 0
+original_id: 16020
+error: 17
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire2.spec b/src/lib/dns/tests/testdata/rdata_tsig_toWire2.spec
new file mode 100644
index 0000000..b2c38e9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire2.spec
@@ -0,0 +1,13 @@
+#
+# An artificial TSIG RDATA for toWire test.
+#
+[custom]
+sections: tsig
+[tsig]
+algorithm: hmac-sha256
+time_signed: 1286779327
+mac_size: 12
+# 0x1402... would be FAKEFAKE... if encoded in BASE64
+mac: 0x140284140284140284140284
+original_id: 16020
+error: 16
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire3.spec b/src/lib/dns/tests/testdata/rdata_tsig_toWire3.spec
new file mode 100644
index 0000000..6520a08
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire3.spec
@@ -0,0 +1,15 @@
+#
+# An artificial TSIG RDATA for toWire test.
+#
+[custom]
+sections: tsig
+[tsig]
+algorithm: hmac-sha1
+time_signed: 1286779327
+mac_size: 12
+# 0x1402... would be FAKEFAKE... if encoded in BASE64
+mac: 0x140284140284140284140284
+original_id: 16020
+error: 18
+other_len: 6
+other_data: 0x140284140284
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire4.spec b/src/lib/dns/tests/testdata/rdata_tsig_toWire4.spec
new file mode 100644
index 0000000..d95cd23
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire4.spec
@@ -0,0 +1,13 @@
+#
+# An artificial TSIG RDATA for toWire test.
+#
+[custom]
+sections: name:tsig
+[name]
+name: hmac-md5.sig-alg.reg.int.
+[tsig]
+algorithm: hmac-md5
+time_signed: 1286779327
+mac_size: 0
+original_id: 16020
+error: 17
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire5.spec b/src/lib/dns/tests/testdata/rdata_tsig_toWire5.spec
new file mode 100644
index 0000000..81e3a78
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire5.spec
@@ -0,0 +1,13 @@
+#
+# An artificial TSIG RDATA for toWire test.
+#
+[custom]
+sections: tsig:name
+[tsig]
+algorithm: hmac-md5
+time_signed: 1286779327
+mac_size: 0
+original_id: 16020
+error: 17
+[name]
+name: ptr=2
diff --git a/src/lib/dns/tests/tsig_unittest.cc b/src/lib/dns/tests/tsig_unittest.cc
deleted file mode 100644
index 420acbe..0000000
--- a/src/lib/dns/tests/tsig_unittest.cc
+++ /dev/null
@@ -1,41 +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.
-
-// $Id: rrtype_unittest.cc 476 2010-01-19 00:29:28Z jinmei $
-
-#include <gtest/gtest.h>
-
-#include <dns/tsig.h>
-
-#include <dns/tests/unittest_util.h>
-
-using isc::UnitTestUtil;
-using namespace std;
-using namespace isc::dns;
-
-namespace {
-class TsigTest : public ::testing::Test {
-protected:
- TsigTest() {}
-};
-
-// simple creation test to get the testing ball rolling
-TEST_F(TsigTest, creates) {
- Tsig tsig(Name("example.com"), Tsig::HMACMD5, "someRandomData");
- EXPECT_TRUE(1);
-}
-
-} // end namespace
-
-
diff --git a/src/lib/dns/tests/tsigkey_unittest.cc b/src/lib/dns/tests/tsigkey_unittest.cc
new file mode 100644
index 0000000..6b2b8c5
--- /dev/null
+++ b/src/lib/dns/tests/tsigkey_unittest.cc
@@ -0,0 +1,230 @@
+// 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 <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/tsigkey.h>
+
+#include <dns/tests/unittest_util.h>
+
+using namespace std;
+using namespace isc::dns;
+using isc::UnitTestUtil;
+
+namespace {
+class TSIGKeyTest : public ::testing::Test {
+protected:
+ TSIGKeyTest() : secret("someRandomData"), key_name("example.com") {}
+ string secret;
+ Name key_name;
+};
+
+TEST_F(TSIGKeyTest, algorithmNames) {
+ EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), TSIGKey::HMACMD5_NAME());
+ EXPECT_EQ(Name("hmac-sha1"), TSIGKey::HMACSHA1_NAME());
+ EXPECT_EQ(Name("hmac-sha256"), TSIGKey::HMACSHA256_NAME());
+}
+
+TEST_F(TSIGKeyTest, construct) {
+ TSIGKey key(key_name, TSIGKey::HMACMD5_NAME(),
+ secret.c_str(), secret.size());
+ EXPECT_EQ(key_name, key.getKeyName());
+ EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), key.getAlgorithmName());
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret.c_str(),
+ secret.size(), key.getSecret(), key.getSecretLength());
+
+ EXPECT_THROW(TSIGKey(key_name, Name("unknown-alg"),
+ secret.c_str(), secret.size()),
+ isc::InvalidParameter);
+
+ // The algorithm name should be converted to the canonical form.
+ EXPECT_EQ("hmac-sha1.",
+ TSIGKey(key_name, Name("HMAC-sha1"),
+ secret.c_str(),
+ secret.size()).getAlgorithmName().toText());
+
+ // Invalid combinations of secret and secret_len:
+ EXPECT_THROW(TSIGKey(key_name, TSIGKey::HMACSHA1_NAME(), secret.c_str(), 0),
+ isc::InvalidParameter);
+ EXPECT_THROW(TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(), NULL, 16),
+ isc::InvalidParameter);
+}
+
+void
+compareTSIGKeys(const TSIGKey& expect, const TSIGKey& actual) {
+ EXPECT_EQ(expect.getKeyName(), actual.getKeyName());
+ EXPECT_EQ(expect.getAlgorithmName(), actual.getAlgorithmName());
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ expect.getSecret(), expect.getSecretLength(),
+ actual.getSecret(), actual.getSecretLength());
+}
+
+TEST_F(TSIGKeyTest, copyConstruct) {
+ const TSIGKey original(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret.c_str(), secret.size());
+ const TSIGKey copy(original);
+ compareTSIGKeys(original, copy);
+
+ // Check the copied data is valid even after the original is deleted
+ TSIGKey* copy2 = new TSIGKey(original);
+ TSIGKey copy3(*copy2);
+ delete copy2;
+ compareTSIGKeys(original, copy3);
+}
+
+TEST_F(TSIGKeyTest, assignment) {
+ const TSIGKey original(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret.c_str(), secret.size());
+ TSIGKey copy = original;
+ compareTSIGKeys(original, copy);
+
+ // Check if the copied data is valid even after the original is deleted
+ TSIGKey* copy2 = new TSIGKey(original);
+ TSIGKey copy3(original);
+ copy3 = *copy2;
+ delete copy2;
+ compareTSIGKeys(original, copy3);
+
+ // self assignment
+ copy = copy;
+ compareTSIGKeys(original, copy);
+}
+
+class TSIGKeyRingTest : public ::testing::Test {
+protected:
+ TSIGKeyRingTest() :
+ key_name("example.com"),
+ secretstring("anotherRandomData"),
+ secret(secretstring.c_str()),
+ secret_len(secretstring.size())
+ {}
+ TSIGKeyRing keyring;
+ Name key_name;
+private:
+ const string secretstring;
+protected:
+ const char* secret;
+ size_t secret_len;
+};
+
+TEST_F(TSIGKeyRingTest, init) {
+ EXPECT_EQ(0, keyring.size());
+}
+
+TEST_F(TSIGKeyRingTest, add) {
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(1, keyring.size());
+ EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add(
+ TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret, secret_len)));
+ // keys are identified their names, the same name of key with a different
+ // algorithm would be considered a duplicate.
+ EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add(
+ TSIGKey(Name("example.com"), TSIGKey::HMACSHA1_NAME(),
+ secret, secret_len)));
+ // names are compared in a case insensitive manner.
+ EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add(
+ TSIGKey(Name("EXAMPLE.COM"), TSIGKey::HMACSHA1_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(1, keyring.size());
+}
+
+TEST_F(TSIGKeyRingTest, addMore) {
+ // essentially the same test, but try adding more than 1
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(Name("another.example"), TSIGKey::HMACMD5_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(Name("more.example"), TSIGKey::HMACSHA1_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(3, keyring.size());
+}
+
+TEST_F(TSIGKeyRingTest, remove) {
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.remove(key_name));
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.remove(key_name));
+}
+
+TEST_F(TSIGKeyRingTest, removeFromSome) {
+ // essentially the same test, but try removing from a larger set
+
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(Name("another.example"), TSIGKey::HMACMD5_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(Name("more.example"), TSIGKey::HMACSHA1_NAME(),
+ secret, secret_len)));
+
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.remove(Name("another.example")));
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.remove(Name("noexist.example")));
+ EXPECT_EQ(2, keyring.size());
+}
+
+TEST_F(TSIGKeyRingTest, find) {
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.find(key_name).code);
+ EXPECT_EQ(static_cast<const TSIGKey*>(NULL), keyring.find(key_name).key);
+
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret, secret_len)));
+ const TSIGKeyRing::FindResult result(keyring.find(key_name));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, result.code);
+ EXPECT_EQ(key_name, result.key->getKeyName());
+ EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result.key->getAlgorithmName());
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret, secret_len,
+ result.key->getSecret(),
+ result.key->getSecretLength());
+}
+
+TEST_F(TSIGKeyRingTest, findFromSome) {
+ // essentially the same test, but search a larger set
+
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(Name("another.example"), TSIGKey::HMACMD5_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(Name("more.example"), TSIGKey::HMACSHA1_NAME(),
+ secret, secret_len)));
+
+ const TSIGKeyRing::FindResult result(
+ keyring.find(Name("another.example")));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, result.code);
+ EXPECT_EQ(Name("another.example"), result.key->getKeyName());
+ EXPECT_EQ(TSIGKey::HMACMD5_NAME(), result.key->getAlgorithmName());
+
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND,
+ keyring.find(Name("noexist.example")).code);
+ EXPECT_EQ(static_cast<const TSIGKey*>(NULL),
+ keyring.find(Name("noexist.example")).key);
+}
+
+} // end namespace
diff --git a/src/lib/dns/tests/unittest_util.cc b/src/lib/dns/tests/unittest_util.cc
index 90b0bdf..c9c0982 100644
--- a/src/lib/dns/tests/unittest_util.cc
+++ b/src/lib/dns/tests/unittest_util.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <config.h>
#include <iostream>
@@ -25,13 +23,15 @@
#include <gtest/gtest.h>
+#include <dns/rcode.h>
#include <dns/name.h>
+#include <dns/message.h>
#include <dns/tests/unittest_util.h>
using namespace std;
+using namespace isc::dns;
using isc::UnitTestUtil;
-using isc::dns::NameComparisonResult;
namespace {
class UnitTestUtilConfig {
@@ -132,10 +132,7 @@ UnitTestUtil::readWireData(const string& datastr,
}
::testing::AssertionResult
-UnitTestUtil::matchWireData(const char* dataexp1 UNUSED_PARAM,
- const char* lenexp1 UNUSED_PARAM,
- const char* dataexp2 UNUSED_PARAM,
- const char* lenexp2 UNUSED_PARAM,
+UnitTestUtil::matchWireData(const char*, const char*, const char*, const char*,
const void* data1, size_t len1,
const void* data2, size_t len2)
{
@@ -162,8 +159,7 @@ UnitTestUtil::matchWireData(const char* dataexp1 UNUSED_PARAM,
}
::testing::AssertionResult
-UnitTestUtil::matchName(const char* nameexp1 UNUSED_PARAM,
- const char* nameexp2 UNUSED_PARAM,
+UnitTestUtil::matchName(const char*, const char*,
const isc::dns::Name& name1,
const isc::dns::Name& name2)
{
@@ -179,3 +175,19 @@ UnitTestUtil::matchName(const char* nameexp1 UNUSED_PARAM,
}
return (::testing::AssertionSuccess());
}
+
+void
+UnitTestUtil::createRequestMessage(Message& message,
+ const Opcode& opcode,
+ const uint16_t qid,
+ const Name& name,
+ const RRClass& rrclass,
+ const RRType& rrtype)
+{
+ message.clear(Message::RENDER);
+ message.setOpcode(opcode);
+ message.setRcode(Rcode::NOERROR());
+ message.setQid(qid);
+ message.addQuestion(Question(name, rrclass, rrtype));
+}
+
diff --git a/src/lib/dns/tests/unittest_util.h b/src/lib/dns/tests/unittest_util.h
index a585426..f85a921 100644
--- a/src/lib/dns/tests/unittest_util.h
+++ b/src/lib/dns/tests/unittest_util.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __UNITTEST_UTIL_H
#define __UNITTEST_UTIL_H 1
@@ -21,6 +19,7 @@
#include <string>
#include <dns/name.h>
+#include <dns/message.h>
#include <gtest/gtest.h>
@@ -80,6 +79,20 @@ public:
static ::testing::AssertionResult
matchName(const char* nameexp1, const char* nameexp2,
const isc::dns::Name& name1, const isc::dns::Name& name2);
+
+ ///
+ /// Populate a request message
+ ///
+ /// Create a request message in 'request_message' using the
+ /// opcode 'opcode' and the name/class/type query tuple specified in
+ /// 'name', 'rrclass' and 'rrtype.
+ static void
+ createRequestMessage(isc::dns::Message& request_message,
+ const isc::dns::Opcode& opcode,
+ const uint16_t qid,
+ const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype);
};
}
#endif // __UNITTEST_UTIL_H
diff --git a/src/lib/dns/tsig.cc b/src/lib/dns/tsig.cc
deleted file mode 100644
index a5f2370..0000000
--- a/src/lib/dns/tsig.cc
+++ /dev/null
@@ -1,33 +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.
-
-// $Id$
-
-#include <cctype>
-#include <cassert>
-#include <iterator>
-#include <functional>
-
-#include <algorithm>
-
-#include <dns/tsig.h>
-
-using namespace std;
-using isc::dns::MessageRenderer;
-
-namespace isc {
-namespace dns {
-
-} // namespace dns
-} // namespace isc
diff --git a/src/lib/dns/tsig.h b/src/lib/dns/tsig.h
deleted file mode 100644
index 7b09fd8..0000000
--- a/src/lib/dns/tsig.h
+++ /dev/null
@@ -1,72 +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.
-
-// $Id$
-
-#ifndef __TSIG_H
-#define __TSIG_H 1
-
-#include <string>
-#include <vector>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/name.h>
-#include <dns/message.h>
-
-namespace isc {
-namespace dns {
-
-class BadTsigKey : public Exception {
-public:
- BadTsigKey(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-//
-// This class holds a Tsig key, including all its attributes.
-//
-class Tsig {
-public:
- enum TsigAlgorithm {
- HMACMD5 = 0,
- GSS = 1,
- HMACSHA1 = 2,
- HMACSHA224 = 3,
- HMACSHA265 = 4,
- HMACSHA384 = 5,
- HMACSHA512 = 6,
- };
-
- Tsig(const Name& name, TsigAlgorithm algorithm,
- const std::string& algorithm_data) :
- name_(name), algorithm_(algorithm), algorithm_data_(algorithm_data) {};
-
- bool signMessage(const Message& message);
- bool verifyMessage(const Message &message);
-
-private:
- Name name_;
- TsigAlgorithm algorithm_;
- std::string algorithm_data_;
-};
-
-}
-}
-
-#endif // __TSIG_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/dns/tsigkey.cc b/src/lib/dns/tsigkey.cc
new file mode 100644
index 0000000..057191d
--- /dev/null
+++ b/src/lib/dns/tsigkey.cc
@@ -0,0 +1,165 @@
+// 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 <map>
+#include <utility>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/tsigkey.h>
+
+using namespace std;
+
+namespace isc {
+namespace dns {
+struct
+TSIGKey::TSIGKeyImpl {
+ TSIGKeyImpl(const Name& key_name, const Name& algorithm_name,
+ const void* secret, size_t secret_len) :
+ key_name_(key_name), algorithm_name_(algorithm_name),
+ secret_(static_cast<const uint8_t*>(secret),
+ static_cast<const uint8_t*>(secret) + secret_len)
+ {
+ // Convert the name to the canonical form.
+ algorithm_name_.downcase();
+ }
+ const Name key_name_;
+ Name algorithm_name_;
+ const vector<uint8_t> secret_;
+};
+
+TSIGKey::TSIGKey(const Name& key_name, const Name& algorithm_name,
+ const void* secret, size_t secret_len) : impl_(NULL)
+{
+ if (algorithm_name != HMACMD5_NAME() &&
+ algorithm_name != HMACSHA1_NAME() &&
+ algorithm_name != HMACSHA256_NAME()) {
+ isc_throw(InvalidParameter, "Unknown TSIG algorithm is specified: " <<
+ algorithm_name);
+ }
+ if ((secret != NULL && secret_len == 0) ||
+ (secret == NULL && secret_len != 0)) {
+ isc_throw(InvalidParameter,
+ "TSIGKey secret and its length are inconsistent");
+ }
+
+ impl_ = new TSIGKeyImpl(key_name, algorithm_name, secret, secret_len);
+}
+
+TSIGKey::TSIGKey(const TSIGKey& source) : impl_(new TSIGKeyImpl(*source.impl_))
+{}
+
+TSIGKey&
+TSIGKey::operator=(const TSIGKey& source) {
+ if (impl_ == source.impl_) {
+ return (*this);
+ }
+
+ TSIGKeyImpl* newimpl = new TSIGKeyImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+TSIGKey::~TSIGKey() {
+ delete impl_;
+}
+
+const Name&
+TSIGKey::getKeyName() const {
+ return (impl_->key_name_);
+}
+
+const Name&
+TSIGKey::getAlgorithmName() const {
+ return (impl_->algorithm_name_);
+}
+
+const void*
+TSIGKey::getSecret() const {
+ return ((impl_->secret_.size() > 0) ? &impl_->secret_[0] : NULL);
+}
+
+size_t
+TSIGKey::getSecretLength() const {
+ return (impl_->secret_.size());
+}
+
+const
+Name& TSIGKey::HMACMD5_NAME() {
+ static Name alg_name("hmac-md5.sig-alg.reg.int");
+ return (alg_name);
+}
+
+const
+Name& TSIGKey::HMACSHA1_NAME() {
+ static Name alg_name("hmac-sha1");
+ return (alg_name);
+}
+
+const
+Name& TSIGKey::HMACSHA256_NAME() {
+ static Name alg_name("hmac-sha256");
+ return (alg_name);
+}
+
+struct TSIGKeyRing::TSIGKeyRingImpl {
+ typedef map<Name, TSIGKey> TSIGKeyMap;
+ typedef pair<Name, TSIGKey> NameAndKey;
+ TSIGKeyMap keys;
+};
+
+TSIGKeyRing::TSIGKeyRing() : impl_(new TSIGKeyRingImpl) {
+}
+
+TSIGKeyRing::~TSIGKeyRing() {
+ delete impl_;
+}
+
+unsigned int
+TSIGKeyRing::size() const {
+ return (impl_->keys.size());
+}
+
+TSIGKeyRing::Result
+TSIGKeyRing::add(const TSIGKey& key) {
+ if (impl_->keys.insert(
+ TSIGKeyRingImpl::NameAndKey(key.getKeyName(), key)).second
+ == true) {
+ return (SUCCESS);
+ } else {
+ return (EXIST);
+ }
+}
+
+TSIGKeyRing::Result
+TSIGKeyRing::remove(const Name& key_name) {
+ return (impl_->keys.erase(key_name) == 1 ? SUCCESS : NOTFOUND);
+}
+
+TSIGKeyRing::FindResult
+TSIGKeyRing::find(const Name& key_name) {
+ TSIGKeyRingImpl::TSIGKeyMap::const_iterator found =
+ impl_->keys.find(key_name);
+ if (found == impl_->keys.end()) {
+ return (FindResult(NOTFOUND, NULL));
+ }
+ return (FindResult(SUCCESS, &((*found).second)));
+}
+
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/tsigkey.h b/src/lib/dns/tsigkey.h
new file mode 100644
index 0000000..e56fa88
--- /dev/null
+++ b/src/lib/dns/tsigkey.h
@@ -0,0 +1,298 @@
+// 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 __TSIGKEY_H
+#define __TSIGKEY_H 1
+
+namespace isc {
+namespace dns {
+
+class Name;
+
+/// \brief TSIG key.
+///
+/// This class holds a TSIG key along with some related attributes as
+/// defined in RFC2845.
+///
+/// A TSIG key consists of the following attributes:
+/// - Key name
+/// - Hash algorithm
+/// - Shared secret
+///
+/// <b>Implementation Notes</b>
+///
+/// We may add more attributes in future versions. For example, if and when
+/// we support the TKEY protocol (RFC2930), we may need to introduce the
+/// notion of inception and expiration times.
+/// At that point we may also have to introduce a class hierarchy to handle
+/// different types of keys in a polymorphic way.
+/// At the moment we use the straightforward value-type class with minimal
+/// attributes.
+///
+/// In the TSIG protocol, hash algorithms are represented in the form of
+/// domain name.
+/// Our interfaces provide direct translation of this concept; for example,
+/// the constructor from parameters take a \c Name object to specify the
+/// algorithm.
+/// On one hand, this may be counter intuitive.
+/// An API user would rather specify "hmac-md5" instead of
+/// <code>Name("hmac-md5.sig-alg.reg.int")</code>.
+/// On the other hand, it may be more convenient for some kind of applications
+/// if we maintain the algorithm as the expected representation for
+/// protocol operations (such as sign and very a message).
+/// Considering these points, we adopt the interface closer to the protocol
+/// specification for now.
+/// To minimize the burden for API users, we also define a set of constants
+/// for commonly used algorithm names so that the users don't have to
+/// remember the actual domain names defined in the protocol specification.
+/// We may also have to add conversion routines between domain names
+/// and more intuitive representations (e.g. strings) for algorithms.
+class TSIGKey {
+public:
+ ///
+ /// \name Constructors, Assignment Operator and Destructor.
+ ///
+ //@{
+ /// \brief Constructor from key parameters
+ ///
+ /// In the current implementation, \c algorithm_name must be a known
+ /// algorithm to this implementation, which are defined via the
+ /// <code>static const</code> member functions. For other names
+ /// an exception of class \c InvalidParameter will be thrown.
+ /// Note: This restriction may be too strict, and we may revisit it
+ /// later.
+ ///
+ /// \c secret and \c secret_len must be consistent in that the latter
+ /// is 0 if and only if the former is \c NULL;
+ /// otherwise an exception of type \c InvalidParameter will be thrown.
+ ///
+ /// This constructor internally involves resource allocation, and if
+ /// it fails, a corresponding standard exception will be thrown.
+ ///
+ /// \param key_name The name of the key as a domain name.
+ /// \param algorithm_name The hash algorithm used for this key in the
+ /// form of domain name. For example, it can be
+ /// \c TSIGKey::HMACSHA256_NAME() for HMAC-SHA256.
+ /// \param secret Point to a binary sequence of the shared secret to be
+ /// used for this key, or \c NULL if the secret is empty.
+ /// \param secret_len The size of the binary %data (\c secret) in bytes.
+ TSIGKey(const Name& key_name, const Name& algorithm_name,
+ const void* secret, size_t secret_len);
+
+ /// \brief The copy constructor.
+ ///
+ /// It internally allocates a resource, and if it fails a corresponding
+ /// standard exception will be thrown.
+ /// This constructor never throws an exception otherwise.
+ TSIGKey(const TSIGKey& source);
+
+ /// \brief Assignment operator.
+ ///
+ /// It internally allocates a resource, and if it fails a corresponding
+ /// standard exception will be thrown.
+ /// This operator never throws an exception otherwise.
+ ///
+ /// This operator provides the strong exception guarantee: When an
+ /// exception is thrown the content of the assignment target will be
+ /// intact.
+ TSIGKey& operator=(const TSIGKey& source);
+
+ /// The destructor.
+ ~TSIGKey();
+ //@}
+
+ ///
+ /// \name Getter Methods
+ ///
+ /// These methods never throw an exception.
+ //@{
+ /// Return the key name.
+ const Name& getKeyName() const;
+
+ /// Return the algorithm name.
+ const Name& getAlgorithmName() const;
+
+ /// Return the length of the TSIG secret in bytes.
+ size_t getSecretLength() const;
+
+ /// Return the value of the TSIG secret.
+ ///
+ /// If it returns a non NULL pointer, the memory region beginning at the
+ /// address returned by this method is valid up to the bytes specified
+ /// by the return value of \c getSecretLength().
+ ///
+ /// The memory region is only valid while the corresponding \c TSIGKey
+ /// object is valid. The caller must hold the \c TSIGKey object while
+ /// it needs to refer to the region or it must make a local copy of the
+ /// region.
+ const void* getSecret() const;
+ //@}
+
+ ///
+ /// \name Well known algorithm names as defined in RFC2845 and RFC4635.
+ ///
+ /// Note: we begin with the "mandatory" algorithms defined in RFC4635
+ /// as a minimal initial set.
+ /// We'll add others as we see the need for them.
+ //@{
+ static const Name& HMACMD5_NAME(); ///< HMAC-MD5 (RFC2845)
+ static const Name& HMACSHA1_NAME(); ///< HMAC-SHA1 (RFC4635)
+ static const Name& HMACSHA256_NAME(); ///< HMAC-SHA256 (RFC4635)
+ //@}
+
+private:
+ struct TSIGKeyImpl;
+ const TSIGKeyImpl* impl_;
+};
+
+/// \brief A simple repository of a set of \c TSIGKey objects.
+///
+/// This is a "key ring" to maintain TSIG keys (\c TSIGKey objects) and
+/// provides trivial operations such as add, remove, and find.
+///
+/// The keys are identified by their key names.
+/// So, for example, two or more keys of the same key name but of different
+/// algorithms are considered to be the same, and cannot be stored in the
+/// key ring at the same time.
+///
+/// <b>Implementation Note:</b>
+/// For simplicity the initial implementation requests the application make
+/// a copy of keys stored in the key ring if it needs to use the keys for
+/// a long period (during which some of the keys may be removed).
+/// This is based on the observations that a single server will not hold
+/// a huge number of keys nor use keys in many different contexts (such as
+/// in different DNS transactions).
+/// If this assumption does not hold and memory consumption becomes an issue
+/// we may have to revisit the design.
+class TSIGKeyRing {
+public:
+ /// Result codes of various public methods of \c TSIGKeyRing
+ enum Result {
+ SUCCESS = 0, ///< The operation is successful.
+ EXIST = 1, ///< A key is already stored in \c TSIGKeyRing.
+ NOTFOUND = 2 ///< The specified key is not found in \c TSIGKeyRing.
+ };
+
+ /// \brief A helper structure to represent the search result of
+ /// <code>TSIGKeyRing::find()</code>.
+ ///
+ /// This is a straightforward pair of the result code and a pointer
+ /// to the found key to represent the result of \c find().
+ /// We use this in order to avoid overloading the return value for both
+ /// the result code ("success" or "not found") and the found object,
+ /// i.e., avoid using \c NULL to mean "not found", etc.
+ ///
+ /// This is a simple value class with no internal state, so for
+ /// convenience we allow the applications to refer to the members
+ /// directly.
+ ///
+ /// See the description of \c find() for the semantics of the member
+ /// variables.
+ struct FindResult {
+ FindResult(Result param_code, const TSIGKey* param_key) :
+ code(param_code), key(param_key)
+ {}
+ const Result code;
+ const TSIGKey* const key;
+ };
+
+ ///
+ /// \name Constructors and Destructor.
+ ///
+ /// \b Note:
+ /// The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non copyable.
+ /// There is no technical reason why this class cannot be copied,
+ /// but since the key ring can potentially have a large number of keys,
+ /// a naive copy operation may cause unexpected overhead.
+ /// It's generally expected for an application to share the same
+ /// instance of key ring and share it throughout the program via
+ /// references, so we prevent the copy operation explicitly to avoid
+ /// unexpected copy operations.
+ //@{
+private:
+ TSIGKeyRing(const TSIGKeyRing& source);
+ TSIGKeyRing& operator=(const TSIGKeyRing& source);
+public:
+ /// \brief The default constructor.
+ ///
+ /// This constructor never throws an exception.
+ TSIGKeyRing();
+
+ /// The destructor.
+ ~TSIGKeyRing();
+ //@}
+
+ /// Return the number of keys stored in the \c TSIGKeyRing.
+ ///
+ /// This method never throws an exception.
+ unsigned int size() const;
+
+ /// Add a \c TSIGKey to the \c TSIGKeyRing.
+ ///
+ /// This method will create a local copy of the given key, so the caller
+ /// does not have to keep owning it.
+ ///
+ /// If internal resource allocation fails, a corresponding standard
+ /// exception will be thrown.
+ /// This method never throws an exception otherwise.
+ ///
+ /// \param key A \c TSIGKey to be added.
+ /// \return \c SUCCESS If the key is successfully added to the key ring.
+ /// \return \c EXIST The key ring already stores a key whose name is
+ /// identical to that of \c key.
+ Result add(const TSIGKey& key);
+
+ /// Remove a \c TSIGKey for the given name from the \c TSIGKeyRing.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param key_name The name of the key to be removed.
+ /// \return \c SUCCESS If the key is successfully removed from the key
+ /// ring.
+ /// \return \c NOTFOUND The key ring does not store the key that matches
+ /// \c key_name.
+ Result remove(const Name& key_name);
+
+ /// Find a \c TSIGKey for the given name in the \c TSIGKeyRing.
+ ///
+ /// It searches the internal storage for a \c TSIGKey whose name is
+ /// \c key_name, and returns the result in the form of a \c FindResult
+ /// object as follows:
+ /// - \c code: \c SUCCESS if a key is found; otherwise \c NOTFOUND.
+ /// - \c key: A pointer to the found \c TSIGKey object if one is found;
+ /// otherwise \c NULL.
+ ///
+ /// The pointer returned in the \c FindResult object is only valid until
+ /// the corresponding key is removed from the key ring.
+ /// The caller must ensure that the key is held in the key ring while
+ /// it needs to refer to it, or it must make a local copy of the key.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param key_name The name of the key to be found.
+ /// \return A \c FindResult object enclosing the search result (see above).
+ FindResult find(const Name& key_name);
+private:
+ struct TSIGKeyRingImpl;
+ TSIGKeyRingImpl* impl_;
+};
+}
+}
+
+#endif // __TSIGKEY_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/util/base32hex.h b/src/lib/dns/util/base32hex.h
index 95df331..cba172e 100644
--- a/src/lib/dns/util/base32hex.h
+++ b/src/lib/dns/util/base32hex.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __BASE32HEX_H
#define __BASE32HEX_H 1
diff --git a/src/lib/dns/util/base64.h b/src/lib/dns/util/base64.h
index ab2326e..46e10a6 100644
--- a/src/lib/dns/util/base64.h
+++ b/src/lib/dns/util/base64.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __BASE64_H
#define __BASE64_H 1
diff --git a/src/lib/dns/util/base_n.cc b/src/lib/dns/util/base_n.cc
index 2f799c3..9d0c777 100644
--- a/src/lib/dns/util/base_n.cc
+++ b/src/lib/dns/util/base_n.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdint.h>
#include <cassert>
#include <iterator>
diff --git a/src/lib/dns/util/hex.h b/src/lib/dns/util/hex.h
index d3b3acc..e2626bf 100644
--- a/src/lib/dns/util/hex.h
+++ b/src/lib/dns/util/hex.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __HEX_H
#define __HEX_H 1
diff --git a/src/lib/exceptions/exceptions.cc b/src/lib/exceptions/exceptions.cc
index 10ddad6..2a374da 100644
--- a/src/lib/exceptions/exceptions.cc
+++ b/src/lib/exceptions/exceptions.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <string>
#include <exceptions/exceptions.h>
diff --git a/src/lib/exceptions/exceptions.h b/src/lib/exceptions/exceptions.h
index c9d13f5..a42037b 100644
--- a/src/lib/exceptions/exceptions.h
+++ b/src/lib/exceptions/exceptions.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __EXCEPTIONS_H
#define __EXCEPTIONS_H 1
@@ -40,7 +38,7 @@ public:
/// file line number.
///
/// @param file the file name where the exception was thrown.
- /// @param line the line in @ref file where the exception was thrown.
+ /// @param line the line in \a file where the exception was thrown.
/// @param what a description (type) of the exception.
Exception(const char* file, size_t line, const char* what) :
file_(file), line_(line), what_(what) {}
@@ -49,7 +47,7 @@ public:
/// file line number.
///
/// @param file the file name where the exception was thrown.
- /// @param line the line in @ref file where the exception was thrown.
+ /// @param line the line in \a file where the exception was thrown.
/// @param what a description (type) of the exception.
Exception(const char* file, size_t line, const std::string& what) :
file_(file), line_(line), what_(what) {}
diff --git a/src/lib/exceptions/tests/exceptions_unittest.cc b/src/lib/exceptions/tests/exceptions_unittest.cc
index 7d5990c..44cbc17 100644
--- a/src/lib/exceptions/tests/exceptions_unittest.cc
+++ b/src/lib/exceptions/tests/exceptions_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <stdexcept>
#include <string>
diff --git a/src/lib/exceptions/tests/run_unittests.cc b/src/lib/exceptions/tests/run_unittests.cc
index 863ac68..0908071 100644
--- a/src/lib/exceptions/tests/run_unittests.cc
+++ b/src/lib/exceptions/tests/run_unittests.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: run_unittests.cc 476 2010-01-19 00:29:28Z jinmei $
-
#include <gtest/gtest.h>
int
diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am
new file mode 100644
index 0000000..d941b01
--- /dev/null
+++ b/src/lib/log/Makefile.am
@@ -0,0 +1,41 @@
+SUBDIRS = . compiler tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = liblog.la
+liblog_la_SOURCES =
+liblog_la_SOURCES += debug_levels.h logger_levels.h
+liblog_la_SOURCES += dummylog.h dummylog.cc
+liblog_la_SOURCES += filename.h filename.cc
+liblog_la_SOURCES += logger.cc logger.h
+liblog_la_SOURCES += logger_impl.cc logger_impl.h
+liblog_la_SOURCES += logger_support.cc logger_support.h
+liblog_la_SOURCES += messagedef.cc messagedef.h
+liblog_la_SOURCES += message_dictionary.cc message_dictionary.h
+liblog_la_SOURCES += message_exception.h message_exception.cc
+liblog_la_SOURCES += message_initializer.cc message_initializer.h
+liblog_la_SOURCES += message_reader.cc message_reader.h
+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 += 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)
+liblog_la_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_GXX
+liblog_la_CXXFLAGS += -Wno-unused-parameter
+endif
+if USE_CLANGPP
+# Same for clang++, but we need to turn off -Werror completely.
+liblog_la_CXXFLAGS += -Wno-error
+endif
+liblog_la_CPPFLAGS = $(AM_CPPFLAGS)
diff --git a/src/lib/log/README b/src/lib/log/README
new file mode 100644
index 0000000..ed11b5b
--- /dev/null
+++ b/src/lib/log/README
@@ -0,0 +1,376 @@
+This directory holds the first release of the logging system.
+
+Basic Ideas
+===========
+The BIND-10 logging system merges two ideas:
+
+* A hierarchical logging system similar to that used in Java (i.e. log4j)
+* Separation of message definitions and text
+
+
+Hierarchical Logging System
+===========================
+When a program writes a message to the logging system, it does so using an
+instance of the Logger class. As well as performing the write of the message,
+the logger identifies the source of the message: different sources can write
+to different destinations and can log different severities of messages.
+For example, the "cache" logger could write messages of DEBUG severity or
+above to a file while all other components write messages of "INFO" severity
+or above to the Syslog file.
+
+The loggers are hierarchical in that each logger is the child of another
+logger. The top of the hierarchy is the root logger, which does not have
+a parent. The point of the hierarchy is that unless a logger is explicitly
+assigned an attribute (such as severity of message being logger), it picks
+it up from the parent. (In BIND-10, there is the root logger (named after
+the program) and every other logger is a child of that.) So in the example
+above, the INFO/Syslog attributes could be associated with the root logger
+while the DEBUG/file attributes are associated with the "cache" logger.
+
+
+Separation of Messages Definitions And Text
+===========================================
+The reason for this is to allow the message text to be overridden by versions
+in a local language. To do this, each message is identified by an identifier
+e.g. "OPENIN". Within the program, this is the symbol passed to the logging
+system. The logger system uses the symbol as an index into a dictionary to
+retrieve the message associated with it (e.g. "unable to open %s for input").
+substitutes any message parameters (in this example, the string that is an
+invalid filename) and logs it to the destination.
+
+In the BIND-10 system, a set of default messages are linked into the
+program. At run-time. each program reads a message file, updating the
+stored definitions; this updated text is logged. However, to aid support,
+the message identifier so in the example above, the message finally logged
+would be something like:
+
+ OPENIN, unable to open a.txt for input
+
+
+Using The System
+================
+The steps in using the system are:
+
+1. Create a message file. This defines messages by an identification - a
+ mnemonic for the message, typically 6-12 characters long - and a message.
+ The file is described in more detail below.
+
+ Ideally the file should have a file type of ".msg".
+
+2. Run it through the message compiler to produce the .h and .cc files. It
+ is intended that this step be included in the build process. However,
+ for now run the compiler (found in the "compiler" subdirectory) manually.
+ The only argument is the name of the message file: it will produce as
+ output two files, having the same name as the input file but with file
+ types of ".h" and ".cc".
+
+ The compiler is built in the "compiler" subdirectory of the "src/lib/log"
+ directory.
+
+3. Include the .h file in your source code to define message symbols, and
+ make sure that the .cc file is compiled and linked into your program -
+ static initialization will add the symbols to the global dictionary.
+
+4. Declare loggers in your code and use them to log messages. This is
+ described in more detail below.
+
+5. To set the debug level and run-time message file, call initLogger (declared
+ in logger_support.h) in the main program unit. This is a temporary solution
+ for Year 2, and will be replaced at a later date, the information coming
+ from the configuration database.
+
+
+Message Files
+=============
+
+File Contents and Format
+------------------------
+A message file is a file containing message definitions. Typically there
+will be one message file for each component that declares message symbols.
+An example file could be:
+
+-- BEGIN --
+
+# Example message file
+# $ID:$
+
+$PREFIX TEST_
+$NAMESPACE isc::log
+TEST1 message %s is much too large
++ This message is a test for the general message code
+
+UNKNOWN unknown message
++ Issued when the message is unknown.
+
+-- END --
+
+Points to note:
+* Leading and trailing space are trimmed from the line. Although the above
+ example has every line starting at column 1, the lines could be indented
+ if desired.
+
+* Blank lines are ignored.
+
+* Lines starting with "#" are comments are are ignored. Comments must be on
+ a line by themselves - inline comments will be interpreted as part of the
+ text of the line.
+
+* Lines starting $ are directives. At present, two directives are recognised:
+
+ * $PREFIX, which has one argument: the string used to prefix symbols. If
+ absent, there is no prefix to the symbols. (Prefixes are explained below.)
+ * $NAMESPACE, which has one argument: the namespace in which the symbols are
+ created. In the absence of a $NAMESPACE directive, symbols will be put
+ in the global namespace.
+
+* Lines starting + indicate an explanation for the preceding message. These
+ are intended to be processed by a separate program and used to generate
+ an error messages manual. However they are treated like comments by the
+ message compiler. As with comments, these must be on a line by themselves;
+ if inline, the text (including the leading "+") will be interpreted as
+ part of the line.
+
+* Message lines. These comprise a symbol name and a message, which may
+ include zero or more printf-style tokens. Symbol names will be upper-cased
+ by the compiler.
+
+
+Message Compiler
+----------------
+The message compiler is a program built in the src/log/compiler directory.
+It is invoked by the command:
+
+ message [-h] [-v] <message-file>
+
+("-v" prints the version number and exits; "-h" prints brief help text.)
+The message compiler processes the message file to produce two files:
+
+1) A C++ header file (called <message-file-name>.h) that holds lines of
+the form:
+
+ namespace <namespace> {
+ extern const isc::log::MessageID PREFIX_IDENTIFIER;
+ :
+ }
+
+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 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).
+
+2) A C++ source file (called <message-file-name>.cc) that holds the definitions
+of the global symbols and code to insert the symbols and messages into the map.
+
+Symbols are defined to be equal to strings holding the identifier, e.g.
+
+ extern const isc::log::MessageID MSG_DUPLNS = "DUPLNS";
+
+(The implementation allows symbols to be compared. However, use of strings
+should not be assumed - a future implementation may change this.)
+
+In addition, the file declares an array of identifiers/messages and an object
+to add them to the global dictionary:
+
+ namespace {
+ const char* values[] = {
+ identifier1, text1,
+ identifier2, text2,
+ :
+ NULL
+ };
+
+ const isc::log::MessageInitializer initializer(values);
+ }
+
+The constructor of the MessageInitializer object retrieves the singleton
+global Dictionary object (created using standard methods to avoid the
+"static initialization fiasco") and adds each identifier and text to it.
+A check is made as each is added; if the identifier already exists, it is
+added to "overflow" vector; the vector is printed to the main logging output
+when logging is finally enabled (to indicate a programming error).
+
+
+Using the Logging
+=================
+To use the current version of the logging:
+
+1. Build message header file and source file as describe above.
+
+2. In the main module of the program, declare an instance of the
+ RootLoggerName class to define the name of the program's root logger, e.g.
+
+ #include <log/root_logger_name.h>
+
+ isc::log::RootLoggerName("b10-auth");
+
+ This can be declared inside or outside an execution unit.
+
+2. In the code that needs to do logging, declare a logger with a given name,
+ e.g.
+
+ #include <log/logger.h>
+ :
+ isc::log::Logger logger("myname"); // "myname" can be anything
+
+ The above example assumes declaration outside a function. If declaring
+ non-statically within a function, declare it as:
+
+ isc::log::Logger logger("myname", true);
+
+ (The argument is required to support a possible future implementation of
+ logging. Currently it has no effect.)
+
+3. The main program unit should include a call to isc::log::initLogger()
+ (defined in logger_support.h) to set the logging severity, debug log level,
+ and external message file.
+
+ a) The logging severity is one of the enum defined in logger.h, i.e.
+
+ isc::log::DEBUG
+ isc::log::INFO
+ isc::log::WARN
+ isc::log::ERROR
+ isc::log::FATAL
+ isc::log::NONE
+
+ b) The debug log level is only interpreted when the severity is DEBUG and
+ is an integer ranging from 0 to 99. 0 should be used for the
+ highest-level debug messages and 99 for the lowest-level (and typically
+ more verbose) messages.
+
+ c) Name of an external message file. This is the same as a standard message
+ file, although it should not include any directives. (A single directive
+ of a particular type will be ignored; multiple directives will cause the
+ read of the file to fail with an error.) If a message is replaced, the
+ message should include the same printf-format directives in the same order
+ as the original message.
+
+4. Issue logging calls using methods on logger, e.g.
+
+ logger.error(DPS_NSTIMEOUT, "isc.org");
+
+ (where, in the example above we might have defined the symbol in the message
+ file with something along the lines of:
+
+ $PREFIX DPS_
+ :
+ NSTIMEOUT queries to all nameservers for %s have timed out
+
+ At present, the only logging is to the console.
+
+
+Severity Guidelines
+===================
+When using logging, the question arises, what severity should a message be
+logged at? The following is a suggestion - as always, the decision must be
+made in the context of which the message is logged.
+
+FATAL
+-----
+The program has encountered an error that is so severe that it cannot
+continue (or there is no point in continuing). When a fatal error has been
+logged, the program will usually exit immediately (via a call to abort()) or
+shortly afterwards, after dumping some diagnostic information.
+
+ERROR
+-----
+Something has happened such that the program can continue but the results
+for the current (or future) operations cannot be guaranteed to be correct,
+or the results will be correct but the service is impaired. For example,
+the program started but attempts to open one or more network interfaces failed.
+
+WARN
+----
+An unusual event happened. Although the program will continue working
+normally, the event was sufficiently out of the ordinary to warrant drawing
+attention to it. For example, at program start-up a zone was loaded that
+contained no resource records,
+
+INFO
+----
+A normal but significant event has occurred that should be recorded,
+e.g. the program has started or is just about to terminate, a new zone has
+been created, etc.
+
+DEBUG
+-----
+This severity is only enabled on for debugging purposes. A debug level is
+associated with debug messages, level 0 (the default) being for high-level
+messages and level 99 (the maximum) for the lowest level. How the messages
+are distributed between the levels is up to the developer. So if debugging
+the NSAS (for example), a level 0 message might record the creation of a new
+zone, a level 10 recording a timeout when trying to get a nameserver address,
+but a level 50 would record every query for an address. (And we might add
+level 51 to record every update of the RTT.)
+
+Note that like severities, levels are cumulative; so if level 25 is set as the
+debug level, all debug levels from 0 to 25 will be output. In fact, it is
+probably easier to visualise the debug levels as part of the severity system:
+
+ FATAL High
+ ERROR
+ WARN
+ INFO
+ DEBUG level 0
+ DEBUG level 1
+ :
+ DEBUG level 99 Low
+
+When a particular severity is set, it - and all severities and/or debug
+levels above it - will be logged.
+
+Logging Sources v Logging Severities
+------------------------------------
+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 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.
+
+There are two approaches to get round this:
+
+a) Make the logging of packet-dependent events a DEBUG-severity message.
+DEBUG is not enabled by default, so these events will not be recorded unless
+DEBUG is specifically chosen.
+
+b) Record system-related and packet-related messages via different loggers
+(e.g. in the example given, server events could be logged using the logger
+"auth" and packet-related events at that level logged using the logger
+"pkt-auth".) As the loggers are independent and the severity levels
+independent, fine-tuning of what and what is not recorded can be achieved.
+
+
+Notes
+=====
+The message compiler is written in C++ (instead of Python) because it
+contains a component that reads the message file. This component is used
+in both the message compiler and the server; in the server it is used when
+the server starts up (or when triggered by a command) to read in a message
+file to overwrite the internal dictionary. Writing it in C++ means there
+is only one piece of code that does this functionality.
+
+
+Outstanding Issues
+==================
+* Ability to configure system according to configuration database.
+* Update the build procedure to create .cc and .h files from the .msg file
+ during the build process. (Requires that the message compiler is built
+ first.)
+
+
+log4cxx Issues
+==============
+Some experimental code to utilise log4cxx as an underlying implementation
+is present in the source code directory although it is not currently used.
+The files are:
+
+ logger_impl_log4cxx.{cc,h}
+ xdebuglevel.{cc,h}
diff --git a/src/lib/log/compiler/Makefile.am b/src/lib/log/compiler/Makefile.am
new file mode 100644
index 0000000..9343793
--- /dev/null
+++ b/src/lib/log/compiler/Makefile.am
@@ -0,0 +1,18 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+noinst_PROGRAMS = message
+message_SOURCES = message.cc
+message_LDADD = $(top_builddir)/src/lib/log/liblog.la
+
diff --git a/src/lib/log/compiler/message.cc b/src/lib/log/compiler/message.cc
new file mode 100644
index 0000000..6f9c4e0
--- /dev/null
+++ b/src/lib/log/compiler/message.cc
@@ -0,0 +1,546 @@
+// 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 <cctype>
+#include <cstddef>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <errno.h>
+#include <getopt.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <log/filename.h>
+#include <log/message_dictionary.h>
+#include <log/message_exception.h>
+#include <log/message_reader.h>
+#include <log/messagedef.h>
+#include <log/strutil.h>
+
+#include <log/logger.h>
+
+using namespace std;
+using namespace isc::log;
+
+static const char* VERSION = "1.0-0";
+
+/// \brief Message Compiler
+///
+/// \b Overview<BR>
+/// This is the program that takes as input a message file and produces:
+///
+/// \li A .h file containing message definition
+/// \li A .cc file containing code that adds the messages to the program's
+/// message dictionary at start-up time.
+///
+/// Alternatively, the program can produce a .py file that contains the
+/// message definitions.
+///
+
+/// \b Invocation<BR>
+/// The program is invoked with the command:
+///
+/// <tt>message [-v | -h | \<message-file\>]</tt>
+///
+/// It reads the message file and writes out two files of the same name but with
+/// extensions of .h and .cc.
+///
+/// \-v causes it to print the version number and exit. \-h prints a help
+/// message (and exits).
+
+
+/// \brief Print Version
+///
+/// Prints the program's version number.
+
+void
+version() {
+ cout << VERSION << "\n";
+}
+
+/// \brief Print Usage
+///
+/// Prints program usage to stdout.
+
+void
+usage() {
+ cout <<
+ "Usage: message [-h] [-v] <message-file>\n" <<
+ "\n" <<
+ "-h Print this message and exit\n" <<
+ "-v Print the program version and exit\n" <<
+ "\n" <<
+ "<message-file> is the name of the input message file.\n";
+}
+
+
+/// \brief Create Time
+///
+/// Returns the current time as a suitably-formatted string.
+///
+/// \return Current time
+
+string
+currentTime() {
+
+ // Get a text representation of the current time.
+ time_t curtime;
+ time(&curtime);
+ char* buffer = ctime(&curtime);
+
+ // Convert to string and strip out the trailing newline
+ string current_time = buffer;
+ return isc::strutil::trim(current_time);
+}
+
+
+/// \brief Create Header Sentinel
+///
+/// Given the name of a file, create an #ifdef sentinel name. The name is
+/// __<name>_<ext>, where <name> is the name of the file, and <ext> is the
+/// extension less the leading period. The sentinel will be upper-case.
+///
+/// \param file Filename object representing the file.
+///
+/// \return Sentinel name
+
+string
+sentinel(Filename& file) {
+
+ string name = file.name();
+ string ext = file.extension();
+ string sentinel_text = "__" + name + "_" + ext.substr(1);
+ isc::strutil::uppercase(sentinel_text);
+ return sentinel_text;
+}
+
+
+/// \brief Quote String
+///
+/// Inserts an escape character (a backslash) prior to any double quote
+/// characters. This is used to handle the fact that the input file does not
+/// contain quotes, yet the string will be included in a C++ literal string.
+
+string
+quoteString(const string& instring) {
+
+ // Create the output string and reserve the space needed to hold the input
+ // string. (Most input strings will not contain quotes, so this single
+ // reservation should be all that is needed.)
+ string outstring;
+ outstring.reserve(instring.size());
+
+ // Iterate through the input string, preceding quotes with a slash.
+ for (size_t i = 0; i < instring.size(); ++i) {
+ if (instring[i] == '"') {
+ outstring += '\\';
+ }
+ outstring += instring[i];
+ }
+
+ return outstring;
+}
+
+
+/// \brief Sorted Identifiers
+///
+/// Given a dictionary, return a vector holding the message IDs in sorted
+/// order.
+///
+/// \param dictionary Dictionary to examine
+///
+/// \return Sorted list of message IDs
+
+vector<string>
+sortedIdentifiers(MessageDictionary& dictionary) {
+ vector<string> ident;
+
+ for (MessageDictionary::const_iterator i = dictionary.begin();
+ i != dictionary.end(); ++i) {
+ ident.push_back(i->first);
+ }
+ sort(ident.begin(), ident.end());
+
+ return ident;
+}
+
+
+/// \brief Split Namespace
+///
+/// The $NAMESPACE directive may well specify a namespace in the form a::b.
+/// Unfortunately, the C++ "namespace" statement can only accept a single
+/// string - to set up the namespace of "a::b" requires two statements, one
+/// for "namspace a" and the other for "namespace b".
+///
+/// This function returns the set of namespace components as a vector of
+/// strings. A vector of one element, containing the empty string, is returned
+/// if the anonymous namespace is specified.
+///
+/// \param ns Argument to $NAMESPACE (passed by value, as we will be modifying
+/// it.)
+
+vector<string>
+splitNamespace(string ns) {
+
+ // Namespaces components are separated by double colon characters -
+ // convert to single colons.
+ size_t dcolon;
+ while ((dcolon = ns.find("::")) != string::npos) {
+ ns.replace(dcolon, 2, ":");
+ }
+
+ // ... and return the vector of namespace components split on the single
+ // colon.
+ return isc::strutil::tokens(ns, ":");
+}
+
+
+/// \brief Write Opening Namespace(s)
+///
+/// Writes the lines listing the namespaces in use.
+void
+writeOpeningNamespace(ostream& output, const vector<string>& ns) {
+ if (!ns.empty()) {
+
+ // Output namespaces in correct order
+ for (int i = 0; i < ns.size(); ++i) {
+ output << "namespace " << ns[i] << " {\n";
+ }
+ output << "\n";
+ }
+}
+
+
+/// \brief Write Closing Namespace(s)
+///
+/// Writes the lines listing the namespaces in use.
+void
+writeClosingNamespace(ostream& output, const vector<string>& ns) {
+ if (!ns.empty()) {
+ for (int i = ns.size() - 1; i >= 0; --i) {
+ output << "} // namespace " << ns[i] << "\n";
+ }
+ output << "\n";
+ }
+}
+
+
+/// \brief Write Header File
+///
+/// Writes the C++ header file containing the symbol definitions. These are
+/// "extern" references to definitions in the .cc file. As such, they should
+/// take up no space in the module in which they are included, and redundant
+/// references should be removed by the compiler.
+///
+/// \param file Name of the message file. The header file is written to a
+/// file of the same name but with a .h suffix.
+/// \param prefix Prefix string to use in symbols
+/// \param ns Namespace in which the definitions are to be placed. An empty
+/// string indicates no namespace.
+/// \param dictionary Dictionary holding the message definitions.
+
+void
+writeHeaderFile(const string& file, const string& prefix,
+ const vector<string>& ns_components, MessageDictionary& dictionary)
+{
+ Filename message_file(file);
+ Filename header_file(message_file.useAsDefault(".h"));
+
+ // Text to use as the sentinels.
+ string sentinel_text = sentinel(header_file);
+
+ // Open the output file for writing
+ ofstream hfile(header_file.fullName().c_str());
+
+ try {
+ if (hfile.fail()) {
+ throw MessageException(MSG_OPNMSGOUT, header_file.fullName(),
+ strerror(errno));
+ }
+
+ // Write the header preamble. If there is an error, we'll pick it up
+ // after the last write.
+
+ hfile <<
+ "// File created from " << message_file.fullName() << " on " <<
+ currentTime() << "\n" <<
+ "\n" <<
+ "#ifndef " << sentinel_text << "\n" <<
+ "#define " << sentinel_text << "\n" <<
+ "\n" <<
+ "#include <log/message_types.h>\n" <<
+ "\n";
+
+ // Write the message identifiers, bounded by a namespace declaration
+ writeOpeningNamespace(hfile, ns_components);
+
+ vector<string> idents = sortedIdentifiers(dictionary);
+ for (vector<string>::const_iterator j = idents.begin();
+ j != idents.end(); ++j) {
+ hfile << "extern const isc::log::MessageID " << prefix << *j << ";\n";
+ }
+ hfile << "\n";
+
+ writeClosingNamespace(hfile, ns_components);
+
+ // ... and finally the postamble
+ hfile << "#endif // " << sentinel_text << "\n";
+
+ // Report errors (if any) and exit
+ if (hfile.fail()) {
+ throw MessageException(MSG_MSGWRTERR, header_file.fullName(),
+ strerror(errno));
+ }
+
+ hfile.close();
+ }
+ catch (MessageException&) {
+ hfile.close();
+ throw;
+ }
+}
+
+
+/// \brief Convert Non Alpha-Numeric Characters to Underscores
+///
+/// Simple function for use in a call to transform
+
+char
+replaceNonAlphaNum(char c) {
+ return (isalnum(c) ? c : '_');
+}
+
+
+/// \brief Write Program File
+///
+/// Writes the C++ source code file. This defines the text of the message
+/// symbols, as well as the initializer object that sets the entries in the
+/// global dictionary.
+///
+/// The construction of the initializer object loads the dictionary with the
+/// message text. However, nothing actually references it. If the initializer
+/// were in a file by itself, the lack of things referencing it would cause the
+/// linker to ignore it when pulling modules out of the logging library in a
+/// static link. By including it in the file with the symbol definitions, the
+/// module will get included in the link process to resolve the symbol
+/// definitions, and so the initializer object will be included in the final
+/// image. (Note that there are no such problems when the logging library is
+/// built as a dynamically-linked library: the whole library - including the
+/// initializer module - gets mapped into address space when the library is
+/// loaded, after which all the initializing code (including the constructors
+/// of objects declared outside functions) gets run.)
+///
+/// There _may_ be a problem when we come to port this to Windows. Microsoft
+/// Visual Studio contains a "Whole Program Optimisation" option, where the
+/// optimisation is done at link-time, not compiler-time. In this it _may_
+/// decide to remove the initializer object because of a lack of references
+/// to it. But until BIND-10 is ported to Windows, we won't know.
+
+void
+writeProgramFile(const string& file, const string& prefix,
+ const vector<string>& ns_components, MessageDictionary& dictionary)
+{
+ Filename message_file(file);
+ Filename program_file(message_file.useAsDefault(".cc"));
+
+ // Open the output file for writing
+ ofstream ccfile(program_file.fullName().c_str());
+ try {
+ if (ccfile.fail()) {
+ throw MessageException(MSG_OPNMSGOUT, program_file.fullName(),
+ strerror(errno));
+ }
+
+ // Write the preamble. If there is an error, we'll pick it up after
+ // the last write.
+
+ ccfile <<
+ "// File created from " << message_file.fullName() << " on " <<
+ currentTime() << "\n" <<
+ "\n" <<
+ "#include <cstddef>\n" <<
+ "#include <log/message_types.h>\n" <<
+ "#include <log/message_initializer.h>\n" <<
+ "\n";
+
+ // Declare the message symbols themselves.
+
+ writeOpeningNamespace(ccfile, ns_components);
+
+ vector<string> idents = sortedIdentifiers(dictionary);
+ for (vector<string>::const_iterator j = idents.begin();
+ j != idents.end(); ++j) {
+ ccfile << "extern const isc::log::MessageID " << prefix << *j <<
+ " = \"" << *j << "\";\n";
+ }
+ ccfile << "\n";
+
+ writeClosingNamespace(ccfile, ns_components);
+
+ // Now the code for the message initialization.
+
+ ccfile <<
+ "namespace {\n" <<
+ "\n" <<
+ "const char* values[] = {\n";
+
+ // Output the identifiers and the associated text.
+ idents = sortedIdentifiers(dictionary);
+ for (vector<string>::const_iterator i = idents.begin();
+ i != idents.end(); ++i) {
+ ccfile << " \"" << *i << "\", \"" <<
+ quoteString(dictionary.getText(*i)) << "\",\n";
+ }
+
+
+ // ... and the postamble
+ ccfile <<
+ " NULL\n" <<
+ "};\n" <<
+ "\n" <<
+ "const isc::log::MessageInitializer initializer(values);\n" <<
+ "\n" <<
+ "} // Anonymous namespace\n" <<
+ "\n";
+
+ // Report errors (if any) and exit
+ if (ccfile.fail()) {
+ throw MessageException(MSG_MSGWRTERR, program_file.fullName(),
+ strerror(errno));
+ }
+
+ ccfile.close();
+ }
+ catch (MessageException&) {
+ ccfile.close();
+ throw;
+ }
+}
+
+
+/// \brief Warn of Duplicate Entries
+///
+/// If the input file contained duplicate message IDs, only the first will be
+/// processed. However, we should warn about it.
+///
+/// \param reader Message Reader used to read the file
+
+void
+warnDuplicates(MessageReader& reader) {
+
+ // Get the duplicates (the overflow) and, if present, sort them into some
+ // order and remove those which occur more than once (which mean that they
+ // occur more than twice in the input file).
+ MessageReader::MessageIDCollection duplicates = reader.getNotAdded();
+ if (duplicates.size() > 0) {
+ cout << "Warning: the following duplicate IDs were found:\n";
+
+ sort(duplicates.begin(), duplicates.end());
+ MessageReader::MessageIDCollection::iterator new_end =
+ unique(duplicates.begin(), duplicates.end());
+ for (MessageReader::MessageIDCollection::iterator i = duplicates.begin();
+ i != new_end; ++i) {
+ cout << " " << *i << "\n";
+ }
+ }
+}
+
+
+/// \brief Main Program
+///
+/// Parses the options then dispatches to the appropriate function. See the
+/// main file header for the invocation.
+
+int
+main(int argc, char* argv[]) {
+
+ const char* soptions = "hv"; // Short options
+
+ optind = 1; // Ensure we start a new scan
+ int opt; // Value of the option
+
+ while ((opt = getopt(argc, argv, soptions)) != -1) {
+ switch (opt) {
+ case 'h':
+ usage();
+ return 0;
+
+ case 'v':
+ version();
+ return 0;
+
+ default:
+ // A message will have already been output about the error.
+ return 1;
+ }
+ }
+
+ // Do we have the message file?
+ if (optind < (argc - 1)) {
+ cout << "Error: excess arguments in command line\n";
+ usage();
+ return 1;
+ } else if (optind >= argc) {
+ cout << "Error: missing message file\n";
+ usage();
+ return 1;
+ }
+ string message_file = argv[optind];
+
+ try {
+ // Have identified the file, so process it. First create a local
+ // dictionary into which the data will be put.
+ MessageDictionary dictionary;
+
+ // Read the data into it.
+ MessageReader reader(&dictionary);
+ reader.readFile(message_file);
+
+ // Get the namespace into which the message definitions will be put and
+ // split it into components.
+ vector<string> ns_components = splitNamespace(reader.getNamespace());
+
+ // Write the header file.
+ writeHeaderFile(message_file, reader.getPrefix(), ns_components,
+ dictionary);
+
+ // Write the file that defines the message symbols and text
+ writeProgramFile(message_file, reader.getPrefix(), ns_components,
+ dictionary);
+
+
+ // Finally, warn of any duplicates encountered.
+ warnDuplicates(reader);
+ }
+ catch (MessageException& e) {
+ // Create an error message from the ID and the text
+ MessageDictionary& global = MessageDictionary::globalDictionary();
+ string text = e.id();
+ text += ", ";
+ text += global.getText(e.id());
+
+ // Format with arguments
+ text = isc::strutil::format(text, e.arguments());
+ cerr << text << "\n";
+
+ return 1;
+ }
+
+ return 0;
+
+}
diff --git a/src/lib/log/debug_levels.h b/src/lib/log/debug_levels.h
new file mode 100644
index 0000000..bb2b524
--- /dev/null
+++ b/src/lib/log/debug_levels.h
@@ -0,0 +1,29 @@
+// 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 __DEBUG_LEVELS_H
+#define __DEBUG_LEVELS_H
+
+/// \brief Defines Debug Levels
+///
+/// Defines the maximum and minimum debug levels and the number of levels.
+/// These are defined using #define as they are referenced in the construction
+/// of variables declared outside execution units. (In this way we avoid the
+/// "static initialization fiasco" problem.)
+
+#define MIN_DEBUG_LEVEL (0)
+#define MAX_DEBUG_LEVEL (99)
+#define NUM_DEBUG_LEVEL (MAX_DEBUG_LEVEL - MIN_DEBUG_LEVEL + 1)
+
+#endif // __DEBUG_LEVELS_H
diff --git a/src/lib/log/dummylog.cc b/src/lib/log/dummylog.cc
new file mode 100644
index 0000000..5f025e1
--- /dev/null
+++ b/src/lib/log/dummylog.cc
@@ -0,0 +1,37 @@
+// 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 "dummylog.h"
+
+#include <iostream>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+bool denabled = false;
+string dprefix;
+
+void dlog(const string& message,bool error_flag) {
+ if (denabled || error_flag) {
+ if (!dprefix.empty()) {
+ cerr << "[" << dprefix << "] ";
+ }
+ cerr << message << endl;
+ }
+}
+
+}
+}
diff --git a/src/lib/log/dummylog.h b/src/lib/log/dummylog.h
new file mode 100644
index 0000000..ef5af13
--- /dev/null
+++ b/src/lib/log/dummylog.h
@@ -0,0 +1,61 @@
+// 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 _ISC_DUMMYLOG_H
+#define _ISC_DUMMYLOG_H 1
+
+#include <string>
+
+namespace isc {
+namespace log {
+
+/// Are we doing logging?
+extern bool denabled;
+/**
+ * \short Prefix into logs.
+ *
+ * The prefix is printed in front of every log message in square brackets.
+ * The usual convention is to put the name of program here.
+ */
+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. 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 program prefix, message and
+ * a newline if denabled is true.
+ *
+ * There are no tests for this function, since it is only temporary and
+ * trivial. Tests will be written for the real logging framework when it is
+ * created.
+ *
+ * It has the d in front of the name so it is unlikely anyone will create
+ * a real logging function with the same name and the place wouldn't be found
+ * as a compilation error.
+ *
+ * @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);
+
+}
+}
+
+#endif // _ISC_DUMMYLOG_H
diff --git a/src/lib/log/filename.cc b/src/lib/log/filename.cc
new file mode 100644
index 0000000..91835af
--- /dev/null
+++ b/src/lib/log/filename.cc
@@ -0,0 +1,138 @@
+// 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 <iostream>
+#include <algorithm>
+#include <string>
+
+#include <ctype.h>
+
+#include <log/filename.h>
+#include <log/strutil.h>
+
+using namespace std;
+
+
+namespace isc {
+namespace log {
+
+// Split string into components. Any backslashes are assumed to have
+// been replaced by forward slashes.
+
+void
+Filename::split(const string& full_name, string& directory,
+ string& name, string& extension) const
+{
+ directory = name = extension = "";
+ bool dir_present = false;
+ if (!full_name.empty()) {
+
+ // Find the directory.
+ size_t last_slash = full_name.find_last_of('/');
+ if (last_slash != string::npos) {
+
+ // Found the last slash, so extract directory component and
+ // set where the scan for the last_dot should terminate.
+ directory = full_name.substr(0, last_slash + 1);
+ if (last_slash == full_name.size()) {
+
+ // The entire string was a directory, so exit not and don't
+ // do any more searching.
+ return;
+ }
+
+ // Found a directory so note the fact.
+ dir_present = true;
+ }
+
+ // Now search backwards for the last ".".
+ size_t last_dot = full_name.find_last_of('.');
+ if ((last_dot == string::npos) ||
+ (dir_present && (last_dot < last_slash))) {
+
+ // Last "." either not found or it occurs to the left of the last
+ // slash if a directory was present (so it is part of a directory
+ // name). In this case, the remainder of the string after the slash
+ // is the name part.
+ name = full_name.substr(last_slash + 1);
+ return;
+ }
+
+ // Did find a valid dot, so it and everything to the right is the
+ // extension...
+ extension = full_name.substr(last_dot);
+
+ // ... and the name of the file is everything in between.
+ if ((last_dot - last_slash) > 1) {
+ name = full_name.substr(last_slash + 1, last_dot - last_slash - 1);
+ }
+ }
+
+}
+
+// Expand the stored filename with the default.
+
+string
+Filename::expandWithDefault(const string& defname) const {
+
+ string def_directory("");
+ string def_name("");
+ string def_extension("");
+
+ // Normalize the input string.
+ string copy_defname = isc::strutil::trim(defname);
+#ifdef WIN32
+ isc::strutil::normalizeSlash(copy_defname);
+#endif
+
+ // Split into the components
+ split(copy_defname, def_directory, def_name, def_extension);
+
+ // Now construct the result.
+ string retstring =
+ (directory_.empty() ? def_directory : directory_) +
+ (name_.empty() ? def_name : name_) +
+ (extension_.empty() ? def_extension : extension_);
+ return (retstring);
+}
+
+// Use the stored name as default for a given name
+
+string
+Filename::useAsDefault(const string& name) const {
+
+ string name_directory("");
+ string name_name("");
+ string name_extension("");
+
+ // Normalize the input string.
+ string copy_name = isc::strutil::trim(name);
+#ifdef WIN32
+ isc::strutil::normalizeSlash(copy_name);
+#endif
+
+ // Split into the components
+ split(copy_name, name_directory, name_name, name_extension);
+
+ // Now construct the result.
+ string retstring =
+ (name_directory.empty() ? directory_ : name_directory) +
+ (name_name.empty() ? name_ : name_name) +
+ (name_extension.empty() ? extension_ : name_extension);
+ return (retstring);
+}
+
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/filename.h b/src/lib/log/filename.h
new file mode 100644
index 0000000..e3cda16
--- /dev/null
+++ b/src/lib/log/filename.h
@@ -0,0 +1,161 @@
+// 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 __FILENAME_H
+#define __FILENAME_H
+
+#include <string>
+
+#include <strutil.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Class to Manipulate Filenames
+///
+/// This is a utility class to manipulate filenames. It repeats some of the
+/// features found in the Boost filename class, but is self-contained so avoids
+/// the need to link in the Boost library.
+///
+/// A Unix-style filename comprises three parts:
+///
+/// Directory - everything up to and including the last "/". If there is no
+/// "/" in the string, there is no directory component. Note that the
+/// requirement of a trailing slash eliminates the ambiguity of whether a
+/// component is a directory or not, e.g. in /alpha/beta", "beta" could be the
+/// name of a directory or is could be a file. The interpretation here is that
+/// "beta" is the name of a file (although that file could be a directory).
+///
+/// Note: Under Windows, the drive letter is considered to be part of the
+/// directory specification. Unless this class becomes more widely-used on
+/// Windows, there is no point in adding redundant code.
+///
+/// Name - everthing from the character after the last "/" up to but not
+/// including the last ".".
+///
+/// Extension - everthing from the right-most "." (after the right-most "/") to
+/// the end of the string. If there is no "." after the last "/", there is
+/// no file extension.
+///
+/// (Note that on Windows, this function will replace all "\" characters
+/// with "/" characters on input strings.)
+///
+/// This class provides functions for extracting the components and for
+/// substituting components.
+
+
+class Filename {
+public:
+
+ /// \brief Constructor
+ Filename(const std::string& name) :
+ full_name_(""), directory_(""), name_(""), extension_("")
+ {
+ setName(name);
+ }
+
+ /// \brief Sets Stored Filename
+ ///
+ /// \param name New name to replaced currently stored name
+ void setName(const std::string& name) {
+ full_name_ = isc::strutil::trim(name);
+#ifdef WIN32
+ isc::strutil::normalizeSlash(full_name_);
+#endif
+ split(full_name_, directory_, name_, extension_);
+ }
+
+ /// \return Stored Filename
+ std::string fullName() const {
+ return (full_name_);
+ }
+
+ /// \return Directory of Given File Name
+ std::string directory() const {
+ return (directory_);
+ }
+
+ /// \return Name of Given File Name
+ std::string name() const {
+ return (name_);
+ }
+
+ /// \return Extension of Given File Name
+ std::string extension() const {
+ return (extension_);
+ }
+
+ /// \brief Expand Name with Default
+ ///
+ /// A default file specified is supplied and used to fill in any missing
+ /// fields. For example, if the name stored is "/a/b" and the supplied
+ /// name is "c.d", the result is "/a/b.d": the only field missing from the
+ /// stored name is the extension, which is supplied by the default.
+ /// Another example would be to store "a.b" and to supply a default of
+ /// "/c/d/" - the result is "/c/d/a.b". (Note that if the supplied default
+ /// was "/c/d", the result would be "/c/a.b", even if "/c/d" were actually
+ /// a directory.)
+ ///
+ /// \param defname Default name
+ ///
+ /// \return Name expanded with defname.
+ std::string expandWithDefault(const std::string& defname) const;
+
+ /// \brief Use as Default and Substitute into String
+ ///
+ /// Does essentially the inverse of expand(); that filled in the stored
+ /// name with a default and returned the result. This treats the stored
+ /// name as the default and uses it to fill in a given name. In essence,
+ /// the code:
+ /// \code
+ /// Filename f("/a/b");
+ /// result = f.expandWithdefault("c.d");
+ /// \endcode
+ /// gives as a result "/a/b.d". This is the same as:
+ /// \code
+ /// Filename f("c.d");
+ /// result = f.useAsDefault("/a/b");
+ /// \endcode
+ ///
+ /// \param name Name to expand
+ ///
+ /// \return Name expanded with stored name
+ std::string useAsDefault(const std::string& name) const;
+
+private:
+ /// \brief Split Name into Components
+ ///
+ /// Splits the file name into the directory, name and extension parts.
+ /// The name is assumed to have had back slashes replaced by forward
+ /// slashes (if appropriate).
+ ///
+ /// \param full_name Name to split
+ /// \param directory Returned directory part
+ /// \param name Returned name part
+ /// \param extension Returned extension part
+ void split(const std::string& full_name, std::string& directory,
+ std::string& name, std::string& extension) const;
+
+ // Members
+
+ std::string full_name_; ///< Given name
+ std::string directory_; ///< Directory part
+ std::string name_; ///< Name part
+ std::string extension_; ///< Extension part
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __FILENAME_H
diff --git a/src/lib/log/logger.cc b/src/lib/log/logger.cc
new file mode 100644
index 0000000..a2465de
--- /dev/null
+++ b/src/lib/log/logger.cc
@@ -0,0 +1,175 @@
+// 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 <stdarg.h>
+#include <stdio.h>
+
+#include <log/logger.h>
+#include <log/logger_impl.h>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+#include <log/root_logger_name.h>
+#include <log/strutil.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// Initialize Logger implementation. Does not check whether the implementation
+// has already been initialized - that was done by the caller (getLoggerPtr()).
+void Logger::initLoggerImpl() {
+ loggerptr_ = new LoggerImpl(name_, infunc_);
+}
+
+// Destructor.
+
+Logger::~Logger() {
+ delete loggerptr_;
+}
+
+// Get Name of Logger
+
+std::string
+Logger::getName() {
+ return (getLoggerPtr()->getName());
+}
+
+// Set the severity for logging.
+
+void
+Logger::setSeverity(isc::log::Severity severity, int dbglevel) {
+ getLoggerPtr()->setSeverity(severity, dbglevel);
+}
+
+// Return the severity of the logger.
+
+isc::log::Severity
+Logger::getSeverity() {
+ return (getLoggerPtr()->getSeverity());
+}
+
+// Get Effective Severity Level for Logger
+
+isc::log::Severity
+Logger::getEffectiveSeverity() {
+ return (getLoggerPtr()->getEffectiveSeverity());
+}
+
+// Debug level (only relevant if messages of severity DEBUG are being logged).
+
+int
+Logger::getDebugLevel() {
+ return (getLoggerPtr()->getDebugLevel());
+}
+
+// Check on the current severity settings
+
+bool
+Logger::isDebugEnabled(int dbglevel) {
+ return (getLoggerPtr()->isDebugEnabled(dbglevel));
+}
+
+bool
+Logger::isInfoEnabled() {
+ return (getLoggerPtr()->isInfoEnabled());
+}
+
+bool
+Logger::isWarnEnabled() {
+ return (getLoggerPtr()->isWarnEnabled());
+}
+
+bool
+Logger::isErrorEnabled() {
+ return (getLoggerPtr()->isErrorEnabled());
+}
+
+bool
+Logger::isFatalEnabled() {
+ return (getLoggerPtr()->isFatalEnabled());
+}
+
+// Format a message: looks up the message text in the dictionary and formats
+// it, replacing tokens with arguments.
+//
+// Owing to the use of variable arguments, this must be inline (hence the
+// definition of the macro). Also note that it expects that the message buffer
+// "message" is declared in the compilation unit.
+
+// Output methods
+
+void
+Logger::debug(int dbglevel, const isc::log::MessageID& ident, ...) {
+ if (isDebugEnabled(dbglevel)) {
+ va_list ap;
+ va_start(ap, ident);
+ getLoggerPtr()->debug(ident, ap);
+ va_end(ap);
+ }
+}
+
+void
+Logger::info(const isc::log::MessageID& ident, ...) {
+ if (isInfoEnabled()) {
+ va_list ap;
+ va_start(ap, ident);
+ getLoggerPtr()->info(ident, ap);
+ va_end(ap);
+ }
+}
+
+void
+Logger::warn(const isc::log::MessageID& ident, ...) {
+ if (isWarnEnabled()) {
+ va_list ap;
+ va_start(ap, ident);
+ getLoggerPtr()->warn(ident, ap);
+ va_end(ap);
+ }
+}
+
+void
+Logger::error(const isc::log::MessageID& ident, ...) {
+ if (isErrorEnabled()) {
+ va_list ap;
+ va_start(ap, ident);
+ getLoggerPtr()->error(ident, ap);
+ va_end(ap);
+ }
+}
+
+void
+Logger::fatal(const isc::log::MessageID& ident, ...) {
+ if (isFatalEnabled()) {
+ va_list ap;
+ va_start(ap, ident);
+ getLoggerPtr()->fatal(ident, ap);
+ va_end(ap);
+ }
+}
+
+bool Logger::operator==(Logger& other) {
+ return (*getLoggerPtr() == *other.getLoggerPtr());
+}
+
+// Protected methods (used for testing)
+
+void
+Logger::reset() {
+ LoggerImpl::reset();
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/logger.h b/src/lib/log/logger.h
new file mode 100644
index 0000000..88e88e2
--- /dev/null
+++ b/src/lib/log/logger.h
@@ -0,0 +1,252 @@
+// 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 __LOGGER_H
+#define __LOGGER_H
+
+#include <cstdlib>
+#include <string>
+
+#include <log/debug_levels.h>
+#include <log/logger_levels.h>
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Logging API
+///
+/// This module forms the interface into the logging subsystem. Features of the
+/// system and its implementation are:
+///
+/// # Multiple logging objects can be created, each given a name; those with the
+/// same name share characteristics (like destination, level being logged
+/// etc.)
+/// # Messages can be logged at severity levels of FATAL, ERROR, WARN, INFO or
+/// DEBUG. The DEBUG level has further sub-levels numbered 0 (least
+/// informative) to 99 (most informative).
+/// # Each logger has a severity level set associated with it. When a message
+/// is logged, it is output only if it is logged at a level equal to the
+/// logger severity level or greater, e.g. if the logger's severity is WARN,
+/// only messages logged at WARN, ERROR or FATAL will be output.
+/// # Messages are identified by message identifiers, which are keys into a
+/// message dictionary.
+
+class LoggerImpl; // Forward declaration of the implementation class
+
+class Logger {
+public:
+
+ /// \brief Constructor
+ ///
+ /// Creates/attaches to a logger of a specific name.
+ ///
+ /// \param name Name of the logger. If the name is that of the root name,
+ /// this creates an instance of the root logger; otherwise it creates a
+ /// child of the root logger.
+ ///
+ /// \param infunc This argument is present to get round a bug in some
+ /// implementations of the logging system. If the logger is declared in
+ /// a function (such that it will be deleted when the function exits,
+ /// before the program ends), set this true. If declared outside a
+ /// function (such that it gets deleted during program rundown), set false
+ /// (the default).\n
+ /// \n
+ /// The problems encountered was that during program rundown, one logging
+ /// implementation (log4cxx) threw a MutexException (this is described in
+ /// https://issues.apache.org/jira/browse/LOGCXX-322). As this only occurs
+ /// during program rundown, the issue is not serious - it just looks bad to
+ /// have the program crash instead of shut down cleanly.\n
+ /// \n
+ /// If log4cxx is chosen as the implementation, this flag controls the
+ /// 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 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.
+ /// \n
+ /// The flag has no effect on non-log4cxx implementations.
+ Logger(const std::string& name, bool infunc = false) :
+ loggerptr_(NULL), name_(name), infunc_(infunc)
+ {}
+
+
+ /// \brief Destructor
+ virtual ~Logger();
+
+
+ /// \brief Get Name of Logger
+ ///
+ /// \return The full name of the logger (including the root name)
+ virtual std::string getName();
+
+
+ /// \brief Set Severity Level for Logger
+ ///
+ /// Sets the level at which this logger will log messages. If none is set,
+ /// the level is inherited from the parent.
+ ///
+ /// \param severity Severity level to log
+ /// \param dbglevel If the severity is DEBUG, this is the debug level.
+ /// This can be in the range 1 to 100 and controls the verbosity. A value
+ /// outside these limits is silently coerced to the nearest boundary.
+ virtual void setSeverity(isc::log::Severity severity, int dbglevel = 1);
+
+
+ /// \brief Get Severity Level for Logger
+ ///
+ /// \return The current logging level of this logger. In most cases though,
+ /// the effective logging level is what is required.
+ virtual isc::log::Severity getSeverity();
+
+
+ /// \brief Get Effective Severity Level for Logger
+ ///
+ /// \return The effective severity level of the logger. This is the same
+ /// as getSeverity() if the logger has a severity level set, but otherwise
+ /// is the severity of the parent.
+ virtual isc::log::Severity getEffectiveSeverity();
+
+
+ /// \brief Return DEBUG Level
+ ///
+ /// \return Current setting of debug level. This is returned regardless of
+ /// whether the severity is set to debug.
+ virtual int getDebugLevel();
+
+
+ /// \brief Returns if Debug Message Should Be Output
+ ///
+ /// \param dbglevel Level for which debugging is checked. Debugging is
+ /// enabled only if the logger has DEBUG enabled and if the dbglevel
+ /// checked is less than or equal to the debug level set for the logger.
+ virtual bool isDebugEnabled(int dbglevel = MIN_DEBUG_LEVEL);
+
+
+ /// \brief Is INFO Enabled?
+ virtual bool isInfoEnabled();
+
+
+ /// \brief Is WARNING Enabled?
+ virtual bool isWarnEnabled();
+
+
+ /// \brief Is ERROR Enabled?
+ virtual bool isErrorEnabled();
+
+
+ /// \brief Is FATAL Enabled?
+ virtual bool isFatalEnabled();
+
+
+ /// \brief Output Debug Message
+ ///
+ /// \param dbglevel Debug level, ranging between 0 and 99. Higher numbers
+ /// are used for more verbose output.
+ /// \param ident Message identification.
+ /// \param ... Optional arguments for the message.
+ void debug(int dbglevel, const MessageID& ident, ...);
+
+
+ /// \brief Output Informational Message
+ ///
+ /// \param ident Message identification.
+ /// \param ... Optional arguments for the message.
+ void info(const MessageID& ident, ...);
+
+
+ /// \brief Output Warning Message
+ ///
+ /// \param ident Message identification.
+ /// \param ... Optional arguments for the message.
+ void warn(const MessageID& ident, ...);
+
+
+ /// \brief Output Error Message
+ ///
+ /// \param ident Message identification.
+ /// \param ... Optional arguments for the message.
+ void error(const MessageID& ident, ...);
+
+
+ /// \brief Output Fatal Message
+ ///
+ /// \param ident Message identification.
+ /// \param ... Optional arguments for the message.
+ void fatal(const MessageID& ident, ...);
+
+ /// \brief Equality
+ ///
+ /// Check if two instances of this logger refer to the same stream.
+ /// (This method is principally for testing.)
+ ///
+ /// \return true if the logger objects are instances of the same logger.
+ bool operator==(Logger& other);
+
+protected:
+
+ /// \brief Reset Global Data
+ ///
+ /// Used for testing, this calls upon the underlying logger implementation
+ /// to clear any global data.
+ static void reset();
+
+private:
+ /// \brief Copy Constructor
+ ///
+ /// Disabled (marked private) as it makes no sense to copy the logger -
+ /// just create another one of the same name.
+ Logger(const Logger&);
+
+ /// \brief Assignment Operator
+ ///
+ /// Disabled (marked private) as it makes no sense to copy the logger -
+ /// just create another one of the same name.
+ Logger& operator=(const Logger&);
+
+ /// \brief Initialize Implementation
+ ///
+ /// Returns the logger pointer. If not yet set, the underlying
+ /// implementation class is initialized.\n
+ /// \n
+ /// The reason for this indirection is to avoid the "static initialization
+ /// fiacso", whereby we cannot rely on the order of static initializations.
+ /// The main problem is the root logger name - declared statically - which
+ /// is referenced by various loggers. By deferring a reference to it until
+ /// after the program starts executing - by which time the root name object
+ /// will be initialized - we avoid this problem.
+ ///
+ /// \return Returns pointer to implementation
+ LoggerImpl* getLoggerPtr() {
+ if (!loggerptr_) {
+ initLoggerImpl();
+ }
+ return (loggerptr_);
+ }
+
+ /// \brief Initialize Underlying Implementation and Set loggerptr_
+ void initLoggerImpl();
+
+ LoggerImpl* loggerptr_; ///< Pointer to the underlying logger
+ std::string name_; ///< Copy of the logger name
+ bool infunc_; ///< Copy of the infunc argument
+};
+
+} // namespace log
+} // namespace isc
+
+
+#endif // __LOGGER_H
diff --git a/src/lib/log/logger_impl.cc b/src/lib/log/logger_impl.cc
new file mode 100644
index 0000000..4b19360
--- /dev/null
+++ b/src/lib/log/logger_impl.cc
@@ -0,0 +1,221 @@
+// 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 <iostream>
+#include <algorithm>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <boost/lexical_cast.hpp>
+
+#include <log/debug_levels.h>
+#include <log/root_logger_name.h>
+#include <log/logger.h>
+#include <log/logger_impl.h>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+#include <log/root_logger_name.h>
+#include <log/strutil.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// Static initializations
+
+LoggerImpl::LoggerInfoMap LoggerImpl::logger_info_;
+LoggerImpl::LoggerInfo LoggerImpl::root_logger_info_(isc::log::INFO, 0);
+
+// Constructor
+LoggerImpl::LoggerImpl(const std::string& name, bool)
+{
+ // Are we the root logger?
+ if (name == getRootLoggerName()) {
+ is_root_ = true;
+ name_ = name;
+ } else {
+ is_root_ = false;
+ name_ = getRootLoggerName() + "." + name;
+ }
+}
+
+// Destructor. (Here because of virtual declaration.)
+
+LoggerImpl::~LoggerImpl() {
+}
+
+// Set the severity for logging.
+
+void
+LoggerImpl::setSeverity(isc::log::Severity severity, int dbglevel) {
+
+ // Silently coerce the debug level into the valid range of 0 to 99
+
+ int debug_level = max(MIN_DEBUG_LEVEL, min(MAX_DEBUG_LEVEL, dbglevel));
+ if (is_root_) {
+
+ // Can only set severity for the root logger, you can't disable it.
+ // Any attempt to do so is silently ignored.
+ if (severity != isc::log::DEFAULT) {
+ root_logger_info_ = LoggerInfo(severity, debug_level);
+ }
+
+ } else if (severity == isc::log::DEFAULT) {
+
+ // Want to set to default; this means removing the information
+ // about this logger from the logger_info_ if it is set.
+ LoggerInfoMap::iterator i = logger_info_.find(name_);
+ if (i != logger_info_.end()) {
+ logger_info_.erase(i);
+ }
+
+ } else {
+
+ // Want to set this information
+ logger_info_[name_] = LoggerInfo(severity, debug_level);
+ }
+}
+
+// Return severity level
+
+isc::log::Severity
+LoggerImpl::getSeverity() {
+
+ if (is_root_) {
+ return (root_logger_info_.severity);
+ }
+ else {
+ LoggerInfoMap::iterator i = logger_info_.find(name_);
+ if (i != logger_info_.end()) {
+ return ((i->second).severity);
+ }
+ else {
+ return (isc::log::DEFAULT);
+ }
+ }
+}
+
+// Get effective severity. Either the current severity or, if not set, the
+// severity of the root level.
+
+isc::log::Severity
+LoggerImpl::getEffectiveSeverity() {
+
+ if (!is_root_ && !logger_info_.empty()) {
+
+ // Not root logger and there is at least one item in the info map for a
+ // logger.
+ LoggerInfoMap::iterator i = logger_info_.find(name_);
+ if (i != logger_info_.end()) {
+
+ // Found, so return the severity.
+ return ((i->second).severity);
+ }
+ }
+
+ // Must be the root logger, or this logger is defaulting to the root logger
+ // settings.
+ return (root_logger_info_.severity);
+}
+
+// Get the debug level. This returns 0 unless the severity is DEBUG.
+
+int
+LoggerImpl::getDebugLevel() {
+
+ if (!is_root_ && !logger_info_.empty()) {
+
+ // Not root logger and there is something in the map, check if there
+ // is a setting for this one.
+ LoggerInfoMap::iterator i = logger_info_.find(name_);
+ if (i != logger_info_.end()) {
+
+ // Found, so return the debug level.
+ if ((i->second).severity == isc::log::DEBUG) {
+ return ((i->second).dbglevel);
+ } else {
+ return (0);
+ }
+ }
+ }
+
+ // Must be the root logger, or this logger is defaulting to the root logger
+ // settings.
+ if (root_logger_info_.severity == isc::log::DEBUG) {
+ return (root_logger_info_.dbglevel);
+ } else {
+ return (0);
+ }
+}
+
+// The code for isXxxEnabled is quite simple and is in the header. The only
+// exception is isDebugEnabled() where we have the complication of the debug
+// levels.
+
+bool
+LoggerImpl::isDebugEnabled(int dbglevel) {
+
+ if (!is_root_ && !logger_info_.empty()) {
+
+ // Not root logger and there is something in the map, check if there
+ // is a setting for this one.
+ LoggerInfoMap::iterator i = logger_info_.find(name_);
+ if (i != logger_info_.end()) {
+
+ // Found, so return the debug level.
+ if ((i->second).severity <= isc::log::DEBUG) {
+ return ((i->second).dbglevel >= dbglevel);
+ } else {
+ return (false); // Nothing lower than debug
+ }
+ }
+ }
+
+ // Must be the root logger, or this logger is defaulting to the root logger
+ // settings.
+ if (root_logger_info_.severity <= isc::log::DEBUG) {
+ return (root_logger_info_.dbglevel >= dbglevel);
+ } else {
+ return (false);
+ }
+}
+
+// Output a general message
+
+void
+LoggerImpl::output(const char* sev_text, const MessageID& ident,
+ va_list ap)
+{
+ char message[512]; // Should be large enough for any message
+
+ // Obtain text of the message and substitute arguments.
+ const string format = MessageDictionary::globalDictionary().getText(ident);
+ vsnprintf(message, sizeof(message), format.c_str(), ap);
+
+ // Get the time in a struct tm format, and convert to text
+ time_t t_time;
+ time(&t_time);
+ struct tm* tm_time = localtime(&t_time);
+
+ char chr_time[32];
+ (void) strftime(chr_time, sizeof(chr_time), "%Y-%m-%d %H:%M:%S", tm_time);
+
+ // Now output.
+ std::cout << chr_time << " " << sev_text << " [" << getName() << "] " <<
+ ident << ", " << message << "\n";
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/logger_impl.h b/src/lib/log/logger_impl.h
new file mode 100644
index 0000000..9fc9cf9
--- /dev/null
+++ b/src/lib/log/logger_impl.h
@@ -0,0 +1,267 @@
+// 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 __LOGGER_IMPL_H
+#define __LOGGER_IMPL_H
+
+#include <stdarg.h>
+#include <time.h>
+
+#include <cstdlib>
+#include <string>
+#include <map>
+#include <utility>
+
+#include <log/debug_levels.h>
+#include <log/logger.h>
+#include <log/message_types.h>
+#include <log/root_logger_name.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Console Logger Implementation
+///
+/// The logger uses a "pimpl" idiom for implementation, where the base logger
+/// class contains little more than a pointer to the implementation class, and
+/// all actions are carried out by the latter. This class is an implementation
+/// class that just outputs to stdout.
+
+class LoggerImpl {
+public:
+
+ /// \brief Information About Logger
+ ///
+ /// Holds a information about a logger, namely its severity and its debug
+ /// level. This could be a std::pair, except that it gets confusing when
+ /// accessing the LoggerInfoMap: that returns a pair, so we to reference
+ /// elements we would use constructs like ((i->first).second);
+ struct LoggerInfo {
+ isc::log::Severity severity;
+ int dbglevel;
+
+ LoggerInfo(isc::log::Severity sev = isc::log::INFO,
+ int dbg = MIN_DEBUG_LEVEL) : severity(sev), dbglevel(dbg)
+ {}
+ };
+
+
+ /// \brief Information About All Loggers
+ ///
+ /// Information about all loggers in the system - except the root logger -
+ /// is held in a map, linking name of the logger (excluding the root
+ /// name component) and its set severity and debug levels. The root
+ /// logger information is held separately.
+ typedef std::map<std::string, LoggerInfo> LoggerInfoMap;
+
+
+ /// \brief Constructor
+ ///
+ /// Creates a logger of the specific name.
+ ///
+ /// \param name Name of the logger.
+ ///
+ /// \param exit_delete This argument is present to get round a bug in
+ /// the log4cxx implementation. It is unused here.
+ LoggerImpl(const std::string& name, bool);
+
+
+ /// \brief Destructor
+ virtual ~LoggerImpl();
+
+
+ /// \brief Get the full name of the logger (including the root name)
+ virtual std::string getName() {
+ return (name_);
+ }
+
+
+ /// \brief Set Severity Level for Logger
+ ///
+ /// Sets the level at which this logger will log messages. If none is set,
+ /// the level is inherited from the parent.
+ ///
+ /// \param severity Severity level to log
+ /// \param dbglevel If the severity is DEBUG, this is the debug level.
+ /// This can be in the range 1 to 100 and controls the verbosity. A value
+ /// outside these limits is silently coerced to the nearest boundary.
+ virtual void setSeverity(isc::log::Severity severity, int dbglevel = 1);
+
+
+ /// \brief Get Severity Level for Logger
+ ///
+ /// \return The current logging level of this logger. In most cases though,
+ /// the effective logging level is what is required.
+ virtual isc::log::Severity getSeverity();
+
+
+ /// \brief Get Effective Severity Level for Logger
+ ///
+ /// \return The effective severity level of the logger. This is the same
+ /// as getSeverity() if the logger has a severity level set, but otherwise
+ /// is the severity of the parent.
+ virtual isc::log::Severity getEffectiveSeverity();
+
+
+ /// \brief Return DEBUG Level
+ ///
+ /// \return Current setting of debug level. This is returned regardless of
+ /// whether the
+ virtual int getDebugLevel();
+
+
+ /// \brief Returns if Debug Message Should Be Output
+ ///
+ /// \param dbglevel Level for which debugging is checked. Debugging is
+ /// enabled only if the logger has DEBUG enabled and if the dbglevel
+ /// checked is less than or equal to the debug level set for the logger.
+ virtual bool
+ isDebugEnabled(int dbglevel = MIN_DEBUG_LEVEL);
+
+ /// \brief Is INFO Enabled?
+ virtual bool isInfoEnabled() {
+ return (isEnabled(isc::log::INFO));
+ }
+
+ /// \brief Is WARNING Enabled?
+ virtual bool isWarnEnabled() {
+ return (isEnabled(isc::log::WARN));
+ }
+
+ /// \brief Is ERROR Enabled?
+ virtual bool isErrorEnabled() {
+ return (isEnabled(isc::log::ERROR));
+ }
+
+ /// \brief Is FATAL Enabled?
+ virtual bool isFatalEnabled() {
+ return (isEnabled(isc::log::FATAL));
+ }
+
+
+ /// \brief Common Severity check
+ ///
+ /// Implements the common severity check. As an optimisation, this checks
+ /// to see if any logger-specific levels have been set (a quick check as it
+ /// just involves seeing if the collection of logger information is empty).
+ /// if not, it returns the information for the root level; if so, it has
+ /// to take longer and look up the information in the map holding the
+ /// logging details.
+ virtual bool isEnabled(isc::log::Severity severity) {
+ if (logger_info_.empty()) {
+ return (root_logger_info_.severity <= severity);
+ }
+ else {
+ return (getSeverity() <= severity);
+ }
+ }
+
+
+ /// \brief Output General Message
+ ///
+ /// The message is formatted to include the date and time, the severity
+ /// and the logger generating the message.
+ ///
+ /// \param sev_text Severity level as a text string
+ /// \param ident Message identification
+ /// \param ap Variable argument list holding message arguments
+ void output(const char* sev_text, const MessageID& ident,
+ va_list ap);
+
+
+ /// \brief Output Debug Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ /// \param ap Variable argument list holding message arguments
+ void debug(const MessageID& ident, va_list ap) {
+ output("DEBUG", ident, ap);
+ }
+
+
+ /// \brief Output Informational Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ /// \param ap Variable argument list holding message arguments
+ void info(const MessageID& ident, va_list ap) {
+ output("INFO ", ident, ap);
+ }
+
+ /// \brief Output Warning Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ /// \param ap Variable argument list holding message arguments
+ void warn(const MessageID& ident, va_list ap) {
+ output("WARN ", ident, ap);
+ }
+
+ /// \brief Output Error Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ /// \param ap Variable argument list holding message arguments
+ void error(const MessageID& ident, va_list ap) {
+ output("ERROR", ident, ap);
+ }
+
+ /// \brief Output Fatal Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ /// \param ap Variable argument list holding message arguments
+ void fatal(const MessageID& ident, va_list ap) {
+ output("FATAL", ident, ap);
+ }
+
+ /// \brief Equality
+ ///
+ /// Check if two instances of this logger refer to the same stream.
+ /// (This method is principally for testing.)
+ ///
+ /// \return true if the logger objects are instances of the same logger.
+ bool operator==(const LoggerImpl& other) {
+ return (name_ == other.name_);
+ }
+
+
+ /// \brief Reset Global Data
+ ///
+ /// Only used for testing, this clears all the logger information and
+ /// resets it back to default values.
+ static void reset() {
+ root_logger_info_ = LoggerInfo(isc::log::INFO, MIN_DEBUG_LEVEL);
+ logger_info_.clear();
+ }
+
+
+private:
+ bool is_root_; ///< true if a root logger
+ std::string name_; ///< Name of this logger
+
+ // Split the status of the root logger from this logger. If - is will
+ // probably be the usual case - no per-logger setting is enabled, a
+ // quick check of logger_info_.empty() will return true and we can quickly
+ // return the root logger status without a length lookup in the map.
+
+ static LoggerInfo root_logger_info_; ///< Status of root logger
+ static LoggerInfoMap logger_info_; ///< Store of debug levels etc.
+};
+
+} // namespace log
+} // namespace isc
+
+
+#endif // __LOGGER_IMPL_H
diff --git a/src/lib/log/logger_impl_log4cxx.cc b/src/lib/log/logger_impl_log4cxx.cc
new file mode 100644
index 0000000..404fd03
--- /dev/null
+++ b/src/lib/log/logger_impl_log4cxx.cc
@@ -0,0 +1,241 @@
+// 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 <iostream>
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <log4cxx/appender.h>
+#include <log4cxx/basicconfigurator.h>
+#include <log4cxx/patternlayout.h>
+#include <log4cxx/consoleappender.h>
+
+#include <log/root_logger_name.h>
+#include <log/logger.h>
+#include <log/logger_impl.h>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+#include <log/strutil.h>
+#include <log/xdebuglevel.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// Static initializations
+
+bool LoggerImpl::init_ = false;
+
+// Destructor. Delete log4cxx stuff if "don't delete" is clear.
+
+LoggerImpl::~LoggerImpl() {
+ if (exit_delete_) {
+ delete loggerptr_;
+ }
+}
+
+// Initialize logger - create a logger as a child of the root logger. With
+// log4cxx this is assured by naming the logger <parent>.<child>.
+
+void
+LoggerImpl::initLogger() {
+
+ // Initialize basic logging if not already done. This is a one-off for
+ // all loggers.
+ if (!init_) {
+
+ // TEMPORARY
+ // Add a suitable console logger to the log4cxx root logger. (This
+ // is the logger at the root of the log4cxx tree, not the BIND-10 root
+ // logger, which is one level down.) The chosen format is:
+ //
+ // YYYY-MM-DD hh:mm:ss.sss [logger] SEVERITY: text
+ //
+ // As noted, this is a temporary hack: it is done here to ensure that
+ // a suitable output and output pattern is set. Future versions of the
+ // software will set this based on configuration data.
+
+ log4cxx::LayoutPtr layout(
+ new log4cxx::PatternLayout(
+ "%d{yyyy-MM-DD HH:mm:ss.SSS} %-5p [%c] %m\n"));
+ log4cxx::AppenderPtr console(
+ new log4cxx::ConsoleAppender(layout));
+ log4cxx::LoggerPtr sys_root_logger = log4cxx::Logger::getRootLogger();
+ sys_root_logger->addAppender(console);
+
+ // Set the default logging to INFO
+ sys_root_logger->setLevel(log4cxx::Level::getInfo());
+
+ // All static stuff initialized
+ init_ = true;
+ }
+
+ // Initialize this logger. Name this as to whether the BIND-10 root logger
+ // name has been set. (If not, this mucks up the hierarchy :-( ).
+ string root_name = RootLoggerName::getName();
+ if (root_name.empty() || (name_ == root_name)) {
+ loggerptr_ = new log4cxx::LoggerPtr(log4cxx::Logger::getLogger(name_));
+ }
+ else {
+ loggerptr_ = new log4cxx::LoggerPtr(
+ log4cxx::Logger::getLogger(root_name + "." + name_)
+ );
+ }
+}
+
+
+// Set the severity for logging. There is a 1:1 mapping between the logging
+// severity and the log4cxx logging levels, apart from DEBUG.
+//
+// In log4cxx, each of the logging levels (DEBUG, INFO, WARN etc.) has a numeric
+// value. The level is set to one of these and any numeric level equal to or
+// above it that is reported. For example INFO has a value of 20000 and ERROR
+// a value of 40000. So if a message of WARN severity (= 30000) is logged, it is
+// not logged when the logger's severity level is ERROR (as 30000 !>= 40000).
+// It is reported if the logger's severity level is set to WARN (as 30000 >=
+/// 30000) or INFO (30000 >= 20000).
+//
+// This gives a simple system for handling different debug levels. The debug
+// level is a number between 0 and 99, with 0 being least verbose and 99 the
+// most. To implement this seamlessly, when DEBUG is set, the numeric value
+// of the logging level is actually set to (DEBUG - debug-level). Similarly
+// messages of level "n" are logged at a logging level of (DEBUG - n). Thus if
+// the logging level is set to DEBUG and the debug level set to 25, the actual
+// level set is 10000 - 25 = 99975.
+//
+// Attempting to log a debug message of level 26 is an attempt to log a message
+// of level 10000 - 26 = 9974. As 9974 !>= 9975, it is not logged. A
+// message of level 25 is, because 9975 >= 9975.
+//
+// The extended set of logging levels is implemented by the XDebugLevel class.
+
+void
+LoggerImpl::setSeverity(isc::log::Severity severity, int dbglevel) {
+ switch (severity) {
+ case NONE:
+ getLogger()->setLevel(log4cxx::Level::getOff());
+ break;
+
+ case FATAL:
+ getLogger()->setLevel(log4cxx::Level::getFatal());
+ break;
+
+ case ERROR:
+ getLogger()->setLevel(log4cxx::Level::getError());
+ break;
+
+ case WARN:
+ getLogger()->setLevel(log4cxx::Level::getWarn());
+ break;
+
+ case INFO:
+ getLogger()->setLevel(log4cxx::Level::getInfo());
+ break;
+
+ case DEBUG:
+ getLogger()->setLevel(
+ log4cxx::XDebugLevel::getExtendedDebug(dbglevel));
+ break;
+
+ // Will get here for DEFAULT or any other value. This disables the
+ // logger's own severity and it defaults to the severity of the parent
+ // logger.
+ default:
+ getLogger()->setLevel(0);
+ }
+}
+
+// Convert between numeric log4cxx logging level and BIND-10 logging severity.
+
+isc::log::Severity
+LoggerImpl::convertLevel(int value) {
+
+ // The order is optimised. This is only likely to be called when testing
+ // for writing debug messages, so the check for DEBUG_INT is first.
+ if (value <= log4cxx::Level::DEBUG_INT) {
+ return (DEBUG);
+ } else if (value <= log4cxx::Level::INFO_INT) {
+ return (INFO);
+ } else if (value <= log4cxx::Level::WARN_INT) {
+ return (WARN);
+ } else if (value <= log4cxx::Level::ERROR_INT) {
+ return (ERROR);
+ } else if (value <= log4cxx::Level::FATAL_INT) {
+ return (FATAL);
+ } else {
+ return (NONE);
+ }
+}
+
+
+// Return the logging severity associated with this logger.
+
+isc::log::Severity
+LoggerImpl::getSeverityCommon(const log4cxx::LoggerPtr& ptrlogger,
+ bool check_parent) {
+
+ log4cxx::LevelPtr level = ptrlogger->getLevel();
+ if (level == log4cxx::LevelPtr()) {
+
+ // Null level returned, logging should be that of the parent.
+
+ if (check_parent) {
+ log4cxx::LoggerPtr parent = ptrlogger->getParent();
+ if (parent == log4cxx::LoggerPtr()) {
+
+ // No parent, so reached the end of the chain. Return INFO
+ // severity.
+ return (INFO);
+ }
+ else {
+ return (getSeverityCommon(parent, check_parent));
+ }
+ }
+ else {
+ return (DEFAULT);
+ }
+ } else {
+ return (convertLevel(level->toInt()));
+ }
+}
+
+
+// Get the debug level. This returns 0 unless the severity is DEBUG.
+
+int
+LoggerImpl::getDebugLevel() {
+
+ log4cxx::LevelPtr level = getLogger()->getLevel();
+ if (level == log4cxx::LevelPtr()) {
+
+ // Null pointer returned, logging should be that of the parent.
+ return (0);
+
+ } else {
+ int severity = level->toInt();
+ if (severity <= log4cxx::Level::DEBUG_INT) {
+ return (log4cxx::Level::DEBUG_INT - severity);
+ }
+ else {
+ return (0);
+ }
+ }
+}
+
+
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/logger_impl_log4cxx.h b/src/lib/log/logger_impl_log4cxx.h
new file mode 100644
index 0000000..3101347
--- /dev/null
+++ b/src/lib/log/logger_impl_log4cxx.h
@@ -0,0 +1,315 @@
+// 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 __LOGGER_IMPL_LOG4CXX_H
+#define __LOGGER_IMPL_LOG4CXX_H
+
+#include <cstdlib>
+#include <string>
+#include <boost/lexical_cast.hpp>
+#include <log4cxx/logger.h>
+#include <log4cxx/logger.h>
+
+#include <log/debug_levels.h>
+#include <log/logger.h>
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Log4cxx Logger Implementation
+///
+/// The logger uses a "pimpl" idiom for implementation, where the base logger
+/// class contains little more than a pointer to the implementation class, and
+/// all actions are carried out by the latter. This class is an implementation
+/// class interfacing to the log4cxx logging system.
+
+class LoggerImpl {
+public:
+
+ /// \brief Constructor
+ ///
+ /// Creates/attaches to a logger of a specific name.
+ ///
+ /// \param name Name of the logger. If the name is that of the root name,
+ /// this creates an instance of the root logger; otherwise it creates a
+ /// child of the root logger.
+ ///
+ /// \param exit_delete This argument is present to get round a bug in
+ /// log4cxx. If a log4cxx logger is declared outside an execution unit, it
+ /// is not deleted until the program runs down. At that point all such
+ /// objects - including internal log4cxx objects - are deleted. However,
+ /// there seems to be a bug in log4cxx where the way that such objects are
+ /// destroyed causes a MutexException to be thrown (this is described in
+ /// https://issues.apache.org/jira/browse/LOGCXX-322). As this only occurs
+ /// during program rundown, the issue is not serious - it just looks bad to
+ /// have the program crash instead of shut down cleanly.\n
+ /// \n
+ /// The original implementation of the isc::log::Logger had as a member a
+ /// log4cxx logger (actually a LoggerPtr). If the isc:: Logger was declared
+ /// statically, when it was destroyed at the end of the program the internal
+ /// LoggerPtr was destroyed, which triggered the problem. The problem did
+ /// not occur if the isc::log::Logger was created on the stack. To get
+ /// round this, the internal LoggerPtr is now created dynamically. The
+ /// exit_delete argument controls its destruction: if true, it is destroyed
+ /// in the ISC Logger destructor. If false, it is not.\n
+ /// \n
+ /// When creating an isc::log::Logger on the stack, the argument should be
+ /// false (the default); when the Logger is destroyed, all the internal
+ /// log4cxx objects are destroyed. As only the logger (and not the internal
+ /// log4cxx data structures are being destroyed), all is well. However,
+ /// when creating the logger statically, the argument should be false. This
+ /// means that the log4cxx objects are not destroyed at program rundown;
+ /// instead memory is reclaimed and files are closed when the process is
+ /// destroyed, something that does not trigger the bug.
+ LoggerImpl(const std::string& name, bool exit_delete = false) :
+ loggerptr_(NULL), name_(name), exit_delete_(exit_delete)
+ {}
+
+
+ /// \brief Destructor
+ virtual ~LoggerImpl();
+
+
+ /// \brief Get the full name of the logger (including the root name)
+ virtual std::string getName() {
+ return (getLogger()->getName());
+ }
+
+
+ /// \brief Set Severity Level for Logger
+ ///
+ /// Sets the level at which this logger will log messages. If none is set,
+ /// the level is inherited from the parent.
+ ///
+ /// \param severity Severity level to log
+ /// \param dbglevel If the severity is DEBUG, this is the debug level.
+ /// This can be in the range 1 to 100 and controls the verbosity. A value
+ /// outside these limits is silently coerced to the nearest boundary.
+ virtual void setSeverity(isc::log::Severity severity, int dbglevel = 1);
+
+
+ /// \brief Get Severity Level for Logger
+ ///
+ /// \return The current logging level of this logger. In most cases though,
+ /// the effective logging level is what is required.
+ virtual isc::log::Severity getSeverity() {
+ return (getSeverityCommon(getLogger(), false));
+ }
+
+
+ /// \brief Get Effective Severity Level for Logger
+ ///
+ /// \return The effective severity level of the logger. This is the same
+ /// as getSeverity() if the logger has a severity level set, but otherwise
+ /// is the severity of the parent.
+ virtual isc::log::Severity getEffectiveSeverity() {
+ return (getSeverityCommon(getLogger(), true));
+ }
+
+
+ /// \brief Return DEBUG Level
+ ///
+ /// \return Current setting of debug level. This is returned regardless of
+ /// whether the
+ virtual int getDebugLevel();
+
+
+ /// \brief Returns if Debug Message Should Be Output
+ ///
+ /// \param dbglevel Level for which debugging is checked. Debugging is
+ /// enabled only if the logger has DEBUG enabled and if the dbglevel
+ /// checked is less than or equal to the debug level set for the logger.
+ virtual bool
+ isDebugEnabled(int dbglevel = MIN_DEBUG_LEVEL) {
+ return (getLogger()->getEffectiveLevel()->toInt() <=
+ (log4cxx::Level::DEBUG_INT - dbglevel));
+ }
+
+
+ /// \brief Is INFO Enabled?
+ virtual bool isInfoEnabled() {
+ return (getLogger()->isInfoEnabled());
+ }
+
+
+ /// \brief Is WARNING Enabled?
+ virtual bool isWarnEnabled() {
+ return (getLogger()->isWarnEnabled());
+ }
+
+
+ /// \brief Is ERROR Enabled?
+ virtual bool isErrorEnabled() {
+ return (getLogger()->isErrorEnabled());
+ }
+
+
+ /// \brief Is FATAL Enabled?
+ virtual bool isFatalEnabled() {
+ return (getLogger()->isFatalEnabled());
+ }
+
+
+ /// \brief Output Debug Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ void debug(const MessageID& ident, const char* text) {
+ LOG4CXX_DEBUG(getLogger(), ident << ", " << text);
+ }
+
+
+ /// \brief Output Informational Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ void info(const MessageID& ident, const char* text) {
+ LOG4CXX_INFO(getLogger(), ident << ", " << text);
+ }
+
+
+ /// \brief Output Warning Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ void warn(const MessageID& ident, const char* text) {
+ LOG4CXX_WARN(getLogger(), ident << ", " << text);
+ }
+
+
+ /// \brief Output Error Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ void error(const MessageID& ident, const char* text) {
+ LOG4CXX_ERROR(getLogger(), ident << ", " << text);
+ }
+
+
+ /// \brief Output Fatal Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ void fatal(const MessageID& ident, const char* text) {
+ LOG4CXX_FATAL(getLogger(), ident << ", " << text);
+ }
+
+ //@{
+ /// \brief Testing Methods
+ ///
+ /// The next set of methods are used in testing. As they are accessed from
+ /// the main logger class, they must be public.
+
+ /// \brief Equality
+ ///
+ /// Check if two instances of this logger refer to the same stream.
+ /// (This method is principally for testing.)
+ ///
+ /// \return true if the logger objects are instances of the same logger.
+ bool operator==(LoggerImpl& other) {
+ return (*loggerptr_ == *other.loggerptr_);
+ }
+
+
+ /// \brief Logger Initialized
+ ///
+ /// Check that the logger has been properly initialized. (This method
+ /// is principally for testing.)
+ ///
+ /// \return true if this logger object has been initialized.
+ bool isInitialized() {
+ return (loggerptr_ != NULL);
+ }
+
+ /// \brief Reset Global Data
+ ///
+ /// Only used for testing, this clears all the logger information and
+ /// resets it back to default values. This is a no-op for log4cxx.
+ static void reset() {
+ }
+
+ //@}
+
+protected:
+
+ /// \brief Convert Between BIND-10 and log4cxx Logging Levels
+ ///
+ /// This method is marked protected to allow for unit testing.
+ ///
+ /// \param value log4cxx numeric logging level
+ ///
+ /// \return BIND-10 logging severity
+ isc::log::Severity convertLevel(int value);
+
+private:
+
+ /// \brief Get Severity Level for Logger
+ ///
+ /// This is common code for getSeverity() and getEffectiveSeverity() -
+ /// it returns the severity of the logger; if not set (and the check_parent)
+ /// flag is set, it searches up the parent-child tree until a severity
+ /// level is found and uses that.
+ ///
+ /// \param ptrlogger Pointer to the log4cxx logger to check.
+ /// \param check_parent true to search up the tree, false to return the
+ /// current level.
+ ///
+ /// \return The effective severity level of the logger. This is the same
+ /// as getSeverity() if the logger has a severity level set, but otherwise
+ /// is the severity of the parent.
+ isc::log::Severity getSeverityCommon(const log4cxx::LoggerPtr& ptrlogger,
+ bool check_parent);
+
+
+
+ /// \brief Initialize log4cxx Logger
+ ///
+ /// Creates the log4cxx logger used internally. A function is provided for
+ /// this so that the creation does not take place when this Logger object
+ /// is created but when it is used. As the latter occurs in executable
+ /// code but the former can occur during initialization, this order
+ /// guarantees that anything that is statically initialized has completed
+ /// its initialization by the time the logger is used.
+ void initLogger();
+
+
+ /// \brief Return underlying log4cxx logger, initializing it if necessary
+ ///
+ /// \return Loggerptr object
+ log4cxx::LoggerPtr& getLogger() {
+ if (loggerptr_ == NULL) {
+ initLogger();
+ }
+ return (*loggerptr_);
+ }
+
+ // Members. Note that loggerptr_ is a pointer to a LoggerPtr, which is
+ // itself a pointer to the underlying log4cxx logger. This is due to the
+ // problems with memory deletion on program exit, explained in the comments
+ // for the "exit_delete" parameter in this class's constructor.
+
+ log4cxx::LoggerPtr* loggerptr_; ///< Pointer to the underlying logger
+ std::string name_; ///< Name of this logger]
+ bool exit_delete_; ///< Delete loggerptr_ on exit?
+
+ // NOTE - THIS IS A PLACE HOLDER
+ static bool init_; ///< Set true when initialized
+};
+
+} // namespace log
+} // namespace isc
+
+
+#endif // __LOGGER_IMPL_LOG4CXX_H
diff --git a/src/lib/log/logger_levels.h b/src/lib/log/logger_levels.h
new file mode 100644
index 0000000..2f123e8
--- /dev/null
+++ b/src/lib/log/logger_levels.h
@@ -0,0 +1,42 @@
+// 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 __LOGGER_LEVELS_H
+#define __LOGGER_LEVELS_H
+
+namespace isc {
+namespace log {
+
+/// \brief Severity Levels
+///
+/// Defines the severity levels for logging. This is shared between the logger
+/// and the implementations classes.
+///
+/// N.B. The order of the levels - DEBUG less than INFO less that WARN etc. is
+/// implicitly assumed in several implementations. They must not be changed.
+
+typedef enum {
+ DEFAULT = 0, // Default to logging level of the parent
+ DEBUG = 1,
+ INFO = 2,
+ WARN = 3,
+ ERROR = 4,
+ FATAL = 5,
+ NONE = 6 // Disable logging
+} Severity;
+
+} // namespace log
+} // namespace isc
+
+#endif // __LOGGER_LEVELS_H
diff --git a/src/lib/log/logger_support.cc b/src/lib/log/logger_support.cc
new file mode 100644
index 0000000..f8bf075
--- /dev/null
+++ b/src/lib/log/logger_support.cc
@@ -0,0 +1,132 @@
+// 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 Temporary Logger Support
+///
+/// Performs run-time initialization of the logger system. In particular, it
+/// is passed information from the command line and:
+///
+/// a) Sets the severity of the messages being logged (and debug level if
+/// appropriate).
+/// b) Reads in the local message file is one has been supplied.
+///
+/// These functions will be replaced once the code has been written to obtain
+/// the logging parameters from the configuration database.
+
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <boost/lexical_cast.hpp>
+
+#include <log/logger.h>
+#include <log/logger_support.h>
+#include <log/messagedef.h>
+#include <log/message_dictionary.h>
+#include <log/message_exception.h>
+#include <log/message_initializer.h>
+#include <log/message_reader.h>
+#include <log/message_types.h>
+#include <log/root_logger_name.h>
+
+namespace isc {
+namespace log {
+
+using namespace std;
+
+// Declare a logger for the logging subsystem. This is a sub-logger of the
+// root logger and is used in all functions in this file.
+Logger logger("log");
+
+
+/// \brief Reads Local Message File
+///
+/// Reads the local message file into the global dictionary, overwriting
+/// existing messages. If the file contained any message IDs not in the
+/// dictionary, they are listed in a warning message.
+///
+/// \param file Name of the local message file
+static void
+readLocalMessageFile(const char* file) {
+
+ MessageDictionary& dictionary = MessageDictionary::globalDictionary();
+ MessageReader reader(&dictionary);
+ try {
+ logger.info(MSG_RDLOCMES, file);
+ reader.readFile(file, MessageReader::REPLACE);
+
+ // File successfully read, list the duplicates
+ MessageReader::MessageIDCollection unknown = reader.getNotAdded();
+ for (MessageReader::MessageIDCollection::const_iterator
+ i = unknown.begin(); i != unknown.end(); ++i) {
+ string message_id = boost::lexical_cast<string>(*i);
+ logger.warn(MSG_IDNOTFND, message_id.c_str());
+ }
+ }
+ catch (MessageException& e) {
+ MessageID ident = e.id();
+ vector<string> args = e.arguments();
+ switch (args.size()) {
+ case 0:
+ logger.error(ident);
+ break;
+
+ case 1:
+ logger.error(ident, args[0].c_str());
+ break;
+
+ default: // 2 or more (2 should be the maximum)
+ logger.error(ident, args[0].c_str(), args[1].c_str());
+ }
+ }
+}
+
+/// Logger Run-Time Initialization
+
+void
+initLogger(const string& root, isc::log::Severity severity, int dbglevel,
+ const char* file) {
+
+ // Create the application root logger and set the default severity and
+ // debug level. This is the logger that has the name of the application.
+ // All other loggers created in this application will be its children.
+ setRootLoggerName(root);
+ Logger root_logger(isc::log::getRootLoggerName(), true);
+
+ // Set the severity associated with it. If no other logger has a severity,
+ // this will be the default.
+ root_logger.setSeverity(severity, dbglevel);
+
+ // Check if there were any duplicate message IDs in the default dictionary
+ // and if so, log them. Log using the logging facility root logger.
+ vector<string>& duplicates = MessageInitializer::getDuplicates();
+ if (!duplicates.empty()) {
+
+ // There are - sort and remove any duplicates.
+ sort(duplicates.begin(), duplicates.end());
+ vector<string>::iterator new_end =
+ unique(duplicates.begin(), duplicates.end());
+ for (vector<string>::iterator i = duplicates.begin(); i != new_end; ++i) {
+ logger.warn(MSG_DUPMSGID, i->c_str());
+ }
+
+ }
+
+ // Replace any messages with local ones (if given)
+ if (file) {
+ readLocalMessageFile(file);
+ }
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/logger_support.h b/src/lib/log/logger_support.h
new file mode 100644
index 0000000..6b5fdec
--- /dev/null
+++ b/src/lib/log/logger_support.h
@@ -0,0 +1,46 @@
+// 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 __LOGGER_SUPPORT_H
+#define __LOGGER_SUPPORT_H
+
+#include <string>
+#include <log/logger.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Run-Time Initialization
+///
+/// Performs run-time initialization of the logger in particular supplying:
+///
+/// - Name of the root logger
+/// - The severity (and if applicable, debug level) for the root logger.
+/// - Name of a local message file, containing localisation of message text.
+///
+/// This function is likely to change over time as more debugging options are
+/// held in the configuration database.
+///
+/// \param root Name of the root logger
+/// \param severity Severity at which to log
+/// \param dbglevel Debug severity (ignored if "severity" is not "DEBUG")
+/// \param file Name of the local message file.
+void initLogger(const std::string& root, isc::log::Severity severity,
+ int dbglevel, const char* file);
+
+} // namespace log
+} // namespace isc
+
+
+#endif // __LOGGER_SUPPORT_H
diff --git a/src/lib/log/message_dictionary.cc b/src/lib/log/message_dictionary.cc
new file mode 100644
index 0000000..deb8232
--- /dev/null
+++ b/src/lib/log/message_dictionary.cc
@@ -0,0 +1,113 @@
+// 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 <cstddef>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// (Virtual) Destructor
+
+MessageDictionary::~MessageDictionary() {
+}
+
+// Add message and note if ID already exists
+
+bool
+MessageDictionary::add(const string& ident, const string& text) {
+ Dictionary::iterator i = dictionary_.find(ident);
+ bool not_found = (i == dictionary_.end());
+ if (not_found) {
+
+ // Message not already in the dictionary, so add it.
+ dictionary_[ident] = text;
+ }
+
+ return (not_found);
+}
+
+// Add message and note if ID does not already exist
+
+bool
+MessageDictionary::replace(const string& ident, const string& text) {
+ Dictionary::iterator i = dictionary_.find(ident);
+ bool found = (i != dictionary_.end());
+ if (found) {
+
+ // Exists, so replace it.
+ dictionary_[ident] = text;
+ }
+
+ return (found);
+}
+
+// Load a set of messages
+
+vector<std::string>
+MessageDictionary::load(const char* messages[]) {
+ vector<std::string> duplicates;
+ int i = 0;
+ while (messages[i]) {
+
+ // ID present, so note it and point to text.
+ const MessageID ident(messages[i++]);
+ if (messages[i]) {
+
+ // Text not null, note it and point to next ident.
+ string text(messages[i++]);
+
+ // Add ID and text to message dictionary, noting if the ID was
+ // already present.
+ bool added = add(ident, text);
+ if (!added) {
+ duplicates.push_back(boost::lexical_cast<string>(ident));
+ }
+ }
+ }
+ return (duplicates);
+}
+
+// Return message text or blank string. A reference is returned to a string
+// in the dictionary - this is fine, as the string is immediately used for
+// output.
+
+const string&
+MessageDictionary::getText(const string& ident) const {
+ static const string empty("");
+ Dictionary::const_iterator i = dictionary_.find(ident);
+ if (i == dictionary_.end()) {
+ return (empty);
+ }
+ else {
+ return (i->second);
+ }
+}
+
+// Return global dictionary
+
+MessageDictionary&
+MessageDictionary::globalDictionary() {
+ static MessageDictionary global;
+ return (global);
+}
+
+
+
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/message_dictionary.h b/src/lib/log/message_dictionary.h
new file mode 100644
index 0000000..23f76d7
--- /dev/null
+++ b/src/lib/log/message_dictionary.h
@@ -0,0 +1,190 @@
+// 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 __MESSAGE_DICTIONARY_H
+#define __MESSAGE_DICTIONARY_H
+
+#include <cstddef>
+#include <string>
+#include <map>
+#include <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Message Dictionary
+///
+/// The message dictionary is a wrapper around a std::map object, and allows
+/// message text to be retrieved given the string identification.
+///
+/// Adding text occurs in two modes:
+///
+/// Through the "Add" method, ID/text mappings are added to the dictionary
+/// unless the ID already exists. This is designed for use during program
+/// initialization, where a local message may supplant a compiled-in message.
+///
+/// Through the "Replace" method, ID/text mappings are added to the dictionary
+/// only if the ID already exists. This is for use when a message file is
+/// supplied to replace messages provided with the program.
+///
+/// Although the class can be used stand-alone, it does supply a static method
+/// to return a particular instance - the "global" dictionary.
+
+class MessageDictionary {
+public:
+
+ typedef std::map<std::string, std::string> Dictionary;
+ typedef Dictionary::const_iterator const_iterator;
+
+ // Default constructor and assignment operator are OK for this class
+
+ /// \brief Virtual Destructor
+ virtual ~MessageDictionary();
+
+ /// \brief Add Message
+ ///
+ /// Adds a message to the dictionary. If the ID already exists, the ID is
+ /// added to the overflow vector.
+ ///
+ /// \param ident Identification of the message to add
+ /// \param text Message text
+ ///
+ /// \return true if the message was added to the dictionary, false if the
+ /// message existed and it was not added.
+ virtual bool add(const MessageID& ident, const std::string& text) {
+ return (add(boost::lexical_cast<std::string>(ident), text));
+ }
+
+ /// \brief Add Message
+ ///
+ /// Alternate signature.
+ ///
+ /// \param ident Identification of the message to add
+ /// \param text Message text
+ ///
+ /// \return true if the message was added to the dictionary, false if the
+ /// message existed and it was not added.
+ virtual bool add (const std::string& ident, const std::string& test);
+
+
+ /// \brief Replace Message
+ ///
+ /// Replaces a message in the dictionary. If the ID does not exist, it is
+ /// added to the overflow vector.
+ ///
+ /// \param ident Identification of the message to replace
+ /// \param text Message text
+ ///
+ /// \return true if the message was added to the dictionary, false if the
+ /// message did not exist and it was not added.
+ virtual bool replace(const MessageID& ident, const std::string& text) {
+ return (replace(boost::lexical_cast<std::string>(ident), text));
+ }
+
+
+ /// \brief Replace Message
+ ///
+ /// Alternate signature.
+ ///
+ /// \param ident Identification of the message to replace
+ /// \param text Message text
+ ///
+ /// \return true if the message was added to the dictionary, false if the
+ /// message did not exist and it was not added.
+ virtual bool replace(const std::string& ident, const std::string& text);
+
+
+ /// \brief Load Dictionary
+ ///
+ /// Designed to be used during the initialization of programs, this
+ /// accepts a set of (ID, text) pairs as a one-dimensional array of
+ /// const char* and adds them to the dictionary. The messages are added
+ /// using "Add".
+ ///
+ /// \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.
+ ///
+ /// \return Vector of message IDs that were not loaded because an ID of the
+ /// same name already existing in the dictionary. This vector may be
+ /// empty.
+ virtual std::vector<std::string> load(const char* elements[]);
+
+
+ /// \brief Get Message Text
+ ///
+ /// Given an ID, retrieve associated message text.
+ ///
+ /// \param ident Message identification
+ ///
+ /// \return Text associated with message or empty string if the ID is not
+ /// recognised. (Note: this precludes an ID being associated with an empty
+ /// string.)
+ virtual const std::string& getText(const MessageID& ident) const {
+ return(getText(boost::lexical_cast<std::string>(ident)));
+ }
+
+
+ /// \brief Get Message Text
+ ///
+ /// Alternate signature.
+ ///
+ /// \param ident Message identification
+ ///
+ /// \return Text associated with message or empty string if the ID is not
+ /// recognised. (Note: this precludes an ID being associated with an empty
+ /// string.)
+ virtual const std::string& getText(const std::string& ident) const;
+
+
+ /// \brief Number of Items in Dictionary
+ ///
+ /// \return Number of items in the dictionary
+ virtual size_t size() const {
+ return (dictionary_.size());
+ }
+
+
+ /// \brief Return begin() iterator of internal map
+ const_iterator begin() const {
+ return (dictionary_.begin());
+ }
+
+
+ /// \brief Return end() iterator of internal map
+ const_iterator end() const {
+ return (dictionary_.end());
+ }
+
+
+ /// \brief Return Global Dictionary
+ ///
+ /// Returns a pointer to the singleton global dictionary.
+ ///
+ /// \return Pointer to global dictionary.
+ static MessageDictionary& globalDictionary();
+
+private:
+ Dictionary dictionary_; ///< Holds the ID to text lookups
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGE_DICTIONARY_H
diff --git a/src/lib/log/message_exception.cc b/src/lib/log/message_exception.cc
new file mode 100644
index 0000000..1a69ca5
--- /dev/null
+++ b/src/lib/log/message_exception.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.
+
+/// \brief Body of Virtual Destructor
+
+#include <log/message_exception.h>
+
+namespace isc {
+namespace log {
+
+MessageException::~MessageException() throw() {
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/message_exception.h b/src/lib/log/message_exception.h
new file mode 100644
index 0000000..30c6618
--- /dev/null
+++ b/src/lib/log/message_exception.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.
+
+#ifndef __MESSAGE_EXCEPTION_H
+#define __MESSAGE_EXCEPTION_H
+
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Message Exception
+///
+/// Used in the message reader, this simple exception class allows a message
+/// code and its arguments to be encapsulated in an exception and thrown
+/// up the stack.
+
+class MessageException : public std::exception {
+public:
+
+ /// \brief Constructor
+ ///
+ /// \param id Message identification
+ MessageException(MessageID id) : id_(id)
+ {}
+
+ /// \brief Constructor
+ ///
+ /// \param id Message identification
+ /// \param arg1 First message argument
+ MessageException(MessageID id, const std::string& arg1) : id_(id)
+ {
+ args_.push_back(arg1);
+ }
+
+ /// \brief Constructor
+ ///
+ /// \param id Message identification
+ /// \param arg1 First message argument
+ /// \param arg2 Second message argument
+ MessageException(MessageID id, const std::string& arg1,
+ const std::string& arg2) : id_(id)
+ {
+ args_.push_back(arg1);
+ args_.push_back(arg2);
+ }
+
+ /// \brief Destructor
+ virtual ~MessageException() throw();
+
+ /// \brief Return Message ID
+ ///
+ /// \return Message identification
+ MessageID id() const {
+ return id_;
+ }
+
+ /// \brief Return Arguments
+ ///
+ /// \return Exception Arguments
+ std::vector<std::string> arguments() const {
+ return (args_);
+ }
+
+private:
+ MessageID id_; // Exception ID
+ std::vector<std::string> args_; // Exception arguments
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGE_EXCEPTION_H
diff --git a/src/lib/log/message_initializer.cc b/src/lib/log/message_initializer.cc
new file mode 100644
index 0000000..0113497
--- /dev/null
+++ b/src/lib/log/message_initializer.cc
@@ -0,0 +1,44 @@
+// 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 <log/message_dictionary.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace log {
+
+// Constructor. Just retrieve the global dictionary and load the IDs and
+// associated text into it.
+
+MessageInitializer::MessageInitializer(const char* values[]) {
+ MessageDictionary& global = MessageDictionary::globalDictionary();
+ std::vector<std::string> repeats = global.load(values);
+
+ // Append the IDs in the list just loaded (the "repeats") to the global list
+ // of duplicate IDs.
+ if (!repeats.empty()) {
+ std::vector<std::string>& duplicates = getDuplicates();
+ duplicates.insert(duplicates.end(), repeats.begin(), repeats.end());
+ }
+}
+
+// Return reference to duplicate array
+
+std::vector<std::string>& MessageInitializer::getDuplicates() {
+ static std::vector<std::string> duplicates;
+ return (duplicates);
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/message_initializer.h b/src/lib/log/message_initializer.h
new file mode 100644
index 0000000..7516823
--- /dev/null
+++ b/src/lib/log/message_initializer.h
@@ -0,0 +1,75 @@
+// 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 __MESSAGEINITIALIZER_H
+#define __MESSAGEINITIALIZER_H
+
+#include <string>
+#include <vector>
+#include <log/message_dictionary.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Initialize Message Dictionary
+///
+/// This is a helper class to add a set of message IDs and associated text to
+/// the global dictionary.
+///
+/// It should be declared outside an execution unit and initialized with a
+/// an array of values, alternating identifier, associated text and ending with
+/// a NULL, e.g.
+///
+/// static const char* values[] = {
+/// "IDENT1", "message for ident 1",
+/// "IDENT2", "message for ident 2",
+/// :
+/// NULL
+/// };
+/// MessageDictionaryHelper xyz(values);
+///
+/// This will automatically add the message ID/text pairs to the global
+/// dictionary during initialization - all that is required is that the module
+/// containing the definition is included into the final executable.
+///
+/// Messages are added via the MessageDictionary::add() method, so any
+/// duplicates are stored in the the global dictionary's overflow vector whence
+/// they can be retrieved at run-time.
+
+class MessageInitializer {
+public:
+
+ /// \brief Constructor
+ ///
+ /// Adds the array of values to the global dictionary, and notes any
+ /// duplicates.
+ ///
+ /// \param values NULL-terminated array of alternating identifier strings
+ /// and associated message text.
+ MessageInitializer(const char* values[]);
+
+ /// \brief Return Duplicates
+ ///
+ /// When messages are added to the global dictionary, any duplicates are
+ /// recorded. They can later be output through the logging system.
+ ///
+ /// \return List of duplicate message IDs when the global dictionary was
+ /// loaded. Note that the duplicates list itself may contain duplicates.
+ static std::vector<std::string>& getDuplicates();
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGEINITIALIZER_H
diff --git a/src/lib/log/message_reader.cc b/src/lib/log/message_reader.cc
new file mode 100644
index 0000000..4402b0e
--- /dev/null
+++ b/src/lib/log/message_reader.cc
@@ -0,0 +1,233 @@
+// 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 <errno.h>
+#include <string.h>
+
+#include <iostream>
+#include <fstream>
+
+#include <log/message_exception.h>
+#include <log/messagedef.h>
+#include <log/message_reader.h>
+#include <log/strutil.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// Virtual destructor.
+MessageReader::~MessageReader() {
+}
+
+
+// Read the file.
+
+void
+MessageReader::readFile(const string& file, MessageReader::Mode mode) {
+
+ // Ensure the non-added collection is empty: this object might be
+ // being reused.
+ not_added_.clear();
+
+ // Open the file
+ ifstream infile(file.c_str());
+ if (infile.fail()) {
+ throw MessageException(MSG_OPNMSGIN, file, strerror(errno));
+ }
+
+ // Loop round reading it.
+ string line;
+ getline(infile, line);
+ while (infile.good()) {
+ processLine(line, mode);
+ getline(infile, line);
+ }
+
+ // Why did the loop terminate?
+ if (!infile.eof()) {
+ throw MessageException(MSG_MSGRDERR, file, strerror(errno));
+ }
+ infile.close();
+}
+
+// Parse a line of the file
+
+void
+MessageReader::processLine(const string& line, MessageReader::Mode mode) {
+
+ // Get rid of leading and trailing spaces
+ string text = isc::strutil::trim(line);
+
+ if (text.empty()) {
+ ; // Ignore blank lines
+
+ } else if ((text[0] == '#') || (text[0] == '+')) {
+ ; // Ignore comments or descriptions
+
+ } else if (text[0] == '$') {
+ parseDirective(text); // Process directives
+
+ } else {
+ parseMessage(text, mode); // Process other lines
+
+ }
+}
+
+// Process directive
+
+void
+MessageReader::parseDirective(const std::string& text) {
+
+
+ // Break into tokens
+ vector<string> tokens = isc::strutil::tokens(text);
+
+ // Uppercase directive and branch on valid ones
+ isc::strutil::uppercase(tokens[0]);
+ if (tokens[0] == string("$PREFIX")) {
+ parsePrefix(tokens);
+ } else if (tokens[0] == string("$NAMESPACE")) {
+ parseNamespace(tokens);
+ } else {
+ throw MessageException(MSG_UNRECDIR, tokens[0]);
+ }
+}
+
+// Process $PREFIX
+
+void
+MessageReader::parsePrefix(const vector<string>& tokens) {
+
+ // Check argument count
+
+ static string valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
+ if (tokens.size() < 2) {
+ throw MessageException(MSG_PRFNOARG);
+ } else if (tokens.size() > 2) {
+ throw MessageException(MSG_PRFEXTRARG);
+
+ }
+
+ // As a style, we are going to have the symbols in uppercase
+ string prefix = tokens[1];
+ isc::strutil::uppercase(prefix);
+
+ // Token is potentially valid providing it only contains alphabetic
+ // and numeric characters (and underscores) and does not start with a
+ // digit.
+ if ((prefix.find_first_not_of(valid) != string::npos) ||
+ (std::isdigit(prefix[0]))) {
+
+ // Invalid character in string or it starts with a digit.
+ throw MessageException(MSG_PRFINVARG, tokens[1]);
+ }
+
+ // All OK - unless the prefix has already been set.
+
+ if (prefix_.size() != 0) {
+ throw MessageException(MSG_DUPLPRFX);
+ }
+
+ // Prefix has not been set, so set it and return success.
+
+ prefix_ = prefix;
+}
+
+// Process $NAMESPACE. A lot of the processing is similar to that of $PREFIX,
+// except that only limited checks will be done on the namespace (to avoid a
+// lot of parsing and separating out of the namespace components.)
+
+void
+MessageReader::parseNamespace(const vector<string>& tokens) {
+
+ // Check argument count
+
+ static string valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_:"
+ "abcdefghijklmnopqrstuvwxyz";
+
+ if (tokens.size() < 2) {
+ throw MessageException(MSG_NSNOARG);
+
+ } else if (tokens.size() > 2) {
+ throw MessageException(MSG_NSEXTRARG);
+
+ }
+
+ // Token is potentially valid providing it only contains alphabetic
+ // and numeric characters (and underscores and colons).
+ if (tokens[1].find_first_not_of(valid) != string::npos) {
+
+ // Invalid character in string or it starts with a digit.
+ throw MessageException(MSG_NSINVARG, tokens[1]);
+ }
+
+ // All OK - unless the namespace has already been set.
+ if (ns_.size() != 0) {
+ throw MessageException(MSG_DUPLNS);
+ }
+
+ // Prefix has not been set, so set it and return success.
+
+ ns_ = tokens[1];
+}
+
+// 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 converted to uppercase
+// and becomes the message ID; the rest of the line is the message text.
+
+void
+MessageReader::parseMessage(const std::string& text, MessageReader::Mode mode) {
+
+ static string delimiters("\t\n "); // Delimiters
+
+ // Look for the first delimiter.
+ size_t first_delim = text.find_first_of(delimiters);
+ if (first_delim == string::npos) {
+
+ // Just a single token in the line - this is not valid
+ throw MessageException(MSG_NOMSGTXT, text);
+ }
+
+ // Extract the first token into the message ID
+ string ident = text.substr(0, first_delim);
+
+ // Locate the start of the message text
+ size_t first_text = text.find_first_not_of(delimiters, first_delim);
+ if (first_text == string::npos) {
+
+ // ?? This happens if there are trailing delimiters, which should not
+ // occur as we have stripped trailing spaces off the line. Just treat
+ // this as a single-token error for simplicity's sake.
+ throw MessageException(MSG_NOMSGTXT, text);
+ }
+
+ // Add the result to the dictionary and to the non-added list if the add to
+ // the dictionary fails.
+ bool added;
+ if (mode == ADD) {
+ added = dictionary_->add(ident, text.substr(first_text));
+ }
+ else {
+ added = dictionary_->replace(ident, text.substr(first_text));
+ }
+ if (!added) {
+ not_added_.push_back(ident);
+ }
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/message_reader.h b/src/lib/log/message_reader.h
new file mode 100644
index 0000000..d07c7f2
--- /dev/null
+++ b/src/lib/log/message_reader.h
@@ -0,0 +1,202 @@
+// 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 __MESSAGE_READER_H
+#define __MESSAGE_READER_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Read Message File
+///
+/// Reads a message file and creates a map of identifier against the text of the
+/// message. This map can be retrieved for subsequent processing.
+
+class MessageReader {
+public:
+
+ /// \brief Read Mode
+ ///
+ /// If ADD, messages are added to the dictionary if the ID does not exist
+ /// there. If it does, the ID is added to the dictionary's overflow
+ /// vector.
+ ///
+ /// If REPLACE, the dictionary is only modified if the message ID already
+ /// exists in it. New message IDs are added to the overflow vector.
+ typedef enum {
+ ADD,
+ REPLACE
+ } Mode;
+
+ /// \brief Visible collection types
+ typedef std::vector<std::string> MessageIDCollection;
+
+ /// \brief Constructor
+ ///
+ /// Default constructor. All work is done in the main readFile code (so
+ /// that a status return can be returned instead of needing to throw an
+ /// exception).
+ ///
+ /// \param dictionary Dictionary to which messages read read from the file
+ /// are added. (This should be a local dictionary when the class is used in
+ /// the message compiler, and the global dictionary when used in a server.
+ /// The ownership of the dictionary object is not transferred - the caller
+ /// is responsible for managing the lifetime of the dictionary.
+ MessageReader(MessageDictionary* dictionary = NULL) :
+ dictionary_(dictionary)
+ {}
+
+
+ /// \brief Virtual Destructor
+ virtual ~MessageReader();
+
+
+ /// \brief Get Dictionary
+ ///
+ /// Returns the pointer to the dictionary object. Note that ownership is
+ /// not transferred - the caller should not delete it.
+ ///
+ /// \return Pointer to current dictionary object
+ MessageDictionary* getDictionary() const {
+ return (dictionary_);
+ }
+
+
+ /// \brief Set Dictionary
+ ///
+ /// Sets the current dictionary object.
+ ///
+ /// \param dictionary New dictionary object. The ownership of the dictionary
+ /// object is not transferred - the caller is responsible for managing the
+ /// lifetime of the dictionary.
+ void setDictionary(MessageDictionary* dictionary) {
+ dictionary_ = dictionary;
+ }
+
+
+ /// \brief Read File
+ ///
+ /// This is the main method of the class and reads in the file, parses it,
+ /// and stores the result in the message dictionary.
+ ///
+ /// \param file Name of the message file.
+ /// \param mode Addition mode. See the description of the "Mode" enum.
+ virtual void readFile(const std::string& file, Mode mode = ADD);
+
+
+ /// \brief Process Line
+ ///
+ /// Parses a text line and adds it to the message map. Although this is
+ /// for use in readFile, it can also be used to add individual messages
+ /// to the message map.
+ ///
+ /// \param line Line of text to process
+ /// \param mode If a message line, how to add the message to the dictionary.
+ virtual void processLine(const std::string& line, Mode mode = ADD);
+
+
+ /// \brief Get Namespace
+ ///
+ /// \return Argument to the $NAMESPACE directive (if present)
+ virtual std::string getNamespace() const {
+ return (ns_);
+ }
+
+
+ /// \brief Clear Namespace
+ ///
+ /// Clears the current namespace.
+ virtual void clearNamespace() {
+ ns_ = "";
+ }
+
+
+ /// \brief Get Prefix
+ ///
+ /// \return Argument to the $PREFIX directive (if present)
+ virtual std::string getPrefix() const {
+ return (prefix_);
+ }
+
+
+ /// \brief Clear Prefix
+ ///
+ /// Clears the current prefix.
+ virtual void clearPrefix() {
+ prefix_ = "";
+ }
+
+
+ /// \brief Get Not-Added List
+ ///
+ /// Returns the list of IDs that were not added during the last
+ /// read of the file.
+ ///
+ /// \return Collection of messages not added
+ MessageIDCollection getNotAdded() const {
+ return (not_added_);
+ }
+
+private:
+
+ /// \brief Handle a Message Definition
+ ///
+ /// Passed a line that should contain a message, this processes that line
+ /// and adds it to the dictionary according to the mode setting.
+ ///
+ /// \param line Line of text
+ /// \param ADD or REPLACE depending on how the reader is operating. (See
+ /// the description of the Mode typedef for details.)
+ void parseMessage(const std::string& line, Mode mode);
+
+
+ /// \brief Handle Directive
+ ///
+ /// Passed a line starting with a "$", this handles the processing of
+ /// directives.
+ ///
+ /// \param line Line of text that starts with "$",
+ void parseDirective(const std::string& line);
+
+
+ /// \brief Parse $PREFIX line
+ ///
+ /// \param tokens $PREFIX line split into tokens
+ void parsePrefix(const std::vector<std::string>& tokens);
+
+
+ /// \brief Parse $NAMESPACE line
+ ///
+ /// \param tokens $NAMESPACE line split into tokens
+ void parseNamespace(const std::vector<std::string>& tokens);
+
+
+ /// Attributes
+ MessageDictionary* dictionary_; ///< Dictionary to add messages to
+ MessageIDCollection not_added_; ///< List of IDs not added
+ std::string prefix_; ///< Argument of $PREFIX statement
+ std::string ns_; ///< Argument of $NAMESPACE statement
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGE_READER_H
diff --git a/src/lib/log/message_types.h b/src/lib/log/message_types.h
new file mode 100644
index 0000000..9f625a9
--- /dev/null
+++ b/src/lib/log/message_types.h
@@ -0,0 +1,37 @@
+// 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 __MESSAGE_TYPES_H
+#define __MESSAGE_TYPES_H
+
+#include <string.h>
+
+namespace isc {
+namespace log {
+
+typedef const char* MessageID;
+
+/// \brief Compare MessageID for Equality
+///
+/// \param m1 First message ID
+/// \param m2 Second message ID
+/// \return true if they are equal, false if not
+bool equalMessageID(const MessageID& m1, const MessageID& m2);
+
+} // namespace log
+} // namespace isc
+
+
+
+#endif // __MESSAGE_TYPES_H
diff --git a/src/lib/log/messagedef.cc b/src/lib/log/messagedef.cc
new file mode 100644
index 0000000..f680a74
--- /dev/null
+++ b/src/lib/log/messagedef.cc
@@ -0,0 +1,57 @@
+// File created from messagedef.mes on Mon Feb 14 11:07:45 2011
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace log {
+
+extern const isc::log::MessageID MSG_DUPLNS = "DUPLNS";
+extern const isc::log::MessageID MSG_DUPLPRFX = "DUPLPRFX";
+extern const isc::log::MessageID MSG_DUPMSGID = "DUPMSGID";
+extern const isc::log::MessageID MSG_IDNOTFND = "IDNOTFND";
+extern const isc::log::MessageID MSG_MSGRDERR = "MSGRDERR";
+extern const isc::log::MessageID MSG_MSGWRTERR = "MSGWRTERR";
+extern const isc::log::MessageID MSG_NOMSGTXT = "NOMSGTXT";
+extern const isc::log::MessageID MSG_NSEXTRARG = "NSEXTRARG";
+extern const isc::log::MessageID MSG_NSINVARG = "NSINVARG";
+extern const isc::log::MessageID MSG_NSNOARG = "NSNOARG";
+extern const isc::log::MessageID MSG_OPNMSGIN = "OPNMSGIN";
+extern const isc::log::MessageID MSG_OPNMSGOUT = "OPNMSGOUT";
+extern const isc::log::MessageID MSG_PRFEXTRARG = "PRFEXTRARG";
+extern const isc::log::MessageID MSG_PRFINVARG = "PRFINVARG";
+extern const isc::log::MessageID MSG_PRFNOARG = "PRFNOARG";
+extern const isc::log::MessageID MSG_RDLOCMES = "RDLOCMES";
+extern const isc::log::MessageID MSG_UNRECDIR = "UNRECDIR";
+
+} // namespace log
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "DUPLNS", "duplicate $NAMESPACE directive found",
+ "DUPLPRFX", "duplicate $PREFIX directive found",
+ "DUPMSGID", "duplicate message ID (%s) in compiled code",
+ "IDNOTFND", "could not replace message for '%s': no such message identification",
+ "MSGRDERR", "error reading from message file %s: %s",
+ "MSGWRTERR", "error writing to %s: %s",
+ "NOMSGTXT", "a line containing a message ID ('%s') and nothing else was found",
+ "NSEXTRARG", "$NAMESPACE directive has too many arguments",
+ "NSINVARG", "$NAMESPACE directive has an invalid argument ('%s')",
+ "NSNOARG", "no arguments were given to the $NAMESPACE directive",
+ "OPNMSGIN", "unable to open message file %s for input: %s",
+ "OPNMSGOUT", "unable to open %s for output: %s",
+ "PRFEXTRARG", "$PREFIX directive has too many arguments",
+ "PRFINVARG", "$PREFIX directive has an invalid argument ('%s')",
+ "PRFNOARG", "no arguments were given to the $PREFIX directive",
+ "RDLOCMES", "reading local message file %s",
+ "UNRECDIR", "unrecognised directive '%s'",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/log/messagedef.h b/src/lib/log/messagedef.h
new file mode 100644
index 0000000..eb8f4ea
--- /dev/null
+++ b/src/lib/log/messagedef.h
@@ -0,0 +1,32 @@
+// File created from messagedef.mes on Mon Feb 14 11:07:45 2011
+
+#ifndef __MESSAGEDEF_H
+#define __MESSAGEDEF_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+extern const isc::log::MessageID MSG_DUPLNS;
+extern const isc::log::MessageID MSG_DUPLPRFX;
+extern const isc::log::MessageID MSG_DUPMSGID;
+extern const isc::log::MessageID MSG_IDNOTFND;
+extern const isc::log::MessageID MSG_MSGRDERR;
+extern const isc::log::MessageID MSG_MSGWRTERR;
+extern const isc::log::MessageID MSG_NOMSGTXT;
+extern const isc::log::MessageID MSG_NSEXTRARG;
+extern const isc::log::MessageID MSG_NSINVARG;
+extern const isc::log::MessageID MSG_NSNOARG;
+extern const isc::log::MessageID MSG_OPNMSGIN;
+extern const isc::log::MessageID MSG_OPNMSGOUT;
+extern const isc::log::MessageID MSG_PRFEXTRARG;
+extern const isc::log::MessageID MSG_PRFINVARG;
+extern const isc::log::MessageID MSG_PRFNOARG;
+extern const isc::log::MessageID MSG_RDLOCMES;
+extern const isc::log::MessageID MSG_UNRECDIR;
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGEDEF_H
diff --git a/src/lib/log/messagedef.mes b/src/lib/log/messagedef.mes
new file mode 100644
index 0000000..3599388
--- /dev/null
+++ b/src/lib/log/messagedef.mes
@@ -0,0 +1,119 @@
+# 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 MSG_
+$NAMESPACE isc::log
+
+# \brief Message Utility Message File
+#
+# This is the source of the set of messages generated by the message and logging
+# components. The associated .h and .cc files are created by hand from this
+# file though and are not built during the build process; this is to avoid the
+# chicken-and-egg situation where we need the files to build the message
+# compiler, yet we need the compiler to build the files.
+
+DUPMSGID duplicate message ID (%s) in compiled code
++ Indicative of a programming error, when it started up, BIND10 detected that
++ the given message ID had been registered by one or more modules. (All message
++ IDs should be unique throughout BIND10.) This has no impact on the operation
++ of the server other that erroneous messages may be logged. (When BIND10 loads
++ the message IDs (and their associated text), if a duplicate ID is found it is
++ discarded. However, when the module that supplied the duplicate ID logs that
++ particular message, the text supplied by the module that added the original
++ ID will be output - something that may bear no relation to the condition being
++ logged.
+
+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 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 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
++ message identification was found in the file, but the identification is not
++ one contained in the compiled-in message dictionary. Either the message
++ identification has been mis-spelled in the file, or the local file was used
++ for an earlier version of the software and the message with that
++ identification has been removed.
++
++ This message may appear a number of times in the file, once for every such
++ unknown message identification.
+
+MSGRDERR error reading from message file %s: %s
++ The specified error was encountered reading from the named message file.
+
+MSGWRTERR error writing to %s: %s
++ The specified error was encountered by the message compiler when writing to
++ the named output file.
+
+NSEXTRARG $NAMESPACE directive has too many arguments
++ The $NAMESPACE directive takes a single argument, a namespace in which all the
++ generated symbol names are placed. This error is generated when the
++ compiler finds a $NAMESPACE directive with more than one argument.
+
+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 namespace
++ are correct. The error is generated when the reader finds an invalid
++ 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
++ symbolic name for the message) and followed by the text of the message. This
++ error is generated when a line is found in the message file that contains just
++ the message identification and no text.
+
+NSNOARG no arguments were given to the $NAMESPACE directive
++ The $NAMESPACE directive takes a single argument, a namespace in which all the
++ generated symbol names are placed. This error is generated when the
++ compiler finds a $NAMESPACE directive with no arguments.
+
+OPNMSGIN unable to open message file %s for input: %s
++ The program was not able to open the specified input message file for the
++ reason given.
+
+OPNMSGOUT unable to open %s for output: %s
++ The program was not able to open the specified output file for the reason
++ given.
+
+PRFEXTRARG $PREFIX directive has too many arguments
++ The $PREFIX directive takes a single argument, a prefix to be added to the
++ symbol names when a C++ .h file is created. This error is generated when the
++ compiler finds a $PREFIX directive with more than one argument.
+
+PRFINVARG $PREFIX directive has an invalid argument ('%s')
++ The $PREFIX argument is used in a symbol name in a C++ header file. As such,
++ it must adhere to restrictions on C++ symbol names (e.g. may only contain
++ alphanumeric characters or underscores, and may nor start with a digit). A
++ $PREFIX directive was found with an argument (given in the message) that
++ violates those restictions.
+
+PRFNOARG no arguments were given to the $PREFIX directive
++ The $PREFIX directive takes a single argument, a prefix to be added to the
++ symbol names when a C++ .h file is created. This error is generated when the
++ compiler finds a $PREFIX directive with no arguments.
+
+RDLOCMES reading local message file %s
++ This is an informational message output by BIND10 when it starts to read a
++ local message file. (A local message file may replace the text of one of more
++ messages; the ID of the message will not be changed though.)
+
+UNRECDIR unrecognised directive '%s'
++ A line starting with a dollar symbol was found, but the first word on the line
++ (shown in the message) was not a recognised message compiler directive.
diff --git a/src/lib/log/root_logger_name.cc b/src/lib/log/root_logger_name.cc
new file mode 100644
index 0000000..58d9407
--- /dev/null
+++ b/src/lib/log/root_logger_name.cc
@@ -0,0 +1,44 @@
+// 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 <root_logger_name.h>
+
+namespace isc {
+namespace log {
+
+namespace {
+
+// Obtain the root logger name in a way that is safe for statically-initialized
+// objects.
+
+std::string&
+getRootLoggerNameInternal() {
+ static std::string root_name;
+ return (root_name);
+}
+
+} // Anonymous namespace
+
+void
+setRootLoggerName(const std::string& name) {
+ getRootLoggerNameInternal() = name;
+}
+
+const std::string& getRootLoggerName() {
+ return (getRootLoggerNameInternal());
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/root_logger_name.h b/src/lib/log/root_logger_name.h
new file mode 100644
index 0000000..9d50332
--- /dev/null
+++ b/src/lib/log/root_logger_name.h
@@ -0,0 +1,46 @@
+// 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 __ROOT_LOGGER_NAME_H
+#define __ROOT_LOGGER_NAME_H
+
+#include <string>
+
+/// \brief Define Name of Root Logger
+///
+/// In BIND-10, the name root logger of a program is the name of the program
+/// itself (in contrast to packages such as log4cxx where the root logger name
+// is something like "."). These trivial functions allow the setting and
+// getting of that name by the logger classes.
+
+namespace isc {
+namespace log {
+
+/// \brief Set Root Logger Name
+///
+/// This function should be called by the program's initialization code before
+/// any logging functions are called.
+///
+/// \param name Name of the root logger. This should be the program name.
+void setRootLoggerName(const std::string& name);
+
+/// \brief Get Root Logger Name
+///
+/// \return Name of the root logger.
+const std::string& getRootLoggerName();
+
+}
+}
+
+#endif // __ROOT_LOGGER_NAME_H
diff --git a/src/lib/log/strutil.cc b/src/lib/log/strutil.cc
new file mode 100644
index 0000000..65fb0cd
--- /dev/null
+++ b/src/lib/log/strutil.cc
@@ -0,0 +1,135 @@
+// 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 <numeric>
+
+#include <string.h>
+#include <strutil.h>
+
+using namespace std;
+
+namespace isc {
+namespace strutil {
+
+// Normalize slashes
+
+void
+normalizeSlash(std::string& name) {
+ if (!name.empty()) {
+ size_t pos = 0;
+ while ((pos = name.find('\\', pos)) != std::string::npos) {
+ name[pos] = '/';
+ }
+ }
+}
+
+// Trim String
+
+string
+trim(const string& instring) {
+ static const char* blanks = " \t\n";
+
+ string retstring = "";
+ if (!instring.empty()) {
+
+ // Search for first non-blank character in the string
+ size_t first = instring.find_first_not_of(blanks);
+ if (first != string::npos) {
+
+ // String not all blanks, so look for last character
+ size_t last = instring.find_last_not_of(blanks);
+
+ // Extract the trimmed substring
+ retstring = instring.substr(first, (last - first + 1));
+ }
+ }
+
+ return (retstring);
+}
+
+// Tokenise string. As noted in the header, this is locally written to avoid
+// another dependency on a Boost library.
+
+vector<string>
+tokens(const std::string& text, const std::string& delim) {
+ vector<string> result;
+
+ // Search for the first non-delimiter character
+ size_t start = text.find_first_not_of(delim);
+ while (start != string::npos) {
+
+ // Non-delimiter found, look for next delimiter
+ size_t end = text.find_first_of(delim, start);
+ if (end != string::npos) {
+
+ // Delimiter found, so extract string & search for start of next
+ // non-delimiter segment.
+ result.push_back(text.substr(start, (end - start)));
+ start = text.find_first_not_of(delim, end);
+
+ } else {
+
+ // End of string found, extract rest of string and flag to exit
+ result.push_back(text.substr(start));
+ start = string::npos;
+ }
+ }
+
+ return (result);
+}
+
+// Local function to pass to accumulate() for summing up string lengths.
+
+namespace {
+
+size_t
+lengthSum(string::size_type curlen, const string& cur_string) {
+ return (curlen + cur_string.size());
+}
+
+}
+
+// Provide printf-style formatting.
+
+std::string
+format(const std::string& format, const std::vector<std::string>& args) {
+
+ static const string flag = "%s";
+
+ // Initialize return string. To speed things up, we'll reserve an
+ // appropriate amount of space - current string size, plus length of all
+ // the argument strings, less two characters for each argument (the %s in
+ // the format string is being replaced).
+ string result;
+ size_t length = accumulate(args.begin(), args.end(), format.size(),
+ lengthSum) - (args.size() * flag.size());
+ result.reserve(length);
+
+ // Iterate through replacing all tokens
+ result = format;
+ size_t tokenpos = 0; // Position of last token replaced
+ int i = 0; // Index into argument array
+
+ while ((i < args.size()) && (tokenpos != string::npos)) {
+ tokenpos = result.find(flag, tokenpos);
+ if (tokenpos != string::npos) {
+ result.replace(tokenpos, flag.size(), args[i++]);
+ }
+ }
+
+ return (result);
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/strutil.h b/src/lib/log/strutil.h
new file mode 100644
index 0000000..087410f
--- /dev/null
+++ b/src/lib/log/strutil.h
@@ -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.
+
+#ifndef __STRUTIL_H
+#define __STRUTIL_H
+
+#include <algorithm>
+#include <cctype>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace strutil {
+
+/// \brief A Set of C++ Utilities for Manipulating Strings
+
+/// \brief Normalize Backslash
+///
+/// Only relevant to Windows, this replaces all "\" in a string with "/" and
+/// returns the result. On other systems it is a no-op. Note that Windows does
+/// recognise file names with the "\" replaced by "/" (at least in system calls,
+/// if not the command line).
+///
+/// \param name Name to be substituted
+void normalizeSlash(std::string& name);
+
+
+/// \brief Trim Leading and Trailing Spaces
+///
+/// Returns a copy of the input string but with any leading or trailing spaces
+/// or tabs removed.
+///
+/// \param instring Input string to modify
+///
+/// \return String with leading and trailing spaces removed
+std::string trim(const std::string& instring);
+
+
+/// \brief Split String into Tokens
+///
+/// Splits a string into tokens (the tokens being delimited by one or more of
+/// the delimiter characters) and returns the tokens in a vector array. Note
+/// that adjacent delimiters are considered to be a single delimiter.
+///
+/// Special cases are:
+/// -# The empty string is considered to be zero tokens.
+/// -# A string comprising nothing but delimiters is considered to be zero
+/// tokens.
+///
+/// The reasoning behind this is that the string can be thought of as having
+/// invisible leading and trailing delimiter characters. Therefore both cases
+/// reduce to a set of contiguous delimiters, which are considered a single
+/// delimiter (so getting rid of the string).
+///
+/// We could use Boost for this, but this (simple) function eliminates one
+/// dependency in the code.
+///
+/// \param text String to be split. Passed by value as the internal copy is
+/// altered during the processing.
+/// \param delim Delimiter characters
+///
+/// \return Vector of tokens.
+std::vector<std::string> tokens(const std::string& text,
+ const std::string& delim = std::string(" \t\n"));
+
+
+/// \brief Uppercase Character
+///
+/// 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 dereferencing a
+/// string::iterator returns a char.
+///
+/// \param chr Character to be upper-cased.
+///
+/// \return Uppercase version of the argument
+inline char toUpper(char chr) {
+ return (static_cast<char>(std::toupper(static_cast<int>(chr))));
+}
+
+
+/// \brief Uppercase String
+///
+/// A convenience function to uppercase a string.
+///
+/// \param text String to be upper-cased.
+inline void uppercase(std::string& text) {
+ std::transform(text.begin(), text.end(), text.begin(),
+ isc::strutil::toUpper);
+}
+
+/// \brief Lowercase Character
+///
+/// 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 dereferencing a
+/// string::iterator returns a char.
+///
+/// \param chr Character to be lower-cased.
+///
+/// \return Lowercase version of the argument
+inline char toLower(char chr) {
+ return (static_cast<char>(std::tolower(static_cast<int>(chr))));
+}
+
+/// \brief Lowercase String
+///
+/// A convenience function to lowercase a string
+///
+/// \param text String to be lower-cased.
+inline void lowercase(std::string& text) {
+ std::transform(text.begin(), text.end(), text.begin(),
+ isc::strutil::toLower);
+}
+
+
+/// \brief Apply Formatting
+///
+/// Given a printf-style format string containing only "%s" place holders
+/// (others are ignored) and a vector of strings, this produces a single string
+/// with the placeholders replaced.
+///
+/// \param format Format string
+/// \param args Vector of argument strings
+///
+/// \return Resultant string
+std::string format(const std::string& format,
+ const std::vector<std::string>& args);
+
+
+} // namespace strutil
+} // namespace isc
+
+#endif // __STRUTIL_H
diff --git a/src/lib/log/tests/Makefile.am b/src/lib/log/tests/Makefile.am
new file mode 100644
index 0000000..1845706
--- /dev/null
+++ b/src/lib/log/tests/Makefile.am
@@ -0,0 +1,45 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = root_logger_name_unittest.cc
+run_unittests_SOURCES += filename_unittest.cc
+run_unittests_SOURCES += logger_unittest.cc
+run_unittests_SOURCES += message_dictionary_unittest.cc
+run_unittests_SOURCES += message_reader_unittest.cc
+run_unittests_SOURCES += message_initializer_unittest.cc
+run_unittests_SOURCES += message_initializer_unittest_2.cc
+run_unittests_SOURCES += strutil_unittest.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/log/liblog.la
+endif
+
+TESTS += logger_support_test
+logger_support_test_SOURCES = logger_support_test.cc
+logger_support_test_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+logger_support_test_LDFLAGS = $(AM_LDFLAGS)
+logger_support_test_LDADD = $(top_builddir)/src/lib/log/liblog.la
+
+noinst_PROGRAMS = $(TESTS)
+
+# Additional test using the shell
+PYTESTS = run_time_init_test.sh
+check-local:
+ $(SHELL) $(abs_builddir)/run_time_init_test.sh
diff --git a/src/lib/log/tests/filename_unittest.cc b/src/lib/log/tests/filename_unittest.cc
new file mode 100644
index 0000000..f3032eb
--- /dev/null
+++ b/src/lib/log/tests/filename_unittest.cc
@@ -0,0 +1,179 @@
+// 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 <gtest/gtest.h>
+
+#include <log/filename.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+class FilenameTest : public ::testing::Test {
+protected:
+ FilenameTest()
+ {
+ }
+};
+
+
+// Check that the name can be changed
+
+TEST_F(FilenameTest, SetName) {
+ Filename fname("/a/b/c.d");
+ EXPECT_EQ("/a/b/c.d", fname.fullName());
+
+ fname.setName("test.txt");
+ EXPECT_EQ("test.txt", fname.fullName());
+}
+
+
+// Check that the components are split correctly. This is a check of the
+// private member split() method.
+
+TEST_F(FilenameTest, Components) {
+
+ // Complete name
+ Filename fname("/alpha/beta/gamma.delta");
+ EXPECT_EQ("/alpha/beta/", fname.directory());
+ EXPECT_EQ("gamma", fname.name());
+ EXPECT_EQ(".delta", fname.extension());
+
+ // Directory only
+ fname.setName("/gamma/delta/");
+ EXPECT_EQ("/gamma/delta/", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ("", fname.extension());
+
+ // Filename only
+ fname.setName("epsilon");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("epsilon", fname.name());
+ EXPECT_EQ("", fname.extension());
+
+ // Extension only
+ fname.setName(".zeta");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ(".zeta", fname.extension());
+
+ // Missing directory
+ fname.setName("eta.theta");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("eta", fname.name());
+ EXPECT_EQ(".theta", fname.extension());
+
+ // Missing filename
+ fname.setName("/iota/.kappa");
+ EXPECT_EQ("/iota/", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ(".kappa", fname.extension());
+
+ // Missing extension
+ fname.setName("lambda/mu/nu");
+ EXPECT_EQ("lambda/mu/", fname.directory());
+ EXPECT_EQ("nu", fname.name());
+ EXPECT_EQ("", fname.extension());
+
+ // Check that the decomposition can occur in the presence of leading and
+ // trailing spaces
+ fname.setName(" lambda/mu/nu\t ");
+ EXPECT_EQ("lambda/mu/", fname.directory());
+ EXPECT_EQ("nu", fname.name());
+ EXPECT_EQ("", fname.extension());
+
+ // Empty string
+ fname.setName("");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ("", fname.extension());
+
+ // ... and just spaces
+ fname.setName(" ");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ("", fname.extension());
+
+ // Check corner cases - where separators are present, but strings are
+ // absent.
+ fname.setName("/");
+ EXPECT_EQ("/", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ("", fname.extension());
+
+ fname.setName(".");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ(".", fname.extension());
+
+ fname.setName("/.");
+ EXPECT_EQ("/", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ(".", fname.extension());
+
+ // Note that the space is a valid filename here; only leading and trailing
+ // spaces should be trimmed.
+ fname.setName("/ .");
+ EXPECT_EQ("/", fname.directory());
+ EXPECT_EQ(" ", fname.name());
+ EXPECT_EQ(".", fname.extension());
+
+ fname.setName(" / . ");
+ EXPECT_EQ("/", fname.directory());
+ EXPECT_EQ(" ", fname.name());
+ EXPECT_EQ(".", fname.extension());
+}
+
+// Check that the expansion with a default works.
+
+TEST_F(FilenameTest, ExpandWithDefault) {
+ Filename fname("a.b");
+
+ // These tests also check that the trimming of the default component is
+ // done properly.
+ EXPECT_EQ("/c/d/a.b", fname.expandWithDefault(" /c/d/ "));
+ EXPECT_EQ("/c/d/a.b", fname.expandWithDefault("/c/d/e.f"));
+ EXPECT_EQ("a.b", fname.expandWithDefault("e.f"));
+
+ fname.setName("/a/b/c");
+ EXPECT_EQ("/a/b/c.d", fname.expandWithDefault(".d"));
+ EXPECT_EQ("/a/b/c.d", fname.expandWithDefault("x.d"));
+ EXPECT_EQ("/a/b/c.d", fname.expandWithDefault("/s/t/u.d"));
+ EXPECT_EQ("/a/b/c", fname.expandWithDefault("/s/t/u"));
+
+ fname.setName(".h");
+ EXPECT_EQ("/a/b/c.h", fname.expandWithDefault("/a/b/c.msg"));
+}
+
+// Check that we can use this as a default in expanding a filename
+
+TEST_F(FilenameTest, UseAsDefault) {
+
+ Filename fname("a.b");
+
+ // These tests also check that the trimming of the default component is
+ // done properly.
+ EXPECT_EQ("/c/d/a.b", fname.useAsDefault(" /c/d/ "));
+ EXPECT_EQ("/c/d/e.f", fname.useAsDefault("/c/d/e.f"));
+ EXPECT_EQ("e.f", fname.useAsDefault("e.f"));
+
+ fname.setName("/a/b/c");
+ EXPECT_EQ("/a/b/c.d", fname.useAsDefault(".d"));
+ EXPECT_EQ("/a/b/x.d", fname.useAsDefault("x.d"));
+ EXPECT_EQ("/s/t/u.d", fname.useAsDefault("/s/t/u.d"));
+ EXPECT_EQ("/s/t/u", fname.useAsDefault("/s/t/u"));
+ EXPECT_EQ("/a/b/c", fname.useAsDefault(""));
+}
diff --git a/src/lib/log/tests/logger_impl_log4cxx_unittest.cc b/src/lib/log/tests/logger_impl_log4cxx_unittest.cc
new file mode 100644
index 0000000..cab2678
--- /dev/null
+++ b/src/lib/log/tests/logger_impl_log4cxx_unittest.cc
@@ -0,0 +1,91 @@
+// 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 <iostream>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <log/root_logger_name.h>
+#include <log/logger.h>
+#include <log/logger_impl.h>
+#include <log/messagedef.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+/// \brief Log4cxx Implementation Tests
+///
+/// Some tests of methods that are not directly tested by the logger unit tests
+/// (when the logger is configured to use log4cxx)
+
+namespace isc {
+namespace log {
+
+/// \brief Test Logger
+///
+/// This logger is a subclass of the logger implementation class under test, but
+/// makes protected methods public (for testing)
+
+class TestLoggerImpl : public LoggerImpl {
+public:
+ /// \brief constructor
+ TestLoggerImpl(const string& name) : LoggerImpl(name, true)
+ {}
+
+
+ /// \brief Conversion Between log4cxx Number and BIND-10 Severity
+ Severity convertLevel(int value) {
+ return (LoggerImpl::convertLevel(value));
+ }
+};
+
+} // namespace log
+} // namespace isc
+
+
+class LoggerImplTest : public ::testing::Test {
+protected:
+ LoggerImplTest()
+ {
+ }
+};
+
+// Test the number to severity conversion function
+
+TEST_F(LoggerImplTest, ConvertLevel) {
+
+ // Create a logger
+ RootLoggerName::setName("test3");
+ TestLoggerImpl logger("alpha");
+
+ // Basic 1:1
+ EXPECT_EQ(isc::log::DEBUG, logger.convertLevel(log4cxx::Level::DEBUG_INT));
+ EXPECT_EQ(isc::log::INFO, logger.convertLevel(log4cxx::Level::INFO_INT));
+ EXPECT_EQ(isc::log::WARN, logger.convertLevel(log4cxx::Level::WARN_INT));
+ EXPECT_EQ(isc::log::WARN, logger.convertLevel(log4cxx::Level::WARN_INT));
+ EXPECT_EQ(isc::log::ERROR, logger.convertLevel(log4cxx::Level::ERROR_INT));
+ EXPECT_EQ(isc::log::FATAL, logger.convertLevel(log4cxx::Level::FATAL_INT));
+ EXPECT_EQ(isc::log::FATAL, logger.convertLevel(log4cxx::Level::FATAL_INT));
+ EXPECT_EQ(isc::log::NONE, logger.convertLevel(log4cxx::Level::OFF_INT));
+
+ // Now some debug levels
+ EXPECT_EQ(isc::log::DEBUG,
+ logger.convertLevel(log4cxx::Level::DEBUG_INT - 1));
+ EXPECT_EQ(isc::log::DEBUG,
+ logger.convertLevel(log4cxx::Level::DEBUG_INT - MAX_DEBUG_LEVEL));
+ EXPECT_EQ(isc::log::DEBUG,
+ logger.convertLevel(log4cxx::Level::DEBUG_INT - 2 * MAX_DEBUG_LEVEL));
+}
diff --git a/src/lib/log/tests/logger_support_test.cc b/src/lib/log/tests/logger_support_test.cc
new file mode 100644
index 0000000..4d8863e
--- /dev/null
+++ b/src/lib/log/tests/logger_support_test.cc
@@ -0,0 +1,104 @@
+// 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 Example Program
+///
+/// Simple example program showing how to use the logger.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <iostream>
+
+#include <log/logger.h>
+#include <log/logger_support.h>
+#include <log/root_logger_name.h>
+
+// Include a set of message definitions.
+#include <log/messagedef.h>
+
+using namespace isc::log;
+
+// Declare logger to use an example.
+Logger logger_ex("example");
+
+// The program is invoked:
+//
+// logger_support_test [-s severity] [-d level ] [local_file]
+//
+// "severity" is one of "debug", "info", "warn", "error", "fatal"
+// "level" is the debug level, a number between 0 and 99
+// "local_file" is the name of a local file.
+//
+// The program sets the attributes on the root logger and logs a set of
+// messages. Looking at the output determines whether the program worked.
+
+int main(int argc, char** argv) {
+
+ isc::log::Severity severity = isc::log::INFO; // Default logger severity
+ int dbglevel = -1; // Logger debug level
+ const char* localfile = NULL; // Local message file
+ int option; // For getopt() processing
+ Logger logger_dlm("dlm", true); // Another example logger
+
+ // Parse options
+ while ((option = getopt(argc, argv, "s:d:")) != -1) {
+ switch (option) {
+ case 's':
+ if (strcmp(optarg, "debug") == 0) {
+ severity = isc::log::DEBUG;
+ } else if (strcmp(optarg, "info") == 0) {
+ severity = isc::log::INFO;
+ } else if (strcmp(optarg, "warn") == 0) {
+ severity = isc::log::WARN;
+ } else if (strcmp(optarg, "error") == 0) {
+ severity = isc::log::ERROR;
+ } else if (strcmp(optarg, "fatal") == 0) {
+ severity = isc::log::FATAL;
+ } else {
+ std::cout << "Unrecognised severity option: " <<
+ optarg << "\n";
+ exit(1);
+ }
+ break;
+
+ case 'd':
+ dbglevel = atoi(optarg);
+ break;
+
+ default:
+ std::cout << "Unrecognised option: " <<
+ static_cast<char>(option) << "\n";
+ }
+ }
+
+ if (optind < argc) {
+ localfile = argv[optind];
+ }
+
+ // Update the logging parameters
+ initLogger("alpha", severity, dbglevel, localfile);
+
+ // Log a few messages
+ logger_ex.fatal(MSG_MSGWRTERR, "test1", "42");
+ logger_ex.error(MSG_UNRECDIR, "false");
+ logger_dlm.warn(MSG_MSGRDERR, "a.txt", "dummy test");
+ logger_dlm.info(MSG_OPNMSGIN, "example.msg", "dummy test");
+ logger_ex.debug(0, MSG_UNRECDIR, "[abc]");
+ logger_ex.debug(24, MSG_UNRECDIR, "[24]");
+ logger_ex.debug(25, MSG_UNRECDIR, "[25]");
+ logger_ex.debug(26, MSG_UNRECDIR, "[26]");
+ return (0);
+}
diff --git a/src/lib/log/tests/logger_unittest.cc b/src/lib/log/tests/logger_unittest.cc
new file mode 100644
index 0000000..4eff622
--- /dev/null
+++ b/src/lib/log/tests/logger_unittest.cc
@@ -0,0 +1,345 @@
+// 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 <iostream>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <log/root_logger_name.h>
+#include <log/logger.h>
+#include <log/messagedef.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+namespace isc {
+namespace log {
+
+/// \brief Test Logger
+///
+/// This logger is a subclass of the logger class under test, but makes
+/// protected methods public (for testing)
+
+class TestLogger : public Logger {
+public:
+ /// \brief constructor
+ TestLogger(const string& name) : Logger(name, true)
+ {}
+
+ static void reset() {
+ Logger::reset();
+ }
+};
+
+} // namespace log
+} // namespace isc
+
+
+class LoggerTest : public ::testing::Test {
+protected:
+ LoggerTest()
+ {
+ }
+
+ ~LoggerTest() {
+ TestLogger::reset();
+ }
+};
+
+
+// Checks that the logger is named correctly.
+
+TEST_F(LoggerTest, Name) {
+
+ // Create a logger
+ setRootLoggerName("test1");
+ Logger logger("alpha");
+
+ // ... and check the name
+ EXPECT_EQ(string("test1.alpha"), logger.getName());
+}
+
+// This test attempts to get two instances of a logger with the same name
+// and checks that they are in fact the same logger.
+
+TEST_F(LoggerTest, GetLogger) {
+
+ // Set the root logger name (not strictly needed, but this will be the
+ // case in the program(.
+ setRootLoggerName("test2");
+
+ const string name1 = "alpha";
+ const string name2 = "beta";
+
+ // Instantiate two loggers that should be the same
+ TestLogger logger1(name1);
+ TestLogger logger2(name1);
+ // And check they equal
+ EXPECT_TRUE(logger1 == logger2);
+
+ // Instantiate another logger with another name and check that it
+ // is different to the previously instantiated ones.
+ TestLogger logger3(name2);
+ EXPECT_FALSE(logger1 == logger3);
+}
+
+// Check that the logger levels are get set properly.
+
+TEST_F(LoggerTest, Severity) {
+
+ // Create a logger
+ setRootLoggerName("test3");
+ TestLogger logger("alpha");
+
+ // Now check the levels
+ logger.setSeverity(isc::log::NONE);
+ EXPECT_EQ(isc::log::NONE, logger.getSeverity());
+
+ logger.setSeverity(isc::log::FATAL);
+ EXPECT_EQ(isc::log::FATAL, logger.getSeverity());
+
+ logger.setSeverity(isc::log::ERROR);
+ EXPECT_EQ(isc::log::ERROR, logger.getSeverity());
+
+ logger.setSeverity(isc::log::WARN);
+ EXPECT_EQ(isc::log::WARN, logger.getSeverity());
+
+ logger.setSeverity(isc::log::INFO);
+ EXPECT_EQ(isc::log::INFO, logger.getSeverity());
+
+ logger.setSeverity(isc::log::DEBUG);
+ EXPECT_EQ(isc::log::DEBUG, logger.getSeverity());
+
+ logger.setSeverity(isc::log::DEFAULT);
+ EXPECT_EQ(isc::log::DEFAULT, logger.getSeverity());
+}
+
+// Check that the debug level is set correctly.
+
+TEST_F(LoggerTest, DebugLevels) {
+
+ // Create a logger
+ setRootLoggerName("test4");
+ TestLogger logger("alpha");
+
+ // Debug level should be 0 if not at debug severity
+ logger.setSeverity(isc::log::NONE, 20);
+ EXPECT_EQ(0, logger.getDebugLevel());
+
+ logger.setSeverity(isc::log::INFO, 42);
+ EXPECT_EQ(0, logger.getDebugLevel());
+
+ // Should be the value set if the severity is set to DEBUG though.
+ logger.setSeverity(isc::log::DEBUG, 32);
+ EXPECT_EQ(32, logger.getDebugLevel());
+
+ logger.setSeverity(isc::log::DEBUG, 97);
+ EXPECT_EQ(97, logger.getDebugLevel());
+
+ // Try the limits
+ logger.setSeverity(isc::log::DEBUG, -1);
+ EXPECT_EQ(0, logger.getDebugLevel());
+
+ logger.setSeverity(isc::log::DEBUG, 0);
+ EXPECT_EQ(0, logger.getDebugLevel());
+
+ logger.setSeverity(isc::log::DEBUG, 1);
+ EXPECT_EQ(1, logger.getDebugLevel());
+
+ logger.setSeverity(isc::log::DEBUG, 98);
+ EXPECT_EQ(98, logger.getDebugLevel());
+
+ logger.setSeverity(isc::log::DEBUG, 99);
+ EXPECT_EQ(99, logger.getDebugLevel());
+
+ logger.setSeverity(isc::log::DEBUG, 100);
+ EXPECT_EQ(99, logger.getDebugLevel());
+}
+
+// Check that changing the parent and child severity does not affect the
+// other.
+
+TEST_F(LoggerTest, SeverityInheritance) {
+
+ // Create to loggers. We cheat here as we know that the underlying
+ // implementation (in this case log4cxx) will set a parent-child
+ // relationship if the loggers are named <parent> and <parent>.<child>.
+
+ setRootLoggerName("test5");
+ TestLogger parent("alpha");
+ TestLogger child("alpha.beta");
+
+ // By default, newly created loggers should have a level of DEFAULT
+ // (i.e. default to parent)
+ EXPECT_EQ(isc::log::DEFAULT, parent.getSeverity());
+ EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
+
+ // Set the severity of the child to something other than the default -
+ // check it changes and that of the parent does not.
+ child.setSeverity(isc::log::INFO);
+ EXPECT_EQ(isc::log::DEFAULT, parent.getSeverity());
+ EXPECT_EQ(isc::log::INFO, child.getSeverity());
+
+ // Reset the child severity and set that of the parent
+ child.setSeverity(isc::log::DEFAULT);
+ EXPECT_EQ(isc::log::DEFAULT, parent.getSeverity());
+ EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
+ parent.setSeverity(isc::log::WARN);
+ EXPECT_EQ(isc::log::WARN, parent.getSeverity());
+ EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
+}
+
+// Check that severity is inherited.
+
+TEST_F(LoggerTest, EffectiveSeverityInheritance) {
+
+ // Create to loggers. We cheat here as we know that the underlying
+ // implementation (in this case log4cxx) will set a parent-child
+ // relationship if the loggers are named <parent> and <parent>.<child>.
+
+ setRootLoggerName("test6");
+ Logger parent("test6");
+ Logger child("test6.beta");
+
+ // By default, newly created loggers should have a level of DEFAULT
+ // (i.e. default to parent) and the root should have a default severity
+ // of INFO. However, the latter is only enforced when created by the
+ // RootLogger class, so explicitly set it for the parent for now.
+ parent.setSeverity(isc::log::INFO);
+ EXPECT_EQ(isc::log::INFO, parent.getEffectiveSeverity());
+
+ EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
+ EXPECT_EQ(isc::log::INFO, child.getEffectiveSeverity());
+
+ // Set the severity of the child to something other than the default -
+ // check it changes and that of the parent does not.
+ child.setSeverity(isc::log::FATAL);
+ EXPECT_EQ(isc::log::INFO, parent.getEffectiveSeverity());
+ EXPECT_EQ(isc::log::FATAL, child.getEffectiveSeverity());
+
+ // Reset the child severity and check again.
+ child.setSeverity(isc::log::DEFAULT);
+ EXPECT_EQ(isc::log::INFO, parent.getEffectiveSeverity());
+ EXPECT_EQ(isc::log::INFO, child.getEffectiveSeverity());
+
+ // Change the parwnt's severity and check it is reflects in the child.
+ parent.setSeverity(isc::log::WARN);
+ EXPECT_EQ(isc::log::WARN, parent.getEffectiveSeverity());
+ EXPECT_EQ(isc::log::WARN, child.getEffectiveSeverity());
+}
+
+// Test the isXxxxEnabled methods.
+
+TEST_F(LoggerTest, IsXxxEnabled) {
+
+ setRootLoggerName("test7");
+ Logger logger("test7");
+
+ logger.setSeverity(isc::log::INFO);
+ EXPECT_FALSE(logger.isDebugEnabled());
+ EXPECT_TRUE(logger.isInfoEnabled());
+ EXPECT_TRUE(logger.isWarnEnabled());
+ EXPECT_TRUE(logger.isErrorEnabled());
+ EXPECT_TRUE(logger.isFatalEnabled());
+
+ logger.setSeverity(isc::log::WARN);
+ EXPECT_FALSE(logger.isDebugEnabled());
+ EXPECT_FALSE(logger.isInfoEnabled());
+ EXPECT_TRUE(logger.isWarnEnabled());
+ EXPECT_TRUE(logger.isErrorEnabled());
+ EXPECT_TRUE(logger.isFatalEnabled());
+
+ logger.setSeverity(isc::log::ERROR);
+ EXPECT_FALSE(logger.isDebugEnabled());
+ EXPECT_FALSE(logger.isInfoEnabled());
+ EXPECT_FALSE(logger.isWarnEnabled());
+ EXPECT_TRUE(logger.isErrorEnabled());
+ EXPECT_TRUE(logger.isFatalEnabled());
+
+ logger.setSeverity(isc::log::FATAL);
+ EXPECT_FALSE(logger.isDebugEnabled());
+ EXPECT_FALSE(logger.isInfoEnabled());
+ EXPECT_FALSE(logger.isWarnEnabled());
+ EXPECT_FALSE(logger.isErrorEnabled());
+ EXPECT_TRUE(logger.isFatalEnabled());
+
+ // Check various debug levels
+
+ logger.setSeverity(isc::log::DEBUG);
+ EXPECT_TRUE(logger.isDebugEnabled());
+ EXPECT_TRUE(logger.isInfoEnabled());
+ EXPECT_TRUE(logger.isWarnEnabled());
+ EXPECT_TRUE(logger.isErrorEnabled());
+ EXPECT_TRUE(logger.isFatalEnabled());
+
+ logger.setSeverity(isc::log::DEBUG, 45);
+ EXPECT_TRUE(logger.isDebugEnabled());
+ EXPECT_TRUE(logger.isInfoEnabled());
+ EXPECT_TRUE(logger.isWarnEnabled());
+ EXPECT_TRUE(logger.isErrorEnabled());
+ EXPECT_TRUE(logger.isFatalEnabled());
+
+ // Create a child logger with no severity set, and check that it reflects
+ // the severity of the parent logger.
+
+ Logger child("test7.child");
+ logger.setSeverity(isc::log::FATAL);
+ EXPECT_FALSE(child.isDebugEnabled());
+ EXPECT_FALSE(child.isInfoEnabled());
+ EXPECT_FALSE(child.isWarnEnabled());
+ EXPECT_FALSE(child.isErrorEnabled());
+ EXPECT_TRUE(child.isFatalEnabled());
+
+ logger.setSeverity(isc::log::INFO);
+ EXPECT_FALSE(child.isDebugEnabled());
+ EXPECT_TRUE(child.isInfoEnabled());
+ EXPECT_TRUE(child.isWarnEnabled());
+ EXPECT_TRUE(child.isErrorEnabled());
+ EXPECT_TRUE(child.isFatalEnabled());
+}
+
+// Within the Debug level there are 100 debug levels. Test that we know
+// when to issue a debug message.
+
+TEST_F(LoggerTest, IsDebugEnabledLevel) {
+
+ setRootLoggerName("test8");
+ Logger logger("test8");
+
+ int MID_LEVEL = (MIN_DEBUG_LEVEL + MAX_DEBUG_LEVEL) / 2;
+
+ logger.setSeverity(isc::log::DEBUG);
+ EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
+ EXPECT_FALSE(logger.isDebugEnabled(MID_LEVEL));
+ EXPECT_FALSE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
+
+ logger.setSeverity(isc::log::DEBUG, MIN_DEBUG_LEVEL);
+ EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
+ EXPECT_FALSE(logger.isDebugEnabled(MID_LEVEL));
+ EXPECT_FALSE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
+
+ logger.setSeverity(isc::log::DEBUG, MID_LEVEL);
+ EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
+ EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL - 1));
+ EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL));
+ EXPECT_FALSE(logger.isDebugEnabled(MID_LEVEL + 1));
+ EXPECT_FALSE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
+
+ logger.setSeverity(isc::log::DEBUG, MAX_DEBUG_LEVEL);
+ EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
+ EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL));
+ EXPECT_TRUE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
+}
diff --git a/src/lib/log/tests/message_dictionary_unittest.cc b/src/lib/log/tests/message_dictionary_unittest.cc
new file mode 100644
index 0000000..a92585c
--- /dev/null
+++ b/src/lib/log/tests/message_dictionary_unittest.cc
@@ -0,0 +1,197 @@
+// 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 <cstddef>
+#include <string>
+#include <gtest/gtest.h>
+#include <log/message_dictionary.h>
+#include <log/message_initializer.h>
+#include <log/message_types.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+// set up another message initializer. This will add a symbol found in the
+// logging library and a symbol not found in the logging library. When the
+// global dictionary is loaded, the former should be marked as a duplicate
+// and the latter should be present.
+
+static const char* values[] = {
+ "DUPLNS", "duplicate $NAMESPACE directive found",
+ "NEWSYM", "new symbol added",
+ NULL
+};
+
+MessageInitializer init(values);
+
+
+
+
+class MessageDictionaryTest : public ::testing::Test {
+protected:
+ MessageDictionaryTest() :
+ alpha_id("ALPHA"), alpha_text("This is alpha"),
+ beta_id("BETA"), beta_text("This is beta"),
+ gamma_id("GAMMA"), gamma_text("This is gamma")
+ {
+ }
+
+ MessageID alpha_id;
+ std::string alpha_text;
+ MessageID beta_id;
+ std::string beta_text;
+ MessageID gamma_id;
+ std::string gamma_text;
+
+};
+
+// Check that adding messages works
+
+TEST_F(MessageDictionaryTest, Add) {
+ MessageDictionary dictionary;
+ EXPECT_EQ(0, dictionary.size());
+
+ // Add a few messages and check that we can look them up and that there is
+ // nothing in the overflow vector.
+ EXPECT_TRUE(dictionary.add(alpha_id, alpha_text));
+ EXPECT_TRUE(dictionary.add(beta_id, beta_text));
+ EXPECT_EQ(2, dictionary.size());
+
+ EXPECT_EQ(alpha_text, dictionary.getText(alpha_id));
+ EXPECT_EQ(beta_text, dictionary.getText(beta_id));
+ EXPECT_EQ(string(""), dictionary.getText(gamma_id));
+
+ // Try adding a duplicate with different text. It should not replace the
+ // current text and the ID should be in the overflow section.
+ EXPECT_FALSE(dictionary.add(alpha_id, gamma_text));
+ EXPECT_EQ(2, dictionary.size());
+}
+
+// Check that replacing messages works.
+
+TEST_F(MessageDictionaryTest, Replace) {
+ MessageDictionary dictionary;
+ EXPECT_EQ(0, dictionary.size());
+
+ // Try to replace a non-existent message
+ EXPECT_FALSE(dictionary.replace(alpha_id, alpha_text));
+ EXPECT_EQ(0, dictionary.size());
+
+ // Add a couple of messages.
+ EXPECT_TRUE(dictionary.add(alpha_id, alpha_text));
+ EXPECT_TRUE(dictionary.add(beta_id, beta_text));
+ EXPECT_EQ(2, dictionary.size());
+
+ // Replace an existing message
+ EXPECT_TRUE(dictionary.replace(alpha_id, gamma_text));
+ EXPECT_EQ(2, dictionary.size());
+ EXPECT_EQ(gamma_text, dictionary.getText(alpha_id));
+
+ // ... and replace non-existent message (but now the dictionary has some
+ // items in it).
+ EXPECT_FALSE(dictionary.replace(gamma_id, alpha_text));
+ EXPECT_EQ(2, dictionary.size());
+ EXPECT_EQ(string(""), dictionary.getText(gamma_id));
+}
+
+// Load test
+
+TEST_F(MessageDictionaryTest, LoadTest) {
+ static const char* data1[] = {
+ "ALPHA", "This is alpha",
+ "BETA", "This is beta",
+ "GAMMA", "This is gamma",
+ NULL
+ };
+
+ static const char* data2[] = {
+ "DELTA", "This is delta",
+ "EPSILON", "This is epsilon",
+ "ETA", NULL
+ };
+
+ MessageDictionary dictionary1;
+ EXPECT_EQ(0, dictionary1.size());
+
+ // Load a dictionary1.
+ vector<string> duplicates = dictionary1.load(data1);
+ EXPECT_EQ(3, dictionary1.size());
+ EXPECT_EQ(string(data1[1]), dictionary1.getText(data1[0]));
+ EXPECT_EQ(string(data1[3]), dictionary1.getText(data1[2]));
+ EXPECT_EQ(string(data1[5]), dictionary1.getText(data1[4]));
+ EXPECT_EQ(0, duplicates.size());
+
+ // Attempt an overwrite
+ duplicates = dictionary1.load(data1);
+ EXPECT_EQ(3, dictionary1.size());
+ EXPECT_EQ(3, duplicates.size());
+
+ // Try a new dictionary but with an incorrect number of elements
+ MessageDictionary dictionary2;
+ EXPECT_EQ(0, dictionary2.size());
+
+ duplicates = dictionary2.load(data2);
+ EXPECT_EQ(2, dictionary2.size());
+ EXPECT_EQ(string(data2[1]), dictionary2.getText(data2[0]));
+ EXPECT_EQ(string(data2[3]), dictionary2.getText(data2[2]));
+ EXPECT_EQ(string(""), dictionary2.getText(data2[4]));
+ EXPECT_EQ(0, duplicates.size());
+}
+
+// Check for some non-existent items
+
+TEST_F(MessageDictionaryTest, Lookups) {
+ static const char* data[] = {
+ "ALPHA", "This is alpha",
+ "BETA", "This is beta",
+ "GAMMA", "This is gamma",
+ NULL
+ };
+
+ MessageDictionary dictionary;
+ vector<string> duplicates = dictionary.load(data);
+ EXPECT_EQ(3, dictionary.size());
+ EXPECT_EQ(0, duplicates.size());
+
+ // Valid lookups
+ EXPECT_EQ(string("This is alpha"), dictionary.getText("ALPHA"));
+ EXPECT_EQ(string("This is beta"), dictionary.getText("BETA"));
+ EXPECT_EQ(string("This is gamma"), dictionary.getText("GAMMA"));
+
+ // ... and invalid ones
+ EXPECT_EQ(string(""), dictionary.getText("XYZZY"));
+ EXPECT_EQ(string(""), dictionary.getText(""));
+ EXPECT_EQ(string(""), dictionary.getText("\n\n\n"));
+}
+
+// Check that the global dictionary is a singleton.
+
+TEST_F(MessageDictionaryTest, GlobalTest) {
+ MessageDictionary& global = MessageDictionary::globalDictionary();
+ MessageDictionary& global2 = MessageDictionary::globalDictionary();
+ EXPECT_TRUE(&global2 == &global);
+}
+
+// Check that the global dictionary has detected the duplicate and the
+// new symbol.
+
+TEST_F(MessageDictionaryTest, GlobalLoadTest) {
+ vector<string>& duplicates = MessageInitializer::getDuplicates();
+ ASSERT_EQ(1, duplicates.size());
+ EXPECT_EQ(string("DUPLNS"), duplicates[0]);
+
+ string text = MessageDictionary::globalDictionary().getText("NEWSYM");
+ EXPECT_EQ(string("new symbol added"), text);
+}
diff --git a/src/lib/log/tests/message_initializer_unittest.cc b/src/lib/log/tests/message_initializer_unittest.cc
new file mode 100644
index 0000000..0cd1879
--- /dev/null
+++ b/src/lib/log/tests/message_initializer_unittest.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 <cstddef>
+#include <string>
+#include <gtest/gtest.h>
+#include <log/message_dictionary.h>
+#include <log/message_initializer.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+// Declare a set of messages to go into the global dictionary.
+
+namespace {
+const char* values1[] = {
+ "GLOBAL1", "global message one",
+ "GLOBAL2", "global message two",
+ NULL
+};
+
+const char* values2[] = {
+ "GLOBAL3", "global message three",
+ "GLOBAL4", "global message four",
+ NULL
+};
+
+}
+
+// Statically initialize the global dictionary with those messages. Three sets
+// are used to check that the declaration of separate initializer objects really// does combine the messages. (The third set is declared in the separately-
+// compiled file message_identifier_initializer_unittest_2.cc.)
+
+MessageInitializer init_message_initializer_unittest_1(values1);
+MessageInitializer init_message_initializer_unittest_2(values2);
+
+
+class MessageInitializerTest : public ::testing::Test {
+protected:
+ MessageInitializerTest()
+ {
+ }
+};
+
+
+// Check that the global dictionary is initialized with the specified
+// messages.
+
+TEST_F(MessageInitializerTest, MessageTest) {
+ MessageDictionary& global = MessageDictionary::globalDictionary();
+
+ EXPECT_EQ(string("global message one"), global.getText("GLOBAL1"));
+ EXPECT_EQ(string("global message two"), global.getText("GLOBAL2"));
+ EXPECT_EQ(string("global message three"), global.getText("GLOBAL3"));
+ EXPECT_EQ(string("global message four"), global.getText("GLOBAL4"));
+ EXPECT_EQ(string("global message five"), global.getText("GLOBAL5"));
+ EXPECT_EQ(string("global message six"), global.getText("GLOBAL6"));
+}
diff --git a/src/lib/log/tests/message_initializer_unittest_2.cc b/src/lib/log/tests/message_initializer_unittest_2.cc
new file mode 100644
index 0000000..94abb08
--- /dev/null
+++ b/src/lib/log/tests/message_initializer_unittest_2.cc
@@ -0,0 +1,39 @@
+// 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.
+
+// The sole purpose of this file is to provide a set of message definitions
+// in a separate compilation unit from the one in which their presence is
+// checked. This tests that merely declaring the MessageInitializer object
+// is enough to include the definitions in the global dictionary.
+
+#include <log/message_initializer.h>
+
+using namespace isc::log;
+
+// Declare a set of messages to go into the global dictionary.
+
+namespace {
+
+const char* values3[] = {
+ "GLOBAL5", "global message five",
+ "GLOBAL6", "global message six",
+ NULL
+};
+
+}
+
+// Statically initialize the global dictionary with those messages.
+// Three sets are used to check that the declaration of separate
+// initializer objects really does combine the messages.
+MessageInitializer init_message_initializer_unittest_3(values3);
diff --git a/src/lib/log/tests/message_reader_unittest.cc b/src/lib/log/tests/message_reader_unittest.cc
new file mode 100644
index 0000000..36288f2
--- /dev/null
+++ b/src/lib/log/tests/message_reader_unittest.cc
@@ -0,0 +1,264 @@
+// 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 <string>
+#include <gtest/gtest.h>
+
+#include <log/messagedef.h>
+#include <log/message_dictionary.h>
+#include <log/message_exception.h>
+#include <log/message_reader.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+class MessageReaderTest : public ::testing::Test {
+protected:
+ MessageReaderTest() : dictionary_(), reader_()
+ {
+ dictionary_ = new MessageDictionary();
+ reader_.setDictionary(dictionary_);
+ }
+
+ ~MessageReaderTest() {
+ delete dictionary_;
+ }
+
+ MessageDictionary* dictionary_; // Dictionary to add messages to
+ MessageReader reader_; // Default reader object
+};
+
+
+// Check the get/set dictionary calls (using a local reader and dictionary).
+
+TEST_F(MessageReaderTest, GetSetDictionary) {
+ MessageReader reader;
+ EXPECT_TRUE(reader.getDictionary() == NULL);
+
+ MessageDictionary dictionary;
+ reader.setDictionary(&dictionary);
+ EXPECT_EQ(&dictionary, reader.getDictionary());
+}
+
+// Check for parsing blank lines and comments. These should not add to the
+// dictionary and each parse should return success.
+
+TEST_F(MessageReaderTest, BlanksAndComments) {
+
+ // Ensure that the dictionary is empty.
+ EXPECT_EQ(0, dictionary_->size());
+
+ // Add a number of blank lines and comments and check that (a) they are
+ // parsed successfully ...
+ EXPECT_NO_THROW(reader_.processLine(""));
+ EXPECT_NO_THROW(reader_.processLine(" "));
+ EXPECT_NO_THROW(reader_.processLine(" \n "));
+ EXPECT_NO_THROW(reader_.processLine("# This is a comment"));
+ EXPECT_NO_THROW(reader_.processLine("\t\t # Another comment"));
+ EXPECT_NO_THROW(reader_.processLine(" + A description line"));
+ EXPECT_NO_THROW(reader_.processLine("#+ A comment"));
+ EXPECT_NO_THROW(reader_.processLine(" +# A description line"));
+
+ // ... and (b) nothing gets added to either the map or the not-added section.
+ EXPECT_EQ(0, dictionary_->size());
+ vector<string> not_added = reader_.getNotAdded();
+ EXPECT_EQ(0, not_added.size());
+}
+
+
+// Local test to check that processLine generates the right exception.
+
+void
+processLineException(MessageReader& reader, const char* what,
+ const MessageID& expected) {
+
+ try {
+ reader.processLine(what);
+ FAIL() << "MessageReader::processLine() should throw an exception " <<
+ " with message ID " << expected << " for '" << what << "'\n";
+ } catch (MessageException& e) {
+ EXPECT_EQ(boost::lexical_cast<string>(expected),
+ boost::lexical_cast<string>(e.id()));
+ } catch (...) {
+ FAIL() << "Unknown exception thrown by MessageReader::processLine()\n";
+ }
+}
+
+// Check that it can parse a prefix
+
+TEST_F(MessageReaderTest, Prefix) {
+
+ // Check that no $PREFIX is present
+ EXPECT_EQ(string(""), reader_.getPrefix());
+
+ // Check that a $PREFIX directive with no argument generates an error.
+ processLineException(reader_, "$PREFIX", MSG_PRFNOARG);
+
+ // Check a $PREFIX with multiple arguments is invalid
+ processLineException(reader_, "$prefix A B", MSG_PRFEXTRARG);
+
+ // Prefixes should be alphanumeric (with underscores) and not start
+ // with a number.
+ processLineException(reader_, "$prefix ab[cd", MSG_PRFINVARG);
+ processLineException(reader_, "$prefix 123", MSG_PRFINVARG);
+ processLineException(reader_, "$prefix 1ABC", MSG_PRFINVARG);
+
+ // A valid prefix should be accepted
+ EXPECT_NO_THROW(reader_.processLine("$PREFIX dlm__"));
+ EXPECT_EQ(string("DLM__"), reader_.getPrefix());
+
+ // And check that the parser fails on invalid prefixes...
+ processLineException(reader_, "$prefix 1ABC", MSG_PRFINVARG);
+
+ // ... and rejects another valid one
+ processLineException(reader_, "$PREFIX ABC", MSG_DUPLPRFX);
+
+ // Check that we can clear the prefix as well
+ reader_.clearPrefix();
+ EXPECT_EQ(string(""), reader_.getPrefix());
+}
+
+// Check that it can parse a namespace
+
+TEST_F(MessageReaderTest, Namespace) {
+
+ // Check that no $NAMESPACE is present
+ EXPECT_EQ(string(""), reader_.getNamespace());
+
+ // Check that a $NAMESPACE directive with no argument generates an error.
+ processLineException(reader_, "$NAMESPACE", MSG_NSNOARG);
+
+ // Check a $NAMESPACE with multiple arguments is invalid
+ processLineException(reader_, "$namespace A B", MSG_NSEXTRARG);
+
+ // Namespaces should be alphanumeric (with underscores and colons)
+ processLineException(reader_, "$namespace ab[cd", MSG_NSINVARG);
+
+ // A valid $NAMESPACE should be accepted
+ EXPECT_NO_THROW(reader_.processLine("$NAMESPACE isc"));
+ EXPECT_EQ(string("isc"), reader_.getNamespace());
+
+ // (Check that we can clear the namespace)
+ reader_.clearNamespace();
+ EXPECT_EQ(string(""), reader_.getNamespace());
+
+ // Check that a valid namespace can include colons
+ EXPECT_NO_THROW(reader_.processLine("$NAMESPACE isc::log"));
+ EXPECT_EQ(string("isc::log"), reader_.getNamespace());
+
+ // Check that the indication of the anonymous namespace will be recognised.
+ reader_.clearNamespace();
+ EXPECT_NO_THROW(reader_.processLine("$NAMESPACE ::"));
+ EXPECT_EQ(string("::"), reader_.getNamespace());
+
+ // ... and that another $NAMESPACE is rejected
+ processLineException(reader_, "$NAMESPACE ABC", MSG_DUPLNS);
+}
+
+// Check that it can parse a line
+
+TEST_F(MessageReaderTest, ValidMessageAddDefault) {
+
+ // Add a couple of valid messages
+ reader_.processLine("GLOBAL1\t\tthis is message global one\n");
+ reader_.processLine("GLOBAL2 this is message global two");
+
+ // ... and check them
+ EXPECT_EQ(string("this is message global one"),
+ dictionary_->getText("GLOBAL1"));
+ EXPECT_EQ(string("this is message global two"),
+ dictionary_->getText("GLOBAL2"));
+ EXPECT_EQ(2, dictionary_->size());
+
+ // ... and ensure no messages were not added
+ vector<string> not_added = reader_.getNotAdded();
+ EXPECT_EQ(0, not_added.size());
+}
+
+TEST_F(MessageReaderTest, ValidMessageAdd) {
+
+ // Add a couple of valid messages
+ reader_.processLine("GLOBAL1\t\tthis is message global one\n",
+ MessageReader::ADD);
+ reader_.processLine("GLOBAL2 this is message global two",
+ MessageReader::ADD);
+
+ // ... and check them
+ EXPECT_EQ(string("this is message global one"),
+ dictionary_->getText("GLOBAL1"));
+ EXPECT_EQ(string("this is message global two"),
+ dictionary_->getText("GLOBAL2"));
+ EXPECT_EQ(2, dictionary_->size());
+
+ // ... and ensure no messages were not added
+ vector<string> not_added = reader_.getNotAdded();
+ EXPECT_EQ(0, not_added.size());
+}
+
+TEST_F(MessageReaderTest, ValidMessageReplace) {
+
+ dictionary_->add("GLOBAL1", "original global1 message");
+ dictionary_->add("GLOBAL2", "original global2 message");
+
+ // Replace a couple of valid messages
+ reader_.processLine("GLOBAL1\t\tthis is message global one\n",
+ MessageReader::REPLACE);
+ reader_.processLine("GLOBAL2 this is message global two",
+ MessageReader::REPLACE);
+
+ // ... and check them
+ EXPECT_EQ(string("this is message global one"),
+ dictionary_->getText("GLOBAL1"));
+ EXPECT_EQ(string("this is message global two"),
+ dictionary_->getText("GLOBAL2"));
+ EXPECT_EQ(2, dictionary_->size());
+
+ // ... and ensure no messages were not added
+ vector<string> not_added = reader_.getNotAdded();
+ EXPECT_EQ(0, not_added.size());
+}
+
+// Do checks on overflows, although this essentially duplicates the checks
+// in MessageDictionary.
+
+TEST_F(MessageReaderTest, Overflows) {
+
+ // Add a couple of valid messages
+ reader_.processLine("GLOBAL1\t\tthis is message global one\n");
+ reader_.processLine("GLOBAL2 this is message global two");
+
+ // Add a duplicate in ADD mode.
+ reader_.processLine("GLOBAL1\t\tthis is a replacement for global one");
+
+ // Replace a non-existent one in REPLACE mode
+ reader_.processLine("LOCAL\t\tthis is a new message",
+ MessageReader::REPLACE);
+
+ // Check what is in the dictionary.
+ EXPECT_EQ(string("this is message global one"),
+ dictionary_->getText("GLOBAL1"));
+ EXPECT_EQ(string("this is message global two"),
+ dictionary_->getText("GLOBAL2"));
+ EXPECT_EQ(2, dictionary_->size());
+
+ // ... and ensure no overflows
+ vector<string> not_added = reader_.getNotAdded();
+ ASSERT_EQ(2, not_added.size());
+
+ sort(not_added.begin(), not_added.end());
+ EXPECT_EQ(string("GLOBAL1"), not_added[0]);
+ EXPECT_EQ(string("LOCAL"), not_added[1]);
+}
diff --git a/src/lib/log/tests/root_logger_name_unittest.cc b/src/lib/log/tests/root_logger_name_unittest.cc
new file mode 100644
index 0000000..8665794
--- /dev/null
+++ b/src/lib/log/tests/root_logger_name_unittest.cc
@@ -0,0 +1,50 @@
+// 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 <gtest/gtest.h>
+
+#include <log/root_logger_name.h>
+
+using namespace isc;
+using namespace isc::log;
+
+class RootLoggerNameTest : public ::testing::Test {
+protected:
+ RootLoggerNameTest()
+ {
+ }
+};
+
+// Check of the (only) functionality of the class.
+
+TEST_F(RootLoggerNameTest, SetGet) {
+ const std::string name1 = "test1";
+ const std::string name2 = "test2";
+
+ // Check that Set/Get works
+ setRootLoggerName(name1);
+ EXPECT_EQ(name1, getRootLoggerName());
+
+ // We could not test that the root logger name is initialised
+ // correctly (as there is one instance of it and we don't know
+ // when this test will be run) so to check that setName() actually
+ // does change the name, run the test again with a different name.
+ //
+ // (There was always the outside chance that the root logger name
+ // was initialised with name1 and that setName() has no effect.)
+ setRootLoggerName(name2);
+ EXPECT_EQ(name2, getRootLoggerName());
+}
diff --git a/src/lib/log/tests/run_time_init_test.sh.in b/src/lib/log/tests/run_time_init_test.sh.in
new file mode 100755
index 0000000..e2bdf6f
--- /dev/null
+++ b/src/lib/log/tests/run_time_init_test.sh.in
@@ -0,0 +1,89 @@
+#!/bin/sh
+# 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.
+
+failcount=0
+localmes=@abs_builddir@/localdef_mes_$$
+tempfile=@abs_builddir@/run_time_init_test_tempfile_$$
+
+passfail() {
+ if [ $1 -eq 0 ]; then
+ echo "pass"
+ else
+ echo "FAIL"
+ fi
+ failcount=`expr $failcount + $1`
+}
+
+# Create the local message file for testing
+
+cat > $localmes << .
+NOTHERE this message is not in the global dictionary
+MSGRDERR replacement read error, parameters: '%s' and '%s'
+UNRECDIR replacement unrecognised directive message, parameter is '%s'
+.
+
+echo -n "1. runInitTest default parameters: "
+cat > $tempfile << .
+FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
+ERROR [alpha.example] UNRECDIR, unrecognised directive 'false'
+WARN [alpha.dlm] MSGRDERR, error reading from message file a.txt: dummy test
+INFO [alpha.dlm] OPNMSGIN, unable to open message file example.msg for input: dummy test
+.
+./logger_support_test | cut -d' ' -f3- | diff $tempfile -
+passfail $?
+
+echo -n "2. Severity filter: "
+cat > $tempfile << .
+FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
+ERROR [alpha.example] UNRECDIR, unrecognised directive 'false'
+.
+./logger_support_test -s error | cut -d' ' -f3- | diff $tempfile -
+passfail $?
+
+echo -n "3. Debug level: "
+cat > $tempfile << .
+FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
+ERROR [alpha.example] UNRECDIR, unrecognised directive 'false'
+WARN [alpha.dlm] MSGRDERR, error reading from message file a.txt: dummy test
+INFO [alpha.dlm] OPNMSGIN, unable to open message file example.msg for input: dummy test
+DEBUG [alpha.example] UNRECDIR, unrecognised directive '[abc]'
+DEBUG [alpha.example] UNRECDIR, unrecognised directive '[24]'
+DEBUG [alpha.example] UNRECDIR, unrecognised directive '[25]'
+.
+./logger_support_test -s debug -d 25 | cut -d' ' -f3- | diff $tempfile -
+passfail $?
+
+echo -n "4. Local message replacement: "
+cat > $tempfile << .
+WARN [alpha.log] IDNOTFND, could not replace message for 'NOTHERE': no such message identification
+FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
+ERROR [alpha.example] UNRECDIR, replacement unrecognised directive message, parameter is 'false'
+WARN [alpha.dlm] MSGRDERR, replacement read error, parameters: 'a.txt' and 'dummy test'
+.
+./logger_support_test -s warn $localmes | cut -d' ' -f3- | diff $tempfile -
+passfail $?
+
+rm -f $localmes
+rm -f $tempfile
+
+if [ $failcount -eq 0 ]; then
+ echo "PASS: run_time_init_test"
+elif [ $failcount -eq 1 ]; then
+ echo "FAIL: run_time_init_test - 1 test failed"
+else
+ echo "FAIL: run_time_init_test - $failcount tests failed"
+fi
+
+exit $failcount
diff --git a/src/lib/log/tests/run_unittests.cc b/src/lib/log/tests/run_unittests.cc
new file mode 100644
index 0000000..bd3c4c9
--- /dev/null
+++ b/src/lib/log/tests/run_unittests.cc
@@ -0,0 +1,21 @@
+// 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/log/tests/strutil_unittest.cc b/src/lib/log/tests/strutil_unittest.cc
new file mode 100644
index 0000000..b3fceef
--- /dev/null
+++ b/src/lib/log/tests/strutil_unittest.cc
@@ -0,0 +1,214 @@
+// 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 <gtest/gtest.h>
+
+#include <log/strutil.h>
+
+using namespace isc;
+using namespace std;
+
+class StringUtilTest : public ::testing::Test {
+protected:
+ StringUtilTest()
+ {
+ }
+};
+
+
+// Check for slash replacement
+
+TEST_F(StringUtilTest, Slash) {
+
+ string instring = "";
+ isc::strutil::normalizeSlash(instring);
+ EXPECT_EQ("", instring);
+
+ instring = "C:\\A\\B\\C.D";
+ isc::strutil::normalizeSlash(instring);
+ EXPECT_EQ("C:/A/B/C.D", instring);
+
+ instring = "// \\ //";
+ isc::strutil::normalizeSlash(instring);
+ EXPECT_EQ("// / //", instring);
+}
+
+// Check that leading and trailing space trimming works
+
+TEST_F(StringUtilTest, Trim) {
+
+ // Empty and full string.
+ EXPECT_EQ("", isc::strutil::trim(""));
+ EXPECT_EQ("abcxyz", isc::strutil::trim("abcxyz"));
+
+ // Trim right-most blanks
+ EXPECT_EQ("ABC", isc::strutil::trim("ABC "));
+ EXPECT_EQ("ABC", isc::strutil::trim("ABC\t\t \n\t"));
+
+ // Left-most blank trimming
+ EXPECT_EQ("XYZ", isc::strutil::trim(" XYZ"));
+ EXPECT_EQ("XYZ", isc::strutil::trim("\t\t \tXYZ"));
+
+ // Right and left, with embedded spaces
+ EXPECT_EQ("MN \t OP", isc::strutil::trim("\t\tMN \t OP \t"));
+}
+
+// Check tokenization. Note that ASSERT_EQ is used to check the size of the
+// returned vector; if not as expected, the following references may be invalid
+// so should not be used.
+
+TEST_F(StringUtilTest, Tokens) {
+ vector<string> result;
+
+ // Default delimiters
+
+ // Degenerate cases
+ result = isc::strutil::tokens(""); // Empty string
+ EXPECT_EQ(0, result.size());
+
+ result = isc::strutil::tokens(" \n "); // String is all delimiters
+ EXPECT_EQ(0, result.size());
+
+ result = isc::strutil::tokens("abc"); // String has no delimiters
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("abc"), result[0]);
+
+ // String containing leading and/or trailing delimiters, no embedded ones.
+ result = isc::strutil::tokens("\txyz"); // One leading delimiter
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("xyz"), result[0]);
+
+ result = isc::strutil::tokens("\t \nxyz"); // Multiple leading delimiters
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("xyz"), result[0]);
+
+ result = isc::strutil::tokens("xyz\n"); // One trailing delimiter
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("xyz"), result[0]);
+
+ result = isc::strutil::tokens("xyz \t"); // Multiple trailing
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("xyz"), result[0]);
+
+ result = isc::strutil::tokens("\t xyz \n"); // Leading and trailing
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("xyz"), result[0]);
+
+ // Embedded delimiters
+ result = isc::strutil::tokens("abc\ndef"); // 2 tokens, one separator
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("abc"), result[0]);
+ EXPECT_EQ(string("def"), result[1]);
+
+ result = isc::strutil::tokens("abc\t\t\ndef"); // 2 tokens, 3 separators
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("abc"), result[0]);
+ EXPECT_EQ(string("def"), result[1]);
+
+ result = isc::strutil::tokens("abc\n \tdef\t\tghi");
+ ASSERT_EQ(3, result.size()); // Multiple tokens, many delims
+ EXPECT_EQ(string("abc"), result[0]);
+ EXPECT_EQ(string("def"), result[1]);
+ EXPECT_EQ(string("ghi"), result[2]);
+
+ // Embedded and non-embedded delimiters
+
+ result = isc::strutil::tokens("\t\t \nabc\n \tdef\t\tghi \n\n");
+ ASSERT_EQ(3, result.size()); // Multiple tokens, many delims
+ EXPECT_EQ(string("abc"), result[0]);
+ EXPECT_EQ(string("def"), result[1]);
+ EXPECT_EQ(string("ghi"), result[2]);
+
+ // Non-default delimiter
+ result = isc::strutil::tokens("alpha/beta/ /gamma//delta/epsilon/", "/");
+ ASSERT_EQ(6, result.size());
+ EXPECT_EQ(string("alpha"), result[0]);
+ EXPECT_EQ(string("beta"), result[1]);
+ EXPECT_EQ(string(" "), result[2]);
+ EXPECT_EQ(string("gamma"), result[3]);
+ EXPECT_EQ(string("delta"), result[4]);
+ EXPECT_EQ(string("epsilon"), result[5]);
+
+ // Non-default delimiters (plural)
+ result = isc::strutil::tokens("+*--alpha*beta+ -gamma**delta+epsilon-+**",
+ "*+-");
+ ASSERT_EQ(6, result.size());
+ EXPECT_EQ(string("alpha"), result[0]);
+ EXPECT_EQ(string("beta"), result[1]);
+ EXPECT_EQ(string(" "), result[2]);
+ EXPECT_EQ(string("gamma"), result[3]);
+ EXPECT_EQ(string("delta"), result[4]);
+ EXPECT_EQ(string("epsilon"), result[5]);
+}
+
+// Changing case
+
+TEST_F(StringUtilTest, ChangeCase) {
+ string mixed("abcDEFghiJKLmno123[]{=+--+]}");
+ string upper("ABCDEFGHIJKLMNO123[]{=+--+]}");
+ string lower("abcdefghijklmno123[]{=+--+]}");
+
+ string test = mixed;
+ isc::strutil::lowercase(test);
+ EXPECT_EQ(lower, test);
+
+ test = mixed;
+ isc::strutil::uppercase(test);
+ EXPECT_EQ(upper, test);
+}
+
+// Formatting
+
+TEST_F(StringUtilTest, Formatting) {
+
+ vector<string> args;
+ args.push_back("arg1");
+ args.push_back("arg2");
+ args.push_back("arg3");
+
+ string format1 = "This is a string with no tokens";
+ EXPECT_EQ(format1, isc::strutil::format(format1, args));
+
+ string format2 = ""; // Empty string
+ EXPECT_EQ(format2, isc::strutil::format(format2, args));
+
+ string format3 = " "; // Empty string
+ EXPECT_EQ(format3, isc::strutil::format(format3, args));
+
+ string format4 = "String with %d non-string tokens %lf";
+ EXPECT_EQ(format4, isc::strutil::format(format4, args));
+
+ string format5 = "String with %s correct %s number of tokens %s";
+ string result5 = "String with arg1 correct arg2 number of tokens arg3";
+ EXPECT_EQ(result5, isc::strutil::format(format5, args));
+
+ string format6 = "String with %s too %s few tokens";
+ string result6 = "String with arg1 too arg2 few tokens";
+ EXPECT_EQ(result6, isc::strutil::format(format6, args));
+
+ string format7 = "String with %s too %s many %s tokens %s !";
+ string result7 = "String with arg1 too arg2 many arg3 tokens %s !";
+ EXPECT_EQ(result7, isc::strutil::format(format7, args));
+
+ string format8 = "String with embedded%s%s%stokens";
+ string result8 = "String with embeddedarg1arg2arg3tokens";
+ EXPECT_EQ(result8, isc::strutil::format(format8, args));
+
+ // Handle an empty vector
+ args.clear();
+ string format9 = "%s %s";
+ EXPECT_EQ(format9, isc::strutil::format(format9, args));
+}
diff --git a/src/lib/log/tests/xdebuglevel_unittest.cc b/src/lib/log/tests/xdebuglevel_unittest.cc
new file mode 100644
index 0000000..ca80e5a
--- /dev/null
+++ b/src/lib/log/tests/xdebuglevel_unittest.cc
@@ -0,0 +1,203 @@
+// 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 <iostream>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <log4cxx/level.h>
+#include <log/xdebuglevel.h>
+#include <log/debug_levels.h>
+
+/// \brief XDebugLevel (Debug Extension to Level Class)
+///
+/// The class is an extension of the log4cxx Level class; this set of tests
+/// only test the extensions, they do not test the underlying Level class
+/// itself.
+
+using namespace log4cxx;
+
+class XDebugLevelTest : public ::testing::Test {
+protected:
+ XDebugLevelTest()
+ {
+ }
+};
+
+// Check a basic assertion about the numeric values of the debug levels
+
+TEST_F(XDebugLevelTest, NumericValues) {
+ EXPECT_EQ(XDebugLevel::XDEBUG_MIN_LEVEL_INT, Level::DEBUG_INT);
+ EXPECT_EQ(XDebugLevel::XDEBUG_MAX_LEVEL_INT,
+ Level::DEBUG_INT - MAX_DEBUG_LEVEL);
+
+ // ... and check that assumptions used below - that the debug levels
+ // range from 0 to 99 - are valid.
+ EXPECT_EQ(0, MIN_DEBUG_LEVEL);
+ EXPECT_EQ(99, MAX_DEBUG_LEVEL);
+}
+
+
+// Checks that the main function for generating logging level objects from
+// debug levels is working.
+
+TEST_F(XDebugLevelTest, GetExtendedDebug) {
+
+ // Get a debug level of 0. This should be the same as the main DEBUG
+ // level.
+ LevelPtr debug0 = XDebugLevel::getExtendedDebug(0);
+ EXPECT_EQ(std::string("DEBUG"), debug0->toString());
+ EXPECT_EQ(Level::DEBUG_INT, debug0->toInt());
+ EXPECT_TRUE(*Level::getDebug() == *debug0);
+
+ // Get an arbitrary debug level in the allowed range.
+ LevelPtr debug32 = XDebugLevel::getExtendedDebug(32);
+ EXPECT_EQ(std::string("DEBUG32"), debug32->toString());
+ EXPECT_TRUE((XDebugLevel::XDEBUG_MIN_LEVEL_INT - 32) == debug32->toInt());
+
+ // Check that a value outside the range gives the nearest level.
+ LevelPtr debug_more = XDebugLevel::getExtendedDebug(MAX_DEBUG_LEVEL + 1);
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(MAX_DEBUG_LEVEL) == *debug_more);
+
+ LevelPtr debug_less = XDebugLevel::getExtendedDebug(MIN_DEBUG_LEVEL - 1);
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(MIN_DEBUG_LEVEL) == *debug_less);
+}
+
+
+// Creation of a level from an int - should return the default debug level
+// if outside the range.
+
+TEST_F(XDebugLevelTest, FromIntOneArg) {
+
+ // Check that a valid debug level is as expected
+ LevelPtr debug42 = XDebugLevel::toLevel(
+ XDebugLevel::XDEBUG_MIN_LEVEL_INT - 42);
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(42) == *debug42);
+
+ // ... and that an invalid one returns an object of type debug.
+ LevelPtr debug_invalid = XDebugLevel::toLevel(Level::getInfo()->toInt());
+ EXPECT_TRUE(*Level::getDebug() == *debug_invalid);
+}
+
+
+// Creation of a level from an int - should return the default level
+// if outside the range.
+
+TEST_F(XDebugLevelTest, FromIntTwoArg) {
+
+ // Check that a valid debug level is as expected
+ LevelPtr debug42 = XDebugLevel::toLevel(
+ (XDebugLevel::XDEBUG_MIN_LEVEL_INT - 42), Level::getFatal());
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(42) == *debug42);
+
+ // ... and that an invalid one returns an object of type debug.
+ LevelPtr debug_invalid = XDebugLevel::toLevel(
+ Level::getInfo()->toInt(), Level::getFatal());
+ EXPECT_TRUE(*Level::getFatal() == *debug_invalid);
+}
+
+
+// Creation of a level from a string - should return the default debug level
+// if outside the range.
+
+TEST_F(XDebugLevelTest, FromStringOneArg) {
+
+ // Check that a valid debug levels are as expected
+ LevelPtr debug85 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG85"));
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(85) == *debug85);
+
+ LevelPtr debug92 = XDebugLevel::toLevelLS(LOG4CXX_STR("debug92"));
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(92) == *debug92);
+
+ LevelPtr debug27 = XDebugLevel::toLevelLS(LOG4CXX_STR("Debug27"));
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(27) == *debug27);
+
+ LevelPtr debug0 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG"));
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(0) == *debug0);
+
+ // ... and that an invalid one returns an object of type debug (which is
+ // the equivalent of a debug level 0 object).
+ LevelPtr debug_invalid1 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBU"));
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(0) == *debug_invalid1);
+
+ LevelPtr debug_invalid2 = XDebugLevel::toLevelLS(LOG4CXX_STR("EBU"));
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(0) == *debug_invalid2);
+
+ LevelPtr debug_invalid3 = XDebugLevel::toLevelLS(LOG4CXX_STR(""));
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(0) == *debug_invalid3);
+
+ LevelPtr debug_invalid4 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUGTEN"));
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(0) == *debug_invalid4);
+
+ LevelPtr debug_invalid5 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG105"));
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(MAX_DEBUG_LEVEL) ==
+ *debug_invalid5);
+
+ LevelPtr debug_invalid6 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG-7"));
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(MIN_DEBUG_LEVEL) ==
+ *debug_invalid6);
+}
+
+
+// Creation of a level from a string - should return the default level
+// if outside the range.
+
+TEST_F(XDebugLevelTest, FromStringTwoArg) {
+
+ // Check that a valid debug levels are as expected
+ LevelPtr debug85 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG85"),
+ Level::getFatal());
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(85) == *debug85);
+
+ LevelPtr debug92 = XDebugLevel::toLevelLS(LOG4CXX_STR("debug92"),
+ Level::getFatal());
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(92) == *debug92);
+
+ LevelPtr debug27 = XDebugLevel::toLevelLS(LOG4CXX_STR("Debug27"),
+ Level::getFatal());
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(27) == *debug27);
+
+ LevelPtr debug0 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG"),
+ Level::getFatal());
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(0) == *debug0);
+
+ // ... and that an invalid one returns an object of type debug (which is
+ // the equivalent of a debug level 0 object).
+ LevelPtr debug_invalid1 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBU"),
+ Level::getFatal());
+ EXPECT_TRUE(*Level::getFatal() == *debug_invalid1);
+
+ LevelPtr debug_invalid2 = XDebugLevel::toLevelLS(LOG4CXX_STR("EBU"),
+ Level::getFatal());
+ EXPECT_TRUE(*Level::getFatal() == *debug_invalid2);
+
+ LevelPtr debug_invalid3 = XDebugLevel::toLevelLS(LOG4CXX_STR(""),
+ Level::getFatal());
+ EXPECT_TRUE(*Level::getFatal() == *debug_invalid3);
+
+ LevelPtr debug_invalid4 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUGTEN"),
+ Level::getFatal());
+ EXPECT_TRUE(*Level::getFatal() == *debug_invalid4);
+
+ LevelPtr debug_invalid5 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG105"),
+ Level::getFatal());
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(MAX_DEBUG_LEVEL) ==
+ *debug_invalid5);
+
+ LevelPtr debug_invalid6 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG-7"),
+ Level::getFatal());
+ EXPECT_TRUE(*XDebugLevel::getExtendedDebug(MIN_DEBUG_LEVEL) ==
+ *debug_invalid6);
+}
diff --git a/src/lib/log/xdebuglevel.cc b/src/lib/log/xdebuglevel.cc
new file mode 100644
index 0000000..c17a515
--- /dev/null
+++ b/src/lib/log/xdebuglevel.cc
@@ -0,0 +1,146 @@
+// 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 <cassert>
+#include <algorithm>
+#include <syslog.h>
+#include <string.h>
+#include <boost/lexical_cast.hpp>
+
+#include <xdebuglevel.h>
+#include <debug_levels.h>
+#include <log4cxx/helpers/stringhelper.h>
+
+using namespace log4cxx;
+using namespace log4cxx::helpers;
+
+// Storage for the logging level objects corresponding to each debug level
+
+bool XDebugLevel::dbglevels_unset_ = true;
+LevelPtr XDebugLevel::dbglevels_[NUM_DEBUG_LEVEL];
+
+// Register the class
+
+IMPLEMENT_LOG4CXX_LEVEL(XDebugLevel)
+
+
+// Create Extended Debug Level Objects
+
+LevelPtr
+XDebugLevel::getExtendedDebug(int level) {
+
+ // Initialize the logging levels corresponding to the possible range of
+ // debug if we have not already done so
+ if (dbglevels_unset_) {
+
+ // Asserting that the minimum debug level is zero - so corresponds
+ // to DEBUG_INT - means that the lowest level is set to main DEBUG
+ // level. This means that the existing logging level object can be
+ // used.
+ assert(MIN_DEBUG_LEVEL == 0);
+ dbglevels_[0] = Level::getDebug();
+
+ // Create the logging level objects for the rest of the debug levels.
+ // They are given names of the form DEBUG<debug level> (e.g. DEBUG42).
+ // They will all correspond to a syslog level of DEBUG.
+ for (int i = 1; i < NUM_DEBUG_LEVEL; ++i) {
+ std::string name = std::string("DEBUG") +
+ boost::lexical_cast<std::string>(i);
+ dbglevels_[i] = new XDebugLevel(
+ (XDebugLevel::XDEBUG_MIN_LEVEL_INT - i),
+ LOG4CXX_STR(name.c_str()), LOG_DEBUG);
+ }
+ dbglevels_unset_ = false;
+ }
+
+ // Now get the logging level object asked for. Coerce the debug level to
+ // lie in the acceptable range.
+ int actual = std::max(MIN_DEBUG_LEVEL, std::min(MAX_DEBUG_LEVEL, level));
+
+ // ... and return a pointer to the appropriate logging level object
+ return (dbglevels_[actual - MIN_DEBUG_LEVEL]);
+}
+
+// Convert an integer (an absolute logging level number, not a debug level) to a
+// logging level object. If it lies outside the valid range, an object
+// corresponding to the minimum debug value is returned.
+
+LevelPtr
+XDebugLevel::toLevel(int val) {
+ return (toLevel(val, getExtendedDebug(MIN_DEBUG_LEVEL)));
+}
+
+LevelPtr
+XDebugLevel::toLevel(int val, const LevelPtr& defaultLevel) {
+
+ // Note the reversal of the notion of MIN and MAX - see the header file for
+ // details.
+ if ((val >= XDEBUG_MAX_LEVEL_INT) && (val <= XDEBUG_MIN_LEVEL_INT)) {
+ return (getExtendedDebug(XDEBUG_MIN_LEVEL_INT - val));
+ }
+ else {
+ return (defaultLevel);
+ }
+}
+
+// Convert string passed to a logging level or return default level.
+
+LevelPtr
+XDebugLevel::toLevelLS(const LogString& sArg) {
+ return (toLevelLS(sArg, getExtendedDebug(0)));
+}
+
+LevelPtr
+XDebugLevel::toLevelLS(const LogString& sArg, const LevelPtr& defaultLevel) {
+ std::string name = sArg; // Get to known type
+ size_t length = name.size(); // Length of the string
+
+ if (length < 5) {
+
+ // String can't possibly start DEBUG so we don't know what it is.
+ return (defaultLevel);
+ }
+ else {
+ if (strncasecmp(name.c_str(), "DEBUG", 5) == 0) {
+
+ // String starts "DEBUG" (or "debug" or any case mixture). The
+ // rest of the string -if any - should be a number.
+ if (length == 5) {
+
+ // It is plain "DEBUG". Take this as level 0.
+ return (getExtendedDebug(0));
+ }
+ else {
+
+ // Try converting the remainder to an integer. The "5" is
+ // the length of the string "DEBUG". Note that if the number
+ // is outside the rangeof debug levels, it is coerced to the
+ // nearest limit. Thus a level of DEBUG509 will end up as
+ // if DEBUG99 has been specified.
+ try {
+ int level = boost::lexical_cast<int>(name.substr(5));
+ return (getExtendedDebug(level));
+ }
+ catch ((boost::bad_lexical_cast&) ){
+ return (defaultLevel);
+ }
+ }
+ }
+ else {
+
+ // Unknown string - return default.
+ return (defaultLevel);
+ }
+ }
+}
diff --git a/src/lib/log/xdebuglevel.h b/src/lib/log/xdebuglevel.h
new file mode 100644
index 0000000..e580b77
--- /dev/null
+++ b/src/lib/log/xdebuglevel.h
@@ -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.
+
+#ifndef __XDEBUGLEVEL_H
+#define __XDEBUGLEVEL_H
+
+#include <syslog.h>
+#include <log4cxx/level.h>
+
+#include <debug_levels.h>
+
+namespace log4cxx {
+
+/// \brief Debug Extension to Level Class
+///
+/// Based on the example given in the log4cxx distribution, this extends the
+/// log4cxx Level class to allow 100 debug levels.
+///
+/// First some terminology, as the use of the term "level" gets confusing. The
+/// code and comments here use the term "level" in two contexts:
+///
+/// Logging level: The category of messages to log. By default log4cxx defines
+/// the following logging levels: OFF, FATAL, ERROR, WARNING, INFO, DEBUG,
+/// TRACE, ALL. Within the context of BIND-10, OFF, TRACE and ALL are not used
+/// and the idea of DEBUG has been extended, as will be seen below.
+///
+/// Debug level: This is a number that ranges from 0 to 99 and is used by the
+/// application to control the detail of debug output. A value of 0 gives the
+/// highest-level debug output; a value of 99 gives the most verbose and most
+/// detailed. Debug messages (or whatever debug level) are only ever output
+/// when the logging level is set to DEBUG.
+///
+///
+/// With log4cxx, the various logging levels have a numeric value associated
+/// with them, such that FATAL > ERROR > WARNING etc. This suggests that the
+/// idea of debug levels can be incorporated into the existing logging level
+/// scheme by assigning them appropriate numeric values, i.e.
+///
+/// WARNING > INFO > DEBUG(0) > DEBUG(2) > ... > DEBUG(99)
+///
+/// Setting a numeric level of DEBUG enables the basic messages; setting lower
+/// numeric levels will enable progressively more messages. The lowest debug
+/// level (0) is chosen such that setting the general DEBUG logging level will
+/// automatically select that debug level.
+///
+/// This sub-class is needed because the log4cxx::Level class does not allow
+/// the setting of the numeric value of the current level to something other
+/// than the values enumerated in the class. It creates a set of log4cxx
+/// logging levels to correspond to the various debug levels. These levels have
+/// names in the range DEBUG1 to DEBUG99 (the existing Level DEBUG is used for
+/// a debug level of 0), although they are not used in BIND-10: instead the
+/// BIND-10 Logger class treats the logging levels and debug levels separately
+/// and combines them to choose the underlying log4cxx logging level.
+
+
+/// \brief Debug-Extended Level
+
+class XDebugLevel : public Level {
+ DECLARE_LOG4CXX_LEVEL(XDebugLevel)
+
+ /// Array of pointers to logging level objects, one for each debug level.
+ /// The pointer corresponding to a debug level of 0 points to the DEBUG
+ /// logging level object.
+ static LevelPtr dbglevels_[NUM_DEBUG_LEVEL];
+ static bool dbglevels_unset_;
+
+public:
+
+ // Minimum and maximum debug levels. Note that XDEBUG_MIN_LEVEL_INT is the
+ // number corresponding to the minimum debug level - and is actually larger
+ // that XDEBUG_MAX_LEVEL_INT, the number corresponding to the maximum debug
+ // level.
+ enum {
+ XDEBUG_MIN_LEVEL_INT = Level::DEBUG_INT - MIN_DEBUG_LEVEL,
+ XDEBUG_MAX_LEVEL_INT = Level::DEBUG_INT - MAX_DEBUG_LEVEL
+ };
+
+ /// \brief Constructor
+ ///
+ /// \param level Numeric value of the logging level.
+ /// \param name Name given to this logging level.
+ /// \param syslogEquivalent The category to be used by syslog when it logs
+ /// an event associated with the specified logging level.
+ XDebugLevel(int level, const LogString& name, int syslogEquivalent) :
+ Level(level, name, syslogEquivalent)
+ {}
+
+ /// \brief Create Logging Level Object
+ ///
+ /// Creates a logging level object corresponding to one of the debug levels.
+ ///
+ /// \param dbglevel The debug level, which ranges from MIN_DEBUG_LEVEL to
+ /// MAX_DEBUG_LEVEL. It is coerced to that range if it lies outside it.
+ ///
+ /// \return Pointer to the desired logging level object.
+ static LevelPtr getExtendedDebug(int dbglevel);
+
+ /// \brief Convert Integer to a Logging Level
+ ///
+ /// Returns a logging level object corresponding to the given value (which
+ /// is an absolute value of a logging level - it is not a debug level).
+ /// If the number is invalid, an object of logging level DEBUG (the
+ /// minimum debug logging level) is returned.
+ ///
+ /// \param val Number to convert to a logging level. This is an absolute
+ /// logging level number, not a debug level.
+ ///
+ /// \return Pointer to the desired logging level object.
+ static LevelPtr toLevel(int val);
+
+ /// \brief Convert Integer to a Level
+ ///
+ /// Returns a logging level object corresponding to the given value (which
+ /// is an absolute value of a logging level - it is not a debug level).
+ /// If the number is invalid, the given default is returned.
+ ///
+ /// \param val Number to convert to a logging level. This is an absolute
+ /// logging level number, not a debug level.
+ /// \param defaultLevel Logging level to return if value is not recognised.
+ ///
+ /// \return Pointer to the desired logging level object.
+ static LevelPtr toLevel(int val, const LevelPtr& defaultLevel);
+
+ /// \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
+ /// logging level) is returned.
+ ///
+ /// \param sArg Name of the logging level.
+ ///
+ /// \return Pointer to the desired logging level object.
+ static LevelPtr toLevelLS(const LogString& sArg);
+
+ /// \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.
+ ///
+ /// \param sArg name of the level.
+ /// \param defaultLevel Logging level to return if name doesn't exist.
+ ///
+ /// \return Pointer to the desired logging level object.
+ static LevelPtr toLevelLS(const LogString& sArg,
+ const LevelPtr& defaultLevel);
+};
+
+} // namespace log4cxx
+
+
+#endif // __XDEBUGLEVEL_H
diff --git a/src/lib/nsas/Makefile.am b/src/lib/nsas/Makefile.am
new file mode 100644
index 0000000..43300f6
--- /dev/null
+++ b/src/lib/nsas/Makefile.am
@@ -0,0 +1,42 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/nsas -I$(top_builddir)/src/lib/nsas
+AM_CPPFLAGS += $(SQLITE_CFLAGS)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_CLANGPP
+# clang++ complains about unused function parameters in some boost header
+# files.
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+lib_LTLIBRARIES = libnsas.la
+libnsas_la_SOURCES = address_entry.h address_entry.cc
+libnsas_la_SOURCES += asiolink.h
+libnsas_la_SOURCES += hash.cc hash.h
+libnsas_la_SOURCES += hash_deleter.h
+libnsas_la_SOURCES += hash_key.cc hash_key.h
+libnsas_la_SOURCES += locks.h
+libnsas_la_SOURCES += hash_table.h
+libnsas_la_SOURCES += lru_list.h
+libnsas_la_SOURCES += nameserver_address_store.cc nameserver_address_store.h
+libnsas_la_SOURCES += nameserver_address.h nameserver_address.cc
+libnsas_la_SOURCES += nameserver_entry.cc nameserver_entry.h
+libnsas_la_SOURCES += nsas_entry_compare.h
+libnsas_la_SOURCES += nsas_entry.h nsas_types.h
+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/README b/src/lib/nsas/README
new file mode 100644
index 0000000..d0598ca
--- /dev/null
+++ b/src/lib/nsas/README
@@ -0,0 +1,7 @@
+For an overview of the Nameserver Address Store, see the requirements and design
+documents at http://bind10.isc.org/wiki/Resolver.
+
+At the time of writing (19 October 2010), the file asiolink.h is present in this
+directory only for the purposes of development. When the resolver's
+asynchronous I/O code has been finished, this will be removed and the NSAS will
+use the "real" code.
diff --git a/src/lib/nsas/TODO b/src/lib/nsas/TODO
new file mode 100644
index 0000000..4a20690
--- /dev/null
+++ b/src/lib/nsas/TODO
@@ -0,0 +1,40 @@
+Long term:
+* Make a mechanism the cache (which does not exist at the time of writing this
+ note) will be able to notify the NSAS that something has changed (address,
+ new nameserver, etc). Because the cache will have access to the data and
+ knows when it changes (it updates its structures), it is the best place. It
+ will be caching even data like authority and additional sections. It will
+ notify us somehow (we will need to tell it when).
+
+ The changes we need to know about is when set of nameservers or set of
+ addresses for a nameserver change and when a NS record or nameserver's A or
+ AAAA record is explicitly removed from the cache.
+* Optimisation to pass max two outstanding queries on the network (but fetch
+ everything from cache right away). The first can be done by having number of
+ packets on the network, with max of 4 (each query are 2 of them, A and AAAA),
+ if it drops to 2, another one can be send.
+* Add the cache cookies/contexts.
+* Logging.
+* Remove LRU from the nameserver entries, drop them when they are not
+ referenced by any zone entry. This will remove duplicates, keep the RTTs
+ longer and will provide access to everything that exists. This is
+ tricky, though, because we need to be thread safe. There seems to be
+ solution to use weak_ptr inside the hash_table instead of shared_ptr and
+ catch the exception inside get() (and getOrAdd) and delete the dead pointer.
+* Better way to dispatch all calbacks in a list is needed. We take them out of
+ the list and dispatch them one by one. This is wrong because when an
+ exception happens inside the callback, we lose the ones not dispatched yet.
+
+ What should be done in this situation anyway? Putting them back? Will anybody
+ still call them? Taking them one by one?
+
+ Or recommend that if the result is really needed, that destruction of it
+ should be considered failure if it wasn't called yet? Make it the default
+ (eg. signal failure by destruction or call that function from destructor)?
+* Make a zone entry hash table have multiple LRU lists, each one for part of the
+ slots. This will prevent locking contention while still keeping close to
+ the theoretical LRU behaviour (statistically, accesses to each of the part
+ should be as common as to others).
+
+ It might be a good idea to encapsulate the LRUs into the hash table directly
+ (or create a class holding both the hash table and the LRU lists).
diff --git a/src/lib/nsas/address_entry.cc b/src/lib/nsas/address_entry.cc
new file mode 100644
index 0000000..24b0dd9
--- /dev/null
+++ b/src/lib/nsas/address_entry.cc
@@ -0,0 +1,44 @@
+// 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.
+
+/// \file address_entry.cc
+///
+/// This file exists to define the single constant \c AddressEntry::UNREACHABLE,
+/// equal to the value \c UINT32_MAX.
+///
+/// Ideally we could use \c UINT32_MAX directly in the header file, but this
+/// constant is defined in \c stdint.h only if the macro \c __STDC_LIMIT_MACROS
+/// is defined first. (This apparently is the C89 standard.) Defining the
+/// macro in \c address_entry.h before including \c stdint.h doesn't work as
+/// it is possible that in a source file, \c stdint.h will be included before
+/// \c address_entry.h. In that case, the \c stdint.h include sentinel will
+/// prevent \c stdint.h being included a second time and the value won't be
+/// defined.
+///
+/// The easiest solution is the one presented here: declare the value as a
+/// static class constant, and define it in this source file. As we can control
+/// the order of include files, this ensures that the value is defined.
+
+#define __STDC_LIMIT_MACROS
+#include <stdint.h>
+
+#include <config.h>
+
+#include "address_entry.h"
+
+namespace isc {
+namespace nsas {
+const uint32_t AddressEntry::UNREACHABLE = UINT32_MAX;
+}
+}
diff --git a/src/lib/nsas/address_entry.h b/src/lib/nsas/address_entry.h
new file mode 100644
index 0000000..8698017
--- /dev/null
+++ b/src/lib/nsas/address_entry.h
@@ -0,0 +1,102 @@
+// 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 __ADDRESS_ENTRY_H
+#define __ADDRESS_ENTRY_H
+
+/// \brief Address Entry
+///
+/// Lightweight class that couples an address with a RTT and provides some
+/// convenience methods for accessing and updating the information.
+
+#include <stdint.h>
+#include <asiolink/io_address.h>
+
+namespace isc {
+namespace nsas {
+
+class AddressEntry {
+public:
+ /// Creates an address entry given IOAddress entry and RTT
+ /// This is the only constructor; the default copy constructor and
+ /// assignment operator are valid for this object.
+ ///
+ /// \param address Address object representing this address
+ /// \param rtt Initial round-trip time
+ AddressEntry(const asiolink::IOAddress& address, uint32_t rtt = 0) :
+ address_(address), rtt_(rtt), dead_until_(0)
+ {}
+
+ /// \return Address object
+ const asiolink::IOAddress& getAddress() const {
+ return address_;
+ }
+
+ /// \return Current round-trip time
+ uint32_t getRTT() {
+ if(dead_until_ != 0 && time(NULL) >= dead_until_){
+ dead_until_ = 0;
+ rtt_ = 1; //reset the rtt to a small value so it has an opportunity to be updated
+ }
+
+ return rtt_;
+ }
+
+ /// Set current RTT
+ ///
+ /// \param rtt New RTT to be associated with this address
+ void setRTT(uint32_t rtt) {
+ if(rtt == UNREACHABLE){
+ dead_until_ = time(NULL) + 5*60;//Cache the unreachable server for 5 minutes (RFC2308 sec7.2)
+ }
+
+ rtt_ = rtt;
+ }
+
+ /// Mark address as unreachable.
+ void setUnreachable() {
+ setRTT(UNREACHABLE); // Largest long number is code for unreachable
+ }
+
+ /// Check if address is unreachable
+ ///
+ /// \return true if the address is unreachable, false if not
+ bool isUnreachable() {
+ return (getRTT() == UNREACHABLE); // The getRTT() will check the cache time for unreachable server
+ }
+
+ /// \return true if the object is a V4 address
+ bool isV4() const {
+ return (address_.getFamily() == AF_INET);
+ }
+
+ /// \return true if the object is a V6 address
+ bool isV6() const {
+ return (address_.getFamily() == AF_INET6);
+ }
+
+ // Next element is defined public for testing
+ static const uint32_t UNREACHABLE; ///< RTT indicating unreachable address
+
+private:
+ asiolink::IOAddress address_; ///< Address
+ uint32_t rtt_; ///< Round-trip time
+ time_t dead_until_; ///< Dead time for unreachable server
+};
+
+} // namespace dns
+} // namespace isc
+
+
+#endif // __ADDRESS_ENTRY_H
diff --git a/src/lib/nsas/address_request_callback.h b/src/lib/nsas/address_request_callback.h
new file mode 100644
index 0000000..ad0630e
--- /dev/null
+++ b/src/lib/nsas/address_request_callback.h
@@ -0,0 +1,72 @@
+// 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 __ADDRESS_REQUEST_CALLBACK_H
+#define __ADDRESS_REQUEST_CALLBACK_H
+
+#include "asiolink.h"
+#include "nameserver_address.h"
+
+namespace isc {
+namespace nsas {
+
+/// \brief Callback When Address Obtained
+///
+/// This is the callback object used to return an address of a nameserver to a
+/// caller. It (or a subclass of it) is passed to the NSAS when a request is
+/// made for the address of a nameserver. When an address is available,
+/// methods on the passed objects are called.
+///
+/// Note that there is no guarantee as to when the methods are called; they
+/// could be called after the function call that made the address request has
+/// returned the caller. Equally, the call could complete before that function
+/// call returns. It is up to the caller to handle all cases.
+///
+/// In terms of use, a shared pointer to this object is passed to the NSAS.
+/// The NSAS will store the object via a shared pointer and after the callback
+/// will delete the pointer. Whether this results in the deletion of the
+/// callback object is up to the caller - if the caller wants to retain it
+/// they should keep the shared pointer.
+
+class AddressRequestCallback {
+public:
+
+ /// Default constructor, copy contructor and assignment operator
+ /// are implicitly present and are OK.
+
+ /// \brief Virtual Destructor
+ virtual ~AddressRequestCallback()
+ {}
+
+ /// \brief Success Callback
+ ///
+ /// This method is used when an address has been retrieved for the request.
+ ///
+ /// \param address Address to be used to access the nameserver.
+ virtual void success(const NameserverAddress& address) = 0;
+
+ /// \brief Unreachable
+ ///
+ /// This method is called when a request is made for an address, but all
+ /// the addresses for the zone are marked as unreachable. This may be
+ /// due to the NS records being unobtainable, or the A records for known
+ /// nameservers being unobtainable.
+ virtual void unreachable() = 0;
+
+};
+
+} // namespace nsas
+} // namespace isc
+
+#endif // __ADDRESS_REQUEST_CALLBACK_H
diff --git a/src/lib/nsas/asiolink.h b/src/lib/nsas/asiolink.h
new file mode 100644
index 0000000..d95868f
--- /dev/null
+++ b/src/lib/nsas/asiolink.h
@@ -0,0 +1,21 @@
+// 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 __ASIOLINK_H
+#define __ASIOLINK_H
+
+#include <string>
+#include <sys/socket.h>
+
+#endif // __ASIOLINK_H
diff --git a/src/lib/nsas/fetchable.h b/src/lib/nsas/fetchable.h
new file mode 100644
index 0000000..461cfca
--- /dev/null
+++ b/src/lib/nsas/fetchable.h
@@ -0,0 +1,66 @@
+// 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 __FETCHABLE_H
+#define __FETCHABLE_H
+
+/**
+ * \file fetchable.h
+ * \short Interface of information that can be fetched.
+ */
+
+namespace isc {
+namespace nsas {
+
+/**
+ * \short Interface of information that can be fetched.
+ *
+ * This just holds a state of information that can be fetched from somewhere.
+ * No locking is performed, if it is desirable, it should be locked manually.
+ */
+class Fetchable {
+ public:
+ /// \short States the Fetchable object can be in.
+ enum State {
+ /// \short No one yet asked for the information.
+ NOT_ASKED,
+ /// \short The information is too old and should not be used.
+ EXPIRED,
+ /// \short The information is asked for but it did not arrive.
+ IN_PROGRESS,
+ /// \short It is not possible to get the information.
+ UNREACHABLE,
+ /// \short The information is already present.
+ READY
+ };
+ /// \short Constructors
+ //@{
+ /// This creates the Fetchable object in the given state.
+ Fetchable(State state = NOT_ASKED) :
+ state_(state)
+ { }
+ //@}
+ /// \short Getter and setter of current state.
+ //@{
+ State getState() const { return state_; }
+ void setState(State state) { state_ = state; }
+ //@}
+ private:
+ State state_;
+};
+
+} // namespace nsas
+} // namespace isc
+
+#endif // __FETCHABLE_H
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.cc b/src/lib/nsas/hash.cc
new file mode 100644
index 0000000..dbd8eec
--- /dev/null
+++ b/src/lib/nsas/hash.cc
@@ -0,0 +1,168 @@
+// 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.
+
+/*! \file
+ * Some parts of this code were copied from BIND-9, which in turn was derived
+ * from universal hash function libraries of Rice University.
+
+\section license UH Universal Hashing Library
+
+Copyright ((c)) 2002, Rice University
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ * Neither the name of Rice University (RICE) nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+
+This software is provided by RICE and the contributors on an "as is"
+basis, without any representations or warranties of any kind, express
+or implied including, but not limited to, representations or
+warranties of non-infringement, merchantability or fitness for a
+particular purpose. In no event shall RICE or contributors be liable
+for any direct, indirect, incidental, special, exemplary, or
+consequential damages (including, but not limited to, procurement of
+substitute goods or services; loss of use, data, or profits; or
+business interruption) however caused and on any theory of liability,
+whether in contract, strict liability, or tort (including negligence
+or otherwise) arising in any way out of the use of this software, even
+if advised of the possibility of such damage.
+*/
+
+#include <cassert>
+#include <stdlib.h>
+#include <algorithm>
+#include <cassert>
+#include <string>
+
+#include <config.h>
+
+#include "hash.h"
+
+using namespace std;
+
+namespace isc {
+namespace nsas {
+
+// Constructor.
+
+Hash::Hash(uint32_t tablesize, uint32_t maxkeylen, bool randomise) :
+ tablesize_(tablesize), maxkeylen_(min<uint32_t>(maxkeylen,
+ (255 - sizeof(uint16_t))))
+{
+ // (Code taken from BIND-9)
+ //
+ // Check to see that we can cope with the maximum key length which, due
+ // to the limitations, should not be more than 255 in total. The actual
+ // number of characters in the name that are considered is reduced to
+ // ensure that the class is taken into account in the hash. (This accounts
+ // for the "+ sizeof(uint16_t)" in the calculations below.
+ //
+ // Overflow check. Since our implementation only does a modulo
+ // operation at the last stage of hash calculation, the accumulator
+ // must not overflow.
+ hash_accum_t overflow_limit =
+ 1 << (((sizeof(hash_accum_t) - sizeof(hash_random_t))) * 8);
+ if (overflow_limit < (maxkeylen_ + sizeof(uint16_t) + 1) * 0xff) {
+ isc_throw(KeyLengthTooLong, "Hash key length too long for Hash class");
+ }
+
+ // Initialize the random number generator with the current time.
+ // TODO: Use something other than pseudo-random numbers.
+ union {
+ unsigned int seed;
+ time_t curtime;
+ } init_value;
+
+ if (randomise) {
+ init_value.curtime = time(NULL);
+ }
+ else {
+ init_value.seed = 0;
+ }
+ srandom(init_value.seed);
+
+ // Fill in the random vector.
+ randvec_.reserve(maxkeylen_ + sizeof(uint16_t) + 1);
+ for (uint32_t i = 0; i < (maxkeylen + sizeof(uint16_t) + 1); ++i) {
+ randvec_.push_back(static_cast<hash_random_t>(random() & 0xffff));
+ }
+ assert(sizeof(hash_random_t) == 2); // So that the "& 0xffff" is valid
+
+ // Finally, initialize the mapping table for uppercase to lowercase
+ // characters. A table is used as indexing a table is faster than calling
+ // the tolower() function.
+ casemap_.reserve(256);
+ for (int i = 0; i < 256; ++i) {
+ casemap_.push_back(i);
+ }
+ for (int i = 'A'; i <= 'Z'; ++i) {
+ casemap_[i] += ('a' - 'A');
+ }
+}
+
+
+uint32_t Hash::operator()(const HashKey& key, bool ignorecase) const
+{
+ // Calculation as given in BIND-9.
+ hash_accum_t partial_sum = 0;
+ uint32_t i = 0; // Used after the end of the loop
+
+ // Perform the hashing. If the key length if more than the maximum we set
+ // up this hash for, ignore the excess.
+ if (ignorecase) {
+ for (i = 0; i < min(key.keylen, maxkeylen_); ++i) {
+ partial_sum += mapLower(key.key[i]) * randvec_[i];
+ }
+ } else {
+ for (i = 0; i < min(key.keylen, maxkeylen_); ++i) {
+ partial_sum += key.key[i] * randvec_[i];
+ }
+ }
+
+ // Add the hash of the class code
+ union {
+ uint16_t class_code; // Copy of the class code
+ char bytes[sizeof(uint16_t)]; // Byte equivalent
+ } convert;
+
+ convert.class_code = key.class_code.getCode();
+ for (int j = 0; j < sizeof(uint16_t); ++j, ++i) {
+ partial_sum += convert.bytes[j] * randvec_[i];
+ }
+
+ // ... and finish up.
+ partial_sum += randvec_[i];
+
+ // Determine the hash value
+ uint32_t value = partial_sum % prime32_;
+
+ // ... and round it to fit the table size
+ return (value % tablesize_);
+}
+
+} // namespace nsas
+} // namespace isc
diff --git a/src/lib/nsas/hash.h b/src/lib/nsas/hash.h
new file mode 100644
index 0000000..85b82c3
--- /dev/null
+++ b/src/lib/nsas/hash.h
@@ -0,0 +1,125 @@
+// 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 __HASH_H
+#define __HASH_H
+
+#include <stdint.h>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+#include "hash_key.h"
+
+namespace isc {
+namespace nsas {
+
+/// \brief Too Long Key Length
+///
+/// Thrown if the expected maximum key length is too long for the data types
+/// declared in the class.
+class KeyLengthTooLong : public isc::Exception {
+public:
+ KeyLengthTooLong(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+
+/// \brief Hash Calculation
+///
+/// Class abstracting the mechanics of the hash calculation.
+class Hash {
+public:
+
+ /// \brief Constructor
+ ///
+ /// Constructs the hash table and initialises all data structures needed
+ /// for the hashing.
+ ///
+ /// \param tablesize Size of the hash table. For best performance, this
+ /// should be a prime number.
+ /// \param maxkeylen Maximum length (in bytes) of a key to be hashed.
+ /// calculation will return a value between 0 and N-1. The default
+ /// value of 255 is the maximum size of a DNS name.
+ /// \param randomise If true (the default), the pseudo-random number
+ /// generator is seeded with the current time. Otherwise it is initialised
+ /// to a known sequence. This is principally for unit tests, where a random
+ /// sequence could lead to problems in checking results.
+ Hash(uint32_t tablesize, uint32_t maxkeylen = 255, bool randomise = true);
+
+ /// \brief Virtual Destructor
+ virtual ~Hash()
+ {}
+
+ /// \brief Return Size
+ ///
+ /// \return The hash table size with which this object was initialized
+ virtual uint32_t tableSize() const {
+ return tablesize_;
+ }
+
+ /// \brief Return Key Length
+ ///
+ /// \return Maximum length of a key that this class can cope with.
+ virtual uint32_t maxKeyLength() const {
+ return maxkeylen_;
+ }
+
+ /// \brief Hash Value
+ ///
+ /// \param key Parameters comprising the key to be hashed.
+ /// \param ignorecase true for case to be ignored when calculating the
+ /// hash value, false for it to be taken into account.
+ ///
+ /// \return Hash value, a number between 0 and N-1.
+ virtual uint32_t operator()(const HashKey& key,
+ bool ignorecase = true) const;
+
+ /// \brief Map Lower Case to Upper Case
+ ///
+ /// Equivalent of tolower(), but using a table lookup instead of a
+ /// function call. This should make the mapping faster.
+ ///
+ /// \param inchar Input character
+ ///
+ /// \return Mapped character
+ virtual unsigned char mapLower(unsigned char inchar) const {
+ return casemap_[inchar];
+ }
+
+private:
+
+ /// \name Local Typedefs
+ ///
+ /// Typedefs for use in the code. This makes it easier to compare the
+ /// hashing code with that in BIND-9.
+ //@{
+ typedef uint32_t hash_accum_t; ///< Accumulator
+ typedef uint16_t hash_random_t; ///< Random number used in hash
+ //@}
+
+ uint32_t tablesize_; ///< Size of the hash table
+ uint32_t maxkeylen_; ///< Maximum key length
+ std::vector<unsigned char> casemap_; ///< Case mapping table
+ std::vector<hash_random_t> randvec_; ///< Vector of random numbers
+
+ static const uint32_t prime32_ = 0xfffffffb; ///< 2^32 - 5
+ ///< Specifies range of hash output
+};
+
+} // namspace nsas
+} // namespace isc
+
+#endif // __HASH_H
diff --git a/src/lib/nsas/hash_deleter.h b/src/lib/nsas/hash_deleter.h
new file mode 100644
index 0000000..29a32d7
--- /dev/null
+++ b/src/lib/nsas/hash_deleter.h
@@ -0,0 +1,74 @@
+// 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 __HASH_DELETER_H
+#define __HASH_DELETER_H
+
+#include <boost/shared_ptr.hpp>
+#include "hash_table.h"
+#include "lru_list.h"
+
+namespace isc {
+namespace nsas {
+
+/// \brief Delete Object from Hash Table
+///
+/// This is the object passed to the LRU list constructors that deletes the
+/// ZoneEntry from the hash table when the zone is deleted from the LRU list.
+///
+/// It is declared as a nested class so as to be able to access the
+/// hash table without the need to be declared as "friend" or the need
+/// to define accessor methods.
+template <typename T>
+class HashDeleter : public LruList<T>::Dropped {
+public:
+
+ /// \brief Constructor
+ ///
+ /// \param hashtable Reference to the hash table from which information is
+ /// to be deleted. The table is assumed to remain in existence for the life
+ /// of this object.
+ ///
+ /// \param hashtable Hash table from which the element should be deleted.
+ HashDeleter(HashTable<T>& hashtable) : hashtable_(hashtable)
+ {}
+
+ /// \brief Destructor
+ ///
+ virtual ~HashDeleter(){}
+
+ // The default copy constructor and assignment operator are correct for
+ // this object.
+
+ /// \brief Deletion Function
+ ///
+ /// Performs the deletion of the zone entry from the hash table.
+ ///
+ /// \param element Element to be deleted
+ virtual void operator()(T* element) const;
+
+private:
+ HashTable<T>& hashtable_; ///< Hash table to access element
+};
+
+// delete the object from the relevant hash table
+template <class T>
+void HashDeleter<T>::operator()(T* element) const {
+ hashtable_.remove(element->hashKey());
+}
+
+} // namespace nsas
+} // namespace isc
+
+#endif // __HASH_DELETER_H
diff --git a/src/lib/nsas/hash_key.cc b/src/lib/nsas/hash_key.cc
new file mode 100644
index 0000000..bf4676b
--- /dev/null
+++ b/src/lib/nsas/hash_key.cc
@@ -0,0 +1,51 @@
+// 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 <config.h>
+#include "hash_key.h"
+
+namespace isc {
+namespace nsas {
+
+/// Hash Equality Function
+bool HashKey::operator==(const isc::nsas::HashKey& other) {
+
+ // Check key lengths
+ if (other.keylen == keylen) {
+
+ // ... and classes
+ if (other.class_code == class_code) {
+
+ // ... before the expensive operation. This involves a
+ // byte-by-byte comparison, doing a case-independent match.
+ // memcmp() doesn't work (exact match) nor does strcmp or its
+ // variation (stops on the first null byte).
+ //
+ // TODO: Use a lookup table to map upper to lower case (for speed)
+ for (int i = 0; i < other.keylen; ++i) {
+ if (tolower(static_cast<unsigned char>(other.key[i])) !=
+ tolower(static_cast<unsigned char>(key[i]))) {
+ return false; // Mismatch
+ }
+ }
+ return true; // All bytes matched
+ }
+ }
+ return false; // Key length or class did not match
+}
+
+} // namespace nsas
+} // namespace isc
diff --git a/src/lib/nsas/hash_key.h b/src/lib/nsas/hash_key.h
new file mode 100644
index 0000000..c89b327
--- /dev/null
+++ b/src/lib/nsas/hash_key.h
@@ -0,0 +1,96 @@
+// 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 __HASH_KEY_H
+#define __HASH_KEY_H
+
+#include <dns/rrclass.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace nsas {
+
+/// \brief Hash Key
+///
+/// In the nameserver address store, an object is placed into a hash table
+/// according to its key (name) and class.
+///
+/// The key comprises two elements, a pointer to the start of a char string
+/// holding the data that describes the key and a length. This has been
+/// chosen over a std::string because:
+///
+/// # The key may not be a string, it may be binary data
+/// # The overhead of creating std::string objects from such data.
+///
+/// "key" is declared as "const char*" - rather than the more semantically
+/// correct "const uint8_t*" - simply because if std::strings are used, then
+/// the c_str function will return a "const char*".
+///
+/// To avoid passing round three elements (key, key length, and class), they
+/// have been combined into this simple struct.
+struct HashKey {
+
+ /// \brief Constructor
+ ///
+ /// Basic constructor to make the hash key.
+ ///
+ /// \param the_key Array of bytes for which key is to be constructed
+ /// \param the_keylen Length of the byte array
+ /// \param the_class_code Class of this entry
+ HashKey(const char* the_key, uint32_t the_keylen,
+ const isc::dns::RRClass& the_class_code) :
+ key(the_key),
+ keylen(the_keylen),
+ class_code(the_class_code)
+ {}
+
+ /// \brief String Constructor
+ ///
+ /// Convenience constructor using a std::string.
+ ///
+ /// \param the_key Name to use as the key for the hash
+ /// \param the_class_code Class of this entry
+ HashKey(const std::string& the_key,
+ const isc::dns::RRClass& the_class_code) :
+ key(the_key.c_str()),
+ keylen(the_key.size()),
+ class_code(the_class_code)
+ {}
+
+ /// \brief Equality
+ ///
+ /// Convenience for unit testing, this matches two hash keys as being
+ /// equal if the key strings match on a case-independent basis and the
+ /// classes match.
+ ///
+ /// Note that the class strings may include null bytes; the match is
+ /// done on a byte-by-byte basis, with codes in the range 'A' to 'Z' being
+ /// mapped to 'a' to 'z'.
+ ///
+ /// \param other Hash key to compare against.
+ ///
+ /// \return true if the two hash key objects are the same.
+ bool operator==(const isc::nsas::HashKey& other);
+
+ const char* key; ///< Pointer to the start of the key string
+ uint32_t keylen; ///< Length of the key string
+ isc::dns::RRClass class_code; ///< Class associated with the key
+};
+
+} // namespace nsas
+} // namespace isc
+
+#endif // __HASH_KEY_H
diff --git a/src/lib/nsas/hash_table.h b/src/lib/nsas/hash_table.h
new file mode 100644
index 0000000..c4a9913
--- /dev/null
+++ b/src/lib/nsas/hash_table.h
@@ -0,0 +1,336 @@
+// 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 __HASH_TABLE_H
+#define __HASH_TABLE_H
+
+#include <list>
+
+#include <boost/shared_ptr.hpp>
+
+#include "locks.h"
+#include "hash.h"
+#include "hash_key.h"
+
+// Maximum key length if the maximum size of a DNS name
+#define MAX_KEY_LENGTH 255
+
+namespace isc {
+namespace nsas {
+
+/// \brief Hash Table Slot
+///
+/// Describes the entry for the hash table. This is non-copyable (because
+/// the mutex is non-copyable), but we need to be able to copy it to initialize
+/// a vector of hash table slots. As the copy is only needed for
+/// initialization, and as there is no need to copy state when this happens, we
+/// cheat: the copy constructor constructs a newly initialized HashTableSlot and
+/// does not copy its argument.
+template <typename T>
+struct HashTableSlot {
+
+ /// \brief Type definitions
+ ///
+ //@{
+
+ typedef typename std::list<boost::shared_ptr<T> >::iterator iterator;
+ ///< Iterator over elements with same hash
+
+ typedef isc::locks::upgradable_mutex mutex_type;
+ ///< Mutex protecting this slot
+ //@}
+
+ /// \brief Default Constructor
+ HashTableSlot()
+ {}
+
+ /// \brief Copy Constructor
+ ///
+ /// ... which as noted in the class description does not copy.
+ HashTableSlot(const HashTableSlot<T>&)
+ { }
+
+public:
+ mutex_type mutex_; ///< Protection mutex
+ std::list<boost::shared_ptr<T> > list_; ///< List head
+};
+
+/// \brief Comparison Object Class
+///
+/// The base class for a comparison object; this object is used to compare
+/// an object in the hash table with a key, and indicates whether the two
+/// match. All objects used for comparison in hash tables should be derived
+/// from this class.
+template <typename T>
+class HashTableCompare {
+public:
+ /// \brief Constructor
+ HashTableCompare(){}
+
+ /// \brief virtual Destructor
+ virtual ~HashTableCompare() {}
+
+ /// \brief Comparison Function
+ ///
+ /// Compares an object against a name in the hash table and reports if the
+ /// object's name is the same.
+ ///
+ /// \param object Pointer to the object
+ /// \param key Key describing the object
+ ///
+ /// \return bool true of the name of the object is equal to the name given.
+ virtual bool operator()(T* object, const HashKey& key) const = 0;
+};
+
+
+/// \brief Hash Table
+///
+/// This class is an implementation of a hash table in which the zones and
+/// nameservers of the Nameserver Address Store are held.
+///
+/// A special class has been written (rather than use an existing hash table
+/// class) to improve concurrency. Rather than lock the entire hash table when
+/// an object is added/removed/looked up, only the entry for a particular hash
+/// value is locked. To do this, each entry in the hash table is a pair of
+/// mutex/STL List; the mutex protects that particular list.
+///
+/// \param T Class of object to be stored in the table.
+template <typename T>
+class HashTable {
+public:
+
+ /// \brief Type Definitions
+ ///
+ //@{
+ typedef typename
+ isc::locks::sharable_lock<typename HashTableSlot<T>::mutex_type>
+ sharable_lock; ///< Type for a scope-limited read-lock
+
+ typedef typename
+ isc::locks::scoped_lock<typename HashTableSlot<T>::mutex_type>
+ scoped_lock; ///< Type for a scope-limited write-lock
+ //@}
+
+ /// \brief Constructor
+ ///
+ /// Initialises the hash table.
+ ///
+ /// \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.
+ /// \param size Size of the hash table. For best result, this should be a
+ /// prime although that is not checked. The default value is the size used
+ /// in BIND-9 for its address database.
+ HashTable(HashTableCompare<T>* cmp, uint32_t size = 1009);
+
+ /// \brief Destructor
+ ///
+ virtual ~HashTable(){}
+
+ /// \brief Get Entry
+ ///
+ /// Returns a shared_ptr object pointing to the table entry
+ ///
+ /// \param key Name of the object (and class). The hash of this is
+ /// calculated and used to index the table.
+ ///
+ /// \return Shared pointer to the object or NULL if it is not there.
+ boost::shared_ptr<T> get(const HashKey& key) {
+ uint32_t index = hash_(key);
+ sharable_lock lock(table_[index].mutex_);
+ return getInternal(key, index);
+ }
+
+ /// \brief Remove Entry
+ ///
+ /// Remove the specified entry. The shared pointer to the object is
+ /// destroyed, so if this is the last pointer, the object itself is also
+ /// destroyed.
+ ///
+ /// \param key Name of the object (and class). The hash of this is
+ /// calculated and used to index the table.
+ ///
+ /// \return true if the object was deleted, false if it was not found.
+ bool remove(const HashKey& key);
+
+ /// \brief Add Entry
+ ///
+ /// Adds the specified entry to the table. If there is an entry already
+ /// there, it is either replaced or the addition fails, depending on the
+ /// setting of the "replace" parameter.
+ ///
+ /// \param object Pointer to the object to be added. If the addition is
+ /// successful, this object will have a shared pointer pointing to it; it
+ /// should not be deleted by the caller.
+ /// \param key Key to use to calculate the hash.
+ /// \param replace If true, when an object is added and an object with the
+ /// same name already exists, the existing object is replaced. If false,
+ // the addition fails and a status is returned.
+ /// \return true if the object was successfully added, false otherwise.
+ bool add(boost::shared_ptr<T>& object, const HashKey& key,
+ bool replace = false)
+ {
+ uint32_t index = hash_(key);
+ scoped_lock lock(table_[index].mutex_);
+ return addInternal(object, key, index, replace);
+ }
+
+ /**
+ * \brief Attomicly lookup an entry or add a new one if it does not exist.
+ *
+ * Looks up an entry specified by key in the table. If it is not there,
+ * it calls generator() and adds its result to the table under given key.
+ * It is performed attomically to prevent race conditions.
+ *
+ * \param key The entry to lookup.
+ * \param generator will be called when the item is not there. Its result
+ * will be added and returned. The generator should return as soon
+ * as possible, the slot is locked during its execution.
+ * \return The boolean part of pair tells if the value was added (true
+ * means new value, false looked up one). The other part is the
+ * object, either found or created.
+ * \todo This uses a scoped_lock, which does not allow sharing and is
+ * used a lot in the code. It might turn out in future that it is a
+ * problem and that most of the accesses is read only. In that case we
+ * could split it to fast-slow path - first try to find it with
+ * shared_lock. If it fails, lock by scoped_lock, try to find again (we
+ * unlocked it, so it might have appeared) and if it still isn't there,
+ * create it. Not implemented now as it might or might not help (it
+ * could even slow it down) and the code would get more complicated.
+ */
+ template<class Generator>
+ std::pair<bool, boost::shared_ptr<T> > getOrAdd(const HashKey& key,
+ const Generator& generator)
+ {
+ uint32_t index = hash_(key);
+ scoped_lock lock(table_[index].mutex_);
+ boost::shared_ptr<T> result(getInternal(key, index));
+ if (result) {
+ return (std::pair<bool, boost::shared_ptr<T> >(false, result));
+ } else {
+ result = generator();
+ addInternal(result, key, index);
+ return (std::pair<bool, boost::shared_ptr<T> >(true, result));
+ }
+ }
+
+ /// \brief Returns Size of Hash Table
+ ///
+ /// \return Size of hash table
+ uint32_t tableSize() const {
+ return table_.size();
+ }
+
+protected:
+ // Internal parts, expect to be already locked
+ boost::shared_ptr<T> getInternal(const HashKey& key,
+ uint32_t index);
+ bool addInternal(boost::shared_ptr<T>& object, const HashKey& key,
+ uint32_t index, bool replace = false);
+
+private:
+ Hash hash_; ///< Hashing function
+ std::vector<HashTableSlot<T> > table_; ///< The hash table itself
+ boost::shared_ptr<HashTableCompare<T> > compare_; ///< Compare object
+};
+
+
+// Constructor
+template <typename T>
+HashTable<T>::HashTable(HashTableCompare<T>* compare, uint32_t size) :
+ hash_(size, MAX_KEY_LENGTH), table_(size), compare_(compare)
+{}
+
+// Lookup an object in the table
+template <typename T>
+boost::shared_ptr<T> HashTable<T>::getInternal(const HashKey& key,
+ uint32_t index)
+{
+ // Locate the object.
+ typename HashTableSlot<T>::iterator i;
+ for (i = table_[index].list_.begin(); i != table_[index].list_.end(); ++i) {
+ if ((*compare_)(i->get(), key)) {
+
+ // Found it, so return the shared pointer object
+ return (*i);
+ }
+ }
+
+ // Did not find it, return an empty shared pointer object.
+ return boost::shared_ptr<T>();
+}
+
+// Remove an entry from the hash table
+template <typename T>
+bool HashTable<T>::remove(const HashKey& key) {
+
+ // Calculate the hash value
+ uint32_t index = hash_(key);
+
+ // Access to the elements of this hash slot are accessed under a mutex.
+ // The mutex will be released when this object goes out of scope and is
+ // destroyed.
+ scoped_lock lock(table_[index].mutex_);
+
+ // Now search this list to see if the element already exists.
+ typename HashTableSlot<T>::iterator i;
+ for (i = table_[index].list_.begin(); i != table_[index].list_.end(); ++i) {
+ if ((*compare_)(i->get(), key)) {
+
+ // Object found so delete it.
+ table_[index].list_.erase(i);
+ return true;;
+ }
+ }
+
+ // When we get here, we know that there is no element with the key in the
+ // list, so tell the caller.
+ return false;
+}
+
+// Add an entry to the hash table
+template <typename T>
+bool HashTable<T>::addInternal(boost::shared_ptr<T>& object,
+ const HashKey& key, uint32_t index, bool replace)
+{
+ // Search this list to see if the element already exists.
+ typename HashTableSlot<T>::iterator i;
+ for (i = table_[index].list_.begin(); i != table_[index].list_.end(); ++i) {
+ if ((*compare_)(i->get(), key)) {
+
+ // Object found. If we are not allowed to replace the element,
+ // return an error. Otherwise erase it from the list and exit the
+ // loop.
+ if (replace) {
+ table_[index].list_.erase(i);
+ break;
+ }
+ else {
+ return false;
+ }
+ }
+ }
+
+ // When we get here, we know that there is no element with the key in the
+ // list - in which case, add the new object.
+ table_[index].list_.push_back(object);
+
+ return true;
+}
+
+} // namespace nsas
+} // namespace isc
+
+#endif // __HASH_TABLE_H
diff --git a/src/lib/nsas/locks.h b/src/lib/nsas/locks.h
new file mode 100644
index 0000000..98197c3
--- /dev/null
+++ b/src/lib/nsas/locks.h
@@ -0,0 +1,116 @@
+// 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.
+
+/// This file (right now) provides dummy locks
+/// It also contains code to use boost/threads locks:
+///
+/// if USE_BOOST_THREADS is defined, we typedef the relevant classes
+/// and derive from the relevant templates so our dummy locks are
+/// replaced by the boost locks (--enable-boost-threads)
+///
+/// If USE_BOOST_THREADS is NOT defined, all locks are dummy classes
+/// that don't actually do anything. At this moment, only the very
+/// minimal set of methods that we actually use is defined.
+///
+/// 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_
+
+#ifndef USE_BOOST_THREADS
+
+namespace isc {
+namespace locks {
+
+class mutex {
+};
+
+class recursive_mutex {
+};
+
+class upgradable_mutex {
+};
+
+template <typename T>
+class sharable_lock {
+public:
+ sharable_lock(T) { }
+};
+
+template <typename T>
+class scoped_lock {
+public:
+ scoped_lock(T) { }
+
+ void lock() {}
+ void unlock() {}
+};
+
+}
+}
+
+#else // USE_BOOST_THREADS
+
+// Workaround for a problem with boost and sunstudio 5.10
+// There is a version check in there that appears wrong,
+// which makes including boost/thread.hpp fail
+// This will probably be fixed in a future version of boost,
+// in which case this part can be removed then
+#ifdef NEED_SUNPRO_WORKAROUND
+#if defined(__SUNPRO_CC) && __SUNPRO_CC == 0x5100
+#undef __SUNPRO_CC
+#define __SUNPRO_CC 0x5090
+#endif
+#endif // NEED_SUNPRO_WORKAROUND
+
+#include <boost/thread.hpp>
+#include <boost/interprocess/sync/sharable_lock.hpp>
+#include <boost/interprocess/sync/scoped_lock.hpp>
+#include <boost/interprocess/sync/interprocess_upgradable_mutex.hpp>
+#include <boost/interprocess/sync/interprocess_recursive_mutex.hpp>
+
+namespace isc {
+namespace locks {
+
+typedef boost::mutex mutex;
+typedef boost::interprocess::interprocess_upgradable_mutex upgradable_mutex;
+typedef boost::interprocess::interprocess_recursive_mutex recursive_mutex;
+
+template <typename T>
+struct sharable_lock : public boost::interprocess::sharable_lock<T> {
+public:
+ sharable_lock(T& mtype) : boost::interprocess::sharable_lock<T>(mtype) {}
+};
+
+
+template <class T>
+struct scoped_lock : public boost::interprocess::scoped_lock<T> {
+public:
+ scoped_lock(T& mtype) : boost::interprocess::scoped_lock<T>(mtype) { }
+};
+
+}
+}
+
+
+#endif // USE_BOOST_THREADS
+
+#endif // __LOCKS_
diff --git a/src/lib/nsas/lru_list.h b/src/lib/nsas/lru_list.h
new file mode 100644
index 0000000..b057baf
--- /dev/null
+++ b/src/lib/nsas/lru_list.h
@@ -0,0 +1,260 @@
+// 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 __LRU_LIST_H
+#define __LRU_LIST_H
+
+#include <list>
+#include <string>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include "locks.h"
+
+namespace isc {
+namespace nsas {
+
+/// \brief LRU List
+///
+/// Provides the LRU list for the zone and nameserver objects. The list is
+/// created with a specific size. Entries are added to the back of the list
+/// and removed from the front. It is also possible to pull an element out
+/// of the middle of the list and add it to the end of the list, an action that
+/// should be done when the element is referenced.
+///
+/// It is not intended that the class be copied, and the derivation from
+/// boost::noncopyable enforces this.
+template <typename T>
+class LruList : boost::noncopyable {
+public:
+ typedef typename std::list<boost::shared_ptr<T> > lru_list;
+ typedef typename lru_list::iterator iterator;
+
+ /// \brief Dropped Operation
+ ///
+ /// When an object is dropped from the LRU list because it has not been
+ /// accessed for some time, it is possible that the action should trigger
+ /// some other functions. For this reason, it is possible to register
+ /// a list-wide functor object to execute in this casee.
+ ///
+ /// Note that the function does not execute as the result of a call to
+ /// remove() - that is an explicit call and it is assumed that the caller
+ /// will handle any additional operations needed.
+ class Dropped {
+ public:
+ /// \brief Constructor
+ Dropped(){}
+
+ /// \brief Virtual Destructor
+ virtual ~Dropped(){}
+
+ /// \brief Dropped Object Handler
+ ///
+ /// Function object called when the object drops off the end of the
+ /// LRU list.
+ ///
+ /// \param drop Object being dropped.
+ virtual void operator()(T* drop) const = 0;
+ };
+
+ /// \brief Constructor
+ ///
+ /// \param max_size Maximum size of the list before elements are dropped.
+ /// \param dropped Pointer to a function object that will get called as
+ /// elements are dropped. This object will be stored using a shared_ptr,
+ /// so should be allocated with new().
+ LruList(uint32_t max_size = 1000, Dropped* dropped = NULL) :
+ max_size_(max_size), count_(0), dropped_(dropped)
+ {}
+
+ /// \brief Virtual Destructor
+ virtual ~LruList()
+ {}
+
+ /// \brief Add Element
+ ///
+ /// Add a new element to the end of the list.
+ ///
+ /// \param element Reference to the element to add.
+ ///
+ /// \return Handle describing the element in the LRU list.
+ virtual void add(boost::shared_ptr<T>& element);
+
+ /// \brief Remove Element
+ ///
+ /// Removes an element from the list. If the element is not present (i.e.
+ /// its internal list pointer is invalid), this is a no-op.
+ ///
+ /// \param element Reference to the element to remove.
+ virtual void remove(boost::shared_ptr<T>& element);
+
+ /// \brief Touch Element
+ ///
+ /// The name comes from the Unix "touch" command. All this does is to
+ /// move the specified entry from the middle of the list to the end of
+ /// the list.
+ ///
+ /// \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
+ /// some time if the list is big.
+ ///
+ /// \return Number of elements in the list
+ virtual uint32_t size() const {
+
+ // Don't bother to lock the mutex. If an update is in progress, we
+ // receive either the value just before the update or just after it.
+ // Either way, this call could have come just before or just after
+ // that operation, so the value would have been just as uncertain.
+ return count_;
+ }
+
+ /// \brief Return Maximum Size
+ ///
+ /// \return Maximum size of the list
+ virtual uint32_t getMaxSize() const {
+ return max_size_;
+ }
+
+ /// \brief Set Maximum Size
+ ///
+ /// \param max_size New maximum list size
+ virtual void setMaxSize(uint32_t max_size) {
+ max_size_ = max_size;
+ }
+
+private:
+ isc::locks::mutex mutex_; ///< List protection
+ std::list<boost::shared_ptr<T> > lru_; ///< The LRU list itself
+ uint32_t max_size_; ///< Max size of the list
+ uint32_t count_; ///< Count of elements
+ boost::shared_ptr<Dropped> dropped_; ///< Dropped object
+};
+
+// Add entry to the list
+template <typename T>
+void LruList<T>::add(boost::shared_ptr<T>& element) {
+
+ // Protect list against concurrent access
+ isc::locks::scoped_lock<isc::locks::mutex> lock(mutex_);
+
+ // Add the entry and set its pointer field to point into the list.
+ // insert() is used to get the pointer.
+ element->setLruIterator(lru_.insert(lru_.end(), element));
+
+ // ... and update the count while we have the mutex.
+ ++count_;
+
+ // If the count takes us above the maximum size of the list, remove elements
+ // from the front. The current list size could be more than one above the
+ // maximum size of the list if the maximum size was changed after
+ // construction.
+ while (count_ > max_size_) {
+ if (!lru_.empty()) {
+
+ // Run the drop handler (if there is one) on the
+
+ // to-be-dropped object.
+ if (dropped_) {
+ (*dropped_)(lru_.begin()->get());
+ }
+
+ // ... and get rid of it from the list
+ lru_.pop_front();
+ --count_;
+ }
+ else {
+
+ // TODO: Log this condition (count_ > 0 when list empty) -
+ // it should not happen
+ count_ = 0;
+ break;
+ }
+ }
+}
+
+// Remove an element from the list
+template <typename T>
+void LruList<T>::remove(boost::shared_ptr<T>& element) {
+
+ // An element can only be removed it its internal pointer is valid.
+ // If it is, the pointer can be used to access the list because no matter
+ // what other elements are added or removed, the pointer remains valid.
+ //
+ // If the pointer is not valid, this is a no-op.
+ if (element->iteratorValid()) {
+
+ // Is valid, so protect list against concurrent access
+ isc::locks::scoped_lock<isc::locks::mutex> lock(mutex_);
+
+ lru_.erase(element->getLruIterator()); // Remove element from list
+ element->invalidateIterator(); // Invalidate pointer
+ --count_; // One less element
+ }
+}
+
+// Touch an element - remove it from the list and add to the end
+template <typename T>
+void LruList<T>::touch(boost::shared_ptr<T>& element) {
+
+ // As before, if the pointer is not valid, this is a no-op.
+ if (element->iteratorValid()) {
+
+ // Protect list against concurrent access
+ isc::locks::scoped_lock<isc::locks::mutex> lock(mutex_);
+
+ // Move the element to the end of the list.
+ lru_.splice(lru_.end(), lru_, element->getLruIterator());
+
+ // Update the iterator in the element to point to it. We can
+ // offset from end() as a list has a bidirectional iterator.
+ iterator i = lru_.end();
+ element->setLruIterator(--i);
+ }
+}
+
+// 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
+
+#endif // __LRU_LIST_H
diff --git a/src/lib/nsas/nameserver_address.cc b/src/lib/nsas/nameserver_address.cc
new file mode 100644
index 0000000..19d18c5
--- /dev/null
+++ b/src/lib/nsas/nameserver_address.cc
@@ -0,0 +1,32 @@
+// 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 <config.h>
+
+#include "nameserver_address.h"
+#include "nameserver_entry.h"
+
+namespace isc {
+namespace nsas {
+
+void
+NameserverAddress::updateRTT(uint32_t rtt) const {
+ // We delegate it to the address entry inside the nameserver entry
+ if (ns_) {
+ ns_->updateAddressRTT(rtt, address_.getAddress(), family_);
+ }
+}
+
+} // namespace nsas
+} // namespace isc
diff --git a/src/lib/nsas/nameserver_address.h b/src/lib/nsas/nameserver_address.h
new file mode 100644
index 0000000..07b6d4a
--- /dev/null
+++ b/src/lib/nsas/nameserver_address.h
@@ -0,0 +1,117 @@
+// 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 __NAMESERVER_ADDRESS_H
+#define __NAMESERVER_ADDRESS_H
+
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include "asiolink.h"
+#include "address_entry.h"
+#include "nsas_types.h"
+
+namespace isc {
+namespace nsas {
+
+class ZoneEntry;
+class NameserverEntry;
+
+/// \brief Empty \c NameserverEntry pointer exception
+///
+/// Thrown if the the \c NameservrEntry pointer in the \c boost::shared_ptr that passed
+/// into \c NameserverAddress' constructor is NULL
+class NullNameserverEntryPointer : public isc::Exception {
+public:
+ NullNameserverEntryPointer(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+/// \brief Nameserver Address
+///
+/// This class implements the object that returned from NSAS when the resolver
+/// request an address for the name server. It contains one address
+/// that can be used by resolver. When the resolver get query back from the name
+/// server, it should update the name server's RTT(Round Trip Time) with this
+/// object.
+///
+/// It is not thread safe, only reentrant. It is expected to be kept inside
+/// the resolver and used only once for the address and once for the update.
+
+class NameserverAddress {
+public:
+ /// \brief Constructor
+ ///
+ /// The NameserverAddress object will contain one shared_ptr object that
+ /// 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 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 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):
+ ns_(nameserver), address_(address), family_(family)
+ {
+ if(!ns_) {
+ isc_throw(NullNameserverEntryPointer, "NULL NameserverEntry pointer.");
+ }
+ }
+
+ /// \brief Default Constructor
+ NameserverAddress() : address_(asiolink::IOAddress("::1")) { }
+
+ /// \brief Return address
+ ///
+ asiolink::IOAddress getAddress() const {
+ return (address_.getAddress());
+ }
+
+ /// \brief Update Round-trip Time
+ ///
+ /// When the user get one request back from the name server, it should
+ /// update the address's RTT.
+ /// \param rtt The new Round-Trip Time
+ void updateRTT(uint32_t rtt) const;
+
+ /// Short access to the AddressEntry inside.
+ //@{
+ const AddressEntry& getAddressEntry() const {
+ return (address_);
+ }
+ AddressEntry& getAddressEntry() {
+ return (address_);
+ }
+ //@}
+private:
+
+ /*
+ * Note: Previous implementation used index into the entry. That is wrong,
+ * as the list of addresses may change. Thil would cause setting a
+ * different address or a crash.
+ */
+ boost::shared_ptr<NameserverEntry> ns_; ///< Shared-pointer to NameserverEntry object
+ AddressEntry address_; ///< The address
+ AddressFamily family_; ///< The address family (V4_ONLY or V6_ONLY)
+};
+
+} // namespace nsas
+} // namespace isc
+
+#endif//__NAMESERVER_ADDRESS_H
diff --git a/src/lib/nsas/nameserver_address_store.cc b/src/lib/nsas/nameserver_address_store.cc
new file mode 100644
index 0000000..e92c177
--- /dev/null
+++ b/src/lib/nsas/nameserver_address_store.cc
@@ -0,0 +1,114 @@
+// 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 <config.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/foreach.hpp>
+#include <boost/bind.hpp>
+
+#include <config.h>
+#include <dns/rdataclass.h>
+
+#include "locks.h"
+#include "hash_table.h"
+#include "lru_list.h"
+#include "hash_deleter.h"
+#include "nsas_entry_compare.h"
+#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;
+using namespace std;
+
+namespace isc {
+namespace nsas {
+
+// Constructor.
+//
+// The LRU lists are set equal to three times the size of the respective
+// hash table, on the assumption that three elements is the longest linear
+// search we want to do when looking up names in the hash table.
+NameserverAddressStore::NameserverAddressStore(
+ boost::shared_ptr<isc::resolve::ResolverInterface> resolver,
+ uint32_t zonehashsize, uint32_t nshashsize) :
+ zone_hash_(new HashTable<ZoneEntry>(new NsasEntryCompare<ZoneEntry>,
+ zonehashsize)),
+ nameserver_hash_(new HashTable<NameserverEntry>(
+ new NsasEntryCompare<NameserverEntry>, nshashsize)),
+ zone_lru_(new LruList<ZoneEntry>((3 * zonehashsize),
+ new HashDeleter<ZoneEntry>(*zone_hash_))),
+ nameserver_lru_(new LruList<NameserverEntry>((3 * nshashsize),
+ new HashDeleter<NameserverEntry>(*nameserver_hash_))),
+ resolver_(resolver.get())
+{ }
+
+namespace {
+
+/*
+ * We use pointers here so there's no call to any copy constructor.
+ * It is easier for the compiler to inline it and prove that there's
+ * no need to copy anything. In that case, the bind should not be
+ * called at all to create the object, just call the function.
+ */
+boost::shared_ptr<ZoneEntry>
+newZone(
+ 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,
+ *ns_hash, *ns_lru));
+ return (result);
+}
+
+}
+
+void
+NameserverAddressStore::lookup(const string& zone, const RRClass& class_code,
+ 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_)));
+ if (zone_obj.first) {
+ zone_lru_->add(zone_obj.second);
+ } else {
+ zone_lru_->touch(zone_obj.second);
+ }
+
+ 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
+} // namespace isc
diff --git a/src/lib/nsas/nameserver_address_store.h b/src/lib/nsas/nameserver_address_store.h
new file mode 100644
index 0000000..d54be84
--- /dev/null
+++ b/src/lib/nsas/nameserver_address_store.h
@@ -0,0 +1,127 @@
+// 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 __NAMESERVER_ADDRESS_STORE_H
+#define __NAMESERVER_ADDRESS_STORE_H
+
+#include <string>
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+
+#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
+
+namespace dns {
+class RRClass;
+}
+
+namespace nsas {
+
+class ResolverInterface;
+template<class T> class HashTable;
+template<class T> class LruList;
+class ZoneEntry;
+class NameserverEntry;
+class AddressRequestCallback;
+
+/// \brief Nameserver Address Store
+///
+/// This class implements the bare bones of the nameserver address store - the
+/// storage of nameserver information. An additional layer above it implements
+/// the logic for sending queries for the nameserver addresses if they are not
+/// in the store.
+
+class NameserverAddressStore {
+public:
+
+ /// \brief Constructor
+ ///
+ /// The constructor sizes all the tables. As there are various
+ /// relationships between the table sizes, and as some values are best as
+ /// prime numbers, the table sizes are determined by compile-time values.
+ ///
+ /// \param resolver Which resolver object (or resolver-like, in case of
+ /// 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 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.
+ NameserverAddressStore(
+ boost::shared_ptr<isc::resolve::ResolverInterface> resolver,
+ uint32_t zonehashsize = 1009, uint32_t nshashsize = 3001);
+
+ /// \brief Destructor
+ ///
+ /// Empty virtual destructor.
+ virtual ~NameserverAddressStore()
+ {}
+
+ /// \brief Lookup Address for a Zone
+ ///
+ /// Looks up the address of a nameserver in the zone.
+ ///
+ /// \param zone Name of zone for which an address is required.
+ /// \param class_code Class of the zone.
+ /// \param callback Callback object used to pass the result back to the
+ /// caller.
+ /// \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, 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
+ ///
+ /// These members should be private. However, with so few public methods
+ /// and with a lot of internal processing, the testing of this class is
+ /// problematical.
+ ///
+ /// To get round this, a number of elements are declared protected. This
+ /// means that tests can be carried out by testing a subclass. The subclass
+ /// does not override the main class methods, but does contain additional
+ /// methods to set up data and examine the internal state of the class.
+ //@{
+protected:
+ // Zone and nameserver hash tables
+ boost::shared_ptr<HashTable<ZoneEntry> > zone_hash_;
+ boost::shared_ptr<HashTable<NameserverEntry> > nameserver_hash_;
+
+ // ... and the LRU lists
+ boost::shared_ptr<LruList<ZoneEntry> > zone_lru_;
+ boost::shared_ptr<LruList<NameserverEntry> > nameserver_lru_;
+ // The resolver we use
+private:
+ isc::resolve::ResolverInterface* resolver_;
+ //}@
+};
+
+} // namespace nsas
+} // namespace isc
+
+
+#endif // __NAMESERVER_ADDRESS_STORE_H
diff --git a/src/lib/nsas/nameserver_entry.cc b/src/lib/nsas/nameserver_entry.cc
new file mode 100644
index 0000000..40d5cd7
--- /dev/null
+++ b/src/lib/nsas/nameserver_entry.cc
@@ -0,0 +1,444 @@
+// 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 <config.h>
+
+#include <algorithm>
+#include <functional>
+#include <cassert>
+#include <iostream>
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+
+#include <ctype.h>
+#include <strings.h>
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/rcode.h>
+#include <dns/opcode.h>
+#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"
+
+using namespace asiolink;
+using namespace isc::nsas;
+using namespace isc::dns;
+using namespace std;
+
+namespace isc {
+namespace nsas {
+
+namespace {
+
+// Just shorter type alias
+typedef isc::locks::scoped_lock<isc::locks::recursive_mutex> Lock;
+
+}
+
+// Returns the list of addresses matching the given family
+Fetchable::State
+NameserverEntry::getAddresses(AddressVector& addresses,
+ AddressFamily family, bool expired_ok)
+{
+ Lock lock(mutex_);
+
+ // Check TTL
+ time_t now(time(NULL));
+ // We take = as well, so we catch TTL 0 correctly
+ // expiration_ == 0 means not set, the reason is we are UNREACHABLE or
+ // NOT_ASKED or IN_PROGRESS
+ if (getState() != NOT_ASKED && expiration_ && expiration_ <= now) {
+ setState(EXPIRED);
+ }
+
+ if (getState() == EXPIRED && !expired_ok) {
+ return EXPIRED;
+ }
+
+ switch (getState()) {
+ case IN_PROGRESS:
+ /*
+ * Did we receive the address already?
+ *
+ * We might have already received the addresses for this family
+ * and still wait for the other (in which case has_address_[family]
+ * will be true). We might already received a negative answer,
+ * in which case expect_address_[family] is false and
+ * has_address_[family] is false as well.
+ */
+ if (!has_address_[family] && expect_address_[family]) {
+ return IN_PROGRESS;
+ }
+ // If we do not expect the address, then fall trough to READY
+ case EXPIRED: // If expired_ok, we pretend to be ready
+ case READY:
+ if (!has_address_[family]) {
+ return UNREACHABLE;
+ }
+ break; // OK, we give some answers
+ case NOT_ASKED:
+ case UNREACHABLE:
+ // Reject giving any data
+ return (getState());
+ }
+
+ boost::shared_ptr<NameserverEntry> self(shared_from_this());
+ // If any address is OK, just pass everything we have
+ if (family == ANY_OK) {
+ BOOST_FOREACH(const AddressEntry& entry, addresses_[V6_ONLY]) {
+ addresses.push_back(NameserverAddress(self, entry, V6_ONLY));
+ }
+ BOOST_FOREACH(const AddressEntry& entry, addresses_[V4_ONLY]) {
+ addresses.push_back(NameserverAddress(self, entry, V4_ONLY));
+ }
+ } else {
+ BOOST_FOREACH(const AddressEntry& entry, addresses_[family]) {
+ addresses.push_back(NameserverAddress(self, entry, family));
+ }
+ }
+ if (getState() == EXPIRED && expired_ok) {
+ return READY;
+ }
+ return getState();
+}
+
+// Return the address corresponding to the family
+asiolink::IOAddress
+NameserverEntry::getAddressAtIndex(size_t index, AddressFamily family) const {
+ Lock lock(mutex_);
+
+ assert(index < addresses_[family].size());
+
+ return (addresses_[family][index].getAddress());
+}
+
+// Set the address RTT to a specific value
+void
+NameserverEntry::setAddressRTT(const IOAddress& address, uint32_t rtt) {
+ Lock lock(mutex_);
+
+ // Search through the list of addresses for a match
+ AddressFamily family(V4_ONLY);
+ for (;;) {
+ BOOST_FOREACH(AddressEntry& entry, addresses_[family]) {
+ if (entry.getAddress().equals(address)) {
+ entry.setRTT(rtt);
+ return;
+ }
+ }
+
+ // Hack. C++ does not allow ++ on enums, enumerating trough them is pain
+ switch (family) {
+ case V4_ONLY: family = V6_ONLY; break;
+ default: return;
+ }
+ }
+}
+
+// Update the address's rtt
+#define UPDATE_RTT_ALPHA 0.7
+void
+NameserverEntry::updateAddressRTTAtIndex(uint32_t rtt, size_t index,
+ AddressFamily family)
+{
+ Lock lock(mutex_);
+
+ //make sure it is a valid index
+ if(index >= addresses_[family].size()) return;
+
+ // Smoothly update the rtt
+ // The algorithm is as the same as bind8/bind9:
+ // new_rtt = old_rtt * alpha + new_rtt * (1 - alpha), where alpha is a float number in [0, 1.0]
+ // The default value for alpha is 0.7
+ 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);
+}
+
+void
+NameserverEntry::updateAddressRTT(uint32_t rtt,
+ const asiolink::IOAddress& address, AddressFamily family)
+{
+ Lock lock(mutex_);
+ for (size_t i(0); i < addresses_[family].size(); ++ i) {
+ if (addresses_[family][i].getAddress().equals(address)) {
+ updateAddressRTTAtIndex(rtt, i, family);
+ return;
+ }
+ }
+}
+
+// Sets the address to be unreachable
+void
+NameserverEntry::setAddressUnreachable(const IOAddress& address) {
+ setAddressRTT(address, AddressEntry::UNREACHABLE);
+}
+
+/**
+ * \short A callback into the resolver.
+ *
+ * Whenever we ask the resolver something, this is created and the answer is
+ * fed back trough this. It holds a shared pointer to the entry so it is not
+ * destroyed too soon.
+ */
+class NameserverEntry::ResolverCallback :
+ public isc::resolve::ResolverInterface::Callback {
+ public:
+ ResolverCallback(boost::shared_ptr<NameserverEntry> entry,
+ AddressFamily family, const RRType& type) :
+ entry_(entry),
+ family_(family),
+ type_(type)
+ { }
+ /**
+ * \short We received the address successfully.
+ *
+ * This extracts the addresses out from the response and puts them
+ * inside the entry. It tries to reuse the address entries from before (if there were any), to keep their RTTs.
+ */
+ virtual void success(MessagePtr response_message) {
+ time_t now = time(NULL);
+
+ Lock lock(entry_->mutex_);
+
+ // TODO: find the correct RRset, not simply the first
+ if (!response_message ||
+ 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;
+
+ vector<AddressEntry> entries;
+
+ if (response->getType() != type_ ||
+ response->getClass() != RRClass(entry_->getClass()))
+ {
+ // TODO Log we got answer of different type
+ failureInternal(lock);
+ return;
+ }
+
+ for (RdataIteratorPtr i(response->getRdataIterator());
+ !i->isLast(); i->next())
+ {
+ // Try to find the original value and reuse it
+ string address(i->getCurrent().toText());
+ AddressEntry *found(NULL);
+ BOOST_FOREACH(AddressEntry& entry,
+ entry_->previous_addresses_[family_])
+ {
+ if (entry.getAddress().toText() == address) {
+ // Good, found it.
+ found = &entry;
+ break;
+ }
+ }
+ // If we found it, use it. If not, create a new one.
+ entries.push_back(found ? *found : AddressEntry(IOAddress(
+ i->getCurrent().toText()), 1));
+ }
+
+ // We no longer need the previous set of addresses, we have
+ // the current ones now.
+ entry_->previous_addresses_[family_].clear();
+
+ if (entries.empty()) {
+ // No data there, count it as a failure
+ failureInternal(lock);
+ } else {
+ // We received the data, so mark it
+ entry_->expect_address_[family_] = false;
+ entry_->expect_address_[ANY_OK] =
+ entry_->expect_address_[V4_ONLY] ||
+ entry_->expect_address_[V6_ONLY];
+ // Everything is here (all address families)
+ if (!entry_->expect_address_[ANY_OK]) {
+ entry_->setState(READY);
+ }
+ // We have some address
+ entry_->has_address_[ANY_OK] =
+ entry_->has_address_[family_] = true;
+ // Insert the entries inside
+ entry_->addresses_[family_].swap(entries);
+ // Update the expiration time. If it is 0, it means we
+ // did not set it yet, so reset
+ time_t expiration(now + response->getTTL().getValue());
+ if (entry_->expiration_) {
+ // We expire at the time first address expires
+ entry_->expiration_ = min(entry_->expiration_, expiration);
+ } else {
+ // We have no expiration time set, use this one
+ entry_->expiration_ = expiration;
+ }
+ // Run the right callbacks
+ dispatchCallbacks(lock);
+ }
+ }
+ /**
+ * \short The resolver failed to retrieve the data.
+ *
+ * So mark the current address family as unreachable.
+ */
+ virtual void failure() {
+ Lock lock(entry_->mutex_);
+ failureInternal(lock);
+ }
+ private:
+ boost::shared_ptr<NameserverEntry> entry_;
+ AddressFamily family_;
+ RRType type_;
+
+ // Dispatches all relevant callbacks. Keeps lock unlocked afterwards.
+ // TODO: We might want to use recursive lock and get rid of this
+ void dispatchCallbacks(Lock& lock)
+ {
+ // We dispatch ANY addresses if there is at last one address or
+ // there's no chance we'll get some in future
+ bool dispatch_any = entry_->has_address_[ANY_OK] ||
+ !entry_->expect_address_[ANY_OK];
+ // Sort out the callbacks we want
+ vector<CallbackPair> keep;
+ vector<boost::shared_ptr<NameserverEntry::Callback> > dispatch;
+ BOOST_FOREACH(const CallbackPair &callback, entry_->callbacks_)
+ {
+ if (callback.first == family_ || (dispatch_any &&
+ callback.first == ANY_OK))
+ {
+ dispatch.push_back(callback.second);
+ } else {
+ keep.push_back(callback);
+ }
+ }
+ // Put there only the ones that we do not want, drop the rest
+ keep.swap(entry_->callbacks_);
+ keep.clear();
+
+ // We can't keep the lock while we execute callbacks
+ lock.unlock();
+ // Run all the callbacks
+ /*
+ * FIXME: This is not completely exception safe. If there's an
+ * exception in a callback, we lose the rest of them.
+ */
+ BOOST_FOREACH(const boost::shared_ptr<NameserverEntry::Callback>&
+ callback, dispatch)
+ {
+ (*callback)(entry_);
+ }
+ }
+
+ // Handle a failure to optain data. Dispatches callbacks and leaves
+ // lock unlocked
+ void failureInternal(Lock &lock) {
+ // Set state of the addresses
+ entry_->expect_address_[family_] = false;
+ entry_->expect_address_[ANY_OK] =
+ entry_->expect_address_[V4_ONLY] ||
+ entry_->expect_address_[V6_ONLY];
+ // When we do not expect any more addresses, decide the state
+ if (!entry_->expect_address_[ANY_OK]) {
+ if (entry_->has_address_[ANY_OK]) {
+ // We have at last one kind of address, so OK
+ entry_->setState(READY);
+ } else {
+ // No addresses :-(
+ entry_->setState(UNREACHABLE);
+ }
+ }
+ // Drop the previous addresses, no use of them now
+ entry_->previous_addresses_[family_].clear();
+ // Dispatch any relevant callbacks
+ dispatchCallbacks(lock);
+ }
+};
+
+void
+NameserverEntry::askIP(isc::resolve::ResolverInterface* resolver,
+ const RRType& type, AddressFamily family)
+{
+ QuestionPtr question(new Question(Name(getName()), RRClass(getClass()),
+ type));
+ boost::shared_ptr<ResolverCallback> callback(new ResolverCallback(
+ shared_from_this(), family, type));
+ resolver->resolve(question, callback);
+}
+
+void
+NameserverEntry::askIP(isc::resolve::ResolverInterface* resolver,
+ boost::shared_ptr<Callback> callback, AddressFamily family)
+{
+ Lock lock(mutex_);
+
+ if (getState() == EXPIRED || getState() == NOT_ASKED) {
+ // We will request the addresses
+
+ // Set internal state first
+ // We store the old addresses so we can pick their RTT when
+ // we get the same addresses again (most probably)
+ previous_addresses_[V4_ONLY].clear();
+ previous_addresses_[V6_ONLY].clear();
+ addresses_[V4_ONLY].swap(previous_addresses_[V4_ONLY]);
+ addresses_[V6_ONLY].swap(previous_addresses_[V6_ONLY]);
+ setState(IN_PROGRESS);
+ has_address_[V4_ONLY] = has_address_[V6_ONLY] = has_address_[ANY_OK] =
+ false;
+ expect_address_[V4_ONLY] = expect_address_[V6_ONLY] =
+ expect_address_[ANY_OK] = true;
+ expiration_ = 0;
+
+ // Store the callback
+ callbacks_.push_back(CallbackPair(family, callback));
+
+ // Ask for both types of addresses
+ // We are unlocked here, as the callback from that might want to lock
+ lock.unlock();
+ askIP(resolver, RRType::A(), V4_ONLY);
+ askIP(resolver, RRType::AAAA(), V6_ONLY);
+ // Make sure we end the routine when we are not locked
+ return;
+ } else {
+ // We already asked. Do we expect this address type still to come?
+ if (!expect_address_[family]) {
+ // We do not expect it to come, dispatch right away
+ lock.unlock();
+ (*callback)(shared_from_this());
+ return;
+ } else {
+ // It will come in future, store the callback until then
+ callbacks_.push_back(CallbackPair(family, callback));
+ }
+ }
+}
+
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/nsas/nameserver_entry.h b/src/lib/nsas/nameserver_entry.h
new file mode 100644
index 0000000..99d7ff5
--- /dev/null
+++ b/src/lib/nsas/nameserver_entry.h
@@ -0,0 +1,283 @@
+// 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 __NAMESERVER_ENTRY_H
+#define __NAMESERVER_ENTRY_H
+
+#include <string>
+#include <vector>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <exceptions/exceptions.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+
+#include <resolve/resolver_interface.h>
+
+#include "address_entry.h"
+#include "asiolink.h"
+#include "nsas_types.h"
+#include "hash_key.h"
+#include "lru_list.h"
+#include "fetchable.h"
+#include "nsas_entry.h"
+#include "nameserver_address.h"
+
+namespace isc {
+namespace nsas {
+
+class NameserverAddress;
+
+/// \brief Inconsistent Owner Names
+///
+/// Thrown if a NameserverEntry is constructed from both an A and AAAA RRset
+/// where the owner names do not match.
+class InconsistentOwnerNames : public Exception {
+public:
+ InconsistentOwnerNames(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+/// \brief RTT is zero
+///
+/// Thrown if a RTT related with an address is 0.
+class RTTIsZero : public Exception {
+public:
+ RTTIsZero(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+/// \brief Inconsistent Class
+///
+/// Thrown if a NameserverEntry is constructed from both an A and AAAA RRset
+/// where the classes do not match.
+class InconsistentClass : public Exception {
+public:
+ InconsistentClass(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+class ZoneEntry;
+
+/// \brief Nameserver Entry
+///
+/// Describes a nameserver and its addresses. A nameserver be authoritative
+/// for several zones (hence is pointed to by more than one zone entry), and
+/// may have several addresses associated with it.
+///
+/// The addresses expire after their TTL has been reached. For simplicity,
+/// (and because it is unlikely that A and AAAA records from the same zone have
+/// different TTLs) there is one expiration time for all address records.
+/// When that is reached, all records are declared expired and new fetches
+/// started for the information.
+///
+/// As this object will be stored in the nameserver address store LRU list,
+/// it is derived from the LRU list entry class.
+///
+/// It uses shared_from_this in its methods. It must live inside a shared_ptr.
+
+class NameserverEntry : public NsasEntry<NameserverEntry>, public Fetchable {
+public:
+ /// List of addresses associated with this nameserver
+ typedef std::vector<NameserverAddress> AddressVector;
+ typedef AddressVector::iterator AddressVectorIterator;
+
+ /// \brief Constructor where no A records are supplied.
+ ///
+ /// \param name Name of the nameserver,
+ /// \param class_code class of the nameserver
+ NameserverEntry(const std::string& name,
+ const isc::dns::RRClass& class_code) :
+ name_(name),
+ classCode_(class_code),
+ expiration_(0)
+ {
+ has_address_[V4_ONLY] = false;
+ has_address_[V6_ONLY] = false;
+ has_address_[ANY_OK] = false;
+ expect_address_[V4_ONLY] = false;
+ expect_address_[V6_ONLY] = false;
+ expect_address_[ANY_OK] = false;
+ }
+
+ /*
+ * \brief Return Address
+ *
+ * Returns a vector of addresses corresponding to this nameserver.
+ *
+ * \param addresses Vector of address entries into which will be appended
+ * addresses that match the specified criteria. (The reason for
+ * choosing this signature is that addresses from more than one
+ * nameserver may be retrieved, in which case appending to an existing
+ * list of addresses is convenient.)
+ * \param family The family of address that is requested.
+ * \param expired_ok Return addresses even when expired. This is here
+ * because an address with TTL 0 is expired at the exact time it
+ * arrives. But when we call the callback, the owner of callback
+ * is allowed to use them anyway so it should set expired_ok
+ * to true.
+ * \return The state this is currently in. If the TTL expires, it enters
+ * the EXPIRED state by itself and passes no addresses. It may be
+ * IN_PROGRESS and still return some addresses (when one address family
+ * arrived and is is returned, but the other is still on the way).
+ * \todo Should we sort out unreachable addresses as well?
+ */
+ Fetchable::State getAddresses(AddressVector& addresses,
+ AddressFamily family = ANY_OK, bool expired_ok = false);
+
+ /// \brief Return Address that corresponding to the index
+ ///
+ /// \param index The address index in the address vector
+ /// \param family The address family, V4_ONLY or V6_ONLY
+ asiolink::IOAddress getAddressAtIndex(size_t index,
+ AddressFamily family) const;
+
+ /// \brief Update RTT
+ ///
+ /// Updates the RTT for a particular address
+ ///
+ /// \param address Address to update
+ /// \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
+ ///
+ /// Shouldn't probably be used directly. Use corresponding
+ /// NameserverAddress.
+ /// \param rtt Round-Trip Time
+ /// \param index The address's index in address vector
+ /// \param family The address family, V4_ONLY or V6_ONLY
+ void updateAddressRTTAtIndex(uint32_t rtt, size_t index,
+ AddressFamily family);
+ /**
+ * \short Update RTT of an address.
+ *
+ * This is similar to updateAddressRTTAtIndex, but you pass the address,
+ * not it's index. Passing the index might be unsafe, because the position
+ * of the address or the cound of addresses may change in time.
+ *
+ * \param rtt Round-Trip Time
+ * \param address The address whose RTT should be updated.
+ * \param family The address family, V4_ONLY or V6_ONLY
+ */
+ void updateAddressRTT(uint32_t rtt, const asiolink::IOAddress& address,
+ AddressFamily family);
+
+ /// \brief Set Address Unreachable
+ ///
+ /// Sets the specified address to be unreachable
+ ///
+ /// \param address Address to update
+ void setAddressUnreachable(const asiolink::IOAddress& address);
+
+ /// \return Owner Name of RRset
+ std::string getName() const {
+ return name_;
+ }
+
+ /// \return Class of RRset
+ const isc::dns::RRClass& getClass() const {
+ return classCode_;
+ }
+
+ /// \return Hash Key of the Nameserver
+ virtual HashKey hashKey() const {
+ return HashKey(name_, classCode_);
+ }
+
+ /// \return Hash Key of the Nameserver
+
+ /// \return Expiration Time of Data
+ ///
+ /// Returns the expiration time of addresses for this nameserver. For
+ /// simplicity, this quantity is calculated as the minimum expiration time
+ /// of the A and AAAA address records.
+ time_t getExpiration() const {
+ return expiration_;
+ }
+
+ /// \name Obtaining the IP addresses from resolver
+ //@{
+ /// \short A callback that some information here arrived (or are unavailable).
+ struct Callback {
+ virtual void operator()(boost::shared_ptr<NameserverEntry> self) = 0;
+ /// \short Virtual destructor, so descendants are properly cleaned up
+ virtual ~ Callback() {}
+ };
+
+ /**
+ * \short Asks the resolver for IP address (or addresses).
+ *
+ * Adds a callback for given zone when they are ready or the information
+ * is found unreachable.
+ *
+ * If it is not in NOT_ASKED or EXPIRED state, it does not ask the for the
+ * IP address again, it just inserts the callback. It is up to the caller
+ * not to insert one callback multiple times.
+ *
+ * The callback might be called directly from this function.
+ *
+ * \param resolver Who to ask.
+ * \param callback The callback.
+ * \param family Which addresses are interesting to the caller. This does
+ * not change which adresses are requested, but the callback might
+ * be executed when at last one requested type is available (eg. not
+ * waiting for the other one).
+ * \return The state the entry is currently in. It can return UNREACHABLE
+ * even when there are addresses, if there are no addresses for this
+ * family.
+ */
+ void askIP(isc::resolve::ResolverInterface* resolver,
+ boost::shared_ptr<Callback> callback, AddressFamily family);
+ //@}
+
+private:
+ mutable isc::locks::recursive_mutex mutex_;///< Mutex protecting this object
+ std::string name_; ///< Canonical name of the nameserver
+ isc::dns::RRClass classCode_; ///< Class of the nameserver
+ /**
+ * \short Address lists.
+ *
+ * Only V4_ONLY and V6_ONLY is used, therefore we use the nearest larger
+ * value as the size of the array.
+ *
+ * previous_addresses is kept until the data arrive again on re-fetch and
+ * is used to pick up the RTTs from there.
+ */
+ std::vector<AddressEntry> addresses_[ANY_OK], previous_addresses_[ANY_OK];
+ time_t expiration_; ///< Summary expiration time. 0 = unset
+ // Do we have some addresses already? Do we expect some to come?
+ // These are set after asking for IP, if NOT_ASKED, they are uninitialized
+ bool has_address_[ADDR_REQ_MAX], expect_address_[ADDR_REQ_MAX];
+ // Callbacks from resolver
+ class ResolverCallback;
+ friend class ResolverCallback;
+ // Callbacks inserted into this object
+ typedef std::pair<AddressFamily, boost::shared_ptr<Callback> >
+ CallbackPair;
+ std::vector<CallbackPair> callbacks_;
+ /// \short Private version that does the actual asking of one address type
+ ///
+ /// Call unlocked.
+ void askIP(isc::resolve::ResolverInterface* resolver,
+ const isc::dns::RRType&, AddressFamily);
+};
+
+} // namespace dns
+} // namespace isc
+
+#endif // __NAMESERVER_ENTRY_H
diff --git a/src/lib/nsas/nsas_entry.h b/src/lib/nsas/nsas_entry.h
new file mode 100644
index 0000000..f739e8d
--- /dev/null
+++ b/src/lib/nsas/nsas_entry.h
@@ -0,0 +1,138 @@
+// 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 __NSAS_ENTRY_H
+#define __NSAS_ENTRY_H
+
+#include <boost/enable_shared_from_this.hpp>
+#include <iostream>
+
+#include <exceptions/exceptions.h>
+
+#include "hash_key.h"
+#include "hash_table.h"
+#include "lru_list.h"
+
+namespace isc {
+namespace nsas {
+
+/// \brief Invalid Iterator
+///
+/// Thrown if an attempt was made to access the iterator - the pointer into
+/// the LRU list where this element is located - when it is marked as invalid.
+class InvalidLruIterator : public isc::Exception {
+public:
+ InvalidLruIterator(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ {}
+};
+
+/// \brief Element of NSAS Internal Lists
+///
+/// This defines an element of the NSAS lists. All elements stored in these
+/// lists *MUST* be derived from this object.
+///
+/// The class provides two properties:
+///
+/// # The method hashKey(), which returns a hash key associated with the
+/// object.
+/// # Storage for a pointer into the LRU list, used to quickly locate the
+/// element when it is being "touched".
+///
+/// Although it would be possible to require classes stored in the list
+/// to have particular methods (and so eliminate the inheritance), this
+/// would require the implementor to know something about the list and to
+/// provide the appropriate logic.
+///
+/// Unfortunately, using a base class does not simplify the definition of
+/// the list classes (by allowing the list to be defined as a list
+/// of base class objects), as the lists are a list of shared pointers to
+/// objects, not a list of pointers to object. Arguments are shared
+/// pointers, but a shared pointer to a base class is not a subclass of a
+/// shared pointer to a derived class. For this reason, the type of element
+/// being stored is a template parameter.
+///
+/// This class is inherited from boost::enable_shared_from_this class
+/// So within a member function a shared_ptr to current object can be obtained
+template <typename T>
+class NsasEntry : public boost::enable_shared_from_this <T> {
+public:
+
+ /// \brief Default Constructor
+ ///
+ /// Ensures that the handle into the LRU list is invalid.
+ NsasEntry() : valid_(false)
+ {}
+
+ /// \brief Virtual Destructor
+ virtual ~NsasEntry()
+ {}
+
+ /// Copy constructor and assignment operator OK for this class
+
+ /// \brief Hash Key
+ ///
+ /// Returns the hash key for this element.
+ ///
+ /// TODO: Consider returning a reference to an internal object, for speed
+ virtual HashKey hashKey() const = 0;
+
+ /// \brief Sets the iterator of the object
+ ///
+ /// Sets the iterator of an object and, as a side effect, marks it as valid.
+ ///
+ /// \param iterator Iterator of this element in the list
+ virtual void setLruIterator(typename LruList<T>::iterator iterator) {
+ iterator_ = iterator;
+ valid_ = true;
+ }
+
+ /// \brief Return Iterator
+ ///
+ /// \return iterator Iterator of this element in the list.
+ ///
+ /// \exception InvalidLruIterator Thrown if the iterator is not valid.
+ virtual typename LruList<T>::iterator getLruIterator() const {
+ if (! valid_) {
+ isc_throw(InvalidLruIterator,
+ "pointer of element into LRU list was not valid");
+ }
+ return iterator_;
+ }
+
+ /// \brief Iterator Valid
+ ///
+ /// \return true if the stored iterator is valid.
+ virtual bool iteratorValid() const {
+ return valid_;
+ }
+
+ /// \brief Invalidate Iterator
+ ///
+ /// Marks the iterator as invalid; it can oly be set valid again by a call
+ /// to setLruIterator.
+ virtual void invalidateIterator() {
+ valid_ = false;
+ }
+
+private:
+ typename LruList<T>::iterator iterator_; ///< Handle into the LRU List
+ bool valid_; ///< true if handle is valid
+};
+
+} // namespace nsas
+} // namespace isc
+
+
+#endif // __NSAS_ENTRY_H
diff --git a/src/lib/nsas/nsas_entry_compare.h b/src/lib/nsas/nsas_entry_compare.h
new file mode 100644
index 0000000..9e9ba7d
--- /dev/null
+++ b/src/lib/nsas/nsas_entry_compare.h
@@ -0,0 +1,53 @@
+// 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 __NSAS_ENTRY_COMPARE_H
+#define __NSAS_ENTRY_COMPARE_H
+
+#include "hash_key.h"
+#include "hash_table.h"
+
+namespace isc {
+namespace nsas {
+
+/// \brief Hash Table Comparison Object
+///
+/// The HashTable class requires a comparison object that checks if an object
+/// matches a hash key. This check can be generalised for objects derived from
+/// NsasEntry, as they all have the hashKey() method; this class takes
+/// advantage of that.
+template <typename T>
+class NsasEntryCompare : public HashTableCompare<T> {
+public:
+
+ // The default constructor is OK for this class.
+
+ /// \brief Comparison Function
+ ///
+ /// Checks the hash key given against the hash key provided by the NSAS
+ /// element.
+ ///
+ /// \param object Pointer to the object
+ /// \param key Hash key to compare against.
+ ///
+ /// \return true if the object matches the key.
+ virtual bool operator()(T* object, const HashKey& key) const {
+ return (object->hashKey() == key);
+ }
+};
+
+} // namespace nsas
+} // namespace isc
+
+#endif // __NSAS_ENTRY_COMPARE_H
diff --git a/src/lib/nsas/nsas_types.h b/src/lib/nsas/nsas_types.h
new file mode 100644
index 0000000..940cc3e
--- /dev/null
+++ b/src/lib/nsas/nsas_types.h
@@ -0,0 +1,47 @@
+// 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 __NSAS_TYPES_H
+#define __NSAS_TYPES_H
+
+/// \file nsas_types.h
+/// \brief Nameserver Address Store Types
+///
+/// Defines a set of types used within the Network Address Store.
+
+namespace isc {
+namespace nsas {
+
+/**
+ * \brief Address requested
+ *
+ * The order is significant, it is used as array indices and sometime only
+ * the first two are used.
+ */
+enum AddressFamily {
+ /// \short Interested only in IPv4 address
+ V4_ONLY,
+ /// \short Interested only in IPv6 address
+ V6_ONLY,
+ /// \short Any address is good
+ ANY_OK,
+ /// \short Bumper value, does not mean anything, it just represents the
+ /// max value
+ ADDR_REQ_MAX
+};
+
+}
+}
+
+#endif // __NSAS_TYPES_H
diff --git a/src/lib/nsas/random_number_generator.h b/src/lib/nsas/random_number_generator.h
new file mode 100644
index 0000000..8884d0e
--- /dev/null
+++ b/src/lib/nsas/random_number_generator.h
@@ -0,0 +1,207 @@
+// 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 __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>
+#include <boost/random/variate_generator.hpp>
+
+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]
+class UniformRandomIntegerGenerator{
+public:
+ /// \brief Constructor
+ ///
+ /// \param min The minimum number in the range
+ /// \param max The maximum number in the range
+ UniformRandomIntegerGenerator(int min, int max):
+ 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));
+ }
+
+ /// \brief Generate uniformly distributed integer
+ int operator()() { return generator_(); }
+private:
+ /// Hide default and copy constructor
+ UniformRandomIntegerGenerator();///< Default constructor
+ UniformRandomIntegerGenerator(const UniformRandomIntegerGenerator&); ///< Copy constructor
+
+ int min_; ///< The minimum integer that can generate
+ int max_; ///< The maximum integer that can generate
+ boost::uniform_int<> dist_; ///< Distribute uniformly.
+ boost::mt19937 rng_; ///< Mersenne Twister: A 623-dimensionally equidistributed uniform pseudo-random number generator
+ boost::variate_generator<boost::mt19937&, boost::uniform_int<> > generator_; ///< Uniform generator
+};
+
+/// \brief Weighted random integer generator
+///
+/// Generate random integers according different probabilities
+class WeightedRandomIntegerGenerator {
+public:
+ /// \brief Constructor
+ ///
+ /// \param probabilities The probabies for all the integers, the probability must be
+ /// between 0 and 1.0, the sum of probabilities must be equal to 1.
+ /// For example, if the probabilities contains the following values:
+ /// 0.5 0.3 0.2, the 1st integer will be generated more frequently than the
+ /// other integers and the probability is proportional to its value.
+ /// \param min The minimum integer that generated, other integers will be
+ /// min, min + 1, ..., min + probabilities.size() - 1
+ WeightedRandomIntegerGenerator(const std::vector<double>& probabilities,
+ size_t min = 0):
+ dist_(0, 1.0), uniform_real_gen_(rng_, dist_), min_(min)
+ {
+ // 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_));
+ // Init with the current time
+ rng_.seed(time(NULL));
+ }
+
+ /// \brief Default constructor
+ ///
+ WeightedRandomIntegerGenerator():
+ dist_(0, 1.0), uniform_real_gen_(rng_, dist_), min_(0)
+ {
+ }
+
+ /// \brief Reset the probabilities
+ ///
+ /// Change the weights of each integers
+ /// \param probabilities The probabies for all the integers
+ /// \param min The minimum integer that generated
+ void reset(const std::vector<double>& probabilities, size_t min = 0)
+ {
+ // The probabilities must be valid.
+ assert(areProbabilitiesValid(probabilities));
+
+ // Reset the cumulative sum
+ cumulative_.clear();
+
+ // Calculate the partial sum of probabilities
+ std::partial_sum(probabilities.begin(), probabilities.end(),
+ std::back_inserter(cumulative_));
+
+ // Reset the minimum integer
+ min_ = min;
+ }
+
+ /// \brief Generate weighted random integer
+ size_t operator()()
+ {
+ return std::lower_bound(cumulative_.begin(), cumulative_.end(), uniform_real_gen_())
+ - cumulative_.begin() + min_;
+ }
+
+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 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) {
+ isc_throw(InvalidProbValue,
+ "probability must be in the range 0..1");
+ }
+
+ sum += *it;
+ }
+
+ double epsilon = 0.0001;
+ // The sum must be equal to 1
+ if (std::fabs(sum - 1.0) >= epsilon) {
+ isc_throw(SumNotOne, "Sum of probabilities is not equal to 1");
+ }
+
+ return true;
+ }
+
+ 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
+ // compiler could not handle it being anywhere else (don't know why)
+ typedef boost::variate_generator<boost::mt19937&, boost::uniform_real<> > UniformRealGenerator;
+ UniformRealGenerator uniform_real_gen_; ///< Uniformly distributed random real numbers generator
+
+ size_t min_; ///< The minimum integer that will be generated
+};
+
+} // namespace dns
+} // namespace isc
+
+
+#endif//__NSAS_RANDOM_NUMBER_GENERATOR_H
diff --git a/src/lib/nsas/tests/Makefile.am b/src/lib/nsas/tests/Makefile.am
new file mode 100644
index 0000000..9d9e61c
--- /dev/null
+++ b/src/lib/nsas/tests/Makefile.am
@@ -0,0 +1,61 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/nsas -I$(top_builddir)/src/lib/nsas
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+AM_LDFLAGS = $(PTHREAD_LDFLAGS)
+if USE_STATIC_LINK
+AM_LDFLAGS += -static
+endif
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_CLANGPP
+# see ../Makefile.am
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += address_entry_unittest.cc
+run_unittests_SOURCES += hash_deleter_unittest.cc
+run_unittests_SOURCES += hash_key_unittest.cc
+run_unittests_SOURCES += hash_table_unittest.cc
+run_unittests_SOURCES += hash_unittest.cc
+run_unittests_SOURCES += lru_list_unittest.cc
+run_unittests_SOURCES += nameserver_address_unittest.cc
+run_unittests_SOURCES += nameserver_address_store_unittest.cc
+run_unittests_SOURCES += nameserver_entry_unittest.cc
+run_unittests_SOURCES += nsas_entry_compare_unittest.cc
+run_unittests_SOURCES += nsas_test.h
+run_unittests_SOURCES += zone_entry_unittest.cc
+run_unittests_SOURCES += fetchable_unittest.cc
+run_unittests_SOURCES += random_number_generator_unittest.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD = $(GTEST_LDADD)
+
+# NOTE: we may have to clean up this hack later (see the note in configure.ac)
+if NEED_LIBBOOST_THREAD
+run_unittests_LDADD += -lboost_thread
+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
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/nsas/tests/address_entry_unittest.cc b/src/lib/nsas/tests/address_entry_unittest.cc
new file mode 100644
index 0000000..02fef51
--- /dev/null
+++ b/src/lib/nsas/tests/address_entry_unittest.cc
@@ -0,0 +1,116 @@
+// 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 <config.h>
+
+#include <limits.h>
+#include <gtest/gtest.h>
+
+// Need to define the following macro to get UINT32_MAX
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS
+#endif
+#include <stdint.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::");
+static std::string V6B_TEXT("1984:1985::1986:1987");
+
+using namespace asiolink;
+using namespace std;
+using namespace isc::nsas;
+
+/// \brief Test Fixture Class
+///
+/// Constructs four IOAddress objects, two V4 addresses and two V6 addresses.
+/// They are initialised in the constructor owing to the restrictions of
+/// the IOAddress class.
+
+class AddressEntryTest : public ::testing::Test {
+protected:
+ AddressEntryTest() :
+ v4a_(V4A_TEXT), v4b_(V4B_TEXT),
+ v6a_(V6A_TEXT), v6b_(V6B_TEXT)
+ {}
+
+ IOAddress v4a_; ///< First V4 address
+ IOAddress v4b_; ///< Second V4 address
+ IOAddress v6a_; ///< First V6 address
+ IOAddress v6b_; ///< Second V6 address
+};
+
+/// Tests of the various constructors
+TEST_F(AddressEntryTest, Constructor) {
+
+ // Basic constructor with V4 address
+ AddressEntry alpha(v4a_);
+ EXPECT_EQ(V4A_TEXT, alpha.getAddress().toText());
+ EXPECT_EQ(0, alpha.getRTT());
+
+ // General constructor with V4 address
+ AddressEntry beta(v4b_, 42);
+ EXPECT_EQ(V4B_TEXT, beta.getAddress().toText());
+ EXPECT_EQ(42, beta.getRTT());
+
+ // Basic constructor with V6 address
+ AddressEntry gamma(v6a_);
+ EXPECT_EQ(V6A_TEXT, gamma.getAddress().toText());
+ EXPECT_EQ(0, gamma.getRTT());
+
+ // General constructor with V6 address
+ AddressEntry delta(v6b_, 75);
+ EXPECT_EQ(V6B_TEXT, delta.getAddress().toText());
+ EXPECT_EQ(75, delta.getRTT());
+}
+
+/// Setting and getting the round-trip time. Also includes unreachable
+/// checks.
+TEST_F(AddressEntryTest, RTT) {
+
+ AddressEntry alpha(v4a_);
+ EXPECT_EQ(0, alpha.getRTT());
+
+ // Check set and get of RTT
+ long rtt = 1276;
+ alpha.setRTT(rtt);
+ EXPECT_EQ(rtt, alpha.getRTT());
+
+ // Check unreachability
+ alpha.setUnreachable();
+ EXPECT_TRUE(alpha.isUnreachable());
+
+ alpha.setRTT(27);
+ EXPECT_FALSE(alpha.isUnreachable());
+
+ // ... and check the implementation of unreachability
+ alpha.setUnreachable();
+ EXPECT_EQ(AddressEntry::UNREACHABLE, alpha.getRTT());
+}
+
+/// Checking the address type.
+TEST_F(AddressEntryTest, AddressType) {
+
+ AddressEntry alpha(v4a_);
+ EXPECT_TRUE(alpha.isV4());
+ EXPECT_FALSE(alpha.isV6());
+
+ AddressEntry beta(v6a_);
+ EXPECT_FALSE(beta.isV4());
+ EXPECT_TRUE(beta.isV6());
+}
diff --git a/src/lib/nsas/tests/fetchable_unittest.cc b/src/lib/nsas/tests/fetchable_unittest.cc
new file mode 100644
index 0000000..4e9f3b4
--- /dev/null
+++ b/src/lib/nsas/tests/fetchable_unittest.cc
@@ -0,0 +1,34 @@
+// 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 <config.h>
+
+#include "../fetchable.h"
+
+#include <gtest/gtest.h>
+
+using namespace isc::nsas;
+
+namespace {
+
+TEST(Fetchable, accessMethods) {
+ Fetchable f;
+ EXPECT_EQ(Fetchable::NOT_ASKED, f.getState());
+ f.setState(Fetchable::IN_PROGRESS);
+ EXPECT_EQ(Fetchable::IN_PROGRESS, f.getState());
+ Fetchable funr(Fetchable::UNREACHABLE);
+ EXPECT_EQ(Fetchable::UNREACHABLE, funr.getState());
+}
+
+}
diff --git a/src/lib/nsas/tests/hash_deleter_unittest.cc b/src/lib/nsas/tests/hash_deleter_unittest.cc
new file mode 100644
index 0000000..97fecbe
--- /dev/null
+++ b/src/lib/nsas/tests/hash_deleter_unittest.cc
@@ -0,0 +1,116 @@
+// 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 <config.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+#include <boost/lexical_cast.hpp>
+
+#include <dns/rrclass.h>
+
+#include "../nsas_entry.h"
+#include "../hash_table.h"
+#include "../hash_key.h"
+#include "../lru_list.h"
+#include "../hash_deleter.h"
+
+#include "nsas_test.h"
+#include "../nsas_entry_compare.h"
+
+using namespace std;
+using namespace isc::dns;
+
+namespace isc {
+namespace nsas {
+
+
+/// \brief Text Fixture Class
+class HashDeleterTest : public ::testing::Test {
+protected:
+ HashDeleterTest() :
+ entry1_(new TestEntry("alpha", RRClass::IN())),
+ entry2_(new TestEntry("beta", RRClass::CH())),
+ entry3_(new TestEntry("gamma", RRClass::HS())),
+ entry4_(new TestEntry("delta", RRClass::IN())),
+ entry5_(new TestEntry("epsilon", RRClass::CH())),
+ entry6_(new TestEntry("zeta", RRClass::HS())),
+ entry7_(new TestEntry("eta", RRClass::IN())),
+ hash_table_(new NsasEntryCompare<TestEntry>()),
+ lru_list_(3, new HashDeleter<TestEntry>(hash_table_))
+ {}
+
+
+ boost::shared_ptr<TestEntry> entry1_;
+ boost::shared_ptr<TestEntry> entry2_;
+ boost::shared_ptr<TestEntry> entry3_;
+ boost::shared_ptr<TestEntry> entry4_;
+ boost::shared_ptr<TestEntry> entry5_;
+ boost::shared_ptr<TestEntry> entry6_;
+ boost::shared_ptr<TestEntry> entry7_;
+
+ HashTable<TestEntry> hash_table_; ///< Hash table being tested
+ LruList<TestEntry> lru_list_; ///< Associated LRU list
+};
+
+
+// Basic test. The test of the constructor etc. have been done in the test
+// fixture class.
+TEST_F(HashDeleterTest, Constructor) {
+
+ // Add entry1 to both the hash table and the LRU list
+ EXPECT_EQ(1, entry1_.use_count());
+ hash_table_.add(entry1_, entry1_->hashKey());
+ EXPECT_EQ(2, entry1_.use_count());
+ lru_list_.add(entry1_);
+ EXPECT_EQ(3, entry1_.use_count());
+
+ // Add entry 2.
+ EXPECT_EQ(1, entry2_.use_count());
+ hash_table_.add(entry2_, entry2_->hashKey());
+ EXPECT_EQ(2, entry2_.use_count());
+ lru_list_.add(entry2_);
+ EXPECT_EQ(3, entry2_.use_count());
+
+ // Add entry 3.
+ EXPECT_EQ(1, entry3_.use_count());
+ hash_table_.add(entry3_, entry3_->hashKey());
+ EXPECT_EQ(2, entry3_.use_count());
+ lru_list_.add(entry3_);
+ EXPECT_EQ(3, entry3_.use_count());
+
+ // Adding entry 4 should drop entry 1 from the list and from the
+ // associated hash table.
+
+ // Add entry 4.
+ EXPECT_EQ(1, entry4_.use_count());
+ hash_table_.add(entry4_, entry4_->hashKey());
+ EXPECT_EQ(2, entry4_.use_count());
+ lru_list_.add(entry4_);
+ EXPECT_EQ(3, entry4_.use_count());
+
+ // Entry 1 should only be referred to by the text fixture, being removed
+ // from both the LRU list and the hash table.
+ EXPECT_EQ(1, entry1_.use_count());
+
+ // ... and check that is does not exist in the table.
+ boost::shared_ptr<TestEntry> x = hash_table_.get(entry1_->hashKey());
+ EXPECT_FALSE(x);
+}
+
+} // namespace nsas
+} // namespace isc
diff --git a/src/lib/nsas/tests/hash_key_unittest.cc b/src/lib/nsas/tests/hash_key_unittest.cc
new file mode 100644
index 0000000..efa5fb2
--- /dev/null
+++ b/src/lib/nsas/tests/hash_key_unittest.cc
@@ -0,0 +1,86 @@
+// 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 <config.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+#include <boost/lexical_cast.hpp>
+
+#include "../hash_key.h"
+#include <dns/rrclass.h>
+
+using namespace std;
+using namespace isc::dns;
+
+namespace isc {
+namespace nsas {
+
+/// \brief Test Fixture Class
+class HashKeyTest : public ::testing::Test {
+};
+
+
+// Test of the constructor
+TEST_F(HashKeyTest, Constructor) {
+
+ // Basic constructor
+ string test1("ABCDEF");
+ HashKey key1(test1.c_str(), test1.size(), RRClass::IN());
+ EXPECT_EQ(key1.key, test1.c_str());
+ EXPECT_EQ(key1.keylen, test1.size());
+ EXPECT_EQ(key1.class_code, RRClass::IN());
+
+ // String constructor
+ string test2("uvwxyz");
+ HashKey key2(test2, RRClass::CH());
+ EXPECT_EQ(key2.key, test2.c_str());
+ EXPECT_EQ(key2.keylen, test2.size());
+ EXPECT_EQ(key2.class_code, RRClass::CH());
+}
+
+// Equality check
+TEST_F(HashKeyTest, Equality) {
+ string test1("abcdef123"); // Simple string
+ string test2("abcdef123"); // Same key, different object
+ string test3("AbCdEf123"); // Same key, different case (unequal)
+ string test4("ABCDE123"); // Different key (almost same)
+ string test5("uvwxyz987"); // Different key
+
+ EXPECT_TRUE(HashKey(test1, RRClass::IN()) == HashKey(test1,
+ RRClass::IN())); // Same key and class
+ EXPECT_FALSE(HashKey(test1, RRClass::IN()) == HashKey(test1,
+ RRClass::CH())); // Different class
+
+ EXPECT_TRUE(HashKey(test1, RRClass::CH()) == HashKey(test2,
+ RRClass::CH())); // Same value key/class
+ EXPECT_FALSE(HashKey(test1, RRClass::CH()) == HashKey(test2,
+ RRClass::IN()));
+
+ EXPECT_TRUE(HashKey(test1, RRClass::HS()) == HashKey(test3,
+ RRClass::HS())); // Same key
+ EXPECT_FALSE(HashKey(test1, RRClass::HS()) == HashKey(test3,
+ RRClass::IN()));
+
+ EXPECT_FALSE(HashKey(test1, RRClass::IN()) == HashKey(test4,
+ RRClass::IN()));
+ EXPECT_FALSE(HashKey(test1, RRClass::IN()) == HashKey(test5,
+ RRClass::IN()));
+}
+
+} // namespace nsas
+} // namespace isc
diff --git a/src/lib/nsas/tests/hash_table_unittest.cc b/src/lib/nsas/tests/hash_table_unittest.cc
new file mode 100644
index 0000000..7ba25b5
--- /dev/null
+++ b/src/lib/nsas/tests/hash_table_unittest.cc
@@ -0,0 +1,256 @@
+// 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 <config.h>
+
+#include <gtest/gtest.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/bind.hpp>
+
+#include <string.h>
+#include <iostream>
+
+#include <dns/rrclass.h>
+
+#include "../hash_table.h"
+#include "../hash_key.h"
+
+#include "../nsas_entry_compare.h"
+#include "nsas_test.h"
+
+using namespace std;
+using boost::shared_ptr;
+using namespace isc::dns;
+
+namespace isc {
+namespace nsas {
+
+/// \brief Text Fixture Class
+///
+/// Many of the tests are based on checking reference counts. In all tests,
+/// objects named dummyN_ have a reference count of 1 because they are a member
+/// of this class which has been instantiated for the test. The reference count
+/// is increased as they are added to the hash table for testing and decreased
+/// as they are removed.
+class HashTableTest : public ::testing::Test {
+protected:
+
+ // Constructor - initialize the objects
+ HashTableTest() :
+ table_(new NsasEntryCompare<TestEntry>()),
+ dummy1_(new TestEntry("test", RRClass::IN())),
+ dummy2_(new TestEntry("test", RRClass::IN())),
+ dummy3_(new TestEntry("Something_Else", RRClass::IN())),
+ dummy4_(new TestEntry("test", RRClass::CH()))
+ {}
+
+ // Members.
+ HashTable<TestEntry> table_;
+ boost::shared_ptr<TestEntry> dummy1_;
+ boost::shared_ptr<TestEntry> dummy2_;
+ boost::shared_ptr<TestEntry> dummy3_;
+ boost::shared_ptr<TestEntry> dummy4_;
+};
+
+
+// Test of the constructor
+TEST_F(HashTableTest, Constructor) {
+
+ // Default constructor
+ HashTable<TestEntry> table1(new NsasEntryCompare<TestEntry>());
+ EXPECT_EQ(HASHTABLE_DEFAULT_SIZE, table1.tableSize());
+
+ // Non default constructor
+ EXPECT_NE(42, HASHTABLE_DEFAULT_SIZE);
+ HashTable<TestEntry> table2(new NsasEntryCompare<TestEntry>(), 42);
+ EXPECT_EQ(42, table2.tableSize());
+}
+
+
+// Basic test of addition
+TEST_F(HashTableTest, AddTest) {
+
+ // Using two objects with the same name and class,
+ EXPECT_EQ(dummy1_->getName(), dummy2_->getName());
+ EXPECT_EQ(dummy1_->getClass(), dummy2_->getClass());
+ EXPECT_EQ(1, dummy1_.use_count());
+ EXPECT_EQ(1, dummy2_.use_count());
+
+ // Add first one to the hash table_
+ bool result = table_.add(dummy1_, dummy1_->hashKey());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(2, dummy1_.use_count());
+ EXPECT_EQ(1, dummy2_.use_count());
+
+ // Attempt to add the second object with the same name and class fails.
+ result = table_.add(dummy2_, dummy2_->hashKey());
+ EXPECT_FALSE(result);
+ EXPECT_EQ(2, dummy1_.use_count());
+ EXPECT_EQ(1, dummy2_.use_count());
+
+ // Replacing an entry should work though
+ result = table_.add(dummy2_, dummy2_->hashKey(), true);
+ EXPECT_TRUE(result);
+ EXPECT_EQ(1, dummy1_.use_count());
+ EXPECT_EQ(2, dummy2_.use_count());
+}
+
+// Test the remove functionality
+TEST_F(HashTableTest, RemoveTest) {
+
+ // Using two objects with different names but the same class
+ EXPECT_NE(dummy1_->getName(), dummy3_->getName());
+ EXPECT_EQ(dummy1_->getClass(), dummy3_->getClass());
+ EXPECT_EQ(1, dummy1_.use_count());
+ EXPECT_EQ(1, dummy3_.use_count());
+
+ // Add first one to the hash table_
+ bool result = table_.add(dummy1_, dummy1_->hashKey());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(2, dummy1_.use_count());
+ EXPECT_EQ(1, dummy3_.use_count());
+
+ // Now remove it.
+ result = table_.remove(dummy1_->hashKey());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(1, dummy1_.use_count());
+ EXPECT_EQ(1, dummy3_.use_count());
+
+ // Attempt to remove it again.
+ result = table_.remove(dummy1_->hashKey());
+ EXPECT_FALSE(result);
+ EXPECT_EQ(1, dummy1_.use_count());
+ EXPECT_EQ(1, dummy3_.use_count());
+
+ // Add both entries to table_, then remove one (checks that it will
+ // remove the correct one).
+ result = table_.add(dummy1_, dummy1_->hashKey());
+ EXPECT_TRUE(result);
+ result = table_.add(dummy3_, dummy3_->hashKey());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(2, dummy1_.use_count());
+ EXPECT_EQ(2, dummy3_.use_count());
+
+ result = table_.remove(dummy1_->hashKey());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(1, dummy1_.use_count());
+ EXPECT_EQ(2, dummy3_.use_count());
+}
+
+// Test the "get" functionality
+TEST_F(HashTableTest, GetTest) {
+
+ // Using two objects with different names but the same class
+ EXPECT_NE(dummy1_->getName(), dummy3_->getName());
+ EXPECT_EQ(dummy1_->getClass(), dummy3_->getClass());
+ EXPECT_EQ(1, dummy1_.use_count());
+ EXPECT_EQ(1, dummy3_.use_count());
+
+ // Add both to the hash table
+ bool result = table_.add(dummy1_, dummy1_->hashKey());
+ EXPECT_TRUE(result);
+ result = table_.add(dummy3_, dummy3_->hashKey());
+ EXPECT_TRUE(result);
+
+ // Lookup the first
+ boost::shared_ptr<TestEntry> value = table_.get(dummy1_->hashKey());
+ EXPECT_EQ(value.get(), dummy1_.get());
+
+ // ... and the second
+ value = table_.get(dummy3_->hashKey());
+ EXPECT_EQ(value.get(), dummy3_.get());
+
+ // Remove the first
+ result = table_.remove(dummy1_->hashKey());
+ EXPECT_TRUE(result);
+
+ // ... and a lookup should return empty
+ value = table_.get(dummy1_->hashKey());
+ EXPECT_TRUE(value.get() == NULL);
+}
+
+shared_ptr<TestEntry>
+pass(shared_ptr<TestEntry> value) {
+ return (value);
+}
+
+TEST_F(HashTableTest, GetOrAddTest) {
+ // Add one entry
+ EXPECT_TRUE(table_.add(dummy1_, dummy1_->hashKey()));
+
+ // Check it looks it up
+ std::pair<bool, shared_ptr<TestEntry> > result = table_.getOrAdd(
+ dummy1_->hashKey(), boost::bind(pass, dummy3_));
+ EXPECT_FALSE(result.first);
+ EXPECT_EQ(dummy1_.get(), result.second.get());
+
+ // Check it says it adds the value
+ result = table_.getOrAdd(dummy3_->hashKey(), boost::bind(pass, dummy3_));
+ EXPECT_TRUE(result.first);
+ EXPECT_EQ(dummy3_.get(), result.second.get());
+
+ // Check it really did add it
+ EXPECT_EQ(dummy3_.get(), table_.get(dummy3_->hashKey()).get());
+}
+
+// Test that objects with the same name and different classes are distinct.
+TEST_F(HashTableTest, ClassTest) {
+
+ // Using two objects with the same name and different classes
+ EXPECT_EQ(dummy1_->getName(), dummy4_->getName());
+ EXPECT_NE(dummy1_->getClass(), dummy4_->getClass());
+ EXPECT_EQ(1, dummy1_.use_count());
+ EXPECT_EQ(1, dummy4_.use_count());
+
+ // Add both to the hash table
+ bool result = table_.add(dummy1_, dummy1_->hashKey());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(2, dummy1_.use_count());
+
+ result = table_.add(dummy4_, dummy4_->hashKey());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(2, dummy4_.use_count());
+
+ // Lookup the first
+ boost::shared_ptr<TestEntry> value1 = table_.get(dummy1_->hashKey());
+ EXPECT_EQ(value1.get(), dummy1_.get());
+ EXPECT_EQ(3, dummy1_.use_count());
+ EXPECT_EQ(2, dummy4_.use_count());
+
+ // ... and the second
+ boost::shared_ptr<TestEntry> value4 = table_.get(dummy4_->hashKey());
+ EXPECT_EQ(value4.get(), dummy4_.get());
+ EXPECT_EQ(3, dummy1_.use_count());
+ EXPECT_EQ(3, dummy4_.use_count());
+
+ // ... and check they are different
+ EXPECT_NE(dummy1_.get(), dummy4_.get());
+
+ // Remove the first
+ result = table_.remove(dummy1_->hashKey());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(2, dummy1_.use_count());
+ EXPECT_EQ(3, dummy4_.use_count());
+
+ // ... and a lookup should return empty
+ boost::shared_ptr<TestEntry> value1a = table_.get(dummy1_->hashKey());
+ EXPECT_TRUE(value1a.get() == NULL);
+}
+
+
+
+
+
+} // namespace nsas
+} // namespace isc
diff --git a/src/lib/nsas/tests/hash_unittest.cc b/src/lib/nsas/tests/hash_unittest.cc
new file mode 100644
index 0000000..251e4b1
--- /dev/null
+++ b/src/lib/nsas/tests/hash_unittest.cc
@@ -0,0 +1,194 @@
+// 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 <config.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+#include <boost/lexical_cast.hpp>
+
+#include "../hash.h"
+
+#include "nsas_test.h"
+
+using namespace std;
+
+namespace isc {
+namespace nsas {
+
+// Utility function to count the number of unique values in a vector
+static uint32_t CountUnique(const vector<uint32_t>& input) {
+
+ // Duplicate the vector as this will be destroyed in the process.
+ vector<uint32_t> vcopy(input);
+
+ // Check to see if values are unique. Do this by sorting the values into
+ // ascending order, removing duplicates, and checking the size again.
+ //
+ // Note that "unique" only shifts elements around - it does not remove non-
+ // unique values, so it does not change the size of the vector. The call to
+ // erase removes the elements between the last unique element and the end
+ // of the vector, so shrinking the vector.
+ sort(vcopy.begin(), vcopy.end());
+ vcopy.erase(unique(vcopy.begin(), vcopy.end()), vcopy.end());
+
+ // ... and return the count of elements remaining.
+ return (vcopy.size());
+}
+
+
+/// \brief Test Fixture Class
+class HashTest : public ::testing::Test {
+};
+
+
+// Test of the constructor
+TEST_F(HashTest, Constructor) {
+
+ // Default constructor
+ Hash hash1(HASHTABLE_DEFAULT_SIZE, 250);
+ EXPECT_EQ(HASHTABLE_DEFAULT_SIZE, hash1.tableSize());
+ EXPECT_EQ(250, hash1.maxKeyLength());
+}
+
+// Test of the hash algorithm. Without duplicating the code for the algorithm
+// here, testing is a bit awkward. So the tests will check that a series of
+// names get hashed to different values. (Choosing a HASHTABLE_DEFAULT_SIZE element array should
+// give minimal overlap; we'll allow for a maximum of 2 collisions with 50
+// similar names. If there are more, perhaps the algorithm is at fault.
+
+TEST_F(HashTest, Algorithm) {
+
+ const int size = HASHTABLE_DEFAULT_SIZE; // Size of the hash table
+ Hash hash(size, 255, false);// Hashing algorithm object with seed
+ // randomisation disabled
+ string base = "alphabeta"; // Base of the names to behashed
+ vector<uint32_t> values; // Results stored here
+
+ // Generate hash values
+ for (int i = 0; i < 50; ++i) {
+ string name = base + boost::lexical_cast<string>(i);
+ uint32_t hashval = hash(HashKey(name.c_str(), name.size(),
+ RRClass(0)));
+ EXPECT_LT(hashval, size);
+ values.push_back(hashval);
+ }
+ uint32_t cursize = values.size();
+
+ // Count the unique values in the array
+ uint32_t newsize = CountUnique(values);
+
+ // We don't expect many clashes.
+ EXPECT_GE(newsize + 2, cursize);
+}
+
+// Test the case mapping function.
+
+TEST_F(HashTest, CaseMapping) {
+
+ Hash hash(HASHTABLE_DEFAULT_SIZE, 255);
+
+ // Check all unsigned characters
+ for (int i = 0; i < 255; ++i) {
+ if ((i >= 'A') && (i <= 'Z')) {
+ EXPECT_EQ(static_cast<unsigned char>(i - 'A' + 'a'),
+ hash.mapLower((unsigned char)i));
+ }
+ else {
+ EXPECT_EQ(i, hash.mapLower(i));
+ }
+ }
+}
+
+// Test the mapping of mixed-case names
+TEST_F(HashTest, MixedCase) {
+
+ std::string test1 = "example1234.co.uk.";
+ std::string test2 = "EXAmple1234.co.uk.";
+
+ Hash hash(HASHTABLE_DEFAULT_SIZE, 255, false); // Disable randomisation for testing
+
+ // Case not ignored, hashes should be different
+ uint32_t value1 = hash(HashKey(test1.c_str(), test1.size(), RRClass::IN()),
+ false);
+ uint32_t value2 = hash(HashKey(test2.c_str(), test2.size(), RRClass::IN()),
+ false);
+ EXPECT_NE(value1, value2);
+
+ // Case ignored, hashes should be the same
+ uint32_t value3 = hash(HashKey(test1.c_str(), test1.size(), RRClass::IN()),
+ true);
+ uint32_t value4 = hash(HashKey(test2.c_str(), test2.size(), RRClass::IN()),
+ true);
+ EXPECT_EQ(value3, value4);
+
+ // Check the default setting.
+ uint32_t value5 = hash(HashKey(test1.c_str(), test1.size(), RRClass::IN()));
+ uint32_t value6 = hash(HashKey(test2.c_str(), test2.size(), RRClass::IN()));
+ EXPECT_EQ(value5, value6);
+
+ // ... and just for good measure
+ EXPECT_EQ(value1, value3);
+ EXPECT_EQ(value1, value5);
+}
+
+// Test that the same name but different classes get mapped differently.
+TEST_F(HashTest, ClassCodes) {
+
+ std::string test1 = "example1234.co.uk.";
+ Hash hash(HASHTABLE_DEFAULT_SIZE, 255, false); // Disable randomisation for testing
+
+ // Just try codes in the range 0 to 9 - more than covers the allocated
+ // codes.
+ vector<uint32_t> values;
+ for (uint32_t i = 0; i < 10; ++i) {
+ values.push_back(hash(HashKey(test1.c_str(), test1.size(),
+ RRClass(i))));
+ }
+
+ // find the number of unique values in the array. Although there can
+ // be some clashes, we don't expect too many.
+ uint32_t cursize = values.size();
+
+ // Count the unique values in the array
+ uint32_t newsize = CountUnique(values);
+
+ // We don't expect many clashes.
+ EXPECT_GE(newsize + 2, cursize);
+}
+
+
+// Test that the code performs when the length of the key is excessive
+TEST_F(HashTest, Overlong) {
+
+ // String 1 is a suitable prefix, string 2 is the same prefix plus 4096
+ // repetions of the character 'x'.
+ std::string string1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
+ std::string string2 = string1 + string(4096, 'x');
+
+ Hash hash(HASHTABLE_DEFAULT_SIZE, string1.size());
+
+ // Do two hashes
+ uint32_t value1 = hash(HashKey(string1.c_str(), string1.size(),
+ RRClass(0)));
+ uint32_t value2 = hash(HashKey(string2.c_str(), string2.size(),
+ RRClass(0)));
+ EXPECT_EQ(value1, value2);
+}
+
+} // namespace nsas
+} // namespace isc
diff --git a/src/lib/nsas/tests/lru_list_unittest.cc b/src/lib/nsas/tests/lru_list_unittest.cc
new file mode 100644
index 0000000..e286826
--- /dev/null
+++ b/src/lib/nsas/tests/lru_list_unittest.cc
@@ -0,0 +1,318 @@
+// 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 <config.h>
+
+#include <iostream>
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+#include <boost/lexical_cast.hpp>
+
+#include "../nsas_entry.h"
+#include "../lru_list.h"
+
+#include "nsas_test.h"
+
+using namespace std;
+
+namespace isc {
+namespace nsas {
+
+/// \brief Dropped Functor Class
+///
+/// Functor object is called when an object is dropped from the LRU list.
+/// To prove that it has run, this function does nothing more than set the
+/// MS bit on the 16-bit class value.
+class Dropped : public LruList<TestEntry>::Dropped {
+public:
+ virtual void operator()(TestEntry* entry) const {
+ entry->setClass(RRClass(entry->getClass().getCode() | 0x8000));
+ }
+};
+
+
+/// \brief Text Fixture Class
+class LruListTest : public ::testing::Test {
+protected:
+ LruListTest() :
+ entry1_(new TestEntry("alpha", RRClass::IN())),
+ entry2_(new TestEntry("beta", RRClass::CH())),
+ entry3_(new TestEntry("gamma", RRClass::HS())),
+ entry4_(new TestEntry("delta", RRClass::IN())),
+ entry5_(new TestEntry("epsilon", RRClass::HS())),
+ entry6_(new TestEntry("zeta", RRClass::CH())),
+ entry7_(new TestEntry("eta", RRClass::IN()))
+ {}
+
+ virtual ~LruListTest()
+ {}
+
+ boost::shared_ptr<TestEntry> entry1_;
+ boost::shared_ptr<TestEntry> entry2_;
+ boost::shared_ptr<TestEntry> entry3_;
+ boost::shared_ptr<TestEntry> entry4_;
+ boost::shared_ptr<TestEntry> entry5_;
+ boost::shared_ptr<TestEntry> entry6_;
+ boost::shared_ptr<TestEntry> entry7_;
+};
+
+
+// Test of the constructor
+TEST_F(LruListTest, Constructor) {
+ LruList<TestEntry> lru(100);
+ EXPECT_EQ(100, lru.getMaxSize());
+ EXPECT_EQ(0, lru.size());
+}
+
+// Test of Get/Set the maximum number of entrys
+TEST_F(LruListTest, GetSet) {
+ LruList<TestEntry> lru(100);
+ EXPECT_EQ(100, lru.getMaxSize());
+ lru.setMaxSize(42);
+ EXPECT_EQ(42, lru.getMaxSize());
+}
+
+// Test that adding an entry really does add an entry
+TEST_F(LruListTest, Add) {
+ LruList<TestEntry> lru(100);
+ EXPECT_EQ(0, lru.size());
+
+ lru.add(entry1_);
+ EXPECT_EQ(1, lru.size());
+
+ lru.add(entry2_);
+ EXPECT_EQ(2, lru.size());
+}
+
+// Test that removing an entry really does remove it.
+TEST_F(LruListTest, Remove) {
+ LruList<TestEntry> lru(100);
+ EXPECT_EQ(0, lru.size());
+
+ EXPECT_FALSE(entry1_->iteratorValid());
+ lru.add(entry1_);
+ EXPECT_TRUE(entry1_->iteratorValid());
+ EXPECT_EQ(1, lru.size());
+
+ EXPECT_FALSE(entry2_->iteratorValid());
+ lru.add(entry2_);
+ EXPECT_TRUE(entry2_->iteratorValid());
+ EXPECT_EQ(2, lru.size());
+
+ lru.remove(entry1_);
+ EXPECT_FALSE(entry1_->iteratorValid());
+ EXPECT_EQ(1, lru.size());
+}
+
+// Check that adding a new entry to a limited size list does delete the
+// oldest entry from the list.
+TEST_F(LruListTest, SizeLimit) {
+ LruList<TestEntry> lru(3);
+ EXPECT_EQ(0, lru.size());
+
+ // Add first entry and check that the shared pointer's reference count
+ // has increased. There will be two references: one from the "entry1_"
+ // member in the test fixture class, and one from the list.
+ EXPECT_EQ(1, entry1_.use_count());
+ lru.add(entry1_);
+ EXPECT_EQ(2, entry1_.use_count());
+ EXPECT_EQ(1, lru.size());
+
+ // Same for entry 2.
+ EXPECT_EQ(1, entry2_.use_count());
+ lru.add(entry2_);
+ EXPECT_EQ(2, entry2_.use_count());
+ EXPECT_EQ(2, lru.size());
+
+ // Same for entry 3.
+ EXPECT_EQ(1, entry3_.use_count());
+ lru.add(entry3_);
+ EXPECT_EQ(2, entry3_.use_count());
+ EXPECT_EQ(3, lru.size());
+
+ // Adding entry 4 should remove entry 1 from the list. This will
+ // delete the list's shared pointer to the entry and will therefore
+ // drop the reference count back to one (from the "entry1_" member in
+ // the text fixture class).
+ EXPECT_EQ(2, entry1_.use_count());
+ EXPECT_EQ(1, entry4_.use_count());
+ lru.add(entry4_);
+ EXPECT_EQ(1, entry1_.use_count());
+ EXPECT_EQ(2, entry4_.use_count());
+ EXPECT_EQ(3, lru.size());
+
+ // Adding entry 5 should remove entry 2 from the list.
+ EXPECT_EQ(2, entry2_.use_count());
+ EXPECT_EQ(1, entry5_.use_count());
+ lru.add(entry5_);
+ EXPECT_EQ(1, entry2_.use_count());
+ EXPECT_EQ(2, entry5_.use_count());
+ EXPECT_EQ(3, lru.size());
+}
+
+// Check that "touching" an entry adds it to the back of the list.
+TEST_F(LruListTest, Touch) {
+
+ // Create the list
+ LruList<TestEntry> lru(3);
+ EXPECT_EQ(0, lru.size());
+ lru.add(entry1_);
+ lru.add(entry2_);
+ lru.add(entry3_);
+
+ // Check the reference counts of the entrys and the list size
+ EXPECT_EQ(2, entry1_.use_count());
+ EXPECT_EQ(2, entry2_.use_count());
+ EXPECT_EQ(2, entry3_.use_count());
+ EXPECT_EQ(1, entry4_.use_count());
+ EXPECT_EQ(1, entry5_.use_count());
+ EXPECT_EQ(1, entry6_.use_count());
+ EXPECT_EQ(1, entry7_.use_count());
+ EXPECT_EQ(3, lru.size());
+
+ // "Touch" the first entry
+ lru.touch(entry1_);
+
+ // Adding two more entries should not remove the touched entry.
+ lru.add(entry4_);
+ lru.add(entry5_);
+
+ // Check the status of the entrys and the list.
+ EXPECT_EQ(2, entry1_.use_count());
+ EXPECT_EQ(1, entry2_.use_count());
+ EXPECT_EQ(1, entry3_.use_count());
+ EXPECT_EQ(2, entry4_.use_count());
+ EXPECT_EQ(2, entry5_.use_count());
+ EXPECT_EQ(1, entry6_.use_count());
+ EXPECT_EQ(1, entry7_.use_count());
+ EXPECT_EQ(3, lru.size());
+
+ // Now touch the entry agin to move it to the back of the list.
+ // This checks that the iterator stored in the entry as a result of the
+ // last touch operation is valid.
+ lru.touch(entry1_);
+
+ // Check this by adding two more entrys and checking reference counts
+ // to see what is stored.
+ lru.add(entry6_);
+ lru.add(entry7_);
+
+ EXPECT_EQ(2, entry1_.use_count());
+ EXPECT_EQ(1, entry2_.use_count());
+ EXPECT_EQ(1, entry3_.use_count());
+ EXPECT_EQ(1, entry4_.use_count());
+ EXPECT_EQ(1, entry5_.use_count());
+ EXPECT_EQ(2, entry6_.use_count());
+ EXPECT_EQ(2, entry7_.use_count());
+ EXPECT_EQ(3, lru.size());
+}
+
+// Dropped functor tests: tests that the function object is called when an
+// object expires from the list.
+TEST_F(LruListTest, Dropped) {
+
+ // 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());
+
+ // Add another entry and check that the handler runs.
+ EXPECT_EQ(0, (entry1_->getClass().getCode() & 0x8000));
+ lru.add(entry4_);
+ EXPECT_NE(0, (entry1_->getClass().getCode() & 0x8000));
+
+ EXPECT_EQ(0, (entry2_->getClass().getCode() & 0x8000));
+ lru.add(entry5_);
+ EXPECT_NE(0, (entry2_->getClass().getCode() & 0x8000));
+
+ // Delete an entry and check that the handler does not run.
+ EXPECT_EQ(0, (entry3_->getClass().getCode() & 0x8000));
+ lru.remove(entry3_);
+ 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) {
+
+ // Zero size list should not allow entrys to be added
+ LruList<TestEntry> lru_1(0);
+ lru_1.add(entry1_);
+ EXPECT_EQ(0, lru_1.size());
+ EXPECT_EQ(1, entry1_.use_count());
+
+ // Removing an uninserted entry should not affect the list.
+ LruList<TestEntry> lru_2(100);
+ lru_2.add(entry1_);
+ lru_2.add(entry2_);
+ lru_2.add(entry3_);
+ EXPECT_EQ(3, lru_2.size());
+
+ lru_2.remove(entry4_);
+ EXPECT_EQ(2, entry1_.use_count());
+ EXPECT_EQ(2, entry2_.use_count());
+ EXPECT_EQ(2, entry3_.use_count());
+ EXPECT_EQ(1, entry4_.use_count());
+ EXPECT_EQ(1, entry5_.use_count());
+ EXPECT_EQ(3, lru_2.size());
+
+ // Touching an uninserted entry should not affect the list.
+ lru_2.touch(entry5_);
+ EXPECT_EQ(2, entry1_.use_count());
+ EXPECT_EQ(2, entry2_.use_count());
+ EXPECT_EQ(2, entry3_.use_count());
+ EXPECT_EQ(1, entry4_.use_count());
+ EXPECT_EQ(1, entry5_.use_count());
+ EXPECT_EQ(3, lru_2.size());
+}
+
+} // namespace nsas
+} // namespace isc
diff --git a/src/lib/nsas/tests/nameserver_address_store_unittest.cc b/src/lib/nsas/tests/nameserver_address_store_unittest.cc
new file mode 100644
index 0000000..9133daf
--- /dev/null
+++ b/src/lib/nsas/tests/nameserver_address_store_unittest.cc
@@ -0,0 +1,398 @@
+// 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 <config.h>
+
+/// \brief Test Deleter Objects
+///
+/// This file contains tests for the "deleter" classes within the nameserver
+/// address store. These act to delete zones from the zone hash table when
+/// the element reaches the top of the LRU list.
+
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+
+#include <gtest/gtest.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/foreach.hpp>
+
+#include <string.h>
+#include <cassert>
+
+#include "../nameserver_address_store.h"
+#include "../nsas_entry_compare.h"
+#include "../nameserver_entry.h"
+#include "../zone_entry.h"
+#include "../address_request_callback.h"
+#include "nsas_test.h"
+
+using namespace isc::dns;
+using namespace std;
+
+namespace isc {
+namespace nsas {
+
+
+/// \brief NSAS Store
+///
+/// This is a subclass of the NameserverAddressStore class, with methods to
+/// access the internal members to check that the deleter objects are working.
+class DerivedNsas : public NameserverAddressStore {
+public:
+ /// \brief Constructor
+ ///
+ /// \param hashsize Size of the zone hash table
+ /// \param lrusize Size of the zone hash table
+ DerivedNsas(boost::shared_ptr<TestResolver> resolver, uint32_t hashsize,
+ uint32_t lrusize) :
+ NameserverAddressStore(resolver, hashsize, lrusize),
+ resolver_(resolver)
+ {}
+
+ /// \brief Virtual Destructor
+ virtual ~DerivedNsas()
+ {}
+
+ /// \brief Add Nameserver Entry to Hash and LRU Tables
+ void AddNameserverEntry(boost::shared_ptr<NameserverEntry>& entry) {
+ HashKey h = entry->hashKey();
+ nameserver_hash_->add(entry, h);
+ nameserver_lru_->add(entry);
+ }
+
+ /// \brief Add Zone Entry to Hash and LRU Tables
+ void AddZoneEntry(boost::shared_ptr<ZoneEntry>& entry) {
+ HashKey h = entry->hashKey();
+ zone_hash_->add(entry, h);
+ zone_lru_->add(entry);
+ }
+ /**
+ * \short Just wraps the common lookup
+ *
+ * It calls the lookup and provides the authority section
+ * if it is asked for by the resolver.
+ */
+ void lookupAndAnswer(const string& name, const RRClass& class_code,
+ RRsetPtr authority,
+ boost::shared_ptr<AddressRequestCallback> callback)
+ {
+ size_t size(resolver_->requests.size());
+ NameserverAddressStore::lookup(name, class_code, callback, ANY_OK);
+ // It asked something, the only thing it can ask is the NS list
+ if (size < resolver_->requests.size()) {
+ resolver_->provideNS(size, authority);
+ // Once answered, drop the request so noone else sees it
+ resolver_->requests.erase(resolver_->requests.begin() + size);
+ } else {
+ ADD_FAILURE() << "Not asked for NS";
+ }
+ }
+private:
+ boost::shared_ptr<TestResolver> resolver_;
+};
+
+
+
+/// \brief Text Fixture Class
+class NameserverAddressStoreTest : public TestWithRdata {
+protected:
+
+ NameserverAddressStoreTest() :
+ authority_(new RRset(Name("example.net."), RRClass::IN(), RRType::NS(),
+ RRTTL(128))),
+ empty_authority_(new RRset(Name("example.net."), RRClass::IN(),
+ RRType::NS(), RRTTL(128))),
+ resolver_(new TestResolver)
+ {
+ // Constructor - initialize a set of nameserver and zone objects. For
+ // convenience, these are stored in vectors.
+ for (int i = 1; i <= 9; ++i) {
+ std::string name = "nameserver" + boost::lexical_cast<std::string>(
+ i);
+ nameservers_.push_back(boost::shared_ptr<NameserverEntry>(
+ new NameserverEntry(name, RRClass(40 + i))));
+ }
+
+ // Some zones. They will not use the tables in this test, so it can be
+ // empty
+ 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_.get(), name, RRClass(40 + i),
+ boost::shared_ptr<HashTable<NameserverEntry> >(),
+ boost::shared_ptr<LruList<NameserverEntry> >())));
+ }
+
+ // A nameserver serving data
+ authority_->addRdata(ConstRdataPtr(new rdata::generic::NS(Name(
+ "ns.example.com."))));
+
+ // This is reused because of convenience, clear it just in case
+ NSASCallback::results.clear();
+ }
+
+ // Vector of pointers to nameserver and zone entries.
+ std::vector<boost::shared_ptr<NameserverEntry> > nameservers_;
+ std::vector<boost::shared_ptr<ZoneEntry> > zones_;
+
+ RRsetPtr authority_, empty_authority_;
+
+ boost::shared_ptr<TestResolver> resolver_;
+
+ class NSASCallback : public AddressRequestCallback {
+ public:
+ typedef pair<bool, NameserverAddress> Result;
+ static vector<Result> results;
+ virtual void success(const NameserverAddress& address) {
+ results.push_back(Result(true, address));
+ }
+ virtual void unreachable() {
+ results.push_back(Result(false, NameserverAddress()));
+ }
+ };
+
+ boost::shared_ptr<AddressRequestCallback> getCallback() {
+ return (boost::shared_ptr<AddressRequestCallback>(new NSASCallback));
+ }
+};
+
+vector<NameserverAddressStoreTest::NSASCallback::Result>
+ NameserverAddressStoreTest::NSASCallback::results;
+
+
+/// \brief Remove Zone Entry from Hash Table
+///
+/// Check that when an entry reaches the top of the zone LRU list, it is removed from the
+/// hash table as well.
+TEST_F(NameserverAddressStoreTest, ZoneDeletionCheck) {
+
+ // Create a NSAS with a hash size of three and a LRU size of 9 (both zone and
+ // nameserver tables).
+ DerivedNsas nsas(resolver_, 2, 2);
+
+ // Add six entries to the tables. After addition the reference count of each element
+ // should be 3 - one for the entry in the zones_ vector, and one each for the entries
+ // in the LRU list and hash table.
+ for (int i = 1; i <= 6; ++i) {
+ EXPECT_EQ(1, zones_[i].use_count());
+ nsas.AddZoneEntry(zones_[i]);
+ EXPECT_EQ(3, zones_[i].use_count());
+ }
+
+ // Adding another entry should cause the first one to drop off the LRU list, which
+ // should also trigger the deletion of the entry from the hash table. This should
+ // reduce its use count to 1.
+ EXPECT_EQ(1, zones_[7].use_count());
+ nsas.AddZoneEntry(zones_[7]);
+ EXPECT_EQ(3, zones_[7].use_count());
+
+ EXPECT_EQ(1, zones_[1].use_count());
+}
+
+
+/// \brief Remove Entry from Hash Table
+///
+/// Check that when an entry reaches the top of the LRU list, it is removed from the
+/// hash table as well.
+TEST_F(NameserverAddressStoreTest, NameserverDeletionCheck) {
+
+ // Create a NSAS with a hash size of three and a LRU size of 9 (both zone and
+ // nameserver tables).
+ DerivedNsas nsas(resolver_, 2, 2);
+
+ // Add six entries to the tables. After addition the reference count of each element
+ // should be 3 - one for the entry in the nameservers_ vector, and one each for the entries
+ // in the LRU list and hash table.
+ for (int i = 1; i <= 6; ++i) {
+ EXPECT_EQ(1, nameservers_[i].use_count());
+ nsas.AddNameserverEntry(nameservers_[i]);
+ EXPECT_EQ(3, nameservers_[i].use_count());
+ }
+
+ // Adding another entry should cause the first one to drop off the LRU list, which
+ // should also trigger the deletion of the entry from the hash table. This should
+ // reduce its use count to 1.
+ EXPECT_EQ(1, nameservers_[7].use_count());
+ nsas.AddNameserverEntry(nameservers_[7]);
+ EXPECT_EQ(3, nameservers_[7].use_count());
+
+ EXPECT_EQ(1, nameservers_[1].use_count());
+}
+
+/// \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
+ nsas.lookupAndAnswer("example.net.", RRClass::IN(), authority_,
+ getCallback());
+ // It should ask for IP addresses for ns.example.com.
+ EXPECT_NO_THROW(resolver_->asksIPs(Name("ns.example.com."), 0, 1));
+
+ // Ask another question for the same zone
+ nsas.lookup("example.net.", RRClass::IN(), getCallback());
+ // It should ask no more questions now
+ EXPECT_EQ(2, resolver_->requests.size());
+
+ // Ask another question with different zone but the same nameserver
+ authority_->setName(Name("example.com."));
+ nsas.lookupAndAnswer("example.com.", RRClass::IN(), authority_,
+ getCallback());
+ // It still should ask nothing
+ EXPECT_EQ(2, resolver_->requests.size());
+
+ // We provide IP address of one nameserver, it should generate all the
+ // results
+ EXPECT_NO_THROW(resolver_->answer(0, Name("ns.example.com."), RRType::A(),
+ rdata::in::A("192.0.2.1")));
+ EXPECT_EQ(3, NSASCallback::results.size());
+ BOOST_FOREACH(const NSASCallback::Result& result, NSASCallback::results) {
+ EXPECT_TRUE(result.first);
+ EXPECT_EQ("192.0.2.1", result.second.getAddress().toText());
+ }
+}
+
+/// \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
+ nsas.lookupAndAnswer("example.net.", RRClass::IN(), empty_authority_,
+ getCallback());
+ // There should be no questions, because there's nothing to ask
+ EXPECT_EQ(0, resolver_->requests.size());
+ // And there should be one "unreachable" answer for the query
+ ASSERT_EQ(1, NSASCallback::results.size());
+ EXPECT_FALSE(NSASCallback::results[0].first);
+}
+
+/// \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
+ nsas.lookupAndAnswer("example.net.", RRClass::IN(), authority_,
+ getCallback());
+ // It should ask for IP addresses for example.com.
+ EXPECT_NO_THROW(resolver_->asksIPs(Name("ns.example.com."), 0, 1));
+
+ // Ask another question with different zone but the same nameserver
+ authority_->setName(Name("example.com."));
+ nsas.lookupAndAnswer("example.com.", RRClass::IN(), authority_,
+ getCallback());
+ // It should ask nothing more now
+ EXPECT_EQ(2, resolver_->requests.size());
+
+ // We say there are no addresses
+ resolver_->requests[0].second->failure();
+ resolver_->requests[1].second->failure();
+
+ // We should have 2 answers now
+ EXPECT_EQ(2, NSASCallback::results.size());
+ // When we ask one same and one other zone with the same nameserver,
+ // it should generate no questions and answer right away
+ nsas.lookup("example.net.", RRClass::IN(), getCallback());
+ authority_->setName(Name("example.org."));
+ nsas.lookupAndAnswer("example.org.", RRClass::IN(), authority_,
+ getCallback());
+ // There should be 4 negative answers now
+ EXPECT_EQ(4, NSASCallback::results.size());
+ BOOST_FOREACH(const NSASCallback::Result& result, NSASCallback::results) {
+ EXPECT_FALSE(result.first);
+ }
+}
+
+/// \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);
+ // Ask for example.net. It has single nameserver out of the zone
+ nsas.lookupAndAnswer("example.net.", RRClass::IN(), authority_,
+ getCallback());
+ // It should ask for the nameserver IP addresses
+ EXPECT_NO_THROW(resolver_->asksIPs(Name("ns.example.com."), 0, 1));
+ EXPECT_EQ(0, NSASCallback::results.size());
+ // But we do not answer it right away. We create a new zone and
+ // let this nameserver entry get out.
+ rrns_->addRdata(rdata::generic::NS("example.cz"));
+ nsas.lookupAndAnswer(EXAMPLE_CO_UK, RRClass::IN(), rrns_, getCallback());
+ // It really should ask something, one of the nameservers
+ // (or both)
+ ASSERT_GT(resolver_->requests.size(), 2);
+ Name name(resolver_->requests[2].first->getName());
+ EXPECT_TRUE(name == Name("example.fr") || name == Name("example.de") ||
+ name == Name("example.cz"));
+ EXPECT_NO_THROW(resolver_->asksIPs(name, 2, 3));
+ EXPECT_EQ(0, NSASCallback::results.size());
+
+ size_t request_count(resolver_->requests.size());
+ // This should still be in the hash table, so try it asks no more questions
+ nsas.lookup("example.net.", RRClass::IN(), getCallback());
+ EXPECT_EQ(request_count, resolver_->requests.size());
+ EXPECT_EQ(0, NSASCallback::results.size());
+
+ // We respond to one of the 3 nameservers
+ EXPECT_NO_THROW(resolver_->answer(2, name, RRType::A(),
+ rdata::in::A("192.0.2.1")));
+ // That should trigger one answer
+ EXPECT_EQ(1, NSASCallback::results.size());
+ EXPECT_TRUE(NSASCallback::results[0].first);
+ EXPECT_EQ("192.0.2.1",
+ NSASCallback::results[0].second.getAddress().toText());
+ EXPECT_NO_THROW(resolver_->answer(3, name, RRType::AAAA(),
+ rdata::in::AAAA("2001:bd8::1")));
+ // And there should be yet another query
+ ASSERT_GT(resolver_->requests.size(), 4);
+ EXPECT_NE(name, resolver_->requests[4].first->getName());
+ Name another_name = resolver_->requests[4].first->getName();
+ EXPECT_TRUE(another_name == Name("example.fr") ||
+ another_name == Name("example.de") ||
+ another_name == Name("example.cz"));
+ request_count = resolver_->requests.size();
+
+ // But when ask for a different zone with the first nameserver, it should
+ // ask again, as it is evicted already
+ authority_->setName(Name("example.com."));
+ nsas.lookupAndAnswer("example.com.", RRClass::IN(), authority_,
+ getCallback());
+ EXPECT_EQ(request_count + 2, resolver_->requests.size());
+ EXPECT_NO_THROW(resolver_->asksIPs(Name("ns.example.com."), request_count,
+ request_count + 1));
+ // Now, we answer both queries for the same address
+ // and three (one for the original, one for this one) more answers should
+ // arrive
+ NSASCallback::results.clear();
+ EXPECT_NO_THROW(resolver_->answer(0, Name("ns.example.com."), RRType::A(),
+ rdata::in::A("192.0.2.2")));
+ EXPECT_NO_THROW(resolver_->answer(request_count, Name("ns.example.com."),
+ RRType::A(), rdata::in::A("192.0.2.2")));
+ EXPECT_EQ(3, NSASCallback::results.size());
+ BOOST_FOREACH(const NSASCallback::Result& result, NSASCallback::results) {
+ EXPECT_TRUE(result.first);
+ EXPECT_EQ("192.0.2.2", result.second.getAddress().toText());
+ }
+}
+
+} // namespace nsas
+} // namespace isc
diff --git a/src/lib/nsas/tests/nameserver_address_unittest.cc b/src/lib/nsas/tests/nameserver_address_unittest.cc
new file mode 100644
index 0000000..457e61c
--- /dev/null
+++ b/src/lib/nsas/tests/nameserver_address_unittest.cc
@@ -0,0 +1,135 @@
+// 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 <config.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+
+#include "../nameserver_address.h"
+#include "../nameserver_entry.h"
+#include "nsas_test.h"
+
+namespace isc {
+namespace nsas {
+
+using namespace dns;
+using namespace rdata;
+
+#define TEST_ADDRESS_INDEX 1
+
+/// \brief NameserverEntry sample class for testing
+class NameserverEntrySample {
+public:
+ NameserverEntrySample():
+ name_("example.org"),
+ 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")));
+ rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("5.6.7.8")));
+ rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("9.10.11.12")));
+
+ ns_.reset(new NameserverEntry(name_.toText(), RRClass::IN()));
+ 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
+ boost::shared_ptr<NameserverEntry>& getNameserverEntry() { return ns_; }
+
+ // Return the IOAddress corresponding to the index in rrv4_
+ asiolink::IOAddress getAddressAtIndex(uint32_t index) {
+ return ns_.get()->getAddressAtIndex(index, V4_ONLY);
+ }
+
+ // Return the addresses count stored in RRset
+ unsigned int getAddressesCount() const { return rrv4_->getRdataCount(); }
+
+ // Return the RTT of the address
+ uint32_t getAddressRTTAtIndex(uint32_t index) {
+ NameserverEntry::AddressVector addresses;
+ ns_.get()->getAddresses(addresses);
+ return (addresses[index].getAddressEntry().getRTT());
+ }
+
+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:
+ virtual void operator()(boost::shared_ptr<NameserverEntry>) { }
+ };
+};
+
+/// \brief Test Fixture Class
+class NameserverAddressTest : public ::testing::Test {
+protected:
+ // Constructor
+ NameserverAddressTest():
+ ns_address_(ns_sample_.getNameserverEntry(),
+ ns_sample_.getNameserverEntry()->getAddressAtIndex(
+ TEST_ADDRESS_INDEX, V4_ONLY), V4_ONLY)
+ {
+ }
+
+ NameserverEntrySample ns_sample_;
+ // Valid NameserverAddress object
+ NameserverAddress ns_address_;
+};
+
+// Test that the address is equal to the address in NameserverEntry
+TEST_F(NameserverAddressTest, Address) {
+ 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
+ ASSERT_THROW({NameserverAddress empty_ns_address(empty_ne,
+ asiolink::IOAddress("127.0.0.1"), V4_ONLY);},
+ NullNameserverEntryPointer);
+}
+
+// Test that the RTT is updated
+TEST_F(NameserverAddressTest, UpdateRTT) {
+ uint32_t old_rtt = ns_sample_.getAddressRTTAtIndex(TEST_ADDRESS_INDEX);
+ uint32_t new_rtt = old_rtt + 10;
+
+ uint32_t old_rtt0 = ns_sample_.getAddressRTTAtIndex(0);
+ uint32_t old_rtt2 = ns_sample_.getAddressRTTAtIndex(2);
+
+ for(int i = 0; i < 10000; ++i){
+ ns_address_.updateRTT(new_rtt);
+ }
+
+ //The RTT should have been updated
+ EXPECT_NE(new_rtt, ns_sample_.getAddressRTTAtIndex(TEST_ADDRESS_INDEX));
+
+ //The RTTs not been updated should remain unchanged
+ EXPECT_EQ(old_rtt0, ns_sample_.getAddressRTTAtIndex(0));
+ EXPECT_EQ(old_rtt2, ns_sample_.getAddressRTTAtIndex(2));
+}
+
+} // namespace nsas
+} // namespace isc
diff --git a/src/lib/nsas/tests/nameserver_entry_unittest.cc b/src/lib/nsas/tests/nameserver_entry_unittest.cc
new file mode 100644
index 0000000..4225e87
--- /dev/null
+++ b/src/lib/nsas/tests/nameserver_entry_unittest.cc
@@ -0,0 +1,518 @@
+// 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 <config.h>
+
+#include <iostream>
+#include <algorithm>
+
+#include <limits.h>
+#include <boost/foreach.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <dns/rdata.h>
+#include <dns/rrset.h>
+#include <dns/rrclass.h>
+#include <dns/rdataclass.h>
+#include <dns/rrttl.h>
+#include <dns/name.h>
+#include <exceptions/exceptions.h>
+
+#include "../asiolink.h"
+#include "../address_entry.h"
+#include "../nameserver_entry.h"
+#include "../nameserver_address.h"
+#include "../zone_entry.h"
+
+#include "nsas_test.h"
+
+using namespace isc::nsas;
+using namespace asiolink;
+using namespace std;
+using namespace isc::dns;
+using namespace rdata;
+
+namespace {
+
+/// \brief Test Fixture Class
+class NameserverEntryTest : public TestWithRdata {
+protected:
+ /// \short Just a really stupid callback counting times called
+ struct Callback : public NameserverEntry::Callback {
+ size_t count;
+ virtual void operator()(boost::shared_ptr<NameserverEntry>) {
+ count ++;
+ }
+ Callback() : count(0) { }
+ };
+private:
+ /**
+ * \short Fills an rrset into the NameserverEntry trough resolver.
+ *
+ * This function is used when we want to pass data to a NameserverEntry
+ * trough the resolver.
+ * \param resolver The resolver used by the NameserverEntry
+ * \param index Index of the query in the resolver.
+ * \param set The answer. If the pointer is empty, it is taken
+ * as a failure.
+ */
+ void fillSet(boost::shared_ptr<TestResolver> resolver, size_t index,
+ RRsetPtr set)
+ {
+ if (set) {
+ resolver->requests[index].second->success(createResponseMessage(set));
+ } else {
+ resolver->requests[index].second->failure();
+ }
+ }
+protected:
+ /// Fills the nameserver entry with data trough ask IP
+ void fillNSEntry(boost::shared_ptr<NameserverEntry> entry,
+ RRsetPtr rrv4, RRsetPtr rrv6)
+ {
+ // Prepare data to run askIP
+ boost::shared_ptr<TestResolver> resolver(new TestResolver);
+ boost::shared_ptr<Callback> callback(new Callback);
+ // Let it ask for data
+ 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
+ fillSet(resolver, 0, rrv4);
+ fillSet(resolver, 1, rrv6);
+ }
+};
+
+/// Tests of the default constructor
+TEST_F(NameserverEntryTest, DefaultConstructor) {
+
+ // Default constructor should not create any RRsets
+ NameserverEntry alpha(EXAMPLE_CO_UK, RRClass::IN());
+ EXPECT_EQ(EXAMPLE_CO_UK, alpha.getName());
+ EXPECT_EQ(RRClass::IN(), alpha.getClass());
+
+ // Also check that no addresses have been created.
+ NameserverEntry::AddressVector addresses;
+ alpha.getAddresses(addresses);
+ EXPECT_TRUE(addresses.empty());
+}
+
+// Test the the RTT on tthe created addresses is not 0 and is different
+TEST_F(NameserverEntryTest, InitialRTT) {
+
+ // Get the RTT for the different addresses
+ boost::shared_ptr<NameserverEntry> alpha(new NameserverEntry(EXAMPLE_CO_UK,
+ RRClass::IN()));
+ fillNSEntry(alpha, rrv4_, rrv6_);
+ NameserverEntry::AddressVector vec;
+ alpha->getAddresses(vec);
+
+ // Check they are not 0 and they are all small, they should be some kind
+ // of randomish numbers, so we can't expect much more here
+ BOOST_FOREACH(NameserverAddress& entry, vec) {
+ EXPECT_GT(entry.getAddressEntry().getRTT(), 0);
+ // 20 is some arbitrary small value
+ EXPECT_LT(entry.getAddressEntry().getRTT(), 20);
+ }
+}
+
+// Set an address RTT to a given value
+TEST_F(NameserverEntryTest, SetRTT) {
+
+ // Get the RTT for the different addresses
+ boost::shared_ptr<NameserverEntry> alpha(new NameserverEntry(EXAMPLE_CO_UK,
+ RRClass::IN()));
+ fillNSEntry(alpha, rrv4_, rrv6_);
+ NameserverEntry::AddressVector vec;
+ alpha->getAddresses(vec);
+
+ ASSERT_TRUE(vec.size() > 0);
+
+ // Take the first address and change the RTT.
+ IOAddress first_address = vec[0].getAddress();
+ uint32_t first_rtt = vec[0].getAddressEntry().getRTT();
+ uint32_t new_rtt = first_rtt + 42;
+ alpha->setAddressRTT(first_address, new_rtt);
+
+ // Now see if it has changed
+ NameserverEntry::AddressVector newvec;
+ alpha->getAddresses(newvec);
+
+ int matchcount = 0;
+ for (NameserverEntry::AddressVectorIterator i = newvec.begin();
+ i != newvec.end(); ++i) {
+ if (i->getAddress().equals(first_address)) {
+ ++matchcount;
+ EXPECT_EQ(i->getAddressEntry().getRTT(), new_rtt);
+ }
+ }
+
+ // ... and make sure there was only one match in the set we retrieved
+ EXPECT_EQ(1, matchcount);
+}
+
+// Set an address RTT to be unreachable
+TEST_F(NameserverEntryTest, Unreachable) {
+
+ // Get the RTT for the different addresses
+ boost::shared_ptr<NameserverEntry> alpha(new NameserverEntry(EXAMPLE_CO_UK,
+ RRClass::IN()));
+ fillNSEntry(alpha, rrv4_, rrv6_);
+ NameserverEntry::AddressVector vec;
+ alpha->getAddresses(vec);
+
+ ASSERT_TRUE(vec.size() > 0);
+
+ // Take the first address and mark as unreachable.
+ IOAddress first_address = vec[0].getAddress();
+ EXPECT_FALSE(vec[0].getAddressEntry().isUnreachable());
+
+ alpha->setAddressUnreachable(first_address);
+
+ // Now see if it has changed
+ NameserverEntry::AddressVector newvec;
+ alpha->getAddresses(newvec);
+
+ int matchcount = 0;
+ for (NameserverEntry::AddressVectorIterator i = newvec.begin();
+ i != newvec.end(); ++i) {
+ if (i->getAddress().equals(first_address)) {
+ ++matchcount;
+ EXPECT_TRUE(i->getAddressEntry().isUnreachable());
+ }
+ }
+
+ // ... and make sure there was only one match in the set we retrieved
+ EXPECT_EQ(1, matchcount);
+}
+
+// Test that the expiration time of records is set correctly.
+//
+// Note that for testing purposes we use the three-argument NameserverEntry
+// constructor (where we supply the time). It eliminates intermittent errors
+// cause when this test is run just as the clock "ticks over" to another second.
+// TODO Return the way where we can pass time inside somehow
+TEST_F(NameserverEntryTest, ExpirationTime) {
+
+ time_t curtime = time(NULL);
+ time_t expiration = 0;
+
+ // Test where there is a single TTL
+ boost::shared_ptr<NameserverEntry> alpha(new NameserverEntry(EXAMPLE_CO_UK,
+ RRClass::IN()));
+ fillNSEntry(alpha, rrv4_, RRsetPtr());
+ expiration = alpha->getExpiration();
+ EXPECT_EQ(expiration, curtime + rrv4_->getTTL().getValue());
+
+ boost::shared_ptr<NameserverEntry> beta(new NameserverEntry(EXAMPLE_CO_UK,
+ RRClass::IN()));
+ fillNSEntry(beta, RRsetPtr(), rrv6_);
+ expiration = beta->getExpiration();
+ EXPECT_EQ(expiration, curtime + rrv6_->getTTL().getValue());
+
+ // Test where there are two different TTLs
+ EXPECT_NE(rrv4_->getTTL().getValue(), rrv6_->getTTL().getValue());
+ boost::shared_ptr<NameserverEntry> gamma(new NameserverEntry(EXAMPLE_CO_UK,
+ RRClass::IN()));
+ fillNSEntry(gamma, rrv4_, rrv6_);
+ uint32_t minttl = min(rrv4_->getTTL().getValue(), rrv6_->getTTL().getValue());
+ expiration = gamma->getExpiration();
+ EXPECT_EQ(expiration, curtime + minttl);
+
+ // Finally check where we don't specify a current time. All we know is
+ // that the expiration time should be greater than the TTL (as the current
+ // time is greater than zero).
+
+ boost::shared_ptr<NameserverEntry> delta(new NameserverEntry(EXAMPLE_CO_UK,
+ RRClass::IN()));
+ fillNSEntry(delta, rrv4_, RRsetPtr());
+ EXPECT_GT(delta->getExpiration(), rrv4_->getTTL().getValue());
+}
+
+
+// Test that the name of this nameserver is set correctly.
+TEST_F(NameserverEntryTest, CheckName) {
+
+ // Default constructor
+ NameserverEntry alpha(EXAMPLE_CO_UK, RRClass::IN());
+ EXPECT_EQ(EXAMPLE_CO_UK, alpha.getName());
+}
+
+// Check that it can cope with non-IN classes.
+TEST_F(NameserverEntryTest, CheckClass) {
+
+ // Default constructor
+ NameserverEntry alpha(EXAMPLE_CO_UK, RRClass::CH());
+ EXPECT_EQ(RRClass::CH(), alpha.getClass());
+}
+
+// Tests if it asks the IP addresses and calls callbacks when it comes
+// including the right addresses are returned and that they expire
+TEST_F(NameserverEntryTest, IPCallbacks) {
+ boost::shared_ptr<NameserverEntry> entry(new NameserverEntry(EXAMPLE_CO_UK,
+ RRClass::IN()));
+ boost::shared_ptr<Callback> callback(new Callback);
+ boost::shared_ptr<TestResolver> resolver(new TestResolver);
+
+ 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
+ EXPECT_EQ(2, resolver->requests.size());
+ ASSERT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 0, 1));
+
+ // Another one might ask
+ 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.get(), callback, V6_ONLY);
+
+ // Answer one and see that the callbacks are called
+ resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(),
+ rdata::in::A("192.0.2.1"));
+
+ // Both callbacks that want IPv4 should be called by now
+ EXPECT_EQ(2, callback->count);
+ // It should contain one IP address
+ NameserverEntry::AddressVector addresses;
+ // Still in progress, waiting for the other address
+ EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getAddresses(addresses));
+ EXPECT_EQ(1, addresses.size());
+ // Answer IPv6 address
+ // It is with zero TTL, so it should expire right away
+ resolver->answer(1, Name(EXAMPLE_CO_UK), RRType::AAAA(),
+ rdata::in::AAAA("2001:db8::1"), 0);
+ // The other callback should appear
+ EXPECT_EQ(3, callback->count);
+ // It should return the one address. It should be expired, but
+ // we ignore it for now
+ EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V6_ONLY, true));
+ // Another address should appear
+ EXPECT_EQ(2, addresses.size());
+ // But when we do not ignore it, it should not appear
+ EXPECT_EQ(Fetchable::EXPIRED, entry->getAddresses(addresses, V6_ONLY));
+ EXPECT_EQ(2, addresses.size());
+}
+
+// Test the callback is called even when the address is unreachable
+TEST_F(NameserverEntryTest, IPCallbacksUnreachable) {
+ boost::shared_ptr<NameserverEntry> entry(new NameserverEntry(EXAMPLE_CO_UK,
+ RRClass::IN()));
+ boost::shared_ptr<Callback> callback(new Callback);
+ boost::shared_ptr<TestResolver> resolver(new TestResolver);
+
+ // Ask for its IP
+ 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));
+ resolver->requests[0].second->failure();
+ // It should still wait for the second one
+ EXPECT_EQ(0, callback->count);
+ EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState());
+ // It should call the callback now and be unrechable
+ resolver->requests[1].second->failure();
+ EXPECT_EQ(1, callback->count);
+ EXPECT_EQ(Fetchable::UNREACHABLE, entry->getState());
+ NameserverEntry::AddressVector addresses;
+ EXPECT_EQ(Fetchable::UNREACHABLE, entry->getAddresses(addresses));
+ EXPECT_EQ(0, addresses.size());
+}
+
+/*
+ * Tests that it works even when we provide the answer right away, directly
+ * from resolve.
+ */
+TEST_F(NameserverEntryTest, DirectAnswer) {
+ boost::shared_ptr<NameserverEntry> entry(new NameserverEntry(EXAMPLE_CO_UK,
+ RRClass::IN()));
+ boost::shared_ptr<Callback> callback(new Callback);
+ boost::shared_ptr<TestResolver> resolver(new TestResolver);
+ resolver->addPresetAnswer(Question(Name(EXAMPLE_CO_UK), RRClass::IN(),
+ RRType::A()), rrv4_);
+ resolver->addPresetAnswer(Question(Name(EXAMPLE_CO_UK), RRClass::IN(),
+ RRType::AAAA()), rrv6_);
+ resolver->addPresetAnswer(Question(Name(EXAMPLE_NET), RRClass::IN(),
+ RRType::A()), RRsetPtr());
+ resolver->addPresetAnswer(Question(Name(EXAMPLE_NET), RRClass::IN(),
+ RRType::AAAA()), RRsetPtr());
+
+ // A successfull test first
+ entry->askIP(resolver.get(), callback, ANY_OK);
+ EXPECT_EQ(0, resolver->requests.size());
+ EXPECT_EQ(1, callback->count);
+ NameserverEntry::AddressVector addresses;
+ EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses));
+ EXPECT_EQ(5, addresses.size());
+
+ // An unsuccessfull test
+ callback->count = 0;
+ entry.reset(new NameserverEntry(EXAMPLE_NET, RRClass::IN()));
+ entry->askIP(resolver.get(), callback, ANY_OK);
+ EXPECT_EQ(0, resolver->requests.size());
+ EXPECT_EQ(1, callback->count);
+ addresses.clear();
+ EXPECT_EQ(Fetchable::UNREACHABLE, entry->getAddresses(addresses));
+ EXPECT_EQ(0, addresses.size());
+}
+
+/*
+ * This one tests if it works when the data time out and a different
+ * data is received the next time.
+ */
+TEST_F(NameserverEntryTest, ChangedExpired) {
+ boost::shared_ptr<NameserverEntry> entry(new NameserverEntry(EXAMPLE_CO_UK,
+ RRClass::IN()));
+ boost::shared_ptr<Callback> callback(new Callback);
+ boost::shared_ptr<TestResolver> resolver(new TestResolver);
+
+ // Ask the first time
+ 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(),
+ rdata::in::A("192.0.2.1"), 0);
+ resolver->answer(1, Name(EXAMPLE_CO_UK), RRType::AAAA(),
+ rdata::in::AAAA("2001:db8::1"), 0);
+ EXPECT_EQ(2, callback->count);
+ NameserverEntry::AddressVector addresses;
+ // We must accept expired as well, as the TTL is 0 (and it is OK,
+ // because we just got the callback)
+ EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V4_ONLY, true));
+ ASSERT_EQ(1, addresses.size());
+ EXPECT_EQ("192.0.2.1", addresses[0].getAddress().toText());
+ EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V6_ONLY, true));
+ ASSERT_EQ(2, addresses.size());
+ EXPECT_EQ("2001:db8::1", addresses[1].getAddress().toText());
+
+ // Ask the second time. The callbacks should not fire right away and it
+ // should request the addresses again
+ 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());
+ resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(),
+ rdata::in::A("192.0.2.2"));
+ resolver->answer(1, Name(EXAMPLE_CO_UK), RRType::AAAA(),
+ rdata::in::AAAA("2001:db8::2"));
+ // We should get the new addresses and they should not expire,
+ // so we should get them without accepting expired
+ EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V4_ONLY));
+ ASSERT_EQ(3, addresses.size());
+ EXPECT_EQ("192.0.2.2", addresses[2].getAddress().toText());
+ EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V6_ONLY));
+ ASSERT_EQ(4, addresses.size());
+ EXPECT_EQ("2001:db8::2", addresses[3].getAddress().toText());
+}
+
+/*
+ * When the data expire and is asked again, the original RTT is kept.
+ */
+TEST_F(NameserverEntryTest, KeepRTT) {
+ boost::shared_ptr<NameserverEntry> entry(new NameserverEntry(EXAMPLE_CO_UK,
+ RRClass::IN()));
+ boost::shared_ptr<Callback> callback(new Callback);
+ boost::shared_ptr<TestResolver> resolver(new TestResolver);
+
+ // Ask the first time
+ 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(),
+ rdata::in::A("192.0.2.1"), 0);
+ resolver->answer(1, Name(EXAMPLE_CO_UK), RRType::AAAA(),
+ rdata::in::AAAA("2001:db8::1"), 0);
+ EXPECT_EQ(2, callback->count);
+ NameserverEntry::AddressVector addresses;
+ // We must accept expired as well, as the TTL is 0 (and it is OK,
+ // because we just got the callback)
+ EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V4_ONLY, true));
+ ASSERT_EQ(1, addresses.size());
+ EXPECT_EQ("192.0.2.1", addresses[0].getAddress().toText());
+ EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V6_ONLY, true));
+ ASSERT_EQ(2, addresses.size());
+ EXPECT_EQ("2001:db8::1", addresses[1].getAddress().toText());
+ BOOST_FOREACH(const NameserverAddress& address, addresses) {
+ entry->setAddressRTT(address.getAddress(), 123);
+ }
+
+ // Ask the second time. The callbacks should not fire right away and it
+ // should request the addresses again
+ 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());
+ resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(),
+ rdata::in::A("192.0.2.1"));
+ resolver->answer(1, Name(EXAMPLE_CO_UK), RRType::AAAA(),
+ rdata::in::AAAA("2001:db8::1"));
+ // We should get the new addresses and they should not expire,
+ // so we should get them without accepting expired
+ addresses.clear();
+ EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V4_ONLY));
+ ASSERT_EQ(1, addresses.size());
+ EXPECT_EQ("192.0.2.1", addresses[0].getAddress().toText());
+ EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V6_ONLY));
+ ASSERT_EQ(2, addresses.size());
+ EXPECT_EQ("2001:db8::1", addresses[1].getAddress().toText());
+ // They should have the RTT we set to them
+ BOOST_FOREACH(NameserverAddress& address, addresses) {
+ EXPECT_EQ(123, address.getAddressEntry().getRTT());
+ }
+}
+
+// Test the RTT is updated smoothly
+TEST_F(NameserverEntryTest, UpdateRTT) {
+ boost::shared_ptr<NameserverEntry> ns(new NameserverEntry(EXAMPLE_CO_UK,
+ RRClass::IN()));
+ fillNSEntry(ns, rrv4_, rrv6_);
+ NameserverEntry::AddressVector vec;
+ ns->getAddresses(vec);
+
+ // Initialize the rtt with a small value
+ uint32_t init_rtt = 1;
+ ns->setAddressRTT(vec[0].getAddress(), init_rtt);
+ // The rtt will be stablized to a large value
+ uint32_t stable_rtt = 100;
+
+ // Update the rtt
+ vec[0].updateRTT(stable_rtt);
+
+ vec.clear();
+ ns->getAddresses(vec);
+ uint32_t new_rtt = vec[0].getAddressEntry().getRTT();
+
+ // The rtt should not close to new rtt immediately
+ EXPECT_TRUE((stable_rtt - new_rtt) > (new_rtt - init_rtt));
+
+ // Update the rtt for enough times
+ for(int i = 0; i < 10000; ++i){
+ vec[0].updateRTT(stable_rtt);
+ }
+ vec.clear();
+ ns->getAddresses(vec);
+ new_rtt = vec[0].getAddressEntry().getRTT();
+
+ // The rtt should be close to stable rtt value
+ EXPECT_TRUE((stable_rtt - new_rtt) < (new_rtt - init_rtt));
+}
+
+} // namespace
diff --git a/src/lib/nsas/tests/nsas_entry_compare_unittest.cc b/src/lib/nsas/tests/nsas_entry_compare_unittest.cc
new file mode 100644
index 0000000..33e41b8
--- /dev/null
+++ b/src/lib/nsas/tests/nsas_entry_compare_unittest.cc
@@ -0,0 +1,61 @@
+// 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 <config.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+#include <boost/lexical_cast.hpp>
+
+#include "../hash_key.h"
+#include "../nsas_entry_compare.h"
+#include "nsas_test.h"
+
+using namespace std;
+
+namespace isc {
+namespace nsas {
+
+/// \brief Test Fixture Class
+class NsasEntryCompareTest : public ::testing::Test {
+};
+
+
+// Test of the comparison
+TEST_F(NsasEntryCompareTest, Compare) {
+ std::string name1("test1");
+ std::string name2("test2");
+
+ // Construct a couple of different objects
+ TestEntry entry1(name1, RRClass(42));
+ TestEntry entry2(name1, RRClass(24));
+ TestEntry entry3(name2, RRClass(42));
+
+ // Create corresponding hash key objects
+ HashKey key1(name1, entry1.getClass());
+ HashKey key2(name1, entry2.getClass());
+ HashKey key3(name2, entry3.getClass());
+
+ // Perform the comparison
+ NsasEntryCompare<TestEntry> compare;
+
+ EXPECT_TRUE(compare(&entry1, key1));
+ EXPECT_FALSE(compare(&entry1, key2));
+}
+
+} // namespace nsas
+} // namespace isc
diff --git a/src/lib/nsas/tests/nsas_test.h b/src/lib/nsas/tests/nsas_test.h
new file mode 100644
index 0000000..c1a18d6
--- /dev/null
+++ b/src/lib/nsas/tests/nsas_test.h
@@ -0,0 +1,433 @@
+// 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 __NSAS_TEST_H
+#define __NSAS_TEST_H
+
+/// \file nsas_test.h
+///
+/// Contains miscellaneous classes and other stuff to help with the nameserver
+/// address store tests.
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include <config.h>
+
+#include <dns/message.h>
+#include <dns/buffer.h>
+#include <dns/rdata.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdataclass.h>
+#include <resolve/resolver_interface.h>
+#include "../nsas_entry.h"
+
+using namespace isc::dns::rdata;
+using namespace isc::dns;
+
+namespace {
+ MessagePtr
+ createResponseMessage(RRsetPtr answer_rrset)
+ {
+ MessagePtr response(new Message(Message::RENDER));
+ response->setOpcode(Opcode::QUERY());
+ response->setRcode(Rcode::NOERROR());
+ response->addRRset(Message::SECTION_ANSWER, answer_rrset);
+ return response;
+ }
+}
+
+namespace isc {
+namespace dns {
+
+/// \brief Class Types
+///
+/// Very simple classes to provide a type for the RdataTest class below.
+/// All they do is return the type code associated with the record type.
+
+class A {
+public:
+ uint16_t getType() const
+ {return RRType::A().getCode();}
+};
+
+class AAAA {
+public:
+ uint16_t getType() const
+ {return RRType::AAAA().getCode();}
+};
+
+class MX {
+public:
+ uint16_t getType() const
+ {return RRType::MX().getCode();}
+};
+
+/// \brief Hold Rdata
+///
+/// A concrete implementation of the Rdata class, this holds data for the
+/// tests. All RRs in the tests are either A, AAAA, NS or MX records, and
+/// as a result the text form of the Rdata is a single uninterpreted string.
+/// For this reason, a single class definition
+
+template <typename T>
+class RdataTest: public Rdata {
+public:
+
+ /// \brief Constructor
+ ///
+ /// Set the data in the object.
+ ///
+ /// \param v4address IPV4 address to store. (The format of this address is
+ /// not checked.)
+ RdataTest(const std::string& data) : data_(data)
+ {}
+
+ /// \brief Convert Rdata to string
+ ///
+ /// This is the convenient interface to extract the information stored
+ /// in this object into a form that can be used by the tests.
+ virtual std::string toText() const {
+ return (data_);
+ }
+
+ /// \brief Return type of Rdata
+ ///
+ /// Returns the type of the data. May be useful in the tests, although this
+ /// will not appear in the main code as this interface is not defined.
+ virtual uint16_t getType() const {
+ return (type_.getType());
+ }
+
+ /// \name Unused Methods
+ ///
+ /// These methods are not used in the tests.
+ ///
+ //@{
+ /// \brief Render the \c Rdata in the wire format to a buffer
+ virtual void toWire(OutputBuffer& buffer) const;
+
+ /// \brief render the \Rdata in the wire format to a \c MessageRenderer
+ virtual void toWire(AbstractMessageRenderer& renderer) const;
+
+ /// \brief Comparison Method
+ virtual int compare(const Rdata& other) const;
+ //@}
+
+private:
+ std::string data_; ///< Rdata itself
+ T type_; ///< Identifies type of the Rdata
+};
+
+template <typename T>
+void RdataTest<T>::toWire(OutputBuffer&) const {
+}
+
+template <typename T>
+void RdataTest<T>::toWire(AbstractMessageRenderer&) const {
+}
+
+template <typename T>
+int RdataTest<T>::compare(const Rdata&) const {
+ return 0;
+}
+
+} // namespace dns
+} // namespace isc
+
+namespace isc {
+namespace nsas {
+
+/// \brief Test Entry Class
+///
+/// This is an element that can be stored in both the hash table and the
+/// LRU list.
+
+class TestEntry : public NsasEntry<TestEntry> {
+public:
+
+ /// \brief Constructor
+ ///
+ /// \param name Name that will be used for the object. This will form
+ /// part of the key.
+ /// \param class_code Class associated with the object.
+ TestEntry(std::string name, const isc::dns::RRClass& class_code) :
+ name_(name), class_code_(class_code)
+ {}
+
+ /// \brief Virtual Destructor
+ virtual ~TestEntry()
+ {}
+
+ /// \brief Return Hash Key
+ ///
+ /// This must be overridden in all classes derived from NsasEntry, and
+ /// returns the hash key corresponding to the name and class.
+ virtual HashKey hashKey() const {
+ return HashKey(name_, class_code_);
+ }
+
+ /// \brief Get the Name
+ ///
+ /// \return Name given to this object
+ virtual std::string getName() const {
+ return name_;
+ }
+
+ /// \brief Set the Name
+ ///
+ /// \param name New name of the object
+ virtual void setName(const std::string& name) {
+ name_ = name;
+ }
+
+ /// \brief Get the Class
+ ///
+ /// \return Class code assigned to this object
+ virtual const isc::dns::RRClass& getClass() const {
+ return class_code_;
+ }
+
+ /// \brief Set the Class
+ ///
+ /// \param class_code New class code of the object
+ virtual void setClass(const isc::dns::RRClass& class_code) {
+ class_code_ = class_code;
+ }
+
+private:
+ std::string name_; ///< Name of the object
+ isc::dns::RRClass class_code_; ///< Class of the object
+};
+
+/// \brief isc::nsas Constants
+///
+/// Some constants used in the various tests.
+
+static const uint32_t HASHTABLE_DEFAULT_SIZE = 1009; ///< First prime above 1000
+
+using namespace std;
+
+/*
+ * This pretends to be a resolver. It stores the queries and
+ * they can be answered.
+ */
+class TestResolver : public isc::resolve::ResolverInterface {
+ private:
+ bool checkIndex(size_t index) {
+ return (requests.size() > index);
+ }
+
+ typedef std::map<isc::dns::Question, RRsetPtr >
+ PresetAnswers;
+ PresetAnswers answers_;
+ 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()) {
+ requests.push_back(Request(q, c));
+ } else {
+ if (it->second) {
+ c->success(createResponseMessage(it->second));
+ } else {
+ c->failure();
+ }
+ }
+ }
+
+ /*
+ * Add a preset answer. If shared_ptr() is passed (eg. NULL),
+ * it will generate failure. If the question is not preset,
+ * it goes to requests and you can answer later.
+ */
+ void addPresetAnswer(const isc::dns::Question& question,
+ RRsetPtr answer)
+ {
+ answers_[question] = answer;
+ }
+
+ // Thrown if the query at the given index does not exist.
+ class NoSuchRequest : public std::exception { };
+
+ // Thrown if the answer does not match the query
+ class DifferentRequest : public std::exception { };
+
+ QuestionPtr operator[](size_t index) {
+ if (index >= requests.size()) {
+ throw NoSuchRequest();
+ }
+ return (requests[index].first);
+ }
+ /*
+ * Looks if the two provided requests in resolver are A and AAAA.
+ * Sorts them so index1 is A.
+ *
+ * Returns false if there aren't enough elements
+ */
+ bool asksIPs(const Name& name, size_t index1, size_t index2) {
+ size_t max = (index1 < index2) ? index2 : index1;
+ if (!checkIndex(max)) {
+ return false;
+ }
+ EXPECT_EQ(name, (*this)[index1]->getName());
+ EXPECT_EQ(name, (*this)[index2]->getName());
+ EXPECT_EQ(RRClass::IN(), (*this)[index1]->getClass());
+ EXPECT_EQ(RRClass::IN(), (*this)[index2]->getClass());
+ // If they are the other way around, swap
+ if ((*this)[index1]->getType() == RRType::AAAA() &&
+ (*this)[index2]->getType() == RRType::A())
+ {
+ TestResolver::Request tmp((*this).requests[index1]);
+ (*this).requests[index1] =
+ (*this).requests[index2];
+ (*this).requests[index2] = tmp;
+ }
+ // Check the correct addresses
+ EXPECT_EQ(RRType::A(), (*this)[index1]->getType());
+ EXPECT_EQ(RRType::AAAA(), (*this)[index2]->getType());
+ return (true);
+ }
+
+ /*
+ * Sends a simple answer to a query.
+ * Provide index of a query and the address to pass.
+ */
+ void answer(size_t index, const Name& name, const RRType& type,
+ const rdata::Rdata& rdata, size_t TTL = 100)
+ {
+ if (index >= requests.size()) {
+ throw NoSuchRequest();
+ }
+ RRsetPtr set(new RRset(name, RRClass::IN(),
+ type, RRTTL(TTL)));
+ set->addRdata(rdata);
+ requests[index].second->success(createResponseMessage(set));
+ }
+
+ void provideNS(size_t index,
+ RRsetPtr nameservers)
+ {
+ if (index >= requests.size()) {
+ throw NoSuchRequest();
+ }
+ if (requests[index].first->getName() != nameservers->getName() ||
+ requests[index].first->getType() != RRType::NS())
+ {
+ throw DifferentRequest();
+ }
+ requests[index].second->success(createResponseMessage(nameservers));
+ }
+};
+
+// String constants. These should end in a dot.
+static const std::string EXAMPLE_CO_UK("example.co.uk.");
+static const std::string EXAMPLE_NET("example.net.");
+static const std::string MIXED_EXAMPLE_CO_UK("EXAmple.co.uk.");
+
+class TestWithRdata : public ::testing::Test {
+protected:
+ typedef boost::shared_ptr<RRset> RRsetPtr;
+ /// \brief Constructor
+ ///
+ /// Initializes the RRsets used in the tests. The RRsets themselves have to
+ /// be initialized with the basic data on their construction. The Rdata for
+ /// them is added in SetUp().
+ TestWithRdata() :
+ rrv4_(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(), RRType::A(),
+ RRTTL(1200))),
+ rrcase_(new RRset(Name(MIXED_EXAMPLE_CO_UK), RRClass::IN(),
+ RRType::A(), RRTTL(1200))),
+ rrch_(new RRset(Name(EXAMPLE_CO_UK), RRClass::CH(), RRType::A(),
+ RRTTL(1200))),
+ rrns_(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(), RRType::NS(),
+ RRTTL(1200))),
+ rr_single_(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(),
+ RRType::NS(), RRTTL(600))),
+ rr_empty_(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(),
+ RRType::NS(), RRTTL(600))),
+ rrv6_(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(),
+ RRType::AAAA(), RRTTL(900))),
+ rrnet_(new RRset(Name(EXAMPLE_NET), RRClass::IN(), RRType::A(),
+ RRTTL(600))),
+ ns_name_("ns.example.net.")
+ {}
+
+ /// \brief Add Rdata to RRsets
+ ///
+ /// The data are added as const pointers to avoid the stricter type checking
+ /// applied by the Rdata code. There is no need for it in these tests.
+ virtual void SetUp() {
+
+ // A records
+ rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("1.2.3.4")));
+ rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("5.6.7.8")));
+ rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("9.10.11.12")));
+
+ // A records
+ rrcase_->addRdata(ConstRdataPtr(new RdataTest<A>("13.14.15.16")));
+
+ // No idea what Chaosnet address look like other than they are 16 bits
+ // The fact that they are type A is probably also incorrect.
+ rrch_->addRdata(ConstRdataPtr(new RdataTest<A>("1324")));
+
+ // NS records take a single name
+ rrns_->addRdata(rdata::generic::NS("example.fr"));
+ rrns_->addRdata(rdata::generic::NS("example.de"));
+
+ // Single NS record with 0 TTL
+ rr_single_->addRdata(rdata::generic::NS(ns_name_));
+
+ // AAAA records
+ rrv6_->addRdata(ConstRdataPtr(new RdataTest<AAAA>("2001::1002")));
+ rrv6_->addRdata(ConstRdataPtr(new RdataTest<AAAA>("dead:beef:feed::")));
+
+ // A record for example.net
+ rrnet_->addRdata(ConstRdataPtr(new RdataTest<A>("17.18.18.20")));
+ }
+
+ /// \brief Data for the tests
+ RRsetPtr rrv4_; ///< Standard RRSet - IN, A, lowercase name
+ RRsetPtr rrcase_; ///< Mixed-case name
+ RRsetPtr rrch_; ///< Non-IN RRset (Chaos in this case)
+ RRsetPtr rrns_; ///< NS RRset
+ RRsetPtr rr_single_; ///< NS RRset with single NS
+ RRsetPtr rr_empty_; ///< NS RRset without any nameservers
+ RRsetPtr rrv6_; ///< Standard RRset, IN, AAAA, lowercase name
+ RRsetPtr rrnet_; ///< example.net A RRset
+ Name ns_name_; ///< Nameserver name of ns.example.net
+};
+
+} // 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
new file mode 100644
index 0000000..85cbcbf
--- /dev/null
+++ b/src/lib/nsas/tests/random_number_generator_unittest.cc
@@ -0,0 +1,309 @@
+// 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 <config.h>
+
+#include <gtest/gtest.h>
+#include <boost/shared_ptr.hpp>
+
+#include <algorithm>
+#include <iostream>
+#include <vector>
+
+#include "random_number_generator.h"
+
+namespace isc {
+namespace nsas {
+
+using namespace std;
+
+/// \brief Test Fixture Class for uniform random number generator
+///
+/// The hard part for this test is how to test that the number is random?
+/// and how to test that the number is uniformly distributed?
+/// Or maybe we can trust the boost implementation
+class UniformRandomIntegerGeneratorTest : public ::testing::Test {
+public:
+ UniformRandomIntegerGeneratorTest():
+ gen_(min_, max_)
+ {
+ }
+ virtual ~UniformRandomIntegerGeneratorTest(){}
+
+ int gen() { return gen_(); }
+ int max() const { return max_; }
+ int min() const { return min_; }
+
+private:
+ UniformRandomIntegerGenerator gen_;
+
+ const static int min_ = 1;
+ const static int max_ = 10;
+};
+
+// Some validation tests will incur performance penalty, so the tests are
+// made only in "debug" version with assert(). But if NDEBUG is defined
+// 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.
+// Note: the death test is not supported by all platforms. We need to
+// compile tests using it selectively.
+#if !defined(NDEBUG)
+// Test of the constructor
+TEST_F(UniformRandomIntegerGeneratorTest, Constructor) {
+ // The range must be min<=max
+ ASSERT_THROW(UniformRandomIntegerGenerator(3, 2), InvalidLimits);
+}
+#endif
+
+// Test of the generated integers are in the range [min, max]
+TEST_F(UniformRandomIntegerGeneratorTest, IntegerRange) {
+ vector<int> numbers;
+
+ // Generate a lot of random integers
+ for (int i = 0; i < max()*10; ++i) {
+ numbers.push_back(gen());
+ }
+
+ // Remove the duplicated values
+ sort(numbers.begin(), numbers.end());
+ vector<int>::iterator it = unique(numbers.begin(), numbers.end());
+
+ // make sure the numbers are in range [min, max]
+ ASSERT_EQ(it - numbers.begin(), max() - min() + 1);
+}
+
+/// \brief Test Fixture Class for weighted random number generator
+class WeightedRandomIntegerGeneratorTest : public ::testing::Test {
+public:
+ WeightedRandomIntegerGeneratorTest()
+ { }
+
+ virtual ~WeightedRandomIntegerGeneratorTest()
+ { }
+};
+
+// Test of the weighted random number generator constructor
+TEST_F(WeightedRandomIntegerGeneratorTest, Constructor) {
+ vector<double> probabilities;
+
+ // If no probabilities is provided, the smallest integer will always be generated
+ WeightedRandomIntegerGenerator gen(probabilities, 123);
+ for (int i = 0; i < 100; ++i) {
+ ASSERT_EQ(gen(), 123);
+ }
+
+/// Some validation tests will incur performance penalty, so the tests are
+/// made only in "debug" version with assert(). But if NDEBUG is defined
+/// 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)
+ //The probability must be >= 0
+ probabilities.push_back(-0.1);
+ probabilities.push_back(1.1);
+ 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_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_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_THROW(WeightedRandomIntegerGenerator gen5(probabilities), SumNotOne);
+#endif
+}
+
+// Test the randomization of the generator
+TEST_F(WeightedRandomIntegerGeneratorTest, WeightedRandomization) {
+ const int repeats = 100000;
+ // We repeat the simulation for N=repeats times
+ // for each probability p, its average is mu = N*p
+ // variance sigma^2 = N * p * (1-p)
+ // sigma = sqrt(N*2/9)
+ // we should make sure that mu - 4sigma < count < mu + 4sigma
+ // which means for 99.99366% of the time this should be true
+ {
+ double p = 0.5;
+ vector<double> probabilities;
+ probabilities.push_back(p);
+ probabilities.push_back(p);
+
+ // Uniformly generated integers
+ WeightedRandomIntegerGenerator gen(probabilities);
+ int c1 = 0;
+ int c2 = 0;
+ for (int i = 0; i < repeats; ++i){
+ int n = gen();
+ if (n == 0) {
+ ++c1;
+ } else if (n == 1) {
+ ++c2;
+ }
+ }
+ double mu = repeats * p;
+ double sigma = sqrt(repeats * p * (1 - p));
+ ASSERT_TRUE(fabs(c1 - mu) < 4*sigma);
+ ASSERT_TRUE(fabs(c2 - mu) < 4*sigma);
+ }
+
+ {
+ vector<double> probabilities;
+ int c1 = 0;
+ int c2 = 0;
+ double p1 = 0.2;
+ double p2 = 0.8;
+ probabilities.push_back(p1);
+ probabilities.push_back(p2);
+ WeightedRandomIntegerGenerator gen(probabilities);
+ for (int i = 0; i < repeats; ++i) {
+ int n = gen();
+ if (n == 0) {
+ ++c1;
+ } else if (n == 1) {
+ ++c2;
+ }
+ }
+ double mu1 = repeats * p1;
+ double mu2 = repeats * p2;
+ double sigma1 = sqrt(repeats * p1 * (1 - p1));
+ double sigma2 = sqrt(repeats * p2 * (1 - p2));
+ ASSERT_TRUE(fabs(c1 - mu1) < 4*sigma1);
+ ASSERT_TRUE(fabs(c2 - mu2) < 4*sigma2);
+ }
+
+ {
+ vector<double> probabilities;
+ int c1 = 0;
+ int c2 = 0;
+ double p1 = 0.8;
+ double p2 = 0.2;
+ probabilities.push_back(p1);
+ probabilities.push_back(p2);
+ WeightedRandomIntegerGenerator gen(probabilities);
+ for (int i = 0; i < repeats; ++i) {
+ int n = gen();
+ if (n == 0) {
+ ++c1;
+ } else if (n == 1) {
+ ++c2;
+ }
+ }
+ double mu1 = repeats * p1;
+ double mu2 = repeats * p2;
+ double sigma1 = sqrt(repeats * p1 * (1 - p1));
+ double sigma2 = sqrt(repeats * p2 * (1 - p2));
+ ASSERT_TRUE(fabs(c1 - mu1) < 4*sigma1);
+ ASSERT_TRUE(fabs(c2 - mu2) < 4*sigma2);
+ }
+
+ {
+ vector<double> probabilities;
+ int c1 = 0;
+ int c2 = 0;
+ int c3 = 0;
+ double p1 = 0.5;
+ double p2 = 0.25;
+ double p3 = 0.25;
+ probabilities.push_back(p1);
+ probabilities.push_back(p2);
+ probabilities.push_back(p3);
+ WeightedRandomIntegerGenerator gen(probabilities);
+ for (int i = 0; i < repeats; ++i){
+ int n = gen();
+ if (n == 0) {
+ ++c1;
+ } else if (n == 1) {
+ ++c2;
+ } else if (n == 2) {
+ ++c3;
+ }
+ }
+ double mu1 = repeats * p1;
+ double mu2 = repeats * p2;
+ double mu3 = repeats * p3;
+ double sigma1 = sqrt(repeats * p1 * (1 - p1));
+ double sigma2 = sqrt(repeats * p2 * (1 - p2));
+ double sigma3 = sqrt(repeats * p3 * (1 - p3));
+ ASSERT_TRUE(fabs(c1 - mu1) < 4*sigma1);
+ ASSERT_TRUE(fabs(c2 - mu2) < 4*sigma2);
+ ASSERT_TRUE(fabs(c3 - mu3) < 4*sigma3);
+ }
+}
+
+// Test the reset function of generator
+TEST_F(WeightedRandomIntegerGeneratorTest, ResetProbabilities) {
+ const int repeats = 100000;
+ vector<double> probabilities;
+ int c1 = 0;
+ int c2 = 0;
+ double p1 = 0.8;
+ double p2 = 0.2;
+ probabilities.push_back(p1);
+ probabilities.push_back(p2);
+ WeightedRandomIntegerGenerator gen(probabilities);
+ for (int i = 0; i < repeats; ++i) {
+ int n = gen();
+ if (n == 0) {
+ ++c1;
+ } else if (n == 1) {
+ ++c2;
+ }
+ }
+ double mu1 = repeats * p1;
+ double mu2 = repeats * p2;
+ double sigma1 = sqrt(repeats * p1 * (1 - p1));
+ double sigma2 = sqrt(repeats * p2 * (1 - p2));
+ ASSERT_TRUE(fabs(c1 - mu1) < 4*sigma1);
+ ASSERT_TRUE(fabs(c2 - mu2) < 4*sigma2);
+
+ // Reset the probabilities
+ probabilities.clear();
+ c1 = c2 = 0;
+ p1 = 0.2;
+ p2 = 0.8;
+ probabilities.push_back(p1);
+ probabilities.push_back(p2);
+ gen.reset(probabilities);
+ for (int i = 0; i < repeats; ++i) {
+ int n = gen();
+ if (n == 0) {
+ ++c1;
+ } else if (n == 1) {
+ ++c2;
+ }
+ }
+ mu1 = repeats * p1;
+ mu2 = repeats * p2;
+ sigma1 = sqrt(repeats * p1 * (1 - p1));
+ sigma2 = sqrt(repeats * p2 * (1 - p2));
+ ASSERT_TRUE(fabs(c1 - mu1) < 4*sigma1);
+ ASSERT_TRUE(fabs(c2 - mu2) < 4*sigma2);
+}
+
+} // namespace nsas
+} // namespace isc
diff --git a/src/lib/nsas/tests/run_unittests.cc b/src/lib/nsas/tests/run_unittests.cc
new file mode 100644
index 0000000..d1277ad
--- /dev/null
+++ b/src/lib/nsas/tests/run_unittests.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2009 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 <dns/tests/unittest_util.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return (RUN_ALL_TESTS());
+}
diff --git a/src/lib/nsas/tests/zone_entry_unittest.cc b/src/lib/nsas/tests/zone_entry_unittest.cc
new file mode 100644
index 0000000..34f995c
--- /dev/null
+++ b/src/lib/nsas/tests/zone_entry_unittest.cc
@@ -0,0 +1,753 @@
+// 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 <config.h>
+
+#include <gtest/gtest.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <cmath>
+
+#include <dns/rrclass.h>
+#include <dns/rdataclass.h>
+
+#include "../asiolink.h"
+#include "../zone_entry.h"
+#include "../nameserver_entry.h"
+#include "../address_request_callback.h"
+#include "../nsas_entry_compare.h"
+#include "../hash_deleter.h"
+
+#include "nsas_test.h"
+
+using namespace isc::nsas;
+using namespace asiolink;
+using namespace std;
+using namespace isc::dns;
+
+namespace {
+
+/// \brief Inherited version with access into its internals for tests
+class InheritedZoneEntry : public ZoneEntry {
+ public:
+ InheritedZoneEntry(
+ boost::shared_ptr<isc::resolve::ResolverInterface> resolver,
+ const std::string& name, const RRClass& class_code,
+ boost::shared_ptr<HashTable<NameserverEntry> > nameserver_table,
+ boost::shared_ptr<LruList<NameserverEntry> > nameserver_lru) :
+ ZoneEntry(resolver.get(), name, class_code, nameserver_table,
+ nameserver_lru)
+ { }
+ NameserverVector& nameservers() { return nameservers_; }
+};
+
+/// \brief Test Fixture Class
+class ZoneEntryTest : public TestWithRdata {
+protected:
+ /// \brief Constructor
+ ZoneEntryTest() :
+ nameserver_table_(new HashTable<NameserverEntry>(
+ new NsasEntryCompare<NameserverEntry>)),
+ nameserver_lru_(new LruList<NameserverEntry>(
+ (3 * nameserver_table_->tableSize()),
+ new HashDeleter<NameserverEntry>(*nameserver_table_))),
+ resolver_(new TestResolver),
+ callback_(new Callback)
+ { }
+ /// \brief Tables of nameservers to pass into zone entry constructor
+ boost::shared_ptr<HashTable<NameserverEntry> > nameserver_table_;
+ boost::shared_ptr<LruList<NameserverEntry> > nameserver_lru_;
+ /// \brief The resolver
+ boost::shared_ptr<TestResolver> resolver_;
+
+ /**
+ * \short A callback we use into the zone.
+ *
+ * It counts failures and stores successufll results.
+ */
+ struct Callback : public AddressRequestCallback {
+ Callback() : unreachable_count_(0) {}
+ size_t unreachable_count_;
+ vector<NameserverAddress> successes_;
+ virtual void unreachable() { unreachable_count_ ++; }
+ virtual void success(const NameserverAddress& address) {
+ successes_.push_back(address);
+ }
+ };
+ boost::shared_ptr<Callback> callback_;
+
+ // Empty callback to pass to nameserver entry, to do injection of them
+ struct NseCallback : public NameserverEntry::Callback {
+ virtual void operator()(boost::shared_ptr<NameserverEntry>) { }
+ };
+
+ boost::shared_ptr<NseCallback> nseCallback() {
+ return (boost::shared_ptr<NseCallback>(new NseCallback));
+ }
+
+ /**
+ * \short Function returning a new zone.
+ *
+ * Convenience funcion, just creating a new zone, to shorten the code.
+ */
+ boost::shared_ptr<InheritedZoneEntry> getZone() {
+ return (boost::shared_ptr<InheritedZoneEntry>(new InheritedZoneEntry(
+ resolver_, EXAMPLE_CO_UK, RRClass::IN(), nameserver_table_,
+ nameserver_lru_)));
+ }
+
+ /**
+ * \short Creates and injects a NameserverEntry
+ *
+ * This is used by few tests checking it works when the nameserver
+ * hash table already contains the NameserverEntry. This function
+ * creates one and puts it into the hash table.
+ */
+ boost::shared_ptr<NameserverEntry> injectEntry() {
+ boost::shared_ptr<NameserverEntry> nse(new NameserverEntry(ns_name_.toText(),
+ RRClass::IN()));
+ nameserver_table_->add(nse, HashKey(ns_name_.toText(), RRClass::IN()));
+ EXPECT_EQ(Fetchable::NOT_ASKED, nse->getState());
+ return (nse);
+ }
+
+ /**
+ * \short Common part of few tests.
+ *
+ * All the tests NameserverEntryReady, NameserverEntryNotAsked,
+ * NameserverEntryInProgress, NameserverEntryExpired,
+ * NameserverEntryUnreachable check that it does not break
+ * when the nameserver hash table already contains the nameserver
+ * in one of the Fetchable::State.
+ *
+ * All the tests prepare the NameserverEntry and then call this
+ * to see if the zone really works. This asks and checks if it
+ * asks for the IP addresses or not and if it succeeds or fails.
+ *
+ * \param answer Should it ask for IP addresses of the nameserver?
+ * If not, it expects it already asked during the preparation
+ * (therefore the request count in resolver is 2).
+ * \param success_count How many callbacks to the zone should
+ * succeed.
+ * \param failure_count How many callbacks to the zone should
+ * fail.
+ */
+ void checkInjected(bool answer, size_t success_count = 1,
+ size_t failure_count = 0)
+ {
+ // Create the zone and check it acts correctly
+ boost::shared_ptr<InheritedZoneEntry> zone(getZone());
+ resolver_->addPresetAnswer(Question(Name(EXAMPLE_CO_UK), RRClass::IN(),
+ RRType::NS()), rr_single_);
+ zone->addCallback(callback_, ANY_OK);
+ EXPECT_EQ(2, resolver_->requests.size());
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 0, 1));
+ if (answer) {
+ EXPECT_NO_THROW(resolver_->answer(0, ns_name_, RRType::A(),
+ rdata::in::A("192.0.2.1")));
+ EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::AAAA(),
+ rdata::in::AAAA("2001:db8::1")));
+ }
+ // Check the answers
+ EXPECT_EQ(Fetchable::READY, zone->getState());
+ 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").equals(
+ callback_->successes_[i].getAddress()) ||
+ IOAddress("2001:db8::1").equals(
+ callback_->successes_[i].getAddress()));
+ }
+ }
+};
+
+/// Tests of the default constructor
+TEST_F(ZoneEntryTest, DefaultConstructor) {
+
+ // Default constructor should not create any RRsets
+ InheritedZoneEntry alpha(resolver_, EXAMPLE_CO_UK,
+ RRClass::IN(), nameserver_table_, nameserver_lru_);
+ EXPECT_EQ(EXAMPLE_CO_UK, alpha.getName());
+ EXPECT_EQ(RRClass::IN(), alpha.getClass());
+ EXPECT_TRUE(alpha.nameservers().empty());
+}
+
+/**
+ * \short Test with no nameservers.
+ *
+ * When we create a zone that does not have any nameservers,
+ * it should return failures right away (eg. no queries to nameservers
+ * should be generated anywhere and the failure should be provided).
+ */
+TEST_F(ZoneEntryTest, CallbackNoNS) {
+ boost::shared_ptr<InheritedZoneEntry> zone(getZone());
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+ // feed it with a callback
+ zone->addCallback(callback_, ANY_OK);
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+ // Give it the (empty) nameserver list
+ EXPECT_NO_THROW(resolver_->provideNS(0, rr_empty_));
+ // It should tell us it is unreachable.
+ EXPECT_TRUE(callback_->successes_.empty());
+ EXPECT_EQ(1, callback_->unreachable_count_);
+}
+
+/**
+ * \short Test how the zone behaves when the list of nameserves change.
+ *
+ * We use TTL of 0, so it asks every time for new list of nameservers.
+ * This allows us to pass a different list each time.
+ *
+ * So, this implicitly tests that it behaves correctly with 0 TTL as well,
+ * it means that it answers even with 0 TTL and that it answers only
+ * one query (or, all queries queued at that time).
+ *
+ * We change the list twice, to see it can ask for another nameserver and
+ * then to see if it can return to the previous (already cached) nameserver.
+ */
+TEST_F(ZoneEntryTest, ChangedNS) {
+ // Make it zero TTL, so it expires right away
+ rr_single_->setTTL(RRTTL(0));
+ boost::shared_ptr<InheritedZoneEntry> zone(getZone());
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+ // Feed it with callback
+ zone->addCallback(callback_, ANY_OK);
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+ EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
+ // It should not be answered yet, it should ask for the IP addresses
+ // (trough the NameserverEntry there)
+ EXPECT_TRUE(callback_->successes_.empty());
+ EXPECT_EQ(0, callback_->unreachable_count_);
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 1, 2));
+ 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").equals(
+ callback_->successes_[0].getAddress()));
+ EXPECT_NO_THROW(resolver_->answer(2, ns_name_, RRType::AAAA(),
+ rdata::in::AAAA("2001:db8::1")));
+ EXPECT_EQ(1, callback_->successes_.size());
+ // It should request the NSs again, as TTL is 0
+ zone->addCallback(callback_, ANY_OK);
+ EXPECT_EQ(4, resolver_->requests.size());
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+
+ Name different_name("ns.example.com");
+ // Create a different answer
+ RRsetPtr different_ns(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(),
+ RRType::NS(), RRTTL(0)));
+ different_ns->addRdata(rdata::generic::NS(different_name));
+ EXPECT_NO_THROW(resolver_->provideNS(3, different_ns));
+ // It should become ready and ask for the new nameserver addresses
+ EXPECT_EQ(Fetchable::READY, zone->getState());
+ // Answer one of the IP addresses, we should get an address now
+ EXPECT_TRUE(resolver_->asksIPs(different_name, 4, 5));
+ 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").equals(
+ callback_->successes_[1].getAddress()));
+
+ // And now, switch back, as it timed out again
+ zone->addCallback(callback_, ANY_OK);
+ EXPECT_EQ(7, resolver_->requests.size());
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+ // When we answer with the original, it should still be cached and
+ // we should get the answer
+ EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
+ 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").equals(
+ callback_->successes_[0].getAddress()));
+}
+
+/**
+ * \short Check it works when everything is answered.
+ *
+ * This test emulates a situation when all queries for NS rrsets and
+ * IP addresses (of the NameserverEntry objects inside) are answered
+ * positively. All the callbacks should be called and answer to them
+ * provided.
+ */
+TEST_F(ZoneEntryTest, CallbacksAnswered) {
+ boost::shared_ptr<InheritedZoneEntry> zone(getZone());
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+ // Feed it with a callback
+ zone->addCallback(callback_, ANY_OK);
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+ EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
+ // It should not be answered yet, its NameserverEntry should ask for the
+ // IP addresses
+ EXPECT_TRUE(callback_->successes_.empty());
+ EXPECT_EQ(0, callback_->unreachable_count_);
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 1, 2));
+ // We should be READY, as it marks we have nameservers
+ // (not that they are ready)
+ EXPECT_EQ(Fetchable::READY, zone->getState());
+ // Give two more callbacks, with different address families
+ zone->addCallback(callback_, V4_ONLY);
+ zone->addCallback(callback_, V6_ONLY);
+ // Nothing more is asked
+ EXPECT_EQ(3, resolver_->requests.size());
+ EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
+ 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").equals(
+ callback_->successes_[0].getAddress()));
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
+ callback_->successes_[1].getAddress()));
+ // None are rejected
+ EXPECT_EQ(0, callback_->unreachable_count_);
+ // Answer the IPv6 one as well
+ EXPECT_NO_THROW(resolver_->answer(2, ns_name_, RRType::AAAA(),
+ rdata::in::AAAA("2001:db8::1")));
+ // This should answer the third callback
+ EXPECT_EQ(0, callback_->unreachable_count_);
+ ASSERT_EQ(3, callback_->successes_.size());
+ EXPECT_TRUE(IOAddress("2001:db8::1").equals(
+ callback_->successes_[2].getAddress()));
+ // It should think it is ready
+ EXPECT_EQ(Fetchable::READY, zone->getState());
+ // When we ask something more, it should be answered right away
+ zone->addCallback(callback_, V4_ONLY);
+ EXPECT_EQ(3, resolver_->requests.size());
+ ASSERT_EQ(4, callback_->successes_.size());
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
+ callback_->successes_[3].getAddress()));
+ EXPECT_EQ(0, callback_->unreachable_count_);
+}
+
+/**
+ * \short Test zone reachable only on IPv4.
+ *
+ * This test simulates a zone with its nameservers reachable only
+ * over IPv4. It means we answer the NS query, the A query, but
+ * we generate a failure for AAAA.
+ *
+ * The callbacks asking for any address and IPv4 address should be
+ * called successfully, while the ones asking for IPv6 addresses should
+ * fail.
+ */
+TEST_F(ZoneEntryTest, CallbacksAOnly) {
+ boost::shared_ptr<InheritedZoneEntry> zone(getZone());
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+ zone->addCallback(callback_, ANY_OK);
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+ EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
+ // It should not be answered yet, it should ask for the IP addresses
+ EXPECT_TRUE(callback_->successes_.empty());
+ EXPECT_EQ(0, callback_->unreachable_count_);
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 1, 2));
+ EXPECT_EQ(Fetchable::READY, zone->getState());
+ // Give two more callbacks, with different address families
+ zone->addCallback(callback_, V4_ONLY);
+ zone->addCallback(callback_, V6_ONLY);
+ ASSERT_GE(resolver_->requests.size(), 3);
+ // We tell its NameserverEntry we can't get IPv6 address
+ resolver_->requests[2].second->failure();
+ // One should be rejected (V6_ONLY one), but two still stay
+ EXPECT_EQ(0, callback_->successes_.size());
+ EXPECT_EQ(1, callback_->unreachable_count_);
+ // Answer the A one and see it answers what can be answered
+ 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").equals(
+ callback_->successes_[0].getAddress()));
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
+ callback_->successes_[1].getAddress()));
+ EXPECT_EQ(1, callback_->unreachable_count_);
+ // Everything arriwed, so we are ready
+ EXPECT_EQ(Fetchable::READY, zone->getState());
+ // Try asking something more and see it asks no more
+ zone->addCallback(callback_, V4_ONLY);
+ EXPECT_EQ(3, resolver_->requests.size());
+ ASSERT_EQ(3, callback_->successes_.size());
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
+ callback_->successes_[2].getAddress()));
+ EXPECT_EQ(1, callback_->unreachable_count_);
+
+ zone->addCallback(callback_, V6_ONLY);
+ EXPECT_EQ(3, resolver_->requests.size());
+ EXPECT_EQ(3, callback_->successes_.size());
+ EXPECT_EQ(2, callback_->unreachable_count_);
+}
+
+/**
+ * \short Test with unreachable and v6-reachable nameserver.
+ *
+ * In this test we have a zone with two nameservers. The first one of them
+ * is unreachable, it will not have any addresses. We check that the ZoneEntry
+ * is patient and asks and waits for the second one and then returns the
+ * (successful) answers to us.
+ */
+TEST_F(ZoneEntryTest, CallbackTwoNS) {
+ boost::shared_ptr<InheritedZoneEntry> zone(getZone());
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+ zone->addCallback(callback_, V4_ONLY);
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+ EXPECT_NO_THROW(resolver_->provideNS(0, rrns_));
+ EXPECT_EQ(Fetchable::READY, zone->getState());
+ // It asks a question (we do not know which nameserver)
+ boost::shared_ptr<Name> name;
+ ASSERT_NO_THROW(name.reset(new Name((*resolver_)[1]->getName())));
+ ASSERT_TRUE(resolver_->asksIPs(*name, 1, 2));
+ resolver_->requests[1].second->failure();
+ // Nothing should be answered or failed yet, there's second one
+ EXPECT_EQ(0, callback_->unreachable_count_);
+ EXPECT_EQ(0, callback_->successes_.size());
+ ASSERT_TRUE(resolver_->asksIPs((*resolver_)[3]->getName(), 3, 4));
+ // Fail the second one
+ resolver_->requests[3].second->failure();
+ // The callback should be failed now, as there is no chance of getting
+ // v4 address
+ EXPECT_EQ(1, callback_->unreachable_count_);
+ EXPECT_EQ(0, callback_->successes_.size());
+ // And question for v6 or any should still wait while v4 should be failed
+ // right away
+ zone->addCallback(callback_, V6_ONLY);
+ EXPECT_EQ(1, callback_->unreachable_count_);
+ EXPECT_EQ(0, callback_->successes_.size());
+
+ zone->addCallback(callback_, ANY_OK);
+ EXPECT_EQ(1, callback_->unreachable_count_);
+ EXPECT_EQ(0, callback_->successes_.size());
+
+ zone->addCallback(callback_, V4_ONLY);
+ EXPECT_EQ(2, callback_->unreachable_count_);
+ EXPECT_EQ(0, callback_->successes_.size());
+ // Answer the IPv6 one
+ EXPECT_NO_THROW(resolver_->answer(2, (*resolver_)[2]->getName(),
+ RRType::AAAA(), rdata::in::AAAA("2001:db8::1")));
+
+ // Ready, as we have at last some address
+ EXPECT_EQ(Fetchable::READY, zone->getState());
+ // 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").equals(
+ callback_->successes_[0].getAddress()));
+ EXPECT_TRUE(IOAddress("2001:db8::1").equals(
+ callback_->successes_[1].getAddress()));
+}
+
+/**
+ * \short This test checks it works with answers from cache.
+ *
+ * The resolver might provide the answer by calling the callback both sometime
+ * later or directly from its resolve method, causing recursion back inside
+ * the ZoneEntry. This test checks it works even in the second case (eg. that
+ * the ZoneEntry is able to handle callback called directly from the
+ * resolve). Tries checking both positive and negative answers.
+ */
+TEST_F(ZoneEntryTest, DirectAnswer) {
+ boost::shared_ptr<InheritedZoneEntry> zone(getZone());
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+
+ // One unsuccessfull attempt, nameservers fail
+ resolver_->addPresetAnswer(Question(Name(EXAMPLE_CO_UK), RRClass::IN(),
+ RRType::NS()), RRsetPtr());
+ zone->addCallback(callback_, ANY_OK);
+ EXPECT_EQ(0, callback_->successes_.size());
+ EXPECT_EQ(1, callback_->unreachable_count_);
+ EXPECT_EQ(0, resolver_->requests.size());
+ EXPECT_EQ(Fetchable::UNREACHABLE, zone->getState());
+
+ // Successfull attempt now
+ zone = getZone();
+ // First, fill the answers to all the questions it should ask
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+ resolver_->addPresetAnswer(Question(Name(EXAMPLE_CO_UK), RRClass::IN(),
+ RRType::NS()), rr_single_);
+ Name ns_name("ns.example.net");
+ rrv4_->setName(ns_name);
+ resolver_->addPresetAnswer(Question(ns_name, RRClass::IN(), RRType::A()),
+ rrv4_);
+ rrv6_->setName(ns_name);
+ resolver_->addPresetAnswer(Question(ns_name, RRClass::IN(),
+ RRType::AAAA()), rrv6_);
+ // Reset the results
+ callback_->unreachable_count_ = 0;
+ // Ask for the IP address
+ zone->addCallback(callback_, ANY_OK);
+ // It should be answered right away, positively
+ EXPECT_EQ(1, callback_->successes_.size());
+ EXPECT_EQ(0, callback_->unreachable_count_);
+ EXPECT_EQ(0, resolver_->requests.size());
+ EXPECT_EQ(Fetchable::READY, zone->getState());
+
+ // Reset the results
+ callback_->successes_.clear();
+ // Now, pretend we do not have IP addresses
+ resolver_->addPresetAnswer(Question(ns_name, RRClass::IN(), RRType::A()),
+ RRsetPtr());
+ resolver_->addPresetAnswer(Question(ns_name, RRClass::IN(),
+ RRType::AAAA()), RRsetPtr());
+ // Get another zone and ask it again. It should fail.
+ // Clean the table first, though, so it does not find the old nameserver
+ nameserver_table_->remove(HashKey(ns_name.toText(), RRClass::IN()));
+ zone = getZone();
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+ zone->addCallback(callback_, ANY_OK);
+ EXPECT_EQ(0, callback_->successes_.size());
+ EXPECT_EQ(1, callback_->unreachable_count_);
+ EXPECT_EQ(0, resolver_->requests.size());
+ // It should be ready, but have no IP addresses on the nameservers
+ EXPECT_EQ(Fetchable::READY, zone->getState());
+}
+
+/**
+ * \short Test it works with timeouting NameserverEntries.
+ *
+ * In this test we have a zone with nameserver addresses at TTL 0.
+ * So, the NameserverEntry expires each time the ZoneEntry tries to get
+ * its addresses and must ask it again.
+ */
+TEST_F(ZoneEntryTest, AddressTimeout) {
+ boost::shared_ptr<InheritedZoneEntry> zone(getZone());
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+ zone->addCallback(callback_, ANY_OK);
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+ EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
+ // It should not be answered yet, it should ask for the IP addresses
+ EXPECT_TRUE(callback_->successes_.empty());
+ EXPECT_EQ(0, callback_->unreachable_count_);
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 1, 2));
+ // We should be READY, as it marks we have nameservers
+ // (not that they are ready)
+ EXPECT_EQ(Fetchable::READY, zone->getState());
+ EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
+ 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").equals(
+ callback_->successes_[0].getAddress()));
+ EXPECT_EQ(0, callback_->unreachable_count_);
+ // As well with IPv6
+ EXPECT_NO_THROW(resolver_->answer(2, ns_name_, RRType::AAAA(),
+ rdata::in::AAAA("2001:db8::1"), 0));
+ EXPECT_EQ(1, callback_->successes_.size());
+ EXPECT_EQ(Fetchable::READY, zone->getState());
+ // When we ask for another one, it should ask for the addresses again
+ zone->addCallback(callback_, ANY_OK);
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 3, 4));
+ EXPECT_EQ(0, callback_->unreachable_count_);
+ EXPECT_EQ(1, callback_->successes_.size());
+ EXPECT_NO_THROW(resolver_->answer(3, ns_name_, RRType::A(),
+ 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").equals(
+ callback_->successes_[1].getAddress()));
+}
+
+/**
+ * \short Injection tests.
+ *
+ * These tests check the ZoneEntry does not break when the nameserver hash
+ * table already contains a NameserverEntry in some given state. Each test
+ * for a different state.
+ */
+//@{
+
+/// \short Test how the zone reacts to a nameserver entry in ready state
+TEST_F(ZoneEntryTest, NameserverEntryReady) {
+ // Inject the entry
+ boost::shared_ptr<NameserverEntry> nse(injectEntry());
+ // Fill it with data
+ 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(),
+ rdata::in::A("192.0.2.1")));
+ EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::AAAA(),
+ rdata::in::AAAA("2001:db8::1")));
+ EXPECT_EQ(Fetchable::READY, nse->getState());
+
+ checkInjected(false);
+}
+
+/// \short Test how the zone reacts to a nameserver in not asked state
+TEST_F(ZoneEntryTest, NameserverEntryNotAsked) {
+ // Inject the entry
+ injectEntry();
+ // We do not need it, nothing to modify on it
+
+ checkInjected(true);
+}
+
+/// \short What if the zone finds a nameserver in progress?
+TEST_F(ZoneEntryTest, NameserverEntryInProgress) {
+ // Prepare the nameserver entry
+ boost::shared_ptr<NameserverEntry> nse(injectEntry());
+ nse->askIP(resolver_.get(), nseCallback(), ANY_OK);
+ EXPECT_EQ(Fetchable::IN_PROGRESS, nse->getState());
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 0, 1));
+
+ checkInjected(true);
+}
+
+/// \short Check Zone's reaction to found expired nameserver
+TEST_F(ZoneEntryTest, NameserverEntryExpired) {
+ boost::shared_ptr<NameserverEntry> nse(injectEntry());
+ 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(),
+ rdata::in::A("192.0.2.1"), 0));
+ EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::AAAA(),
+ rdata::in::AAAA("2001:db8::1"), 0));
+ EXPECT_EQ(Fetchable::READY, nse->getState());
+ NameserverEntry::AddressVector addresses;
+ EXPECT_EQ(Fetchable::EXPIRED, nse->getAddresses(addresses));
+ EXPECT_EQ(Fetchable::EXPIRED, nse->getState());
+ resolver_->requests.clear();
+
+ checkInjected(true);
+}
+
+/// \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_.get(), nseCallback(), ANY_OK);
+ ASSERT_EQ(2, resolver_->requests.size());
+ resolver_->requests[0].second->failure();
+ resolver_->requests[1].second->failure();
+ EXPECT_EQ(Fetchable::UNREACHABLE, nse->getState());
+
+ checkInjected(false, 0, 1);
+}
+
+//@}
+
+// Count hits of each address
+void
+countHits(size_t *counts, const vector<NameserverAddress>& successes) {
+ BOOST_FOREACH(const NameserverAddress& address, successes) {
+ // We use the last digit as an index
+ string address_string(address.getAddress().toText());
+ size_t index(address_string[address_string.size() - 1] - '0' - 1);
+ ASSERT_LT(index, 3);
+ counts[index] ++;
+ }
+}
+
+// Select one address from the address list
+TEST_F(ZoneEntryTest, AddressSelection) {
+ const size_t repeats = 100000;
+ // Create the zone, give it 2 nameservers and total of 3 addresses
+ // (one of them is ipv6)
+ boost::shared_ptr<ZoneEntry> zone(getZone());
+ zone->addCallback(callback_, ANY_OK);
+ EXPECT_NO_THROW(resolver_->provideNS(0, rrns_));
+ ASSERT_GT(resolver_->requests.size(), 1);
+ Name name1(resolver_->requests[1].first->getName());
+ EXPECT_TRUE(resolver_->asksIPs(name1, 1, 2));
+ resolver_->answer(1, name1, RRType::A(), rdata::in::A("192.0.2.1"));
+ resolver_->answer(2, name1, RRType::AAAA(),
+ rdata::in::AAAA("2001:db8::2"));
+ ASSERT_GT(resolver_->requests.size(), 3);
+ Name name2(resolver_->requests[3].first->getName());
+ EXPECT_TRUE(resolver_->asksIPs(name2, 3, 4));
+ resolver_->answer(3, name2, RRType::A(), rdata::in::A("192.0.2.3"));
+ resolver_->requests[4].second->failure();
+
+ boost::shared_ptr<NameserverEntry> ns1(nameserver_table_->get(HashKey(
+ name1.toText(), RRClass::IN()))),
+ ns2(nameserver_table_->get(HashKey(name2.toText(), RRClass::IN())));
+
+ size_t counts[3] = {0, 0, 0};
+ callback_->successes_.clear();
+
+ // Test they have the same probabilities when they have the same RTT
+ for (size_t i(0); i < repeats; ++ i) {
+ zone->addCallback(callback_, ANY_OK);
+ }
+ countHits(counts, callback_->successes_);
+ // We repeat the simulation for N=repeats times
+ // for each address, the probability is p = 1/3, the average mu = N*p
+ // variance sigma^2 = N * p * (1-p) = N * 1/3 * 2/3 = N*2/9
+ // sigma = sqrt(N*2/9)
+ // we should make sure that mu - 4sigma < c < mu + 4sigma
+ // which means for 99.99366% of the time this should be true
+ double p = 1.0 / 3.0;
+ double mu = repeats * p;
+ double sigma = sqrt(repeats * p * (1 - p));
+ for (size_t i(0); i < 3; ++ i) {
+ ASSERT_TRUE(fabs(counts[i] - mu) < 4*sigma);
+ }
+
+ // reset the environment
+ callback_->successes_.clear();
+ counts[0] = counts[1] = counts[2] = 0;
+
+ // Test when the RTT is not the same
+ ns1->setAddressRTT(IOAddress("192.0.2.1"), 1);
+ ns1->setAddressRTT(IOAddress("2001:db8::2"), 2);
+ ns2->setAddressRTT(IOAddress("192.0.2.3"), 3);
+ for (size_t i(0); i < repeats; ++ i) {
+ zone->addCallback(callback_, ANY_OK);
+ }
+ countHits(counts, callback_->successes_);
+ // We expect that the selection probability for each address that
+ // it will be in the range of [mu-4Sigma, mu+4Sigma]
+ double ps[3];
+ ps[0] = 1.0/(1.0 + 1.0/4.0 + 1.0/9.0);
+ ps[1] = (1.0/4.0)/(1.0 + 1.0/4.0 + 1.0/9.0);
+ ps[2] = (1.0/9.0)/(1.0 + 1.0/4.0 + 1.0/9.0);
+ for (size_t i(0); i < 3; ++ i) {
+ double mu = repeats * ps[i];
+ double sigma = sqrt(repeats * ps[i] * (1 - ps[i]));
+ ASSERT_TRUE(fabs(counts[i] - mu) < 4 * sigma);
+ }
+
+ // reset the environment
+ callback_->successes_.clear();
+ counts[0] = counts[1] = counts[2] = 0;
+
+ // Test with unreachable address
+ ns1->setAddressRTT(IOAddress("192.0.2.1"), 1);
+ ns1->setAddressRTT(IOAddress("2001:db8::2"), 100);
+ ns2->setAddressUnreachable(IOAddress("192.0.2.3"));
+ for (size_t i(0); i < repeats; ++ i) {
+ zone->addCallback(callback_, ANY_OK);
+ }
+ countHits(counts, callback_->successes_);
+ // The unreachable one shouldn't be called
+ EXPECT_EQ(0, counts[2]);
+
+ // reset the environment
+ callback_->successes_.clear();
+ counts[0] = counts[1] = counts[2] = 0;
+
+ // Test with all unreachable
+ ns1->setAddressUnreachable(IOAddress("192.0.2.1"));
+ ns1->setAddressUnreachable(IOAddress("2001:db8::2"));
+ ns2->setAddressUnreachable(IOAddress("192.0.2.3"));
+ for (size_t i(0); i < repeats; ++ i) {
+ zone->addCallback(callback_, ANY_OK);
+ }
+ countHits(counts, callback_->successes_);
+ // They should have about the same probability
+ for (size_t i(0); i < 3; ++ i) {
+ ASSERT_TRUE(fabs(counts[i] - mu) < 4*sigma);
+ }
+
+ // TODO: The unreachable server should be changed to reachable after 5minutes, but how to test?
+}
+
+} // namespace
diff --git a/src/lib/nsas/zone_entry.cc b/src/lib/nsas/zone_entry.cc
new file mode 100644
index 0000000..3af70a8
--- /dev/null
+++ b/src/lib/nsas/zone_entry.cc
@@ -0,0 +1,568 @@
+// 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 <map>
+
+#include <config.h>
+
+#include "zone_entry.h"
+#include "address_request_callback.h"
+#include "nameserver_entry.h"
+
+#include <algorithm>
+#include <boost/foreach.hpp>
+#include <boost/bind.hpp>
+#include <dns/rrttl.h>
+#include <dns/rcode.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+
+namespace isc {
+
+using namespace dns;
+
+namespace nsas {
+
+ZoneEntry::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) :
+ expiry_(0),
+ name_(name), class_code_(class_code), resolver_(resolver),
+ nameserver_table_(nameserver_table), nameserver_lru_(nameserver_lru)
+{
+ in_process_[ANY_OK] = false;
+ in_process_[V4_ONLY] = false;
+ in_process_[V6_ONLY] = false;
+}
+
+namespace {
+// Shorter aliases for frequently used types
+typedef isc::locks::scoped_lock<isc::locks::recursive_mutex> Lock; // Local lock, nameservers not locked
+typedef boost::shared_ptr<AddressRequestCallback> CallbackPtr;
+
+/*
+ * Create a nameserver.
+ * Called inside a mutex so it is filled in atomically.
+ */
+boost::shared_ptr<NameserverEntry>
+newNs(const std::string* name, const RRClass* class_code) {
+ return (boost::shared_ptr<NameserverEntry>(new NameserverEntry(*name,
+ *class_code)));
+}
+
+}
+
+/**
+ * \short Callback class that ZoneEntry passes to a resolver.
+ *
+ * We need to ask for the list of nameservers. So we pass ResolverCallback
+ * object to it, when it knows the answer, method of this thing will be
+ * called.
+ *
+ * It is a nested friend class and should be considered as a part of ZoneEntry
+ * code. It manipulates directly ZoneEntry's data members, locks it and like
+ * that. Mostly eliminates C++ bad design of missing lambda functions.
+ */
+class ZoneEntry::ResolverCallback :
+ public isc::resolve::ResolverInterface::Callback {
+ public:
+ /// \short Constructor. Pass "this" zone entry
+ ResolverCallback(boost::shared_ptr<ZoneEntry> entry) :
+ entry_(entry)
+ { }
+ /**
+ * \short It successfully received nameserver list.
+ *
+ * It fills the nameservers into the ZoneEntry whose callback this is.
+ * If there are in the hash table, it is used. If not, they are
+ * created. This might still fail, if the list is empty.
+ *
+ * It then calls process, to go trough the list of nameservers,
+ * examining them and seeing if some addresses are already there
+ * and to ask for the rest of them.
+ */
+ virtual void success(MessagePtr response_message) {
+ Lock lock(entry_->mutex_);
+
+ // TODO: find the correct RRset, not simply the first
+ if (!response_message ||
+ response_message->getRcode() != isc::dns::Rcode::NOERROR() ||
+ response_message->getRRCount(isc::dns::Message::SECTION_ANSWER) == 0) {
+ // todo: define this
+ failureInternal(300);
+ }
+
+ isc::dns::RRsetIterator rrsi =
+ response_message->beginSection(isc::dns::Message::SECTION_ANSWER);
+ const isc::dns::RRsetPtr answer = *rrsi;
+
+ RdataIteratorPtr iterator(answer->getRdataIterator());
+ // If there are no data
+ if (iterator->isLast()) {
+ failureInternal(answer->getTTL().getValue());
+ return;
+ } else {
+ /*
+ * We store the nameservers we have currently (we might have
+ * none, at startup, but when we time out and ask again, we
+ * do), so we can just reuse them instead of looking them up in
+ * the table or creating them.
+ */
+ std::map<string, NameserverPtr> old;
+ BOOST_FOREACH(const NameserverPtr& ptr, entry_->nameservers_) {
+ old[ptr->getName()] = ptr;
+ }
+ /*
+ * List of original nameservers we did not ask for IP address
+ * yet.
+ */
+ set<NameserverPtr> old_not_asked;
+ old_not_asked.swap(entry_->nameservers_not_asked_);
+
+ // Once we have them put aside, remove the original set
+ // of nameservers from the entry
+ entry_->nameservers_.clear();
+ // And put the ones from the answer them, reusing if possible
+ for (; !iterator->isLast(); iterator->next()) {
+ try {
+ // Get the name from there
+ Name ns_name(dynamic_cast<const rdata::generic::NS&>(
+ iterator->getCurrent()).getNSName());
+ // Try to find it in the old ones
+ std::map<string, NameserverPtr>::iterator old_ns(old.find(
+ ns_name.toText()));
+ /*
+ * We didn't have this nameserver before. So we just
+ * look it up in the hash table or create it.
+ */
+ if (old_ns == old.end()) {
+ // Look it up or create it
+ string ns_name_str(ns_name.toText());
+ pair<bool, NameserverPtr> from_hash(
+ entry_->nameserver_table_->getOrAdd(HashKey(
+ ns_name_str, entry_->class_code_), boost::bind(
+ newNs, &ns_name_str, &entry_->class_code_)));
+ // Make it at the front of the list
+ if (from_hash.first) {
+ entry_->nameserver_lru_->add(from_hash.second);
+ } else {
+ entry_->nameserver_lru_->touch(
+ from_hash.second);
+ }
+ // And add it at last to the entry
+ entry_->nameservers_.push_back(from_hash.second);
+ entry_->nameservers_not_asked_.insert(
+ from_hash.second);
+ } else {
+ // We had it before, reuse it
+ entry_->nameservers_.push_back(old_ns->second);
+ // Did we ask it already? If not, it is still not
+ // asked (the one designing std interface must
+ // have been mad)
+ if (old_not_asked.find(old_ns->second) !=
+ old_not_asked.end())
+ {
+ entry_->nameservers_not_asked_.insert(
+ old_ns->second);
+ }
+ }
+ }
+ // OK, we skip this one as it is not NS (log?)
+ catch (bad_cast&) { }
+ }
+
+ // It is unbelievable, but we found no nameservers there
+ if (entry_->nameservers_.empty()) {
+ // So we fail the same way as if we got empty list
+ failureInternal(answer->getTTL().getValue());
+ return;
+ } else {
+ // Ok, we have them. So set us as ready, set our
+ // expiration time and try to answer what we can, ask
+ // if there's still someone to ask.
+ entry_->setState(READY);
+ entry_->expiry_ = answer->getTTL().getValue() + time(NULL);
+ entry_->process(ADDR_REQ_MAX, NameserverPtr());
+ return;
+ }
+ }
+ }
+ /// \short Failed to receive answer.
+ virtual void failure() {
+ failureInternal(300);
+ }
+ private:
+ /**
+ * \short Common function called when "it did not work"
+ *
+ * It marks the ZoneEntry as unreachable and processes callbacks (by
+ * calling process).
+ */
+ void failureInternal(time_t ttl) {
+ Lock lock(entry_->mutex_);
+ entry_->setState(UNREACHABLE);
+ entry_->expiry_ = ttl + time(NULL);
+ // Process all three callback lists and tell them KO
+ entry_->process(ADDR_REQ_MAX, NameserverPtr());
+ }
+ /// \short The entry we are callback of
+ boost::shared_ptr<ZoneEntry> entry_;
+};
+
+void
+ZoneEntry::addCallback(CallbackPtr callback, AddressFamily family,
+ const GlueHints& glue_hints) {
+ Lock lock(mutex_);
+
+ bool ask(false);
+
+ // Look at expiration time
+ if (expiry_ && time(NULL) >= expiry_) {
+ setState(EXPIRED);
+ }
+
+ // We need to ask (again)
+ 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());
+
+ // 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());
+ return;
+ }
+
+ if (ask) {
+ setState(IN_PROGRESS);
+ // Our callback might be directly called from resolve, unlock now
+ QuestionPtr question(new Question(Name(name_), class_code_,
+ RRType::NS()));
+ boost::shared_ptr<ResolverCallback> resolver_callback(
+ new ResolverCallback(shared_from_this()));
+ resolver_->resolve(question, resolver_callback);
+ return;
+ }
+}
+
+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
+template<class Container>
+void
+move(Container& into, Container& from) {
+ into.insert(into.end(), from.begin(), from.end());
+ from.clear();
+}
+
+// Update the address selector according to the RTTs
+//
+// Each address has a probability to be selected if multiple addresses are available
+// The weight factor is equal to 1/(rtt*rtt), then all the weight factors are normalized
+// to make the sum equal to 1.0
+void
+updateAddressSelector(std::vector<NameserverAddress>& addresses,
+ WeightedRandomIntegerGenerator& selector)
+{
+ vector<double> probabilities;
+ BOOST_FOREACH(NameserverAddress& address, addresses) {
+ uint32_t rtt = address.getAddressEntry().getRTT();
+ if(rtt == 0) {
+ isc_throw(RTTIsZero, "The RTT is 0");
+ }
+
+ if(rtt == AddressEntry::UNREACHABLE) {
+ probabilities.push_back(0);
+ } else {
+ probabilities.push_back(1.0/(rtt*rtt));
+ }
+ }
+ // Calculate the sum
+ double sum = accumulate(probabilities.begin(), probabilities.end(), 0.0);
+
+ if(sum != 0) {
+ // Normalize the probabilities to make the sum equal to 1.0
+ for(vector<double>::iterator it = probabilities.begin();
+ it != probabilities.end(); ++it){
+ (*it) /= sum;
+ }
+ } else if(probabilities.size() > 0){
+ // If all the nameservers are unreachable, the sum will be 0
+ // So give each server equal opportunity to be selected.
+ for(vector<double>::iterator it = probabilities.begin();
+ it != probabilities.end(); ++it){
+ (*it) = 1.0/probabilities.size();
+ }
+ }
+
+ selector.reset(probabilities);
+}
+
+}
+
+/**
+ * \short Sets given boolean to false when destroyed.
+ *
+ * This is hack eliminating C++ missing finally. We need to make sure
+ * the value gets set to false when we leave the function, so we use
+ * a Guard object, that sets it when it gets out of scope.
+ */
+class ZoneEntry::ProcessGuard {
+ public:
+ ProcessGuard(bool& guarded) :
+ guarded_(guarded)
+ { }
+ ~ ProcessGuard() {
+ guarded_ = false;
+ }
+ private:
+ bool& guarded_;
+};
+
+/**
+ * \short Callback from NameserverEntry to us.
+ *
+ * We registre object of this class whenever some ZoneEntry has a need to be
+ * notified of a change (received data) inside its NameserverEntry.
+ *
+ * This is part of the ZoneEntry code (not visible from outside, accessing
+ * private functions). It is here just because C++ does not know propper lambda
+ * functions.
+ */
+class ZoneEntry::NameserverCallback : public NameserverEntry::Callback {
+ public:
+ /**
+ * \short Constructor.
+ *
+ * \param entry The ZoneEntry to be notified.
+ * \param family For which address family this change is, so we
+ * do not process all the nameserves and callbacks there.
+ */
+ NameserverCallback(boost::shared_ptr<ZoneEntry> entry, AddressFamily family) :
+ entry_(entry),
+ family_(family)
+ { }
+ /**
+ * \short Callback method.
+ *
+ * This is called by NameserverEntry when the change happens.
+ * We just call process to go trough relevant nameservers and call
+ * any callbacks we can.
+ */
+ virtual void operator()(NameserverPtr ns) {
+ entry_->process(family_, ns);
+ }
+ private:
+ boost::shared_ptr<ZoneEntry> entry_;
+ AddressFamily family_;
+};
+
+void
+ZoneEntry::dispatchFailures(AddressFamily family) {
+ // We extract all the callbacks
+ vector<CallbackPtr> callbacks;
+ if (family == ADDR_REQ_MAX) {
+ move(callbacks_[ANY_OK], callbacks_[V4_ONLY]);
+ move(callbacks_[ANY_OK], callbacks_[V6_ONLY]);
+ family = ANY_OK;
+ }
+ callbacks.swap(callbacks_[family]);
+ BOOST_FOREACH(const CallbackPtr& callback, callbacks) {
+ callback->unreachable();
+ }
+}
+
+void
+ZoneEntry::process(AddressFamily family,
+ const boost::shared_ptr<NameserverEntry>& nameserver)
+{
+ Lock lock(mutex_);
+ switch (getState()) {
+ // These are not interesting, nothing to return now
+ case NOT_ASKED:
+ case IN_PROGRESS:
+ case EXPIRED:
+ break;
+ case UNREACHABLE: {
+ dispatchFailures(family);
+ // And we do nothing more now
+ break;
+ }
+ case READY:
+ if (family == ADDR_REQ_MAX) {
+ // Just process each one separately
+ // TODO Think this over, is it safe, to unlock in the middle?
+ process(ANY_OK, nameserver);
+ process(V4_ONLY, nameserver);
+ process(V6_ONLY, nameserver);
+ } else {
+ // Nothing to do anyway for this family, be dormant
+ if (callbacks_[family].empty()) {
+ return;
+ }
+ /*
+ * If we have multiple nameservers and more than 1 of them
+ * is in the cache, we want to choose from all their addresses.
+ * So we ensure this instance of process is the only one on
+ * the stack. If not, we terminate and let the outernmost
+ * one handle it when we return to it.
+ *
+ * If we didn't do it, one instance would call "resolve". If it
+ * was from cache, it would imediatelly recurse back to another
+ * process (trough the nameserver callback, etc), which would
+ * take that only one nameserver and trigger all callbacks.
+ * Only then would resolve terminate and we could ask for the
+ * second nameserver. This way, we first receive all the
+ * nameservers that are already in cache and trigger the
+ * callbacks only then.
+ *
+ * However, this does not wait for external fetches of
+ * nameserver addresses, as the callback is called after
+ * process terminates. Therefore this waits only for filling
+ * of the nameservers which we already have in cache.
+ */
+ if (in_process_[family]) {
+ return;
+ }
+ // Mark we are on the stack
+ ProcessGuard guard(in_process_[family]);
+ in_process_[family] = true;
+ // Variables to store the data to
+ NameserverEntry::AddressVector addresses;
+ NameserverVector to_ask;
+ bool pending(false);
+
+ // Pick info from the nameservers
+ BOOST_FOREACH(const NameserverPtr& ns, nameservers_) {
+ Fetchable::State ns_state(ns->getAddresses(addresses,
+ family, ns == nameserver));
+ switch (ns_state) {
+ case IN_PROGRESS:
+ pending = true;
+ // Someone asked it, but not us, we don't have
+ // callback
+ if (nameservers_not_asked_.find(ns) !=
+ nameservers_not_asked_.end())
+ {
+ to_ask.push_back(ns);
+ }
+ break;
+ case NOT_ASKED:
+ case EXPIRED:
+ to_ask.push_back(ns);
+ break;
+ case UNREACHABLE:
+ case READY:
+ // Not interested, but avoiding warning
+ break;
+ }
+ }
+
+ // We have someone to ask, so do it
+ if (!to_ask.empty()) {
+ // We ask everything that makes sense now
+ nameservers_not_asked_.clear();
+ /*
+ * TODO: Possible place for an optimisation. We now ask
+ * everything we can. We should limit this to something like
+ * 2 concurrent NS fetches (and fetch cache first, then
+ * fetch the remote ones). But fetching everything right
+ * away is simpler.
+ */
+ BOOST_FOREACH(const NameserverPtr& ns, to_ask) {
+ // Put all 3 callbacks there. If we put just the
+ // current family, it might not work due to missing
+ // callback for different one.
+ // If they recurse back to us (call directly), we kill
+ // it by the in_process_
+ insertCallback(ns, ADDR_REQ_MAX);
+ }
+ // Retry with all the data that might have arrived
+ in_process_[family] = false;
+ // We do not provide the callback again
+ process(family, nameserver);
+ // And be done
+ return;
+ // We have some addresses to answer
+ } else if (!addresses.empty()) {
+ // Prepare the selector of addresses
+ // TODO: Think of a way how to keep it for a while
+ // (not update every time)
+ updateAddressSelector(addresses, address_selector);
+
+ // Extract the callbacks
+ vector<CallbackPtr> to_execute;
+ // FIXME: Think of a solution where we do not lose
+ // any callbacks upon exception
+ to_execute.swap(callbacks_[family]);
+
+ // Run the callbacks
+ BOOST_FOREACH(const CallbackPtr& callback, to_execute) {
+ callback->success(addresses[address_selector()]);
+ }
+ return;
+ } else if (!pending) {
+ dispatchFailures(family);
+ return;
+ }
+ }
+ return;
+ }
+}
+
+void
+ZoneEntry::insertCallback(NameserverPtr ns, AddressFamily family) {
+ if (family == ADDR_REQ_MAX) {
+ insertCallback(ns, ANY_OK);
+ insertCallback(ns, V4_ONLY);
+ insertCallback(ns, V6_ONLY);
+ } else {
+ boost::shared_ptr<NameserverCallback> callback(new NameserverCallback(
+ shared_from_this(), family));
+ ns->askIP(resolver_, callback, family);
+ }
+}
+
+}; // namespace nsas
+}; // namespace isc
diff --git a/src/lib/nsas/zone_entry.h b/src/lib/nsas/zone_entry.h
new file mode 100644
index 0000000..92ac75a
--- /dev/null
+++ b/src/lib/nsas/zone_entry.h
@@ -0,0 +1,191 @@
+// 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 __ZONE_ENTRY_H
+#define __ZONE_ENTRY_H
+
+#include <string>
+#include <vector>
+#include <set>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <dns/rrset.h>
+
+#include <resolve/resolver_interface.h>
+
+#include "locks.h"
+#include "hash_key.h"
+#include "nsas_entry.h"
+#include "asiolink.h"
+#include "fetchable.h"
+#include "nsas_types.h"
+#include "random_number_generator.h"
+#include "glue_hints.h"
+
+namespace isc {
+namespace nsas {
+
+class NameserverEntry;
+class AddressRequestCallback;
+
+/// \brief Zone Entry
+///
+/// The zone entry object describes a zone for which nameserver address
+/// information is held.
+///
+/// Although the interface is simple, the internal processing is fairly
+/// complicated, in that the class takes account of triggering fetches for
+/// addresses of nameservers when the address records expire.
+///
+/// It uses shared_from_this in its methods. It must live inside a shared_ptr.
+
+class ZoneEntry : public NsasEntry<ZoneEntry>, public Fetchable {
+public:
+
+ /**
+ * \brief Constructor.
+ *
+ * It asks the resolver any needed questions to get the nameservers.
+ *
+ * \param resolver The resolver used to ask for IP addresses
+ * \param name Name of the zone
+ * \param class_code Class of this zone (zones of different classes have
+ * different objects.
+ * \param nameserver_table Hashtable of NameServerEntry objects for
+ * this zone
+ * \param namesever_lru LRU for the nameserver entries
+ * \todo Move to cc file, include the lookup (if NSAS uses resolver for
+ * everything)
+ */
+ 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);
+
+ /// \return Name of the zone
+ std::string getName() const {
+ return name_;
+ }
+
+ /// \return Class of zone
+ const isc::dns::RRClass& getClass() const {
+ return class_code_;
+ }
+
+ /// \return Return Hash Key
+ virtual HashKey hashKey() const {
+ return HashKey(name_, class_code_);
+ }
+
+ /**
+ * \short Put another callback inside.
+ *
+ * This callback is either executed right away, if it is possible,
+ * or queued for later.
+ *
+ * \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,
+ 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.
+ //@{
+protected:
+ // TODO Read-Write lock?
+ typedef boost::shared_ptr<NameserverEntry> NameserverPtr;
+ typedef std::vector<NameserverPtr> NameserverVector;
+ NameserverVector nameservers_; ///< Nameservers
+ // Which nameservers didn't have any of our callbacks yet
+ std::set<NameserverPtr> nameservers_not_asked_;
+ /*
+ * Callbacks. For each fimily type one vector, so we can process
+ * them separately.
+ */
+ std::vector<boost::shared_ptr<AddressRequestCallback> >
+ callbacks_[ADDR_REQ_MAX];
+ time_t expiry_; ///< Expiry time of this entry, 0 means not set
+ //}@
+private:
+ mutable isc::locks::recursive_mutex mutex_;///< Mutex protecting this zone entry
+ std::string name_; ///< Canonical zone name
+ isc::dns::RRClass class_code_; ///< Class code
+ /**
+ * \short Process all the callbacks that can be processed
+ *
+ * The purpose of this funtion is to ask all nameservers for their IP
+ * addresses and execute all callbacks that can be executed. It is
+ * called whenever new callback appears and there's a chance it could
+ * be answered or when new information is available (list of nameservers,
+ * nameserver is unreachable or has an address).
+ * \param family Which is the interesting address family where the change
+ * happened. ADDR_REQ_MAX means it could be any of them and it will
+ * trigger processing of all callbacks no matter what their family
+ * was.
+ * \param nameserver Pass a nameserver if the change was triggered by
+ * the nameserver (if it wasn't triggered by a nameserver, pass empty
+ * pointer). This one will be accepted even with 0 TTL, the information
+ * just arrived and we are allowed to use it just now.
+ * \todo With the recursive locks now, we might want to simplify executing
+ * callbacks (here and other functions as well);
+ */
+ void process(AddressFamily family,
+ const boost::shared_ptr<NameserverEntry>& nameserver);
+ // Resolver we use
+ 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_;
+ boost::shared_ptr<LruList<NameserverEntry> > nameserver_lru_;
+ // Resolver callback class, documentation with the class declaration
+ class ResolverCallback;
+ // It has direct access to us
+ friend class ResolverCallback;
+ // Guard class to eliminate missing finally
+ class ProcessGuard;
+ friend class ProcessGuard;
+ // Are we in the process method?
+ bool in_process_[ADDR_REQ_MAX];
+ // Callback from nameserver entry (documented with the class)
+ class NameserverCallback;
+ // And it can get into our internals as well (call process)
+ friend class NameserverCallback;
+ // This dispatches callbacks of given family with failures
+ void dispatchFailures(AddressFamily family);
+ // Put a callback into the nameserver entry. Same ADDR_REQ_MAX means for
+ // all families
+ void insertCallback(NameserverPtr nameserver, AddressFamily family);
+ // A random generator for this zone entry
+ // TODO: A more global one? Per thread one?
+ WeightedRandomIntegerGenerator address_selector;
+};
+
+} // namespace nsas
+} // namespace isc
+
+#endif // __ZONE_ENTRY_H
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/cc/data.py b/src/lib/python/isc/cc/data.py
index 094586f..ce1bba0 100644
--- a/src/lib/python/isc/cc/data.py
+++ b/src/lib/python/isc/cc/data.py
@@ -31,10 +31,8 @@ def remove_identical(a, b):
to_remove = []
if type(a) != dict or type(b) != dict:
raise DataTypeError("Not a dict in remove_identical()")
- for ka in a.keys():
- if ka in b and a[ka] == b[ka]:
- to_remove.append(ka)
- for id in to_remove:
+ duplicate_keys = [key for key in a.keys() if key in b and a[key] == b[key]]
+ for id in duplicate_keys:
del(a[id])
def merge(orig, new):
@@ -43,32 +41,131 @@ def merge(orig, new):
new it will be removed in orig."""
if type(orig) != dict or type(new) != dict:
raise DataTypeError("Not a dict in merge()")
- for kn in new.keys():
- if kn in orig:
- if new[kn]:
- if type(new[kn]) == dict:
- merge(orig[kn], new[kn])
- else:
- orig[kn] = new[kn]
- else:
- del orig[kn]
- else:
- orig[kn] = new[kn]
+ orig.update(new)
+ remove_null_items(orig)
-def find(element, identifier):
- """Returns the subelement in the given data element, raises DataNotFoundError if not found"""
- if type(identifier) != str or (type(element) != dict and identifier != ""):
- raise DataTypeError("identifier in merge() is not a string")
- if type(identifier) != str or (type(element) != dict and identifier != ""):
- raise DataTypeError("element in merge() is not a dict")
- id_parts = identifier.split("/")
+def remove_null_items(d):
+ """Recursively removes all (key,value) pairs from d where the
+ value is None"""
+ null_keys = []
+ for key in d.keys():
+ if type(d[key]) == dict:
+ remove_null_items(d[key])
+ elif d[key] is None:
+ null_keys.append(key)
+ for k in null_keys:
+ del d[k]
+
+def _concat_identifier(id_parts):
+ """Concatenates the given identifier parts into a string,
+ delimited with the '/' character.
+ """
+ return '/'.join(id_parts)
+
+def split_identifier(identifier):
+ """Splits the given identifier into a list of identifier parts,
+ as delimited by the '/' character.
+ Raises a DataTypeError if identifier is not a string."""
+ if type(identifier) != str:
+ raise DataTypeError("identifier is not a string")
+ id_parts = identifier.split('/')
id_parts[:] = (value for value in id_parts if value != "")
+ return id_parts
+
+def split_identifier_list_indices(identifier):
+ """Finds list indexes in the given identifier, which are of the
+ format [integer].
+ Identifier must be a string.
+ This will only give the list index for the last 'part' of the
+ given identifier (as delimited by the '/' sign).
+ Raises a DataTypeError if the identifier is not a string,
+ or if the format is bad.
+ Returns a tuple, where the first element is the string part of
+ the identifier, and the second element is a list of (nested) list
+ indices.
+ Examples:
+ 'a/b/c' will return ('a/b/c', None)
+ 'a/b/c[1]' will return ('a/b/c', [1])
+ 'a/b/c[1][2][3]' will return ('a/b/c', [1, 2, 3])
+ 'a[0]/b[1]/c[2]' will return ('a[0]/b[1]/c', [2])
+ """
+ if type(identifier) != str:
+ raise DataTypeError("identifier in "
+ "split_identifier_list_indices() "
+ "not a string: " + str(identifier))
+
+ # We only work on the final 'part' of the identifier
+ id_parts = split_identifier(identifier)
+ id_str = id_parts[-1]
+
+ i = id_str.find('[')
+ if i < 0:
+ if id_str.find(']') >= 0:
+ raise DataTypeError("Bad format in identifier (] but no [): " + str(identifier))
+ return identifier, None
+
+ # keep the non-index part of that to replace later
+ id = id_str[:i]
+ indices = []
+ while i >= 0:
+ e = id_str.find(']')
+ if e < i + 1:
+ raise DataTypeError("Bad format in identifier (] before [): " + str(identifier))
+ try:
+ indices.append(int(id_str[i+1:e]))
+ except ValueError:
+ raise DataTypeError("List index in " + identifier + " not an integer")
+ id_str = id_str[e + 1:]
+ i = id_str.find('[')
+ if i > 0:
+ raise DataTypeError("Bad format in identifier ([ within []): " + str(identifier))
+ if id.find(']') >= 0 or len(id_str) > 0:
+ raise DataTypeError("Bad format in identifier (extra ]): " + str(identifier))
+
+ # we replace the final part of the original identifier with
+ # the stripped string
+ id_parts[-1] = id
+ id = _concat_identifier(id_parts)
+ return id, indices
+
+def _find_child_el(element, id):
+ """Finds the child of element with the given id. If the id contains
+ [i], where i is a number, and the child element is a list, the
+ i-th element of that list is returned instead of the list itself.
+ Raises a DataTypeError if the element is of wrong type, if id
+ is not a string, or if the id string contains a bad value.
+ Raises a DataNotFoundError if the element at id could not be
+ found.
+ """
+ id, list_indices = split_identifier_list_indices(id)
+ if type(element) == dict and id in element.keys():
+ result = element[id]
+ else:
+ raise DataNotFoundError(id + " in " + str(element))
+ if type(result) == list and list_indices is not None:
+ for list_index in list_indices:
+ if list_index >= len(result):
+ raise DataNotFoundError("Element " + str(list_index) + " in " + str(result))
+ result = result[list_index]
+ return result
+
+def find(element, identifier):
+ """Returns the subelement in the given data element, raises
+ DataNotFoundError if not found.
+ Returns the given element if the identifier is an empty string.
+ Raises a DataTypeError if identifier is not a string, or if
+ identifier is not empty, and element is not a dict.
+ """
+ if type(identifier) != str:
+ raise DataTypeError("identifier in find() is not a str")
+ if identifier == "":
+ return element
+ if type(element) != dict:
+ raise DataTypeError("element in find() is not a dict")
+ id_parts = split_identifier(identifier)
cur_el = element
for id in id_parts:
- if type(cur_el) == dict and id in cur_el.keys():
- cur_el = cur_el[id]
- else:
- raise DataNotFoundError(identifier + " in " + str(element))
+ cur_el = _find_child_el(cur_el, id)
return cur_el
def set(element, identifier, value):
@@ -82,25 +179,46 @@ def set(element, identifier, value):
if type(element) != dict:
raise DataTypeError("element in set() is not a dict")
if type(identifier) != str:
- raise DataTypeError("identifier in set() is not a string")
- id_parts = identifier.split("/")
- id_parts[:] = (value for value in id_parts if value != "")
+ raise DataTypeError("identifier in set() is not a str")
+ id_parts = split_identifier(identifier)
cur_el = element
for id in id_parts[:-1]:
- if id in cur_el.keys():
- cur_el = cur_el[id]
- else:
- if value == None:
+ try:
+ cur_el = _find_child_el(cur_el, id)
+ except DataNotFoundError:
+ if value is None:
# ok we are unsetting a value that wasn't set in
# the first place. Simply stop.
return
cur_el[id] = {}
cur_el = cur_el[id]
- # value can be an empty list or dict, so check for None eplicitely
- if value != None:
- cur_el[id_parts[-1]] = value
- elif id_parts[-1] in cur_el:
- del cur_el[id_parts[-1]]
+
+ id, list_indices = split_identifier_list_indices(id_parts[-1])
+ if list_indices is None:
+ # value can be an empty list or dict, so check for None eplicitely
+ if value is not None:
+ cur_el[id] = value
+ else:
+ del cur_el[id]
+ else:
+ cur_el = cur_el[id]
+ # in case of nested lists, we need to get to the next to last
+ for list_index in list_indices[:-1]:
+ if type(cur_el) != list:
+ raise DataTypeError("Element at " + identifier + " is not a list")
+ if len(cur_el) <= list_index:
+ raise DataNotFoundError("List index at " + identifier + " out of range")
+ cur_el = cur_el[list_index]
+ # value can be an empty list or dict, so check for None eplicitely
+ list_index = list_indices[-1]
+ if type(cur_el) != list:
+ raise DataTypeError("Element at " + identifier + " is not a list")
+ if len(cur_el) <= list_index:
+ raise DataNotFoundError("List index at " + identifier + " out of range")
+ if value is not None:
+ cur_el[list_index] = value
+ else:
+ del cur_el[list_index]
return element
def unset(element, identifier):
@@ -115,17 +233,12 @@ def find_no_exc(element, identifier):
"""Returns the subelement in the given data element, returns None
if not found, or if an error occurred (i.e. this function should
never raise an exception)"""
- if type(identifier) != str:
+ try:
+ return find(element, identifier)
+ except DataNotFoundError:
+ return None
+ except DataTypeError:
return None
- id_parts = identifier.split("/")
- id_parts[:] = (value for value in id_parts if value != "")
- cur_el = element
- for id in id_parts:
- if (type(cur_el) == dict and id in cur_el.keys()) or id=="":
- cur_el = cur_el[id]
- else:
- return None
- return cur_el
def parse_value_str(value_str):
"""Parses the given string to a native python object. If the
@@ -138,7 +251,4 @@ def parse_value_str(value_str):
except ValueError as ve:
# simply return the string itself
return value_str
- except SyntaxError as ve:
- # simply return the string itself
- return value_str
diff --git a/src/lib/python/isc/cc/session.py b/src/lib/python/isc/cc/session.py
index 35b9b81..fb7dd06 100644
--- a/src/lib/python/isc/cc/session.py
+++ b/src/lib/python/isc/cc/session.py
@@ -78,6 +78,8 @@ class Session:
raise SessionError("Session has been closed.")
if type(env) == dict:
env = isc.cc.message.to_wire(env)
+ if len(env) > 65535:
+ raise ProtocolError("Envelope too large")
if type(msg) == dict:
msg = isc.cc.message.to_wire(msg)
self._socket.setblocking(1)
@@ -113,9 +115,6 @@ class Session:
if (seq == None and "reply" not in env) or (seq != None and "reply" in env and seq == env["reply"]):
return env, msg
else:
- tmp = None
- if "reply" in env:
- tmp = env["reply"]
self._queue.append((env,msg))
return self.recvmsg(nonblock, seq)
else:
diff --git a/src/lib/python/isc/cc/tests/Makefile.am b/src/lib/python/isc/cc/tests/Makefile.am
index 929110d..dc19758 100644
--- a/src/lib/python/isc/cc/tests/Makefile.am
+++ b/src/lib/python/isc/cc/tests/Makefile.am
@@ -1,16 +1,21 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+
PYTESTS = message_test.py data_test.py session_test.py
# NOTE: test_session.py is to be run manually, so not automated.
EXTRA_DIST = $(PYTESTS)
EXTRA_DIST += sendcmd.py
EXTRA_DIST += test_session.py
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python \
BIND10_TEST_SOCKET_FILE=$(builddir)/test_socket.sock \
- $(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/lib/python/isc/cc/tests/data_test.py b/src/lib/python/isc/cc/tests/data_test.py
index 94e3b82..b8dc222 100644
--- a/src/lib/python/isc/cc/tests/data_test.py
+++ b/src/lib/python/isc/cc/tests/data_test.py
@@ -70,6 +70,11 @@ class TestData(unittest.TestCase):
c = { "a": { "b": "c" } }
data.remove_identical(a, b)
self.assertEqual(a, c)
+
+ self.assertRaises(data.DataTypeError, data.remove_identical,
+ a, 1)
+ self.assertRaises(data.DataTypeError, data.remove_identical,
+ 1, b)
def test_merge(self):
d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } }
@@ -82,6 +87,45 @@ class TestData(unittest.TestCase):
self.assertRaises(data.DataTypeError, data.merge, 1, d2)
self.assertRaises(data.DataTypeError, data.merge, None, None)
+
+ def test_split_identifier_list_indices(self):
+ id, indices = data.split_identifier_list_indices('a')
+ self.assertEqual(id, 'a')
+ self.assertEqual(indices, None)
+ id, indices = data.split_identifier_list_indices('a[0]')
+ self.assertEqual(id, 'a')
+ self.assertEqual(indices, [0])
+ id, indices = data.split_identifier_list_indices('a[0][1]')
+ self.assertEqual(id, 'a')
+ self.assertEqual(indices, [0, 1])
+
+ # examples from the docstring
+ id, indices = data.split_identifier_list_indices('a/b/c')
+ self.assertEqual(id, 'a/b/c')
+ self.assertEqual(indices, None)
+
+ id, indices = data.split_identifier_list_indices('a/b/c[1]')
+ self.assertEqual(id, 'a/b/c')
+ self.assertEqual(indices, [1])
+
+ id, indices = data.split_identifier_list_indices('a/b/c[1][2][3]')
+ self.assertEqual(id, 'a/b/c')
+ self.assertEqual(indices, [1, 2, 3])
+
+ id, indices = data.split_identifier_list_indices('a[0]/b[1]/c[2]')
+ self.assertEqual(id, 'a[0]/b[1]/c')
+ self.assertEqual(indices, [2])
+
+ # bad formats
+ self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a[')
+ self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a]')
+ self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a[[0]]')
+ self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a[0]a')
+ self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a[0]a[1]')
+
+ self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 1)
+
+
def test_find(self):
d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2, 'more': { 'data': 'here' } } }
self.assertEqual(data.find(d1, ''), d1)
@@ -93,19 +137,47 @@ class TestData(unittest.TestCase):
self.assertRaises(data.DataNotFoundError, data.find, d1, 'f')
self.assertRaises(data.DataTypeError, data.find, d1, 1)
self.assertRaises(data.DataTypeError, data.find, None, 1)
+ self.assertRaises(data.DataTypeError, data.find, None, "foo")
self.assertRaises(data.DataTypeError, data.find, "123", "123")
self.assertEqual(data.find("123", ""), "123")
+
+ d2 = { 'a': [ 1, 2, 3 ] }
+ self.assertEqual(data.find(d2, 'a[0]'), 1)
+ self.assertEqual(data.find(d2, 'a[1]'), 2)
+ self.assertEqual(data.find(d2, 'a[2]'), 3)
+ self.assertRaises(data.DataNotFoundError, data.find, d2, 'a[3]')
+ self.assertRaises(data.DataTypeError, data.find, d2, 'a[a]')
+
+ d3 = { 'a': [ { 'b': [ {}, { 'c': 'd' } ] } ] }
+ self.assertEqual(data.find(d3, 'a[0]/b[1]/c'), 'd')
+ self.assertRaises(data.DataNotFoundError, data.find, d3, 'a[1]/b[1]/c')
def test_set(self):
d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } }
d12 = { 'b': 1, 'c': { 'e': 3, 'f': [ 1 ] } }
+ d13 = { 'b': 1, 'c': { 'e': 3, 'f': [ 2 ] } }
+ d14 = { 'b': 1, 'c': { 'e': 3, 'f': [ { 'g': [ 1, 2 ] } ] } }
+ d15 = { 'b': 1, 'c': { 'e': 3, 'f': [ { 'g': [ 1, 3 ] } ] } }
data.set(d1, 'a', None)
data.set(d1, 'c/d', None)
data.set(d1, 'c/e/', 3)
data.set(d1, 'c/f', [ 1 ] )
self.assertEqual(d1, d12)
+ data.set(d1, 'c/f[0]', 2 )
+ self.assertEqual(d1, d13)
+
+ data.set(d1, 'c/f[0]', { 'g': [ 1, 2] } )
+ self.assertEqual(d1, d14)
+ data.set(d1, 'c/f[0]/g[1]', 3)
+ self.assertEqual(d1, d15)
+
self.assertRaises(data.DataTypeError, data.set, d1, 1, 2)
self.assertRaises(data.DataTypeError, data.set, 1, "", 2)
+ self.assertRaises(data.DataTypeError, data.set, d1, 'c[1]', 2)
+ self.assertRaises(data.DataTypeError, data.set, d1, 'c[1][2]', 2)
+ self.assertRaises(data.DataNotFoundError, data.set, d1, 'c/f[5]', 2)
+ self.assertRaises(data.DataNotFoundError, data.set, d1, 'c/f[5][2]', 2)
+
d3 = {}
e3 = data.set(d3, "does/not/exist", 123)
self.assertEqual(d3,
@@ -114,11 +186,25 @@ class TestData(unittest.TestCase):
{ 'does': { 'not': { 'exist': 123 } } })
def test_unset(self):
- d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } }
+ d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': [ 1, 2, 3 ] } }
data.unset(d1, 'a')
data.unset(d1, 'c/d')
data.unset(d1, 'does/not/exist')
- self.assertEqual(d1, { 'b': 1, 'c': { 'e': 2 } })
+ self.assertEqual(d1, { 'b': 1, 'c': { 'e': [ 1, 2, 3 ] } })
+ data.unset(d1, 'c/e[0]')
+ self.assertEqual(d1, { 'b': 1, 'c': { 'e': [ 2, 3 ] } })
+ data.unset(d1, 'c/e[1]')
+ self.assertEqual(d1, { 'b': 1, 'c': { 'e': [ 2 ] } })
+ # index 1 should now be out of range
+ self.assertRaises(data.DataNotFoundError, data.unset, d1, 'c/e[1]')
+ d2 = { 'a': [ { 'b': [ 1, 2 ] } ] }
+ data.unset(d2, 'a[0]/b[1]')
+ self.assertEqual(d2, { 'a': [ { 'b': [ 1 ] } ] })
+ d3 = { 'a': [ [ 1, 2 ] ] }
+ data.set(d3, "a[0][1]", 3)
+ self.assertEqual(d3, { 'a': [ [ 1, 3 ] ] })
+ data.unset(d3, 'a[0][1]')
+ self.assertEqual(d3, { 'a': [ [ 1 ] ] })
def test_find_no_exc(self):
d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2, 'more': { 'data': 'here' } } }
@@ -146,6 +232,9 @@ class TestData(unittest.TestCase):
self.assertEqual(data.parse_value_str("{ \"a\": \"b\", \"c\": 1 }"), { 'a': 'b', 'c': 1 })
self.assertEqual(data.parse_value_str("[ a c"), "[ a c")
+ self.assertEqual(data.parse_value_str(1), None)
+
+
if __name__ == '__main__':
#if not 'CONFIG_TESTDATA_PATH' in os.environ:
# print("You need to set the environment variable CONFIG_TESTDATA_PATH to point to the directory containing the test data files")
diff --git a/src/lib/python/isc/cc/tests/session_test.py b/src/lib/python/isc/cc/tests/session_test.py
index 0f74821..fe35a6c 100644
--- a/src/lib/python/isc/cc/tests/session_test.py
+++ b/src/lib/python/isc/cc/tests/session_test.py
@@ -137,6 +137,11 @@ class testSession(unittest.TestCase):
sess.close()
self.assertRaises(SessionError, sess.sendmsg, {}, {"hello": "a"})
+ def test_env_too_large(self):
+ sess = MySession()
+ largeenv = { "a": "b"*65535 }
+ self.assertRaises(ProtocolError, sess.sendmsg, largeenv, {"hello": "a"})
+
def test_session_sendmsg(self):
sess = MySession()
sess.sendmsg({}, {"hello": "a"})
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index 64c014f..942bf79 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
@@ -24,17 +23,17 @@
# made there as well
"""Classes and functions for handling configuration and commands
- This module provides the ModuleCCSession and UICCSession classes,
- as well as a set of utility functions to create and parse messages
- related to commands and configuration
+ This module provides the ModuleCCSession and UIModuleCCSession
+ classes, as well as a set of utility functions to create and parse
+ messages related to commands and configuration
Modules should use the ModuleCCSession class to connect to the
configuration manager, and receive updates and commands from
other modules.
- Configuration user interfaces should use the UICCSession to connect
- to b10-cmdctl, and receive and send configuration and commands
- through that to the configuration manager.
+ Configuration user interfaces should use the UIModuleCCSession
+ to connect to b10-cmdctl, and receive and send configuration and
+ commands through that to the configuration manager.
"""
from isc.cc import Session
@@ -224,7 +223,7 @@ class ModuleCCSession(ConfigData):
if not self._config_handler:
answer = create_answer(2, self._module_name + " has no config handler")
elif not self.get_module_spec().validate_config(False, new_config, errors):
- answer = create_answer(1, " ".join(errors))
+ answer = create_answer(1, ", ".join(errors))
else:
isc.cc.data.remove_identical(new_config, self.get_local_config())
answer = self._config_handler(new_config)
@@ -285,7 +284,7 @@ class ModuleCCSession(ConfigData):
if answer:
rcode, value = parse_answer(answer)
if rcode == 0:
- if value != None and self.get_module_spec().validate_config(False, value):
+ if value != None and module_spec.validate_config(False, value):
module_cfg.set_local_config(value);
# all done, add it
@@ -374,20 +373,34 @@ class UIModuleCCSession(MultiConfigData):
self._set_current_config(config)
- def add_value(self, identifier, value_str):
+ def add_value(self, identifier, value_str = None):
"""Add a value to a configuration list. Raises a DataTypeError
if the value does not conform to the list_item_spec field
- of the module config data specification"""
+ of the module config data specification. If value_str is
+ not given, we add the default as specified by the .spec
+ file."""
module_spec = self.find_spec_part(identifier)
if (type(module_spec) != dict or "list_item_spec" not in module_spec):
raise isc.cc.data.DataNotFoundError(str(identifier) + " is not a list")
- value = isc.cc.data.parse_value_str(value_str)
+
cur_list, status = self.get_value(identifier)
if not cur_list:
cur_list = []
+
+ # Hmm. Do we need to check for duplicates?
+ value = None
+ if value_str is not None:
+ value = isc.cc.data.parse_value_str(value_str)
+ else:
+ if "item_default" in module_spec["list_item_spec"]:
+ value = module_spec["list_item_spec"]["item_default"]
+
+ if value is None:
+ raise isc.cc.data.DataNotFoundError("No value given and no default for " + str(identifier))
+
if value not in cur_list:
cur_list.append(value)
- self.set_value(identifier, cur_list)
+ self.set_value(identifier, cur_list)
def remove_value(self, identifier, value_str):
"""Remove a value from a configuration list. The value string
@@ -398,22 +411,40 @@ class UIModuleCCSession(MultiConfigData):
module_spec = self.find_spec_part(identifier)
if (type(module_spec) != dict or "list_item_spec" not in module_spec):
raise isc.cc.data.DataNotFoundError(str(identifier) + " is not a list")
- value = isc.cc.data.parse_value_str(value_str)
- isc.config.config_data.check_type(module_spec, [value])
- cur_list, status = self.get_value(identifier)
- #if not cur_list:
- # cur_list = isc.cc.data.find_no_exc(self.config.data, identifier)
- if not cur_list:
- cur_list = []
- if value in cur_list:
- cur_list.remove(value)
- self.set_value(identifier, cur_list)
+
+ if value_str is None:
+ # we are directly removing an list index
+ id, list_indices = isc.cc.data.split_identifier_list_indices(identifier)
+ if list_indices is None:
+ raise DataTypeError("identifier in remove_value() does not contain a list index, and no value to remove")
+ else:
+ self.set_value(identifier, None)
+ else:
+ value = isc.cc.data.parse_value_str(value_str)
+ isc.config.config_data.check_type(module_spec, [value])
+ cur_list, status = self.get_value(identifier)
+ #if not cur_list:
+ # cur_list = isc.cc.data.find_no_exc(self.config.data, identifier)
+ if not cur_list:
+ cur_list = []
+ if value in cur_list:
+ cur_list.remove(value)
+ self.set_value(identifier, cur_list)
def commit(self):
"""Commit all local changes, send them through b10-cmdctl to
the configuration manager"""
if self.get_local_changes():
- self._conn.send_POST('/ConfigManager/set_config', [ self.get_local_changes() ])
- # todo: check result
- self.request_current_config()
- self.clear_local_changes()
+ response = self._conn.send_POST('/ConfigManager/set_config',
+ [ self.get_local_changes() ])
+ answer = isc.cc.data.parse_value_str(response.read().decode())
+ # answer is either an empty dict (on success), or one
+ # containing errors
+ if answer == {}:
+ self.request_current_config()
+ self.clear_local_changes()
+ elif "error" in answer:
+ print("Error: " + answer["error"])
+ print("Configuration not committed")
+ else:
+ raise ModuleCCSessionError("Unknown format of answer in commit(): " + str(answer))
diff --git a/src/lib/python/isc/config/cfgmgr.py b/src/lib/python/isc/config/cfgmgr.py
index 212b67e..8561378 100644
--- a/src/lib/python/isc/config/cfgmgr.py
+++ b/src/lib/python/isc/config/cfgmgr.py
@@ -26,6 +26,7 @@ import os
import copy
import tempfile
import json
+import errno
from isc.cc import data
from isc.config import ccsession, config_data
@@ -43,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 = "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."""
+ 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.
+
+ 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:
@@ -87,7 +99,12 @@ class ConfigManagerData:
else:
raise ConfigManagerDataReadError("No version information in configuration file " + config.db_filename)
except IOError as ioe:
- raise ConfigManagerDataEmpty("No configuration file found")
+ # if IOError is 'no such file or directory', then continue
+ # (raise empty), otherwise fail (raise error)
+ if ioe.errno == errno.ENOENT:
+ raise ConfigManagerDataEmpty("No configuration file found")
+ else:
+ raise ConfigManagerDataReadError("Can't read configuration file: " + str(ioe))
except ValueError:
raise ConfigManagerDataReadError("Configuration file out of date or corrupt, please update or remove " + config.db_filename)
finally:
@@ -136,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:
@@ -217,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):
@@ -270,16 +292,15 @@ class ConfigManager:
else:
return ccsession.create_answer(0, self.config.data)
- def _handle_set_config_module(self, cmd):
+ def _handle_set_config_module(self, module_name, cmd):
# the answer comes (or does not come) from the relevant module
# so we need a variable to see if we got it
answer = None
# todo: use api (and check the data against the definition?)
old_data = copy.deepcopy(self.config.data)
- module_name = cmd[0]
conf_part = data.find_no_exc(self.config.data, module_name)
if conf_part:
- data.merge(conf_part, cmd[1])
+ data.merge(conf_part, cmd)
update_cmd = ccsession.create_command(ccsession.COMMAND_CONFIG_UPDATE,
conf_part)
seq = self.cc.group_sendmsg(update_cmd, module_name)
@@ -289,7 +310,7 @@ class ConfigManager:
answer = ccsession.create_answer(1, "Timeout waiting for answer from " + module_name)
else:
conf_part = data.set(self.config.data, module_name, {})
- data.merge(conf_part[module_name], cmd[1])
+ data.merge(conf_part[module_name], cmd)
# send out changed info
update_cmd = ccsession.create_command(ccsession.COMMAND_CONFIG_UPDATE,
conf_part[module_name])
@@ -309,29 +330,21 @@ class ConfigManager:
def _handle_set_config_all(self, cmd):
old_data = copy.deepcopy(self.config.data)
- data.merge(self.config.data, cmd[0])
- # send out changed info
got_error = False
err_list = []
- for module in self.config.data:
- if module != "version" and \
- (module not in old_data or self.config.data[module] != old_data[module]):
- update_cmd = ccsession.create_command(ccsession.COMMAND_CONFIG_UPDATE,
- self.config.data[module])
- seq = self.cc.group_sendmsg(update_cmd, module)
- try:
- answer, env = self.cc.group_recvmsg(False, seq)
- if answer == None:
- got_error = True
- err_list.append("No answer message from " + module)
- else:
- rcode, val = ccsession.parse_answer(answer)
- if rcode != 0:
- got_error = True
- err_list.append(val)
- except isc.cc.SessionTimeout:
+ # The format of the command is a dict with module->newconfig
+ # sets, so we simply call set_config_module for each of those
+ for module in cmd:
+ if module != "version":
+ answer = self._handle_set_config_module(module, cmd[module])
+ if answer == None:
got_error = True
- err_list.append("CC Timeout waiting on answer message from " + module)
+ err_list.append("No answer message from " + module)
+ else:
+ rcode, val = ccsession.parse_answer(answer)
+ if rcode != 0:
+ got_error = True
+ err_list.append(val)
if not got_error:
self.write_config()
return ccsession.create_answer(0)
@@ -343,12 +356,13 @@ class ConfigManager:
def _handle_set_config(self, cmd):
"""Private function that handles the 'set_config' command"""
answer = None
+
if cmd == None:
return ccsession.create_answer(1, "Wrong number of arguments")
if len(cmd) == 2:
- answer = self._handle_set_config_module(cmd)
+ answer = self._handle_set_config_module(cmd[0], cmd[1])
elif len(cmd) == 1:
- answer = self._handle_set_config_all(cmd)
+ answer = self._handle_set_config_all(cmd[0])
else:
answer = ccsession.create_answer(1, "Wrong number of arguments")
if not answer:
diff --git a/src/lib/python/isc/config/config_data.py b/src/lib/python/isc/config/config_data.py
index b57e910..582c11c 100644
--- a/src/lib/python/isc/config/config_data.py
+++ b/src/lib/python/isc/config/config_data.py
@@ -22,6 +22,7 @@ two through the classes in ccsession)
import isc.cc.data
import isc.config.module_spec
+import ast
class ConfigDataError(Exception): pass
@@ -56,14 +57,14 @@ def check_type(spec_part, value):
raise isc.cc.data.DataTypeError(str(value) + " is not a map")
def convert_type(spec_part, value):
- """Convert the give value(type is string) according specification
+ """Convert the given value(type is string) according specification
part relevant for the value. Raises an isc.cc.data.DataTypeError
exception if conversion failed.
"""
if type(spec_part) == dict and 'item_type' in spec_part:
data_type = spec_part['item_type']
else:
- raise isc.cc.data.DataTypeError(str("Incorrect specification part for type convering"))
+ raise isc.cc.data.DataTypeError(str("Incorrect specification part for type conversion"))
try:
if data_type == "integer":
@@ -81,18 +82,25 @@ def convert_type(spec_part, value):
ret.append(convert_type(spec_part['list_item_spec'], item))
elif type(value) == str:
value = value.split(',')
- for item in value:
+ for item in value:
sub_value = item.split()
for sub_item in sub_value:
- ret.append(convert_type(spec_part['list_item_spec'], sub_item))
+ ret.append(convert_type(spec_part['list_item_spec'],
+ sub_item))
if ret == []:
raise isc.cc.data.DataTypeError(str(value) + " is not a list")
return ret
elif data_type == "map":
- return dict(value)
- # todo: check types of map contents too
+ map = ast.literal_eval(value)
+ if type(map) == dict:
+ # todo: check types of map contents too
+ return map
+ else:
+ raise isc.cc.data.DataTypeError(
+ "Value in convert_type not a string "
+ "specifying a dict")
else:
return value
except ValueError as err:
@@ -108,7 +116,12 @@ def find_spec_part(element, identifier):
id_parts = identifier.split("/")
id_parts[:] = (value for value in id_parts if value != "")
cur_el = element
- for id in id_parts:
+
+ for id_part in id_parts:
+ # strip list selector part
+ # don't need it for the spec part, so just drop it
+ id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
+ # is this part still needed? (see below)
if type(cur_el) == dict and 'map_item_spec' in cur_el.keys():
found = False
for cur_el_item in cur_el['map_item_spec']:
@@ -116,15 +129,24 @@ def find_spec_part(element, identifier):
cur_el = cur_el_item
found = True
if not found:
- raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el))
+ raise isc.cc.data.DataNotFoundError(id + " not found")
+ elif type(cur_el) == dict and 'list_item_spec' in cur_el.keys():
+ cur_el = cur_el['list_item_spec']
elif type(cur_el) == list:
found = False
for cur_el_item in cur_el:
if cur_el_item['item_name'] == id:
cur_el = cur_el_item
+ # if we need to go further, we may need to 'skip' a step here
+ # but not if we're done
+ if id_parts[-1] != id_part and type(cur_el) == dict:
+ if "map_item_spec" in cur_el:
+ cur_el = cur_el["map_item_spec"]
+ elif "list_item_spec" in cur_el:
+ cur_el = cur_el["list_item_spec"]
found = True
if not found:
- raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el))
+ raise isc.cc.data.DataNotFoundError(id + " not found")
else:
raise isc.cc.data.DataNotFoundError("Not a correct config specification")
return cur_el
@@ -158,11 +180,9 @@ def spec_name_list(spec, prefix="", recurse=False):
result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse))
else:
name = list_el['item_name']
- if list_el['item_type'] in ["list", "map"]:
- name += "/"
result.append(prefix + name)
else:
- raise ConfigDataError("Bad specication")
+ raise ConfigDataError("Bad specification")
else:
raise ConfigDataError("Bad specication")
return result
@@ -228,6 +248,20 @@ class ConfigData:
result[item] = value
return result
+# should we just make a class for these?
+def _create_value_map_entry(name, type, value, status = None):
+ entry = {}
+ entry['name'] = name
+ entry['type'] = type
+ entry['value'] = value
+ entry['modified'] = False
+ entry['default'] = False
+ if status == MultiConfigData.LOCAL:
+ entry['modified'] = True
+ if status == MultiConfigData.DEFAULT:
+ entry['default'] = True
+ return entry
+
class MultiConfigData:
"""This class stores the module specs, current non-default
configuration values and 'local' (uncommitted) changes for
@@ -272,7 +306,7 @@ class MultiConfigData:
identifier (up to the first /) is interpreted as the module
name. Returns None if not found, or if identifier is not a
string."""
- if type(identifier) != str:
+ if type(identifier) != str or identifier == "":
return None
if identifier[0] == '/':
identifier = identifier[1:]
@@ -330,43 +364,141 @@ class MultiConfigData:
See get_value() for a general way to find a configuration
value
"""
- if identifier[0] == '/':
- identifier = identifier[1:]
- module, sep, id = identifier.partition("/")
try:
+ if identifier[0] == '/':
+ identifier = identifier[1:]
+ module, sep, id = identifier.partition("/")
+ # if there is a 'higher-level' list index specified, we need
+ # to check if that list specification has a default that
+ # overrides the more specific default in the final spec item
+ # (ie. list_default = [1, 2, 3], list_item_spec=int, default=0)
+ # def default list[1] should return 2, not 0
+ id_parts = isc.cc.data.split_identifier(id)
+ id_prefix = ""
+ while len(id_parts) > 0:
+ id_part = id_parts.pop(0)
+ item_id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
+ id_list = module + "/" + id_prefix + "/" + item_id
+ id_prefix += "/" + id_part
+ if list_indices is not None:
+ # there's actually two kinds of default here for
+ # lists; they can have a default value (like an
+ # empty list), but their elements can also have
+ # default values.
+ # So if the list item *itself* is a default,
+ # we need to get the value out of that. If not, we
+ # need to find the default for the specific element.
+ list_value, type = self.get_value(id_list)
+ list_spec = find_spec_part(self._specifications[module].get_config_spec(), id_prefix)
+ if type == self.DEFAULT:
+ if 'item_default' in list_spec:
+ list_value = list_spec['item_default']
+ for i in list_indices:
+ if i < len(list_value):
+ list_value = list_value[i]
+ else:
+ # out of range, return None
+ return None
+
+ if len(id_parts) > 0:
+ rest_of_id = "/".join(id_parts)
+ return isc.cc.data.find(list_value, rest_of_id)
+ else:
+ return list_value
+ else:
+ # we do have a non-default list, see if our indices
+ # exist
+ for i in list_indices:
+ if i < len(list_value):
+ list_value = list_value[i]
+ else:
+ # out of range, return None
+ return None
+
spec = find_spec_part(self._specifications[module].get_config_spec(), id)
if 'item_default' in spec:
return spec['item_default']
else:
return None
+
except isc.cc.data.DataNotFoundError as dnfe:
return None
- def get_value(self, identifier):
+ def get_value(self, identifier, default = True):
"""Returns a tuple containing value,status.
The value contains the configuration value for the given
identifier. The status reports where this value came from;
it is one of: LOCAL, CURRENT, DEFAULT or NONE, corresponding
(local change, current setting, default as specified by the
- specification, or not found at all)."""
+ specification, or not found at all). Does not check and
+ set DEFAULT if the argument 'default' is False (default
+ defaults to True)"""
value = self.get_local_value(identifier)
if value != None:
return value, self.LOCAL
value = self.get_current_value(identifier)
if value != None:
return value, self.CURRENT
- value = self.get_default_value(identifier)
- if value != None:
- return value, self.DEFAULT
+ if default:
+ value = self.get_default_value(identifier)
+ if value != None:
+ return value, self.DEFAULT
return None, self.NONE
- def get_value_maps(self, identifier = None):
+ def _append_value_item(self, result, spec_part, identifier, all, first = False):
+ # Look at the spec; it is a list of items, or a map containing 'item_name' etc
+ if type(spec_part) == list:
+ for spec_part_element in spec_part:
+ spec_part_element_name = spec_part_element['item_name']
+ self._append_value_item(result, spec_part_element, identifier + "/" + spec_part_element_name, all)
+ elif type(spec_part) == dict:
+ # depending on item type, and the value of argument 'all'
+ # we need to either add an item, or recursively go on
+ # In the case of a list that is empty, we do need to show that
+ item_name = spec_part['item_name']
+ item_type = spec_part['item_type']
+ if item_type == "list" and (all or first):
+ 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
+ if type(list_value) != list:
+ # the identifier specified a single element
+ self._append_value_item(result, spec_part_list, identifier, all)
+ else:
+ list_len = len(list_value)
+ if len(list_value) == 0 and (all or first):
+ entry = _create_value_map_entry(identifier,
+ item_type,
+ [], status)
+ result.append(entry)
+ else:
+ for i in range(len(list_value)):
+ self._append_value_item(result, spec_part_list, "%s[%d]" % (identifier, i), all)
+ elif item_type == "map":
+ # just show the specific contents of a map, we are
+ # almost never interested in just its name
+ spec_part_map = spec_part['map_item_spec']
+ self._append_value_item(result, spec_part_map, identifier, all)
+ else:
+ value, status = self.get_value(identifier)
+ entry = _create_value_map_entry(identifier,
+ item_type,
+ value, status)
+ result.append(entry)
+ return
+
+
+ def get_value_maps(self, identifier = None, all = False):
"""Returns a list of dicts, containing the following values:
name: name of the entry (string)
type: string containing the type of the value (or 'module')
value: value of the entry if it is a string, int, double or bool
- modified: true if the value is a local change
- default: true if the value has been changed
+ modified: true if the value is a local change that has not
+ been committed
+ default: true if the value has not been changed (i.e. the
+ value is the default from the specification)
TODO: use the consts for those last ones
Throws DataNotFoundError if the identifier is bad
"""
@@ -374,13 +506,14 @@ class MultiConfigData:
if not identifier:
# No identifier, so we need the list of current modules
for module in self._specifications.keys():
- entry = {}
- entry['name'] = module
- entry['type'] = 'module'
- entry['value'] = None
- entry['modified'] = False
- entry['default'] = False
- result.append(entry)
+ if all:
+ spec = self.get_module_spec(module)
+ if spec:
+ spec_part = spec.get_config_spec()
+ self._append_value_item(result, spec_part, module, all, True)
+ else:
+ entry = _create_value_map_entry(module, 'module', None)
+ result.append(entry)
else:
if identifier[0] == '/':
identifier = identifier[1:]
@@ -388,52 +521,7 @@ class MultiConfigData:
spec = self.get_module_spec(module)
if spec:
spec_part = find_spec_part(spec.get_config_spec(), id)
- if type(spec_part) == list:
- for item in spec_part:
- entry = {}
- entry['name'] = item['item_name']
- entry['type'] = item['item_type']
- value, status = self.get_value("/" + identifier + "/" + item['item_name'])
- entry['value'] = value
- if status == self.LOCAL:
- entry['modified'] = True
- else:
- entry['modified'] = False
- if status == self.DEFAULT:
- entry['default'] = False
- else:
- entry['default'] = False
- result.append(entry)
- elif type(spec_part) == dict:
- item = spec_part
- if item['item_type'] == 'list':
- li_spec = item['list_item_spec']
- item_list, status = self.get_value("/" + identifier)
- if item_list != None:
- for value in item_list:
- result_part2 = {}
- result_part2['name'] = li_spec['item_name']
- result_part2['value'] = value
- result_part2['type'] = li_spec['item_type']
- result_part2['default'] = False
- result_part2['modified'] = False
- result.append(result_part2)
- else:
- entry = {}
- entry['name'] = item['item_name']
- entry['type'] = item['item_type']
- #value, status = self.get_value("/" + identifier + "/" + item['item_name'])
- value, status = self.get_value("/" + identifier)
- entry['value'] = value
- if status == self.LOCAL:
- entry['modified'] = True
- else:
- entry['modified'] = False
- if status == self.DEFAULT:
- entry['default'] = False
- else:
- entry['default'] = False
- result.append(entry)
+ self._append_value_item(result, spec_part, identifier, all, True)
return result
def set_value(self, identifier, value):
@@ -441,8 +529,31 @@ class MultiConfigData:
there is a specification for the given identifier, the type
is checked."""
spec_part = self.find_spec_part(identifier)
- if spec_part != None:
- check_type(spec_part, value)
+ if spec_part is not None:
+ if value is not None:
+ id, list_indices = isc.cc.data.split_identifier_list_indices(identifier)
+ if list_indices is not None \
+ and spec_part['item_type'] == 'list':
+ spec_part = spec_part['list_item_spec']
+ check_type(spec_part, value)
+ else:
+ raise isc.cc.data.DataNotFoundError(identifier)
+
+ # Since we do not support list diffs (yet?), we need to
+ # copy the currently set list of items to _local_changes
+ # if we want to modify an element in there
+ # (for any list indices specified in the full identifier)
+ id_parts = isc.cc.data.split_identifier(identifier)
+ cur_id_part = '/'
+ for id_part in id_parts:
+ id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
+ if list_indices is not None:
+ cur_list, status = self.get_value(cur_id_part + id)
+ if status != MultiConfigData.LOCAL:
+ isc.cc.data.set(self._local_changes,
+ cur_id_part + id,
+ cur_list)
+ cur_id_part = cur_id_part + id_part + "/"
isc.cc.data.set(self._local_changes, identifier, value)
def get_config_item_list(self, identifier = None, recurse = False):
diff --git a/src/lib/python/isc/config/module_spec.py b/src/lib/python/isc/config/module_spec.py
index 1793cb6..6c90677 100644
--- a/src/lib/python/isc/config/module_spec.py
+++ b/src/lib/python/isc/config/module_spec.py
@@ -328,7 +328,25 @@ def _validate_spec(spec, full, data, errors):
return True
def _validate_spec_list(module_spec, full, data, errors):
+ # we do not return immediately, there may be more errors
+ # so we keep a boolean to keep track if we found errors
+ validated = True
+
+ # check if the known items are correct
for spec_item in module_spec:
if not _validate_spec(spec_item, full, data, errors):
- return False
- return True
+ validated = False
+
+ # check if there are items in our data that are not in the
+ # specification
+ if data is not None:
+ for item_name in data:
+ found = False
+ for spec_item in module_spec:
+ if spec_item["item_name"] == item_name:
+ found = True
+ if not found:
+ if errors != None:
+ errors.append("unknown item " + item_name)
+ validated = False
+ return validated
diff --git a/src/lib/python/isc/config/tests/Makefile.am b/src/lib/python/isc/config/tests/Makefile.am
index e4f6d7a..622b23c 100644
--- a/src/lib/python/isc/config/tests/Makefile.am
+++ b/src/lib/python/isc/config/tests/Makefile.am
@@ -1,16 +1,20 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
PYTESTS = ccsession_test.py cfgmgr_test.py config_data_test.py
PYTESTS += module_spec_test.py
EXTRA_DIST = $(PYTESTS)
EXTRA_DIST += unittest_fakesession.py
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python \
CONFIG_TESTDATA_PATH=$(abs_top_srcdir)/src/lib/config/tests/testdata \
CONFIG_WR_TESTDATA_PATH=$(abs_top_builddir)/src/lib/config/tests/testdata \
- $(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/lib/python/isc/config/tests/ccsession_test.py b/src/lib/python/isc/config/tests/ccsession_test.py
index feb039f..2ae37f5 100644
--- a/src/lib/python/isc/config/tests/ccsession_test.py
+++ b/src/lib/python/isc/config/tests/ccsession_test.py
@@ -13,8 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
#
# Tests for the ConfigData and MultiConfigData classes
#
@@ -290,7 +288,7 @@ class TestModuleCCSession(unittest.TestCase):
mccs = self.create_session("spec2.spec", None, None, fake_session)
mccs.set_config_handler(self.my_config_handler_ok)
self.assertEqual(len(fake_session.message_queue), 0)
- cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'Spec2': { 'item1': 2 }})
+ cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'item1': 2 })
fake_session.group_sendmsg(cmd, 'Spec2')
self.assertEqual(len(fake_session.message_queue), 1)
mccs.check_command()
@@ -303,12 +301,12 @@ class TestModuleCCSession(unittest.TestCase):
mccs = self.create_session("spec2.spec", None, None, fake_session)
mccs.set_config_handler(self.my_config_handler_err)
self.assertEqual(len(fake_session.message_queue), 0)
- cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'Spec2': { 'item1': 'aaa' }})
+ cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'item1': 'aaa' })
fake_session.group_sendmsg(cmd, 'Spec2')
self.assertEqual(len(fake_session.message_queue), 1)
mccs.check_command()
self.assertEqual(len(fake_session.message_queue), 1)
- self.assertEqual({'result': [1, 'just an error']},
+ self.assertEqual({'result': [1, 'aaa should be an integer']},
fake_session.get_message('Spec2', None))
def test_check_command5(self):
@@ -316,12 +314,12 @@ class TestModuleCCSession(unittest.TestCase):
mccs = self.create_session("spec2.spec", None, None, fake_session)
mccs.set_config_handler(self.my_config_handler_exc)
self.assertEqual(len(fake_session.message_queue), 0)
- cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'Spec2': { 'item1': 'aaa' }})
+ cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'item1': 'aaa' })
fake_session.group_sendmsg(cmd, 'Spec2')
self.assertEqual(len(fake_session.message_queue), 1)
mccs.check_command()
self.assertEqual(len(fake_session.message_queue), 1)
- self.assertEqual({'result': [1, 'just an exception']},
+ self.assertEqual({'result': [1, 'aaa should be an integer']},
fake_session.get_message('Spec2', None))
def test_check_command6(self):
@@ -416,7 +414,7 @@ class TestModuleCCSession(unittest.TestCase):
mccs = self.create_session("spec2.spec", None, None, fake_session)
mccs.set_config_handler(self.my_config_handler_ok)
self.assertEqual(len(fake_session.message_queue), 0)
- cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'Spec2': { 'item1': 2 }})
+ cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'item1': 2 })
self.assertEqual(len(fake_session.message_queue), 0)
env = { 'group':'Spec2', 'from':None }
mccs.check_command_without_recvmsg(cmd, env)
@@ -530,7 +528,19 @@ class TestModuleCCSession(unittest.TestCase):
self.assertTrue("Spec2" in fake_session.subscriptions)
mccs = None
self.assertFalse("Spec2" in fake_session.subscriptions)
-
+
+ def test_remote_module_with_custom_config(self):
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ # override the default config value for "item1". add_remote_config()
+ # should incorporate the overridden value, and we should be abel to
+ # get it via get_remote_config_value().
+ fake_session.group_sendmsg({'result': [0, {"item1": 10}]}, 'Spec2')
+ rmodname = mccs.add_remote_config(self.spec_file("spec2.spec"))
+ value, default = mccs.get_remote_config_value(rmodname, "item1")
+ self.assertEqual(10, value)
+ self.assertEqual(False, default)
+
def test_ignore_command_remote_module(self):
# Create a Spec1 module and subscribe to remote config for Spec2
fake_session = FakeModuleCCSession()
@@ -560,6 +570,14 @@ class TestModuleCCSession(unittest.TestCase):
self.assertEqual(len(fake_session.message_queue), 0)
+class fakeData:
+ def decode(self):
+ return "{}";
+
+class fakeAnswer:
+ def read(self):
+ return fakeData();
+
class fakeUIConn():
def __init__(self):
self.get_answers = {}
@@ -581,7 +599,7 @@ class fakeUIConn():
if name in self.post_answers:
return self.post_answers[name]
else:
- return None
+ return fakeAnswer()
class TestUIModuleCCSession(unittest.TestCase):
@@ -637,6 +655,8 @@ class TestUIModuleCCSession(unittest.TestCase):
self.assertEqual({'Spec2': {'item5': ['foo']}}, uccs._local_changes)
uccs.add_value("Spec2/item5", "foo")
self.assertEqual({'Spec2': {'item5': ['foo']}}, uccs._local_changes)
+ uccs.remove_value("Spec2/item5[0]", None)
+ self.assertEqual({'Spec2': {'item5': []}}, uccs._local_changes)
def test_commit(self):
fake_conn = fakeUIConn()
diff --git a/src/lib/python/isc/config/tests/cfgmgr_test.py b/src/lib/python/isc/config/tests/cfgmgr_test.py
index 476644b..9534e14 100644
--- a/src/lib/python/isc/config/tests/cfgmgr_test.py
+++ b/src/lib/python/isc/config/tests/cfgmgr_test.py
@@ -13,8 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
#
# Tests for the configuration manager module
#
@@ -29,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)
@@ -41,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")
@@ -70,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)
@@ -87,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)
@@ -288,6 +312,66 @@ class TestConfigManager(unittest.TestCase):
},
{'result': [0]})
+ def test_set_config_all(self):
+ my_ok_answer = { 'result': [ 0 ] }
+
+ self.assertEqual({"version": 2}, self.cm.config.data)
+
+ self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
+ self.cm._handle_set_config_all({"test": { "value1": 123 }})
+ self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
+ "test": { "value1": 123 }
+ }, self.cm.config.data)
+
+ self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
+ self.cm._handle_set_config_all({"test": { "value1": 124 }})
+ self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
+ "test": { "value1": 124 }
+ }, self.cm.config.data)
+
+ self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
+ self.cm._handle_set_config_all({"test": { "value2": True }})
+ self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
+ "test": { "value1": 124,
+ "value2": True
+ }
+ }, self.cm.config.data)
+
+ self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
+ self.cm._handle_set_config_all({"test": { "value3": [ 1, 2, 3 ] }})
+ self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
+ "test": { "value1": 124,
+ "value2": True,
+ "value3": [ 1, 2, 3 ]
+ }
+ }, self.cm.config.data)
+
+ self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
+ self.cm._handle_set_config_all({"test": { "value2": False }})
+ self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
+ "test": { "value1": 124,
+ "value2": False,
+ "value3": [ 1, 2, 3 ]
+ }
+ }, self.cm.config.data)
+
+ self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
+ self.cm._handle_set_config_all({"test": { "value1": None }})
+ self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
+ "test": { "value2": False,
+ "value3": [ 1, 2, 3 ]
+ }
+ }, self.cm.config.data)
+
+ self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
+ self.cm._handle_set_config_all({"test": { "value3": [ 1 ] }})
+ self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
+ "test": { "value2": False,
+ "value3": [ 1 ]
+ }
+ }, self.cm.config.data)
+
+
def test_run(self):
self.fake_session.group_sendmsg({ "command": [ "get_commands_spec" ] }, "ConfigManager")
self.fake_session.group_sendmsg({ "command": [ "shutdown" ] }, "ConfigManager")
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 f4b44da..1aded94 100644
--- a/src/lib/python/isc/config/tests/config_data_test.py
+++ b/src/lib/python/isc/config/tests/config_data_test.py
@@ -13,8 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
#
# Tests for the ConfigData and MultiConfigData classes
#
@@ -107,6 +105,8 @@ class TestConfigData(unittest.TestCase):
self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, "a")
self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ 1, 2 ])
self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, { "a": 1 })
+ self.assertRaises(isc.cc.data.DataTypeError, convert_type, 1, "a")
+ self.assertRaises(isc.cc.data.DataTypeError, convert_type, { 'somedict': 'somevalue' }, "a")
spec_part = find_spec_part(config_spec, "value2")
self.assertEqual(1.1, convert_type(spec_part, '1.1'))
@@ -146,6 +146,18 @@ class TestConfigData(unittest.TestCase):
self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ "1", "b" ])
self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, { "a": 1 })
+ spec_part = find_spec_part(config_spec, "value6")
+ self.assertEqual({}, convert_type(spec_part, '{}'))
+ self.assertEqual({ 'v61': 'a' }, convert_type(spec_part, '{ \'v61\': \'a\' }'))
+
+ self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, 1.1)
+ self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, True)
+ self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, "a")
+ self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, "1")
+ self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ "a", "b" ])
+ self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ "1", "b" ])
+ self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, { "a": 1 })
+
spec_part = find_spec_part(config_spec, "value7")
self.assertEqual(['1', '2'], convert_type(spec_part, '1, 2'))
self.assertEqual(['1', '2', '3'], convert_type(spec_part, '1 2 3'))
@@ -175,9 +187,9 @@ class TestConfigData(unittest.TestCase):
def test_spec_name_list(self):
name_list = spec_name_list(self.cd.get_module_spec().get_config_spec())
- self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/'], name_list)
+ self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5', 'item6'], name_list)
name_list = spec_name_list(self.cd.get_module_spec().get_config_spec(), "", True)
- self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/value1', 'item6/value2'], name_list)
+ self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5', 'item6/value1', 'item6/value2'], name_list)
spec_part = find_spec_part(self.cd.get_module_spec().get_config_spec(), "item6")
name_list = spec_name_list(spec_part, "item6", True)
self.assertEqual(['item6/value1', 'item6/value2'], name_list)
@@ -193,7 +205,7 @@ class TestConfigData(unittest.TestCase):
name_list = spec_name_list({ "myModule": config_spec }, "", False)
self.assertEqual(['myModule/'], name_list)
name_list = spec_name_list({ "myModule": config_spec }, "", True)
- self.assertEqual(['myModule/', 'myModule/value1', 'myModule/value2', 'myModule/value3', 'myModule/value4', 'myModule/value5/', 'myModule/value6/v61', 'myModule/value6/v62', 'myModule/value7/', 'myModule/value8/', 'myModule/value9/v91', 'myModule/value9/v92/v92a', 'myModule/value9/v92/v92b'], name_list)
+ self.assertEqual(['myModule/', 'myModule/value1', 'myModule/value2', 'myModule/value3', 'myModule/value4', 'myModule/value5', 'myModule/value6/v61', 'myModule/value6/v62', 'myModule/value7', 'myModule/value8', 'myModule/value9/v91', 'myModule/value9/v92/v92a', 'myModule/value9/v92/v92b'], name_list)
self.assertRaises(ConfigDataError, spec_name_list, 1)
self.assertRaises(ConfigDataError, spec_name_list, [ 'a' ])
@@ -240,19 +252,19 @@ class TestConfigData(unittest.TestCase):
def test_get_item_list(self):
name_list = self.cd.get_item_list()
- self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/'], name_list)
+ self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5', 'item6'], name_list)
name_list = self.cd.get_item_list("", True)
- self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/value1', 'item6/value2'], name_list)
+ self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5', 'item6/value1', 'item6/value2'], name_list)
name_list = self.cd.get_item_list("item6", False)
self.assertEqual(['item6/value1', 'item6/value2'], name_list)
def test_get_full_config(self):
full_config = self.cd.get_full_config()
- self.assertEqual({ "item1": 1, "item2": 1.1, "item3": True, "item4": "test", "item5/": ['a', 'b'], "item6/value1": 'default', 'item6/value2': None}, full_config)
+ self.assertEqual({ "item1": 1, "item2": 1.1, "item3": True, "item4": "test", "item5": ['a', 'b'], "item6/value1": 'default', 'item6/value2': None}, full_config)
my_config = { "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5": [ "c", "d" ] }
self.cd.set_local_config(my_config)
full_config = self.cd.get_full_config()
- self.assertEqual({ "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5/": [ "c", "d" ], "item6/value1": 'default', 'item6/value2': None}, full_config)
+ self.assertEqual({ "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5": [ "c", "d" ], "item6/value1": 'default', 'item6/value2': None}, full_config)
class TestMultiConfigData(unittest.TestCase):
def setUp(self):
@@ -321,6 +333,8 @@ class TestMultiConfigData(unittest.TestCase):
pass
def test_get_local_value(self):
+ module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
+ self.mcd.set_specification(module_spec)
value = self.mcd.get_local_value("Spec2/item1")
self.assertEqual(None, value)
self.mcd.set_value("Spec2/item1", 2)
@@ -342,6 +356,14 @@ class TestMultiConfigData(unittest.TestCase):
self.assertEqual(1, value)
value = self.mcd.get_default_value("/Spec2/item1")
self.assertEqual(1, value)
+ value = self.mcd.get_default_value("Spec2/item5[0]")
+ self.assertEqual('a', value)
+ value = self.mcd.get_default_value("Spec2/item5[1]")
+ self.assertEqual('b', value)
+ value = self.mcd.get_default_value("Spec2/item5[5]")
+ self.assertEqual(None, value)
+ value = self.mcd.get_default_value("Spec2/item5[0][1]")
+ self.assertEqual(None, value)
value = self.mcd.get_default_value("Spec2/item6/value1")
self.assertEqual('default', value)
value = self.mcd.get_default_value("Spec2/item6/value2")
@@ -353,20 +375,43 @@ class TestMultiConfigData(unittest.TestCase):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
self.mcd.set_specification(module_spec)
self.mcd.set_value("Spec2/item1", 2)
- value,status = self.mcd.get_value("Spec2/item1")
+
+ value, status = self.mcd.get_value("Spec2/item1")
self.assertEqual(2, value)
self.assertEqual(MultiConfigData.LOCAL, status)
- value,status = self.mcd.get_value("Spec2/item2")
+
+ value, status = self.mcd.get_value("Spec2/item2")
self.assertEqual(1.1, value)
self.assertEqual(MultiConfigData.DEFAULT, status)
+
self.mcd._current_config = { "Spec2": { "item3": False } }
- value,status = self.mcd.get_value("Spec2/item3")
+
+ value, status = self.mcd.get_value("Spec2/item3")
self.assertEqual(False, value)
self.assertEqual(MultiConfigData.CURRENT, status)
- value,status = self.mcd.get_value("Spec2/no_such_item")
+
+ value, status = self.mcd.get_value("Spec2/no_such_item")
self.assertEqual(None, value)
self.assertEqual(MultiConfigData.NONE, status)
+ value, status = self.mcd.get_value("Spec2/item5")
+ self.assertEqual(['a', 'b'], value)
+ self.assertEqual(MultiConfigData.DEFAULT, status)
+
+ value, status = self.mcd.get_value("Spec2/item5[0]")
+ self.assertEqual("a", value)
+ self.assertEqual(MultiConfigData.DEFAULT, status)
+
+ value, status = self.mcd.get_value("Spec2/item5[0]", False)
+ self.assertEqual(None, value)
+ self.assertEqual(MultiConfigData.NONE, status)
+
+ value, status = self.mcd.get_value("Spec2/item5[1]")
+ self.assertEqual("b", value)
+ self.assertEqual(MultiConfigData.DEFAULT, status)
+
+
+
def test_get_value_maps(self):
maps = self.mcd.get_value_maps()
self.assertEqual([], maps)
@@ -389,30 +434,34 @@ class TestMultiConfigData(unittest.TestCase):
self.mcd._set_current_config({ "Spec2": { "item1": 2 } })
self.mcd.set_value("Spec2/item3", False)
maps = self.mcd.get_value_maps("/Spec2")
- self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False},
- {'default': False, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False},
- {'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True},
- {'default': False, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False},
- {'default': False, 'type': 'list', 'name': 'item5', 'value': ['a', 'b'], 'modified': False},
- {'default': False, 'type': 'map', 'name': 'item6', 'value': {}, 'modified': False}], maps)
+ self.assertEqual([{'default': False, 'type': 'integer', 'name': 'Spec2/item1', 'value': 2, 'modified': False},
+ {'default': True, 'type': 'real', 'name': 'Spec2/item2', 'value': 1.1, 'modified': False},
+ {'default': False, 'type': 'boolean', 'name': 'Spec2/item3', 'value': False, 'modified': True},
+ {'default': True, 'type': 'string', 'name': 'Spec2/item4', 'value': 'test', 'modified': False},
+ {'default': True, 'type': 'list', 'name': 'Spec2/item5', 'value': ['a', 'b'], 'modified': False},
+ {'default': True, 'type': 'string', 'name': 'Spec2/item6/value1', 'value': 'default', 'modified': False},
+ {'default': False, 'type': 'integer', 'name': 'Spec2/item6/value2', 'value': None, 'modified': False}], maps)
maps = self.mcd.get_value_maps("Spec2")
- self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False},
- {'default': False, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False},
- {'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True},
- {'default': False, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False},
- {'default': False, 'type': 'list', 'name': 'item5', 'value': ['a', 'b'], 'modified': False},
- {'default': False, 'type': 'map', 'name': 'item6', 'value': {}, 'modified': False}], maps)
+ self.assertEqual([{'default': False, 'type': 'integer', 'name': 'Spec2/item1', 'value': 2, 'modified': False},
+ {'default': True, 'type': 'real', 'name': 'Spec2/item2', 'value': 1.1, 'modified': False},
+ {'default': False, 'type': 'boolean', 'name': 'Spec2/item3', 'value': False, 'modified': True},
+ {'default': True, 'type': 'string', 'name': 'Spec2/item4', 'value': 'test', 'modified': False},
+ {'default': True, 'type': 'list', 'name': 'Spec2/item5', 'value': ['a', 'b'], 'modified': False},
+ {'default': True, 'type': 'string', 'name': 'Spec2/item6/value1', 'value': 'default', 'modified': False},
+ {'default': False, 'type': 'integer', 'name': 'Spec2/item6/value2', 'value': None, 'modified': False}], maps)
maps = self.mcd.get_value_maps("/Spec2/item5")
- self.assertEqual([{'default': False, 'type': 'string', 'name': 'list_element', 'value': 'a', 'modified': False},
- {'default': False, 'type': 'string', 'name': 'list_element', 'value': 'b', 'modified': False}], maps)
+ self.assertEqual([{'default': True, 'type': 'string', 'name': 'Spec2/item5[0]', 'value': 'a', 'modified': False},
+ {'default': True, 'type': 'string', 'name': 'Spec2/item5[1]', 'value': 'b', 'modified': False}], maps)
+ maps = self.mcd.get_value_maps("/Spec2/item5[0]")
+ self.assertEqual([{'default': True, 'modified': False, 'name': 'Spec2/item5[0]', 'type': 'string', 'value': 'a'}], maps)
maps = self.mcd.get_value_maps("/Spec2/item1")
- self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False}], maps)
+ self.assertEqual([{'default': False, 'type': 'integer', 'name': 'Spec2/item1', 'value': 2, 'modified': False}], maps)
maps = self.mcd.get_value_maps("/Spec2/item2")
- self.assertEqual([{'default': False, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False}], maps)
+ self.assertEqual([{'default': True, 'type': 'real', 'name': 'Spec2/item2', 'value': 1.1, 'modified': False}], maps)
maps = self.mcd.get_value_maps("/Spec2/item3")
- self.assertEqual([{'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True}], maps)
+ self.assertEqual([{'default': False, 'type': 'boolean', 'name': 'Spec2/item3', 'value': False, 'modified': True}], maps)
maps = self.mcd.get_value_maps("/Spec2/item4")
- self.assertEqual([{'default': False, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False}], maps)
+ self.assertEqual([{'default': True, 'type': 'string', 'name': 'Spec2/item4', 'value': 'test', 'modified': False}], maps)
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec24.spec")
self.mcd.set_specification(module_spec)
@@ -420,16 +469,48 @@ class TestMultiConfigData(unittest.TestCase):
self.assertEqual([], maps)
self.mcd._set_current_config({ "Spec24": { "item": [] } })
maps = self.mcd.get_value_maps("/Spec24/item")
- self.assertEqual([], maps)
-
+ self.assertEqual([{'default': False, 'modified': False, 'name': 'Spec24/item', 'type': 'list', 'value': []}], maps)
+ module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec22.spec")
+ self.mcd.set_specification(module_spec)
+ expected = [{'default': True,
+ 'modified': False,
+ 'name': 'Spec22/value9/v91',
+ 'type': 'string',
+ 'value': 'def'},
+ {'default': True,
+ 'modified': False,
+ 'name': 'Spec22/value9/v92/v92a',
+ 'type': 'string',
+ 'value': 'Hello'
+ },
+ {'default': True,
+ 'modified': False,
+ 'name': 'Spec22/value9/v92/v92b',
+ 'type': 'integer',
+ 'value': 47806
+ }
+ ]
+ maps = self.mcd.get_value_maps("/Spec22/value9")
+ self.assertEqual(expected, maps)
def test_set_value(self):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
self.mcd.set_specification(module_spec)
self.mcd.set_value("Spec2/item1", 2)
- self.assertRaises(isc.cc.data.DataTypeError, self.mcd.set_value, "Spec2/item1", "asdf")
- self.mcd.set_value("Spec2/no_such_item", 4)
+ self.assertRaises(isc.cc.data.DataTypeError,
+ self.mcd.set_value, "Spec2/item1", "asdf")
+
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.mcd.set_value, "Spec2/no_such_item", 4)
+
+ self.mcd.set_value("Spec2/item5[0]", "c")
+ value, status = self.mcd.get_value("Spec2/item5[0]")
+ self.assertEqual(value, "c")
+ self.assertEqual(MultiConfigData.LOCAL, status)
+
+ self.assertRaises(isc.cc.data.DataTypeError, self.mcd.set_value, "Spec2/item5[a]", "asdf")
+
def test_get_config_item_list(self):
config_items = self.mcd.get_config_item_list()
@@ -441,13 +522,15 @@ class TestMultiConfigData(unittest.TestCase):
config_items = self.mcd.get_config_item_list(None, False)
self.assertEqual(['Spec2'], config_items)
config_items = self.mcd.get_config_item_list(None, True)
- self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
+ self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
config_items = self.mcd.get_config_item_list("Spec2", True)
- self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
+ self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
config_items = self.mcd.get_config_item_list("Spec2")
- self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/'], config_items)
+ self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6'], config_items)
+ config_items = self.mcd.get_config_item_list("/Spec2")
+ self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6'], config_items)
config_items = self.mcd.get_config_item_list("Spec2", True)
- self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
+ self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
if __name__ == '__main__':
unittest.main()
diff --git a/src/lib/python/isc/config/tests/module_spec_test.py b/src/lib/python/isc/config/tests/module_spec_test.py
index 6d0adda..a4dcdec 100644
--- a/src/lib/python/isc/config/tests/module_spec_test.py
+++ b/src/lib/python/isc/config/tests/module_spec_test.py
@@ -13,8 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
#
# Tests for the module_spec module
#
@@ -109,6 +107,9 @@ class TestModuleSpec(unittest.TestCase):
return dd.validate_command(cmd_name, params)
def test_command_validation(self):
+ # tests for a command that doesn't take an argument
+ self.assertEqual(True, self.read_spec_file("spec2.spec").validate_command("shutdown", None));
+ self.assertEqual(False, self.read_spec_file("spec2.spec").validate_command("shutdown", '{"val": 1}'));
self.assertEqual(True, self.validate_command_params("spec27.spec", "data22_1.data", 'cmd1'))
self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_2.data",'cmd1'))
self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_3.data", 'cmd1'))
@@ -312,6 +313,19 @@ class TestModuleSpec(unittest.TestCase):
self.assertEqual(False, isc.config.module_spec._validate_spec(spec, True, {}, None))
self.assertEqual(False, isc.config.module_spec._validate_spec(spec, True, {}, errors))
self.assertEqual(['non-optional item an_item missing'], errors)
+
+ def test_validate_unknown_items(self):
+ spec = [{ 'item_name': "an_item",
+ 'item_type': "string",
+ 'item_optional': True,
+ 'item_default': "asdf"
+ }]
+
+ errors = []
+ self.assertEqual(True, isc.config.module_spec._validate_spec_list(spec, True, None, None))
+ self.assertEqual(False, isc.config.module_spec._validate_spec_list(spec, True, { 'does_not_exist': 1 }, None))
+ self.assertEqual(False, isc.config.module_spec._validate_spec_list(spec, True, { 'does_not_exist': 1 }, errors))
+ self.assertEqual(['unknown item does_not_exist'], errors)
diff --git a/src/lib/python/isc/config/tests/unittest_fakesession.py b/src/lib/python/isc/config/tests/unittest_fakesession.py
index 8bd2607..e31b436 100644
--- a/src/lib/python/isc/config/tests/unittest_fakesession.py
+++ b/src/lib/python/isc/config/tests/unittest_fakesession.py
@@ -13,8 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
import isc
class WouldBlockForever(Exception):
diff --git a/src/lib/python/isc/datasrc/Makefile.am b/src/lib/python/isc/datasrc/Makefile.am
index 16be6f3..5b9dafb 100644
--- a/src/lib/python/isc/datasrc/Makefile.am
+++ b/src/lib/python/isc/datasrc/Makefile.am
@@ -1,3 +1,5 @@
+SUBDIRS = . tests
+
python_PYTHON = __init__.py master.py sqlite3_ds.py
pythondir = $(pyexecdir)/isc/datasrc
diff --git a/src/lib/python/isc/datasrc/master.py b/src/lib/python/isc/datasrc/master.py
index 0b0266d..bba0805 100644
--- a/src/lib/python/isc/datasrc/master.py
+++ b/src/lib/python/isc/datasrc/master.py
@@ -13,8 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
import sys, re, string
import time
import os
@@ -103,7 +101,7 @@ def isname(s):
# isttl: check whether a string is a valid TTL specifier.
# returns: boolean
#########################################################################
-ttl_regex = re.compile('([0-9]+[wdhms]?)+', re.I)
+ttl_regex = re.compile('([0-9]+[wdhms]?)+$', re.I)
def isttl(s):
global ttl_regex
if ttl_regex.match(s):
diff --git a/src/lib/python/isc/datasrc/sqlite3_ds.py b/src/lib/python/isc/datasrc/sqlite3_ds.py
index 563d84d..77b0828 100644
--- a/src/lib/python/isc/datasrc/sqlite3_ds.py
+++ b/src/lib/python/isc/datasrc/sqlite3_ds.py
@@ -13,8 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
import sqlite3, re, random
import isc
@@ -25,25 +23,24 @@ RR_NAME_INDEX = 2
RR_TTL_INDEX = 4
RR_RDATA_INDEX = 7
-#########################################################################
-# define exceptions
-#########################################################################
class Sqlite3DSError(Exception):
+ """ Define exceptions."""
pass
-#########################################################################
-# create: set up schema for a newly created zones/records database
-#########################################################################
def create(cur):
- """Create new zone database"""
+ """ Set up schema for a newly created zones/records database.
+
+ Arguments:
+ cur - sqlite3 cursor.
+ """
cur.execute("CREATE TABLE schema_version (version INTEGER NOT NULL)")
cur.execute("INSERT INTO schema_version VALUES (1)")
- cur.execute("""CREATE TABLE zones (id INTEGER PRIMARY KEY,
+ cur.execute("""CREATE TABLE zones (id INTEGER PRIMARY KEY,
name STRING NOT NULL COLLATE NOCASE,
- rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN',
+ rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN',
dnssec BOOLEAN NOT NULL DEFAULT 0)""")
cur.execute("CREATE INDEX zones_byname ON zones (name)")
- cur.execute("""CREATE TABLE records (id INTEGER PRIMARY KEY,
+ cur.execute("""CREATE TABLE records (id INTEGER PRIMARY KEY,
zone_id INTEGER NOT NULL,
name STRING NOT NULL COLLATE NOCASE,
rname STRING NOT NULL COLLATE NOCASE,
@@ -53,7 +50,7 @@ def create(cur):
rdata STRING NOT NULL)""")
cur.execute("CREATE INDEX records_byname ON records (name)")
cur.execute("CREATE INDEX records_byrname ON records (rname)")
- cur.execute("""CREATE TABLE nsec3 (id INTEGER PRIMARY KEY,
+ cur.execute("""CREATE TABLE nsec3 (id INTEGER PRIMARY KEY,
zone_id INTEGER NOT NULL,
hash STRING NOT NULL COLLATE NOCASE,
owner STRING NOT NULL COLLATE NOCASE,
@@ -62,17 +59,17 @@ def create(cur):
rdata STRING NOT NULL)""")
cur.execute("CREATE INDEX nsec3_byhash ON nsec3 (hash)")
-#########################################################################
-# open: open a database. if the database is not yet set up,
-# call create to do so.
-# input:
-# dbfile - the filename for the sqlite3 database
-# returns:
-# sqlite3 connection, sqlite3 cursor
-#########################################################################
def open(dbfile):
- """Open the database file. If necessary, set it up"""
- try:
+ """ Open a database, if the database is not yet set up, call create
+ to do so. It may raise Sqlite3DSError if failed to open sqlite3
+ database file or find bad database schema version in the database.
+
+ Arguments:
+ dbfile - the filename for the sqlite3 database.
+
+ Return sqlite3 connection, sqlite3 cursor.
+ """
+ try:
conn = sqlite3.connect(dbfile)
cur = conn.cursor()
except Exception as e:
@@ -93,12 +90,15 @@ def open(dbfile):
return conn, cur
-#########################################################################
-# get_zone_datas
-# a generator function producing an iterable set of
-# the records in the zone with the given zone name.
-#########################################################################
+
def get_zone_datas(zonename, dbfile):
+ """ A generator function producing an iterable set of
+ the records in the zone with the given zone name.
+
+ Arguments:
+ zonename - the zone's origin name.
+ dbfile - the filename for the sqlite3 database.
+ """
conn, cur = open(dbfile)
zone_id = get_zoneid(zonename, cur)
@@ -112,12 +112,14 @@ def get_zone_datas(zonename, dbfile):
conn.close()
-#########################################################################
-# get_zone_soa
-# returns the soa record of the zone with the given zone name.
-# If the zone doesn't exist, return None.
-#########################################################################
def get_zone_soa(zonename, dbfile):
+ """Return the soa record of the zone with the given zone name.
+ If the zone doesn't exist, return None.
+
+ Arguments:
+ zonename - the zone's origin name.
+ dbfile - the filename for the sqlite3 database.
+ """
conn, cur = open(dbfile)
id = get_zoneid(zonename, cur)
cur.execute("SELECT * FROM records WHERE zone_id = ? and rdtype = ?", [id, 'SOA'])
@@ -128,16 +130,20 @@ def get_zone_soa(zonename, dbfile):
return datas
-#########################################################################
-# get_zone_rrset
-# returns the rrset of the zone with the given zone name, rrset name
-# and given rd type.
-# If the zone doesn't exist or rd type doesn't exist, return an empty list.
-#########################################################################
def get_zone_rrset(zonename, rr_name, rdtype, dbfile):
+ """Return the rrset of the zone with the given zone name, rrset
+ name and given RR type. If the zone doesn't exist or RR type
+ doesn't exist, return an empty list.
+
+ Arguments:
+ zonename - the zone's origin name.
+ rr_name - rr name.
+ rdtype - RR type.
+ dbfile - the filename for the sqlite3 database.
+ """
conn, cur = open(dbfile)
id = get_zoneid(zonename, cur)
- cur.execute("SELECT * FROM records WHERE name = ? and zone_id = ? and rdtype = ?",
+ cur.execute("SELECT * FROM records WHERE name = ? and zone_id = ? and rdtype = ?",
[rr_name, id, rdtype])
datas = cur.fetchall()
cur.close()
@@ -145,12 +151,13 @@ def get_zone_rrset(zonename, rr_name, rdtype, dbfile):
return datas
-#########################################################################
-# get_zones_info:
-# returns all the zones' information.
-#########################################################################
-def get_zones_info(db_file):
- conn, cur = open(db_file)
+def get_zones_info(dbfile):
+ """ Return all the zones' information in the database.
+
+ Arguments:
+ dbfile - the filename for the sqlite3 database.
+ """
+ conn, cur = open(dbfile)
cur.execute("SELECT name, rdclass FROM zones")
info = cur.fetchone()
while info:
@@ -160,44 +167,69 @@ def get_zones_info(db_file):
cur.close()
conn.close()
-#########################################################################
-# get_zoneid:
-# returns the zone_id for a given zone name, or an empty
-# string if the zone is not found
-#########################################################################
-def get_zoneid(zone, cur):
- cur.execute("SELECT id FROM zones WHERE name = ?", [zone])
+
+def get_zoneid(zonename, cur):
+ """ Get the zone_id for a given zone name.
+
+ Arguments:
+ zonename - the zone's origin name.
+ cur - sqlite3 cursor.
+
+ Return zone id for the given zone name, or an empty string if the
+ zone is not found.
+ """
+ cur.execute("SELECT id FROM zones WHERE name = ?", [zonename])
row = cur.fetchone()
if row:
return row[0]
else:
return ''
-
-#########################################################################
-# reverse_name:
-# reverse the labels of a DNS name. (for example,
-# "bind10.isc.org." would become "org.isc.bind10.")
-#########################################################################
+
+
+def zone_exist(zonename, dbfile):
+ """ Search for the zone with the given zone name in databse. This
+ method may throw a Sqlite3DSError exception because its underlying
+ method open() can throw that exception.
+
+ Arguments:
+ zonename - the zone's origin name.
+ dbfile - the filename for the sqlite3 database.
+
+ Return True if the zone is found, otherwise False.
+ """
+ conn, cur = open(dbfile)
+ zoneid = get_zoneid(zonename, cur)
+ cur.close()
+ conn.close()
+ if zoneid:
+ return True
+ return False
+
+
def reverse_name(name):
"""Reverse the labels of a domain name; for example,
- given 'www.isc.org.', return 'org.isc.www.' This is needed
- for DNSSEC sort order."""
+ given 'www.example.org.', return 'org.example.www.' This is needed
+ for DNSSEC sort order.
+
+ Arguments:
+ name - the DNS name will be reversed.
+ """
new = name.split('.')
new.reverse()
if new[0] == '':
new.pop(0)
return '.'.join(new)+'.'
-#########################################################################
-# load:
-# load a zone into the SQL database.
-# input:
-# dbfile: the sqlite3 database fileanme
-# zone: the zone origin
-# reader: a generator function producing an iterable set of
-# name/ttl/class/rrtype/rdata-text tuples
-#########################################################################
+
def load(dbfile, zone, reader):
+ """ Load a zone into the SQL database.
+
+ Arguments:
+ dbfile - the sqlite3 database filename
+ zone - the zone origin
+ reader - a generator function producing an iterable set of
+ name/ttl/class/rrtype/rdata-text tuples.
+ """
# if the zone name doesn't contain the trailing dot, automatically add it.
if zone[-1] != '.':
zone += '.'
diff --git a/src/lib/python/isc/datasrc/tests/Makefile.am b/src/lib/python/isc/datasrc/tests/Makefile.am
new file mode 100644
index 0000000..4f87cc9
--- /dev/null
+++ b/src/lib/python/isc/datasrc/tests/Makefile.am
@@ -0,0 +1,20 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = master_test.py sqlite3_ds_test.py
+EXTRA_DIST = $(PYTESTS)
+
+EXTRA_DIST += testdata/brokendb.sqlite3
+EXTRA_DIST += testdata/example.com.sqlite3
+
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/python/isc/log \
+ TESTDATA_PATH=$(abs_srcdir)/testdata \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
diff --git a/src/lib/python/isc/datasrc/tests/master_test.py b/src/lib/python/isc/datasrc/tests/master_test.py
new file mode 100644
index 0000000..c65858e
--- /dev/null
+++ b/src/lib/python/isc/datasrc/tests/master_test.py
@@ -0,0 +1,35 @@
+# Copyright (C) 2010 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from isc.datasrc.master import *
+import unittest
+
+class TestTTL(unittest.TestCase):
+ def test_ttl(self):
+ self.assertTrue(isttl('3600'))
+ self.assertTrue(isttl('1W'))
+ self.assertTrue(isttl('1w'))
+ self.assertTrue(isttl('2D'))
+ self.assertTrue(isttl('2d'))
+ self.assertTrue(isttl('30M'))
+ self.assertTrue(isttl('30m'))
+ self.assertTrue(isttl('10S'))
+ self.assertTrue(isttl('10s'))
+ self.assertTrue(isttl('2W1D'))
+ self.assertFalse(isttl('not a ttl'))
+ self.assertFalse(isttl('1X'))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py b/src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py
new file mode 100644
index 0000000..013c7d7
--- /dev/null
+++ b/src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py
@@ -0,0 +1,43 @@
+# 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 isc.datasrc import sqlite3_ds
+import os
+import socket
+import unittest
+
+TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
+
+class TestSqlite3_ds(unittest.TestCase):
+ def test_zone_exist(self):
+ # The following file must be non existent and must be non
+ # "creatable"; the sqlite3 library will try to create a new
+ # DB file if it doesn't exist, so to test a failure case the
+ # create operation should also fail. The "nodir", a non
+ # existent directory, is inserted for this purpose.
+ nodir = "/nodir/notexist"
+ self.assertRaises(sqlite3_ds.Sqlite3DSError,
+ sqlite3_ds.zone_exist, "example.com", nodir)
+ # Open a broken database file
+ self.assertRaises(sqlite3_ds.Sqlite3DSError,
+ sqlite3_ds.zone_exist, "example.com",
+ TESTDATA_PATH + "brokendb.sqlite3")
+ self.assertTrue(sqlite3_ds.zone_exist("example.com.",
+ TESTDATA_PATH + "example.com.sqlite3"))
+ self.assertFalse(sqlite3_ds.zone_exist("example.org.",
+ TESTDATA_PATH + "example.com.sqlite3"))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/lib/python/isc/datasrc/tests/testdata/brokendb.sqlite3 b/src/lib/python/isc/datasrc/tests/testdata/brokendb.sqlite3
new file mode 100644
index 0000000..7aad3af
Binary files /dev/null and b/src/lib/python/isc/datasrc/tests/testdata/brokendb.sqlite3 differ
diff --git a/src/lib/python/isc/datasrc/tests/testdata/example.com.sqlite3 b/src/lib/python/isc/datasrc/tests/testdata/example.com.sqlite3
new file mode 100644
index 0000000..cc8cfc3
Binary files /dev/null and b/src/lib/python/isc/datasrc/tests/testdata/example.com.sqlite3 differ
diff --git a/src/lib/python/isc/log/tests/Makefile.am b/src/lib/python/isc/log/tests/Makefile.am
index 7ddfba1..86b3e5d 100644
--- a/src/lib/python/isc/log/tests/Makefile.am
+++ b/src/lib/python/isc/log/tests/Makefile.am
@@ -1,12 +1,16 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = log_test.py
EXTRA_DIST = $(PYTESTS)
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/python/isc/log \
- $(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
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/Makefile.am b/src/lib/python/isc/net/tests/Makefile.am
index e89635f..73528d2 100644
--- a/src/lib/python/isc/net/tests/Makefile.am
+++ b/src/lib/python/isc/net/tests/Makefile.am
@@ -1,12 +1,16 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = addr_test.py parse_test.py
EXTRA_DIST = $(PYTESTS)
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
- $(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
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/notify/notify_out.py b/src/lib/python/isc/notify/notify_out.py
index f5dc6de..43dc7af 100644
--- a/src/lib/python/isc/notify/notify_out.py
+++ b/src/lib/python/isc/notify/notify_out.py
@@ -367,6 +367,7 @@ class NotifyOut:
zone_id = self._waiting_zones.pop(0)
self._notify_infos[zone_id].prepare_notify_out()
self.notify_num += 1
+ self._notifying_zones.append(zone_id)
def _send_notify_message_udp(self, zone_notify_info, addrinfo):
msg, qid = self._create_notify_message(zone_notify_info.zone_name,
@@ -402,13 +403,13 @@ class NotifyOut:
msg.set_qid(qid)
msg.set_opcode(Opcode.NOTIFY())
msg.set_rcode(Rcode.NOERROR())
- msg.set_header_flag(MessageFlag.AA())
+ msg.set_header_flag(Message.HEADERFLAG_AA)
question = Question(Name(zone_name), RRClass(zone_class), RRType('SOA'))
msg.add_question(question)
# Add soa record to answer section
soa_record = sqlite3_ds.get_zone_rrset(zone_name, zone_name, 'SOA', self._db_file)
rrset_soa = self._create_rrset_from_db_record(soa_record[0], zone_class)
- msg.add_rrset(Section.ANSWER(), rrset_soa)
+ msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
return msg, qid
def _handle_notify_reply(self, zone_notify_info, msg_data):
@@ -420,7 +421,7 @@ class NotifyOut:
try:
errstr = 'notify reply error: '
msg.from_wire(msg_data)
- if not msg.get_header_flag(MessageFlag.QR()):
+ if not msg.get_header_flag(Message.HEADERFLAG_QR):
self._log_msg('error', errstr + 'bad flags')
return _BAD_QR
diff --git a/src/lib/python/isc/notify/tests/Makefile.am b/src/lib/python/isc/notify/tests/Makefile.am
index 061e535..c5f0165 100644
--- a/src/lib/python/isc/notify/tests/Makefile.am
+++ b/src/lib/python/isc/notify/tests/Makefile.am
@@ -1,3 +1,4 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
PYTESTS = notify_out_test.py
EXTRA_DIST = $(PYTESTS)
@@ -8,13 +9,16 @@ 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:$$$(ENV_LIBRARY_PATH)
endif
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
$(LIBRARY_PATH_PLACEHOLDER) \
- $(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/lib/python/isc/notify/tests/notify_out_test.py b/src/lib/python/isc/notify/tests/notify_out_test.py
index bfa3d7c..c4c149c 100644
--- a/src/lib/python/isc/notify/tests/notify_out_test.py
+++ b/src/lib/python/isc/notify/tests/notify_out_test.py
@@ -22,9 +22,45 @@ import socket
from isc.datasrc import sqlite3_ds
from isc.notify import notify_out, SOCK_DATA
+# our fake socket, where we can read and insert messages
+class MockSocket():
+ def __init__(self, family, type):
+ self.family = family
+ self.type = type
+ self._local_sock, self._remote_sock = socket.socketpair()
+
+ def connect(self, to):
+ pass
+
+ def fileno(self):
+ return self._local_sock.fileno()
+
+ def close(self):
+ self._local_sock.close()
+ self._remote_sock.close()
+
+ def sendto(self, data, flag, dst):
+ return self._local_sock.send(data)
+
+ def recvfrom(self, length):
+ data = self._local_sock.recv(length)
+ return (data, None)
+
+ # provide a remote end which can write data to MockSocket for testing.
+ def remote_end(self):
+ return self._remote_sock
+
+# We subclass the ZoneNotifyInfo class we're testing here, only
+# to override the prepare_notify_out() method.
+class MockZoneNotifyInfo(notify_out.ZoneNotifyInfo):
+ def prepare_notify_out(self):
+ super().prepare_notify_out();
+ self._sock.close()
+ self._sock = MockSocket(socket.AF_INET, socket.SOCK_DGRAM)
+
class TestZoneNotifyInfo(unittest.TestCase):
def setUp(self):
- self.info = notify_out.ZoneNotifyInfo('cn.', 'IN')
+ self.info = notify_out.ZoneNotifyInfo('example.net.', 'IN')
def test_prepare_finish_notify_out(self):
self.info.prepare_notify_out()
@@ -46,7 +82,7 @@ class TestZoneNotifyInfo(unittest.TestCase):
self.info.set_next_notify_target()
self.assertIsNone(self.info.get_current_notify_target())
- temp_info = notify_out.ZoneNotifyInfo('com.', 'IN')
+ temp_info = notify_out.ZoneNotifyInfo('example.com.', 'IN')
temp_info.prepare_notify_out()
self.assertIsNone(temp_info.get_current_notify_target())
@@ -54,16 +90,16 @@ class TestZoneNotifyInfo(unittest.TestCase):
class TestNotifyOut(unittest.TestCase):
def setUp(self):
self._db_file = tempfile.NamedTemporaryFile(delete=False)
- sqlite3_ds.load(self._db_file.name, 'cn.', self._cn_data_reader)
- sqlite3_ds.load(self._db_file.name, 'com.', self._com_data_reader)
+ sqlite3_ds.load(self._db_file.name, 'example.net.', self._example_net_data_reader)
+ sqlite3_ds.load(self._db_file.name, 'example.com.', self._example_com_data_reader)
self._notify = notify_out.NotifyOut(self._db_file.name)
- self._notify._notify_infos[('com.', 'IN')] = notify_out.ZoneNotifyInfo('com.', 'IN')
- self._notify._notify_infos[('com.', 'CH')] = notify_out.ZoneNotifyInfo('com.', 'CH')
- self._notify._notify_infos[('cn.', 'IN')] = notify_out.ZoneNotifyInfo('cn.', 'IN')
- self._notify._notify_infos[('org.', 'IN')] = notify_out.ZoneNotifyInfo('org.', 'IN')
- self._notify._notify_infos[('org.', 'CH')] = notify_out.ZoneNotifyInfo('org.', 'CH')
-
- info = self._notify._notify_infos[('cn.', 'IN')]
+ self._notify._notify_infos[('example.com.', 'IN')] = MockZoneNotifyInfo('example.com.', 'IN')
+ self._notify._notify_infos[('example.com.', 'CH')] = MockZoneNotifyInfo('example.com.', 'CH')
+ self._notify._notify_infos[('example.net.', 'IN')] = MockZoneNotifyInfo('example.net.', 'IN')
+ self._notify._notify_infos[('example.org.', 'IN')] = MockZoneNotifyInfo('example.org.', 'IN')
+ self._notify._notify_infos[('example.org.', 'CH')] = MockZoneNotifyInfo('example.org.', 'CH')
+
+ info = self._notify._notify_infos[('example.net.', 'IN')]
info.notify_slaves.append(('127.0.0.1', 53))
info.notify_slaves.append(('1.1.1.1', 5353))
@@ -72,62 +108,59 @@ class TestNotifyOut(unittest.TestCase):
os.unlink(self._db_file.name)
def test_send_notify(self):
- self._notify.send_notify('cn')
+ self._notify.send_notify('example.net')
self.assertEqual(self._notify.notify_num, 1)
- self.assertEqual(self._notify._notifying_zones[0], ('cn.','IN'))
+ self.assertEqual(self._notify._notifying_zones[0], ('example.net.','IN'))
- self._notify.send_notify('com')
+ self._notify.send_notify('example.com')
self.assertEqual(self._notify.notify_num, 2)
- self.assertEqual(self._notify._notifying_zones[1], ('com.','IN'))
+ self.assertEqual(self._notify._notifying_zones[1], ('example.com.','IN'))
notify_out._MAX_NOTIFY_NUM = 3
- self._notify.send_notify('com', 'CH')
+ self._notify.send_notify('example.com', 'CH')
self.assertEqual(self._notify.notify_num, 3)
- self.assertEqual(self._notify._notifying_zones[2], ('com.','CH'))
-
- self._notify.send_notify('org.')
- self.assertEqual(self._notify._waiting_zones[0], ('org.', 'IN'))
- self._notify.send_notify('org.')
+ self.assertEqual(self._notify._notifying_zones[2], ('example.com.','CH'))
+
+ self._notify.send_notify('example.org.')
+ self.assertEqual(self._notify._waiting_zones[0], ('example.org.', 'IN'))
+ self._notify.send_notify('example.org.')
self.assertEqual(1, len(self._notify._waiting_zones))
- self._notify.send_notify('org.', 'CH')
+ self._notify.send_notify('example.org.', 'CH')
self.assertEqual(2, len(self._notify._waiting_zones))
- self.assertEqual(self._notify._waiting_zones[1], ('org.', 'CH'))
+ self.assertEqual(self._notify._waiting_zones[1], ('example.org.', 'CH'))
def test_wait_for_notify_reply(self):
- self._notify.send_notify('cn.')
- self._notify.send_notify('com.')
-
+ self._notify.send_notify('example.net.')
+ self._notify.send_notify('example.com.')
+
notify_out._MAX_NOTIFY_NUM = 2
- self._notify.send_notify('org.')
+ self._notify.send_notify('example.org.')
replied_zones, timeout_zones = self._notify._wait_for_notify_reply()
self.assertEqual(len(replied_zones), 0)
self.assertEqual(len(timeout_zones), 2)
# Now make one socket be readable
- addr = ('localhost', 12340)
- self._notify._notify_infos[('cn.', 'IN')]._sock.bind(addr)
- self._notify._notify_infos[('cn.', 'IN')].notify_timeout = time.time() + 10
- self._notify._notify_infos[('com.', 'IN')].notify_timeout = time.time() + 10
-
- send_fd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ self._notify._notify_infos[('example.net.', 'IN')].notify_timeout = time.time() + 10
+ self._notify._notify_infos[('example.com.', 'IN')].notify_timeout = time.time() + 10
+
#Send some data to socket 12340, to make the target socket be readable
- send_fd.sendto(b'data', addr)
+ self._notify._notify_infos[('example.net.', 'IN')]._sock.remote_end().send(b'data')
replied_zones, timeout_zones = self._notify._wait_for_notify_reply()
self.assertEqual(len(replied_zones), 1)
self.assertEqual(len(timeout_zones), 1)
- self.assertTrue(('cn.', 'IN') in replied_zones.keys())
- self.assertTrue(('com.', 'IN') in timeout_zones.keys())
- self.assertLess(time.time(), self._notify._notify_infos[('com.', 'IN')].notify_timeout)
-
+ self.assertTrue(('example.net.', 'IN') in replied_zones.keys())
+ self.assertTrue(('example.com.', 'IN') in timeout_zones.keys())
+ self.assertLess(time.time(), self._notify._notify_infos[('example.com.', 'IN')].notify_timeout)
+
def test_wait_for_notify_reply_2(self):
# Test the returned value when the read_side socket is readable.
- self._notify.send_notify('cn.')
- self._notify.send_notify('com.')
+ self._notify.send_notify('example.net.')
+ self._notify.send_notify('example.com.')
# Now make one socket be readable
- self._notify._notify_infos[('cn.', 'IN')].notify_timeout = time.time() + 10
- self._notify._notify_infos[('com.', 'IN')].notify_timeout = time.time() + 10
+ self._notify._notify_infos[('example.net.', 'IN')].notify_timeout = time.time() + 10
+ self._notify._notify_infos[('example.com.', 'IN')].notify_timeout = time.time() + 10
self._notify._read_sock, self._notify._write_sock = socket.socketpair()
self._notify._write_sock.send(SOCK_DATA)
replied_zones, timeout_zones = self._notify._wait_for_notify_reply()
@@ -135,13 +168,13 @@ class TestNotifyOut(unittest.TestCase):
self.assertEqual(0, len(timeout_zones))
def test_notify_next_target(self):
- self._notify.send_notify('cn.')
- self._notify.send_notify('com.')
+ self._notify.send_notify('example.net.')
+ self._notify.send_notify('example.com.')
notify_out._MAX_NOTIFY_NUM = 2
- self._notify.send_notify('org.')
- self._notify.send_notify('com.', 'CH')
+ self._notify.send_notify('example.org.')
+ self._notify.send_notify('example.com.', 'CH')
- info = self._notify._notify_infos[('cn.', 'IN')]
+ info = self._notify._notify_infos[('example.net.', 'IN')]
self._notify._notify_next_target(info)
self.assertEqual(0, info.notify_try_num)
self.assertEqual(info.get_current_notify_target(), ('1.1.1.1', 5353))
@@ -153,101 +186,101 @@ class TestNotifyOut(unittest.TestCase):
self.assertEqual(2, self._notify.notify_num)
self.assertEqual(1, len(self._notify._waiting_zones))
- com_info = self._notify._notify_infos[('com.', 'IN')]
- self._notify._notify_next_target(com_info)
+ example_com_info = self._notify._notify_infos[('example.com.', 'IN')]
+ self._notify._notify_next_target(example_com_info)
self.assertEqual(2, self._notify.notify_num)
- self.assertEqual(0, len(self._notify._notifying_zones))
-
+ self.assertEqual(2, len(self._notify._notifying_zones))
+
def test_handle_notify_reply(self):
self.assertEqual(notify_out._BAD_REPLY_PACKET, self._notify._handle_notify_reply(None, b'badmsg'))
- com_info = self._notify._notify_infos[('com.', 'IN')]
- com_info.notify_msg_id = 0X2f18
+ example_com_info = self._notify._notify_infos[('example.com.', 'IN')]
+ example_com_info.notify_msg_id = 0X2f18
# test with right notify reply message
- data = b'\x2f\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03com\x00\x00\x06\x00\x01'
- self.assertEqual(notify_out._REPLY_OK, self._notify._handle_notify_reply(com_info, data))
+ data = b'\x2f\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03com\x00\x00\x06\x00\x01'
+ self.assertEqual(notify_out._REPLY_OK, self._notify._handle_notify_reply(example_com_info, data))
# test with unright query id
- data = b'\x2e\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03com\x00\x00\x06\x00\x01'
- self.assertEqual(notify_out._BAD_QUERY_ID, self._notify._handle_notify_reply(com_info, data))
+ data = b'\x2e\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03com\x00\x00\x06\x00\x01'
+ self.assertEqual(notify_out._BAD_QUERY_ID, self._notify._handle_notify_reply(example_com_info, data))
# test with unright query name
- data = b'\x2f\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x02cn\x00\x00\x06\x00\x01'
- self.assertEqual(notify_out._BAD_QUERY_NAME, self._notify._handle_notify_reply(com_info, data))
+ data = b'\x2f\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03net\x00\x00\x06\x00\x01'
+ self.assertEqual(notify_out._BAD_QUERY_NAME, self._notify._handle_notify_reply(example_com_info, data))
# test with unright opcode
- data = b'\x2f\x18\x80\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03com\x00\x00\x06\x00\x01'
- self.assertEqual(notify_out._BAD_OPCODE, self._notify._handle_notify_reply(com_info, data))
+ data = b'\x2f\x18\x80\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03com\x00\x00\x06\x00\x01'
+ self.assertEqual(notify_out._BAD_OPCODE, self._notify._handle_notify_reply(example_com_info, data))
# test with unright qr
- data = b'\x2f\x18\x10\x10\x00\x01\x00\x00\x00\x00\x00\x00\x03com\x00\x00\x06\x00\x01'
- self.assertEqual(notify_out._BAD_QR, self._notify._handle_notify_reply(com_info, data))
+ data = b'\x2f\x18\x10\x10\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03com\x00\x00\x06\x00\x01'
+ self.assertEqual(notify_out._BAD_QR, self._notify._handle_notify_reply(example_com_info, data))
def test_send_notify_message_udp(self):
- com_info = self._notify._notify_infos[('cn.', 'IN')]
- com_info.prepare_notify_out()
- ret = self._notify._send_notify_message_udp(com_info, ('1.1.1.1', 53))
+ example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
+ example_com_info.prepare_notify_out()
+ ret = self._notify._send_notify_message_udp(example_com_info, ('1.1.1.1', 53))
self.assertTrue(ret)
def test_zone_notify_handler(self):
old_send_msg = self._notify._send_notify_message_udp
- def _fake_send_notify_message_udp(va1, va2):
+ def _fake_send_notify_message_udp(va1, va2):
pass
self._notify._send_notify_message_udp = _fake_send_notify_message_udp
- self._notify.send_notify('cn.')
- self._notify.send_notify('com.')
+ self._notify.send_notify('example.net.')
+ self._notify.send_notify('example.com.')
notify_out._MAX_NOTIFY_NUM = 2
- self._notify.send_notify('org.')
+ self._notify.send_notify('example.org.')
- cn_info = self._notify._notify_infos[('cn.', 'IN')]
- cn_info.prepare_notify_out()
+ example_net_info = self._notify._notify_infos[('example.net.', 'IN')]
+ example_net_info.prepare_notify_out()
- cn_info.notify_try_num = 2
- self._notify._zone_notify_handler(cn_info, notify_out._EVENT_TIMEOUT)
- self.assertEqual(3, cn_info.notify_try_num)
+ example_net_info.notify_try_num = 2
+ self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_TIMEOUT)
+ self.assertEqual(3, example_net_info.notify_try_num)
- time1 = cn_info.notify_timeout
- self._notify._zone_notify_handler(cn_info, notify_out._EVENT_TIMEOUT)
- self.assertEqual(4, cn_info.notify_try_num)
- self.assertGreater(cn_info.notify_timeout, time1 + 2) # bigger than 2 seconds
+ time1 = example_net_info.notify_timeout
+ self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_TIMEOUT)
+ self.assertEqual(4, example_net_info.notify_try_num)
+ self.assertGreater(example_net_info.notify_timeout, time1 + 2) # bigger than 2 seconds
- cur_tgt = cn_info._notify_current
- cn_info.notify_try_num = notify_out._MAX_NOTIFY_TRY_NUM
- self._notify._zone_notify_handler(cn_info, notify_out._EVENT_NONE)
- self.assertNotEqual(cur_tgt, cn_info._notify_current)
+ cur_tgt = example_net_info._notify_current
+ example_net_info.notify_try_num = notify_out._MAX_NOTIFY_TRY_NUM
+ self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_NONE)
+ self.assertNotEqual(cur_tgt, example_net_info._notify_current)
- def _cn_data_reader(self):
+ def _example_net_data_reader(self):
zone_data = [
- ('cn.', '1000', 'IN', 'SOA', 'a.dns.cn. mail.cn. 1 1 1 1 1'),
- ('cn.', '1000', 'IN', 'NS', 'a.dns.cn.'),
- ('cn.', '1000', 'IN', 'NS', 'b.dns.cn.'),
- ('cn.', '1000', 'IN', 'NS', 'c.dns.cn.'),
- ('a.dns.cn.', '1000', 'IN', 'A', '1.1.1.1'),
- ('a.dns.cn.', '1000', 'IN', 'AAAA', '2:2::2:2'),
- ('b.dns.cn.', '1000', 'IN', 'A', '3.3.3.3'),
- ('b.dns.cn.', '1000', 'IN', 'AAAA', '4:4::4:4'),
- ('b.dns.cn.', '1000', 'IN', 'AAAA', '5:5::5:5'),
- ('c.dns.cn.', '1000', 'IN', 'A', '6.6.6.6'),
- ('c.dns.cn.', '1000', 'IN', 'A', '7.7.7.7'),
- ('c.dns.cn.', '1000', 'IN', 'AAAA', '8:8::8:8')]
+ ('example.net.', '1000', 'IN', 'SOA', 'a.dns.example.net. mail.example.net. 1 1 1 1 1'),
+ ('example.net.', '1000', 'IN', 'NS', 'a.dns.example.net.'),
+ ('example.net.', '1000', 'IN', 'NS', 'b.dns.example.net.'),
+ ('example.net.', '1000', 'IN', 'NS', 'c.dns.example.net.'),
+ ('a.dns.example.net.', '1000', 'IN', 'A', '1.1.1.1'),
+ ('a.dns.example.net.', '1000', 'IN', 'AAAA', '2:2::2:2'),
+ ('b.dns.example.net.', '1000', 'IN', 'A', '3.3.3.3'),
+ ('b.dns.example.net.', '1000', 'IN', 'AAAA', '4:4::4:4'),
+ ('b.dns.example.net.', '1000', 'IN', 'AAAA', '5:5::5:5'),
+ ('c.dns.example.net.', '1000', 'IN', 'A', '6.6.6.6'),
+ ('c.dns.example.net.', '1000', 'IN', 'A', '7.7.7.7'),
+ ('c.dns.example.net.', '1000', 'IN', 'AAAA', '8:8::8:8')]
for item in zone_data:
yield item
- def _com_data_reader(self):
+ def _example_com_data_reader(self):
zone_data = [
- ('com.', '1000', 'IN', 'SOA', 'a.dns.com. mail.com. 1 1 1 1 1'),
- ('com.', '1000', 'IN', 'NS', 'a.dns.com.'),
- ('com.', '1000', 'IN', 'NS', 'b.dns.com.'),
- ('com.', '1000', 'IN', 'NS', 'c.dns.com.'),
- ('a.dns.com.', '1000', 'IN', 'A', '1.1.1.1'),
- ('b.dns.com.', '1000', 'IN', 'A', '3.3.3.3'),
- ('b.dns.com.', '1000', 'IN', 'AAAA', '4:4::4:4'),
- ('b.dns.com.', '1000', 'IN', 'AAAA', '5:5::5:5')]
+ ('example.com.', '1000', 'IN', 'SOA', 'a.dns.example.com. mail.example.com. 1 1 1 1 1'),
+ ('example.com.', '1000', 'IN', 'NS', 'a.dns.example.com.'),
+ ('example.com.', '1000', 'IN', 'NS', 'b.dns.example.com.'),
+ ('example.com.', '1000', 'IN', 'NS', 'c.dns.example.com.'),
+ ('a.dns.example.com.', '1000', 'IN', 'A', '1.1.1.1'),
+ ('b.dns.example.com.', '1000', 'IN', 'A', '3.3.3.3'),
+ ('b.dns.example.com.', '1000', 'IN', 'AAAA', '4:4::4:4'),
+ ('b.dns.example.com.', '1000', 'IN', 'AAAA', '5:5::5:5')]
for item in zone_data:
yield item
def test_get_notify_slaves_from_ns(self):
- records = self._notify._get_notify_slaves_from_ns('cn.')
+ records = self._notify._get_notify_slaves_from_ns('example.net.')
self.assertEqual(6, len(records))
self.assertEqual('8:8::8:8', records[5])
self.assertEqual('7.7.7.7', records[4])
@@ -256,36 +289,36 @@ class TestNotifyOut(unittest.TestCase):
self.assertEqual('4:4::4:4', records[1])
self.assertEqual('3.3.3.3', records[0])
- records = self._notify._get_notify_slaves_from_ns('com.')
+ records = self._notify._get_notify_slaves_from_ns('example.com.')
self.assertEqual(3, len(records))
self.assertEqual('5:5::5:5', records[2])
self.assertEqual('4:4::4:4', records[1])
self.assertEqual('3.3.3.3', records[0])
-
+
def test_init_notify_out(self):
self._notify._init_notify_out(self._db_file.name)
- self.assertListEqual([('3.3.3.3', 53), ('4:4::4:4', 53), ('5:5::5:5', 53)],
- self._notify._notify_infos[('com.', 'IN')].notify_slaves)
-
+ self.assertListEqual([('3.3.3.3', 53), ('4:4::4:4', 53), ('5:5::5:5', 53)],
+ self._notify._notify_infos[('example.com.', 'IN')].notify_slaves)
+
def test_prepare_select_info(self):
timeout, valid_fds, notifying_zones = self._notify._prepare_select_info()
self.assertEqual(notify_out._IDLE_SLEEP_TIME, timeout)
self.assertListEqual([], valid_fds)
- self._notify._notify_infos[('cn.', 'IN')]._sock = 1
- self._notify._notify_infos[('cn.', 'IN')].notify_timeout = time.time() + 5
+ self._notify._notify_infos[('example.net.', 'IN')]._sock = 1
+ self._notify._notify_infos[('example.net.', 'IN')].notify_timeout = time.time() + 5
timeout, valid_fds, notifying_zones = self._notify._prepare_select_info()
self.assertGreater(timeout, 0)
self.assertListEqual([1], valid_fds)
- self._notify._notify_infos[('cn.', 'IN')]._sock = 1
- self._notify._notify_infos[('cn.', 'IN')].notify_timeout = time.time() - 5
+ self._notify._notify_infos[('example.net.', 'IN')]._sock = 1
+ self._notify._notify_infos[('example.net.', 'IN')].notify_timeout = time.time() - 5
timeout, valid_fds, notifying_zones = self._notify._prepare_select_info()
self.assertEqual(timeout, 0)
self.assertListEqual([1], valid_fds)
- self._notify._notify_infos[('com.', 'IN')]._sock = 2
- self._notify._notify_infos[('com.', 'IN')].notify_timeout = time.time() + 5
+ self._notify._notify_infos[('example.com.', 'IN')]._sock = 2
+ self._notify._notify_infos[('example.com.', 'IN')].notify_timeout = time.time() + 5
timeout, valid_fds, notifying_zones = self._notify._prepare_select_info()
self.assertEqual(timeout, 0)
self.assertListEqual([2, 1], valid_fds)
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/socketserver_mixin.py b/src/lib/python/isc/util/socketserver_mixin.py
index fb5f9a2..e954fe1 100644
--- a/src/lib/python/isc/util/socketserver_mixin.py
+++ b/src/lib/python/isc/util/socketserver_mixin.py
@@ -64,7 +64,7 @@ class NoPollMixIn:
called in anther thread. Note, parameter 'poll_interval' is
just used for interface compatibility; it's never used in this
function.
- '''
+ '''
while True:
# block until the self.socket or self.__read_sock is readable
try:
@@ -74,11 +74,11 @@ class NoPollMixIn:
continue
else:
break
-
+
if self.__read_sock in r:
break
else:
- self._handle_request_noblock()
+ self._handle_request_noblock();
self._is_shut_down.set()
diff --git a/src/lib/python/isc/util/tests/Makefile.am b/src/lib/python/isc/util/tests/Makefile.am
index 730c9a6..f32fda0 100644
--- a/src/lib/python/isc/util/tests/Makefile.am
+++ b/src/lib/python/isc/util/tests/Makefile.am
@@ -1,12 +1,16 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = process_test.py socketserver_mixin_test.py
EXTRA_DIST = $(PYTESTS)
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
- $(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
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/python/isc/util/tests/socketserver_mixin_test.py b/src/lib/python/isc/util/tests/socketserver_mixin_test.py
index 61bc248..a6686d8 100644
--- a/src/lib/python/isc/util/tests/socketserver_mixin_test.py
+++ b/src/lib/python/isc/util/tests/socketserver_mixin_test.py
@@ -25,7 +25,7 @@ class MyHandler(socketserver.BaseRequestHandler):
data = self.request.recv(20)
self.request.send(data)
-class MyServer(NoPollMixIn,
+class MyServer(NoPollMixIn,
socketserver.ThreadingMixIn,
socketserver.TCPServer):
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
new file mode 100644
index 0000000..0b29da4
--- /dev/null
+++ b/src/lib/resolve/Makefile.am
@@ -0,0 +1,29 @@
+SUBDIRS = . tests
+
+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_CPPFLAGS += $(SQLITE_CFLAGS)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libresolve.la
+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..0ee5813
--- /dev/null
+++ b/src/lib/resolve/recursive_query.cc
@@ -0,0 +1,802 @@
+// 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 where we build and store our final answer
+ MessagePtr answer_message_;
+
+ // currently we use upstream as the current list of NS records
+ // we should differentiate between forwarding and resolving
+ boost::shared_ptr<AddressVector> upstream_;
+
+ // 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; if we are in forwarder mode, send a query to
+ // a random nameserver in our forwarders list. If we are in
+ // recursive mode, ask the NSAS to give us an address.
+ void send(IOFetch::Protocol protocol = IOFetch::UDP) {
+ // If are in forwarder mode, send it to a random
+ // forwarder. If not, ask the NSAS for an address
+ const int uc = upstream_->size();
+ 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 if (uc > 0) {
+ // TODO: use boost, or rand()-utility function we provide
+ int serverIndex = rand() % uc;
+ dlog("Sending upstream query (" + question_.toText() +
+ ") to " + upstream_->at(serverIndex).first);
+ ++outstanding_events_;
+ gettimeofday(¤t_ns_qsent_time, NULL);
+ IOFetch query(protocol, io_, question_,
+ upstream_->at(serverIndex).first,
+ upstream_->at(serverIndex).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,
+ boost::shared_ptr<AddressVector> upstream,
+ 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),
+ answer_message_(answer_message),
+ upstream_(upstream),
+ 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 (recursive_mode() &&
+ 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");
+ if (recursive_mode()) {
+ current_ns_address.updateRTT(isc::nsas::AddressEntry::UNREACHABLE);
+ }
+ send();
+ } else {
+ // We are either already done, or out of retries
+ if (recursive_mode() && 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());
+ }
+
+ // Returns true if we are in 'recursive' mode
+ // Returns false if we are in 'forwarding' mode
+ // (i.e. if we have anything in upstream_)
+ bool recursive_mode() const {
+ return upstream_->empty();
+ }
+};
+
+}
+
+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, upstream_,
+ 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, upstream_,
+ test_server_, buffer, crs, query_timeout_,
+ client_timeout_, lookup_timeout_, retries_,
+ nsas_, cache_);
+ }
+ }
+}
+
+
+
+} // namespace asiolink
diff --git a/src/lib/resolve/recursive_query.h b/src/lib/resolve/recursive_query.h
new file mode 100644
index 0000000..1180d55
--- /dev/null
+++ b/src/lib/resolve/recursive_query.h
@@ -0,0 +1,133 @@
+// 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 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/resolve.cc b/src/lib/resolve/resolve.cc
new file mode 100644
index 0000000..f741121
--- /dev/null
+++ b/src/lib/resolve/resolve.cc
@@ -0,0 +1,78 @@
+// 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 <resolve/resolve.h>
+
+#include <dns/message.h>
+#include <dns/opcode.h>
+
+using namespace isc::dns;
+
+namespace {
+ class SectionInserter {
+ public:
+ SectionInserter(MessagePtr message, const Message::Section sect) :
+ message_(message), section_(sect)
+ {}
+ void operator()(const RRsetPtr rrset) {
+ message_->addRRset(section_, rrset, true);
+ }
+ MessagePtr message_;
+ const Message::Section section_;
+ };
+}
+
+namespace isc {
+namespace resolve {
+
+void
+makeErrorMessage(MessagePtr answer_message,
+ const Rcode& error_code)
+{
+ answer_message->clearSection(Message::SECTION_ANSWER);
+ answer_message->clearSection(Message::SECTION_AUTHORITY);
+ answer_message->clearSection(Message::SECTION_ADDITIONAL);
+
+ answer_message->setRcode(error_code);
+}
+
+void initResponseMessage(const isc::dns::Message& query_message,
+ isc::dns::Message& response_message)
+{
+ response_message.setOpcode(query_message.getOpcode());
+ response_message.setQid(query_message.getQid());
+ assert(response_message.getRRCount(Message::SECTION_QUESTION) == 0);
+ response_message.appendSection(Message::SECTION_QUESTION,
+ query_message);
+}
+
+void initResponseMessage(const isc::dns::Question& question,
+ isc::dns::Message& response_message)
+{
+ response_message.setOpcode(isc::dns::Opcode::QUERY());
+ response_message.addQuestion(question);
+}
+
+void copyResponseMessage(const Message& source, MessagePtr target) {
+ target->setRcode(source.getRcode());
+
+ target->appendSection(Message::SECTION_ANSWER, source);
+ target->appendSection(Message::SECTION_AUTHORITY, source);
+ target->appendSection(Message::SECTION_ADDITIONAL, source);
+}
+
+
+} // namespace resolve
+} // namespace isc
+
diff --git a/src/lib/resolve/resolve.h b/src/lib/resolve/resolve.h
new file mode 100644
index 0000000..550b620
--- /dev/null
+++ b/src/lib/resolve/resolve.h
@@ -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.
+
+#ifndef _ISC_RESOLVE_H
+#define _ISC_RESOLVE_H 1
+
+/// This file includes all other libresolve headers, and provides
+/// several helper functions used in resolving.
+
+#include <resolve/resolver_interface.h>
+#include <resolve/resolver_callback.h>
+#include <resolve/response_classifier.h>
+
+#include <dns/rcode.h>
+
+namespace isc {
+namespace resolve {
+
+/// \brief Create an error response
+///
+/// Clears the answer, authority, and additional section of the
+/// given MessagePtr and sets the given error code
+///
+/// Notes: Assuming you have already done initial preparations
+/// on the given answer message (copy the opcode, qid and question
+/// section), you can simply use this to create an error response.
+///
+/// \param answer_message The message to clear and place the error in
+/// \param question The question to add to the
+/// \param error_code The error Rcode
+void makeErrorMessage(isc::dns::MessagePtr answer_message,
+ const isc::dns::Rcode& error_code);
+
+
+/// \brief Initialize a response message
+///
+/// Based on the given query message, this fills in the very
+/// first details of the response (i.e. the Question section and
+/// the Opcode). This allows for direct usage of makeErrorMessage(),
+/// as well as ResolveCache.lookup().
+///
+/// Raises an isc::dns::InvalidMessageOperation if reponse_message is
+/// not in RENDER mode.
+///
+/// \param query_message The query message to take the Question, Qid,
+/// and Opcode from.
+/// \param response_message The fresh response message to initialize
+/// (must be in RENDER mode)
+void initResponseMessage(const isc::dns::Message& query_message,
+ isc::dns::Message& response_message);
+
+
+/// \brief Initialize a response message
+///
+/// Based on the given question, this fills in the very
+/// first details of the response (i.e. the Question section and the
+/// Opcode Query). This allows for direct usage of makeErrorMessage(),
+/// as well as ResolveCache.lookup().
+///
+/// Raises an isc::dns::InvalidMessageOperation if reponse_message is
+/// not in RENDER mode.
+///
+/// \param question The question to place in the Question section
+/// \param response_message The fresh response message to initialize
+/// (must be in RENDER mode)
+void initResponseMessage(const isc::dns::Question& question,
+ isc::dns::Message& response_message);
+
+
+/// \brief Copies the parts relevant for a DNS response to the
+/// target message
+///
+/// This adds all the RRsets in the answer, authority and
+/// additional sections to the target, as well as the response
+/// code
+/// \param source The Message to copy the data from
+/// \param target The Message to copy the data to
+void copyResponseMessage(const isc::dns::Message& source,
+ isc::dns::MessagePtr target);
+
+
+} // namespace resolve
+} // namespace isc
+
+#endif // ISC_RESOLVE_H_
diff --git a/src/lib/resolve/resolver_callback.cc b/src/lib/resolve/resolver_callback.cc
new file mode 100644
index 0000000..c0db55e
--- /dev/null
+++ b/src/lib/resolve/resolver_callback.cc
@@ -0,0 +1,36 @@
+// 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 <resolve/resolver_callback.h>
+
+namespace isc {
+namespace resolve {
+
+void
+ResolverCallbackServer::success(const isc::dns::MessagePtr response)
+{
+ // ignore our response here
+ (void)response;
+
+ server_->resume(true);
+}
+
+void
+ResolverCallbackServer::failure()
+{
+ server_->resume(false);
+}
+
+} // namespace resolve
+} // namespace isc
diff --git a/src/lib/resolve/resolver_callback.h b/src/lib/resolve/resolver_callback.h
new file mode 100644
index 0000000..f69d8a7
--- /dev/null
+++ b/src/lib/resolve/resolver_callback.h
@@ -0,0 +1,48 @@
+// 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 _ISC_RESOLVER_CALLBACK_H
+#define _ISC_RESOLVER_CALLBACK_H 1
+
+#include <asiolink/asiolink.h>
+#include <dns/message.h>
+
+namespace isc {
+namespace resolve {
+
+/// \short Standard Callback for sendQuery for DNSServer instances
+///
+/// This is a standard ResolverInterface::Callback implementation
+/// that is used by Resolver; when RunningQuery finishes and has either
+/// some data or an error, DNSServer::resume() will be called.
+///
+/// This class will ignore the response MessagePtr in the callback,
+/// as the server itself should also have a reference.
+class ResolverCallbackServer : public ResolverInterface::Callback {
+public:
+ ResolverCallbackServer(asiolink::DNSServer* server) :
+ server_(server->clone()) {}
+ ~ResolverCallbackServer() { delete server_; };
+
+ void success(const isc::dns::MessagePtr response);
+ void failure();
+
+private:
+ asiolink::DNSServer* server_;
+};
+
+} //namespace resolve
+} //namespace isc
+
+#endif // ISC_RESOLVER_CALLBACK_H_
diff --git a/src/lib/resolve/resolver_interface.h b/src/lib/resolve/resolver_interface.h
new file mode 100644
index 0000000..1d01e90
--- /dev/null
+++ b/src/lib/resolve/resolver_interface.h
@@ -0,0 +1,98 @@
+// 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 __RESOLVER_INTERFACE_H
+#define __RESOLVER_INTERFACE_H
+
+#include <dns/message.h>
+
+///
+/// \file resolver_interface.h
+/// \short Interface to resolver.
+///
+/// This file contains an interface for the resolver. By subclassing
+/// this abstract interface, other parts of the system can ask the
+/// resolver to do some resolving too.
+///
+/// This is done by creating a subclass of ResolverInterface::Callback,
+/// which defines what to do with the result, and then calling resolve()
+/// on the ResolverInterface implementation.
+///
+/// One default Callback subclass is provided right now, in
+/// resolver_callback.[h|cc], which calls resumse() on a given DNSServer
+///
+
+namespace isc {
+namespace resolve {
+
+///
+/// \short Abstract interface to the resolver.
+///
+/// Abstract interface to the resolver. The NameserverAddressStore uses this
+/// to ask for addresses. It is here because resolver does not yet exist.
+///
+/// It is abstract to allow tests pass dummy resolvers.
+///
+class ResolverInterface {
+ public:
+ /// \short An abstract callback for when the resolver is done.
+ ///
+ /// You can pass an instance of a subclass of this (as a
+ /// CallbackPtr) to RecursiveQuery::sendQuery(), and when it
+ /// is done, it will either call success() if there is an
+ /// answer MessagePtr, or failure(), if the resolver was not
+ /// able to find anything.
+ ///
+ /// Note that a result Message does not necessarily contain
+ /// the actual answer (it could be a noerror/nodata response).
+ class Callback {
+ public:
+ /// \short Some data arrived.
+ virtual void success(const isc::dns::MessagePtr response) = 0;
+
+ ///
+ ///\short No data available.
+ ///
+ ///\todo Provide error reason (result of the
+ /// classification call, for instance? We'd also
+ /// need some way to say 'everything times out')
+ ///
+ virtual void failure() = 0;
+
+ /// \short Virtual destructor, so descendants are cleaned up
+ virtual ~Callback() {};
+ };
+
+ typedef boost::shared_ptr<Callback> CallbackPtr;
+
+ ///
+ ///\short Ask a question.
+ ///
+ /// Asks the resolver a question. Once the answer is ready
+ /// the callback is called.
+ ///
+ /// \param question What to ask. The resolver will decide who.
+ /// \param callback What should happen when the answer is ready.
+ ///
+ virtual void resolve(const isc::dns::QuestionPtr& question,
+ const CallbackPtr& callback) = 0;
+
+ /// \short Virtual destructor, so descendants are properly cleaned up
+ virtual ~ ResolverInterface() {}
+};
+
+} // namespace nsas
+} // namespace isc
+
+#endif //__RESOLVER_INTERFACE_H
diff --git a/src/lib/resolve/response_classifier.cc b/src/lib/resolve/response_classifier.cc
new file mode 100644
index 0000000..02808e4
--- /dev/null
+++ b/src/lib/resolve/response_classifier.cc
@@ -0,0 +1,278 @@
+// 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 <cstddef>
+#include <vector>
+
+#include <resolve/response_classifier.h>
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/rrset.h>
+
+using namespace isc::dns;
+using namespace std;
+
+namespace isc {
+namespace resolve {
+
+// Classify the response in the "message" object.
+
+ResponseClassifier::Category ResponseClassifier::classify(
+ const Question& question, const Message& message,
+ Name& cname_target, unsigned int& cname_count, bool tcignore
+ )
+{
+ // Check header bits
+ if (!message.getHeaderFlag(Message::HEADERFLAG_QR)) {
+ return (NOTRESPONSE); // Query-response bit not set in the response
+ }
+
+ // We only recognise responses to queries here
+ if (message.getOpcode() != Opcode::QUERY()) {
+ return (OPCODE);
+ }
+
+ // Apparently have a response. There must be a single question in it...
+ const vector<QuestionPtr> msgquestion(message.beginQuestion(),
+ message.endQuestion());
+ if (msgquestion.size() != 1) {
+ return (NOTONEQUEST); // Not one question in response question section
+ }
+
+ // ... and the question should be equal to the question given.
+ // XXX: This means that "question" may not be the question sent by the
+ // client. In the case of a CNAME response, the qname of subsequent
+ // questions needs to be altered.
+ if (question != *(msgquestion[0])) {
+ return (MISMATQUEST);
+ }
+
+ // Check for Rcode-related errors.
+ const Rcode& rcode = message.getRcode();
+ if (rcode != Rcode::NOERROR()) {
+ if (rcode == Rcode::NXDOMAIN()) {
+
+ // No such domain. According to RFC2308, the domain referred to by
+ // the QNAME does not exist, although there may be a CNAME in the
+ // answer section and there may be an SOA and/or NS RRs in the
+ // authority section (ignoring any DNSSEC RRs for now).
+ //
+ // Note the "may". There may not be anything. Also, note that if
+ // there is a CNAME in the answer section, the authoritative server
+ // has verified that the name given in the CNAME's RDATA field does
+ // not exist. And that if a CNAME is returned in the answer, then
+ // the QNAME of the RRs in the authority section will refer to the
+ // authority for the CNAME's RDATA and not to the original question.
+ //
+ // Without doing further classification, it is sufficient to say
+ // that if an NXDOMAIN is received, there was no translation of the
+ // QNAME available.
+ return (NXDOMAIN); // Received NXDOMAIN from parent.
+
+ } else {
+
+ // Not NXDOMAIN but not NOERROR either. Must be an RCODE-related
+ // error.
+ return (RCODE);
+ }
+ }
+
+ // All seems OK and we can start looking at the content. However, one
+ // more header check remains - was the response truncated? If so, we'll
+ // probably want to re-query over TCP. However, in some circumstances we
+ // might want to go with what we have. So give the caller the option of
+ // ignoring the TC bit.
+ if (message.getHeaderFlag(Message::HEADERFLAG_TC) && (!tcignore)) {
+ return (TRUNCATED);
+ }
+
+ // By the time we get here, we're assured that the packet format is correct.
+ // We now need to decide as to whether it is an answer, a CNAME, or a
+ // referral. For this, we need to inspect the contents of the answer
+ // and authority sections.
+ const vector<RRsetPtr> answer(
+ message.beginSection(Message::SECTION_ANSWER),
+ message.endSection(Message::SECTION_ANSWER)
+ );
+ const vector<RRsetPtr> authority(
+ message.beginSection(Message::SECTION_AUTHORITY),
+ message.endSection(Message::SECTION_AUTHORITY)
+ );
+
+ // If there is nothing in the answer section, it is a referral - unless
+ // there is no NS in the authority section
+ if (answer.empty()) {
+ if (authority.empty()) {
+ return (EMPTY);
+ }
+ 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
+ // the answer.
+ if (answer.size() == 1) {
+
+ // Does the name and class of the answer match that of the question?
+ if ((answer[0]->getName() == question.getName()) &&
+ (answer[0]->getClass() == question.getClass())) {
+
+ // It does. How about the type of the response? The response
+ // is an answer if the type matches that of the question, or if the
+ // question was for type ANY. It is a CNAME reply if the answer
+ // type is CNAME. And it is an error for anything else.
+ if ((answer[0]->getType() == question.getType()) ||
+ (question.getType() == RRType::ANY())) {
+ return (ANSWER);
+ } else if (answer[0]->getType() == RRType::CNAME()) {
+ RdataIteratorPtr it = answer[0]->getRdataIterator();
+ cname_target = Name(it->getCurrent().toText());
+ ++cname_count;
+ return (CNAME);
+ } else {
+ return (INVTYPE);
+ }
+ }
+ else {
+
+ // Either the name and/or class of the reply don't match that of
+ // the question.
+ return (INVNAMCLASS);
+ }
+ }
+
+ // There are multiple RRsets in the answer. They should all have the same
+ // QCLASS, else there is some error in the response.
+ for (int i = 1; i < answer.size(); ++i) {
+ if (answer[0]->getClass() != answer[i]->getClass()) {
+ return (MULTICLASS);
+ }
+ }
+
+ // If the request type was ANY and they all have the same QNAME, we have
+ // an answer. But if they don't have the same QNAME, we must have an error;
+ // the only way we could get different QNAMES in an answer is if one were a
+ // CNAME - in which case there should no other record types at that QNAME.
+ if (question.getType() == RRType::ANY()) {
+ bool all_same = true;
+ for (int i = 1; (i < answer.size()) && all_same; ++i) {
+ all_same = (answer[0]->getName() == answer[i]->getName());
+ }
+ if (all_same) {
+ return (ANSWER);
+ } else {
+ return (EXTRADATA);
+ }
+ }
+
+ // Multiple RRs in the answer, and not all the same QNAME. This
+ // is either an answer, a CNAME (in either case, there could be multiple
+ // CNAMEs in the chain) or an error.
+ //
+ // So we need to follow the CNAME chain to resolve this. For this to work:
+ //
+ // a) There must be one RR that matches the name, class and type of
+ // the question, and this is a CNAME.
+ // b) The CNAME chain is followed until the end of the chain does not
+ // exist (answer is a CNAME) or it is not of type CNAME (ANSWER).
+ //
+ // In the latter case, if there are additional RRs, it must be an error.
+
+ vector<RRsetPtr> ansrrset(answer);
+ vector<int> present(ansrrset.size(), 1);
+ return cnameChase(question.getName(), question.getType(),
+ cname_target, cname_count,
+ ansrrset, present, ansrrset.size());
+}
+
+// Search the CNAME chain.
+ResponseClassifier::Category ResponseClassifier::cnameChase(
+ const Name& qname, const RRType& qtype,
+ Name& cname_target, unsigned int& cname_count,
+ vector<RRsetPtr>& ansrrset, vector<int>& present, size_t size)
+{
+ // Search through the vector of RRset pointers until we find one with the
+ // right QNAME.
+ for (int i = 0; i < ansrrset.size(); ++i) {
+ if (present[i]) {
+
+ // This entry has not been logically removed, so look at it.
+ if (ansrrset[i]->getName() == qname) {
+
+ // QNAME match. If this RRset is a CNAME, remove it from
+ // further consideration. If nothing is left, the end of the
+ // chain is a CNAME so this is a CNAME. Otherwise replace
+ // the name with the RDATA of the CNAME and call ourself
+ // recursively.
+ if (ansrrset[i]->getType() == RRType::CNAME()) {
+
+ // Don't consider it in the next iteration (although we
+ // can still access it for now).
+ present[i] = 0;
+ --size;
+ if (size == 0) {
+ RdataIteratorPtr it = ansrrset[i]->getRdataIterator();
+ cname_target = Name(it->getCurrent().toText());
+ return (CNAME);
+ } else {
+ if (ansrrset[i]->getRdataCount() != 1) {
+
+ // Multiple RDATA for a CNAME? This is invalid.
+
+ return (NOTSINGLE);
+ }
+ RdataIteratorPtr it = ansrrset[i]->getRdataIterator();
+ Name newname(it->getCurrent().toText());
+
+ // Increase CNAME count, and continue
+ return cnameChase(newname, qtype, cname_target,
+ ++cname_count, ansrrset, present, size);
+ }
+
+ } else {
+
+ // We've got here because the element is not a CNAME. If
+ // this is the last element and the type is the one we are
+ // after, we've found the answer, or it is an error. If
+ // there is more than one RRset left in the list we are
+ // searching, we have extra data in the answer.
+ if (ansrrset[i]->getType() == qtype) {
+ if (size == 1) {
+ return (ANSWERCNAME);
+ } else {
+ return (EXTRADATA);
+ }
+ }
+ return (INVTYPE);
+ }
+ }
+ }
+ }
+
+ // We get here if we've dropped off the end of the list without finding the
+ // QNAME we are looking for. This means that the CNAME chain has ended
+ // but there are additional RRsets in the data.
+
+ return (EXTRADATA);
+}
+
+} // namespace resolve
+} // namespace isc
diff --git a/src/lib/resolve/response_classifier.h b/src/lib/resolve/response_classifier.h
new file mode 100644
index 0000000..3821560
--- /dev/null
+++ b/src/lib/resolve/response_classifier.h
@@ -0,0 +1,157 @@
+// 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 __RESPONSE_CLASSIFIER_H
+#define __RESPONSE_CLASSIFIER_H
+
+#include <cstddef>
+
+#include <dns/question.h>
+#include <dns/message.h>
+#include <dns/question.h>
+
+#define RESOLVER_MAX_CNAME_CHAIN 16
+
+namespace isc {
+namespace resolve {
+
+/// \brief Classify Server Response
+///
+/// This class is used in the recursive server. It is passed an answer received
+/// from an upstream server and categorises it.
+///
+/// TODO: The code here does not take into account any EDNS0 fields.
+
+class ResponseClassifier {
+public:
+
+ /// \brief Category of Answer
+ ///
+ /// In the valid answers, not the distinction between REFERRAL and CNAME.
+ /// A REFERRAL answer means that the answer section of the message is
+ /// empty, but there is something in the authority section. A CNAME means
+ /// that the answer section contains one or more CNAMES in a chain that
+ /// do not end with a non-CNAME RRset.
+ enum Category {
+
+ // Codes indicating that a message is valid.
+
+ ANSWER, ///< Response contains the answer
+ 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()
+ // method relies on these appearing after the "message valid" codes.
+
+ EMPTY, ///< No answer or authority sections
+ EXTRADATA, ///< Answer section contains more RRsets than needed
+ INVNAMCLASS, ///< Invalid name or class in answer
+ INVTYPE, ///< Name/class of answer correct, type is wrong
+ MISMATQUEST, ///< Response question section != question
+ MULTICLASS, ///< Multiple classes in multi-RR answer
+ NOTONEQUEST, ///< Not one question in response question section
+ NOTRESPONSE, ///< Response has the Query/Response bit clear
+ NOTSINGLE, ///< CNAME has multiple RDATA elements.
+ OPCODE, ///< Opcode field does not indicate a query
+ RCODE, ///< RCODE indicated an error
+ TRUNCATED ///< Response was truncated
+ };
+
+ /// \brief Check Error
+ ///
+ /// An inline routine to quickly classify whether the return category is
+ /// an error or not. This makes use of internal knowledge of the order of
+ /// codes in the Category enum.
+ ///
+ /// \param code Return category from classify()
+ ///
+ /// \return true if the category is an error, false if not.
+ static bool error(Category code) {
+ return (code > REFERRAL);
+ }
+
+ /// \brief Classify
+ ///
+ /// Classify the response in the "message" object.
+ ///
+ /// \param question Question that was sent to the server
+ /// \param message Pointer to the associated response from the server.
+ /// \param cname_target If the message contains an (unfinished) CNAME
+ /// chain, this Name will be replaced by the target of the last CNAME
+ /// in the chain
+ /// \param cname_count This unsigned int will be incremented with
+ /// the number of CNAMEs followed
+ /// \param tcignore If set, the TC bit in a response packet is
+ /// ignored. Otherwise the error code TRUNCATED will be returned. The
+ /// only time this is likely to be used is in development where we are not
+ /// going to fail over to TCP and will want to use what is returned, even
+ /// if some of the response was lost.
+ static Category classify(const isc::dns::Question& question,
+ const isc::dns::Message& message,
+ isc::dns::Name& cname_target, unsigned int& cname_count,
+ bool tcignore = false);
+
+private:
+ /// \brief Follow CNAMEs
+ ///
+ /// Given a QNAME and an answer section that contains CNAMEs, assume that
+ /// they form a CNAME chain and search through them. Possible outcomes
+ /// are:
+ ///
+ /// a) All CNAMES and they form a chain. The result is a referral.
+ /// b) All but one are CNAMES and they form a chain. The other is pointed
+ /// to by the last element of the chain and is the correct QTYPE. The
+ /// result is an answer.
+ /// c) Having followed the CNAME chain as far as we can, there is one
+ /// remaining RRset that is of the wrong type, or there are multiple
+ /// RRsets remaining. return the EXTRADATA code.
+ ///
+ /// \param qname Question name we are searching for
+ /// \param qtype Question type we are search for. (This is assumed not
+ /// to be "ANY".)
+ /// \param ansrrset Vector of RRsetPtr pointing to the RRsets we are
+ /// considering.
+ /// \param present Array of "int" the same size of ansrrset, with each
+ /// element set to "1" to allow the corresponding element of ansrrset to
+ /// be checked, and "0" to skip it. This might be premature optimisation,
+ /// but the algorithm would otherwise involve duplicating the RRset
+ /// vector then removing elements from random positions one by one. As
+ /// each removal involves the destruction of an "xxxPtr" element (which
+ /// presently is implemented by boost::shared_ptr), the overhad of memory
+ /// management seemed high. This solution imposes some additional loop
+ /// cycles, but that should be minimal compared with the overhead of the
+ /// memory management.
+ /// \param cname_target If the message contains an (unfinished) CNAME
+ /// chain, this Name will be replaced by the target of the last CNAME
+ /// in the chain
+ /// \param cname_count This unsigned int will be incremented with
+ /// the number of CNAMEs followed
+ /// \param size Number of elements to check. See description of \c present
+ /// for details.
+ static Category cnameChase(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ isc::dns::Name& cname_target, unsigned int& cname_count,
+ std::vector<isc::dns::RRsetPtr>& ansrrset, std::vector<int>& present,
+ size_t size);
+};
+
+#endif // __RESPONSE_CLASSIFIER_H
+
+} // namespace resolve
+} // namespace isc
diff --git a/src/lib/resolve/tests/Makefile.am b/src/lib/resolve/tests/Makefile.am
new file mode 100644
index 0000000..a403272
--- /dev/null
+++ b/src/lib/resolve/tests/Makefile.am
@@ -0,0 +1,36 @@
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+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
+
+noinst_PROGRAMS = $(TESTS)
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..ab1ffa3
--- /dev/null
+++ b/src/lib/resolve/tests/recursive_query_unittest.cc
@@ -0,0 +1,861 @@
+// 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>
+
+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());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ rq.resolve(q, 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));
+ query.resolve(question, 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));
+ query.resolve(question, 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));
+ 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(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/resolve_unittest.cc b/src/lib/resolve/tests/resolve_unittest.cc
new file mode 100644
index 0000000..85d264d
--- /dev/null
+++ b/src/lib/resolve/tests/resolve_unittest.cc
@@ -0,0 +1,198 @@
+// 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 <iostream>
+#include <gtest/gtest.h>
+
+#include <dns/message.h>
+#include <dns/question.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <resolve/resolve.h>
+
+using namespace isc::dns;
+
+namespace {
+
+class ResolveHelperFunctionsTest : public ::testing::Test {
+public:
+ ResolveHelperFunctionsTest() :
+ message_a_(new Message(Message::RENDER)),
+ message_b_(new Message(Message::RENDER)),
+ question_(new Question(Name("www.example.com"), RRClass::IN(), RRType::A()))
+ {
+ createMessageA();
+ createMessageB();
+ };
+
+ void createMessageA() {
+ message_a_->setOpcode(Opcode::QUERY());
+ message_a_->setRcode(Rcode::NOERROR());
+ message_a_->addQuestion(question_);
+ }
+
+ void createMessageB() {
+ message_b_->setOpcode(Opcode::QUERY());
+ message_b_->setRcode(Rcode::NOERROR());
+ message_b_->addQuestion(question_);
+
+ // We could reuse the same rrset in the different sections,
+ // but to be sure, we create separate ones
+ RRsetPtr answer_rrset(new RRset(Name("www.example.com"),
+ RRClass::IN(), RRType::TXT(),
+ RRTTL(3600)));
+ answer_rrset->addRdata(rdata::createRdata(RRType::TXT(),
+ RRClass::IN(),
+ "Answer"));
+ message_b_->addRRset(Message::SECTION_ANSWER, answer_rrset);
+
+ RRsetPtr auth_rrset(new RRset(Name("www.example.com"),
+ RRClass::IN(), RRType::TXT(),
+ RRTTL(3600)));
+ auth_rrset->addRdata(rdata::createRdata(RRType::TXT(),
+ RRClass::IN(),
+ "Authority"));
+ auth_rrset->addRdata(rdata::createRdata(RRType::TXT(),
+ RRClass::IN(),
+ "Rdata"));
+ message_b_->addRRset(Message::SECTION_AUTHORITY, auth_rrset);
+
+ RRsetPtr add_rrset(new RRset(Name("www.example.com"),
+ RRClass::IN(), RRType::TXT(),
+ RRTTL(3600)));
+ add_rrset->addRdata(rdata::createRdata(RRType::TXT(),
+ RRClass::IN(),
+ "Additional"));
+ add_rrset->addRdata(rdata::createRdata(RRType::TXT(),
+ RRClass::IN(),
+ "Rdata"));
+ add_rrset->addRdata(rdata::createRdata(RRType::TXT(),
+ RRClass::IN(),
+ "fields."));
+ message_b_->addRRset(Message::SECTION_ADDITIONAL, add_rrset);
+ };
+
+ MessagePtr message_a_;
+ MessagePtr message_b_;
+ QuestionPtr question_;
+};
+
+TEST_F(ResolveHelperFunctionsTest, makeErrorMessageEmptyMessage) {
+ ASSERT_EQ(Rcode::NOERROR(), message_a_->getRcode());
+ ASSERT_EQ(1, message_a_->getRRCount(Message::SECTION_QUESTION));
+ ASSERT_EQ(0, message_a_->getRRCount(Message::SECTION_ANSWER));
+ ASSERT_EQ(0, message_a_->getRRCount(Message::SECTION_AUTHORITY));
+ ASSERT_EQ(0, message_a_->getRRCount(Message::SECTION_ADDITIONAL));
+
+ isc::resolve::makeErrorMessage(message_a_, Rcode::SERVFAIL());
+ EXPECT_EQ(Rcode::SERVFAIL(), message_a_->getRcode());
+ EXPECT_EQ(1, message_a_->getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(0, message_a_->getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, message_a_->getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, message_a_->getRRCount(Message::SECTION_ADDITIONAL));
+}
+
+TEST_F(ResolveHelperFunctionsTest, makeErrorMessageNonEmptyMessage) {
+
+ ASSERT_EQ(Rcode::NOERROR(), message_b_->getRcode());
+ ASSERT_EQ(1, message_b_->getRRCount(Message::SECTION_QUESTION));
+ ASSERT_EQ(1, message_b_->getRRCount(Message::SECTION_ANSWER));
+ ASSERT_EQ(2, message_b_->getRRCount(Message::SECTION_AUTHORITY));
+ ASSERT_EQ(3, message_b_->getRRCount(Message::SECTION_ADDITIONAL));
+
+ isc::resolve::makeErrorMessage(message_b_, Rcode::FORMERR());
+ EXPECT_EQ(Rcode::FORMERR(), message_b_->getRcode());
+ EXPECT_EQ(1, message_b_->getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(0, message_b_->getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, message_b_->getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, message_b_->getRRCount(Message::SECTION_ADDITIONAL));
+}
+
+void
+compareSections(const Message& message_a, const Message& message_b,
+ Message::Section section)
+{
+ RRsetIterator rrs_a = message_a.beginSection(section);
+ RRsetIterator rrs_b = message_b.beginSection(section);
+ while (rrs_a != message_a.endSection(section) &&
+ rrs_b != message_b.endSection(section)
+ ) {
+ EXPECT_EQ(*rrs_a, *rrs_b);
+ ++rrs_a;
+ ++rrs_b;
+ }
+ // can't use EXPECT_EQ here, no eqHelper for endsection comparison
+ EXPECT_TRUE(rrs_a == message_a.endSection(section));
+ EXPECT_TRUE(rrs_b == message_b.endSection(section));
+}
+
+TEST_F(ResolveHelperFunctionsTest, initResponseMessage) {
+ Message response_parse(Message::PARSE);
+ EXPECT_THROW(isc::resolve::initResponseMessage(*message_a_,
+ response_parse),
+ isc::dns::InvalidMessageOperation);
+ EXPECT_THROW(isc::resolve::initResponseMessage(*question_,
+ response_parse),
+ isc::dns::InvalidMessageOperation);
+
+ Message response1(Message::RENDER);
+ isc::resolve::initResponseMessage(*message_a_, response1);
+ ASSERT_EQ(message_a_->getOpcode(), response1.getOpcode());
+ ASSERT_EQ(message_a_->getQid(), response1.getQid());
+ isc::dns::QuestionIterator qi = response1.beginQuestion();
+ ASSERT_EQ(*question_, **qi);
+ ASSERT_TRUE(++qi == response1.endQuestion());
+
+ Message response2(Message::RENDER);
+ isc::resolve::initResponseMessage(*question_, response2);
+ ASSERT_EQ(Opcode::QUERY(), response2.getOpcode());
+ ASSERT_EQ(0, response2.getQid());
+ qi = response2.beginQuestion();
+ ASSERT_EQ(*question_, **qi);
+ ASSERT_TRUE(++qi == response2.endQuestion());
+}
+
+TEST_F(ResolveHelperFunctionsTest, copyAnswerMessage) {
+ message_b_->setRcode(Rcode::NXDOMAIN());
+
+ ASSERT_NE(message_b_->getRcode(), message_a_->getRcode());
+ ASSERT_NE(message_b_->getRRCount(Message::SECTION_ANSWER),
+ message_a_->getRRCount(Message::SECTION_ANSWER));
+ ASSERT_NE(message_b_->getRRCount(Message::SECTION_AUTHORITY),
+ message_a_->getRRCount(Message::SECTION_AUTHORITY));
+ ASSERT_NE(message_b_->getRRCount(Message::SECTION_ADDITIONAL),
+ message_a_->getRRCount(Message::SECTION_ADDITIONAL));
+ ASSERT_EQ(0, message_a_->getRRCount(Message::SECTION_ANSWER));
+ ASSERT_EQ(0, message_a_->getRRCount(Message::SECTION_AUTHORITY));
+ ASSERT_EQ(0, message_a_->getRRCount(Message::SECTION_ADDITIONAL));
+
+ isc::resolve::copyResponseMessage(*message_b_, message_a_);
+
+ EXPECT_EQ(message_b_->getRcode(), message_a_->getRcode());
+ ASSERT_EQ(message_b_->getRRCount(Message::SECTION_ANSWER),
+ message_a_->getRRCount(Message::SECTION_ANSWER));
+ ASSERT_EQ(message_b_->getRRCount(Message::SECTION_AUTHORITY),
+ message_a_->getRRCount(Message::SECTION_AUTHORITY));
+ ASSERT_EQ(message_b_->getRRCount(Message::SECTION_ADDITIONAL),
+ message_a_->getRRCount(Message::SECTION_ADDITIONAL));
+
+
+ compareSections(*message_a_, *message_b_, Message::SECTION_ANSWER);
+ compareSections(*message_a_, *message_b_, Message::SECTION_AUTHORITY);
+ compareSections(*message_a_, *message_b_, Message::SECTION_ADDITIONAL);
+}
+
+} // Anonymous namespace
diff --git a/src/lib/resolve/tests/resolver_callback_unittest.cc b/src/lib/resolve/tests/resolver_callback_unittest.cc
new file mode 100644
index 0000000..6370e22
--- /dev/null
+++ b/src/lib/resolve/tests/resolver_callback_unittest.cc
@@ -0,0 +1,90 @@
+// 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 <gtest/gtest.h>
+#include <resolve/resolver_callback.h>
+#include <asiolink/asiolink.h>
+
+using namespace isc::resolve;
+
+// Dummy subclass for DNSServer*
+// We want to check if resume is called
+// Since the server will get cloned(), we want the clones to share
+// our bools for whether resume got called and with what value
+class DummyServer : public asiolink::DNSServer {
+public:
+ DummyServer(DummyServer* orig) {
+ resume_called_ = orig->getResumeCalled();
+ resume_value_ = orig->getResumeValue();
+ }
+ DummyServer(bool* resume_called, bool* resume_value) :
+ resume_called_(resume_called), resume_value_(resume_value)
+ {}
+
+ bool* getResumeCalled() { return resume_called_; }
+ bool* getResumeValue() { return resume_value_; }
+
+ DNSServer* clone() {
+ DummyServer* n = new DummyServer(this);
+ return n;
+ }
+
+ void resume(bool value) {
+ *resume_called_ = true;
+ *resume_value_ = value;
+ }
+
+private:
+ bool* resume_called_;
+ bool* resume_value_;
+};
+
+class ResolverCallbackServerTest : public ::testing::Test {
+public:
+ ResolverCallbackServerTest() : resume_called_(false),
+ resume_value_(false) {
+ server_ = new DummyServer(&resume_called_, &resume_value_);
+ callback_ = new ResolverCallbackServer(server_);
+ };
+
+ ~ResolverCallbackServerTest() {
+ delete callback_;
+ delete server_;
+ }
+
+ DummyServer* getServer() { return server_; }
+ ResolverCallbackServer* getCallback() { return callback_; }
+ bool getResumeCalled() { return resume_called_; }
+ bool getResumeValue() { return resume_value_; }
+
+private:
+ DummyServer* server_;
+ ResolverCallbackServer* callback_;
+ bool resume_called_;
+ bool resume_value_;
+};
+
+TEST_F(ResolverCallbackServerTest, testSuccess) {
+ EXPECT_FALSE(getResumeCalled());
+ getCallback()->success(isc::dns::MessagePtr());
+ EXPECT_TRUE(getResumeCalled());
+ EXPECT_TRUE(getResumeValue());
+}
+
+TEST_F(ResolverCallbackServerTest, testFailure) {
+ EXPECT_FALSE(getResumeCalled());
+ getCallback()->failure();
+ EXPECT_TRUE(getResumeCalled());
+ EXPECT_FALSE(getResumeValue());
+}
diff --git a/src/lib/resolve/tests/response_classifier_unittest.cc b/src/lib/resolve/tests/response_classifier_unittest.cc
new file mode 100644
index 0000000..23c8666
--- /dev/null
+++ b/src/lib/resolve/tests/response_classifier_unittest.cc
@@ -0,0 +1,554 @@
+// 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 <iostream>
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+
+#include <resolve/response_classifier.h>
+
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/question.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace rdata;
+using namespace isc::dns::rdata::generic;
+using namespace isc::dns::rdata::in;
+using namespace isc::resolve;
+
+
+namespace {
+class ResponseClassifierTest : public ::testing::Test {
+public:
+ /// \brief Constructor
+ ///
+ /// The naming convention is:
+ ///
+ /// <category>_<class>_<type>_<name>
+ ///
+ /// <category> is "qu" (question), "rrs" (rrset),
+ /// <qclass> is self-explanatory
+ /// <qtype> is self-explanatory
+ /// <name> is the first part of the domain name (all expected to be in
+ /// example.com)
+ ///
+ /// Message variables
+ ///
+ /// msg_<qtype> Where <qtype> is the type of query. These are only used
+ /// in the early tests where simple messages are required.
+
+ ResponseClassifierTest() :
+ msg_a(Message::RENDER),
+ msg_any(Message::RENDER),
+ qu_ch_a_www(Name("www.example.com"), RRClass::CH(), RRType::A()),
+ qu_in_any_www(Name("www.example.com"), RRClass::IN(), RRType::ANY()),
+ qu_in_a_www2(Name("www2.example.com"), RRClass::IN(), RRType::A()),
+ qu_in_a_www(Name("www.example.com"), RRClass::IN(), RRType::A()),
+ qu_in_cname_www1(Name("www1.example.com"), RRClass::IN(), RRType::A()),
+ qu_in_ns_(Name("example.com"), RRClass::IN(), RRType::NS()),
+ qu_in_txt_www(Name("www.example.com"), RRClass::IN(), RRType::TXT()),
+ rrs_hs_txt_www(new RRset(Name("www.example.com"), RRClass::HS(),
+ RRType::TXT(), RRTTL(300))),
+ rrs_in_a_mail(new RRset(Name("mail.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_a_www(new RRset(Name("www.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_cname_www1(new RRset(Name("www1.example.com"), RRClass::IN(),
+ RRType::CNAME(), RRTTL(300))),
+ rrs_in_cname_www2(new RRset(Name("www2.example.com"), RRClass::IN(),
+ 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("."),
+ cname_count(0)
+ {
+ // Set up the message to indicate a successful response to the question
+ // "www.example.com A", but don't add in any response sections.
+ msg_a.setHeaderFlag(Message::HEADERFLAG_QR);
+ msg_a.setOpcode(Opcode::QUERY());
+ msg_a.setRcode(Rcode::NOERROR());
+ msg_a.addQuestion(qu_in_a_www);
+
+ // ditto for the query "www.example.com ANY"
+ msg_any.setHeaderFlag(Message::HEADERFLAG_QR);
+ msg_any.setOpcode(Opcode::QUERY());
+ msg_any.setRcode(Rcode::NOERROR());
+ msg_any.addQuestion(qu_in_any_www);
+
+ // The next set of assignments set up the following zone records
+ //
+ // example.com NS ns0.isc.org
+ // NS ns0.example.org
+ //
+ // www.example.com A 1.2.3.4
+ // TXT "An example text string"
+ //
+ // mail.example.com A 4.5.6.7
+ //
+ // www1.example.com CNAME www.example.com
+ //
+ // www2.example.com CNAME www1.example.com
+
+ // 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")));
+ rrs_in_txt_www->addRdata(ConstRdataPtr(
+ new TXT("An example text string")));
+
+ // ... for the mail host
+ rrs_in_a_mail->addRdata(ConstRdataPtr(new A("5.6.7.8")));
+
+ // ... the CNAME records
+ rrs_in_cname_www1->addRdata(ConstRdataPtr(
+ new CNAME("www.example.com")));
+ rrs_in_cname_www2->addRdata(ConstRdataPtr(
+ new CNAME("www1.example.com")));
+ }
+
+ Message msg_a; // Pointer to message in RENDER state
+ Message msg_any; // Pointer to message in RENDER state
+ Question qu_ch_a_www; // www.example.com CH A
+ Question qu_in_any_www; // www.example.com IN ANY
+ Question qu_in_a_www2; // www.example.com IN ANY
+ Question qu_in_a_www; // www.example.com IN A
+ Question qu_in_cname_www1; // www1.example.com IN CNAME
+ Question qu_in_ns_; // example.com IN NS
+ Question qu_in_txt_www; // www.example.com IN TXT
+ RRsetPtr rrs_hs_txt_www; // www.example.com HS TXT
+ RRsetPtr rrs_in_a_mail; // mail.example.com IN A
+ RRsetPtr rrs_in_a_www; // www.example.com IN A
+ 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
+ // CNAME chain
+ unsigned int cname_count; // Used to count cnames in a chain
+};
+
+// Test that the error() function categorises the codes correctly.
+
+TEST_F(ResponseClassifierTest, StatusCodes) {
+ EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::ANSWER));
+ EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::ANSWERCNAME));
+ EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::CNAME));
+ EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::NXDOMAIN));
+ EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::REFERRAL));
+
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::EMPTY));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::EXTRADATA));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::INVNAMCLASS));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::INVTYPE));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::MISMATQUEST));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::MULTICLASS));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::NOTONEQUEST));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::NOTRESPONSE));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::NOTSINGLE));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::OPCODE));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::RCODE));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::TRUNCATED));
+}
+
+// Test that the system will reject a message which is a query.
+
+TEST_F(ResponseClassifierTest, Query) {
+
+ // Set up message to indicate a query (QR flag = 0, one question). By
+ // default the opcode will be 0 (query)
+ msg_a.setHeaderFlag(Message::HEADERFLAG_QR, false);
+
+ // Should be rejected as it is a query, not a response
+ EXPECT_EQ(ResponseClassifier::NOTRESPONSE,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+}
+
+// Check that we get an OPCODE error on all but QUERY opcodes.
+
+TEST_F(ResponseClassifierTest, Opcode) {
+
+ uint8_t query = static_cast<uint8_t>(Opcode::QUERY().getCode());
+
+ for (uint8_t i = 0; i < (1 << 4); ++i) {
+ msg_a.setOpcode(Opcode(i));
+ if (i == query) {
+ EXPECT_NE(ResponseClassifier::OPCODE,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+ } else {
+ EXPECT_EQ(ResponseClassifier::OPCODE,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+ }
+ }
+}
+
+// Test that the system will reject a response with anything other than one
+// question.
+
+TEST_F(ResponseClassifierTest, MultipleQuestions) {
+
+ // Create a message object for this test that has no question section.
+ Message message(Message::RENDER);
+ message.setHeaderFlag(Message::HEADERFLAG_QR);
+ message.setOpcode(Opcode::QUERY());
+ message.setRcode(Rcode::NOERROR());
+
+ // Zero questions
+ EXPECT_EQ(ResponseClassifier::NOTONEQUEST,
+ ResponseClassifier::classify(qu_in_a_www, message,
+ cname_target, cname_count));
+
+ // One question
+ message.addQuestion(qu_in_a_www);
+ EXPECT_NE(ResponseClassifier::NOTONEQUEST,
+ ResponseClassifier::classify(qu_in_a_www, message,
+ cname_target, cname_count));
+
+ // Two questions
+ message.addQuestion(qu_in_ns_);
+ EXPECT_EQ(ResponseClassifier::NOTONEQUEST,
+ ResponseClassifier::classify(qu_in_a_www, message,
+ cname_target, cname_count));
+
+ // And finish the check with three questions
+ message.addQuestion(qu_in_txt_www);
+ EXPECT_EQ(ResponseClassifier::NOTONEQUEST,
+ ResponseClassifier::classify(qu_in_a_www, message,
+ cname_target, cname_count));
+}
+
+// Test that the question in the question section in the message response
+// is equal to the question supplied.
+
+TEST_F(ResponseClassifierTest, SameQuestion) {
+
+ EXPECT_EQ(ResponseClassifier::MISMATQUEST,
+ ResponseClassifier::classify(qu_in_ns_, msg_a,
+ cname_target, cname_count));
+ EXPECT_NE(ResponseClassifier::MISMATQUEST,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+}
+
+// Should get an NXDOMAIN response only on an NXDOMAIN RCODE.
+
+TEST_F(ResponseClassifierTest, NXDOMAIN) {
+
+ uint16_t nxdomain = static_cast<uint16_t>(Rcode::NXDOMAIN().getCode());
+
+ for (uint8_t i = 0; i < (1 << 4); ++i) {
+ msg_a.setRcode(Rcode(i));
+ if (i == nxdomain) {
+ EXPECT_EQ(ResponseClassifier::NXDOMAIN,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+ } else {
+ EXPECT_NE(ResponseClassifier::NXDOMAIN,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+ }
+ }
+}
+
+// Check that we get an RCODE error on all but NXDOMAIN and NOERROR responses.
+
+TEST_F(ResponseClassifierTest, RCODE) {
+
+ uint16_t nxdomain = static_cast<uint16_t>(Rcode::NXDOMAIN().getCode());
+ uint16_t noerror = static_cast<uint16_t>(Rcode::NOERROR().getCode());
+
+ for (uint8_t i = 0; i < (1 << 4); ++i) {
+ msg_a.setRcode(Rcode(i));
+ if ((i == nxdomain) || (i == noerror)) {
+ EXPECT_NE(ResponseClassifier::RCODE,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+ } else {
+ EXPECT_EQ(ResponseClassifier::RCODE,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+ }
+ }
+}
+
+// Test that the code will detect a truncated message. Even if nothing else
+// is wrong, we'll want to retry the query if we receive a truncated code.
+// However, we give the option to the user of the code aws to whether they
+// want to take into account the truncated bit.
+
+TEST_F(ResponseClassifierTest, Truncated) {
+
+ // Don't expect the truncated code whatever option we ask for if the TC
+ // bit is not set.
+ msg_a.setHeaderFlag(Message::HEADERFLAG_TC, false);
+ EXPECT_NE(ResponseClassifier::TRUNCATED,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count, true));
+ EXPECT_NE(ResponseClassifier::TRUNCATED,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count, false));
+
+ // Expect the truncated code if the TC bit is set, only if we don't ignore
+ // it.
+ msg_a.setHeaderFlag(Message::HEADERFLAG_TC, true);
+ EXPECT_NE(ResponseClassifier::TRUNCATED,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count, true));
+ EXPECT_EQ(ResponseClassifier::TRUNCATED,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count, false));
+}
+
+// Check for an empty packet (i.e. no error, but with the answer and additional
+// sections empty).
+
+TEST_F(ResponseClassifierTest, Empty) {
+
+ EXPECT_EQ(ResponseClassifier::EMPTY,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count));
+}
+
+// Anything where we have an empty answer section but something in the
+// authority section is a referral (if the status is NOERROR).
+
+TEST_F(ResponseClassifierTest, EmptyAnswerReferral) {
+
+ msg_a.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_);
+ EXPECT_EQ(ResponseClassifier::REFERRAL,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count));
+
+}
+
+// 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
+// must be on the QNAME/QCLASS alone.
+
+TEST_F(ResponseClassifierTest, SingleAnswer) {
+
+ // Check a question that matches the answer
+ msg_a.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ EXPECT_EQ(ResponseClassifier::ANSWER,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count));
+
+ // Check an ANY question that matches the answer
+ msg_any.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ EXPECT_EQ(ResponseClassifier::ANSWER,
+ ResponseClassifier::classify(qu_in_any_www, msg_any, cname_target,
+ cname_count));
+
+ // Check a CNAME response that matches the QNAME.
+ Message message_a(Message::RENDER);
+ message_a.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_a.setOpcode(Opcode::QUERY());
+ message_a.setRcode(Rcode::NOERROR());
+ message_a.addQuestion(qu_in_cname_www1);
+ message_a.addRRset(Message::SECTION_ANSWER, rrs_in_cname_www1);
+ EXPECT_EQ(ResponseClassifier::CNAME,
+ ResponseClassifier::classify(qu_in_cname_www1, message_a,
+ cname_target, cname_count));
+
+ // Check if the answer QNAME does not match the question
+ // Q: www.example.com IN A
+ // A: mail.example.com IN A
+ Message message_b(Message::RENDER);
+ message_b.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_b.setOpcode(Opcode::QUERY());
+ message_b.setRcode(Rcode::NOERROR());
+ message_b.addQuestion(qu_in_a_www);
+ message_b.addRRset(Message::SECTION_ANSWER, rrs_in_a_mail);
+ EXPECT_EQ(ResponseClassifier::INVNAMCLASS,
+ ResponseClassifier::classify(qu_in_a_www, message_b,
+ cname_target, cname_count));
+
+ // Check if the answer class does not match the question
+ // Q: www.example.com CH A
+ // A: www.example.com IN A
+ Message message_c(Message::RENDER);
+ message_c.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_c.setOpcode(Opcode::QUERY());
+ message_c.setRcode(Rcode::NOERROR());
+ message_c.addQuestion(qu_ch_a_www);
+ message_c.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ EXPECT_EQ(ResponseClassifier::INVNAMCLASS,
+ ResponseClassifier::classify(qu_ch_a_www, message_c,
+ cname_target, cname_count));
+
+ // Check if the answer type does not match the question
+ // Q: www.example.com IN A
+ // A: www.example.com IN TXT
+ Message message_d(Message::RENDER);
+ message_d.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_d.setOpcode(Opcode::QUERY());
+ message_d.setRcode(Rcode::NOERROR());
+ message_d.addQuestion(qu_in_a_www);
+ message_d.addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+ EXPECT_EQ(ResponseClassifier::INVTYPE,
+ ResponseClassifier::classify(qu_in_a_www, message_d,
+ cname_target, cname_count));
+}
+
+// Check what happens if we have multiple RRsets in the answer.
+
+TEST_F(ResponseClassifierTest, MultipleAnswerRRsets) {
+
+ // All the same QNAME but different types is only valid on an ANY query.
+ Message message_a(Message::RENDER);
+ message_a.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_a.setOpcode(Opcode::QUERY());
+ message_a.setRcode(Rcode::NOERROR());
+ message_a.addQuestion(qu_in_any_www);
+ message_a.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ message_a.addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+ EXPECT_EQ(ResponseClassifier::ANSWER,
+ ResponseClassifier::classify(qu_in_any_www, message_a,
+ cname_target, cname_count));
+
+ // On another type of query, it results in an EXTRADATA error
+ Message message_b(Message::RENDER);
+ message_b.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_b.setOpcode(Opcode::QUERY());
+ message_b.setRcode(Rcode::NOERROR());
+ message_b.addQuestion(qu_in_a_www);
+ message_b.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ message_b.addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+ EXPECT_EQ(ResponseClassifier::EXTRADATA,
+ ResponseClassifier::classify(qu_in_a_www, message_b,
+ cname_target, cname_count));
+
+ // Same QNAME on an ANY query is not valid with mixed classes
+ Message message_c(Message::RENDER);
+ message_c.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_c.setOpcode(Opcode::QUERY());
+ message_c.setRcode(Rcode::NOERROR());
+ message_c.addQuestion(qu_in_any_www);
+ message_c.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ message_c.addRRset(Message::SECTION_ANSWER, rrs_hs_txt_www);
+ EXPECT_EQ(ResponseClassifier::MULTICLASS,
+ ResponseClassifier::classify(qu_in_any_www, message_c,
+ cname_target, cname_count));
+
+ // Mixed QNAME is not valid unless QNAME requested is a CNAME.
+ Message message_d(Message::RENDER);
+ message_d.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_d.setOpcode(Opcode::QUERY());
+ message_d.setRcode(Rcode::NOERROR());
+ message_d.addQuestion(qu_in_a_www);
+ message_d.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ message_d.addRRset(Message::SECTION_ANSWER, rrs_in_a_mail);
+ EXPECT_EQ(ResponseClassifier::EXTRADATA,
+ ResponseClassifier::classify(qu_in_a_www, message_d,
+ cname_target, cname_count));
+
+ // Mixed QNAME is not valid when the query is an ANY.
+ Message message_e(Message::RENDER);
+ message_e.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_e.setOpcode(Opcode::QUERY());
+ message_e.setRcode(Rcode::NOERROR());
+ message_e.addQuestion(qu_in_any_www);
+ message_e.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ message_e.addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+ message_e.addRRset(Message::SECTION_ANSWER, rrs_in_a_mail);
+ EXPECT_EQ(ResponseClassifier::EXTRADATA,
+ ResponseClassifier::classify(qu_in_any_www, message_e,
+ cname_target, cname_count));
+}
+
+// CNAME chain is CNAME if it terminates in a CNAME, answer if it
+// does not, and error if there are RRs left over.
+TEST_F(ResponseClassifierTest, CNAMEChain) {
+
+ // Answer contains a single CNAME
+ Message message_a(Message::RENDER);
+ message_a.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_a.setOpcode(Opcode::QUERY());
+ message_a.setRcode(Rcode::NOERROR());
+ message_a.addQuestion(qu_in_a_www2);
+ message_a.addRRset(Message::SECTION_ANSWER, rrs_in_cname_www2);
+ EXPECT_EQ(ResponseClassifier::CNAME,
+ ResponseClassifier::classify(qu_in_a_www2, message_a,
+ cname_target, cname_count));
+
+ // Add a CNAME for www1, and it should still return a CNAME
+ message_a.addRRset(Message::SECTION_ANSWER, rrs_in_cname_www1);
+ EXPECT_EQ(ResponseClassifier::CNAME,
+ ResponseClassifier::classify(qu_in_a_www2, message_a,
+ cname_target, cname_count));
+
+ // Add the A record for www and it should be an answer
+ message_a.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ EXPECT_EQ(ResponseClassifier::ANSWERCNAME,
+ ResponseClassifier::classify(qu_in_a_www2, message_a,
+ cname_target, cname_count));
+
+ // Adding an unrelated TXT record should result in EXTRADATA
+ message_a.addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+ EXPECT_EQ(ResponseClassifier::EXTRADATA,
+ ResponseClassifier::classify(qu_in_a_www2, message_a,
+ cname_target, cname_count));
+
+ // Recreate the chain, but this time end with a TXT RR and not the A
+ // record. This should return INVTYPE.
+ Message message_b(Message::RENDER);
+ message_b.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_b.setOpcode(Opcode::QUERY());
+ message_b.setRcode(Rcode::NOERROR());
+ message_b.addQuestion(qu_in_a_www2);
+ message_b.addRRset(Message::SECTION_ANSWER, rrs_in_cname_www2);
+ message_b.addRRset(Message::SECTION_ANSWER, rrs_in_cname_www1);
+ message_b.addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+
+ EXPECT_EQ(ResponseClassifier::INVTYPE,
+ ResponseClassifier::classify(qu_in_a_www2, message_b,
+ cname_target, cname_count));
+}
+
+} // Anonymous namespace
diff --git a/src/lib/resolve/tests/run_unittests.cc b/src/lib/resolve/tests/run_unittests.cc
new file mode 100644
index 0000000..f80e167
--- /dev/null
+++ b/src/lib/resolve/tests/run_unittests.cc
@@ -0,0 +1,24 @@
+// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return (RUN_ALL_TESTS());
+}
diff --git a/src/lib/server_common/Makefile.am b/src/lib/server_common/Makefile.am
new file mode 100644
index 0000000..dfb3014
--- /dev/null
+++ b/src/lib/server_common/Makefile.am
@@ -0,0 +1,26 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_CLANGPP
+# clang++ complains about unused function parameters in some boost header
+# files.
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+lib_LTLIBRARIES = libserver_common.la
+libserver_common_la_SOURCES = portconfig.h portconfig.cc
+libserver_common_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+libserver_common_la_LIBADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+libserver_common_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
+libserver_common_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
+
+CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/server_common/portconfig.cc b/src/lib/server_common/portconfig.cc
new file mode 100644
index 0000000..3765f52
--- /dev/null
+++ b/src/lib/server_common/portconfig.cc
@@ -0,0 +1,119 @@
+// 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 <server_common/portconfig.h>
+
+#include <asiolink/io_address.h>
+#include <asiolink/dns_service.h>
+#include <log/dummylog.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+using namespace std;
+using namespace isc::data;
+using namespace asiolink;
+using isc::log::dlog;
+
+namespace isc {
+namespace server_common {
+namespace portconfig {
+
+AddressList
+parseAddresses(isc::data::ConstElementPtr addresses,
+ const std::string& elemName)
+{
+ AddressList result;
+ if (addresses) {
+ if (addresses->getType() == Element::list) {
+ for (size_t i(0); i < addresses->size(); ++ i) {
+ ConstElementPtr addrPair(addresses->get(i));
+ ConstElementPtr addr(addrPair->get("address"));
+ ConstElementPtr port(addrPair->get("port"));
+ if (!addr || ! port) {
+ isc_throw(BadValue, "Address must contain both the IP"
+ "address and port");
+ }
+ try {
+ IOAddress(addr->stringValue());
+ if (port->intValue() < 0 ||
+ port->intValue() > 0xffff) {
+ isc_throw(BadValue, "Bad port value (" <<
+ port->intValue() << ")");
+ }
+ result.push_back(AddressPair(addr->stringValue(),
+ port->intValue()));
+ }
+ catch (const TypeError &e) { // Better error message
+ isc_throw(TypeError,
+ "Address must be a string and port an integer");
+ }
+ }
+ } else if (addresses->getType() != Element::null) {
+ isc_throw(TypeError, elemName + " config element must be a list");
+ }
+ }
+ return (result);
+}
+
+namespace {
+
+void
+setAddresses(DNSService& service, const AddressList& addresses) {
+ service.clearServers();
+ BOOST_FOREACH(const AddressPair &address, addresses) {
+ service.addServer(address.second, address.first);
+ }
+}
+
+}
+
+void
+installListenAddresses(const AddressList& newAddresses,
+ AddressList& addressStore,
+ asiolink::DNSService& service)
+{
+ try {
+ dlog("Setting listen addresses:");
+ BOOST_FOREACH(const AddressPair& addr, newAddresses) {
+ dlog(" " + addr.first + ":" +
+ boost::lexical_cast<string>(addr.second));
+ }
+ setAddresses(service, newAddresses);
+ addressStore = newAddresses;
+ }
+ catch (const exception& e) {
+ /*
+ * We couldn't set it. So return it back. If that fails as well,
+ * we have a problem.
+ *
+ * If that fails, bad luck, but we are useless anyway, so just die
+ * and let boss start us again.
+ */
+ dlog(string("Unable to set new address: ") + e.what(), true);
+ try {
+ setAddresses(service, addressStore);
+ }
+ catch (const exception& e2) {
+ dlog("Unable to recover from error;", true);
+ dlog(string("Rollback failed with: ") + e2.what(), true);
+ abort();
+ }
+ throw; // Let it fly a little bit further
+ }
+}
+
+}
+}
+}
diff --git a/src/lib/server_common/portconfig.h b/src/lib/server_common/portconfig.h
new file mode 100644
index 0000000..bcb8528
--- /dev/null
+++ b/src/lib/server_common/portconfig.h
@@ -0,0 +1,121 @@
+// 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 ISC_SERVER_COMMON_PORTCONFIG_H
+#define ISC_SERVER_COMMON_PORTCONFIG_H
+
+#include <utility>
+#include <string>
+#include <stdint.h>
+#include <vector>
+
+#include <cc/data.h>
+
+/*
+ * Some forward declarations.
+ */
+namespace asiolink {
+class DNSService;
+}
+
+namespace isc {
+namespace server_common {
+/**
+ * \brief Utilities to configure ports and addresses.
+ *
+ * Here are some utilities to help a server to parse configuration of addresses
+ * and ports and install the configuration.
+ */
+namespace portconfig {
+
+/**
+ * \brief An address-port pair.
+ *
+ * It is just a pair of string for an address and unsigned integer for port
+ * number. Anything more fancy would be an overkill, it is used only to pass
+ * the addresses and ports around as intermediate results.
+ */
+typedef std::pair<std::string, uint16_t> AddressPair;
+
+/// \brief Bunch of address pairs
+typedef std::vector<AddressPair> AddressList;
+
+/**
+ * \brief
+ *
+ * This parses a list of address-port configurations and returns them. The
+ * configuration looks like this:
+ *
+ * \verbatim
+[
+ {
+ "address": "192.0.2.1",
+ "port": 13
+ },
+ {
+ "address": "::",
+ "port": 80
+ }
+]
+ * \endverbatim
+ * \param addresses The configuration element to parse (the list). Empty list,
+ * null element and null pointer all mean empty list of addresses.
+ * \param elemName The name of the element, used to create descriptions for
+ * exceptions.
+ * \return Vector of parsed address-port pairs found in the configuration.
+ * \throw isc::data::TypeError if something in the configuration is of a wrong
+ * type (string passed to a port, element in the list that isn't hash,
+ * etc).
+ * \throw asiolink::IOError if the provided address string can't be parsed.
+ * \throw BadValue for other invalid configurations (missing port or address
+ * element in the hash, port number out of range).
+ * \throw std::bad_alloc when allocation fails.
+ */
+AddressList
+parseAddresses(isc::data::ConstElementPtr addresses,
+ const std::string& elemName);
+
+/**
+ * \brief Changes current listening addresses and ports.
+ *
+ * Removes all sockets we currently listen on and starts listening on the
+ * addresses and ports requested in newAddresses.
+ *
+ * If it fails to set up the new addresses, it attempts to roll back to the
+ * previous addresses (but it still propagates the exception). If the rollback
+ * fails as well, it aborts the application (it assumes if it can't listen
+ * on the new addresses nor on the old ones, the application is useless anyway
+ * and should be restarted by Boss, not to mention that the internal state is
+ * probably broken).
+ *
+ * \param newAddresses are the addresses you want to listen on.
+ * \param addressStore is the place you store your current addresses. It is
+ * used when there's a need for rollback. The newAddresses are copied here
+ * when the change is successful.
+ * \param dnsService is the DNSService object we use now. The requests from
+ * the new sockets are handled using this dnsService (and all current
+ * sockets on the service are closed first).
+ * \throw asiolink::IOError when initialization or closing of socket fails.
+ * \throw std::bad_alloc when allocation fails.
+ */
+void
+installListenAddresses(const AddressList& newAddresses,
+ AddressList& addressStore,
+ asiolink::DNSService& dnsService);
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/server_common/tests/Makefile.am b/src/lib/server_common/tests/Makefile.am
new file mode 100644
index 0000000..55ccc85
--- /dev/null
+++ b/src/lib/server_common/tests/Makefile.am
@@ -0,0 +1,42 @@
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/server_common
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/server_common
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+AM_LDFLAGS =
+if USE_STATIC_LINK
+AM_LDFLAGS += -static
+endif
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_CLANGPP
+# see ../Makefile.am
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += portconfig_unittest.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/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/server_common/tests/portconfig_unittest.cc b/src/lib/server_common/tests/portconfig_unittest.cc
new file mode 100644
index 0000000..fabdfa2
--- /dev/null
+++ b/src/lib/server_common/tests/portconfig_unittest.cc
@@ -0,0 +1,182 @@
+// 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 <server_common/portconfig.h>
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <asiolink/asiolink.h>
+
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc::server_common::portconfig;
+using namespace isc::data;
+using namespace isc;
+using namespace std;
+using namespace asiolink;
+
+namespace {
+
+/// Testcase for parseAddresses call (struct, nobody cares about private here)
+struct ParseAddresses : public ::testing::Test {
+ AddressList result_;
+ void empty(ElementPtr config, const string& name) {
+ SCOPED_TRACE(name);
+ EXPECT_NO_THROW(result_ = parseAddresses(config, "test"));
+ EXPECT_TRUE(result_.empty());
+ }
+ template<class Exception>
+ void invalidTest(const string& json, const string& name) {
+ SCOPED_TRACE(name);
+ ElementPtr config(Element::fromJSON(json));
+ EXPECT_THROW(parseAddresses(config, "test"), Exception) <<
+ "Should throw " << typeid(Exception).name();
+ }
+};
+
+// Parse valid IPv4 address
+TEST_F(ParseAddresses, ipv4) {
+ ElementPtr config(Element::fromJSON("["
+ " {"
+ " \"address\": \"192.0.2.1\","
+ " \"port\": 53"
+ " }"
+ "]"));
+ EXPECT_NO_THROW(result_ = parseAddresses(config, "test"));
+ ASSERT_EQ(1, result_.size());
+ EXPECT_EQ("192.0.2.1", result_[0].first);
+ EXPECT_EQ(53, result_[0].second);
+}
+
+// Parse valid IPv6 address
+TEST_F(ParseAddresses, ipv6) {
+ ElementPtr config(Element::fromJSON("["
+ " {"
+ " \"address\": \"2001:db8::1\","
+ " \"port\": 53"
+ " }"
+ "]"));
+ EXPECT_NO_THROW(result_ = parseAddresses(config, "test"));
+ ASSERT_EQ(1, result_.size());
+ EXPECT_EQ("2001:db8::1", result_[0].first);
+ EXPECT_EQ(53, result_[0].second);
+}
+
+// Parse multiple addresses at once
+// (even the ports are different to see they are not mistaken)
+TEST_F(ParseAddresses, multi) {
+ ElementPtr config(Element::fromJSON("["
+ " {"
+ " \"address\": \"2001:db8::1\","
+ " \"port\": 53"
+ " },"
+ " {"
+ " \"address\": \"192.0.2.1\","
+ " \"port\": 54"
+ " }"
+ "]"));
+ EXPECT_NO_THROW(result_ = parseAddresses(config, "test"));
+ ASSERT_EQ(2, result_.size());
+ EXPECT_EQ("2001:db8::1", result_[0].first);
+ EXPECT_EQ(53, result_[0].second);
+ EXPECT_EQ("192.0.2.1", result_[1].first);
+ EXPECT_EQ(54, result_[1].second);
+}
+
+// Parse various versions of empty list
+TEST_F(ParseAddresses, empty) {
+ empty(Element::fromJSON("[]"), "Empty list");
+ empty(ElementPtr(new NullElement), "Null element");
+ empty(ElementPtr(), "Null pointer");
+}
+
+// Reject invalid configs
+TEST_F(ParseAddresses, invalid) {
+ invalidTest<TypeError>("{}", "Not a list");
+ invalidTest<BadValue>("[{}]", "Empty element");
+ invalidTest<TypeError>("[{"
+ " \"port\": 1.5,"
+ " \"address\": \"192.0.2.1\""
+ "}]", "Float port");
+ invalidTest<BadValue>("[{"
+ " \"port\": -5,"
+ " \"address\": \"192.0.2.1\""
+ "}]", "Negative port");
+ invalidTest<BadValue>("[{"
+ " \"port\": 1000000,"
+ " \"address\": \"192.0.2.1\""
+ "}]", "Port too big");
+ invalidTest<IOError>("[{"
+ " \"port\": 53,"
+ " \"address\": \"bad_address\""
+ "}]", "Bad address");
+}
+
+// Test fixture for installListenAddresses
+struct InstallListenAddresses : public ::testing::Test {
+ InstallListenAddresses() :
+ dnss_(ios_, NULL, NULL, NULL)
+ {
+ valid_.push_back(AddressPair("127.0.0.1", 5288));
+ valid_.push_back(AddressPair("::1", 5288));
+ invalid_.push_back(AddressPair("192.0.2.2", 1));
+ }
+ IOService ios_;
+ DNSService dnss_;
+ AddressList store_;
+ // We should be able to bind to these addresses
+ AddressList valid_;
+ // But this shouldn't work
+ AddressList invalid_;
+ // Check that the store_ addresses are the same as expected
+ void checkAddresses(const AddressList& expected, const string& name) {
+ SCOPED_TRACE(name);
+
+ ASSERT_EQ(expected.size(), store_.size()) <<
+ "Different amount of elements, not checking content";
+ // Run in parallel trough the vectors
+ for (AddressList::const_iterator ei(expected.begin()),
+ si(store_.begin()); ei != expected.end(); ++ei, ++si) {
+ EXPECT_EQ(ei->first, si->first);
+ EXPECT_EQ(ei->second, si->second);
+ }
+ }
+};
+
+// Try switching valid addresses
+TEST_F(InstallListenAddresses, valid) {
+ // First, bind to the valid addresses
+ EXPECT_NO_THROW(installListenAddresses(valid_, store_, dnss_));
+ checkAddresses(valid_, "Valid addresses");
+ // TODO Maybe some test to actually connect to them
+ // Try setting it back to nothing
+ EXPECT_NO_THROW(installListenAddresses(AddressList(), store_, dnss_));
+ checkAddresses(AddressList(), "No addresses");
+ // Try switching back again
+ EXPECT_NO_THROW(installListenAddresses(valid_, store_, dnss_));
+ checkAddresses(valid_, "Valid addresses");
+}
+
+// Try if rollback works
+TEST_F(InstallListenAddresses, rollback) {
+ // Set some addresses
+ EXPECT_NO_THROW(installListenAddresses(valid_, store_, dnss_));
+ checkAddresses(valid_, "Before rollback");
+ // This should not bind them, but should leave the original addresses
+ EXPECT_THROW(installListenAddresses(invalid_, store_, dnss_), IOError);
+ checkAddresses(valid_, "After rollback");
+}
+
+}
diff --git a/src/lib/server_common/tests/run_unittests.cc b/src/lib/server_common/tests/run_unittests.cc
new file mode 100644
index 0000000..7ebc985
--- /dev/null
+++ b/src/lib/server_common/tests/run_unittests.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 <config.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return (RUN_ALL_TESTS());
+}
diff --git a/src/lib/testutils/Makefile.am b/src/lib/testutils/Makefile.am
new file mode 100644
index 0000000..ae5c6da
--- /dev/null
+++ b/src/lib/testutils/Makefile.am
@@ -0,0 +1,17 @@
+SUBDIRS = . testdata
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS=$(B10_CXXFLAGS)
+
+if HAVE_GTEST
+lib_LTLIBRARIES = libtestutils.la
+
+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/README b/src/lib/testutils/README
new file mode 100644
index 0000000..578365d
--- /dev/null
+++ b/src/lib/testutils/README
@@ -0,0 +1,2 @@
+Here is some code used by more than one test. No code is used for bind10
+itself, only for testing.
diff --git a/src/lib/testutils/dnsmessage_test.cc b/src/lib/testutils/dnsmessage_test.cc
new file mode 100644
index 0000000..af354d5
--- /dev/null
+++ b/src/lib/testutils/dnsmessage_test.cc
@@ -0,0 +1,115 @@
+// 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 <dns/message.h>
+#include <dns/opcode.h>
+#include <dns/rdata.h>
+#include <dns/rcode.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+
+#include <gtest/gtest.h>
+
+#include <testutils/dnsmessage_test.h>
+
+using namespace isc::dns;
+
+namespace isc {
+namespace testutils {
+const unsigned int QR_FLAG = 0x1;
+const unsigned int AA_FLAG = 0x2;
+const unsigned int TC_FLAG = 0x4;
+const unsigned int RD_FLAG = 0x8;
+const unsigned int RA_FLAG = 0x10;
+const unsigned int AD_FLAG = 0x20;
+const unsigned int CD_FLAG = 0x40;
+
+void
+headerCheck(const Message& message, const qid_t qid, const Rcode& rcode,
+ const uint16_t opcodeval, const unsigned int flags,
+ const unsigned int qdcount,
+ const unsigned int ancount, const unsigned int nscount,
+ const unsigned int arcount)
+{
+ EXPECT_EQ(qid, message.getQid());
+ EXPECT_EQ(rcode, message.getRcode());
+ EXPECT_EQ(opcodeval, message.getOpcode().getCode());
+ EXPECT_EQ((flags & QR_FLAG) != 0,
+ message.getHeaderFlag(Message::HEADERFLAG_QR));
+ EXPECT_EQ((flags & AA_FLAG) != 0,
+ message.getHeaderFlag(Message::HEADERFLAG_AA));
+ EXPECT_EQ((flags & TC_FLAG) != 0,
+ message.getHeaderFlag(Message::HEADERFLAG_TC));
+ EXPECT_EQ((flags & RA_FLAG) != 0,
+ message.getHeaderFlag(Message::HEADERFLAG_RA));
+ EXPECT_EQ((flags & RD_FLAG) != 0,
+ message.getHeaderFlag(Message::HEADERFLAG_RD));
+ EXPECT_EQ((flags & AD_FLAG) != 0,
+ message.getHeaderFlag(Message::HEADERFLAG_AD));
+ EXPECT_EQ((flags & CD_FLAG) != 0,
+ message.getHeaderFlag(Message::HEADERFLAG_CD));
+
+ EXPECT_EQ(qdcount, message.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(ancount, message.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(nscount, message.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(arcount, message.getRRCount(Message::SECTION_ADDITIONAL));
+}
+
+namespace {
+::testing::AssertionResult
+matchRdata(const char*, const char*,
+ const rdata::Rdata& expected, const rdata::Rdata& actual)
+{
+ if (expected.compare(actual) != 0) {
+ ::testing::Message msg;
+ msg << "Two RDATAs are expected to be equal but not:\n"
+ << " Actual: " << actual.toText() << "\n"
+ << "Expected: " << expected.toText();
+ return (::testing::AssertionFailure(msg));
+ }
+ return (::testing::AssertionSuccess());
+}
+}
+
+void
+rrsetCheck(isc::dns::ConstRRsetPtr expected_rrset,
+ isc::dns::ConstRRsetPtr actual_rrset)
+{
+ EXPECT_EQ(expected_rrset->getName(), actual_rrset->getName());
+ EXPECT_EQ(expected_rrset->getClass(), actual_rrset->getClass());
+ EXPECT_EQ(expected_rrset->getType(), actual_rrset->getType());
+ EXPECT_EQ(expected_rrset->getTTL(), actual_rrset->getTTL());
+
+ isc::dns::RdataIteratorPtr rdata_it = actual_rrset->getRdataIterator();
+ isc::dns::RdataIteratorPtr expected_rdata_it =
+ expected_rrset->getRdataIterator();
+ while (!expected_rdata_it->isLast()) {
+ EXPECT_FALSE(rdata_it->isLast());
+ if (rdata_it->isLast()) {
+ // buggy case, should stop here
+ break;
+ }
+
+ EXPECT_PRED_FORMAT2(matchRdata, expected_rdata_it->getCurrent(),
+ rdata_it->getCurrent());
+
+ expected_rdata_it->next();
+ rdata_it->next();
+ }
+
+ // Make sure we have examined all sets of rrset RDATA
+ EXPECT_TRUE(rdata_it->isLast());
+}
+} // end of namespace testutils
+} // end of namespace isc
diff --git a/src/lib/testutils/dnsmessage_test.h b/src/lib/testutils/dnsmessage_test.h
new file mode 100644
index 0000000..a8b7284
--- /dev/null
+++ b/src/lib/testutils/dnsmessage_test.h
@@ -0,0 +1,304 @@
+// 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 <functional>
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/masterload.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace testutils {
+///
+/// \name Header flags
+///
+/// These are flags to indicate whether the corresponding flag bit of the
+/// DNS header is to be set in the test cases using \c headerCheck().
+/// (The flag values is irrelevant to their wire-format values).
+/// The meaning of the flags should be obvious from the variable names.
+//@{
+extern const unsigned int QR_FLAG;
+extern const unsigned int AA_FLAG;
+extern const unsigned int TC_FLAG;
+extern const unsigned int RD_FLAG;
+extern const unsigned int RA_FLAG;
+extern const unsigned int AD_FLAG;
+extern const unsigned int CD_FLAG;
+//@}
+
+/// Set of unit tests to examine a DNS message header.
+///
+/// This function takes a dns::Message object and performs various tests
+/// to confirm if the header fields of the message have the given specified
+/// value. The \c message parameter is the Message object to be tested,
+/// and the remaining parameters specify the expected values of the fields.
+///
+/// If all fields have the expected values the test will be considered
+/// successful. Otherwise, some of the tests will indicate a failure, which
+/// will make the test case that calls this function fail.
+///
+/// The meaning of the parameters should be obvious, but here are some notes
+/// that may not be so trivial:
+/// - \c opcode is an integer, not an \c dns::Opcode object. This is because
+/// we can easily iterate over all possible OPCODEs in a test.
+/// - \c flags is a bitmask so that we can specify a set of header flags
+/// via a single parameter. For example, when we expect the message has
+/// QR and AA flags are on and others are off, we'd set this parameter to
+/// <code>(QR_FLAG | AA_FLAG)</code>.
+///
+/// \param message The DNS message to be tested.
+/// \param qid The expected QID
+/// \param rcode The expected RCODE
+/// \param opcodeval The code value of the expected OPCODE
+/// \param flags Bit flags specifying header flags that are expected to be set
+/// \param qdcount The expected value of QDCOUNT
+/// \param ancount The expected value of ANCOUNT
+/// \param nscount The expected value of NSCOUNT
+/// \param arcount The expected value of ARCOUNT
+void
+headerCheck(const isc::dns::Message& message, const isc::dns::qid_t qid,
+ const isc::dns::Rcode& rcode,
+ const uint16_t opcodeval, const unsigned int flags,
+ const unsigned int qdcount,
+ const unsigned int ancount, const unsigned int nscount,
+ const unsigned int arcount);
+
+/// Set of unit tests to check equality of two RRsets
+///
+/// This function takes two RRset objects and performs detailed tests to
+/// check if these two are "equal", where equal means:
+/// - The owner name, RR class, RR type and TTL are all equal. Names are
+/// compared in case-insensitive manner.
+/// - The number of RRs (more accurately RDATAs) is the same.
+/// - RDATAs are equal as a sequence. That is, the first RDATA of
+/// \c expected_rrset is equal to the first RDATA of \c actual_rrset,
+/// the second RDATA of \c expected_rrset is equal to the second RDATA
+/// of \c actual_rrset, and so on. Two RDATAs are equal iff they have
+/// the same DNSSEC sorting order as defined in RFC4034.
+///
+/// Some of the tests will fail if any of the above isn't met.
+///
+/// \note In future we may want to allow more flexible matching for RDATAs.
+/// For example, we may want to allow comparison as "sets", i.e., comparing
+/// RDATAs regardless of the ordering; we may also want to support suppressing
+/// duplicate RDATA. For now, it's caller's responsibility to match the
+/// ordering (and any duplicates) between the expected and actual sets.
+/// Even if and when we support the flexible behavior, this "strict mode"
+/// will still be useful.
+///
+/// \param expected_rrset The expected RRset
+/// \param actual_rrset The RRset to be tested
+void rrsetCheck(isc::dns::ConstRRsetPtr expected_rrset,
+ isc::dns::ConstRRsetPtr actual_rrset);
+
+/// The definitions in this name space are not supposed to be used publicly,
+/// but are given here because they are used in templated functions.
+namespace detail {
+// Helper matching class used in rrsetsCheck()
+struct RRsetMatch : public std::unary_function<isc::dns::ConstRRsetPtr, bool> {
+ RRsetMatch(isc::dns::ConstRRsetPtr target) : target_(target) {}
+ bool operator()(isc::dns::ConstRRsetPtr rrset) const {
+ return (rrset->getType() == target_->getType() &&
+ rrset->getClass() == target_->getClass() &&
+ rrset->getName() == target_->getName());
+ }
+ const isc::dns::ConstRRsetPtr target_;
+};
+
+// Helper callback functor for masterLoad() used in rrsetsCheck (stream
+// version)
+class RRsetInserter {
+public:
+ RRsetInserter(std::vector<isc::dns::ConstRRsetPtr>& rrsets) :
+ rrsets_(rrsets)
+ {}
+ void operator()(isc::dns::ConstRRsetPtr rrset) const {
+ rrsets_.push_back(rrset);
+ }
+private:
+ std::vector<isc::dns::ConstRRsetPtr>& rrsets_;
+};
+}
+
+/// Set of unit tests to check if two sets of RRsets are identical.
+///
+/// This templated function takes two sets of sequences, each defined by
+/// two input iterators pointing to \c ConstRRsetPtr (begin and end).
+/// This function compares these two sets of RRsets as "sets", and considers
+/// they are equal when:
+/// - The number of RRsets are the same.
+/// - For any RRset in one set, there is an equivalent RRset in the other set,
+/// and vice versa, where the equivalence of two RRsets is tested using
+/// \c rrsetCheck().
+///
+/// Note that the sets of RRsets are compared as "sets", i.e, they don't have
+/// to be listed in the same order.
+///
+/// The entire tests will pass if the two sets are identical. Otherwise
+/// some of the tests will indicate a failure.
+///
+/// \note
+/// - There is one known restriction: each set of RRsets must not have more
+/// than one RRsets for the same name, RR type and RR class. If this
+/// condition isn't met, some of the tests will fail either against an
+/// explicit duplication check or as a result of counter mismatch.
+/// - This function uses linear searches on the expected and actual sequences,
+/// and won't be scalable for large input. For the purpose of testing it
+/// should be acceptable, but be aware of the size of test data.
+///
+/// \param expected_begin The beginning of the expected set of RRsets
+/// \param expected_end The end of the expected set of RRsets
+/// \param actual_begin The beginning of the set of RRsets to be tested
+/// \param actual_end The end of the set of RRsets to be tested
+template<typename EXPECTED_ITERATOR, typename ACTUAL_ITERATOR>
+void
+rrsetsCheck(EXPECTED_ITERATOR expected_begin, EXPECTED_ITERATOR expected_end,
+ ACTUAL_ITERATOR actual_begin, ACTUAL_ITERATOR actual_end)
+{
+ std::vector<isc::dns::ConstRRsetPtr> checked_rrsets; // for duplicate check
+ unsigned int rrset_matched = 0;
+ ACTUAL_ITERATOR it;
+ for (it = actual_begin; it != actual_end; ++it) {
+ // Make sure there's no duplicate RRset in actual (using a naive
+ // search). Since the actual set is guaranteed to be unique, we can
+ // detect it if the expected data has a duplicate by the match/size
+ // checks at the end of the function.
+ // Note: we cannot use EXPECT_EQ for iterators
+ EXPECT_TRUE(checked_rrsets.end() ==
+ std::find_if(checked_rrsets.begin(), checked_rrsets.end(),
+ detail::RRsetMatch(*it)));
+ checked_rrsets.push_back(*it);
+
+ EXPECTED_ITERATOR found_rrset_it =
+ std::find_if(expected_begin, expected_end,
+ detail::RRsetMatch(*it));
+ if (found_rrset_it != expected_end) {
+ rrsetCheck(*found_rrset_it, *it);
+ ++rrset_matched;
+ }
+ }
+
+ // make sure all expected RRsets are in actual sets
+ EXPECT_EQ(std::distance(expected_begin, expected_end), rrset_matched);
+ // make sure rrsets only contains expected RRsets
+ EXPECT_EQ(std::distance(expected_begin, expected_end),
+ std::distance(actual_begin, actual_end));
+}
+
+/// Set of unit tests to check if two sets of RRsets are identical using
+/// streamed expected data.
+///
+/// This templated function takes a standard input stream that produces
+/// a sequence of textural RRs and compares the entire set of RRsets
+/// with the range of RRsets specified by two input iterators.
+///
+/// This function is actually a convenient wrapper for the other version
+/// of function; it internally builds a standard vector of RRsets
+/// from the input stream and uses iterators of the vector as the expected
+/// input iterators for the backend function.
+/// Expected data in the form of input stream would be useful for testing
+/// as it can be easily hardcoded in test cases using string streams or
+/// given from a data source file.
+///
+/// One common use case of this function is to test whether a particular
+/// section of a DNS message contains an expected set of RRsets.
+/// For example, when \c message is an \c dns::Message object, the following
+/// test code will check if the additional section of \c message contains
+/// the hardcoded two RRsets (2 A RRs and 1 AAAA RR) and only contains these
+/// RRsets:
+/// \code std::stringstream expected;
+/// expected << "foo.example.com. 3600 IN A 192.0.2.1\n"
+/// << "foo.example.com. 3600 IN A 192.0.2.2\n"
+/// << "foo.example.com. 7200 IN AAAA 2001:db8::1\n"
+/// rrsetsCheck(expected, message.beginSection(Message::SECTION_ADDITIONAL),
+/// message.endSection(Message::SECTION_ADDITIONAL));
+/// \endcode
+///
+/// The input stream is parsed using the \c dns::masterLoad() function,
+/// and notes and restrictions of that function apply.
+/// This is also the reason why this function takes \c origin and \c rrclass
+/// parameters. The default values of these parameters should just work
+/// in many cases for usual tests, but due to a validity check on the SOA RR
+/// in \c dns::masterLoad(), if the input stream contains an SOA RR, the
+/// \c origin parameter will have to be set to the owner name of the SOA
+/// explicitly. Likewise, all RRsets must have the same RR class.
+/// (We may have to modify \c dns::masterLoad() so that it can
+/// have an option to be more generous about these points if it turns out
+/// to be too restrictive).
+///
+/// \param expected_stream An input stream object that is to emit expected set
+/// of RRsets
+/// \param actual_begin The beginning of the set of RRsets to be tested
+/// \param actual_end The end of the set of RRsets to be tested
+/// \param origin A domain name that is a super domain of the owner name
+/// of all RRsets contained in the stream.
+/// \param rrclass The RR class of the RRsets contained in the stream.
+template<typename ACTUAL_ITERATOR>
+void
+rrsetsCheck(std::istream& expected_stream,
+ ACTUAL_ITERATOR actual_begin, ACTUAL_ITERATOR actual_end,
+ const isc::dns::Name& origin = isc::dns::Name::ROOT_NAME(),
+ const isc::dns::RRClass& rrclass = isc::dns::RRClass::IN())
+{
+ std::vector<isc::dns::ConstRRsetPtr> expected;
+ isc::dns::masterLoad(expected_stream, origin, rrclass,
+ detail::RRsetInserter(expected));
+ rrsetsCheck(expected.begin(), expected.end(), actual_begin, actual_end);
+}
+
+/// Set of unit tests to check if two sets of RRsets are identical using
+/// expected data as string.
+///
+/// This function is a wrapper for the input stream version:
+/// \c rrsetsCheck(std::istream&, ACTUAL_ITERATOR, ACTUAL_ITERATOR, const isc::dns::Name&, const isc::dns::RRClass&)(),
+/// and takes a string object instead of a stream.
+/// While the stream version is more generic, this version would be more
+/// convenient for tests using hardcoded expected data. Using this version,
+/// the example test case shown for the stream version would look as follows:
+/// \code
+/// rrsetsCheck("foo.example.com. 3600 IN A 192.0.2.1\n"
+/// "foo.example.com. 3600 IN A 192.0.2.2\n"
+/// "foo.example.com. 7200 IN AAAA 2001:db8::1\n",
+/// message.beginSection(Message::SECTION_ADDITIONAL),
+/// message.endSection(Message::SECTION_ADDITIONAL));
+/// \endcode
+///
+/// The semantics of parameters is the same as that of the stream version
+/// except that \c expected is a string of expected sets of RRsets.
+template<typename ACTUAL_ITERATOR>
+void
+rrsetsCheck(const std::string& expected,
+ ACTUAL_ITERATOR actual_begin, ACTUAL_ITERATOR actual_end,
+ const isc::dns::Name& origin = isc::dns::Name::ROOT_NAME(),
+ const isc::dns::RRClass& rrclass = isc::dns::RRClass::IN())
+{
+ std::stringstream expected_stream(expected);
+ rrsetsCheck(expected_stream, actual_begin, actual_end, origin,
+ rrclass);
+}
+
+} // end of namespace testutils
+} // end of namespace isc
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/testutils/mockups.h b/src/lib/testutils/mockups.h
new file mode 100644
index 0000000..4bec83d
--- /dev/null
+++ b/src/lib/testutils/mockups.h
@@ -0,0 +1,151 @@
+// 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 <config.h>
+
+#include <cc/data.h>
+#include <cc/session.h>
+
+#include <xfr/xfrout_client.h>
+
+#include <asiolink/asiolink.h>
+
+// A minimal mock configuration session. Most the methods are
+// stubbed out, except for a very basic group_sendmsg() and
+// group_recvmsg(). hasQueuedMessages() always returns false.
+class MockSession : public isc::cc::AbstractSession {
+public:
+ MockSession() :
+ // by default we return a simple "success" message.
+ msg_(isc::data::Element::fromJSON("{\"result\": [0, \"SUCCESS\"]}")),
+ send_ok_(true), receive_ok_(true)
+ {}
+
+
+ virtual void establish(const char*) {}
+ virtual void disconnect() {}
+
+ virtual int group_sendmsg(isc::data::ConstElementPtr msg, std::string group,
+ std::string, std::string)
+ {
+ if (!send_ok_) {
+ isc_throw(isc::cc::SessionError,
+ "mock session send is disabled for test");
+ }
+
+ sent_msg_ = msg;
+ msg_dest_ = group;
+ return (0);
+ }
+
+ virtual bool group_recvmsg(isc::data::ConstElementPtr&,
+ isc::data::ConstElementPtr& msg, bool, int)
+ {
+ if (!receive_ok_) {
+ isc_throw(isc::cc::SessionError,
+ "mock session receive is disabled for test");
+ }
+
+ msg = msg_;
+ return (true);
+ }
+
+ virtual void subscribe(std::string, std::string) {}
+ virtual void unsubscribe(std::string, std::string) {}
+
+ virtual void startRead(boost::function<void()>) {}
+
+ virtual int reply(isc::data::ConstElementPtr, isc::data::ConstElementPtr) {
+ return (-1);
+ }
+
+ virtual bool hasQueuedMsgs() const {
+ return (false);
+ }
+
+ virtual void setTimeout(size_t) {};
+ virtual size_t getTimeout() const { return 0; };
+
+ // The following methods extent AbstractSession to allow testing:
+ void setMessage(isc::data::ConstElementPtr msg) { msg_ = msg; }
+ void disableSend() { send_ok_ = false; }
+ void disableReceive() { receive_ok_ = false; }
+
+ isc::data::ConstElementPtr getSentMessage() { return (sent_msg_); }
+ std::string getMessageDest() { return (msg_dest_); }
+
+private:
+ isc::data::ConstElementPtr sent_msg_;
+ std::string msg_dest_;
+ isc::data::ConstElementPtr msg_;
+ bool send_ok_;
+ bool receive_ok_;
+};
+
+// A nonoperative DNSServer object to be used in calls to processMessage().
+class MockServer : public asiolink::DNSServer {
+public:
+ MockServer() : done_(false) {}
+ void operator()(asio::error_code, size_t) {}
+ virtual void resume(const bool done) { done_ = done; }
+ virtual bool hasAnswer() { return (done_); }
+ virtual int value() { return (0); }
+private:
+ bool done_;
+};
+
+// Mock Xfrout client
+class MockXfroutClient : public isc::xfr::AbstractXfroutClient {
+public:
+ MockXfroutClient() :
+ is_connected_(false), connect_ok_(true), send_ok_(true),
+ disconnect_ok_(true)
+ {}
+
+ virtual void connect() {
+ if (!connect_ok_) {
+ isc_throw(isc::xfr::XfroutError,
+ "xfrout connection disabled for test");
+ }
+ is_connected_ = true;
+ }
+
+ virtual void disconnect() {
+ if (!disconnect_ok_) {
+ isc_throw(isc::xfr::XfroutError,
+ "closing xfrout connection is disabled for test");
+ }
+ is_connected_ = false;
+ }
+
+ virtual int sendXfroutRequestInfo(int, const void*, uint16_t) {
+ if (!send_ok_) {
+ isc_throw(isc::xfr::XfroutError,
+ "xfrout connection send is disabled for test");
+ }
+ return (0);
+ }
+
+ bool isConnected() const { return (is_connected_); }
+ void disableConnect() { connect_ok_ = false; }
+ void disableDisconnect() { disconnect_ok_ = false; }
+ void enableDisconnect() { disconnect_ok_ = true; }
+ void disableSend() { send_ok_ = false; }
+private:
+ bool is_connected_;
+ bool connect_ok_;
+ bool send_ok_;
+ bool disconnect_ok_;
+};
+
diff --git a/src/lib/testutils/portconfig.h b/src/lib/testutils/portconfig.h
new file mode 100644
index 0000000..8e61ffc
--- /dev/null
+++ b/src/lib/testutils/portconfig.h
@@ -0,0 +1,189 @@
+// 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 TESTUTILS_PORTCONFIG_H
+#define TESTUTILS_PORTCONFIG_H
+
+#include <gtest/gtest.h>
+#include <cc/data.h>
+#include <server_common/portconfig.h>
+
+namespace isc {
+namespace testutils {
+/**
+ * \brief Bits of tests for server port configuration.
+ *
+ * These are bits of tests that can be reused by server classes to check if
+ * configuration of the listening addresses work.
+ *
+ * You can put any of these functions into a TEST_F test and pass the server
+ * to it.
+ *
+ * \todo There's quite a lot of common code in the basic server handling.
+ * We should refactor it, so both Resolver server and Auth server have
+ * a common base class. When this is done, the common parts would be put
+ * there and the tests would be at the base class, not here.
+ */
+namespace portconfig {
+
+/**
+ * \brief Check setting of the listening addresses directly (as a list) works.
+ *
+ * \param server The server to test against.
+ */
+template<class Server>
+void
+listenAddresses(Server& server) {
+ using namespace isc::server_common::portconfig;
+ // Default value should be fully recursive
+ EXPECT_TRUE(server.getListenAddresses().empty());
+
+ // Try putting there some addresses
+ AddressList addresses;
+ addresses.push_back(AddressPair("127.0.0.1", 53210));
+ addresses.push_back(AddressPair("::1", 53210));
+ server.setListenAddresses(addresses);
+ EXPECT_EQ(2, server.getListenAddresses().size());
+ EXPECT_EQ("::1", server.getListenAddresses()[1].first);
+
+ // Is it independent from what we do with the vector later?
+ addresses.clear();
+ EXPECT_EQ(2, server.getListenAddresses().size());
+
+ // Did it return to fully recursive?
+ server.setListenAddresses(addresses);
+ EXPECT_TRUE(server.getListenAddresses().empty());
+}
+
+/**
+ * \brief Check setting of the addresses by config value.
+ *
+ * This passes an listen_on element to the server's updateConfig function.
+ * It tries little bit of switching around. It tries both setting a presumably
+ * valid addresses and then setting something that cant be bound, rolling back
+ * back to original.
+ *
+ * \param server The server object to test against.
+ */
+template<class Server>
+void
+listenAddressConfig(Server& server) {
+ using namespace isc::data;
+ // Try putting there some address
+ ElementPtr config(Element::fromJSON("{"
+ "\"listen_on\": ["
+ " {"
+ " \"address\": \"127.0.0.1\","
+ " \"port\": 53210"
+ " }"
+ "]"
+ "}"));
+ ConstElementPtr result(server.updateConfig(config));
+ EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+ ASSERT_EQ(1, server.getListenAddresses().size());
+ EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first);
+ EXPECT_EQ(53210, server.getListenAddresses()[0].second);
+
+ // As this is example address, the machine should not have it on
+ // any interface
+ config = Element::fromJSON("{"
+ "\"listen_on\": ["
+ " {"
+ " \"address\": \"192.0.2.0\","
+ " \"port\": 53210"
+ " }"
+ "]"
+ "}");
+ result = server.updateConfig(config);
+ EXPECT_FALSE(result->equals(*isc::config::createAnswer()));
+ ASSERT_EQ(1, server.getListenAddresses().size());
+ EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first);
+ EXPECT_EQ(53210, server.getListenAddresses()[0].second);
+
+}
+
+/**
+ * \brief Check that given config is rejected.
+ *
+ * Try if given config is considered invalid by the server and is rejected.
+ * The value is converted from JSON to the data elements and passed to server's
+ * updateConfig method. It should not crash, but return a negative answer.
+ *
+ * It is used internally by invalidListenAddressConfig, but you can use it
+ * to test any other invalid configs.
+ *
+ * \todo It might be better to put it to some other namespace, as this is more
+ * generic. But right now it is used only here, so until something else
+ * needs it, it might as well stay here.
+ * \param server The server to test against.
+ * \param JSON Config to use.
+ * \param name It is used in the output if the test fails.
+ */
+template<class Server>
+void
+configRejected(Server& server, const std::string& JSON,
+ const std::string& name)
+{
+ SCOPED_TRACE(name);
+
+ using namespace isc::data;
+ ElementPtr config(Element::fromJSON(JSON));
+ EXPECT_FALSE(server.updateConfig(config)->
+ equals(*isc::config::createAnswer())) <<
+ "Accepted invalid config " << JSON;
+}
+
+/**
+ * \brief Check some invalid address configs.
+ *
+ * It tries a series of invalid listen_on configs against the server and checks
+ * it is rejected.
+ * \param server The server to check against.
+ */
+template<class Server>
+void
+invalidListenAddressConfig(Server& server) {
+ configRejected(server, "{"
+ "\"listen_on\": \"error\""
+ "}", "Wrong element type");
+ configRejected(server, "{"
+ "\"listen_on\": [{}]"
+ "}", "Empty address element");
+ configRejected(server, "{"
+ "\"listen_on\": [{"
+ " \"port\": 1.5,"
+ " \"address\": \"192.0.2.1\""
+ "}]}", "Float port");
+ configRejected(server, "{"
+ "\"listen_on\": [{"
+ " \"port\": -5,"
+ " \"address\": \"192.0.2.1\""
+ "}]}", "Negative port");
+ configRejected(server, "{"
+ "\"listen_on\": [{"
+ " \"port\": 1000000,"
+ " \"address\": \"192.0.2.1\""
+ "}]}", "Huge port");
+ configRejected(server, "{"
+ "\"listen_on\": [{"
+ " \"port\": 53,"
+ " \"address\": \"bad_address\""
+ "}]}", "Bad address");
+}
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/testutils/srv_test.cc b/src/lib/testutils/srv_test.cc
new file mode 100644
index 0000000..4fec4ca
--- /dev/null
+++ b/src/lib/testutils/srv_test.cc
@@ -0,0 +1,235 @@
+// 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 <netinet/in.h>
+
+#include <dns/message.h>
+#include <dns/rcode.h>
+
+#include <asiolink/asiolink.h>
+
+#include <dns/tests/unittest_util.h>
+
+#include <testutils/dnsmessage_test.h>
+#include <testutils/srv_test.h>
+
+using namespace isc::dns;
+using namespace asiolink;
+
+namespace isc {
+namespace testutils {
+const char* const DEFAULT_REMOTE_ADDRESS = "192.0.2.1";
+
+SrvTestBase::SrvTestBase() : request_message(Message::RENDER),
+ parse_message(new Message(Message::PARSE)),
+ response_message(new Message(Message::RENDER)),
+ default_qid(0x1035),
+ opcode(Opcode(Opcode::QUERY())),
+ qname("www.example.com"),
+ qclass(RRClass::IN()),
+ qtype(RRType::A()), io_sock(NULL),
+ io_message(NULL), endpoint(NULL),
+ request_obuffer(0),
+ request_renderer(request_obuffer),
+ response_obuffer(new OutputBuffer(0))
+{}
+
+SrvTestBase::~SrvTestBase() {
+ delete io_message;
+ delete endpoint;
+}
+
+void
+SrvTestBase::createDataFromFile(const char* const datafile,
+ const int protocol)
+{
+ delete io_message;
+ data.clear();
+
+ delete endpoint;
+
+ endpoint = IOEndpoint::create(protocol,
+ IOAddress(DEFAULT_REMOTE_ADDRESS), 53210);
+ UnitTestUtil::readWireData(datafile, data);
+ io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
+ &IOSocket::getDummyTCPSocket();
+ io_message = new IOMessage(&data[0], data.size(), *io_sock, *endpoint);
+}
+
+void
+SrvTestBase::createRequestPacket(Message& message,
+ const int protocol)
+{
+ message.toWire(request_renderer);
+
+ delete io_message;
+
+ endpoint = IOEndpoint::create(protocol,
+ IOAddress(DEFAULT_REMOTE_ADDRESS), 53210);
+ io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
+ &IOSocket::getDummyTCPSocket();
+ io_message = new IOMessage(request_renderer.getData(),
+ request_renderer.getLength(),
+ *io_sock, *endpoint);
+}
+
+// Unsupported requests. Should result in NOTIMP.
+void
+SrvTestBase::unsupportedRequest() {
+ for (unsigned int i = 0; i < 16; ++i) {
+ // set Opcode to 'i', which iterators over all possible codes except
+ // the standard query and notify
+ if (i == isc::dns::Opcode::QUERY().getCode() ||
+ i == isc::dns::Opcode::NOTIFY().getCode()) {
+ continue;
+ }
+ createDataFromFile("simplequery_fromWire.wire");
+ data[2] = ((i << 3) & 0xff);
+
+ parse_message->clear(isc::dns::Message::PARSE);
+ processMessage();
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, isc::dns::Rcode::NOTIMP(), i,
+ QR_FLAG, 0, 0, 0, 0);
+ }
+}
+
+// Multiple questions. Should result in FORMERR.
+void
+SrvTestBase::multiQuestion() {
+ createDataFromFile("multiquestion_fromWire.wire");
+ processMessage();
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, isc::dns::Rcode::FORMERR(),
+ opcode.getCode(), QR_FLAG, 2, 0, 0, 0);
+
+ isc::dns::QuestionIterator qit = parse_message->beginQuestion();
+ EXPECT_EQ(isc::dns::Name("example.com"), (*qit)->getName());
+ EXPECT_EQ(isc::dns::RRClass::IN(), (*qit)->getClass());
+ EXPECT_EQ(isc::dns::RRType::A(), (*qit)->getType());
+ ++qit;
+ EXPECT_EQ(isc::dns::Name("example.com"), (*qit)->getName());
+ EXPECT_EQ(isc::dns::RRClass::IN(), (*qit)->getClass());
+ EXPECT_EQ(isc::dns::RRType::AAAA(), (*qit)->getType());
+ ++qit;
+ EXPECT_TRUE(qit == parse_message->endQuestion());
+}
+
+// Incoming data doesn't even contain the complete header. Must be silently
+// dropped.
+void
+SrvTestBase::shortMessage() {
+ createDataFromFile("shortmessage_fromWire");
+ processMessage();
+ EXPECT_FALSE(dnsserv.hasAnswer());
+}
+
+// Response messages. Must be silently dropped, whether it's a valid response
+// or malformed or could otherwise cause a protocol error.
+void
+SrvTestBase::response() {
+ // A valid (although unusual) response
+ createDataFromFile("simpleresponse_fromWire.wire");
+ processMessage();
+ EXPECT_FALSE(dnsserv.hasAnswer());
+
+ // A response with a broken question section. must be dropped rather than
+ //returning FORMERR.
+ createDataFromFile("shortresponse_fromWire");
+ processMessage();
+ EXPECT_FALSE(dnsserv.hasAnswer());
+
+ // A response to iquery. must be dropped rather than returning NOTIMP.
+ createDataFromFile("iqueryresponse_fromWire.wire");
+ processMessage();
+ EXPECT_FALSE(dnsserv.hasAnswer());
+}
+
+// Query with a broken question
+void
+SrvTestBase::shortQuestion() {
+ createDataFromFile("shortquestion_fromWire");
+ processMessage();
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ // Since the query's question is broken, the question section of the
+ // response should be empty.
+ headerCheck(*parse_message, default_qid, isc::dns::Rcode::FORMERR(),
+ opcode.getCode(), QR_FLAG, 0, 0, 0, 0);
+}
+
+// Query with a broken answer section
+void
+SrvTestBase::shortAnswer() {
+ createDataFromFile("shortanswer_fromWire.wire");
+ processMessage();
+ EXPECT_TRUE(dnsserv.hasAnswer());
+
+ // This is a bogus query, but question section is valid. So the response
+ // should copy the question section.
+ headerCheck(*parse_message, default_qid, isc::dns::Rcode::FORMERR(),
+ opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+
+ isc::dns::QuestionIterator qit = parse_message->beginQuestion();
+ EXPECT_EQ(isc::dns::Name("example.com"), (*qit)->getName());
+ EXPECT_EQ(isc::dns::RRClass::IN(), (*qit)->getClass());
+ EXPECT_EQ(isc::dns::RRType::A(), (*qit)->getType());
+ ++qit;
+ EXPECT_TRUE(qit == parse_message->endQuestion());
+}
+
+// Query with unsupported version of EDNS.
+void
+SrvTestBase::ednsBadVers() {
+ createDataFromFile("queryBadEDNS_fromWire.wire");
+ processMessage();
+ EXPECT_TRUE(dnsserv.hasAnswer());
+
+ // The response must have an EDNS OPT RR in the additional section,
+ // it will be added automatically at the render time.
+ // Note that the DNSSEC DO bit is cleared even if this bit in the query
+ // is set. This is a limitation of the current implementation.
+ headerCheck(*parse_message, default_qid, isc::dns::Rcode::BADVERS(),
+ opcode.getCode(), QR_FLAG, 1, 0, 0, 1);
+ EXPECT_FALSE(parse_message->getEDNS()); // EDNS isn't added at this point
+
+ isc::dns::InputBuffer ib(response_obuffer->getData(),
+ response_obuffer->getLength());
+ isc::dns::Message parsed(isc::dns::Message::PARSE);
+ parsed.fromWire(ib);
+ EXPECT_EQ(isc::dns::Rcode::BADVERS(), parsed.getRcode());
+ isc::dns::ConstEDNSPtr edns(parsed.getEDNS());
+ ASSERT_TRUE(edns);
+ EXPECT_FALSE(edns->getDNSSECAwareness());
+}
+
+void
+SrvTestBase::axfrOverUDP() {
+ // AXFR over UDP is invalid and should result in FORMERR.
+ UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+ isc::dns::Name("example.com"),
+ isc::dns::RRClass::IN(),
+ isc::dns::RRType::AXFR());
+ createRequestPacket(request_message, IPPROTO_UDP);
+ processMessage();
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, isc::dns::Rcode::FORMERR(),
+ opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+}
+} // end of namespace testutils
+} // end of namespace isc
+
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/testutils/srv_test.h b/src/lib/testutils/srv_test.h
new file mode 100644
index 0000000..7361a76
--- /dev/null
+++ b/src/lib/testutils/srv_test.h
@@ -0,0 +1,112 @@
+// 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 <dns/buffer.h>
+#include <dns/name.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include "mockups.h"
+
+namespace asiolink {
+class IOSocket;
+class IOMessage;
+class IOEndpoint;
+}
+
+namespace isc {
+namespace testutils {
+extern const char* const DEFAULT_REMOTE_ADDRESS;
+
+// These are flags to indicate whether the corresponding flag bit of the
+// DNS header is to be set in the test cases. (The flag values
+// is irrelevant to their wire-format values)
+extern const unsigned int QR_FLAG;
+extern const unsigned int AA_FLAG;
+extern const unsigned int TC_FLAG;
+extern const unsigned int RD_FLAG;
+extern const unsigned int RA_FLAG;
+extern const unsigned int AD_FLAG;
+extern const unsigned int CD_FLAG;
+
+// The base class for Auth and Recurse test case
+class SrvTestBase : public ::testing::Test {
+protected:
+ SrvTestBase();
+ virtual ~SrvTestBase();
+
+ /// Let the server process a DNS message.
+ ///
+ /// The derived class implementation is expected to pass \c io_message,
+ /// \c parse_message, \c response_obuffer, and \c dnsserv to the server
+ /// implementation it is testing.
+ virtual void processMessage() = 0;
+
+ /// The following methods implement server independent test logic using
+ /// the template method pattern. Each test calls \c processMessage()
+ /// to delegate the server-dependent behavior to the actual implementation
+ /// classes.
+ void unsupportedRequest();
+ void multiQuestion();
+ void shortMessage();
+ void response();
+ void shortQuestion();
+ void shortAnswer();
+ void ednsBadVers();
+ void axfrOverUDP();
+
+ /// Create DNS packet data from a file.
+ ///
+ /// It constructs wire-format DNS packet data from \c datafile in the
+ /// form of \c IOMessage in \c io_message.
+ /// The existing content of \c io_message, if any, will be deleted.
+ void createDataFromFile(const char* const datafile,
+ int protocol = IPPROTO_UDP);
+
+ /// Create DNS packet data from a message.
+ ///
+ /// It constructs wire-format DNS packet data from \c message in the
+ /// form of \c IOMessage in \c io_message.
+ /// The existing content of \c io_message, if any, will be deleted.
+ void createRequestPacket(isc::dns::Message& message,
+ const int protocol = IPPROTO_UDP);
+
+ MockSession notify_session;
+ MockServer dnsserv;
+ isc::dns::Message request_message;
+ isc::dns::MessagePtr parse_message;
+ isc::dns::MessagePtr response_message;
+ const isc::dns::qid_t default_qid;
+ const isc::dns::Opcode opcode;
+ const isc::dns::Name qname;
+ const isc::dns::RRClass qclass;
+ const isc::dns::RRType qtype;
+ asiolink::IOSocket* io_sock;
+ asiolink::IOMessage* io_message;
+ const asiolink::IOEndpoint* endpoint;
+ isc::dns::OutputBuffer request_obuffer;
+ isc::dns::MessageRenderer request_renderer;
+ isc::dns::OutputBufferPtr response_obuffer;
+ std::vector<uint8_t> data;
+};
+} // end of namespace testutils
+} // end of namespace isc
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/testutils/testdata/Makefile.am b/src/lib/testutils/testdata/Makefile.am
new file mode 100644
index 0000000..93b9eb9
--- /dev/null
+++ b/src/lib/testutils/testdata/Makefile.am
@@ -0,0 +1,35 @@
+CLEANFILES = *.wire *.copied
+
+BUILT_SOURCES = badExampleQuery_fromWire.wire examplequery_fromWire.wire
+BUILT_SOURCES += iqueryresponse_fromWire.wire multiquestion_fromWire.wire
+BUILT_SOURCES += queryBadEDNS_fromWire.wire shortanswer_fromWire.wire
+BUILT_SOURCES += simplequery_fromWire.wire simpleresponse_fromWire.wire
+BUILT_SOURCES += iquery_fromWire.wire iquery_response_fromWire.wire
+
+# NOTE: keep this in sync with real file listing
+# so is included in tarball
+EXTRA_DIST = badExampleQuery_fromWire.spec
+EXTRA_DIST += examplequery_fromWire.spec
+EXTRA_DIST += iqueryresponse_fromWire.spec
+EXTRA_DIST += multiquestion_fromWire.spec
+EXTRA_DIST += queryBadEDNS_fromWire.spec
+EXTRA_DIST += shortanswer_fromWire.spec
+EXTRA_DIST += shortmessage_fromWire
+EXTRA_DIST += shortquestion_fromWire
+EXTRA_DIST += shortresponse_fromWire
+EXTRA_DIST += simplequery_fromWire.spec
+EXTRA_DIST += simpleresponse_fromWire.spec
+EXTRA_DIST += iquery_fromWire.spec iquery_response_fromWire.spec
+EXTRA_DIST += example.com.zone example.net.zone example.org.zone example.zone
+
+EXTRA_DIST += example.com
+EXTRA_DIST += example.sqlite3
+
+EXTRA_DIST += test1.zone.in
+EXTRA_DIST += test1-new.zone.in
+EXTRA_DIST += test1-broken.zone.in
+EXTRA_DIST += test2.zone.in
+EXTRA_DIST += test2-new.zone.in
+
+.spec.wire:
+ $(abs_top_builddir)/src/lib/dns/tests/testdata/gen-wiredata.py -o $@ $<
diff --git a/src/lib/testutils/testdata/badExampleQuery_fromWire.spec b/src/lib/testutils/testdata/badExampleQuery_fromWire.spec
new file mode 100644
index 0000000..8e69890
--- /dev/null
+++ b/src/lib/testutils/testdata/badExampleQuery_fromWire.spec
@@ -0,0 +1,10 @@
+#
+# A simple QUERY message for the example.com zone that would hit a broken
+# record of the data source.
+#
+
+[header]
+# use default
+[question]
+name: broken.example.com
+rrtype: AAAA
diff --git a/src/lib/testutils/testdata/example.com b/src/lib/testutils/testdata/example.com
new file mode 100644
index 0000000..5e0e079
--- /dev/null
+++ b/src/lib/testutils/testdata/example.com
@@ -0,0 +1,8 @@
+$TTL 3600
+@ SOA ns.example.com. admin.example.com. 1234 3600 1800 2419200 7200
+ NS ns.example.com.
+ns.example.com. A 192.0.2.1
+
+;; bogus RDATA for CNAME RR, but the loadzone tool accepts it. looking up this
+;; record will trigger an exception.
+broken.example.com. CNAME 0123456789012345678901234567890123456789012345678901234567890123456789.example.com.
diff --git a/src/lib/testutils/testdata/example.com.zone b/src/lib/testutils/testdata/example.com.zone
new file mode 100644
index 0000000..92d1f45
--- /dev/null
+++ b/src/lib/testutils/testdata/example.com.zone
@@ -0,0 +1,3 @@
+example.com. 3600 IN SOA ns.example.com. admin.example.com. 1234 3600 1800 2419200 7200
+example.com. 3600 IN NS ns.example.com.
+ns.example.com. 3600 IN A 192.0.2.1
diff --git a/src/lib/testutils/testdata/example.net.zone b/src/lib/testutils/testdata/example.net.zone
new file mode 100644
index 0000000..e590462
--- /dev/null
+++ b/src/lib/testutils/testdata/example.net.zone
@@ -0,0 +1,3 @@
+example.net. 3600 IN SOA ns.example.net. admin.example.net. 1234 3600 1800 2419200 7200
+example.net. 3600 IN NS ns.example.net.
+ns.example.net. 3600 IN A 192.0.2.1
diff --git a/src/lib/testutils/testdata/example.org.zone b/src/lib/testutils/testdata/example.org.zone
new file mode 100644
index 0000000..ddaa924
--- /dev/null
+++ b/src/lib/testutils/testdata/example.org.zone
@@ -0,0 +1,3 @@
+example.org. 3600 IN SOA ns.example.org. admin.example.org. 1234 3600 1800 2419200 7200
+example.org. 3600 IN NS ns.example.org.
+ns.example.org. 3600 IN A 192.0.2.1
diff --git a/src/lib/testutils/testdata/example.sqlite3 b/src/lib/testutils/testdata/example.sqlite3
new file mode 100644
index 0000000..e8e255b
Binary files /dev/null and b/src/lib/testutils/testdata/example.sqlite3 differ
diff --git a/src/lib/testutils/testdata/example.zone b/src/lib/testutils/testdata/example.zone
new file mode 100644
index 0000000..92d1f45
--- /dev/null
+++ b/src/lib/testutils/testdata/example.zone
@@ -0,0 +1,3 @@
+example.com. 3600 IN SOA ns.example.com. admin.example.com. 1234 3600 1800 2419200 7200
+example.com. 3600 IN NS ns.example.com.
+ns.example.com. 3600 IN A 192.0.2.1
diff --git a/src/lib/testutils/testdata/examplequery_fromWire.spec b/src/lib/testutils/testdata/examplequery_fromWire.spec
new file mode 100644
index 0000000..2f56bab
--- /dev/null
+++ b/src/lib/testutils/testdata/examplequery_fromWire.spec
@@ -0,0 +1,9 @@
+#
+# A simple QUERY message for the example.com zone
+#
+
+[header]
+# use default
+[question]
+# use default
+name: ns.example.com
diff --git a/src/lib/testutils/testdata/iquery_fromWire.spec b/src/lib/testutils/testdata/iquery_fromWire.spec
new file mode 100644
index 0000000..6443d92
--- /dev/null
+++ b/src/lib/testutils/testdata/iquery_fromWire.spec
@@ -0,0 +1,8 @@
+#
+# An IQUERY message
+#
+
+[header]
+opcode: iquery
+[question]
+# use default
diff --git a/src/lib/testutils/testdata/iquery_response_fromWire.spec b/src/lib/testutils/testdata/iquery_response_fromWire.spec
new file mode 100644
index 0000000..54d16d9
--- /dev/null
+++ b/src/lib/testutils/testdata/iquery_response_fromWire.spec
@@ -0,0 +1,9 @@
+#
+# A response to IQUERY message (NOTIMP)
+#
+
+[header]
+qr: response
+opcode: iquery
+rcode: notimp
+qdcount: 0
diff --git a/src/lib/testutils/testdata/iqueryresponse_fromWire.spec b/src/lib/testutils/testdata/iqueryresponse_fromWire.spec
new file mode 100644
index 0000000..4738f9a
--- /dev/null
+++ b/src/lib/testutils/testdata/iqueryresponse_fromWire.spec
@@ -0,0 +1,9 @@
+#
+# A response to an IQUERY request.
+#
+
+[header]
+qr: response
+opcode: iquery
+[question]
+# use default
diff --git a/src/lib/testutils/testdata/multiquestion_fromWire.spec b/src/lib/testutils/testdata/multiquestion_fromWire.spec
new file mode 100644
index 0000000..c631809
--- /dev/null
+++ b/src/lib/testutils/testdata/multiquestion_fromWire.spec
@@ -0,0 +1,12 @@
+#
+# A QUERY message with multiple questions.
+#
+
+[custom]
+sections: header:question/0:question/1
+[header]
+qdcount: 2
+[question/0]
+# use default
+[question/1]
+rrtype: AAAA
diff --git a/src/lib/testutils/testdata/queryBadEDNS_fromWire.spec b/src/lib/testutils/testdata/queryBadEDNS_fromWire.spec
new file mode 100644
index 0000000..4ffb29f
--- /dev/null
+++ b/src/lib/testutils/testdata/queryBadEDNS_fromWire.spec
@@ -0,0 +1,12 @@
+#
+# A QUERY message with unsupported version of EDNS..
+#
+
+[header]
+arcount: 1
+# use default
+[question]
+# use default
+[edns]
+version: 1
+do: 1
diff --git a/src/lib/testutils/testdata/shortanswer_fromWire.spec b/src/lib/testutils/testdata/shortanswer_fromWire.spec
new file mode 100644
index 0000000..396c9fa
--- /dev/null
+++ b/src/lib/testutils/testdata/shortanswer_fromWire.spec
@@ -0,0 +1,10 @@
+#
+# A QUERY message with a broken answer section (ancount > 0 but the section
+# is empty)
+#
+
+[header]
+# use default
+arcount: 1
+[question]
+# use default
diff --git a/src/lib/testutils/testdata/shortmessage_fromWire b/src/lib/testutils/testdata/shortmessage_fromWire
new file mode 100644
index 0000000..71cec91
--- /dev/null
+++ b/src/lib/testutils/testdata/shortmessage_fromWire
@@ -0,0 +1,9 @@
+###
+### DNS message-like data but doesn't contain sufficient length of data.
+###
+
+# Header Section
+# ID=4149 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 0000
+# QDCNT=1, ANCNT=0, NSCNT=0, (ARCNT is missing)
+0001 0000 0000
diff --git a/src/lib/testutils/testdata/shortquestion_fromWire b/src/lib/testutils/testdata/shortquestion_fromWire
new file mode 100644
index 0000000..564bdc5
--- /dev/null
+++ b/src/lib/testutils/testdata/shortquestion_fromWire
@@ -0,0 +1,13 @@
+###
+### A query-like data, but missing QCLASS field in the Question section.
+###
+
+# Header Section
+# ID=4149 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 0000
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=0
+0001 0000 0000 0000
+
+# Question Section
+# QNAME=example.com. QTYPE=A(1) (QCLASS missing)
+076578616d706c6503636f6d00 0001
diff --git a/src/lib/testutils/testdata/shortresponse_fromWire b/src/lib/testutils/testdata/shortresponse_fromWire
new file mode 100644
index 0000000..eb77261
--- /dev/null
+++ b/src/lib/testutils/testdata/shortresponse_fromWire
@@ -0,0 +1,13 @@
+###
+### A response-like data, but missing QCLASS field in the Question section.
+###
+
+# Header Section
+# ID=4149 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 8000
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=0
+0001 0000 0000 0000
+
+# Question Section
+# QNAME=example.com. QTYPE=A(1) (QCLASS is missing)
+076578616d706c6503636f6d00 0001
diff --git a/src/lib/testutils/testdata/simplequery_fromWire.spec b/src/lib/testutils/testdata/simplequery_fromWire.spec
new file mode 100644
index 0000000..d1239cf
--- /dev/null
+++ b/src/lib/testutils/testdata/simplequery_fromWire.spec
@@ -0,0 +1,8 @@
+#
+# A simple QUERY message.
+#
+
+[header]
+# use default
+[question]
+# use default
diff --git a/src/lib/testutils/testdata/simpleresponse_fromWire.spec b/src/lib/testutils/testdata/simpleresponse_fromWire.spec
new file mode 100644
index 0000000..a8662b4
--- /dev/null
+++ b/src/lib/testutils/testdata/simpleresponse_fromWire.spec
@@ -0,0 +1,8 @@
+#
+# A simple response message.
+#
+
+[header]
+qr: response
+[question]
+# use default
diff --git a/src/lib/testutils/testdata/test1-broken.zone.in b/src/lib/testutils/testdata/test1-broken.zone.in
new file mode 100644
index 0000000..547bb17
--- /dev/null
+++ b/src/lib/testutils/testdata/test1-broken.zone.in
@@ -0,0 +1,5 @@
+test1.example. 3600 IN SOA ns.test1.example. admin.test1.example. 1241 3600 1800 2419200 7200
+test1.example. 3600 IN NS ns.test1.example.
+ns.test1.example. 3600 IN A 192.0.2.1
+;; out-of-zone RR
+www.example.com. 3600 IN AAAA 2001:db8::80
diff --git a/src/lib/testutils/testdata/test1-new.zone.in b/src/lib/testutils/testdata/test1-new.zone.in
new file mode 100644
index 0000000..20bb3f4
--- /dev/null
+++ b/src/lib/testutils/testdata/test1-new.zone.in
@@ -0,0 +1,4 @@
+test1.example. 3600 IN SOA ns.test1.example. admin.test1.example. 1238 3600 1800 2419200 7200
+test1.example. 3600 IN NS ns.test1.example.
+ns.test1.example. 3600 IN A 192.0.2.1
+ns.test1.example. 3600 IN AAAA 2001:db8::1
diff --git a/src/lib/testutils/testdata/test1.zone.in b/src/lib/testutils/testdata/test1.zone.in
new file mode 100644
index 0000000..19393b1
--- /dev/null
+++ b/src/lib/testutils/testdata/test1.zone.in
@@ -0,0 +1,3 @@
+test1.example. 3600 IN SOA ns.test1.example. admin.test1.example. 1235 3600 1800 2419200 7200
+test1.example. 3600 IN NS ns.test1.example.
+ns.test1.example. 3600 IN A 192.0.2.1
diff --git a/src/lib/testutils/testdata/test2-new.zone.in b/src/lib/testutils/testdata/test2-new.zone.in
new file mode 100644
index 0000000..9326503
--- /dev/null
+++ b/src/lib/testutils/testdata/test2-new.zone.in
@@ -0,0 +1,4 @@
+test2.example. 3600 IN SOA ns.test2.example. admin.test2.example. 1242 3600 1800 2419200 7200
+test2.example. 3600 IN NS ns.test2.example.
+ns.test2.example. 3600 IN A 192.0.2.2
+ns.test2.example. 3600 IN AAAA 2001:db8::2
diff --git a/src/lib/testutils/testdata/test2.zone.in b/src/lib/testutils/testdata/test2.zone.in
new file mode 100644
index 0000000..d45f410
--- /dev/null
+++ b/src/lib/testutils/testdata/test2.zone.in
@@ -0,0 +1,3 @@
+test2.example. 3600 IN SOA ns.test2.example. admin.test2.example. 1238 3600 1800 2419200 7200
+test2.example. 3600 IN NS ns.test2.example.
+ns.test2.example. 3600 IN A 192.0.2.2
diff --git a/src/lib/xfr/fd_share.cc b/src/lib/xfr/fd_share.cc
index bc7824b..4e1f093 100644
--- a/src/lib/xfr/fd_share.cc
+++ b/src/lib/xfr/fd_share.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <cstring>
#include <cstdlib>
@@ -93,7 +91,7 @@ recv_fd(const int sock) {
if (recvmsg(sock, &msghdr, 0) < 0) {
free(msghdr.msg_control);
- return (-1);
+ return (XFR_FD_RECEIVE_FAIL);
}
const struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
int fd = -1;
diff --git a/src/lib/xfr/fd_share.h b/src/lib/xfr/fd_share.h
index 8eece89..4ee5fd5 100644
--- a/src/lib/xfr/fd_share.h
+++ b/src/lib/xfr/fd_share.h
@@ -12,20 +12,22 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#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',
+// 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);
@@ -35,6 +37,6 @@ int send_fd(const int sock, const int fd);
#endif
-// Local Variables:
+// Local Variables:
// mode: c++
-// End:
+// End:
diff --git a/src/lib/xfr/fdshare_python.cc b/src/lib/xfr/fdshare_python.cc
index b28c12f..82b1b6e 100644
--- a/src/lib/xfr/fdshare_python.cc
+++ b/src/lib/xfr/fdshare_python.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <structmember.h>
@@ -22,8 +20,9 @@
#include <xfr/fd_share.h>
+
static PyObject*
-fdshare_recv_fd(PyObject *self UNUSED_PARAM, PyObject *args) {
+fdshare_recv_fd(PyObject*, PyObject* args) {
int sock, fd;
if (!PyArg_ParseTuple(args, "i", &sock)) {
return (NULL);
@@ -33,7 +32,7 @@ fdshare_recv_fd(PyObject *self UNUSED_PARAM, PyObject *args) {
}
static PyObject*
-fdshare_send_fd(PyObject *self UNUSED_PARAM, PyObject *args) {
+fdshare_send_fd(PyObject*, PyObject* args) {
int sock, fd, result;
if (!PyArg_ParseTuple(args, "ii", &sock, &fd)) {
return (NULL);
@@ -63,11 +62,23 @@ static PyModuleDef bind10_fdshare_python = {
PyMODINIT_FUNC
PyInit_libxfr_python(void) {
- PyObject *mod = PyModule_Create(&bind10_fdshare_python);
+ 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
index 8ecfb58..52848ad 100644
--- a/src/lib/xfr/python_xfr.cc
+++ b/src/lib/xfr/python_xfr.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <boost/python.hpp>
#include <boost/python/class.hpp>
#include <boost/python/module.hpp>
diff --git a/src/lib/xfr/xfrout_client.cc b/src/lib/xfr/xfrout_client.cc
index 4030698..e9e736b 100644
--- a/src/lib/xfr/xfrout_client.cc
+++ b/src/lib/xfr/xfrout_client.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <cstdlib>
#include <cstring>
#include <iostream>
@@ -69,7 +67,7 @@ XfroutClient::disconnect() {
}
}
-int
+int
XfroutClient::sendXfroutRequestInfo(const int tcp_sock,
const void* const msg_data,
const uint16_t msg_len)
@@ -93,12 +91,6 @@ XfroutClient::sendXfroutRequestInfo(const int tcp_sock,
isc_throw(XfroutError,
"failed to send XFR request data to xfrout module");
}
-
- int databuf = 0;
- if (recv(impl_->socket_.native(), &databuf, sizeof(int), 0) != 0) {
- isc_throw(XfroutError,
- "xfr query hasn't been processed properly by xfrout module");
- }
return (0);
}
diff --git a/src/lib/xfr/xfrout_client.h b/src/lib/xfr/xfrout_client.h
index 6539009..bad963c 100644
--- a/src/lib/xfr/xfrout_client.h
+++ b/src/lib/xfr/xfrout_client.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef _XFROUT_CLIENT_H
#define _XFROUT_CLIENT_H
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..d4008c0
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = system
diff --git a/tests/system/Makefile.am b/tests/system/Makefile.am
new file mode 100644
index 0000000..663258b
--- /dev/null
+++ b/tests/system/Makefile.am
@@ -0,0 +1,16 @@
+systest:
+ sh $(srcdir)/runall.sh
+
+distclean-local:
+ sh $(srcdir)/cleanall.sh
+
+# Most of the files under this directory (including test subdirectories)
+# must be listed in EXTRA_DIST.
+EXTRA_DIST = README cleanall.sh ifconfig.sh start.pl stop.pl run.sh runall.sh
+EXTRA_DIST += common/default_user.csv
+EXTRA_DIST += glue/auth.good glue/example.good glue/noglue.good glue/test.good
+EXTRA_DIST += glue/tests.sh glue/clean.sh
+EXTRA_DIST += glue/nsx1/com.db glue/nsx1/net.db glue/nsx1/root-servers.nil.db
+EXTRA_DIST += glue/nsx1/root.db
+EXTRA_DIST += bindctl/tests.sh bindctl/clean.sh bindctl/setup.sh
+EXTRA_DIST += bindctl/nsx1/root.db bindctl/nsx1/example-normalized.db
diff --git a/tests/system/README b/tests/system/README
new file mode 100644
index 0000000..a43d49e
--- /dev/null
+++ b/tests/system/README
@@ -0,0 +1,63 @@
+Copyright (C) 2004, 2010, 2011 Internet Systems Consortium, Inc. ("ISC")
+Copyright (C) 2000, 2001 Internet Software Consortium.
+See COPYRIGHT in the source root or http://isc.org/copyright.html for terms.
+
+This is a simple test environment for running BIND 10 system tests
+involving multiple name servers. It was originally developed for BIND
+9, and has been ported to test BIND 10 implementations. Ideally we
+should share the same framework for both versions, so some part of
+the original setup are kept, even though they are BIND 9 specific and
+not currently used.
+
+Also, these tests generally rely on BIND 9 programs, most commonly its
+dig, and will sometimes be its name server (named). So, the test
+environment assumes that there's a source tree of BIND 9 where its
+programs are built, and that an environment variable "BIND9_TOP" is
+set to point to the top directory of the source tree.
+
+There are multiple test suites, each in a separate subdirectory and
+involving a different DNS setup. They are:
+
+ bindctl/ Some basic management operations using the bindctl tool
+ glue/ Glue handling tests
+(the following tests are planned to be added soon)
+ dnssec/ DNSSEC tests
+ masterfile/ Master file parser
+ xfer/ Zone transfer tests
+
+Typically each test suite sets up 2-5 instances of BIND 10 (or BIND 9
+named) and then performs one or more tests against them. Within the
+test suite subdirectory, each instance has a separate subdirectory
+containing its configuration data. By convention, these
+subdirectories are named "nsx1", "nsx2", etc for BIND 10 ("x" means
+BIND 10), and "ns1", "ns2", etc. for BIND 9.
+
+The tests are completely self-contained and do not require access to
+the real DNS. Generally, one of the test servers (ns[x]1) is set up
+as a root name server and is listed in the hints file of the others.
+
+To enable all servers to run on the same machine, they bind to
+separate virtual IP address on the loopback interface. ns[x]1 runs on
+10.53.0.1, ns[x]2 on 10.53.0.2, etc. Before running any tests, you
+must set up these addresses by running "ifconfig.sh up" as root.
+
+Mac OS X:
+If you wish to make the interfaces survive across reboots
+copy org.isc.bind.system and org.isc.bind.system to
+/Library/LaunchDaemons then run
+"launchctl load /Library/LaunchDaemons/org.isc.bind.system.plist" as
+root.
+
+The servers use port 53210 instead of the usual port 53, so they can be
+run without root privileges once the interfaces have been set up.
+
+The tests can be run individually like this:
+
+ sh run.sh xfer
+ sh run.sh glue
+ etc.
+
+To run all the tests, just type "make systest" either on this directory
+or on the top source directory. Note: currently these tests cannot be
+run when built under a separate build directory. Everything must be
+run within the original source tree.
diff --git a/tests/system/bindctl/clean.sh b/tests/system/bindctl/clean.sh
new file mode 100755
index 0000000..f691512
--- /dev/null
+++ b/tests/system/bindctl/clean.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000, 2001 Internet Software Consortium.
+#
+# 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.
+
+rm -f */b10-config.db
+rm -f dig.out.* bindctl.out.*
+rm -f */msgq_socket */zone.sqlite3
diff --git a/tests/system/bindctl/nsx1/b10-config.db.template.in b/tests/system/bindctl/nsx1/b10-config.db.template.in
new file mode 100644
index 0000000..162329a
--- /dev/null
+++ b/tests/system/bindctl/nsx1/b10-config.db.template.in
@@ -0,0 +1,10 @@
+{"version": 2,
+ "Auth": {
+ "listen_on": [{"address": "10.53.0.1", "port": 53210}],
+ "database_file": "@abs_builddir@/zone.sqlite3",
+ "statistics-interval": 1
+ },
+ "Xfrout": {
+ "log_file": "@abs_builddir@/Xfrout.log"
+ }
+}
diff --git a/tests/system/bindctl/nsx1/example-normalized.db b/tests/system/bindctl/nsx1/example-normalized.db
new file mode 100644
index 0000000..7129522
--- /dev/null
+++ b/tests/system/bindctl/nsx1/example-normalized.db
@@ -0,0 +1,3 @@
+com. 300 IN SOA postmaster.example. ns.example.com. 2000042100 600 600 1200 600
+com. 300 IN NS ns.example.com.
+ns.example.com. 300 IN A 192.0.2.2
diff --git a/tests/system/bindctl/nsx1/root.db b/tests/system/bindctl/nsx1/root.db
new file mode 100644
index 0000000..31293de
--- /dev/null
+++ b/tests/system/bindctl/nsx1/root.db
@@ -0,0 +1,25 @@
+; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+; Copyright (C) 2000, 2001 Internet Software Consortium.
+;
+; 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.
+
+$TTL 300
+. IN SOA postmaster.example. a.root.servers.nil. (
+ 2000042100 ; serial
+ 600 ; refresh
+ 600 ; retry
+ 1200 ; expire
+ 600 ; minimum
+ )
+. NS ns.example.com.
+ns.example.com. A 192.0.2.1
diff --git a/tests/system/bindctl/setup.sh b/tests/system/bindctl/setup.sh
new file mode 100755
index 0000000..55afcc1
--- /dev/null
+++ b/tests/system/bindctl/setup.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# 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.
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+SUBTEST_TOP=${TEST_TOP}/bindctl
+
+cp ${SUBTEST_TOP}/nsx1/b10-config.db.template ${SUBTEST_TOP}/nsx1/b10-config.db
+
+rm -f ${SUBTEST_TOP}/*/zone.sqlite3
+${B10_LOADZONE} -o . -d ${SUBTEST_TOP}/nsx1/zone.sqlite3 \
+ ${SUBTEST_TOP}//nsx1/root.db
diff --git a/tests/system/bindctl/tests.sh b/tests/system/bindctl/tests.sh
new file mode 100755
index 0000000..6923c41
--- /dev/null
+++ b/tests/system/bindctl/tests.sh
@@ -0,0 +1,106 @@
+#!/bin/sh
+#
+# 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.
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+#
+# Do bindctl tests.
+#
+
+status=0
+n=0
+
+echo "I:Checking b10-auth is working by default ($n)"
+$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
+# perform a simple check on the output (digcomp would be too much for this)
+grep 192.0.2.1 dig.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+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
+echo 'Stats show
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
+# the server should have received 1 UDP and 1 TCP queries (TCP query was
+# sent from the server startup script)
+grep "\"auth.queries.tcp\": 1," bindctl.out.$n > /dev/null || status=1
+grep "\"auth.queries.udp\": 1," bindctl.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Stopping b10-auth and checking that ($n)"
+echo 'config set Boss/start_auth false
+config commit
+quit
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
+# dig should exit with a failure code.
+$DIG +tcp +norec @10.53.0.1 -p 53210 ns.example.com. A && status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Restarting b10-auth and checking that ($n)"
+echo 'config set Boss/start_auth true
+config commit
+quit
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
+$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
+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 pause ($n)"
+sleep 2
+echo 'Stats show
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
+# The statistics counters should have been reset while stop/start.
+grep "\"auth.queries.tcp\": 0," bindctl.out.$n > /dev/null || status=1
+grep "\"auth.queries.udp\": 1," bindctl.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Changing the data source from sqlite3 to in-memory ($n)"
+DATASRC_SPEC='[{"type": "memory", "zones": [{"origin": "com","file":'
+DATASRC_SPEC="${DATASRC_SPEC} \"${TEST_TOP}/bindctl/nsx1/example-normalized.db\"}]}]"
+echo "config set Auth/datasources ${DATASRC_SPEC}
+config commit
+quit
+" | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
+$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
+grep 192.0.2.2 dig.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Rechecking BIND 10 statistics after changing the datasource ($n)"
+sleep 2
+echo 'Stats show
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
+# The statistics counters shouldn't be reset due to hot-swapping datasource.
+grep "\"auth.queries.tcp\": 0," bindctl.out.$n > /dev/null || status=1
+grep "\"auth.queries.udp\": 2," bindctl.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:exit status: $status"
+exit $status
diff --git a/tests/system/cleanall.sh b/tests/system/cleanall.sh
new file mode 100755
index 0000000..17c3d4a
--- /dev/null
+++ b/tests/system/cleanall.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000, 2001 Internet Software Consortium.
+#
+# 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.
+
+#
+# Clean up after system tests.
+#
+
+find . -type f \( \
+ -name 'K*' -o -name '*~' -o -name '*.core' -o -name '*.log' \
+ -o -name '*.pid' -o -name '*.keyset' -o -name named.run \
+ -o -name bind10.run -o -name lwresd.run -o -name ans.run \) -print | \
+ xargs rm -f
+
+status=0
+
+for d in `find . -type d -maxdepth 1 -mindepth 1 -print`
+do
+ test ! -f $d/clean.sh || ( cd $d && sh clean.sh )
+done
diff --git a/tests/system/common/default_user.csv b/tests/system/common/default_user.csv
new file mode 100644
index 0000000..e13e194
--- /dev/null
+++ b/tests/system/common/default_user.csv
@@ -0,0 +1 @@
+root,bind10
diff --git a/tests/system/conf.sh.in b/tests/system/conf.sh.in
new file mode 100755
index 0000000..66aa3f5
--- /dev/null
+++ b/tests/system/conf.sh.in
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+# Copyright (C) 2004-2011 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000-2003 Internet Software Consortium.
+#
+# 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.
+
+#
+# Common configuration data for system tests, to be sourced into
+# other shell scripts.
+#
+
+# Prerequisite check
+if [ @srcdir@ != @builddir@ ]; then
+ echo "Currently systest doesn't work for a separate build tree."
+ echo "Rebuild BIND 10 on the source tree and run the tests."
+ exit 1
+fi
+
+if [ -z $BIND9_TOP ]; then
+ echo "systest assumes there's a compiled tree of BIND 9 which can be"
+ echo "accessed via the BIND9_TOP environment variable."
+ echo "Please make sure this assumption is met."
+ exit 1
+fi
+
+# Find the top of the source and test trees.
+TOP=@abs_top_srcdir@
+TEST_TOP=@abs_builddir@
+
+RUN_BIND10=$TOP/src/bin/bind10/run_bind10.sh
+RUN_BINDCTL=$TOP/src/bin/bindctl/run_bindctl.sh
+BINDCTL_CSV_DIR=@abs_srcdir@/common/
+B10_LOADZONE=$TOP/src/bin/loadzone/run_loadzone.sh
+BIND9_NAMED=$BIND9_TOP/bin/named/named
+DIG=$BIND9_TOP/bin/dig/dig
+# Test tools borrowed from BIND 9's system test (without change).
+TESTSOCK=$BIND9_TOP/bin/tests/system/testsock.pl
+DIGCOMP=$BIND9_TOP/bin/tests/system/digcomp.pl
+
+SUBDIRS="bindctl glue"
+#SUBDIRS="dnssec masterfile xfer"
+
+# PERL will be an empty string if no perl interpreter was found.
+PERL=@PERL@
+
+export RUN_BIND10 BIND9_NAMED DIG SUBDIRS PERL TESTSOCK
diff --git a/tests/system/glue/auth.good b/tests/system/glue/auth.good
new file mode 100644
index 0000000..2c619f6
--- /dev/null
+++ b/tests/system/glue/auth.good
@@ -0,0 +1,15 @@
+
+; <<>> DiG 9.0 <<>> +norec @10.53.0.1 -p 5300 foo.bar.example.org. a
+;; global options: printcmd
+;; Got answer:
+;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41239
+;; flags: qr ad; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
+
+;; QUESTION SECTION:
+;foo.bar.example.org. IN A
+
+;; AUTHORITY SECTION:
+example.org. 172800 IN NS b.root-servers.nil.
+
+;; ADDITIONAL SECTION:
+b.root-servers.nil. 300 IN A 10.53.0.2
diff --git a/tests/system/glue/clean.sh b/tests/system/glue/clean.sh
new file mode 100755
index 0000000..b2c1e02
--- /dev/null
+++ b/tests/system/glue/clean.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+#
+# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000, 2001 Internet Software Consortium.
+#
+# 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.
+
+#
+# Clean up after glue tests.
+#
+
+rm -f dig.out.*
+rm -f */msgq_socket */zone.sqlite3
diff --git a/tests/system/glue/example.good b/tests/system/glue/example.good
new file mode 100644
index 0000000..3b7bbb8
--- /dev/null
+++ b/tests/system/glue/example.good
@@ -0,0 +1,19 @@
+
+; <<>> DiG 9.0 <<>> +norec @10.53.0.1 -p 5300 foo.bar.example. A
+;; global options: printcmd
+;; Got answer:
+;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58772
+;; flags: qr ad; QUERY: 1, ANSWER: 0, AUTHORITY: 6, ADDITIONAL: 7
+
+;; QUESTION SECTION:
+;foo.bar.example. IN A
+
+;; AUTHORITY SECTION:
+example. 172800 IN NS NS1.example.COM.
+example. 172800 IN NS NS.example.
+
+;; ADDITIONAL SECTION:
+NS.example. 172800 IN A 192.0.2.1
+NS.example. 172800 IN A 192.0.2.2
+NS1.example.COM. 172800 IN A 192.0.2.101
+NS1.example.COM. 172800 IN AAAA 2001:db8::1
diff --git a/tests/system/glue/noglue.good b/tests/system/glue/noglue.good
new file mode 100644
index 0000000..57a2211
--- /dev/null
+++ b/tests/system/glue/noglue.good
@@ -0,0 +1,14 @@
+
+; <<>> DiG 9.0 <<>> @10.53.0.1 -p 5300 example.net a
+;; global options: printcmd
+;; Got answer:
+;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29409
+;; flags: qr rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 0
+
+;; QUESTION SECTION:
+;example.net. IN A
+
+;; AUTHORITY SECTION:
+example.net. 300 IN NS ns2.example.info.
+example.net. 300 IN NS ns1.example.info.
+
diff --git a/tests/system/glue/nsx1/b10-config.db.in b/tests/system/glue/nsx1/b10-config.db.in
new file mode 100644
index 0000000..acd040c
--- /dev/null
+++ b/tests/system/glue/nsx1/b10-config.db.in
@@ -0,0 +1,9 @@
+{"version": 2,
+ "Auth": {
+ "listen_on": [{"address": "10.53.0.1", "port": 53210}],
+ "database_file": "@abs_builddir@/zone.sqlite3"
+ },
+ "Xfrout": {
+ "log_file": "@abs_builddir@/Xfrout.log"
+ }
+}
diff --git a/tests/system/glue/nsx1/com.db b/tests/system/glue/nsx1/com.db
new file mode 100644
index 0000000..c4b94e1
--- /dev/null
+++ b/tests/system/glue/nsx1/com.db
@@ -0,0 +1,31 @@
+; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+; Copyright (C) 2000, 2001 Internet Software Consortium.
+;
+; 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.
+
+$ORIGIN com.
+$TTL 300
+@ IN SOA root.example.com. a.root.servers.nil. (
+ 2000042100 ; serial
+ 600 ; refresh
+ 600 ; retry
+ 1200 ; expire
+ 600 ; minimum
+ )
+@ NS a.root-servers.nil.
+
+example.com. NS ns1.example.com.
+example.com. NS ns2.example.com.
+ns1.example.com. 172800 IN A 192.0.2.101
+ns1.example.com. 172800 IN AAAA 2001:db8::1
+ns2.example.com. 172800 IN A 192.0.2.102
diff --git a/tests/system/glue/nsx1/net.db b/tests/system/glue/nsx1/net.db
new file mode 100644
index 0000000..8b66521
--- /dev/null
+++ b/tests/system/glue/nsx1/net.db
@@ -0,0 +1,32 @@
+; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+; Copyright (C) 2000, 2001 Internet Software Consortium.
+;
+; 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.
+
+$ORIGIN net.
+$TTL 300
+@ IN SOA root.example.net. a.root.servers.nil. (
+ 2000042100 ; serial
+ 600 ; refresh
+ 600 ; retry
+ 1200 ; expire
+ 600 ; minimum
+ )
+@ NS a.root-servers.nil.
+
+; Referral outside of server authority, but with glue records present.
+; Don't hand out the glue.
+example.net. NS ns1.example.info.
+example.net. NS ns2.example.info.
+ns1.example.info. 172800 IN A 192.0.2.101
+ns2.example.info. 172800 IN A 192.0.2.102
diff --git a/tests/system/glue/nsx1/root-servers.nil.db b/tests/system/glue/nsx1/root-servers.nil.db
new file mode 100644
index 0000000..45050a9
--- /dev/null
+++ b/tests/system/glue/nsx1/root-servers.nil.db
@@ -0,0 +1,26 @@
+; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+; Copyright (C) 2000, 2001 Internet Software Consortium.
+;
+; 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.
+
+$TTL 300
+@ IN SOA ns hostmaster (
+ 1
+ 3600
+ 1800
+ 1814400
+ 3600
+ )
+ NS a
+a A 10.53.0.1
+b A 10.53.0.2
diff --git a/tests/system/glue/nsx1/root.db b/tests/system/glue/nsx1/root.db
new file mode 100644
index 0000000..e43f2d2
--- /dev/null
+++ b/tests/system/glue/nsx1/root.db
@@ -0,0 +1,55 @@
+; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+; Copyright (C) 2000, 2001 Internet Software Consortium.
+;
+; 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.
+
+$TTL 300
+. IN SOA postmaster.example. a.root.servers.nil. (
+ 2000042100 ; serial
+ 600 ; refresh
+ 600 ; retry
+ 1200 ; expire
+ 600 ; minimum
+ )
+. NS a.root-servers.nil.
+
+root-servers.nil. NS a.root-servers.nil.
+a.root-servers.nil. A 10.53.0.1
+
+; Delegate some domains that contain name servers for the sample
+; ccTLDs below.
+com. 172800 IN NS a.root-servers.nil.
+
+;
+; A sample TLD
+;
+example. 172800 IN NS NS.example.
+example. 172800 IN NS NS1.example.COM.
+NS.example. 172800 IN A 192.0.2.1
+NS.example. 172800 IN A 192.0.2.2
+; this "glue" is below a zone cut for com. BIND 9 still uses it for
+; the delegation to example. BIND 10 (with sqlite3 data source) doesn't.
+NS1.example.COM. 172800 IN A 192.0.2.3
+
+;
+;
+;
+test. 172800 IN NS ns.test.
+test. 172800 IN NS ns1.example.net.
+ns.test. 172800 IN A 192.0.2.200
+ns1.example.net. 172800 IN A 192.0.2.201
+
+;
+; A hypothetical ccTLD where we are authoritative for the NS glue.
+;
+example.org 172800 IN NS b.root-servers.nil.
diff --git a/tests/system/glue/setup.sh.in b/tests/system/glue/setup.sh.in
new file mode 100755
index 0000000..dc5b28a
--- /dev/null
+++ b/tests/system/glue/setup.sh.in
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# 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.
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+rm -f */zone.sqlite3
+${B10_LOADZONE} -o . -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/root.db
+${B10_LOADZONE} -o root-servers.nil -d @builddir@/nsx1/zone.sqlite3 \
+ @builddir@/nsx1/root-servers.nil.db
+${B10_LOADZONE} -o com -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/com.db
+${B10_LOADZONE} -o net -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/net.db
diff --git a/tests/system/glue/test.good b/tests/system/glue/test.good
new file mode 100644
index 0000000..b9b4719
--- /dev/null
+++ b/tests/system/glue/test.good
@@ -0,0 +1,19 @@
+
+; <<>> DiG 9.8.0 <<>> @127.0.0.1 -p 5300 foo.bar.test
+; (1 server found)
+;; global options: +cmd
+;; Got answer:
+;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 55069
+;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 2
+;; WARNING: recursion requested but not available
+
+;; QUESTION SECTION:
+;foo.bar.test. IN A
+
+;; AUTHORITY SECTION:
+test. 172800 IN NS ns.test.
+test. 172800 IN NS ns1.example.net.
+
+;; ADDITIONAL SECTION:
+ns.test. 172800 IN A 192.0.2.200
+ns1.example.net. 172800 IN A 192.0.2.201
diff --git a/tests/system/glue/tests.sh b/tests/system/glue/tests.sh
new file mode 100755
index 0000000..50b2330
--- /dev/null
+++ b/tests/system/glue/tests.sh
@@ -0,0 +1,66 @@
+#!/bin/sh
+#
+# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000, 2001, 2003 Internet Software Consortium.
+#
+# 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.
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+#
+# Do glue tests.
+#
+
+status=0
+n=0
+
+# This query should result in a delegation with two NS; one in the delegated
+# zone and one in a so called out-of-bailiwick zone for which the auth server
+# has authority, too. For the former, the server should return glue in the
+# parent zone. For the latter, BIND 9 and BIND 10 behave differently; BIND 9
+# uses "glue" in the parent zone (since this is the root zone everything can
+# be considered a valid glue). BIND 10 (using sqlite3 data source) searches
+# the other zone and uses the authoritative data in that zone (which is
+# intentionally different from the glue in the root zone).
+echo "I:testing that a TLD referral gets a full glue set from the root zone ($n)"
+$DIG +norec @10.53.0.1 -p 53210 foo.bar.example. A >dig.out.$n || status=1
+$PERL $DIGCOMP example.good dig.out.$n || status=1
+n=`expr $n + 1`
+
+echo "I:testing that we find glue A RRs we are authoritative for ($n)"
+$DIG +norec @10.53.0.1 -p 53210 foo.bar.example.org. a >dig.out.$n || status=1
+$PERL $DIGCOMP auth.good dig.out.$n || status=1
+n=`expr $n + 1`
+
+# We cannot do this test for BIND 10 because b10-auth doesn't act as a
+# recursive (caching) server (by design)
+# echo "I:testing that we find glue A/AAAA RRs in the cache ($n)"
+# $DIG +norec @10.53.0.1 -p 53210 foo.bar.yy. a >dig.out.$n || status=1
+# $PERL $DIGCOMP yy.good dig.out.$n || status=1
+# n=`expr $n + 1`
+
+echo "I:testing that we don't find out-of-zone glue ($n)"
+$DIG +norec @10.53.0.1 -p 53210 example.net. a > dig.out.$n || status=1
+$PERL $DIGCOMP noglue.good dig.out.$n || status=1
+n=`expr $n + 1`
+
+# This test currently fails (additional section will be empty, which is
+# incorrect). See Trac ticket #646.
+#echo "I:testing that we are finding partial out-of-zone glue ($n)"
+#$DIG +norec @10.53.0.1 -p 53210 foo.bar.test. a >dig.out.$n || status=1
+#$PERL $DIGCOMP test.good dig.out.$n || status=1
+#n=`expr $n + 1`
+
+echo "I:exit status: $status"
+exit $status
diff --git a/tests/system/ifconfig.sh b/tests/system/ifconfig.sh
new file mode 100755
index 0000000..c0c365a
--- /dev/null
+++ b/tests/system/ifconfig.sh
@@ -0,0 +1,226 @@
+#!/bin/sh
+#
+# Copyright (C) 2004, 2007-2010 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000-2003 Internet Software Consortium.
+#
+# 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.
+
+#
+# Set up interface aliases for bind9 system tests.
+#
+# IPv4: 10.53.0.{1..7} RFC 1918
+# IPv6: fd92:7065:b8e:ffff::{1..7} ULA
+#
+
+config_guess=""
+for f in ./config.guess ../../config.guess
+do
+ if test -f $f
+ then
+ config_guess=$f
+ fi
+done
+
+if test "X$config_guess" = "X"
+then
+ cat <<EOF >&2
+$0: must be run from the top level source directory or the
+bin/tests/system directory
+EOF
+ exit 1
+fi
+
+# If running on hp-ux, don't even try to run config.guess.
+# It will try to create a temporary file in the current directory,
+# which fails when running as root with the current directory
+# on a NFS mounted disk.
+
+case `uname -a` in
+ *HP-UX*) sys=hpux ;;
+ *) sys=`sh $config_guess` ;;
+esac
+
+case "$2" in
+[0-9]|[1-9][0-9]|[1-9][0-9][0-9]) base=$2;;
+*) base=""
+esac
+
+case "$3" in
+[0-9]|[1-9][0-9]|[1-9][0-9][0-9]) base6=$2;;
+*) base6=""
+esac
+
+case "$1" in
+
+ start|up)
+ for ns in 1 2 3 4 5 6 7
+ do
+ if test -n "$base"
+ then
+ int=`expr $ns + $base - 1`
+ else
+ int=$ns
+ fi
+ if test -n "$base6"
+ then
+ int6=`expr $ns + $base6 - 1`
+ else
+ int6=$ns
+ fi
+ case "$sys" in
+ *-pc-solaris2.5.1)
+ ifconfig lo0:$int 10.53.0.$ns netmask 0xffffffff up
+ ;;
+ *-sun-solaris2.[6-7])
+ ifconfig lo0:$int 10.53.0.$ns netmask 0xffffffff up
+ ;;
+ *-*-solaris2.[8-9]|*-*-solaris2.1[0-9])
+ /sbin/ifconfig lo0:$int plumb
+ /sbin/ifconfig lo0:$int 10.53.0.$ns up
+ if test -n "$int6"
+ then
+ /sbin/ifconfig lo0:$int6 inet6 plumb
+ /sbin/ifconfig lo0:$int6 \
+ inet6 fd92:7065:b8e:ffff::$ns up
+ fi
+ ;;
+ *-*-linux*)
+ ifconfig lo:$int 10.53.0.$ns up netmask 255.255.255.0
+ ifconfig lo inet6 add fd92:7065:b8e:ffff::$ns/64
+ ;;
+ *-unknown-freebsd*)
+ ifconfig lo0 10.53.0.$ns alias netmask 0xffffffff
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
+ ;;
+ *-unknown-netbsd*)
+ ifconfig lo0 10.53.0.$ns alias netmask 255.255.255.0
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
+ ;;
+ *-unknown-openbsd*)
+ ifconfig lo0 10.53.0.$ns alias netmask 255.255.255.0
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
+ ;;
+ *-*-bsdi[3-5].*)
+ ifconfig lo0 add 10.53.0.$ns netmask 255.255.255.0
+ ;;
+ *-dec-osf[4-5].*)
+ ifconfig lo0 alias 10.53.0.$ns
+ ;;
+ *-sgi-irix6.*)
+ ifconfig lo0 alias 10.53.0.$ns
+ ;;
+ *-*-sysv5uw7*|*-*-sysv*UnixWare*|*-*-sysv*OpenUNIX*)
+ ifconfig lo0 10.53.0.$ns alias netmask 0xffffffff
+ ;;
+ *-ibm-aix4.*|*-ibm-aix5.*)
+ ifconfig lo0 alias 10.53.0.$ns
+ ifconfig lo0 inet6 alias -dad fd92:7065:b8e:ffff::$ns/64
+ ;;
+ hpux)
+ ifconfig lo0:$int 10.53.0.$ns netmask 255.255.255.0 up
+ ifconfig lo0:$int inet6 fd92:7065:b8e:ffff::$ns up
+ ;;
+ *-sco3.2v*)
+ ifconfig lo0 alias 10.53.0.$ns
+ ;;
+ *-darwin*)
+ ifconfig lo0 alias 10.53.0.$ns
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
+ ;;
+ *)
+ echo "Don't know how to set up interface. Giving up."
+ exit 1
+ esac
+ done
+ ;;
+
+ stop|down)
+ for ns in 7 6 5 4 3 2 1
+ do
+ if test -n "$base"
+ then
+ int=`expr $ns + $base - 1`
+ else
+ int=$ns
+ fi
+ case "$sys" in
+ *-pc-solaris2.5.1)
+ ifconfig lo0:$int 0.0.0.0 down
+ ;;
+ *-sun-solaris2.[6-7])
+ ifconfig lo0:$int 10.53.0.$ns down
+ ;;
+ *-*-solaris2.[8-9]|*-*-solaris2.1[0-9])
+ ifconfig lo0:$int 10.53.0.$ns down
+ ifconfig lo0:$int 10.53.0.$ns unplumb
+ if test -n "$int6"
+ then
+ ifconfig lo0:$int6 inet6 down
+ ifconfig lo0:$int6 inet6 unplumb
+ fi
+ ;;
+ *-*-linux*)
+ ifconfig lo:$int 10.53.0.$ns down
+ ifconfig lo inet6 del fd92:7065:b8e:ffff::$ns/64
+ ;;
+ *-unknown-freebsd*)
+ ifconfig lo0 10.53.0.$ns delete
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
+ ;;
+ *-unknown-netbsd*)
+ ifconfig lo0 10.53.0.$ns delete
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
+ ;;
+ *-unknown-openbsd*)
+ ifconfig lo0 10.53.0.$ns delete
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
+ ;;
+ *-*-bsdi[3-5].*)
+ ifconfig lo0 remove 10.53.0.$ns
+ ;;
+ *-dec-osf[4-5].*)
+ ifconfig lo0 -alias 10.53.0.$ns
+ ;;
+ *-sgi-irix6.*)
+ ifconfig lo0 -alias 10.53.0.$ns
+ ;;
+ *-*-sysv5uw7*|*-*-sysv*UnixWare*|*-*-sysv*OpenUNIX*)
+ ifconfig lo0 -alias 10.53.0.$ns
+ ;;
+ *-ibm-aix4.*|*-ibm-aix5.*)
+ ifconfig lo0 delete 10.53.0.$ns
+ ifconfig lo0 delete inet6 fd92:7065:b8e:ffff::$ns/64
+ ;;
+ hpux)
+ ifconfig lo0:$int 0.0.0.0
+ ifconfig lo0:$int inet6 ::
+ ;;
+ *-sco3.2v*)
+ ifconfig lo0 -alias 10.53.0.$ns
+ ;;
+ *darwin*)
+ ifconfig lo0 -alias 10.53.0.$ns
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
+ ;;
+ *)
+ echo "Don't know how to destroy interface. Giving up."
+ exit 1
+ esac
+ done
+
+ ;;
+
+ *)
+ echo "Usage: $0 { up | down } [base]"
+ exit 1
+esac
diff --git a/tests/system/run.sh b/tests/system/run.sh
new file mode 100755
index 0000000..4f852f4
--- /dev/null
+++ b/tests/system/run.sh
@@ -0,0 +1,125 @@
+#!/bin/sh
+#
+# Copyright (C) 2004, 2007, 2010 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000, 2001 Internet Software Consortium.
+#
+# 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.
+
+#
+# Run a system test.
+#
+
+SYSTEMTESTTOP=.
+. $SYSTEMTESTTOP/conf.sh
+
+stopservers=true
+
+case $1 in
+ --keep) stopservers=false; shift ;;
+esac
+
+test $# -gt 0 || { echo "usage: $0 [--keep] test-directory" >&2; exit 1; }
+
+test=$1
+shift
+
+test -d $test || { echo "$0: $test: no such test" >&2; exit 1; }
+
+echo "S:$test:`date`" >&2
+echo "T:$test:1:A" >&2
+echo "A:System test $test" >&2
+
+if [ x$PERL = x ]
+then
+ echo "I:Perl not available. Skipping test." >&2
+ echo "R:UNTESTED" >&2
+ echo "E:$test:`date`" >&2
+ exit 0;
+fi
+
+$PERL $TESTSOCK || {
+ echo "I:Network interface aliases not set up. Skipping test." >&2;
+ echo "R:UNTESTED" >&2;
+ echo "E:$test:`date`" >&2;
+ exit 0;
+}
+
+
+# Check for test-specific prerequisites.
+test ! -f $test/prereq.sh || ( cd $test && sh prereq.sh "$@" )
+result=$?
+
+if [ $result -eq 0 ]; then
+ : prereqs ok
+else
+ echo "I:Prerequisites for $test missing, skipping test." >&2
+ [ $result -eq 255 ] && echo "R:SKIPPED" || echo "R:UNTESTED"
+ echo "E:$test:`date`" >&2
+ exit 0
+fi
+
+# Check for PKCS#11 support
+if
+ test ! -f $test/usepkcs11 || sh cleanpkcs11.sh
+then
+ : pkcs11 ok
+else
+ echo "I:Need PKCS#11 for $test, skipping test." >&2
+ echo "R:PKCS11ONLY" >&2
+ echo "E:$test:`date`" >&2
+ exit 0
+fi
+
+# Set up any dynamically generated test data
+if test -f $test/setup.sh
+then
+ ( cd $test && sh setup.sh "$@" )
+fi
+
+# Start name servers running
+$PERL start.pl $test || exit 1
+
+# Run the tests
+( cd $test ; sh tests.sh )
+
+status=$?
+
+if $stopservers
+then
+ :
+else
+ exit $status
+fi
+
+# Shutdown
+$PERL stop.pl $test
+
+status=`expr $status + $?`
+
+if [ $status != 0 ]; then
+ echo "R:FAIL"
+ # Don't clean up - we need the evidence.
+ find . -name core -exec chmod 0644 '{}' \;
+else
+ echo "R:PASS"
+
+ # Clean up.
+ if test -f $test/clean.sh
+ then
+ ( cd $test && sh clean.sh "$@" )
+ fi
+fi
+
+echo "E:$test:`date`"
+
+exit $status
diff --git a/tests/system/runall.sh b/tests/system/runall.sh
new file mode 100755
index 0000000..5d0fe9b
--- /dev/null
+++ b/tests/system/runall.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# Copyright (C) 2004, 2007, 2010 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000, 2001 Internet Software Consortium.
+#
+# 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.
+
+#
+# Run all the system tests.
+#
+
+SYSTEMTESTTOP=.
+. $SYSTEMTESTTOP/conf.sh
+
+status=0
+
+for d in $SUBDIRS
+do
+ sh run.sh $d || status=1
+done
+
+$PERL $TESTSOCK || {
+ cat <<EOF >&2
+I:
+I:NOTE: Many of the tests were skipped because they require that
+I: the IP addresses 10.53.0.1 through 10.53.0.7 are configured
+I: as alias addresses on the loopback interface. Please run
+I: "tests/system/ifconfig.sh up" as root to configure them
+I: and rerun the tests.
+EOF
+ exit 0;
+}
+
+exit $status
diff --git a/tests/system/start.pl b/tests/system/start.pl
new file mode 100755
index 0000000..56f00c4
--- /dev/null
+++ b/tests/system/start.pl
@@ -0,0 +1,226 @@
+#!/usr/bin/perl -w
+#
+# Copyright (C) 2004-2008, 2010 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2001 Internet Software Consortium.
+#
+# 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.
+
+# Framework for starting test servers.
+# Based on the type of server specified, check for port availability, remove
+# temporary files, start the server, and verify that the server is running.
+# If a server is specified, start it. Otherwise, start all servers for test.
+
+use strict;
+use Cwd 'abs_path';
+use Getopt::Long;
+
+# Option handling
+# --noclean test [server [options]]
+#
+# --noclean - Do not cleanup files in server directory
+# test - name of the test directory
+# server - name of the server directory
+# options - alternate options for the server
+
+my $usage = "usage: $0 [--noclean] test-directory [server-directory [server-options]]";
+my $noclean;
+GetOptions('noclean' => \$noclean);
+my $test = $ARGV[0];
+my $server = $ARGV[1];
+my $options = $ARGV[2];
+
+if (!$test) {
+ print "$usage\n";
+}
+if (!-d $test) {
+ print "No test directory: \"$test\"\n";
+}
+if ($server && !-d "$test/$server") {
+ print "No server directory: \"$test/$server\"\n";
+}
+
+# Global variables
+my $topdir = abs_path("$test/..");
+my $testdir = abs_path("$test");
+my $RUN_BIND10 = $ENV{'RUN_BIND10'};
+my $NAMED = $ENV{'NAMED'};
+my $LWRESD = $ENV{'LWRESD'};
+my $DIG = $ENV{'DIG'};
+my $PERL = $ENV{'PERL'};
+my $TESTSOCK = $ENV{'TESTSOCK'};
+
+# Start the server(s)
+
+if ($server) {
+ if ($server =~ /^ns/) {
+ &check_ports($server);
+ }
+ &start_server($server, $options);
+ if ($server =~ /^ns/) {
+ &verify_server($server);
+ }
+} else {
+ # Determine which servers need to be started for this test.
+ opendir DIR, $testdir;
+ my @files = sort readdir DIR;
+ closedir DIR;
+
+ my @ns = grep /^nsx?[0-9]*$/, @files;
+ my @lwresd = grep /^lwresd[0-9]*$/, @files;
+ my @ans = grep /^ans[0-9]*$/, @files;
+
+ # Start the servers we found.
+ &check_ports();
+ foreach my $s (@ns, @lwresd, @ans) {
+ &start_server($s);
+ }
+ foreach my $s (@ns) {
+ &verify_server($s);
+ }
+}
+
+# Subroutines
+
+sub check_ports {
+ my $server = shift;
+ my $options = "";
+
+ if ($server && $server =~ /(\d+)$/) {
+ $options = "-i $1";
+ }
+
+ my $tries = 0;
+ while (1) {
+ my $return = system("$PERL $TESTSOCK -p 53210 $options");
+ last if ($return == 0);
+ if (++$tries > 4) {
+ print "$0: could not bind to server addresses, still running?\n";
+ print "I:server sockets not available\n";
+ print "R:FAIL\n";
+ system("$PERL $topdir/stop.pl $testdir"); # Is this the correct behavior?
+ exit 1;
+ }
+ print "I:Couldn't bind to socket (yet)\n";
+ sleep 2;
+ }
+}
+
+sub start_server {
+ my $server = shift;
+ my $options = shift;
+
+ my $cleanup_files;
+ my $command;
+ my $pid_file;
+
+ if ($server =~ /^nsx/) {
+ $cleanup_files = "{bind10.run}";
+ $command = "B10_FROM_SOURCE_LOCALSTATEDIR=$testdir/$server/ ";
+ $command .= "$RUN_BIND10 ";
+ if ($options) {
+ $command .= "$options";
+ } else {
+ $command .= "--msgq-socket-file=$testdir/$server/msgq_socket ";
+ $command .= "--pid-file=$testdir/$server/bind10.pid ";
+ $command .= "-v";
+ }
+ $command .= " >bind10.run 2>&1 &";
+ $pid_file = "bind10.pid";
+ } elsif ($server =~ /^ns/) {
+ $cleanup_files = "{*.jnl,*.bk,*.st,named.run}";
+ $command = "$NAMED ";
+ if ($options) {
+ $command .= "$options";
+ } else {
+ $command .= "-m record,size,mctx ";
+ $command .= "-T clienttest ";
+ $command .= "-T nosoa "
+ if (-e "$testdir/$server/named.nosoa");
+ $command .= "-T noaa "
+ if (-e "$testdir/$server/named.noaa");
+ $command .= "-c named.conf -d 99 -g";
+ }
+ $command .= " >named.run 2>&1 &";
+ $pid_file = "named.pid";
+ } elsif ($server =~ /^lwresd/) {
+ $cleanup_files = "{lwresd.run}";
+ $command = "$LWRESD ";
+ if ($options) {
+ $command .= "$options";
+ } else {
+ $command .= "-m record,size,mctx ";
+ $command .= "-T clienttest ";
+ $command .= "-C resolv.conf -d 99 -g ";
+ $command .= "-i lwresd.pid -P 9210 -p 53210";
+ }
+ $command .= " >lwresd.run 2>&1 &";
+ $pid_file = "lwresd.pid";
+ } elsif ($server =~ /^ans/) {
+ $cleanup_files = "{ans.run}";
+ $command = "$PERL ./ans.pl ";
+ if ($options) {
+ $command .= "$options";
+ } else {
+ $command .= "";
+ }
+ $command .= " >ans.run 2>&1 &";
+ $pid_file = "ans.pid";
+ } else {
+ print "I:Unknown server type $server\n";
+ print "R:FAIL\n";
+ system "$PERL $topdir/stop.pl $testdir";
+ exit 1;
+ }
+
+ # print "I:starting server $server\n";
+
+ chdir "$testdir/$server";
+
+ unless ($noclean) {
+ unlink glob $cleanup_files;
+ }
+
+ system "$command";
+
+ my $tries = 0;
+ while (!-f $pid_file) {
+ if (++$tries > 14) {
+ print "I:Couldn't start server $server\n";
+ print "R:FAIL\n";
+ system "$PERL $topdir/stop.pl $testdir";
+ exit 1;
+ }
+ sleep 1;
+ }
+}
+
+sub verify_server {
+ my $server = shift;
+ my $n = $server;
+ $n =~ s/^nsx?//;
+
+ my $tries = 0;
+ while (1) {
+ my $return = system("$DIG +tcp +noadd +nosea +nostat +noquest +nocomm +nocmd -p 53210 version.bind. chaos txt \@10.53.0.$n > dig.out");
+ last if ($return == 0);
+ print `grep ";" dig.out`;
+ if (++$tries >= 30) {
+ print "I:no response from $server\n";
+ print "R:FAIL\n";
+ system("$PERL $topdir/stop.pl $testdir");
+ exit 1;
+ }
+ sleep 2;
+ }
+ unlink "dig.out";
+}
diff --git a/tests/system/stop.pl b/tests/system/stop.pl
new file mode 100755
index 0000000..a803f52
--- /dev/null
+++ b/tests/system/stop.pl
@@ -0,0 +1,188 @@
+#!/usr/bin/perl -w
+#
+# Copyright (C) 2004-2007 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2001 Internet Software Consortium.
+#
+# 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.
+
+# Framework for stopping test servers
+# Based on the type of server specified, signal the server to stop, wait
+# briefly for it to die, and then kill it if it is still alive.
+# If a server is specified, stop it. Otherwise, stop all servers for test.
+
+use strict;
+use Cwd 'abs_path';
+
+# Option handling
+# [--use-rndc] test [server]
+#
+# test - name of the test directory
+# server - name of the server directory
+
+my $usage = "usage: $0 [--use-rndc] test-directory [server-directory]";
+my $use_rndc;
+
+while (@ARGV && $ARGV[0] =~ /^-/) {
+ my $opt = shift @ARGV;
+ if ($opt eq '--use-rndc') {
+ $use_rndc = 1;
+ } else {
+ die "$usage\n";
+ }
+}
+
+my $test = $ARGV[0];
+my $server = $ARGV[1];
+
+my $errors = 0;
+
+die "$usage\n" unless defined($test);
+die "No test directory: \"$test\"\n" unless (-d $test);
+die "No server directory: \"$server\"\n" if (defined($server) && !-d "$test/$server");
+
+# Global variables
+my $testdir = abs_path($test);
+my @servers;
+
+
+# Determine which servers need to be stopped.
+if (defined $server) {
+ @servers = ($server);
+} else {
+ local *DIR;
+ opendir DIR, $testdir or die "$testdir: $!\n";
+ my @files = sort readdir DIR;
+ closedir DIR;
+
+ my @ns = grep /^nsx?[0-9]*$/, @files;
+ my @lwresd = grep /^lwresd[0-9]*$/, @files;
+ my @ans = grep /^ans[0-9]*$/, @files;
+
+ push @servers, @ns, @lwresd, @ans;
+}
+
+
+# Stop the server(s), pass 1: rndc.
+if ($use_rndc) {
+ foreach my $server (grep /^ns/, @servers) {
+ stop_rndc($server);
+ }
+
+ wait_for_servers(30, grep /^ns/, @servers);
+}
+
+
+# Pass 2: SIGTERM
+foreach my $server (@servers) {
+ stop_signal($server, "TERM");
+}
+
+wait_for_servers(60, @servers);
+
+# Pass 3: SIGABRT
+foreach my $server (@servers) {
+ stop_signal($server, "ABRT");
+}
+
+exit($errors ? 1 : 0);
+
+# Subroutines
+
+# Return the full path to a given server's PID file.
+sub server_pid_file {
+ my($server) = @_;
+
+ my $pid_file;
+ if ($server =~ /^nsx/) {
+ $pid_file = "bind10.pid";
+ } elsif ($server =~ /^ns/) {
+ $pid_file = "named.pid";
+ } elsif ($server =~ /^lwresd/) {
+ $pid_file = "lwresd.pid";
+ } elsif ($server =~ /^ans/) {
+ $pid_file = "ans.pid";
+ } else {
+ print "I:Unknown server type $server\n";
+ exit 1;
+ }
+ $pid_file = "$testdir/$server/$pid_file";
+}
+
+# Read a PID.
+sub read_pid {
+ my($pid_file) = @_;
+
+ local *FH;
+ my $result = open FH, "< $pid_file";
+ if (!$result) {
+ print "I:$pid_file: $!\n";
+ unlink $pid_file;
+ return;
+ }
+
+ my $pid = <FH>;
+ chomp($pid);
+ return $pid;
+}
+
+# Stop a named process with rndc.
+sub stop_rndc {
+ my($server) = @_;
+
+ return unless ($server =~ /^ns(\d+)$/);
+ my $ip = "10.53.0.$1";
+
+ # Ugly, but should work.
+ system("$ENV{RNDC} -c $testdir/../common/rndc.conf -s $ip -p 9953 stop | sed 's/^/I:$server /'");
+ return;
+}
+
+# Stop a server by sending a signal to it.
+sub stop_signal {
+ my($server, $sig) = @_;
+
+ my $pid_file = server_pid_file($server);
+ return unless -f $pid_file;
+
+ my $pid = read_pid($pid_file);
+ return unless defined($pid);
+
+ if ($sig eq 'ABRT') {
+ print "I:$server didn't die when sent a SIGTERM\n";
+ $errors++;
+ }
+
+ my $result = kill $sig, $pid;
+ if (!$result) {
+ print "I:$server died before a SIG$sig was sent\n";
+ unlink $pid_file;
+ $errors++;
+ }
+
+ return;
+}
+
+sub wait_for_servers {
+ my($timeout, @servers) = @_;
+
+ my @pid_files = grep { defined($_) }
+ map { server_pid_file($_) } @servers;
+
+ while ($timeout > 0 && @pid_files > 0) {
+ @pid_files = grep { -f $_ } @pid_files;
+ sleep 1 if (@pid_files > 0);
+ $timeout--;
+ }
+
+ return;
+}
diff --git a/tools/README b/tools/README
index a18b38a..ce8ddea 100644
--- a/tools/README
+++ b/tools/README
@@ -1,3 +1,4 @@
The "tools" directory contains scripts for helping the BIND 10
-developers maintain the source tree. These are not intended
-to be built nor installed by the build system.
+developers with various tasks, eg. maintaining the source tree,
+running some tests. These are not intended to be built nor
+installed by the build system.
diff --git a/tools/import_boost.sh b/tools/import_boost.sh
deleted file mode 100755
index 07abe8c..0000000
--- a/tools/import_boost.sh
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/bin/sh
-
-# given a directory, copy all needed parts from boost into the
-# current branch
-
-# only run this to update boost! (i.e. almost never)
-
-# usage example:
-# cd /tmp
-# tar xzvf /location/of/boost/tarball
-# cd /home/user/svn/bind10/trunk
-# tools/import_boost.sh /tmp/boost-version
-# svn commit
-
-# need new boost stuff?
-# TODO: LICENSE_1_0.txt
-# add files to list 'ere
-FILES="
-boost/*.hpp
-boost/algorithm
-boost/asio
-boost/assign/list_inserter.hpp
-boost/assign/std/vector.hpp
-boost/bind
-boost/config
-boost/concept
-boost/detail
-boost/exception
-boost/function
-boost/iterator
-boost/mpl
-boost/preprocessor
-boost/python
-boost/range
-boost/smart_ptr
-boost/type_traits
-boost/utility
-"
-
-TARGET="ext"
-
-if [ $# -ne 1 ]
-then
- echo "Usage: boost_import.sh <boost directory>"
- exit
-fi
-
-if [ ! -d $TARGET/boost ]
-then
- echo "This does not appear to be the main trunk/branch directory"
- exit
-fi
-
-
-DIR=$1
-
-do_cmd()
-{
- echo $@
- $@
-}
-
-
-#echo "cp ${DIR}/boost/shared_ptr.hpp boost/"
-for FILE in ${FILES}
-do
-TGT=`echo ${FILE} | sed 's/[^\/]*$//'`
-cmd="mkdir -p ${TARGET}/${TGT}"
-do_cmd ${cmd}
-cmd="cp -r ${DIR}/${FILE} ${TARGET}/${TGT}"
-do_cmd ${cmd}
-done
-
-
diff --git a/tools/tests_in_valgrind.sh b/tools/tests_in_valgrind.sh
new file mode 100755
index 0000000..14e91ba
--- /dev/null
+++ b/tools/tests_in_valgrind.sh
@@ -0,0 +1,75 @@
+#!/bin/sh
+
+###########################################
+# This script runs all tests in valgrind. Configure and compile bind the way
+# you want it to be tested (you should use --with-gtest, however, or you get
+# no tests). Then run this script from the top build directory.
+#
+# Note that the test isn't what you would call "production quality" (it is
+# expected to be used by the bind10 developers, not end user) and might break,
+# some ways of breaking it are known.
+#
+# There are two variables that modify it's behaviour.
+# * VALGRIND_FLAGS are the flag passed to valgrind. There are some, hopefully
+# reasonable defaults which you can overwrite. Note that the variable is
+# used unmodified inside a sed pattern with # as a modifier, which can
+# easily break it. There was no motivation to fix this.
+# * VALGRIND_FILE is the file to store the output into. Default is valgrind.log
+###########################################
+
+# First, make sure the tests are up to date
+make
+
+if [ $? = 2 ] ; then
+ echo "Did you run configure? Or maybe you're running the script from the tools directory? (you need to run it from the top bind10 build directory)"
+ exit 1
+fi
+
+set -e
+
+# Some configuration
+# TODO Escape for sed, this might break
+LOGFILE="${VALGRIND_FILE:-`pwd`/valgrind.log}"
+FLAGS="${VALGRIND_FLAGS:---leak-check=full --track-fds=yes}"
+FLAGS="$FLAGS --log-file=$LOGFILE.%p"
+
+FOUND_ANY=false
+FAILED=
+
+# Find all the tests (yes, doing it by a name is a nasty hack)
+# Since the while runs in a subprocess, we need to get the assignments out, done by the eval
+eval $(find . -type f -name run_unittests -print | grep -v '\.libs/run_unittests$' | while read testname ; do
+ sed -e 's#exec "#exec valgrind '"$FLAGS"' "#' "$testname" > "$testname.valgrind"
+ chmod +x "$testname.valgrind"
+ echo "$testname" >>"$LOGFILE"
+ echo "===============" >>"$LOGFILE"
+ OLDDIR="`pwd`"
+ cd $(dirname "$testname")
+ ./run_unittests.valgrind >&2 &
+ PID="$!"
+ set +e
+ wait "$PID"
+ CODE="$?"
+ set -e
+ cd "$OLDDIR"
+ if [ "$CODE" != 0 ] ; then
+ echo 'FAILED="$FAILED
+'"$testname"'"'
+ fi
+ NAME="$LOGFILE.$PID"
+ rm "$testname.valgrind"
+ # Remove the ones from death tests
+ grep "==$PID==" "$NAME" >>"$LOGFILE"
+ rm "$NAME"
+ echo 'FOUND_ANY=true'
+done)
+
+if test -n "$FAILED"; then
+ echo "These tests failed:" >&2
+ echo "$FAILED" >&2
+fi
+
+if ! $FOUND_ANY ; then
+ echo "No test was found. It is possible you configured without --with-gtest or you run it from wrong directory" >&2
+ exit 1
+fi
diff --git a/tools/valgrind_test_cleaner.pl b/tools/valgrind_test_cleaner.pl
new file mode 100755
index 0000000..9928e9f
--- /dev/null
+++ b/tools/valgrind_test_cleaner.pl
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# This script can be used on a valgrind output of the tests (from
+# tests_in_valgrind.sh) to remove some uninteresting error reports.
+# Since we care about the tested application not leaking/crashing, not
+# the tests itself, memory leaks that are caused only by the tests
+# (eg. unreleased test data), we don't want to have logs full of them.
+#
+# This script does some heuristics to eliminate some of such error
+# reports. Currently, the memory lost reports whose stack contains
+# no call from the real application are suppressed.
+#
+# Of course, the rest still can contain many uninteresting entries.
+
+# Yes, it's perl even when we use python. I wrote it for myself when
+# I needed to clean the outputs and after it proved useful to me, I
+# thought it might be for others too, so I just included it. It's not
+# that we would be switching to perl. If it should grow in future to
+# include more heuristics and do something more fancy, we should probably
+# rewrite it in python instead.
+
+my ($block, $blockOK);
+
+sub endBlock(_) {
+ return unless $block;
+ if ($blockOK) {
+ print @$block;
+ }
+ undef $block;
+ undef $blockOK;
+}
+
+sub startBlock(_) {
+ $block = [@_];
+}
+
+sub addToBlock(_) {
+ my ($line) = @_;
+ push @$block, $line;
+ return unless $line =~ /^==\d+==\s+(at|by) 0x[0-9A-F]+: (.*) \(.+:\d+\)$/;
+ $_ = $2;
+ return $blockOK = 1 if /^isc::/;
+ return $blockOK = 1 if /^asiolink:/;
+ return if /^main \(/;
+ return if /^testing::/;
+ return if /^\(anonymous namespace\)::/;
+ $blockOK = 1;
+}
+
+while(<>) {
+ if (/^==\d+==\s*$/) {
+ print;
+ endBlock;
+ } elsif (/^==\d+==\s+\d+bytes.*lost in loss record/) {
+ startBlock;
+ } elsif ($block) {
+ addToBlock;
+ } else {
+ print;
+ }
+}
+endBlock;
More information about the bind10-changes
mailing list