BIND 10 trac1540, updated. a40b6c665617125eeb8716b12d92d806f0342396 Merge branch 'master' into trac1540
BIND 10 source code commits
bind10-changes at lists.isc.org
Wed May 9 11:24:11 UTC 2012
The branch, trac1540 has been updated
via a40b6c665617125eeb8716b12d92d806f0342396 (commit)
via b22ff2de4d9421bd1ef3976967aee886c8811611 (commit)
via 409ca483d117688be552d27d1e06616d44701c82 (commit)
via 892f6b266acb98ad005e1c6dad6c0621c04f1d82 (commit)
via 18675a2d6a237c775ac8bb91c26c94c7b4219095 (commit)
via 25f0bb96ddbe5adc6538e68c2d6eb2da6bcc7c0c (commit)
via 6c0da87a7bdbb7be59fbac47b6c5f2ef0043aecb (commit)
via 36d930c2e66f13fadfff64689bee2547ca2ab8cd (commit)
via 0c1c1eb1a972e9f85f99510766290b7ed9fe06f5 (commit)
via b3dd1982b72e5f4710e33266310180c71203ce0d (commit)
via 1c88a49755f6617184c45fe127f00ffec9e1b89d (commit)
via fbe7a931b3f74452a87449ff17792f40b8d8e08e (commit)
via 253701eb2cfbf5830f71c3b00e2254df6b4ecdef (commit)
via 84ce6a7875bd85a0a7c96bff18ba2c07a65120d0 (commit)
via 6567d28ea958b0a89204f04714bbd90f70dd0d78 (commit)
via 4846fa711c1003092a15f61be4ccd0660828a28c (commit)
via 8c1eb198095738cac179dd2252119cfb27f47fdd (commit)
via fcf2f08db9ebc2198236bfa25cf73286821cba6b (commit)
via 7d8efaf11c66fc7dfbb79060ded52f779fd523f8 (commit)
via 0a05819f8a0404f2aaa1a0a1ae06caf40a895863 (commit)
via c805a1176ab52d0326771b432de744037f0a76fb (commit)
via 255456507a465ee2cb1b94e386366f259c8e9487 (commit)
via 491896718f86b7514c661b7964cd92673f7e037f (commit)
via 8b0e391e3a838b2d76d87e87088f025491802c8b (commit)
via 408043d940134c7e7c301e762888f10a4184a400 (commit)
via d7191f1b7ae74dc696c386576c422582800aa5b2 (commit)
via 56083b82568a61b2fc48afd95b7e998568216894 (commit)
via cc0b929d0d82eab6df92a23a0a09d330938d79eb (commit)
via d6d267370155e3feeaa108af1d05931afac1f787 (commit)
via 5fb18596288d2eae586cd01da0b27f329fb4e155 (commit)
via dfaf9712d15392caef04df6c2149744f8abace87 (commit)
via f894ef742226f6c9d438a21be01f6a5d0cbcc5fb (commit)
via 3d876bb262c8eea4b3cdc60f293249068adde2c2 (commit)
via 3a9c1b6387eb19a1749e7e9a4dec753b33da5766 (commit)
via 37227b85bf18cf42bcc08bacedbe86bbe7bf6cfc (commit)
via 1ff3eb19126e4c7a43a6f6b64fef46590f462fb8 (commit)
via 156ace04dc1267c73f5e58006dc35fb0d3788c86 (commit)
via 7b0e7e17467d2a8c31fd8a8b4ab34e16f1ad84bf (commit)
via bd2568ebded2746a7a6741c5b2f927392820506a (commit)
via 5ffee903b4e48f34c363215668d3867c0a7f6c94 (commit)
via 95947109c58b1e099ee5c4e955f739213824e49f (commit)
via 5c8bbd7ab648b6b7c48e366e7510dedca5386f6c (commit)
via 653847cf994c75cacd23df1ae0e5c9002cf338c4 (commit)
via 0f18039bc751a8f498c1f832196e2ecc7b997b2a (commit)
via 4c43c7d967b6b9fe70d8431058808926509755f5 (commit)
via c73ffa69fd26e03b0879a7773f7f6796f19aee2e (commit)
via 551657702a4197ef302c567b5c0eaf2fded3e121 (commit)
via 58757c09f3b72768da9a43f1affa7120ebbdeba8 (commit)
via 3e6773dbdbc31ca20637455b110c2907daf2ce0c (commit)
via 17f72c2e942afec37711c9b05c4a5351f40b4676 (commit)
via c016b74723d24469961682defaee0034bb8b5920 (commit)
via 4429acb90d47320d20e9126457594a1f38191b28 (commit)
via 2f7b1a1865ae1c8b981d5f6396ae612421be764b (commit)
via 9a76caecbc29f35dad73a2f9874e8e3a64e4154f (commit)
via 3de6e881f69c6462cf3da43c662cbc1a35595504 (commit)
via a475ef271d4606f791e5ed88d9b8eb8ed8c90ce6 (commit)
via 3055e37cd7bdb8ada5cdf54e611343665ff93675 (commit)
via f9d8efd545fb5bdb5bbab14eac2111ae4e47f75f (commit)
via e63005a633e4f62a5e359ac26ba1704ff55a734c (commit)
via a3893c3664567e1ce3c724b8d478989405ddaa77 (commit)
via 0ddeda7f0669b481c4d7ceadc9f8d76db41baed7 (commit)
via 597e059afaa4a89e767f8f10d2a4d78223af3940 (commit)
via 84a0413302fa935314dd81a352aeb6d746cbbf3a (commit)
via dc142a44a0a0de3d9278937bdb0d70c545e6da6e (commit)
via 8df566b82de165047fceccf6b4eda1974f3e78e5 (commit)
via 373ef178d799790eb5917f5fbb2b725391088b0d (commit)
via e0cb5ab6d32eae3df6e309ff73c6d331f0750168 (commit)
via fd1a4b0eac2f586c2efc98cb99235ddb1c77da8a (commit)
via b24b0f2312ce99cba4af8456179c712d5cc0fe9d (commit)
via 146b5941b9f3d09537b56eb83df14355c2edde59 (commit)
via 4cfab0d82574c3501502a9ed9b84c39112d411a8 (commit)
via e4fd9934de5258ad1668c4cbba408fe59427c040 (commit)
via bed1681e323f5c278a4271b7c948a44b0605e73b (commit)
via 53a7c709b06e670dacf81907a7360ea030341b1d (commit)
via 50d20d7206fad792c4eb035a9e643f4d47518505 (commit)
via c8e538c10d73ea56f079fcb8f3c6f99e5407bdef (commit)
via b1c727db35e23c3c72262af0f0a54ab8fe4b411e (commit)
via 5b37859360df57221621144d34119fcc7e417099 (commit)
via 6e05014aa5bd412778a0927ac1babff042e5f46f (commit)
via 264691395b76a2a6f18b02dbd43c78f6e9de83ff (commit)
via 438b5b183c82b04d74be71c7cc396d6393d215b6 (commit)
via da4d8df9ee0412d3717f1009ecccac58736313b8 (commit)
via 672f129700dae33b701bb02069cf276238d66be3 (commit)
via a6cc81d79b42baf06058c25f404b8ec61de0c2d6 (commit)
via a9b59f8a7728418038125bfb3ed2335bf5524b3f (commit)
via bcb62dd7e4d8a3bca40e3725aa457074ada36134 (commit)
via 400ff18a8c98cd0c1c393ef32e1d76bb4222dce6 (commit)
via 1c3fe6e537bf9e7bc742f84a36efbbbb49a6365d (commit)
via 64f69a14336846132a5588f6523c3f5aadf63382 (commit)
via 233c7295de7021fe24b8342db5d966d3d9b354e6 (commit)
via 52707b19511b499401a61b42eb3a03207f79b0c6 (commit)
via f782145ec0759ef8e595c87d4baf15de89624ea7 (commit)
via f93cd951e3ead684e4efbc8d2eaa523141e0cf65 (commit)
via e7c91fe725369cabe19bd227e06176a117955461 (commit)
via 14a13b8494a53f2a488859ed27726f32e57781b6 (commit)
via 47f1415387f974e7ccdf7d95d7a380827486bc22 (commit)
via 9a4619a0a1ffdde67a58af51f3826e5c6a8da20b (commit)
via fb7201b9ff0aaa126ff82595b7a4c2f69ea8f4c7 (commit)
via bea9aaf1441dd9da91cc1197d02ea52c0d0095f1 (commit)
via d7cf5f81d983e95e4f8bd85637a0ae9785d94a9d (commit)
via 91cb18e244588b8a99ddf9b12fb99986b0258fdb (commit)
via 7aac25a59bfb9dbdbdbc4c8334204d24f7e0b131 (commit)
via 358beb2e1e15bcbe09438719e756f8641ea929d5 (commit)
via d68d574b69ee627b11982276312dfa70160b072a (commit)
via c3be9fa408b02750d56f63ba593b9a0c92647928 (commit)
via c9b2b1cc3060d991f9fdc89a8d70d4364f2e9d6d (commit)
via 0422339a5a2c934aec17869b8096f263d6cae922 (commit)
via 4deb49cf84c399a0ca1a84fef1cd0dee4ca4d5d5 (commit)
via f00dd21c6c495e180feb855139d36fc7f99f23d8 (commit)
via 95410c80370f458bf2d3908e92a69c2602d071d3 (commit)
via 89071a214099a1de3ce7253e4f91fa4bc8d00272 (commit)
via 36efa7d10ecc4efd39d2ce4dfffa0cbdeffa74b0 (commit)
via a318061e3c20ad5deeebe41884cbe3dcf9a3a413 (commit)
via 4a7381b8b8a03c010372c7a62192bec325a40a8a (commit)
via 211934fe44eb6ea5a74857d4312e10b6cd6a3c55 (commit)
via 136e63b8234f46a46c4278e4ccfe9712a76ebfc9 (commit)
via 149ac337e9a8e43896e84268a462463429061aed (commit)
via 93f11d2a96ce4dba9308889bdb9be6be4a765b27 (commit)
via a7dbb75a88c71513c2ff2d0c86eb98b1f287e6f4 (commit)
via 952cf4c1d92cf00179347abbc15fa37318c5ec90 (commit)
via 277af902ee91a90c1ef8ebce3895ecc27d8dec2d (commit)
via 7f108b1ad180d002ff6bc2d4be26b6a93ead8c99 (commit)
via a93523dfcf7d640b15d9af71596caa09362cbbfd (commit)
via 56bd002f57d397ec4c1fffb40c499425e36b21de (commit)
via 4653281fb863709e2ab9ca26514fe9e31cd8444d (commit)
via 0f9b1d45d37223a5e1f7167a97c9760b52e744e9 (commit)
via d3fd5c7fa44e0f19a97d2dcb99ee937eaa80d704 (commit)
via ae46e78f9e1d4261bbf86a3dcaf7bd813851edf0 (commit)
via 0464833becda85dca7deae50a7f4adbf9589076f (commit)
via 2c8983c96edd2e98f285d35343a5e4958fb1971c (commit)
via c89bd8e38d3fcad506676876cc110c937cc3fbfe (commit)
via 22041b8ed4dd46d27981c3e645973de62c34a25e (commit)
via 66a9862c8ec60bd4c64f1535590ac16fae2fd088 (commit)
via 2fd8732e2b16ad5dffcbf12345e97a05b146c592 (commit)
via 5a3ba49ea9cdd4cde08d1855a0944f6899eaae8b (commit)
via efc43a67d27a14c468d3cf53df87ed3f808fd730 (commit)
via d833b85d17a2958890faa25927c1d1ed3443279c (commit)
via 3e27e1508a29251494a907a6b00258900a5200b2 (commit)
via 412304add5726cd16f91c010471fd895d65c2a6d (commit)
via a2da847353f3fd79feb1a4b902faebd74b31fa64 (commit)
via 8b8dbb612ba7f55ae49d113297a8e119c51f492d (commit)
via 837533a24607e825f2dc72f7fb45665c4e8aec99 (commit)
via c545fba97414013e52eda55f2d61ba23f9e076e9 (commit)
via 2e25759b8ff76ff81d75201bc4c8ef6eb243d144 (commit)
via 0a9dad718d84b7de43478df6804da1da25e5dab9 (commit)
via ff240f0bc7b13b7932054f435b7159f788eaf912 (commit)
via 7544617e812757e1d1f02ebf9d1c44e1fd01a4df (commit)
via c537d66e97d1a10a544baeba5e5490bd061f1590 (commit)
via cf96b2ad155c63f0ffceaf69b634e5f772d22770 (commit)
via 92c00e5601f7816113eab6be8a165cca0fe6956e (commit)
via 8cdf67b6ff82596aa51e56fd5eccbd75763e9011 (commit)
via 69bcd5348b07321645cbd749dc964d96bf654076 (commit)
via a83554ce6da992e5d060ceb12baa6948c6e4bf33 (commit)
via 9cc9ffd9f10433f85a6b056d7face686b5522a13 (commit)
via c3b46d0b46f88f759c7a4738040e6edaf6845d5b (commit)
via 034bc4e02eb482913c2e2c0892d5cf10e5525aae (commit)
via b303b434d956c7b5842fc0ca5c43f4aa69351a34 (commit)
via b7f87132783143fab9f8f6ecc0977fe220f2e58e (commit)
via 22aa6eec949ced4a40a599019a275c7e732cbb70 (commit)
via b23c36ef2e61cb4133f8c2708aa8cf85e4809d61 (commit)
via a17f42a08fa2d0fbd2de357d90dbbfeaaba32335 (commit)
via 0d2ebae0cc3b6119ae0cc2f7fa509a5792290ecf (commit)
via 325b81328b624d467c674a7bf74e73a271892d4a (commit)
via be50e95723a83adcb63e9622eaada199f3f17463 (commit)
via 31de885ba0409f54d9a1615eff5a4b03ed420393 (commit)
via 29264296474f103f8ece3e299171f835b81d9e9a (commit)
via 65e64487c28d3688d65ed625cdb1531ce5691095 (commit)
via 4108ca766cb9bce434bfbb786436b7c08adb87a4 (commit)
via de9ccf5df44c42526b2cd8e7f078d6073729c35a (commit)
via d78d0dbcec53a339b7789b181954768d181c2a1b (commit)
via be9562a0d38bdb2ba5d3e8456dd13a0cbc19b48e (commit)
via 5149a8149791f6f828704a2aa63178ca98707ebc (commit)
via 78c6b5331109e94c7c5217c500162106b587ae59 (commit)
via ffbfe8fb2978b60c8309b3d7b7d006c98778b304 (commit)
via fa7b9c53b18d220062ed252abbc241e8871e8927 (commit)
via 61fa7a41089bf18f39ae6415fa92c6c2bca6a026 (commit)
via 0bc5c68ef6c5811eb93227e1d8d44f7762a3f7b2 (commit)
via 7efc7ddb385ef56c5df5a23474a10900bbe36cf9 (commit)
via fcdc7a7b7fbf23338d1c7165e6f3a8ffe4a20459 (commit)
via e0bcbb981c3b22f3380fcee592ae7cc2dadd0c00 (commit)
via 57512ac60b298b3873b9ee74d46f4f56ab746494 (commit)
via 00a36e752802df3cc683023d256687bf222e256a (commit)
via 3057455dd008c612f64fd265a957bdf9c0b58679 (commit)
via 65193e8384b25f0092177adb91a8ee6c2679d2db (commit)
via 5dcf1dadd19d9b251fa6dbefefbc98f16fb62c66 (commit)
via 7c75154f52853de902bfd7c880b6e16fe3a79c0f (commit)
via d127560b02816a7ba12869d19ca51a30e695ff35 (commit)
via 793772f302013324ee4964e5b2c3439c0eed2221 (commit)
via be2b8d67e266598e0fba9e658042986c6833d220 (commit)
via 6cd82ad2a63dfc9bcd15dabdf650da242eaf924b (commit)
via 493f952e6937adf7045732a2f7e0c4a4313fc0a5 (commit)
via 9b6993002b4ba9019551e50613c8a2c6c7ff9fec (commit)
via d8b8e46b853f13d3e9ef2857f0fce424f1876ef5 (commit)
via 7116ee3c764180aad581e52b36dc67124b7d72f0 (commit)
via fb2317550507bb357cba2eee89c3a469f1a89803 (commit)
via 291d0cbfdc964c1d60542edbe9f442cc383657cd (commit)
via 52e971851f0c7ee8f45c511d810497e3c038dc71 (commit)
via 07274f662a772c856f0bf80213b246e689582409 (commit)
via c3bc4e02519d15e27b8e32291bda1a59ed08f42b (commit)
via b77375be27718eea1619f4e4fdb4899a29eea18e (commit)
via 05793b5a18793908b17190008382a27e133e5979 (commit)
via 186bacfc7ba324647f6689fd627c8d9d2d724c0c (commit)
via 058af3dc4b9f1e03d46c549f3ea848fb1a5c7960 (commit)
via c47c4c3541a5a9ed7a77f47610d8f14c29295969 (commit)
via 7130e28820b2e9e603f64546deebf12b410b897d (commit)
via a29df11575ac9b1aa036ab212b49f4706ba1e607 (commit)
via 56338ac70f174880ff1ca73bda0afe73dad2e7d4 (commit)
via 2d99288b3400b01e3eb1402717a182f5e828c7b7 (commit)
via 52ee8a28742a20415742651cbdf7982387050641 (commit)
via 33f9ea32c67a6e5e7df816432c98f3c9772b0b0f (commit)
via ed4c07d55c90e871d0c2b8ce571273cf83740e66 (commit)
via 283053c1a3dd96ff3811f25361f900dd7c9d97ef (commit)
via f173bdf07ec3f4d099176dcc6b7f878b218fb93a (commit)
via 7ce82914391a42fde56946b284f386bf0f3fe169 (commit)
via 5c9b7307a56c8b578be2b5b1ad73799b899a2e93 (commit)
via 77b918b70668c7755f3b7fb8335f5fc1f9f119a6 (commit)
via 0c0e8a5f4ddafbd7724545c08bc813c70f360faa (commit)
via 4eeff0e79de122645ffd3bf117a1486147fc9541 (commit)
via a98fc15799b0d8898ab3b5071ba55dd8935d45dc (commit)
via 94077743ff440dfbcfd4a723f3fd676acbfbefe4 (commit)
via b407617f16ae9d672ef0e7812a8eb9e18509b6e6 (commit)
via 76f364b152f174c57982f294f97274a212684121 (commit)
via 6d7a20689e5ac2dd8139c5d6cece17b5d1a4f178 (commit)
via d12134b3a9f135f9ae4317587eef31f45e6c0454 (commit)
via 55cecba6f3c43d725fc7c1614e5b47bc7729d5ec (commit)
via 7ef140c77fa2ddd4194ffdc344781a55232d68b3 (commit)
via 7a0dc75cee48aceeb218147331ddc81a05376358 (commit)
via 61446fd4004c89a7d568988776a0fc2c1b67046a (commit)
via 9ad569a0f568d9faf5f213f1403dce51cbcf08b8 (commit)
via 9b060a79e29c1691d1e84da5d6a656e99f2ec8a2 (commit)
via 501c5f296f27b565b6e2229e62f8f19754818abf (commit)
via 99adadb0496046011060ec22e6834dfede9f1ce3 (commit)
via e267a6928ace0651795f188f43a6a5a85c479d44 (commit)
via 08d13321640259a5036053852ff0c8731b54239c (commit)
via 7610a7bacea56b07ae06975ee771d010846addd2 (commit)
via 0996aa92a103e7af59bf1ae9c501f5feb2a5894c (commit)
via f1f0bc00441057e7050241415ee0367a09c35032 (commit)
via fb9b8cd9f1200dcd6d7146a45fdbfd2c7675be56 (commit)
via 16a1358aa2960039e192ac66d504150a55b374fe (commit)
via c02b9f4c1f0970cd50c5486f739dd750046ae97b (commit)
via aaf4fb3d70f5868ba9c6fa16c490abd7ed5035d3 (commit)
via 2397c86b17e71f1472a867dfa1fb4e7223cb811c (commit)
via 2fa92e23966279921c57bd1fa67ec79e56b328ed (commit)
via 9858d6dd9af2640765266d6b99056e93dc368277 (commit)
via 654f0cbd1850a78758194cf3dcc7d5a90d37e495 (commit)
via dcdd4e10f22f795754f91805b741988ef8a389c2 (commit)
via 68308bd95eb0a3d52f048790cd16611b5fdc7ee9 (commit)
via d5531c9856c5bb85f63d2e3168fc8b08c9700418 (commit)
via ea528f50fddfb83618aa338bdd4607791fd788ef (commit)
via b4466188150a50872bc3c426242bc7bba4c5f38d (commit)
via 4df912c903ef7469b0034f1750de4cf0711e9e2d (commit)
via 81300648c7274effe356e4ba421cdf3cdab8d316 (commit)
via 97389bad9bf82cd32329407557aed4ea669401f4 (commit)
via 8f3e82a987df1e5abc1ab2d4f0785ca3f9ab97ff (commit)
via 44d2850a0d3e96ad2b405aadb222a975c0462826 (commit)
via e663cfecd5385a0d2e27c301f089e1f60d3e9a28 (commit)
via efb79bb638d7c88d3f46c9486d7f29f2d2e6e8df (commit)
via 2645b194340a5a7fe315505f6d4cab874573c2ae (commit)
via 00bc99c5a2c8412994dd67b0e01318abb2bde893 (commit)
via f48ddb14605e0ba0bf226ab074dc881e4a782a9b (commit)
via cf16578b3ecf2da1b38a724107dfaa802b03339c (commit)
via 92cc4a7b436ebad4536979bddbcea21ffa0b118b (commit)
via ed1f3349ee00294fbeb42760a53a2694f9dc02eb (commit)
via 635e3dae01973485a2d81487fb684d40c8db930a (commit)
via ceaa247d89ac7d97594572bc17f005144c5efb8d (commit)
via dddc267e555071b02a90623758f947d18c46356c (commit)
via d5e250eff9f9c7996f16eebfc6f4bb7e0dd02aee (commit)
via 923c859c0ff6fcfc3d0c268cd20d493b71e66d29 (commit)
via 7f720276658e694a1edbb8ce105b04a8dcf1d36c (commit)
via 8644866497053f91ada4e99abe444d7876ed00ff (commit)
via 36ec15a2721f408c1868001f8dfa4c358f2f0ec3 (commit)
via c30d4ccd7f07819b60695d11dd5f946b61e8fe17 (commit)
via 8a33648413f84e7a7be65db8890d217d91f7c8f6 (commit)
via 49c83cc4d393dd69453f4fc43cd7d801e03300ef (commit)
via f4489fa39cb50a2d0a80925ac6709d4c2beffc29 (commit)
via 350e65820efee213ef09122b88cdfdb7f4ab38b0 (commit)
via 8ae80ada66174a7748d0ac5a02d9fbe6f443303f (commit)
via fe9349ae3a6f283089af635b2453f3b7a6c66ac6 (commit)
via d9ede029ea8224a62edbdb2dc890a14e068a870c (commit)
via 16b92d014e713933d591ebde9cbc4540044ce8fe (commit)
via 365b5c443aa170831bc1d6a40b0e3323192fb532 (commit)
via fa6a9dbe36ffc55bcadf54a523bedcfb7118474c (commit)
via 3b09268518e4e90032218083bcfebf7821be7bd5 (commit)
via daf2b85fc21361544855324799f2d3af777044e4 (commit)
via 098da24dddad497810aa2787f54126488bb1095c (commit)
via 69e76e0edb5a28773bd8e29d82902c4dbbb412ad (commit)
via 9e77d650e31a106518ad263092f6a7e2a69d22a1 (commit)
via 1eb1ca43bb8682d49c2396395919d8445a28c012 (commit)
via a3f1538ebc5aa4b0a6ee49fee142646ff3bfee2c (commit)
via 297646b5cd8cee65d80ef74d07cc7a3b4890d555 (commit)
via cc73f7e20f8534b0986bcc9a41e99511109546d7 (commit)
via 1c45a4e0bc84ab2d3e006ceb63ce18b9c32adf53 (commit)
via 80c297d9ae634e44c510884ede894c6737941820 (commit)
via 7a74c2d58e76b8fcc9aea301bd4ea42a0269aca8 (commit)
via bbe403303f87ef541114aceaa0aba02d2df714b4 (commit)
via 698534e5564fb14a384d1cf28eda058453dcb1a5 (commit)
via 947f08c755bd91255b2f8034a65b99445f13b024 (commit)
via 4df83ec7d1b92d7fb8c6fd2c0906694683da8c30 (commit)
via 9ee14145b2879b044fdf0c32678838ee67343094 (commit)
via 6e57b76919731d8f5a15d21d2c008336362f44c9 (commit)
via a29f99f27df0d39ad097b393dff0ef12f6e89388 (commit)
via 7733a51887b6370c92cfb02a2446b10d15e224dc (commit)
via 66300a3c4769a48b765f70e2d0dbf8bbb714435b (commit)
via 952cb2ceeb8e23145a18839d42b12641e520ecdb (commit)
via 318ecd4e65de22d957ed55f571e868ceb3152f97 (commit)
via 9a56eb7ebbefcaa29ea623bbf8d7102b6970ff70 (commit)
via ead53d5c0f105d4f60d451734d66c8f202cbbd15 (commit)
via c31cfdf8449030f874406f4efad754ba7eb786e6 (commit)
via cd6faa250e082808a86d9ccb1f11ea07ab81a618 (commit)
via 5893305969aec78850e2462859b3bf4b7a157057 (commit)
via 419665026524f1a1b46efba377d41bd1f7f806d0 (commit)
via ad0ce258df14fa88a299ef37238d4c2527f273c8 (commit)
via fa4aa9c87c599a985bb19b78ae3f2d1d4ab9bb63 (commit)
via 70515e4857db49a5d2cec93f00839ce2cf1ea52d (commit)
via 65b293e59954c95c245f44691f5c82fdcd6579c9 (commit)
via 532aa507e57aaa8c406cd4cc537058de6fcb5a88 (commit)
via 94793e41d922cb10e35e0ff146b19c38ace415b1 (commit)
via c1a0b11d84b6953c0c53bfe26d7c1a946bb772fb (commit)
via 737c4eb744e7edcd77410a11000b49e17ca3af8b (commit)
via 1a4d0ae65b2c1012611f4c15c5e7a29d65339104 (commit)
via f95e202d7e972f92ebf5ee9ca11c4ad846cd1524 (commit)
via 6ac392ce798a63a024a2890e3838df8a15a233d0 (commit)
via 376c01fc1b6114e6717a9f515999dd1bf67db39a (commit)
via 9d192aa44e2cb1495e78b70ad5af23c21acfe34d (commit)
via 88a80126ce73428584f8f36657cb159e581c700e (commit)
via 77eddb37c51caf6efb688a27aa60ec15fd0d3535 (commit)
via 252fc353bab9ae9eac99cc5c54b66b6e493d91b3 (commit)
via 1a46994e855e3a2a0b5b407df94e52ada3efee7e (commit)
via c80eae98a8dd78a2c0c711f642a02a4b24a3a819 (commit)
via ae9eae29057da3aa8c585b86b662967801ed8bb2 (commit)
via 48cd72d455b5bd1298485f58fc7dcf82c7e4c3cf (commit)
via 06280cc6b480f6976f25ccf8b9cedbdf7aa00e34 (commit)
via af354fd9eb8390cb388348ad66a4d8269b945ac3 (commit)
via 48cfa00bb988cfeebab4e6ac7ae440cd685137e5 (commit)
via 16ad60fe0d9ba8cf53fc6a190ad25d92ece8e8bd (commit)
via 9aa4dd78f5cc00acb5ecc4fdc5ba1beb423aa40d (commit)
via 61cdd0f353e20153706af04ac884a4375386648c (commit)
via eee654360f42459c1930f83e78f901cd4e9170f7 (commit)
via 6cce379ad4e562fae0e899b2134f59f947d54f3e (commit)
via 8ef09a5063e52e52e1192f81f001c186a32aa18d (commit)
via 5b0ea09b5f533d2cc8c572693d882061b5d55e44 (commit)
via 52f1b58737e7c7e276cc7ee7daabf2f2e2d7f2a8 (commit)
via 6736c51047cfe2ac2c4678299b5b768ba3585db0 (commit)
via 3104e910b9a4753d2bf4025d59f0f1a13f01e519 (commit)
via 01d11e9fe77f1ea869aa1a0e800a965dcc638050 (commit)
via a3ccc5c6476526739e363d3aafbd2e497cee069b (commit)
via 07403ec675bf43e0936aea2ab5d4f5d903770f13 (commit)
via ce81fab6415ae7f50ed29c201162e912d41eb50d (commit)
via 061f5a1ae10248ac161b9c5a489eb0405b2c8426 (commit)
via 9f4292fa5d27c5569384ca57b7878be69cbf5499 (commit)
via 5a4cf2dd5915ed8f499570faa1005462396b233b (commit)
via fe5549bb7e546732e338db24abb7ad571f34f668 (commit)
via 63e4fc15cc2c66b07168bb15e2e6af464c235a3d (commit)
via 2df61b4c21596ca9b1f9e2e2364d6ed352c2eca0 (commit)
via 6a9dbeff7d28a8ef91ceddff5dbcdef9a7d1452e (commit)
via 5ef450d431113ab2aa349d6200f7f69f55e975f4 (commit)
via f8c0d29e25d71f77314ffc997e678c7b24c34ca5 (commit)
via 34da803fd804c6c0fa7acb90ff46780df69d5898 (commit)
via 6db3556488ae8a75f5250a175f3507cc609ada19 (commit)
via f07fcf3c8e3f28d97772994f26520ac20436eba2 (commit)
via c9337b1576d127bfdf11ed0e87db9c6cc1f98a09 (commit)
via 342f5b2ae66112e9416da86436b7932ea8e6f9c4 (commit)
via f7ae819d576cd95231c89c269654e23afd16aeff (commit)
via 5b0ef5ce8137711c67bc9ce0e4561b7adc2ebcc2 (commit)
via 3a3f1942d99e404b9e8a7b83f6688fb8b8518afb (commit)
via 434d8db8dfcd23a87b8e798e5702e91f0bbbdcf6 (commit)
via 58283fa48ac4c232e20edb144de0dad791f429cb (commit)
via 8a3564c8893c5b9df451ff29d58f65604740b310 (commit)
via 435436f1402c345fec712d923848f934999f3107 (commit)
via 98483c34555c48143e6bfcdab63a9ec68a6fb86a (commit)
via 99afc8ab28a7748f9dda301da98e64a2b0b5e04c (commit)
via 724baae9fa3e9cb0e6e0835ae3dafc83fd30b84f (commit)
via 93e12f75baf215fdc8434461a2d5428e73f06860 (commit)
via 9d84626f72a70b3b71330c6fa31d767109a67d9b (commit)
via da214e3e13d2ee898a409df35cff9f208654503b (commit)
via 418a6dc9ee0369d13d14bfa825229c0e35a694e4 (commit)
via 8ab7817d7c346d35a7be2a1d6bfe51aed4fef15c (commit)
via 9e5da4f42072e2bee454178eb1422103504a40dc (commit)
via 78bb8f4b9676d6345f3fdd1e5cc89039806a9aba (commit)
via a8486dda670bf22c27c7cb2ee95b84cc53c1fc08 (commit)
via 73a6349b16ce1c95ff6216fdbff9551d574988d9 (commit)
via 5f7f84779f66576555728e70ea75383945e0700a (commit)
via eb3cc6a30fc6ddf4124a0051d44bcfede286520e (commit)
via 59464e665cd90e476e6c45843151ab6209a8517d (commit)
via 29f876f89516fb0696d85e760bb9cb15e7e398b2 (commit)
via 17ab7fae73ebcf624a0c719b16f45b9f16db1419 (commit)
via 21b1adb387dab476f88c301ec1538fb6be87ea1e (commit)
via a2004cbca71aef99c8f640c4d7f06944c63b4c7a (commit)
via 49ba2cf8ac63246f389ab5e8ea3b3d081dba9adf (commit)
via 423640029ecf6f138eb8b160fc40ca5afef44e4a (commit)
via 52b36c921ee59ec69deefb6123cbdb1b91dc3bc7 (commit)
via b0a028bf381e5467acca8d66f37e778d569e331d (commit)
via 46cd22e159df5387c036e285e8398e9f1320e2b4 (commit)
via ad40537d9f91fcf489d3e9a313a73385ed5fb241 (commit)
via 6353f50a59f621f96a3ccf0003e5ecd527dcd07c (commit)
via d2661eddfcbca6532d6cbe18954d345ab8566345 (commit)
via a02f49d16d40c8b38514af00b62001225f9f7a6d (commit)
via 774aee5f844849e58608ca86bec27e731c3eac5b (commit)
via 194ef6477663b3c8671724bb6ab7b0bb13ae0b09 (commit)
via 631e9f192cf33fb58c0a945ef57592fe7813eaa8 (commit)
via c6fc5883c41ed20f77ebaea543057521f76fe4d9 (commit)
via b3388dfb13dffdd653139673eceef34824eab072 (commit)
via 774554f46b20ca5ec2ef6c6d5e608114f14e2102 (commit)
via 91abb7aed95fb3cf15d25a971af2fa0b952e9570 (commit)
via e3f4b290d17a68db728166cdffcbe93517966e8b (commit)
via 7d2867d02fc20295ae0622c987a25e3119d3ca7f (commit)
via f027496530a43f7b5eb5beb840266a9bc5aaffec (commit)
via 0e9213727bb177ede9253fa8d0c1e1316487e33b (commit)
via 5e5a33213b60d89e146cd5e47d65f3f9833a9297 (commit)
via 4e80da3959ae51851ce68294bd59fed429977f4d (commit)
via d01a04e0f57552e78fe19beb99789888fecab7f4 (commit)
via deafd46a146bf83ea3af9076c9ec808cfd1c168b (commit)
via a7254a232bedb445f4c7dd47b8f623a94f7056b1 (commit)
via 90df5f971e7988a7a024ce95fc83dd9dea3c9b6f (commit)
via 0161c02ad4d593dc6a7ad5b352cf7db4e70b3b48 (commit)
via d98c6ee0863c784e79204242a3d868d4aedc3d5a (commit)
via 6600a2c2981b2c37ab42c325e905d4a70a415342 (commit)
via bec26c6137c9b0a59a3a8ca0f55a17cfcb8a23de (commit)
via 91071d03e1192378c50012f9e820674d891ed4af (commit)
via da7f88d1c170826babe93e93b1b32ade5a75cd7e (commit)
via f20a049cb0e6b29464acec69d26a46f26b7e2170 (commit)
via 4fbbae439b8086e83e60f1708f40a83de61beaa8 (commit)
via bbeaed010f51dd88fc28743d4a9194c9d19c2421 (commit)
via 012688738d646844e69080484a1fb92d11a4f693 (commit)
via b77baca56ffb1b9016698c00ae0a1496d603d197 (commit)
via 46f994f729d0f554ad4f58abc82acbc7b526ffc5 (commit)
via 22a8be53ada3f90b8aa226f08081d579b377784a (commit)
via 5e70d900e46fb148acfebf8c2aa068159e58d91b (commit)
via ab560987671d76c85ce36a09bfc66cf5eb9398dd (commit)
via b91bb9719ff759417ce8f6412ca5f8be57a2e19c (commit)
via b41d5a89cc84139a08dc48de7ac08bf602d1c580 (commit)
via 2c0a8fc3bfa0fb8f5f6cb2df504b326741996025 (commit)
via b204a39d5b9003f991104c2bd6896013f19a05d0 (commit)
via 0f487a998063a01ea16e8be048076f53b11afd02 (commit)
via dfd2aeefef39f064183c84c23451637247e32399 (commit)
via 1c57fb2350d9b8440ae4cf50a94be7c61cc462b0 (commit)
via 8c55200c178773691ace2785240bfc65f4e351a9 (commit)
via ae40f56b7e12776479161c7d7a2d6616fae09850 (commit)
via dd8d9d4ab35db98d5269dd9c0728a5af6e748f1a (commit)
via 868ecf071728924a23e3f0d3d8967cde5fbf8c30 (commit)
via 475284be04f902de8e20e1756bbfeb74d7f63779 (commit)
via 0eaac383a129e72f6a0d1236cbdf725520c63bf0 (commit)
via 953d4bcf1e60b63785216f523ce5cd9929ced8e7 (commit)
via 50795e21557b68a31f0c497f6de311d428b5d88a (commit)
via 46f937392ece1d17476f44ca7d06869c28a3da72 (commit)
via 3dae42840853b9ff8b4e580c794a29e8e2f30030 (commit)
via 7713b147008a03d871a30d3072bdc1085f11b942 (commit)
via 04e0ef7fe56b4c9c65b71bba010147af4506675d (commit)
via fda8ee0de5fc6416661134a261fb1a5a1569f93e (commit)
via 43b5b823592d258002b741a5f88f9c3f60783c8a (commit)
via c5a9c69b1e925a9f3846f65f64f7f61077e44d3a (commit)
via 3c89c290c7296591b773caf0c1a626bba40eb588 (commit)
via 29e01b55a5acd72e651c2b2bd5cc63ffe8b21da8 (commit)
via 1448f69f1c4e070f05502d67ae4297c248e7658a (commit)
via e00a4d69a403758e012f0f2924b40bc79e84e23d (commit)
via 51b3f091d6a750ad4a65825c4f36bb7c05dddbcb (commit)
via 652ae9e92f8c08cab84f603b8b6119afa23c8441 (commit)
via ba9dcb416a6170c8b4174f5353e3ff9b12a4bf66 (commit)
via c6d6f765fa9faf89c5159374f3ad60066e7e4ccc (commit)
via 927397a7699daae124085d2ddbdfba9af5c06f8c (commit)
via 54c1a257f7c93d1249f8bc7bdfb69d72c323d1d4 (commit)
via 2482bf23634f5fd5db0037eec6799bf824a70a40 (commit)
via 40d727bf4c011c28575fb099fa8370591afa4a2a (commit)
via d3e876aacdad6468ff7cba157f5ab31400b1a474 (commit)
via af816ab1e0c767c3ab001555d5882bf086b506e7 (commit)
via 6f8e187c1bc8671be1e6da370f192d58eda3995c (commit)
via e70bd3ba8f491df4570c137a9d7561557b9f1574 (commit)
via a6d9e2bc94cb26c34521f3f793da354fa2cb1f9b (commit)
via 4ad96ab57e9fbe8ce9c0bb4032f4c731d41d2371 (commit)
via 847525d5ac7902c5eb90a5ecdaedf34ae3e73366 (commit)
via fd5a4de5485f23e9044965f7082b0078f4330113 (commit)
via 826ac1a139f2c14e94d4b3477950daa02efc98ea (commit)
via c0f704fc0969565af020e1b51db5aaac63d68d52 (commit)
via 5110dd59de80220e35c709850edd1ca8c311355c (commit)
via 23f9c3670b544c5f8105958ff148aeba050bc1b4 (commit)
via a06071b1dc4e1e4d6c6977f099ea3c9673e96509 (commit)
via 24b2851dc08da2e9d63a072c1c8b4550ef919996 (commit)
via e17fed2f2bf0061fe942cc8ae9c31f44f0d6e303 (commit)
via 6784b5dbb0e599f370fb5965acf7038ab214b107 (commit)
via f2594b4779f799cfcc724999f4162f8e2860a8bc (commit)
via f6c1593309be9e02485a90788f02194ffe036d1c (commit)
via 874cd35c928b9d2d0f53011a5d51fc92a8e9b95c (commit)
via 33fc0787924af12936ef147c48f6140de0aeb9fd (commit)
via 15aea6b5feb747f6e61ce5d7b2e7110c71c1ca7b (commit)
via 766e74511e7c99d3d224e15b5f8a8afb5d154ea5 (commit)
via 228fe4a17c5da83de7ea31735d2f283d0638d928 (commit)
via d576a1526ed1a9bc3275fa966768ce465692386a (commit)
via 6ea0b1d62e7b8b6596209291aa6c8b34b8e73191 (commit)
via 9c4fea2673759af91aa6dc3e9f10fe74e7275684 (commit)
via beb36d9729b675c3fccc8c6aafe406f7f7b7d221 (commit)
via d85eda60ea4f2304859ff935f998585dae2f4854 (commit)
via b09579f0af1644cd441ceca5deb2b6e1c40119bc (commit)
via 4de96e9a3231ed79cb9d5549c00079df4dd7ceba (commit)
via 181d405a1e606d4581d84b7fb110875c6561387a (commit)
via 79af4f917ed615538c88ef4df43f8884a0d3e5bc (commit)
via ff926e066594778c68772d77c3ecfe271fe3079c (commit)
via 0acc164c6a3be5eb97ba3963264bdfa5211d6b9c (commit)
via c96ac865ac20c4e80b3206a00c15fa998cb85bfd (commit)
via da4d1f0f461a2ffd46fd6d09b78d4bce47df6816 (commit)
via 3dfd3b25e2ed142ce86126c411e78194d890679f (commit)
via 7b7252d0442b7cc618094784d1b243d09309791f (commit)
via e0cee3754d5c1e2654223bb87e88567ad8befdce (commit)
via 538350b7db14e063f716b040a5b0f0ca2aa35278 (commit)
via bd2da89767c713fab31eb24cf5840cfe9b925d64 (commit)
via 7e7b44723b80637d14bbbb714b69fe4ef7ea1f9a (commit)
via 8db9dee3621661108c12faedf26bfaf9381c13bb (commit)
via 02cb1de4a0e0d3c12bc29aa7686a9015dadc637d (commit)
via 2e06048a76a016e90baef2d7a6b218457f97e3a1 (commit)
via 438bcd6747bf177dfd6355c77c06d28560a1369a (commit)
via 21c1954e0e26413c1110f68d2b7c730ae79c3d44 (commit)
via abd717cfc9b0b01bfe6a5d651a2d34ffaf61d055 (commit)
via c2b8d8433c9a4ec5fb6817a104b6a6309d413f7e (commit)
via 37960ee83cb33be5cd683fb0d458343752caa01a (commit)
via 8f36290ceaae7abb6ee5b5affdf0c7bb31e5bea5 (commit)
via e80de6f13a404687f4ed896e548fc614dc953e6e (commit)
via 823f30307bae21675418262a1b423b3bfbbdca93 (commit)
via 8e4c96040527d952da60338f7cf061f976780543 (commit)
via b713d234a982a804aa58839ef17ea5bb54fdf017 (commit)
via 3285353a660e881ec2b645e1bc10d94e5020f357 (commit)
via f46b788569366fefbfaaf6ec0d57bf1861b1b2ba (commit)
via 20e1bd7772429ac6d6fc2ec993da36985ce75984 (commit)
via c4e3eaef7ed29851b7cf3f0714043cfbee6f5852 (commit)
via c8c69b2a7b47c67655985c506a6467acbc471a05 (commit)
via ce65bd4fa0bc59f262277d7bedb2a649becd1fb8 (commit)
via 489ef518a4a386d7c86534f15db8621210c8ce9d (commit)
via a7f2ef92fd007ffc44e2e2572d0390efe8fcf901 (commit)
via e0c470b595d05c8c075732bd090fcb02b2af996e (commit)
via 69d99618a538d70a3626a2aa403b9aa5d0977b03 (commit)
via fde67b3671c8bbb24f12dce63cc14fd8ffc599a4 (commit)
via c387dc0811456b30f4c4e12a9973e246dab385e1 (commit)
via 154021bfa4d4e8f1cd6bf701fd3f50ea1d12665c (commit)
via 3441ed2e58f5b467928ca1d3145e158144674644 (commit)
via 582bcd66dbd8d39f48aef952902f797260280637 (commit)
via 5c61cd71a9423cf90166c057512f6cc168adcfb8 (commit)
via 4065c72003e29f7ceec60c3551dcae6f6a73b1f5 (commit)
via 65d2a83510c3abf2f2cc3fb082ce56c99be32dfb (commit)
via b24200598f331f9b46805e04b92c23dd7018f2d4 (commit)
via b28f331a0299014773151ec3bd652f3e59557e5d (commit)
via b842ace68bb3cebe88c05c72c11dd25d5ebcf9d3 (commit)
via af8054f481486c2d9ff364ef8b97aa3aed97958e (commit)
via 5a75cef501889781df4a90919bf48873c7f00ced (commit)
via c0eb91022adc9d00b60902c6345a9ae8b319b856 (commit)
via 0f1e080b028eb0ef89f88ba41ffc8e701888a8f6 (commit)
via 9a25a7d76337270c5a555bd98cba95a2fb7d7295 (commit)
via c71fd9915c82c618d865780815bfec5aaf6c19a0 (commit)
via 2bb6957f057d02e70338d1c8586d53bbda7f7d33 (commit)
via 8e3c36663ec9ee59b710e97a1f6716fb59df1d51 (commit)
via 12f57073eb9f5ff5d11035852e253d25071debab (commit)
via 61992b3bdb4d128971e459da4e6d741a48ad01d1 (commit)
via d05c01185756886ce13c49610a813eeb4288a04f (commit)
via 1f945d96f610d2961699f0f7227b82600df7f06e (commit)
via 579ba09beadc10c63ff523ccab6a21f112f364c5 (commit)
via 6fde5a92a9e7610e4bd6612221239fe686c3c004 (commit)
via 373eda27fc0e591356a7311e2ad560bb9441e64f (commit)
via 09d97fb51ee5d9d00077a52b3678f94c158a365b (commit)
via 50a11ee4e494c9a57d43cdfb2af953d6c8565e47 (commit)
via 1303709492895c08fac466f75069a2cafafb1de5 (commit)
via 94f99cad8539239a93877a1774fbadf89238c181 (commit)
via 3ca2c002d807fa1f8e7dbf6731ac4f1796f4e5a0 (commit)
via 572cd0d4d2866708af03a0413cac704b98e5538a (commit)
via 77ab7a1390765dead65be7b3a75bfafdee1f6abc (commit)
via 3d8ac71299d439906a11820e4ef914eb18b9d1d3 (commit)
via 1e62bf975ba54ebee2e5ddc2a2cc26ca5c254f44 (commit)
via 1ddc5eccbe9fc1784a3447c38f708a389a45d83f (commit)
via 9c8dd4effe803bedeb2660c30f72a3ac4ecb308d (commit)
via 5852324e1ad021881339e6ef4cb62611bdfc1b5a (commit)
via 5845ab9615b27fe8efa38b38622fd12a42350e04 (commit)
via 0bb258f8610620191d75cfd5d2308b6fc558c280 (commit)
via bc67fb7ccb6d655fd46272c1dfd89a0e58677035 (commit)
via 113f736e002db510897feb1e6e55600d622f9d26 (commit)
via c6bb0438548d5a7eaa6b2e7cc27f801f7c156cd8 (commit)
via aa72029e21dc74db7eb7a354ef9f921227e53a6b (commit)
via 941381b6afa208e485a1de772783029b3ed04abe (commit)
via 5c681eddd0ab1819dc7fadd692cadf98991a4d64 (commit)
via 8173e046080fe51963a37ba89107a8871a0b5353 (commit)
via 758d1d155f95a65702969866454ccfdfd90a7336 (commit)
via ddf22289ac033aa2ffdaf8e348c4a05f2d1d6951 (commit)
via b35f3264ece9358619c3831aa6fe6bf74a14c8c9 (commit)
via e9a7f0106db4f30af9f64e663d39db262850f153 (commit)
via 8b2c30923f169c6b747158740a85be6d68a1d05c (commit)
via 699068ef1467867fbbb86cfebc20e823c5128100 (commit)
via 69aae7b1a831c5393785759efa3bcdcd238f599c (commit)
via 28ec5cffac56ead6c2c79ffd31427c46dde1b061 (commit)
via 06d963e3098ea6ead0eae1b54f178222b9fff479 (commit)
via ac2c63f07c78ba94fb8b76ce1051a5810b97a99a (commit)
via 02db7759b655b9eb3e5d4e750e5f5bf212e1157f (commit)
via 1007c99c026d5d7f51a95061d25f59d621441b39 (commit)
via 216b2e18c9811ec25dc99ae9b320140033e26a36 (commit)
via 64eef65d27bf5310afa53cac418c18bc8a989b31 (commit)
via 1af6565d9d5f789c82ececbf83ca725cf3208b3f (commit)
via 83474d8f558bebb4be0b0f75e843fab993ee3713 (commit)
via 961f28cdfb0f5cc1f5be9613d5f60a7801c92b0a (commit)
via 20fb475bd5cf9051c6f92163e2c427bb782db60b (commit)
via 78efcc84de61e1a4aa6715ed29a4343c5482dae1 (commit)
via adb839e3c26c64dc820508f5befe61f806d97fa2 (commit)
via 1b4f658815fa755130791c8a668dd5aa423cb48f (commit)
via 9c15821896107f88730d33d364b1bc0aa82dc5f2 (commit)
via cf5904a4ad2685c8923f4fbee1987132595908db (commit)
via 3d402d65a8aa0f271190b40f5e141c4e5ee7b31d (commit)
via 0b1b0da6ffc60991c0ccad85695631dec02db4da (commit)
via 55d44fc94c8e0ddae7b9b383e7e29b9514ce23ff (commit)
via b8d64503f7fcbf832c4ca6bdd0dbd55678baf88c (commit)
via 9a2a86f3f47b60ff017ce1a040941d0c145cfe16 (commit)
via 896dd6912cefe2190fc12f58b2ce291f17599b8e (commit)
via a8c1ced88d2e49827f5082555e335e209fd32ef8 (commit)
via 2cc4d39e5c74092c2fda2d9be3682b432243099b (commit)
via ae873233be8b119b0495b98d843fada53949ab58 (commit)
via 62a3bb1472df7765c40525dfe9c07ab949fa4a72 (commit)
via 3422adaa26b0e1c24d6e230694975a4986cb0965 (commit)
via 812cea250215b93bd4567873910e2449f5b559ec (commit)
via fec011eb0defae05d4f0803467c841d2f27aee29 (commit)
via a8e53be7039ad50d8587c0972244029ff3533b6e (commit)
via a167e7085865d77a1a9311a5cfae067da988d5ac (commit)
via 292fc936f6bfbcf84e56e67b52a16368a726d32f (commit)
via ede52d8098375352ae87a124619ad78f01fa2e28 (commit)
via 4dbcf3b666bf27ae2c3018007e163235e5c326e2 (commit)
via b469e6036af3a8d4b12dded6597ea08abb2196bb (commit)
via 869ed54d546cbc8d019993e3c3b81b8a14dd0128 (commit)
via 5cdde563b1ff647a83729c768f688188ac90bfd4 (commit)
via 5a045dfb928e3a2d5702bea2e803a6f6ac5b030d (commit)
via 3d8de45a64068a289bc8eebca3b21c308047cdc8 (commit)
via ec745dbffae8c60801738225ecb3c71b9d758091 (commit)
via cebaf1622cb9e73257b9876bfe863a3102803766 (commit)
via 9ef6f17654ebfc0f103f8563a21db07dd141806f (commit)
via 8cff3a658f4c21b8e83042e475491dbdcd404983 (commit)
via 8a0d8d026e4489913560819c438299560db77724 (commit)
via abca9b6b8de3fcb0c6d99df48793eb975aea0e5e (commit)
via ea5ac57d508a17611cfae9d9ea1c238f59d52c51 (commit)
via 75b062049db49cade952510e66324761a8ba09b6 (commit)
via 38278a2b5a74a76708d89862f8e9eee56bd84ab1 (commit)
via bf8e9e937a158253853013a6f316573d3b53e3ee (commit)
via a2d27b3d05763fa267d6f19cff7468ac86063a38 (commit)
via 0f0ab5b692e5670b0ff6774d0003a5f676a79dcc (commit)
via b87b288e07b97629fa80b36b0cfa0833727b785a (commit)
via a979801041c9a650f2b24a4c39498df80d72b75a (commit)
via 97bca3e1ce5cb69be9fb6fcbc9bf976fb05e3a2d (commit)
via 27ec89e237865a6f9d92639256291acade7af69f (commit)
via 004375eb0955a2eb1fbfa5d5988cdc5b10ae441f (commit)
via 6c506ce59394354abe02a650579ec0517867a3a4 (commit)
via 8efd8d022529b4d5178329bee08a6909d5823c04 (commit)
via f64dbc690b23b7354c8c67ca0add3b2ae07fcd0c (commit)
via d4935b95254646ab389e43517bf3cf61373d23e0 (commit)
via 08ac200f2f3e15548a81b4c252b72fc8ac72edbe (commit)
via b78153aae59d37e4c2807c4d19c4b548be13adc0 (commit)
via 4bb8254e151217f9ac5c29743d57b239c8f11d1c (commit)
via 0b55e11e55f2422e2aa1f920f48debac92d826af (commit)
via c3962c4f4d5f08e3ff194132a6308dbede212997 (commit)
via 91a89cb8cac804441d957430593c43f40645a44d (commit)
via 8123c01dbd4f71cdff0ba73a45343ff51de0908e (commit)
via 914212e06d69ae4e24ac673b050dd60a7eadfbae (commit)
via 5043998b29c17dd31a06cdfa9bcd73eb2832127b (commit)
via 6b1caed1a4161932e68ef52f067d07d822d85c94 (commit)
via 2b01d944b6a137f95d47673ea8367315289c205d (commit)
via c0c59aceac2afeec9ae3e2711b21e177046b9db3 (commit)
via 460facf264d8a653a959013ce6d7c25875695bc3 (commit)
via ee3042d8a998de0a2a272003f53957a1af500901 (commit)
via b1fb64710cddf53857625d7a871f82775e20d93b (commit)
via 6e7eee31fa6b4884490778ce4148eb346fa4bf28 (commit)
via cd8b0868f1b4bd63f4bc9b661f0afca448e57797 (commit)
via c3374ed01863bf7643cddd90f4ef47d338df6225 (commit)
via 1389eb8fa945be3ffd35d0daff3bcbf17da15549 (commit)
via 55fdccd716864e9f33675546d3b6fcce18027473 (commit)
via 5434c74a0bf1a4435af9879572143a7b9574f713 (commit)
via eabc47cf4ee5ff9e3cc92a2e2b7c5b9481fa7bca (commit)
via 2b13ceb6879f7c02662a0973d5c37333b3b3d660 (commit)
via 84a7f88f41fd49749deda05aaef7e978bd0fb90f (commit)
via dd95b7f18281fd4f06d5f13b5a99ca9e083f774e (commit)
via 62411740bf363a984e453df37b95bbbdebc443e4 (commit)
via 79b80617cf3f9c979b5f541d1cb82274631f5fdc (commit)
via ae7b2a915adb90a32070ab3cca4f589b449ed435 (commit)
via 4f93a3a8366f971078dc9c8fe4c846c447a7a45d (commit)
via af7d66f37a3784fae54021afeba046383984ffc9 (commit)
via 70c188de44d2705f6944e37c3e1513572fb5d3d9 (commit)
via 3e344c14a192be56666978933dd4ca0af3cd6a86 (commit)
via 0c1908c1225e8933ac6f2e093f63ef12d3dfc6ee (commit)
via 71032a09e3cc7e34133378b426ad83d13ca3e0ac (commit)
via 42991798446e79e8e5e60641430d3d5a8cc0c1bd (commit)
via fe8e4b87d5b493fa2cf41f1e8dce1b5809c27679 (commit)
via 99c9c5e589d0359c115265864302b8dfefb210db (commit)
via ead42e5f5189b6fbd72823d6931eddff10409d9a (commit)
via 3adabc6a1ece55ee01bc5b0d6aee8aebca2510d2 (commit)
via 1cd0d0e4fc9324bbe7f8593478e2396d06337b1e (commit)
via 1030e6751920c754fa9d02360ef95342541b85b6 (commit)
via 638db2f91b681d050081a15e701011630c421f06 (commit)
via 14d4b5dd3ef4cb9cee473e70aaeaa5d63dac6113 (commit)
via 321b22346c57951b3e9758b471c4a1c95f5bb5be (commit)
via abfbe328e4a378f63a4eb5b3fb95949a38d21557 (commit)
via 2e940ea65d5b9f371c26352afd9e66719c38a6b9 (commit)
via 4a82ece4c4e353ab8e3ceb01fd8e0f4824ac6bbf (commit)
via b8960ab85c717fe70ad282e0052ac0858c5b57f7 (commit)
via f537c7e12fb7b25801408f93132ed33410edae76 (commit)
via 737c22c1d763e638bd958a5507bb2012267c8a22 (commit)
via bfe0f613e15f8c732462f43677f846610e496e08 (commit)
via 4e7ace34dbde7f41d474fc93a0bee27361b9651d (commit)
via 21a0d06de02275f51776d1177c7b64ababc2489a (commit)
via 81d738596851cc2f0d4e475c9c26e7e41c8bc1f7 (commit)
via d7fb4b7244e60a034e21873d5bd32f7148ccd973 (commit)
via d9ae23e5bc7ef47d29953bf54f2735d8f6f5f531 (commit)
via 5e17c750847213e462b9ca7537358d3d97975f61 (commit)
via 736fb87e53b0d57241c4e414ed5de2f3eacc6e6e (commit)
via 64caf0ab35936f4df289032579783a63a5f26fee (commit)
via 4cee65dd891d8dac00680084680fa57d89bea82d (commit)
via 7eda3c0ee0795ed21cb4fb44ff20f905de1cf800 (commit)
via beed5d700bd2ef38453f3a60e1bc2d4785c3bc68 (commit)
via f0b94ec2fb650ddf64d8d51dabb427056465747a (commit)
via 1296fe25ed708996f9aca9d3ae063dd80d444d9d (commit)
via ba84010ff254a3159eaac71db89a6c128d163736 (commit)
via d31372d7b6961c777c325350a4f415988a567715 (commit)
via 2903127df129af292b510a1c423e274ddc91ed2b (commit)
via 5a8474bf5e3b8e10959c76b573d3f0e1af03ebf1 (commit)
via cdbcf6b6134197bb28a3ee1c5460e78f9e89cdb5 (commit)
via d5ec40dace9fddaaec9873cfca2d670e8d35650a (commit)
via 56824afc873d66a300f2dbde46e906c163e2d492 (commit)
via fc609f2e7f47c724e2f7d713f4c3c41616751e76 (commit)
via 681c927f0d8150bbf3f71fa8c689b7712b834aa7 (commit)
via 03f1a5ea8f070b8b92704fc780cb858f6af04da8 (commit)
via bfb21aba64298f219218cf58ae9b6024c1ed7005 (commit)
via 516e15b88a729bc28ec04ee2713aa6b352f38875 (commit)
via 510292e639de813a6f8b02ee3e36726fae1da5d8 (commit)
via 717c10a44cc2b1826933a51bb17868f7456e686d (commit)
via a0a39d4b6e67fe575206ae191ddadc2f6f29e35c (commit)
via cb40afcbf13f2f456f5e671c755db9457c04e012 (commit)
via 97fa3578b37fce3897f78bfd59990ec9928822b9 (commit)
via b5740c6b3962a55e46325b3c8b14c9d64cf0d845 (commit)
via bc65585535ce775f6a5cf8dae25dfbda98a489ae (commit)
via a2a9e24d6a5744ab6b916c8e94ab0b77137417f2 (commit)
via b3933db8909e3040dec00fffd670b0963e264506 (commit)
via b587cb1fb68c3392af63abfdb4c91055dab6060f (commit)
via 95af6c2e54fb45fa5af1a2d3650376ccadd32c7b (commit)
via dc0963ad902d475f08d4f1bebab4e5114c013922 (commit)
via d08e8c4fca2bdcbe3fd573c1872c8e9347a71fb0 (commit)
via 2730ac6f20e6512c9c72cf22e0fe223f9bf66b09 (commit)
via c12cdc5ffe704bca024a84223774871e5049f90f (commit)
via 13fe9c1bdcc2e0a1c8947a328429e6f301c6fb3e (commit)
via 55b948ebbc47f5ab7a66eff50083921d9eb740bf (commit)
via d908fc0e44923c9e9196e137f4b8cc6a1a285568 (commit)
via 5b7155e4ac83a12410038ea4bcba080689378d73 (commit)
via ba1bfc79c7079e9c0886d542229c917d305695d9 (commit)
via b33929ed5df7c8f482d095e96e667d4a03180c78 (commit)
via cb28f3c4199610fb06c8939f5b5d95b99ae7a071 (commit)
via 64611b969652b074976c72867bc78ca43f8e51be (commit)
via 6f31530ff7e74757980eb2d01b82852636991672 (commit)
via 29fab3811a8bbae6bc28198f7aec4378b338a03c (commit)
via 1633572f050616e4fc41502fa2bcbfd70ea594ef (commit)
via 70778ebed8f5d0af186460600facc563865bafe8 (commit)
via ef1eba02e4cf550e48e7318702cff6d67c1ec82e (commit)
via 184bd43c0451a18f9d7d792050c9089f8d329a1c (commit)
via b2e7f7e47b9ac2dc93ca57c63635255a9a50fbe7 (commit)
via de4b10ee8d53f5c9537ba98ad401f84d008efd69 (commit)
via 9a5e82f81a296fb59dfe75c7f47fe91471fcb13c (commit)
via 1d3d7c590668be4503e70c2d3f2ee1da955874ac (commit)
via 3e88cb09890b3d05c82c56be350b1b76325dff15 (commit)
via ac299353640a32e77ad0e3f630d1a6bdbfdbbb06 (commit)
via 118f1f9d41fed38bb12d46f41d50dd2fd7367a80 (commit)
via ff863e03a8cbdb0c971a101af01b604dc13d1457 (commit)
via ee3e3d95c2554eed560ae6fea8f24864d0f32074 (commit)
via d2e2cb97e821844dc430ef1e47d13d618c06d9fb (commit)
via 6b3dabe34a4fafac4a91fd0b953b49dfb846b713 (commit)
via 4bcec5ffb6b84ccc0e3b6598567d1e0b67b95976 (commit)
via 9e89c7638a259fd103316f2b7f9be539b2cf3dce (commit)
via 1202e2c13c17c4e3013c635f9e4ef8629c9c7b69 (commit)
via eddbaf44858226fafadfad360eba55a79f69f085 (commit)
via 56f14dee951465a712f2fee1d4dcf36d7b87a4b7 (commit)
via ea85b4abd01332057c8a8a50e729baf444baaaca (commit)
via d0111f488addd8cf0f4a7217ce09a91e68cc04cc (commit)
via d93639f9d862eee755aecbde69e37f40543f7109 (commit)
via 48b2af87d906f6280ab477950362e76f73b23482 (commit)
via c271fcc053b0ff9b2e7273e992591549258c18b7 (commit)
via 03f679c989087d9d4a5e403bf37b2c99ad7c5ecd (commit)
via dd519159e091b7cc977b47692a45800157ed65ea (commit)
via 103b3ba8ad4891afa8f808fd9b1187983737a0b6 (commit)
via 567c9411dc8574817c4155ac1215a2a8f08fd192 (commit)
via 9c4d0cadf4adc802cc41a2610dc2c30b25aad728 (commit)
via a74802e8424747421511511f78a344de22105c1d (commit)
via fbe90b8b726e59ec1cddd5338850c7f744340759 (commit)
via bb4cc9928f968cd9e72503116a83c1fbe8f07361 (commit)
via fa862e7d1208c8cd0526b9512b9a1979366252ba (commit)
via 9cabc799f2bf9a3579dae7f1f5d5467c8bb1aa40 (commit)
via 75ff883128aa4a2e4bd003342a2d84c216418e00 (commit)
via 3e173f46ee0d807f752ffb88d908c7ca601d0c3a (commit)
via 548aac884cccb2c18420f3a14d1736a904c096c6 (commit)
via e85922d37b1f4b43db76b41286268af4d8649288 (commit)
via c0a44c70c4a2e0dce3792bbb7f9ca15938128633 (commit)
via 5900df9c41a9ab0837cd25667cf6a371bd7e850f (commit)
via f88766fd3085974acaa0c3e7930f8295374a8467 (commit)
via dbfaad4a2ce1f682267373b323a293e365bc4cc0 (commit)
via fbc79ee7d2f1a79f6388939beccaaf50dde0865e (commit)
via e5d53f4bb27024759f8712af254998c9dfa48f7a (commit)
via 11b5709d5a2e18ed0f83c291ec684a46d94609ff (commit)
via 055622f347c9e5beb43a5145c82a4f4a54e89d4b (commit)
via a9d6dd7cab00acd5379309d94ecb8b088ca3d295 (commit)
via a756c801315d320a855f312da4cad370e52371e3 (commit)
via e202628c6950839aaa6d318ea7a08b4f596499c0 (commit)
via 776afd136b2675b928605368199af7dc130607d1 (commit)
via 7b92c815b458b3fb4011fede59b787391bec7058 (commit)
via a7f978695cb84f9be3dd4ca4ff819c85d4477746 (commit)
via 4724623b5fd7748dac9fd291e6661ab94780fbb6 (commit)
via 36601beb98e8f746156ec430cc54ee9f03db2fe7 (commit)
via c257c2e0301a0605bd038e52e4df72734ccd92dc (commit)
via 62c19c0fdfd14b00dc515852731211f951c75201 (commit)
via 39b898c33b05f9936f9c822dfd0f90bfa2089369 (commit)
via 2d0b7b1cbf42fd7a92e09b8b3d37752829ce65b3 (commit)
via f6bd9f9c413e93070c2eddc6d84d6e77dd789454 (commit)
via 045edb53054c1e4acd985430f6b21b605348e959 (commit)
via ed3ab3da850807c3df2a6fc991702086bc65c008 (commit)
via c983a484207a8af30fbe90dcf2b2b4078ae8ff09 (commit)
via 1c1308a191d8e151abb28acbb22db033764acb67 (commit)
via 107995cf3a709e71baab30a82eafa6d7850750fc (commit)
via abb524e9809b09307c95c82a09d0d57bea652401 (commit)
via 6ebbb5d27d5a52d5f5e5caf89d3514a5135f07c7 (commit)
via 24347dec2bc47e3112a2b8103a8570ffc4a8ade5 (commit)
via b98523c1260637cb33436964dc18e9763622a242 (commit)
via 3b53272a6f77fa03e34f298ec06ec31de7cd32e1 (commit)
via c9f943875b76aba6204609dcd70eac4a5fb60b4e (commit)
via 3f6b00419fb5d447a4e2e84be58397632a716abe (commit)
via afa40133222b39078a914b0bf5780be013b8b992 (commit)
via b6c08e3ac581afd21e033808bdcf7c96f83f0ef4 (commit)
via c2d13346d9c16ba018a4f154c6862c3f9b208a82 (commit)
via c8fe7f2e869938bee1b3283c55c99f9487b6995b (commit)
via b5ae5d8fe31c721fe63f6cfa7ef880cf27ffbb06 (commit)
via bdb2771f51482995116460fb6594ba69b13e725e (commit)
via 8684a411d7718a71ad9fb616f56b26436c4f03e5 (commit)
via 555c57669226bca31e961412fde174f04db876ab (commit)
via 04f9d8ebe54788a4f4f3aa7be7d50347a26565c0 (commit)
via 90e30a79b4fdfe52a70fbc72f9f84a8dd1968506 (commit)
via bb52e879ee7b68c162b5ad36648b9f2c0fc18f19 (commit)
via d125d7da8b990b238993f59353e8a11832785706 (commit)
via 3b8892835a95f7b17f93f5f3ca36c14da1a82363 (commit)
via ff013364643f9bfa736b2d23fec39ac35872d6ad (commit)
via c5ba45e293726f7642f6e081de16bc3b2daff98b (commit)
via 280742f34c2471024aebdf2736edf5570f1f3da2 (commit)
via 79e99b7285b93325862f80746d3a4d1b5938ca4d (commit)
via 5a7953933a49a0ddd4ee1feaddc908cd2285522d (commit)
via 064dd51b725493270dec9f1cd2c2b7164135ee6b (commit)
via d2b5da0a8146343bd6f72f3a145fc90be7888d18 (commit)
via 56b495e4bd8aeffed8642bfb6bb4802ba39a6277 (commit)
via 429c88cfe0483e0c67f4625481995c2510485792 (commit)
via ffd2c98603eb6db553d4cf18d2fd7ac29a62080f (commit)
via 16b39f3c2422a324963b67e136b07619209f869e (commit)
via d0fc36b1522db78541bbcc560f5f05cbefd4c5bc (commit)
via 5ba4c6e1f349f890200322b2e2d2ff0e5b179944 (commit)
via fceb2a853bf88760d5d5dec2a3e6f169799415ea (commit)
via 30d7686cb6e2fa64866c983e0cfb7b8fabedc7a2 (commit)
via 04aa12f01ec871e625fdb8ee1a07c387ab0a8f2a (commit)
via 4d472746171f67ac492234ef46a3bba43f55d5de (commit)
via cf00216570a36d2e3e688b197deea781bfbe7d8d (commit)
via e4b2c2633ebb3859286e9a4c19e97e17bcac41b3 (commit)
via 54c6617096e184520c918d308ccb31eb422046ec (commit)
via 883ef5b0d7466d080916f98af4ea6a94258ca655 (commit)
via 82bb5bc1cd3385d1bd0362c10308afb04a0e6914 (commit)
via b995540a1bd00fab2ca883c965edc954080be84c (commit)
via 38edb7e80589f08524b2753a7f29f1b6570ef4d6 (commit)
via 6f771b28eea25c693fe93a0e2379af924464a562 (commit)
via 389ceb4af859b59d18db14ef25a2bd3c2dd3ddd7 (commit)
via d18c04987a47c89aa3038d2bb0e99aa40b2f4e51 (commit)
via dad96d1c2aac267f2a11b9bdb7c83ce33c1a3f34 (commit)
via dbdf5296c3e98beb234ea1a161b004bed5e17a8a (commit)
via 8f5187d167219ac263ef940eb33923ef8a86e87b (commit)
via 6b3f5f252791b1c4b92589deaa3810e5e1eb867f (commit)
via a72ef46cca91d1718b2830e9d827040e6742d643 (commit)
via a11e3b2165821e9ca039196b12d70025845a5d22 (commit)
via b0d241f85f89f1d352f9b7a521b24d80107ffc20 (commit)
via fcbe40140a5e21ecf3ff078206afe1d68e945462 (commit)
via 07dd87ca1a14058c926497ec1c6ac61ffd3a41f2 (commit)
via 4085d32d3df744aaa7fe333032f978edbfc293c3 (commit)
via 462e2807bed92a21e8e2c070e49bab118323de6b (commit)
via 89f1ae8c2f2bb8829ae249414e9d8464b74b54be (commit)
via a9d3e3d62464b76c467c5cd8c0c229a75b5df5fc (commit)
via fb9060e5dccc2bd43ede4da3e0fe0733aa9cc6fa (commit)
via 0169ac7327f43e82979102b0fbd87bf004dece16 (commit)
via 464cada643779ceff4b3886aa5ccfec6605e2e92 (commit)
via 73b4d32109e8103c50f73dcd0c171930c3d9b322 (commit)
via cb1ca47492ba2c493f88a45725f91d1b53251023 (commit)
via 9d56a4097505b05e6a8e69ebb967a30416e39b41 (commit)
via 324be1acbdb7d432f4204d9cd6ed28e71a4fe7cf (commit)
via 6ca1f0da6b1e7dc488e5ff57b9ef041f4322ab67 (commit)
via 766508d03d1304dc4d66b814a7a21d160b3404c2 (commit)
via a4789bb27d209a54652bc2d7c6bf0992613c18df (commit)
via bc4fc1865977c94d4ad6eddf0cf96a7881a71536 (commit)
via f43af5ae2a1a904d1fa091d227f6d5cb1c580fa3 (commit)
via 5339fd0dc7b0a00f2e80754e7a1aed146440cff9 (commit)
via ccc2fb769ced7cef416b55d9074591022b8a673b (commit)
via 69360a12e78e7567f88dcd3079c9a56be534ea7d (commit)
via 38fdfa7720fd8e4c07aeb6aad4b7ac492d364b88 (commit)
via 71898b1a575fdefa01c657290b99ce7ba4693054 (commit)
via 920106980b5a66212397f5f699f2aac9e6d69cbc (commit)
via c430386cb0a163ffabc477d864635b4a21f2ef59 (commit)
via 900180e27ecac721a7ea7a5c3cff62230082a391 (commit)
via 6ec73d5de3e72a8d6adbb7e3bc353b68584b344f (commit)
via d6d42bcdfcc80b450b89937ffd5ba39b071b3f37 (commit)
via 0c0161c6e555e2c8b33b3291043727bc07324dc2 (commit)
via 6dc12fe4b4f751a8692bb2f577d178de458cbe31 (commit)
via c8f1422e69ad8cce8538c4f25a92ff225b4c2bb8 (commit)
via e07c5a1459d94b1c7c24debdbc05f23a5c79b4f0 (commit)
via ce39e53bff7967573672e7172ec9d18b8bac4bab (commit)
via f161f37c16c1ce2b84ca467b6f6302e0d7d7a462 (commit)
via 9e60ca625be2c1875e65e360fd2b06c9ee8c4a57 (commit)
via d1f64edacac52f7016b36381d93bec0275dde2cb (commit)
via f6d6f8d947ffe15b7aa0d1fe73adea1a1963f774 (commit)
via 7ac5487d208f8b4cb9c3e2955bd7d1f03b2ad942 (commit)
via d7dc63b8a899e4e4c1be30e6f24ad113e02c6582 (commit)
via bd0a50e9b37f2e8c0030e905ef1889729df3ad5c (commit)
via 51146871aa0303636eff16193b66f61c80b9b7f8 (commit)
via 2ec4a397f113081c96807b3138549bd4bf1e2edf (commit)
via 5b301ea5a3aa71a5f82f0924713a7360c5f32f35 (commit)
via d121f511463ae2c469ec5ddeac1eb1ad60ca4cd9 (commit)
via 18eda2228413b1bd5764ad23dd5ab45a2d9c1809 (commit)
via eed7f24a8ee8ac1647a2998e38ce4766e48adb72 (commit)
via 19ba70c7cc3da462c70e8c4f74b321b8daad0100 (commit)
via ecd7dbe0f231c04d19eba3cd02ff513a23a9003a (commit)
via bcf5e635365f60d79e5b7d28b9280482fbe1529a (commit)
via 7626330333e85f0f0e3d63530681b3708932e7a5 (commit)
via f472a687b70982f906bcc96900fdbc3ee8d36ff6 (commit)
via aa0318899f62fc3069572bb18bc6dd071bb8448d (commit)
via 5758984e174f25954af41dcf07979e2dfca12763 (commit)
via 11fc8fbb59146320ba5ce8a86d08f937878250b3 (commit)
via 5f4eb187db9895ceb2297711b6eef6fdcc520625 (commit)
via 0255bfb9925710dc04d1bf88ad138c33ed7e7530 (commit)
via 4fea1ab53d06b5deaa87def247818f67839a9c9e (commit)
via 719a5941f529e8139cc2cf970d8903adf0741043 (commit)
via 6ab057e9726a30b87909a09026c797e99cd935d7 (commit)
via 0e8c2c9e572204db1b0586d6da94891168fd3a48 (commit)
via ac91cd885b563c71193cca2a33d6d11aa4a25927 (commit)
via 73506bcbd64a043f7e66c193a15b2d09a0a47bf0 (commit)
via aacfc3fee9cbf5a2b7945c165cace1af81877f5f (commit)
via 65bbecec757696dbde3faa5b019a574feb002ab4 (commit)
via 2ea7c1a1fbb55f0f2ba0e42fca3b4e237c5edd16 (commit)
via 70cccd62f3b6f433cb8790f3bc0a2455b1d11b6f (commit)
via e98da500d7b02e11347431a74f2efce5a7d622aa (commit)
via 5b6f56a3edae024549cf37438ef2000347befdd6 (commit)
via 7713d4f0d29ad4b19bcb67e2702a6c153cc115b5 (commit)
via ea1476c0b43a024db7ccdfabd23904a8d1ddf7ab (commit)
via f8cecc0e0625c40be6a44f24b8bcd0420e19946b (commit)
via c540444d6220133977db22de4c0adc116afd24a6 (commit)
via a915ddb9782f93f69b922cf04110725b46f97d31 (commit)
via fe58afc5c628dec30829fea01a9083d401c1e8de (commit)
via 5b2b5a2c16102329bdbc7d1421758dcd206fa45f (commit)
via 66d14dd2dff194e69a12db0c89176c399ac5ce0b (commit)
via 30f55bf6510a4463d511a90c0b7deaeb046f80f2 (commit)
via 61f4da5053c6a79fbc162fb16f195cdf8f94df64 (commit)
via f55b90a5fa11ee55e33e49f25f9f5e4858965e88 (commit)
via cb086eeafa84b7cbc30ef2da3e3f8bae3e58a473 (commit)
via 694ee39256cd2ebad4b1f5b657fd2a2556ad10a5 (commit)
via 863e56e656f11f8bd37ed75564d01640076d0215 (commit)
via 80df2d4fcda75c7b9e8ca0e8d2e4824c13ff2867 (commit)
via 4851408343c11ba763b1c7e4318ef68f1e3393fc (commit)
via 31ae358bc11d43cde0206b534548d142a42f3fcc (commit)
via a4abbe54f677835af7dd7ccc4a9aee1225da06f5 (commit)
via c9b1b85ad2266b1f1971193db1c0ee7f093b7697 (commit)
via af00b01970fa7d8da6d40d8621f4a1a58dcdf297 (commit)
via 966c129cc3c538841421f1e554167d33ef9bdf25 (commit)
via 45c762f1a150d2ba1f21ca169642ff6eccd0eb3a (commit)
via 2a349b194cf4f946cbd60d2295a278a6e0f3c0fa (commit)
via a49f5b952b7d8d93b0ae275c3f3f8fe95ae34939 (commit)
via 673496a27df7e7fbf98ccbdb26171b7c4618bfc2 (commit)
via fddc9d07afa5e4c26f701672c9503f353b27fe69 (commit)
via fcc5fed77df391264c67db28bba28dc48aa5021b (commit)
via 543e6f2de2a1d9193b2e5c24f0f536594e9050bf (commit)
via 003ca8597c8d0eb558b1819dbee203fda346ba77 (commit)
via 83e953bee2f7762739880a4ef01ca3246230af31 (commit)
via 38742cb1561683410ab0f61388dbccd57e9f0f3c (commit)
via 955d8f31227b9429548de4da6fbe40350509cf04 (commit)
via eadab44ef359700ba6ca50a2dc93b7bef2feae39 (commit)
via 451fd576615b47baa616d49ccaccedfae5595a76 (commit)
via a265a99016e8fe7943e1b6e8d4f2a69c0b5fb391 (commit)
via 6129c3537cc81af4561269d5652321ac2250ab93 (commit)
via 097a376ed65be53dbfe4cd1f2f46df288fb9d6ae (commit)
via a0f1ca23cd394e0d94845ecc08c35e56bf313b01 (commit)
via 466339143a9161f821fa63e5545ee9cc72d8e1a6 (commit)
via fe5b8226c1d3237ed8feb924ad789cbdbee0287e (commit)
via e494212d02fa6efcb437f966708992de8b02cb74 (commit)
via 2217a6591fef2fbd4026d6c2c2384a026044fc0c (commit)
via 7de090b9c37f4f68a4242127af8bb4da9eca9eb8 (commit)
via e7b760a474b50989ba32c3766580508a3283148d (commit)
via 7f74352cccef3e5b8ed292930746e8b8c723f7af (commit)
via ecb4b75c7b226482e5f2592d14f8534f6f9e03a4 (commit)
via 8d191a6771288e0cf71d48e93091f355719a3089 (commit)
via e50e1bf6c169c72fb084deb42e739b7c9ebfb94e (commit)
via be83d3bfd516ff84824a44ff8d2e0ad68bc0c613 (commit)
via d3781127f6d22976c761003fba52df8c9c30436d (commit)
via b0d21f92b8fa563a1cba116a16109dc4c7164dc1 (commit)
via 48aa1e839cf475521de07bdc0d8c870bb29f335e (commit)
via 5af17f43de48dd58fe87b85d974a554ac0136b83 (commit)
via 427039d6ab89abc66c53479f27e5c15d677b1431 (commit)
via c8739afa54bafeffac2cad157020242b8acb3b28 (commit)
via 82708a4ff9185158d779cc194e94fc3aa1c3bd09 (commit)
via 326b046810b3d77997fe95f2eab21b591a709ac7 (commit)
via d3877a19035f09caa0b457cbe01fe28edacdeddd (commit)
via e40cec7b4d5a0fc473bb5d4abfb6a93a712e0cb4 (commit)
via ebd9ca8fd9d7a39e8f566644e62b68234e566155 (commit)
via 61dd18377938c6f2e5d026d432062db6773abf65 (commit)
via ee4edb317a513bcc1230a4edb8edcd91b8a796ca (commit)
via 77ceb4384e75a1e613b1fbf8766745dbd95221a9 (commit)
via aa39c499c1f3b58685987252e7e5dc1e41a4b915 (commit)
via 01a765b995b55195fc2fdffe3017e7280018dae5 (commit)
via 17f7af0d8a42a0a67a2aade5bc269533efeb840a (commit)
via d7aff6bf2f4d45365305117fc19648b41e01c8ca (commit)
via b1a417fa43cc2d467fd67d79ee7749fddaf92011 (commit)
via 001717c6baf2ed1929f9a585408ae66f26eed014 (commit)
via bf67156c7798685263abd1fc8c60669a78bcec2d (commit)
via 6292462f7e27fe5970d7f46b0cb3b360d21d830c (commit)
via cfb3f053d87cd3d406c84acd548290b3ca77492d (commit)
via 602204a5a15d7e602381a885f366acf9ba10b0d4 (commit)
via 75e6307ae2766d7eb46875d95fb13b5ef2b11f10 (commit)
via c91c89ac62e179ac0a1568cd835f55990de3fd19 (commit)
via c356ad2e53fe54c417b00ab7b6d806a31fa3ac5f (commit)
via a5fec871b80f8ef0bddc2e708235581bde00533f (commit)
via 95b8b13fa13f48b524aa46691e96bc21062559d6 (commit)
via a2bc5862342ad6063a80809c284fe395cf3e6531 (commit)
via d20389d04053862712eae4e1005ce38faf813f20 (commit)
via 0a3535e441c8b183c0d20f1cc8236ad191e6a4ca (commit)
via 47ed2b145a1194d54b7d267d5addf74032f8acc3 (commit)
via bbd3fd0f48b0f20174ad7fb75ff1ba75c4bd2731 (commit)
via c70cfef4856f2df56349d9f4798f63e32633f6d2 (commit)
via 1293666c78916a823abb78d2cc4069667ed274d1 (commit)
via bb45b29c9fc7c350b43389d6eec8dbc8613d1d3e (commit)
via b31b7eff36397f30f00d7c31805bf7634395dfa8 (commit)
via 811f0ab9da1b1e5029c9ec9d6d2fc8e5ddc1e03e (commit)
via 082faedd5757d9a4e6c63c52fca1aa266cb0b183 (commit)
via 8c5e01a82168993347e060a7079ec5014106e57e (commit)
via c5a39e131286add03835505d9cf7d82cd9a23efc (commit)
via 7be2f0a4db2e3e20ee28429fddea1dea11592eb7 (commit)
via 578f4a5e28f06ff84d75502d5484d995904947dd (commit)
via 9ba95c664865ceb3fc551dbb0d8cc120c8aa2ba2 (commit)
via f9593f5d5a28d42d01251f009606aab5cc1e6997 (commit)
via a9ade7fc8f8db95124c33bf475a59fd522914e49 (commit)
via 4c8164b131661552f3bc8e74806d4cbbe1cda1d1 (commit)
via f15c13fb80fd6512f31df035555392494c6dfbd2 (commit)
via da724029ca02951b51d63333394bac0d62dbb0a2 (commit)
via eb872b35100ce2b602064017cd2de8ab517ffb76 (commit)
via d20fafd80c35a7c5ad85f097556e79b9d73dbf8a (commit)
via 11401abc34828a747f7dd716a1645b143efaf2f8 (commit)
via 988a731a52ff69d86cd5669a98994662d705fb85 (commit)
via 3f6415fc870ed25719f6c71f27ab4f2154358f9c (commit)
via 9b1d96cccb6572d7ebdeed0ac9c59fa3059a3db3 (commit)
via bc105b59161101bb6cf757e5f863bb7a3c97e6cc (commit)
via 05a6a866c9ab6eb451ba8d106c78726b64c5c5c2 (commit)
via 605afe96c79c356df9488f2e75c05efc9ceddedc (commit)
via 4d266d80beb7e0d8b1f45a4a4bc7cc39a1657486 (commit)
via 1c0df5ba07b95d2e2b1c678675be16a2a1059593 (commit)
via 28ba8bd71ae4d100cb250fd8d99d80a17a6323a2 (commit)
via 0aef7c479e7d127d0c219d6b3a5a4d0f3f701161 (commit)
via 185cb45b7ab1a164a7bb0a989c582a74ce50f2f3 (commit)
via 047380ffbbf6e876282cc269ef16d8d6b168d6d2 (commit)
via 0db8f9c91a54b2454a9210741da887280981a26a (commit)
via eaff1f5816bd2ed57a011f658e0b2dd762800079 (commit)
via 2a29f6b8725bf106dce2f45f2705bac555022c5d (commit)
via 9ff58bb76e632a8f7a28a4f86f94dd1d77690796 (commit)
via a9eadb451bea1a4813a7b3942a7ca2eea97618a5 (commit)
via ddaaee4b050468048409170f1bb56530f33567d6 (commit)
via 0a78488462cb3d7998f8af45fc830c18648dd45e (commit)
via 1aa4924e5a9560ceca73c0e777156309e125f5bb (commit)
via 540d2a66787b2ff57e20a2c99a84d2875dfbcc54 (commit)
via 1cde47ac78e581f185afe2b7e8523c34bcec9a38 (commit)
via 6d675cbff066b5ee3f1c3bde7aab7dfd0ad1482c (commit)
via 72b239285aa03f9afa5685a4665451d89a941143 (commit)
via 1b43a3adbb58860353dba59f48b23e2efc936041 (commit)
via ad2bcdd85d6e40cbfd9eb99acf817b626083962c (commit)
via 9e3c7d57f3144b6992d8967af5a4583aa3d55c62 (commit)
via 809c35dc7dfc5e9fd0a498928f6aaff89049757c (commit)
via 9e363eaa09c4ab1945ba91a117cca7ea892ea78a (commit)
via 30219fe1326f00e468fd05bcf80e3ac47b5f4e3c (commit)
via 6e12d95c6a1fac2d4a182e58072934d2ab4b237e (commit)
via 78785509428db0da450a2be7e89a342d1200cf0a (commit)
via 8e98da42d48e5e847cd12debab6738c585db4740 (commit)
via f10604eb75f876d77af25b30b0709d5c554a6b26 (commit)
via d9640f6f30078a6b86d7b096ad0563bf45ffeb0f (commit)
via 5c4967145b426d190fb6086307ade06e8ad093e1 (commit)
via 4a20cc2eef19a7ada8e2c594724ca44e40be5cf8 (commit)
via d72e05e550f443209afbfd0b173d0a1e96dff749 (commit)
via e9647bb3c689941916508a663b72df63be7d3963 (commit)
via b86f7c330eba5b252adbea0594c2225fbc31e2e2 (commit)
via ecd4db4c298f4f1aae038bc0b42837478fe9910b (commit)
via 263d38ef6a1fe8bce1a229acc1cc14144d180c0a (commit)
via 03a1f23dbeec538ee042f3f28203ea8cb3b29bfd (commit)
via 67167017ac51545493ed1adb6c7c33d3c19501c5 (commit)
via 716ae36b91bc71f72098b8c831c5e86c845a99bc (commit)
via e6532b8bb0bca5d9b2a64c721f99063371055add (commit)
via 56596cd21e95779796a1e69dcd759ce77c6f3ce0 (commit)
via 64b4c44c2d67130c304cf720bff62723d2fc2293 (commit)
via c41cda65e06b6776c7111e65b04c72d5bc84c54f (commit)
via 17e78fa1bb1227340aa9815e91ed5c50d174425d (commit)
via 2da5a3de561484f902aa7fb8475551217b9535b9 (commit)
via db3d9fdfd8da2d9ced567ca43d9be8d9885e0cff (commit)
via 6bb658482335f1904c3b210ef4380719170677eb (commit)
via ac686701c48b92a98507e1533ccd4e3c851bf3ec (commit)
via 2858b2098a10a8cc2d34bf87463ace0629d3670e (commit)
via f328341c1cfed8e5ca0479619e9d12d3b4907c86 (commit)
via 373435e91924553439f4881a1fbf71aacd5fb4a1 (commit)
via 5a3853394c09ef698795c3b7fe98661e752e0426 (commit)
via 308d0c39eade6985c4f61feb215255cf2ec92abf (commit)
via 0ad08d16ff5100ec01865bf2814481960c79358e (commit)
via 63f7b1ab7c81110633ed2e381e5fdfe05e143d72 (commit)
via 61203697e8fc714835ef504a7e3eda925dfcda42 (commit)
via 5da80cb570399e277dd02d6d45bc4efb3f949d61 (commit)
via 5d11f8513af16aca9eb298db3226791f1f4e584a (commit)
via 871e4c22effffd79ff1d0a44d06a102b27b24ac7 (commit)
via cb4049864f51f5dae26d7e985568e06e3ee1f9bc (commit)
via 66a1a1ac3cec4b9b5f453370c85546ddafb1a9a4 (commit)
via 18828275414b6a7fa529282e2e53d3ffb2b55082 (commit)
via e8580780dacffa82089d030c5b9950eb31c693f6 (commit)
via 959f4cebed4f1182f4c9f4c3058364e83d553344 (commit)
via ef979231a2925c3cb6c079b181c6747005d72baa (commit)
via 5eaa333fe39ddc02af78ffceba8173497e1352c3 (commit)
via 82f947e1bc6978f7bde731955b96dc5c68840a6f (commit)
via 4f23a482e885f3fe3dd104fa43638352f9ff3e7b (commit)
via 88aa930fd6189c121c784b2175aab519d72ed5ab (commit)
via 46232cfc39be398a04cfbc4e6fe2e75b41d8eb7c (commit)
via a5fa9a0f01113a92f6136f6dfc1716f877cbf07e (commit)
via 90f98423f675dec3fc8335f17bbf761662536cce (commit)
via cb2af9730fdf9f0a9488c1197f29dec90b31c19d (commit)
via bf8c9216523617f5834b4acf9f876587995f5078 (commit)
via 013e14c7d9c84f00510aa8f7a99955aa47c018ad (commit)
via 4b7786c01f7cc727ae9e36b3808bf727022e562e (commit)
via 82caa5734136ca29981a5107c9b0e4f644a573cf (commit)
via 6b45467a71b534657a435dbb455f0ec3da99d16a (commit)
via 170556d806125d1556fa570070b9f876c97df30c (commit)
via 42b7d6a5c8f04659a4463d932b95e5305d6de3f7 (commit)
via b82c78512d63424b805e1ab15e6b781e4d923dbd (commit)
via f5ea0617661d39ecd576f3d997cf20009ac05048 (commit)
via 0ad8fea24b58d05e8c57337c7b77f1b76be12015 (commit)
via c6aec4cdb46dd8825248437bc9c45d6a54b81121 (commit)
via 287e17100b60fbfceacc7337cd6ac9bab36185be (commit)
via deadd3b30b20cad6964bebf38dba417c42b78289 (commit)
via 3d457417da0ef05512383a9ca4e2448882339344 (commit)
via 94ca32cb24762fc41c23631d7183c35f643545e7 (commit)
via 58b7b149b8b3afbdbd8c960fc716f4e85f1803ee (commit)
via 3071211d2c537150a691120b0a5ce2b18d010239 (commit)
via 33ff861b4cd62e8d2e6063452cc14e980d35bbaf (commit)
via b341b3ca37749a29e1f1f286d103298d3c3fa568 (commit)
via 4a8fa93781cce0dbd33f55e604d87db511fb5903 (commit)
via 78e535f5a0e65658f7de0c3b777f9e0b296ad53e (commit)
via 53cde03994472c09094721f94307f46599c0d1d5 (commit)
via a092343db9338c7e6acfb238d8c432291ad31e58 (commit)
via f364574080cd8f9d1a2acb27ddb3318157393ebc (commit)
via efd87c8cddb380d78a0cef4ee7fcc91559f7352b (commit)
via 3007ba9ef2cffdf4903b43a14ccf52a0c1d3ec65 (commit)
via afc721fe5f2964c9d03bdf0738852556c69ad41b (commit)
via 6cba1c1241e8c23779d0dc29374ce8420fadc2c5 (commit)
via 76f823d42af55ce3f30a0d741fc9297c211d8b38 (commit)
via ab457a83d9ab58e722b84a7f99bd83dd49d75483 (commit)
via b9aed5e8c47dd0e44981e6a7c40b5b3c8e403e53 (commit)
via f5a196c2aaa02ab50f8bb50f9e23cdff7e945559 (commit)
via 158f5113fe1effcf74cd0e09b5b072508e8462ed (commit)
via 3d7ea0c377844f75053cf095f1e982a5bb6c32d7 (commit)
via d1c5effaacd00d4ae5c97a48f820c5e281964cdb (commit)
via caf03348be996e4fe58efa70927b119b4fdf3d84 (commit)
via f45a95082d8db85dff8d7b0bf0e0817e7175103e (commit)
via 2c40ed8bbbab2e095be163a6f144eb45be465f80 (commit)
via e3f86da049a7af0ddb0369db2e9db3fa0903d3c5 (commit)
via 0e7c11723dcde11c0d9d789d072801d383e9ec83 (commit)
via 9bbda62a5e88e9f1e4c8aeb7924308e37ef65a7c (commit)
via 0d1406f71c9cd9439017b56957fa9d37ba7729cb (commit)
via 78c0ce22b39230347bc652f7903aac57d2b4c577 (commit)
via ed110c7f595e6a761d9fdbebdcc552e39edbddb5 (commit)
via ea6a608404e2b8d78aee06d19cc173f8e9458142 (commit)
via 32d9c527a98f1e417d21799d982361892ca87c6c (commit)
via 47bca52baed667a6788450e1a0912c7a06363c8c (commit)
via 00de2fb5e636d527a7d1a7bf97a6e4552d84b1f0 (commit)
via 5fbff1bd2b79b417955e53dba3e3788ff32ebd6a (commit)
via df25eedae7f4e65ed0a8ecd2f95722061335d498 (commit)
via e16deb4665c50c5232cec96ae2bd69348610c926 (commit)
via ddbf6fe0f244d0c808408e94b502bc462c5d2f41 (commit)
via 03a80ffa461b3881b875b4a9bfe8d5023c53a59c (commit)
via 943206cea937e858ac301eec764abd1069350783 (commit)
via 548926e319f5ed75099667a063f5d8862aeeec3c (commit)
via ae0979752fd9062a5b184ec855c2f9eab1363aba (commit)
via 4e8a429913d5ecf888ec95d5d337f4ebc5c1976a (commit)
via 4684af80eb76977c7bc0a62057fc324b8951381e (commit)
via 721278917f81b75862a813a9acd7b45edc636737 (commit)
via 4d3cf5eb83569ee90646cc070642d0075cf488cb (commit)
via 5e05ed3052388f3bdd87e9d9e8f938b101014b98 (commit)
via 7ee50a91c4e5d294e92e0472e3d05485fc63dd12 (commit)
via 7074f97e8f9a6cc7b288f161fd5fea221c45f67a (commit)
via cb79b6fae1cd7c348eb2c2671f1a44e857e7a82d (commit)
via cccb06b7d9afb36217b873ae007f23eb5bffd0e1 (commit)
via b614d6fb5ce0950bd212f5b6b01daa634fa5bb22 (commit)
via e657a6ace7b2ef3935f34ba87b85f8c0989eb9e4 (commit)
via 8080602fae702171f2f08301e3058956cffed881 (commit)
via d86d66e6d7c9a94356e76164e7675b3de448aeee (commit)
via 1d74a54df8e93bc300e575bf1e3732d14c1432ec (commit)
via ceeb013ba37445e7728d82a850c69d8b2c3377b0 (commit)
via ec3e1e87637f0bb63c942758b1f5af4eb50708b7 (commit)
via e475cf69a701cb84b7261b3a73d67f8f8dbaf6c4 (commit)
via 0b23de1d0bce5d32486fb6dc2de411047c325dec (commit)
via 5883a489bd72fd9a7faa485d770cc74b99457e7f (commit)
via ee5cc69d9e5320e3d9c420c22b54d0ebae255268 (commit)
via 85c2888f2fe2c9985ce89fe22fa518b91976398b (commit)
via d5a8a75cf9e5cd88b5e7471eb7be8255cd2da28d (commit)
via 88b907df1f580e6241f2af12870df5c769c539fa (commit)
via 13ef8a1a140dd572deeb4f971109e2fe4a8363ac (commit)
via c653a1bd0fa764039207f5ed1375acaa4170e8e5 (commit)
via 8b51e48caf77ca64a06e1a3206445b1fe0b33261 (commit)
via f2ae6da3091138750b4bf51475c781e3d7ddc5f1 (commit)
via d69234b667207783c40d1f4b021ac50682fe2341 (commit)
via 6afe3a0422647e7e6973857bed9c657366f10674 (commit)
via 848e9d9759c84f822a6d866477016b5a95415dfc (commit)
via 6b562b9f7c7df89081cd3e9c98625350a19cbbb7 (commit)
via ee0782b63e4972a3388db5367f7b79a3a1ba2984 (commit)
via 49ac4659f15c443e483922bf9c4f2de982bae25d (commit)
via fdf120001f60678e573b8c398a45a74256bf3374 (commit)
via bd7a3ac98177573263950303d4b2ea7400781d0f (commit)
via bcb041edfbe5af5bae2818c2415b008772b97eee (commit)
via 937690e47bc87c365e82467ab5c425eb255aa989 (commit)
via 97d02ec04e4c0266ec3df0899ff5b6e2f0177746 (commit)
via d17500bf67cd807502793f1eb5525ed238b7e577 (commit)
via 74fc8c25fff06acbe992a34f78f0756d06c78673 (commit)
via dc8a5a5b1022bb8624773ca9adda6025e683a3a9 (commit)
via 95f2ccbb56b3dcb8f77e3def5beaade65a8891b2 (commit)
via 7af06f97cddc0ee6734a5302096ebad4539d1d59 (commit)
via 3602cd9d13d6add722665b25bea2ed5ba56fa28d (commit)
via a2943fb1b3f1416efcf0d3fc3b46293e34830ebf (commit)
via 37c1b4a1d2fb5cfd630d43b016e514ce2d58fa59 (commit)
via 52456f2f08fd688705f485540b5a65d178cbb810 (commit)
via d046d809853a5da2c8546cb49758d818a657d28c (commit)
via 2c421b58e810028b303d328e4e2f5b74ea124839 (commit)
via a8c8f392aa951115313dc35159896fe506742079 (commit)
via 35b589adb440f744d15a077855f7882df5b9e8c9 (commit)
via 32f93f7158f1291d24960eedf199e6a5e9a9fc98 (commit)
via e2ede2f46b3136296ae06282f686c04b9f751939 (commit)
via 1639806c250603b964f79a528285d3c3d20853cd (commit)
via a19c58f52726ab4f47f920b777209dee44c1e3b2 (commit)
via 94cf54362f836eed349e87b153fe853d561e09bb (commit)
via 84e105c7ffbda055f37bfa99324aae5c2189ff9c (commit)
via 4cb88939cad52712bbfd4ae1903de9bb51465499 (commit)
via 4164bd33ee8f465820a6d22aad0d2a63db7765b7 (commit)
via 80a2fbfd0db2b19beb0e18b3b3dc166ccdae76f6 (commit)
via 47c87e9c2cb3239284a64f9e029e52693106dccd (commit)
via ea2af1351e60e715bb70fb1b021c87e47add5d24 (commit)
via 45baff8527758f9c3610f75308d9262452c713b0 (commit)
via 789a59358ffa4f403bf84016f703f5cedafa22ab (commit)
via e7a62e622c23f787ee54a42866684fcdc3adaf4a (commit)
via 99a142d0e0cb43bff136c1143cca58187210b74c (commit)
via 2080e0316a339fa3cadea00e10b1ec4bc322ada0 (commit)
via 6868aeaa6c5e5404a783c65dd6e7dad30a843305 (commit)
via b0d52a6010210282915fa8defa027b6cf1f0e36b (commit)
via 81e5f0caa91e4be3b3e97d97ea6f56d92333bc16 (commit)
via 8d8a836667898774e1472eb94ea83175fbd76e52 (commit)
via f9f0ce117a1905e633489bf476bd22c673aff323 (commit)
via 5b11239e580e3a283e0f120da4119f9dd7604257 (commit)
via 0d483e15ebb9339797ba6a29dc96edb8cba2ec07 (commit)
via da9546a3569e096c68bdc9c5dda7c317cd0a77bf (commit)
via 058f2664b387c531920c6068b29cee34fc966a05 (commit)
via 1b9ab32f79fe18b25a378808192cc2f8f5f6c718 (commit)
via 0dc861f4c32cfd154904819d5c4b8dff71fdc47c (commit)
via 49752533bcb8680e8633161230b89e990efcee58 (commit)
via c911317eb2d13c3221b468be0f21118dba140d19 (commit)
via b93b50507fc8e49e5f9e204f86f1f06d059abcb0 (commit)
via 59902e100c9a9249d633fd084f9d31e42c4a62d5 (commit)
via cd78ecd9fb6d0c59abe3ffab4418add6a7f1173f (commit)
via ef67582080db054694be522689b892c967bdba29 (commit)
via 37c946ce22e15183167033b147ac20c1c94c33fc (commit)
via b8cc8c81923195cfd626ad5bb9683cdb7b27332a (commit)
via 2ee63204725537842cc027a25d31cf6ba071d3e8 (commit)
via f635adf0a3eb754afc8bd171d8a7a274db5e31c8 (commit)
via b8c42f3562a1d73a425118c1e3fcab3589c44228 (commit)
via 08796619c96ac087417929a43c1c1c71a4aa76f0 (commit)
via e867d09129ec735157e7d035eeefa508e05e5416 (commit)
via c175c9c06034b4118e0dfdbccd532c2ebd4ba7e8 (commit)
via 067474bf67041f96b79d58c78ebec6e613ebfe97 (commit)
via e8241ea5a4adea1b42a60ee7f2c5cfb87301734c (commit)
via a1a68b2c05820950568fadc18e0496d57ff6cd5c (commit)
via 0abbd388a70bd75a6460bcc7599bb8383fb6575d (commit)
via b4cc4325aaef9d57ecfe39e7cda0cac83773980b (commit)
via 28cd9c1145fd52f2729f1f9f4d28c263ca2cc0d4 (commit)
via 5d3ea6cbda70b4ffeb278fdd8aa3c3a84750e580 (commit)
via d4436ec72742cfa447c7a46b377b134a8e82185d (commit)
via efcff547c56719305bc35186683b450ea759d684 (commit)
via c9739b8fcdb24832f58bcc0c550eb1f63917539f (commit)
via 67b1031ec37365288502d0894cccddec7e12657e (commit)
via d81cf24b9e37773ba9a0d5061c779834ff7d62b9 (commit)
via 9a6b7baf300a6b7e9c9c71709614348b9bdbf029 (commit)
via 745181fc6da942a22525cbe1b952e7ba4b967864 (commit)
via 023d2257c83bac2e06333f2bce2e4b9eda6abbde (commit)
via d22e90b5ef94880183cd652e112399b3efb9bd67 (commit)
via af9d2a8ef57a42c6a00ba0045ad0c8d558d61672 (commit)
via 562aecbe253e09da28891fbdf3d5450ce41e8b9c (commit)
via 9447f34869eee38313cbf11f5052f12141f08472 (commit)
via 196eb4d56de69eaec1bb87ea0c086db4e19d1eba (commit)
via ad80da2f843962bc6a7b05c9c3b624cee48a72eb (commit)
via 470359f0cfef05cf9a5348b4255fd71208b2a6c2 (commit)
via fa538d5bf3b6ac1eb944008ff70aaf8dc3750730 (commit)
via 177c449a612320ca77e8bfe2718e4dfc9ff7b8d4 (commit)
via f439a4c386df14e59ca2c234c8d82d1a7dccafca (commit)
via d66a6761de5dfc6adea4247044bfde7d8db0ecb3 (commit)
via aa74005fd2e1c6332dfad4a5a2f397d40dd4660a (commit)
via c6c8a84bbbd9faf4275e4889f2035d4a2861ddc7 (commit)
via af944b95fca98047398f0a3b43c24caecdee450e (commit)
via 6570e46bd47c20b2e0749518602b1051852773a7 (commit)
via e3a6fbfd30d271144679cb6114434e62b736c501 (commit)
via 311dc7d8227fc9f71f10e2f6ac8584850a469e66 (commit)
via aba3a761d14827363d4c0d43c1d9af554d773b5f (commit)
via 670eb7ae3f4c9a2ed02543d894cb821f2333d122 (commit)
via 92295a5242c1dd0b03f7f57500aa6bc5424e0487 (commit)
via 54928776a98b487264994fa61221e4448b5ece79 (commit)
via c9a7d521f00d4ed6252a67fd593af5959f027e8c (commit)
via 11d920b3d0dcb91ce84835d555df912a7da29d64 (commit)
via 019980ab4aff6cc4f12682e4870f4f4745509301 (commit)
via f0fde33067a3d34dee7638f4212024a5aae735ff (commit)
via 945e0d9a675c38b82b6604cc4f103b5b16147a6a (commit)
via 880c740396b51f86d7d8d818fde919cfd5146ab0 (commit)
via 2e0fd5445e6813e92281061c8b1009aaeff4d3d8 (commit)
via 30ef882f31963e8254a6c37b4afc4dc76822c57d (commit)
via df7eb5eab53ef88f7cfe704aaa72fc1218cb7f95 (commit)
via b8fec5c4dd4b0280dd393c10718f516217435efe (commit)
via 4209099e4baeab29615cc28208f1ca8889ca71ad (commit)
via 1262df829dea239e99206d71aa0281928c5ff426 (commit)
via c790b4dfc1f63128f017f48db730cadc1396126b (commit)
via 93f4485d035d308bf60041f331ce75138ff5d7bb (commit)
via 2528257c4f3621822cdc030d0201453506c5a56b (commit)
via 3dabb15d73f757c4e30ab8dfbfbb84a94eea8a86 (commit)
via 2b16771b9cd34d55fe3916669b53531b29bdf0ce (commit)
via 91a26d94d9a0b70a030d79f7e927610c1e745878 (commit)
via 5ba668a1fb8e174cf1e3100556b3c9580f41fc5b (commit)
via 9acbd355c22f0c46c6146fb13346b790d5281c35 (commit)
via 0457ee07936085e0b0843fa645b9873a01e60b8b (commit)
via 6579042087fdc53e1c6b7fa929449bed3e22637c (commit)
via 6f19208c5954d030c0b9795fe399c4ef1e11c8c4 (commit)
via c95f08240cfb08954c361f36fa0877b0cc3385d7 (commit)
via 453b8ba9fa6ae7f846184aa490e93f326250178b (commit)
via 8854cf0ab6ce6b443bc8ad01d1724e0ad6cadff9 (commit)
via a3af8383ee7ca64f7058698a6753ffa74c4457e9 (commit)
via 89258069451e9b293f9967871d3198593353a92b (commit)
via 80f19c3152c510fe58434bc9a420c289642fba6a (commit)
via 42b606583daa7b0c5702cebf9e45ee8138793cf0 (commit)
via 0e93f4c179df049891567808accf7dab349398fa (commit)
via cf8e3900e4fa31a7bb2b2e917f3b5e032e9a28f3 (commit)
via fb20b8bb0c4ed9445a2a97504fdfd201ff15df38 (commit)
via 0458ade871c5d0c1dfced7970379965a8b9443c5 (commit)
via 079133e3457fc2b6864f977d2970b5dfccad19e5 (commit)
via 07206ec76e2834de35f2e1304a274865f8f8c1a5 (commit)
via 5ae62d711ab9abf8c47de5f8f292841b258ca12e (commit)
via 793de0ce0c34913c63c9929bab75cce51ac843ec (commit)
via fe3d37670b71b33c7f146fce15ba22b09a0dedb4 (commit)
via 79787f23a43c6630ac1dd2c5bba757995012bfbd (commit)
via 66600c431f86b630eeee0d318febf89d866e537e (commit)
via dc80769a1a921e533849b5ccfdb17c24c625a382 (commit)
via 5ebfdae884ecd62aea13d6674341273b6d8e5199 (commit)
via 754a15219eebd73203f6da6de9a29781455897cf (commit)
via 4f85cc85d53701f871e047ce2146da33065ed757 (commit)
via 876da4518e24aac874604630104b2de86bcf309a (commit)
via bf3c795a32b841b0c3ee2d29a9f89c4781a18e86 (commit)
via 4590e29cee633f57d4fa4b91e24f6b570c8eaa6e (commit)
via 23b502611a895742cf9bff65babc5e65c5c1a5cd (commit)
via 41de9e41553ca902630520dfe12178f391aa0d4d (commit)
via 60086bec0b86e5e8590e2fc3f3ef82ebc63829c3 (commit)
via 7c3157f20008eead9731a9193b39e5775d3e44d5 (commit)
via 90b9fb73e2b1ce8f0b4e0b8597e4705e6f540920 (commit)
via 5589ea8da0a906e66eed1d85b26da4e75e3902a2 (commit)
via 354bb735d31ec83aba2eb0afe3a0e05aa8f254d5 (commit)
via 3617d5a8087d8489734f613fe4a85bc6fedd7a74 (commit)
via 029db306a1acc7c7856cb9da3e18fda5cdaf630f (commit)
via 349a930441b3bf216c25d308a9a31054180b67ce (commit)
via 33618742d5adfd9c8c064d51845dfacb3e8f7d07 (commit)
via 8c872c890d607442fd0e44c1c4d5e4827abd0284 (commit)
via 73d2c507ffae1519e72f6b2e372f75aa5ad536c1 (commit)
via edc5b3c12eb45437361484c843794416ad86bb00 (commit)
via 47eecefe9805a080decaefa12509a361b9213677 (commit)
via 142ae6ee996b782e97a7db7c1b2318a0548178b0 (commit)
via 803c513e70af98eb71d4fcd954ae1714b6f1c968 (commit)
via f59f3629b17d25cf0f8920582af2e5e1ca5283c1 (commit)
via 109c7a70bc0fb293e717e1bbe653ce9d01d1a737 (commit)
via 98ec643b85ed681f32dfbcb29cc8e6bd51b290c4 (commit)
via 5279c2e13084fd954021cd667caa99dc982594c2 (commit)
via be7b75528c3bd4ae882136cdac6ecf925ac9f621 (commit)
via 17c32814ead8f8f4241e7f89eeed668d89868ef0 (commit)
via 591f208dba31d33d147d39c68dbf0dfbfc64c70f (commit)
via 0ee2267ab384059dde33817da15395806f45433f (commit)
via 1830215f884e3b5efda52bd4dbb120bdca863a6a (commit)
via 1627c0152628294dea5b55833b1ac6d66a08cbc0 (commit)
via 7c98c9fd24240015fdebbbae3ae8c313c364d52a (commit)
via 91b5b26bc39a66dc82eb3b863f7aa3264a3a436f (commit)
via 138bb39fd8fdf323fbecdf4a1b2cabea4dcd624e (commit)
via 7f9f716e39143885f6c84b3699faa3e79f4dcb2b (commit)
via e8dcc4afc2eba29c4746ab447559968097afe68f (commit)
via 70004148a3e39ca32f01d1ddd0cf0344df97b728 (commit)
via 07ff448e6c11eba022786cccf03d89e658c37d9f (commit)
via b37cfa5481840b795a6891f0f26bfed5e498317b (commit)
via 61d3224981ddac0ce238e61378dff60996288c21 (commit)
via 526b0b5b5615cd258335b14c6417ee26f0d671f5 (commit)
via ca3cc561237dde1fac32c592056051eb339bea03 (commit)
via d66d0cc215a2436ddf91343f266c47a73d0993f2 (commit)
via f786ba4251dac7a4feb71a604cfdf27003305ddf (commit)
via 24ec2f1431f588dac255930a6cac73519b4fa4ea (commit)
via a3cf5322a73e8a97b388c6f8025b92957e5d8986 (commit)
via 01d2bc6d797953339d3d99cdb6fdba7ff76f83d0 (commit)
via ef021f7736be3ae23b19e71707cd383bad8d7c9a (commit)
via 619dbae4c1fdd5668db33590b94dbd847691bad2 (commit)
via 3cdb00878c3a0dd3d31fd2a8a64b47a376b87d37 (commit)
via 9f776b729b14aa1d3678325e880504c68bbe8488 (commit)
via ab54494aee9089bac237cc7308f06145f62312b8 (commit)
via f68758a5f5ebb9bc2405202ee5455944f3d95600 (commit)
via 43fda10b4f6f788050dbed8198fa85ea5b385a80 (commit)
via c836c770e7109302fa1a27d51ae2fec573c03cb5 (commit)
via 4fdec3477a07431fadd2f36a68504038a0a78323 (commit)
via 78b0456c73d95d4a3d4a0c702aedd2c547404107 (commit)
via 3fcadc1a85ca56620c418e8b18cf43e43afbe98d (commit)
via 55982f5001add9a6da88b65ff543034a19b37b2b (commit)
via 0d120aa641cdb7fe66b04cb80974e3994d8c0251 (commit)
via 7b92b4d81235e99cbb210e427a8a56d44a1e9998 (commit)
via 2e14ad5917f64c67866f5b49a87515c5c6479ef3 (commit)
via 45e6dee0837447b6ea501394a6a3b9274f77f315 (commit)
via 9d4ae52d559448299b5561bd0d76930c2be2275b (commit)
via 908c1b114ba233fb2c238c7e102ddc0fdb605c9e (commit)
via 5565505e948f85534896a03e7a083c30496638e7 (commit)
via 444f92d580471b1ed9474a373a4797d497fdcea4 (commit)
via 50839dcce8152c27e4642f3c073e41a466cca0b8 (commit)
via 63a5191b95065ff4d918df0b9143e9371cd6f5d6 (commit)
via 5e04e03abdb6960f58127868ae91d596f18edd30 (commit)
via 619af407edf1c63ee20fed09ee39a338a41183e3 (commit)
via 8bb7f2c2df4a11a1301ee114ef4d2f6e8b8e71fd (commit)
via 25768c5398397bb271e0da9d8be89f545feb620e (commit)
via 1f9be59567c5c6154c7f840e67c1353163e9e097 (commit)
via 65000f777ce98ccdcb51c2c6d685bf6045d7c511 (commit)
via d93776a7695e631375165090e001bcbe38923d7e (commit)
via d66d5fea076e4174323aa0920dc91eb09c680674 (commit)
via cd0841fa5d7671f14baadaeb4e0177868caf21bc (commit)
via a842f40cddc919f5bb0aa68943fd7ef7b8938a04 (commit)
via dfe23099c41cc78082d7b5b67cd3a2dcea86c8c6 (commit)
via 5ec29b7633767f56675930402ccc7a7b9a1fdd2d (commit)
via b6aa3ff5422ec34da9c413aabff15145d86596e6 (commit)
via 9af13dfc788b7ddf5667545aa483ced00c349a20 (commit)
via e7b1c03c8c93645f923e842f4af89bdaf0a59576 (commit)
via 7319f208048b41a6279c36e5ae5d4b0e5c86751a (commit)
via 849cf2de22c31a2ef5c446a30f9ad5bac23d8c42 (commit)
via e031e1adcfa042da3cb6baa9834e42f573ea66fb (commit)
via bb00c59b03275b564938cbbb32956639bf76e5e8 (commit)
via 0a9bcf007a5e71875e15a0c878c440bd51ca5e40 (commit)
via ad4b9819be1af780f17018d82097dedd6965c19d (commit)
via 17680761ae76b62e33c801732a2f86180d995cd0 (commit)
via 199ebdc9c9db2f7744bfc50f0b13eec40eeed5ed (commit)
via d38b9068dde3191aef574fdda44f381f528b8146 (commit)
via 3e87a325eb979710f55db75c87a70392edd9a3c8 (commit)
via 85bf91d5836306a39b471567952ad1e0af91d948 (commit)
via 0a867ce22896ee7c1075caf58c706cec120fed40 (commit)
via e1cf741287a9203fbe00b18858cc908f677c449c (commit)
via ba9038e23c411ba6249a5bc38e2da30f7d95fb49 (commit)
via 182024bdd5343af59f6e4550eb977168d1745154 (commit)
via d8ac471ae3dc121fd598c47d19bf047ecb1b443d (commit)
via f02087d57966c11bd12197ad778f781ca0a8ddb6 (commit)
via 80d9d4fa922b82c6f47b5482de84399b1eee3c45 (commit)
via 412ea40020ab62f1b83fa78aa41d2b461f97a996 (commit)
via fb6cd6d5a9cb93c5e2e6f274919c16c2348eead2 (commit)
via ff1a0cd96fd213256d1e91c21cca3de2be634314 (commit)
via 4c80cfde85d88b2d8846197075ff22eed2bca306 (commit)
via dcfd99e26c1cf8040d58a77a689ad7939cc24884 (commit)
via 7fc73c203b222c1dbbd12744b4244b2673aa6f94 (commit)
via 81d3340075975fd4d9ff0e0fb3c526bf1f1b58a6 (commit)
via 6ce0d2a309b5032305ae13528ad0a71494558b58 (commit)
via 04dca588b5ec0f63e91800c612f6f616422cc9df (commit)
via 9e43c8d7558c31febb16c412a4ce425cac5257b5 (commit)
via 67f67098c15323d3760976a15f3d2a5d81b0b83f (commit)
via 6716f216a82cb64d12782c8db4dec0a0b62ea4a8 (commit)
via da770d40d1543308ace33c08182fe33e59f8ca7c (commit)
via b42300ae4b958bef9ff55eec929a227c4db4b5e3 (commit)
via 0524cba1473680ecf3fb6d1f7031622047472f83 (commit)
via 6d541b2acec75ea2ab8811619f8ccda8c05eec69 (commit)
via afc36c12bea2d68fdce04d7e0a5f22c980e61edc (commit)
via 8718c072463cc32d97749c2870f4f659129942ac (commit)
via b82303ac5c494936e9fc7dee82be764b242e48ba (commit)
via cccc0f378a5aa81c51dc529e2b01823f00efc326 (commit)
via af568f35fc96c7d844845b7136cb794bc54391b9 (commit)
via d2fc3f36ec89c950850b1aab3b8bef5d4413aaaf (commit)
via 294566c2b9a3dff9b9d3f929f09ad8f7a9d760c2 (commit)
via 33a396ecf7eddfa82054f4056d471ce9ff0616e4 (commit)
via dc1f905a3c787577a3627a1ead607a13460dbb2f (commit)
via c497ad2631b59d6645a7917d3a96e3d8bed577ee (commit)
via f7df24d2d83828d3e7a0652a83c1736a5aa79ced (commit)
via ddb94ad8340db3f41ede1a4509db23df5288cc05 (commit)
via 9ec8253a3af8de3f85b1bce0ebabc88b7fa5a746 (commit)
via e40c9f512e4e0a388890e81b2825a51d8ce10739 (commit)
via cec38f4ec016d0b510fb884def95898080fa66f3 (commit)
via c8caecc35195080d091611bb0809cbc2834ba3f4 (commit)
via 5345a3c2ba2daf9329b33984782ad50f72734837 (commit)
via d02b99f79bec594fc795936b18954543c87edc67 (commit)
via ac03fb060596dbebbb012d091292e4c9690f1c88 (commit)
from be560f4516ae9b0bbbad1740a519feed99c04170 (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 a40b6c665617125eeb8716b12d92d806f0342396
Merge: be560f4516ae9b0bbbad1740a519feed99c04170 b22ff2de4d9421bd1ef3976967aee886c8811611
Author: Tomek Mrugalski <tomasz at isc.org>
Date: Wed May 9 13:23:33 2012 +0200
Merge branch 'master' into trac1540
Conflicts:
ChangeLog
src/bin/dhcp4/dhcp4_srv.h
src/lib/dhcp/libdhcp++.h
src/lib/dhcp/option.cc
src/lib/dhcp/option.h
src/lib/dhcp/option4_addrlst.cc
src/lib/dhcp/option6_ia.cc
src/lib/dhcp/option6_iaaddr.h
src/lib/dhcp/pkt4.h
src/lib/dhcp/pkt6.cc
src/lib/dhcp/pkt6.h
-----------------------------------------------------------------------
Summary of changes:
.gitignore | 31 +
COPYING | 6 +
ChangeLog | 513 ++++++-
INSTALL | 2 +-
Makefile.am | 9 +-
README | 5 +-
compatcheck/.gitignore | 1 +
compatcheck/Makefile.am | 12 +-
compatcheck/sqlite3-difftbl-check.py.in | 60 -
configure.ac | 404 +++--
depcomp | 630 -------
dns++.pc.in | 11 +
doc/.gitignore | 2 +
doc/Doxyfile | 25 +-
doc/Makefile.am | 13 +
doc/devel/01-dns.dox | 14 +
doc/devel/02-dhcp.dox | 117 ++
doc/devel/mainpage.dox | 36 +
doc/guide/bind10-guide.html | 499 ++++---
doc/guide/bind10-guide.txt | 1088 +++++++++----
doc/guide/bind10-guide.xml | 457 ++++--
doc/guide/bind10-messages.html | 435 +++++-
doc/guide/bind10-messages.xml | 1065 +++++++++++-
doc/images/isc-logo.png | Bin 0 -> 12595 bytes
ext/LICENSE_1_0.txt | 23 +
ext/asio/asio/detail/impl/kqueue_reactor.ipp | 2 +
ext/asio/asio/detail/impl/socket_ops.ipp | 36 +-
install-sh | 520 ------
m4macros/.gitignore | 5 +
missing | 376 -----
src/bin/Makefile.am | 2 +-
src/bin/auth/.gitignore | 7 +
src/bin/auth/Makefile.am | 1 -
src/bin/auth/auth.spec.pre.in | 276 +++-
src/bin/auth/auth_config.cc | 68 +-
src/bin/auth/auth_log.h | 2 +
src/bin/auth/auth_messages.mes | 49 +-
src/bin/auth/auth_srv.cc | 300 +++--
src/bin/auth/auth_srv.h | 45 +-
src/bin/auth/b10-auth.8 | 31 +-
src/bin/auth/b10-auth.xml | 36 +-
src/bin/auth/benchmarks/.gitignore | 1 +
src/bin/auth/benchmarks/Makefile.am | 4 +
src/bin/auth/benchmarks/query_bench.cc | 31 +-
src/bin/auth/change_user.cc | 54 -
src/bin/auth/change_user.h | 57 -
src/bin/auth/command.cc | 143 ++-
src/bin/auth/common.cc | 2 +
src/bin/auth/common.h | 5 +
src/bin/auth/main.cc | 21 +-
src/bin/auth/query.cc | 592 +++++---
src/bin/auth/query.h | 371 ++++-
src/bin/auth/statistics.cc | 81 +-
src/bin/auth/statistics.h | 53 +-
src/bin/auth/tests/.gitignore | 1 +
src/bin/auth/tests/Makefile.am | 19 +-
src/bin/auth/tests/auth_srv_unittest.cc | 610 ++++++-
src/bin/auth/tests/change_user_unittest.cc | 65 -
src/bin/auth/tests/command_unittest.cc | 394 ++++--
src/bin/auth/tests/common_unittest.cc | 2 +-
src/bin/auth/tests/config_syntax_unittest.cc | 71 +
src/bin/auth/tests/config_unittest.cc | 101 +-
src/bin/auth/tests/datasrc_util.cc | 77 +
src/bin/auth/tests/datasrc_util.h | 58 +
src/bin/auth/tests/query_unittest.cc | 1708 ++++++++++++++++---
src/bin/auth/tests/statistics_unittest.cc | 147 ++
src/bin/auth/tests/testdata/example.sqlite3 | Bin 11264 -> 15360 bytes
src/bin/bind10/.gitignore | 3 +
src/bin/bind10/bind10.8 | 61 +-
src/bin/bind10/bind10.xml | 107 +-
src/bin/bind10/bind10_messages.mes | 2 +-
src/bin/bind10/bind10_src.py.in | 145 +-
src/bin/bind10/bob.spec | 13 -
src/bin/bind10/tests/.gitignore | 1 +
src/bin/bind10/tests/bind10_test.py.in | 93 +-
src/bin/bindctl/.gitignore | 3 +
src/bin/bindctl/Makefile.am | 2 +-
src/bin/bindctl/bindcmd.py | 120 ++-
src/bin/bindctl/bindctl_main.py.in | 2 +
src/bin/bindctl/command_sets.py | 95 ++
src/bin/bindctl/moduleinfo.py | 39 +-
src/bin/bindctl/tests/.gitignore | 1 +
src/bin/bindctl/tests/bindctl_test.py | 13 +-
src/bin/cfgmgr/.gitignore | 2 +
src/bin/cfgmgr/b10-cfgmgr.py.in | 7 +-
src/bin/cfgmgr/tests/.gitignore | 1 +
src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in | 15 +-
src/bin/cmdctl/.gitignore | 5 +
src/bin/cmdctl/b10-cmdctl.8 | 33 +-
src/bin/cmdctl/b10-cmdctl.xml | 50 +-
src/bin/cmdctl/cmdctl.py.in | 15 +-
src/bin/cmdctl/cmdctl.spec.pre.in | 8 +-
src/bin/cmdctl/tests/.gitignore | 1 +
src/bin/cmdctl/tests/cmdctl_test.py | 34 +
src/bin/dbutil/.gitignore | 3 +
src/bin/dbutil/Makefile.am | 39 +
src/bin/dbutil/b10-dbutil.8 | 92 +
src/bin/dbutil/b10-dbutil.xml | 192 +++
src/bin/dbutil/dbutil.py.in | 608 +++++++
src/bin/dbutil/dbutil_messages.mes | 114 ++
src/bin/dbutil/run_dbutil.sh.in | 40 +
src/bin/dbutil/tests/.gitignore | 2 +
src/bin/dbutil/tests/Makefile.am | 6 +
src/bin/dbutil/tests/dbutil_test.sh.in | 481 ++++++
src/bin/dbutil/tests/testdata/Makefile.am | 12 +
src/bin/dbutil/tests/testdata/README | 41 +
src/bin/dbutil/tests/testdata/corrupt.sqlite3 | Bin 0 -> 215040 bytes
src/bin/dbutil/tests/testdata/empty_schema.sqlite3 | Bin 0 -> 215040 bytes
src/bin/dbutil/tests/testdata/empty_v1.sqlite3 | Bin 0 -> 215040 bytes
.../dbutil/tests/testdata/empty_version.sqlite3 | Bin 0 -> 13312 bytes
src/bin/dbutil/tests/testdata/invalid_v1.sqlite3 | Bin 0 -> 215040 bytes
src/bin/dbutil/tests/testdata/new_v1.sqlite3 | Bin 0 -> 215040 bytes
src/bin/dbutil/tests/testdata/no_schema.sqlite3 | Bin 0 -> 2048 bytes
src/bin/dbutil/tests/testdata/old_v1.sqlite3 | Bin 0 -> 215040 bytes
.../dbutil/tests/testdata/too_many_version.sqlite3 | Bin 0 -> 13312 bytes
src/bin/dbutil/tests/testdata/v2_0.sqlite3 | Bin 0 -> 13312 bytes
src/bin/ddns/.gitignore | 2 +
src/bin/ddns/b10-ddns.8 | 41 +-
src/bin/ddns/b10-ddns.xml | 26 +-
src/bin/ddns/ddns.py.in | 116 ++-
src/bin/ddns/ddns.spec | 8 +-
src/bin/ddns/ddns_messages.mes | 24 +
src/bin/ddns/tests/Makefile.am | 1 +
src/bin/ddns/tests/ddns_test.py | 279 +++-
src/bin/dhcp4/.gitignore | 3 +
src/bin/dhcp4/Makefile.am | 2 +-
src/bin/dhcp4/tests/.gitignore | 1 +
src/bin/dhcp6/Makefile.am | 2 +-
src/bin/host/.gitignore | 1 +
src/bin/host/host.cc | 7 +-
src/bin/loadzone/.gitignore | 3 +
src/bin/loadzone/b10-loadzone.8 | 6 +-
src/bin/loadzone/b10-loadzone.xml | 4 +-
src/bin/loadzone/tests/correct/.gitignore | 1 +
src/bin/loadzone/tests/error/.gitignore | 1 +
src/bin/msgq/.gitignore | 3 +
src/bin/msgq/tests/.gitignore | 1 +
src/bin/resolver/.gitignore | 7 +
src/bin/resolver/Makefile.am | 3 +-
src/bin/resolver/b10-resolver.8 | 21 +-
src/bin/resolver/b10-resolver.xml | 26 +-
src/bin/resolver/common.cc | 17 +
src/bin/resolver/common.h | 23 +
src/bin/resolver/main.cc | 90 +-
src/bin/resolver/resolver.cc | 20 +-
src/bin/resolver/resolver.h | 6 +-
src/bin/resolver/resolver.spec.pre.in | 8 +-
src/bin/resolver/resolver_messages.mes | 4 +
src/bin/resolver/tests/.gitignore | 1 +
src/bin/resolver/tests/resolver_config_unittest.cc | 44 +-
src/bin/sockcreator/.gitignore | 1 +
src/bin/sockcreator/Makefile.am | 3 +-
src/bin/sockcreator/main.cc | 8 +-
src/bin/sockcreator/sockcreator.cc | 349 +++--
src/bin/sockcreator/sockcreator.h | 185 ++-
src/bin/sockcreator/tests/.gitignore | 1 +
src/bin/sockcreator/tests/Makefile.am | 5 +-
src/bin/sockcreator/tests/sockcreator_tests.cc | 507 ++++--
src/bin/stats/.gitignore | 4 +
src/bin/stats/b10-stats-httpd.8 | 23 +-
src/bin/stats/b10-stats-httpd.xml | 17 +-
src/bin/stats/b10-stats.8 | 54 +-
src/bin/stats/b10-stats.xml | 62 +-
src/bin/stats/stats-httpd.spec | 8 +-
src/bin/stats/stats.py.in | 134 ++-
src/bin/stats/stats.spec | 15 +-
src/bin/stats/stats_httpd.py.in | 8 +-
src/bin/stats/tests/b10-stats-httpd_test.py | 29 +-
src/bin/stats/tests/b10-stats_test.py | 193 +++-
src/bin/stats/tests/test_utils.py | 29 +-
src/bin/tests/.gitignore | 1 +
src/bin/usermgr/.gitignore | 3 +
src/bin/xfrin/.gitignore | 3 +
src/bin/xfrin/tests/.gitignore | 1 +
src/bin/xfrin/tests/testdata/example.com.sqlite3 | Bin 12288 -> 15360 bytes
src/bin/xfrin/tests/xfrin_test.py | 278 +++-
src/bin/xfrin/xfrin.py.in | 202 ++-
src/bin/xfrin/xfrin.spec | 8 +-
src/bin/xfrin/xfrin_messages.mes | 292 ++--
src/bin/xfrout/.gitignore | 5 +
src/bin/xfrout/b10-xfrout.8 | 65 +-
src/bin/xfrout/b10-xfrout.xml | 72 +-
src/bin/xfrout/tests/.gitignore | 2 +
src/bin/xfrout/tests/testdata/test.sqlite3 | Bin 12288 -> 15360 bytes
src/bin/xfrout/tests/xfrout_test.py.in | 83 +-
src/bin/xfrout/xfrout.py.in | 53 +-
src/bin/xfrout/xfrout.spec.pre.in | 63 +-
src/bin/xfrout/xfrout_messages.mes | 4 +
src/bin/zonemgr/.gitignore | 5 +
src/bin/zonemgr/b10-zonemgr.8 | 10 +-
src/bin/zonemgr/b10-zonemgr.xml | 11 +-
src/bin/zonemgr/tests/.gitignore | 2 +
src/bin/zonemgr/tests/Makefile.am | 1 +
src/bin/zonemgr/tests/zonemgr_test.py | 41 +-
src/bin/zonemgr/zonemgr.py.in | 13 +-
src/bin/zonemgr/zonemgr.spec.pre.in | 8 +-
src/bin/zonemgr/zonemgr_messages.mes | 12 +-
src/cppcheck-suppress.lst | 16 +-
src/lib/acl/ip_check.cc | 1 +
src/lib/acl/tests/.gitignore | 1 +
src/lib/asiodns/.gitignore | 2 +
src/lib/asiodns/Makefile.am | 2 +
src/lib/asiodns/asiodns_messages.mes | 8 +
src/lib/asiodns/dns_server.h | 16 -
src/lib/asiodns/dns_service.cc | 172 +--
src/lib/asiodns/dns_service.h | 155 ++-
src/lib/asiodns/io_fetch.cc | 17 +-
src/lib/asiodns/logger.cc | 25 +
src/lib/asiodns/logger.h | 26 +
src/lib/asiodns/sync_udp_server.cc | 193 +++
src/lib/asiodns/sync_udp_server.h | 148 ++
src/lib/asiodns/tcp_server.cc | 33 +-
src/lib/asiodns/tcp_server.h | 21 +-
src/lib/asiodns/tests/.gitignore | 1 +
src/lib/asiodns/tests/Makefile.am | 2 +-
src/lib/asiodns/tests/dns_server_unittest.cc | 401 ++++--
src/lib/asiodns/tests/dns_service_unittest.cc | 232 +++
src/lib/asiodns/tests/io_fetch_unittest.cc | 27 +-
src/lib/asiodns/tests/io_service_unittest.cc | 118 --
src/lib/asiodns/udp_server.cc | 38 +-
src/lib/asiodns/udp_server.h | 25 +-
src/lib/asiolink/io_address.cc | 4 +-
src/lib/asiolink/io_service.cc | 2 +-
src/lib/asiolink/tests/.gitignore | 1 +
src/lib/bench/benchmark.h | 52 +-
src/lib/bench/benchmark_util.cc | 9 +-
src/lib/bench/example/.gitignore | 1 +
src/lib/bench/example/search_bench.cc | 4 +-
src/lib/bench/tests/.gitignore | 1 +
src/lib/bench/tests/benchmark_unittest.cc | 18 +-
src/lib/cache/.gitignore | 2 +
src/lib/cache/local_zone_data.cc | 2 +-
src/lib/cache/local_zone_data.h | 2 +-
src/lib/cache/rrset_cache.cc | 2 +-
src/lib/cache/rrset_cache.h | 2 +-
src/lib/cache/rrset_copy.cc | 2 +-
src/lib/cache/rrset_copy.h | 2 +-
src/lib/cache/rrset_entry.cc | 3 +-
src/lib/cache/rrset_entry.h | 3 +-
src/lib/cache/tests/.gitignore | 1 +
src/lib/cache/tests/Makefile.am | 5 -
src/lib/cache/tests/negative_cache_unittest.cc | 52 +-
src/lib/cc/.gitignore | 4 +
src/lib/cc/cc_messages.mes | 2 +-
src/lib/cc/data.cc | 99 +-
src/lib/cc/session.cc | 2 +-
src/lib/cc/tests/.gitignore | 2 +
src/lib/cc/tests/Makefile.am | 7 +-
src/lib/cc/tests/data_unittests.cc | 53 +-
src/lib/config/.gitignore | 2 +
src/lib/config/ccsession.cc | 23 +
src/lib/config/ccsession.h | 45 +-
src/lib/config/config_data.h | 2 +-
src/lib/config/config_messages.mes | 22 +-
src/lib/config/module_spec.cc | 22 +-
src/lib/config/module_spec.h | 12 +-
src/lib/config/tests/.gitignore | 2 +
src/lib/config/tests/ccsession_unittests.cc | 63 +
src/lib/config/tests/fake_session.cc | 9 +-
src/lib/config/tests/fake_session.h | 9 +
src/lib/config/tests/testdata/.gitignore | 1 +
src/lib/config/tests/testdata/Makefile.am | 1 +
src/lib/config/tests/testdata/spec39.spec | 21 +
src/lib/cryptolink/tests/.gitignore | 1 +
src/lib/datasrc/.gitignore | 4 +
src/lib/datasrc/Makefile.am | 16 +-
src/lib/datasrc/data_source.cc | 3 +-
src/lib/datasrc/database.cc | 724 ++++++---
src/lib/datasrc/database.h | 378 ++++-
src/lib/datasrc/datasrc_config.h.pre.in | 2 +-
src/lib/datasrc/datasrc_messages.mes | 206 ++-
src/lib/datasrc/factory.h | 2 +-
src/lib/datasrc/memory_datasrc.cc | 1771 +++++++++++++++-----
src/lib/datasrc/memory_datasrc.h | 65 +-
src/lib/datasrc/memory_datasrc_link.cc | 173 ++
src/lib/datasrc/rbnode_rrset.h | 228 +++
src/lib/datasrc/rbtree.h | 11 +-
src/lib/datasrc/sqlite3_accessor.cc | 604 +++++---
src/lib/datasrc/sqlite3_accessor.h | 35 +-
src/lib/datasrc/sqlite3_accessor_link.cc | 107 ++
src/lib/datasrc/sqlite3_datasrc.cc | 196 ++-
src/lib/datasrc/sqlite3_datasrc.h | 6 +
src/lib/datasrc/static_datasrc.cc | 3 +
src/lib/datasrc/tests/.gitignore | 4 +
src/lib/datasrc/tests/Makefile.am | 68 +-
src/lib/datasrc/tests/database_unittest.cc | 1057 +++++++++---
src/lib/datasrc/tests/datasrc_unittest.cc | 3 +-
src/lib/datasrc/tests/faked_nsec3.cc | 209 +++
src/lib/datasrc/tests/faked_nsec3.h | 88 +
src/lib/datasrc/tests/memory_datasrc_unittest.cc | 1241 ++++++++++++--
src/lib/datasrc/tests/rbnode_rrset_unittest.cc | 276 +++
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc | 427 +++++-
src/lib/datasrc/tests/sqlite3_unittest.cc | 15 +
src/lib/datasrc/tests/static_unittest.cc | 5 +-
src/lib/datasrc/tests/test_client.cc | 92 +
src/lib/datasrc/tests/test_client.h | 71 +
src/lib/datasrc/tests/testdata/.gitignore | 1 +
src/lib/datasrc/tests/testdata/contexttest.zone | 81 +
src/lib/datasrc/tests/testdata/diffs.sqlite3 | Bin 16384 -> 20480 bytes
.../tests/testdata/example.org.nsec3-signed | 14 +
.../testdata/example.org.nsec3-signed-noparam | 15 +
src/lib/datasrc/tests/testdata/example.org.sqlite3 | Bin 14336 -> 16384 bytes
.../datasrc/tests/testdata/example2.com.sqlite3 | Bin 14336 -> 16384 bytes
.../tests/testdata/new_minor_schema.sqlite3 | Bin 0 -> 2048 bytes
src/lib/datasrc/tests/testdata/newschema.sqlite3 | Bin 0 -> 2048 bytes
src/lib/datasrc/tests/testdata/oldschema.sqlite3 | Bin 0 -> 2048 bytes
.../{dns => datasrc}/tests/testdata/rrset_toWire1 | 0
src/lib/datasrc/tests/testdata/rrset_toWire2 | 26 +
src/lib/datasrc/tests/testdata/rwtest.sqlite3 | Bin 13312 -> 0 bytes
src/lib/datasrc/tests/testdata/test-root.sqlite3 | Bin 17408 -> 22528 bytes
src/lib/datasrc/tests/testdata/test.sqlite3 | Bin 44032 -> 70656 bytes
.../datasrc/tests/testdata/test.sqlite3.nodiffs | Bin 43008 -> 0 bytes
.../datasrc/tests/zone_finder_context_unittest.cc | 413 +++++
src/lib/datasrc/zone.h | 581 +++++--
src/lib/datasrc/zone_finder_context.cc | 102 ++
src/lib/dhcp/iface_mgr.cc | 4 +-
src/lib/dhcp/iface_mgr_linux.cc | 1 -
src/lib/dhcp/option.cc | 4 +-
src/lib/dhcp/option4_addrlst.cc | 7 +-
src/lib/dhcp/option6_ia.cc | 4 +-
src/lib/dhcp/pkt6.cc | 24 +-
src/lib/dhcp/pkt6.h | 9 +-
src/lib/dhcp/tests/.gitignore | 1 +
src/lib/dhcp/tests/iface_mgr_unittest.cc | 3 +-
src/lib/dns/.gitignore | 6 +
src/lib/dns/Makefile.am | 16 +-
src/lib/dns/benchmarks/.gitignore | 2 +
src/lib/dns/benchmarks/Makefile.am | 10 +-
src/lib/dns/benchmarks/message_renderer_bench.cc | 176 ++
src/lib/dns/benchmarks/oldmessagerenderer.cc | 278 +++
src/lib/dns/benchmarks/oldmessagerenderer.h | 55 +
src/lib/dns/benchmarks/rdatarender_bench.cc | 3 +-
src/lib/dns/labelsequence.cc | 114 ++
src/lib/dns/labelsequence.h | 177 ++
src/lib/dns/masterload.cc | 48 +-
src/lib/dns/masterload.h | 5 +-
src/lib/dns/message.cc | 4 +
src/lib/dns/message.h | 16 +-
src/lib/dns/messagerenderer.cc | 353 +++--
src/lib/dns/messagerenderer.h | 104 +-
src/lib/dns/name.cc | 55 +-
src/lib/dns/name.h | 39 +-
src/lib/dns/name_internal.h | 43 +
src/lib/dns/nsec3hash.cc | 195 +++
src/lib/dns/nsec3hash.h | 253 +++
src/lib/dns/python/Makefile.am | 4 +-
src/lib/dns/python/message_python.cc | 5 +-
src/lib/dns/python/messagerenderer_python.cc | 6 +-
src/lib/dns/python/nsec3hash_python.cc | 271 +++
src/lib/dns/python/nsec3hash_python.h | 47 +
src/lib/dns/python/nsec3hash_python_inc.cc | 68 +
src/lib/dns/python/pydnspp.cc | 9 +
src/lib/dns/python/rrset_python.cc | 4 +-
src/lib/dns/python/rrset_python.h | 4 +-
src/lib/dns/python/tests/Makefile.am | 1 +
src/lib/dns/python/tests/nsec3hash_python_test.py | 128 ++
.../dns/rdata/generic/detail/nsec3param_common.cc | 130 ++
.../dns/rdata/generic/detail/nsec3param_common.h | 134 ++
src/lib/dns/rdata/generic/detail/nsec_bitmap.cc | 84 +-
src/lib/dns/rdata/generic/detail/nsec_bitmap.h | 68 +-
src/lib/dns/rdata/generic/nsec3_50.cc | 261 ++--
src/lib/dns/rdata/generic/nsec3param_51.cc | 116 +-
src/lib/dns/rdata/generic/nsec_47.cc | 62 +-
src/lib/dns/rdata/generic/sshfp_44.cc | 164 ++
src/lib/dns/rdata/generic/sshfp_44.h | 58 +
src/lib/dns/rdata/template.cc | 1 +
src/lib/dns/rdatafields.cc | 6 +-
src/lib/dns/rrset.cc | 10 +
src/lib/dns/rrset.h | 158 ++-
src/lib/dns/tests/.gitignore | 1 +
src/lib/dns/tests/Makefile.am | 4 +
src/lib/dns/tests/edns_unittest.cc | 4 +-
src/lib/dns/tests/labelsequence_unittest.cc | 348 ++++
src/lib/dns/tests/masterload_unittest.cc | 112 ++
src/lib/dns/tests/message_unittest.cc | 15 +-
src/lib/dns/tests/messagerenderer_unittest.cc | 118 +-
src/lib/dns/tests/name_unittest.cc | 69 +-
src/lib/dns/tests/nsec3hash_unittest.cc | 222 +++
src/lib/dns/tests/question_unittest.cc | 6 +-
src/lib/dns/tests/rdata_afsdb_unittest.cc | 2 +-
src/lib/dns/tests/rdata_cname_unittest.cc | 4 +-
src/lib/dns/tests/rdata_dhcid_unittest.cc | 1 +
src/lib/dns/tests/rdata_dname_unittest.cc | 4 +-
src/lib/dns/tests/rdata_dnskey_unittest.cc | 4 +-
src/lib/dns/tests/rdata_ds_like_unittest.cc | 4 +-
src/lib/dns/tests/rdata_hinfo_unittest.cc | 5 +-
src/lib/dns/tests/rdata_in_a_unittest.cc | 3 +-
src/lib/dns/tests/rdata_in_aaaa_unittest.cc | 3 +-
src/lib/dns/tests/rdata_minfo_unittest.cc | 8 +-
src/lib/dns/tests/rdata_mx_unittest.cc | 7 +-
src/lib/dns/tests/rdata_naptr_unittest.cc | 5 +-
src/lib/dns/tests/rdata_ns_unittest.cc | 4 +-
src/lib/dns/tests/rdata_nsec3_unittest.cc | 122 +-
.../dns/tests/rdata_nsec3param_like_unittest.cc | 260 +++
src/lib/dns/tests/rdata_nsec3param_unittest.cc | 57 +-
src/lib/dns/tests/rdata_nsec_unittest.cc | 33 +-
src/lib/dns/tests/rdata_nsecbitmap_unittest.cc | 253 +++-
src/lib/dns/tests/rdata_ptr_unittest.cc | 4 +-
src/lib/dns/tests/rdata_rp_unittest.cc | 2 +-
src/lib/dns/tests/rdata_soa_unittest.cc | 4 +-
src/lib/dns/tests/rdata_srv_unittest.cc | 4 +-
src/lib/dns/tests/rdata_sshfp_unittest.cc | 94 +
src/lib/dns/tests/rdata_unittest.cc | 6 +-
src/lib/dns/tests/rdatafields_unittest.cc | 5 +-
src/lib/dns/tests/rrclass_unittest.cc | 4 +-
src/lib/dns/tests/rrset_unittest.cc | 27 +-
src/lib/dns/tests/rrttl_unittest.cc | 10 +-
src/lib/dns/tests/rrtype_unittest.cc | 4 +-
src/lib/dns/tests/testdata/.gitignore | 117 ++
src/lib/dns/tests/testdata/Makefile.am | 14 +
src/lib/dns/tests/testdata/rdata_mx_fromWire | 2 +-
.../dns/tests/testdata/rdata_nsec3_fromWire16.spec | 8 +
.../dns/tests/testdata/rdata_nsec3_fromWire17.spec | 8 +
.../dns/tests/testdata/rdata_nsec3param_fromWire1 | 4 +-
.../testdata/rdata_nsec3param_fromWire11.spec | 8 +
.../testdata/rdata_nsec3param_fromWire13.spec | 9 +
.../tests/testdata/rdata_nsec3param_fromWire2.spec | 9 +
.../dns/tests/testdata/rdata_nsec_fromWire16.spec | 8 +
src/lib/dns/tests/testdata/rdata_sshfp_fromWire | 4 +
.../dns/tests/testdata/rdata_sshfp_fromWire1.spec | 6 +
src/lib/dns/tests/testdata/rdata_sshfp_fromWire2 | 4 +
.../dns/tests/testdata/rdata_sshfp_fromWire2.spec | 7 +
src/lib/dns/tests/tsig_unittest.cc | 3 +-
src/lib/dns/tests/tsigrecord_unittest.cc | 2 +-
src/lib/dns/tests/unittest_util.cc | 18 +
src/lib/dns/tests/unittest_util.h | 16 +
src/lib/exceptions/Makefile.am | 2 +-
src/lib/exceptions/exceptions.h | 32 +
src/lib/exceptions/tests/.gitignore | 1 +
src/lib/log/compiler/.gitignore | 1 +
src/lib/log/compiler/Makefile.am | 1 +
src/lib/log/compiler/message.cc | 50 +-
src/lib/log/log_formatter.cc | 36 +-
src/lib/log/log_formatter.h | 22 +
src/lib/log/logger.h | 43 +-
src/lib/log/logger_manager.cc | 4 +
src/lib/log/logger_manager_impl.cc | 4 +-
src/lib/log/message_exception.h | 24 +-
src/lib/log/message_initializer.cc | 71 +-
src/lib/log/message_initializer.h | 56 +-
src/lib/log/message_reader.cc | 40 +-
src/lib/log/message_reader.h | 2 +-
src/lib/log/tests/.gitignore | 11 +
src/lib/log/tests/Makefile.am | 99 +-
src/lib/log/tests/log_formatter_unittest.cc | 64 +-
src/lib/log/tests/logger_example.cc | 2 +-
src/lib/log/tests/logger_manager_unittest.cc | 10 +-
src/lib/log/tests/logger_support_unittest.cc | 2 +-
src/lib/log/tests/logger_unittest.cc | 35 +-
.../log/tests/message_initializer_1_unittest.cc | 79 +
.../log/tests/message_initializer_1a_unittest.cc | 37 +
.../log/tests/message_initializer_2_unittest.cc | 52 +
src/lib/log/tests/message_initializer_unittest.cc | 70 -
.../log/tests/message_initializer_unittest_2.cc | 39 -
src/lib/log/tests/run_initializer_unittests.cc | 24 +
src/lib/nsas/.gitignore | 2 +
src/lib/nsas/glue_hints.cc | 4 +-
src/lib/nsas/hash.cc | 5 +-
src/lib/nsas/hash_table.h | 2 +-
src/lib/nsas/tests/.gitignore | 1 +
src/lib/nsas/tests/Makefile.am | 5 -
.../tests/nameserver_address_store_unittest.cc | 2 +-
src/lib/nsas/tests/nameserver_entry_unittest.cc | 4 +-
src/lib/nsas/zone_entry.cc | 2 +-
src/lib/python/.gitignore | 1 +
src/lib/python/Makefile.am | 2 +-
src/lib/python/bind10_config.py.in | 41 +-
src/lib/python/isc/Makefile.am | 2 +-
src/lib/python/isc/acl/Makefile.am | 4 +-
src/lib/python/isc/acl/tests/dns_test.py | 2 +-
src/lib/python/isc/bind10/component.py | 36 +-
src/lib/python/isc/bind10/socket_cache.py | 4 +-
src/lib/python/isc/bind10/special_component.py | 38 +-
src/lib/python/isc/bind10/tests/component_test.py | 50 +-
src/lib/python/isc/cc/data.py | 8 +
src/lib/python/isc/cc/session.py | 25 +-
src/lib/python/isc/cc/tests/.gitignore | 1 +
src/lib/python/isc/cc/tests/session_test.py | 31 +-
src/lib/python/isc/config/Makefile.am | 1 +
src/lib/python/isc/config/ccsession.py | 149 ++-
src/lib/python/isc/config/cfgmgr.py | 96 +-
src/lib/python/isc/config/cfgmgr_messages.mes | 6 +-
src/lib/python/isc/config/config_data.py | 121 ++-
src/lib/python/isc/config/config_messages.mes | 6 +
src/lib/python/isc/config/tests/.gitignore | 1 +
src/lib/python/isc/config/tests/ccsession_test.py | 383 ++++-
src/lib/python/isc/config/tests/cfgmgr_test.py | 312 +++-
.../python/isc/config/tests/config_data_test.py | 148 ++-
src/lib/python/isc/datasrc/Makefile.am | 2 +-
src/lib/python/isc/datasrc/datasrc.cc | 18 +-
src/lib/python/isc/datasrc/finder_inc.cc | 144 ++-
src/lib/python/isc/datasrc/finder_python.cc | 50 +-
src/lib/python/isc/datasrc/sqlite3_ds.py | 49 +-
src/lib/python/isc/datasrc/tests/.gitignore | 1 +
src/lib/python/isc/datasrc/tests/Makefile.am | 8 +-
src/lib/python/isc/datasrc/tests/datasrc_test.py | 161 +-
.../python/isc/datasrc/tests/sqlite3_ds_test.py | 124 +--
.../isc/datasrc/tests/testdata/example.com.sqlite3 | Bin 44032 -> 70656 bytes
.../tests/testdata/new_minor_schema.sqlite3 | Bin 0 -> 2048 bytes
.../isc/datasrc/tests/testdata/newschema.sqlite3 | Bin 0 -> 2048 bytes
.../isc/datasrc/tests/testdata/oldschema.sqlite3 | Bin 0 -> 2048 bytes
.../datasrc/tests/testdata/test.sqlite3.nodiffs | Bin 43008 -> 0 bytes
src/lib/python/isc/datasrc/updater_python.cc | 2 +-
src/lib/python/isc/log/Makefile.am | 11 +-
src/lib/python/isc/log/tests/.gitignore | 1 +
src/lib/python/isc/log_messages/Makefile.am | 4 +
src/lib/python/isc/log_messages/dbutil_messages.py | 1 +
.../isc/log_messages/server_common_messages.py | 1 +
src/lib/python/isc/log_messages/work/.gitignore | 2 +
src/lib/python/isc/log_messages/work/Makefile.am | 2 +-
src/lib/python/isc/notify/notify_out.py | 43 +-
src/lib/python/isc/notify/tests/.gitignore | 1 +
src/lib/python/isc/notify/tests/notify_out_test.py | 29 +-
.../isc/notify/tests/testdata/brokentest.sqlite3 | Bin 11264 -> 15360 bytes
.../python/isc/notify/tests/testdata/test.sqlite3 | Bin 13312 -> 19456 bytes
src/lib/python/isc/server_common/Makefile.am | 24 +
.../isc/{bind10 => server_common}/__init__.py | 0
.../isc/server_common/server_common_messages.mes | 36 +
src/lib/python/isc/server_common/tests/Makefile.am | 24 +
.../isc/server_common/tests/tsig_keyring_test.py | 193 +++
src/lib/python/isc/server_common/tsig_keyring.py | 121 ++
src/lib/python/isc/testutils/Makefile.am | 3 +-
src/lib/python/isc/testutils/ccsession_mock.py | 34 +
src/lib/python/isc/util/Makefile.am | 2 +-
src/lib/python/isc/util/cio/Makefile.am | 41 +
src/lib/python/isc/util/{io => cio}/__init__.py | 0
src/lib/python/isc/util/cio/socketsession.py | 26 +
.../isc/util/{io => cio}/socketsession_inc.cc | 0
.../python/isc/util/cio/socketsession_python.cc | 79 +
.../isc/util/{io => cio}/socketsession_python.h | 0
.../util/{io => cio}/socketsessionforwarder_inc.cc | 0
.../isc/util/cio/socketsessionforwarder_python.cc | 309 ++++
.../{io => cio}/socketsessionforwarder_python.h | 0
.../util/{io => cio}/socketsessionreceiver_inc.cc | 0
.../isc/util/cio/socketsessionreceiver_python.cc | 327 ++++
.../{io => cio}/socketsessionreceiver_python.h | 0
.../python/isc/util/{io => cio}/tests/Makefile.am | 0
.../isc/util/cio/tests/socketsession_test.py | 253 +++
src/lib/python/isc/util/io/Makefile.am | 41 -
src/lib/python/isc/util/io/socketsession.py | 26 -
src/lib/python/isc/util/io/socketsession_python.cc | 79 -
.../isc/util/io/socketsessionforwarder_python.cc | 309 ----
.../isc/util/io/socketsessionreceiver_python.cc | 327 ----
.../python/isc/util/io/tests/socketsession_test.py | 253 ---
src/lib/resolve/.gitignore | 2 +
src/lib/resolve/Makefile.am | 1 +
src/lib/resolve/recursive_query.cc | 4 +-
src/lib/resolve/recursive_query.h | 4 +-
src/lib/resolve/tests/.gitignore | 1 +
src/lib/resolve/tests/recursive_query_unittest.cc | 248 ++-
.../resolve/tests/recursive_query_unittest_2.cc | 17 +-
.../resolve/tests/recursive_query_unittest_3.cc | 17 +-
src/lib/server_common/.gitignore | 2 +
src/lib/server_common/Makefile.am | 2 +
src/lib/server_common/portconfig.cc | 66 +-
src/lib/server_common/portconfig.h | 80 +-
src/lib/server_common/server_common_messages.mes | 43 +-
src/lib/server_common/socket_request.cc | 428 +++++
src/lib/server_common/socket_request.h | 278 +++
src/lib/server_common/tests/.gitignore | 2 +
src/lib/server_common/tests/Makefile.am | 1 +
src/lib/server_common/tests/client_unittest.cc | 1 +
src/lib/server_common/tests/portconfig_unittest.cc | 185 ++-
.../server_common/tests/socket_requestor_test.cc | 587 +++++++
src/lib/statistics/tests/.gitignore | 1 +
src/lib/testutils/Makefile.am | 2 +-
src/lib/testutils/dnsmessage_test.cc | 29 +
src/lib/testutils/dnsmessage_test.h | 58 +-
src/lib/testutils/mockups.h | 57 +
src/lib/testutils/portconfig.h | 16 +-
src/lib/testutils/socket_request.h | 219 +++
src/lib/testutils/srv_test.cc | 4 +-
src/lib/testutils/srv_test.h | 7 +-
src/lib/testutils/testdata/.gitignore | 15 +
src/lib/testutils/testdata/Makefile.am | 4 +
src/lib/testutils/testdata/auth_test.sqlite3 | Bin 0 -> 16384 bytes
src/lib/testutils/testdata/example.sqlite3 | Bin 11264 -> 15360 bytes
.../testutils/testdata/nsec3query_fromWire.spec | 11 +
.../testdata/nsec3query_nodnssec_fromWire.spec | 9 +
.../testutils/testdata/rfc5155-example.zone.signed | 72 +
src/lib/testutils/testdata/rwtest.sqlite3 | Bin 0 -> 16384 bytes
src/lib/util/buffer.h | 46 +-
src/lib/util/io/Makefile.am | 2 +-
src/lib/util/io/fd.cc | 35 +-
src/lib/util/io/fd_share.cc | 17 +-
src/lib/util/io/sockaddr_util.h | 14 +
src/lib/util/locks.h | 58 +-
src/lib/util/python/.gitignore | 2 +
src/lib/util/python/gen_wiredata.py.in | 71 +-
src/lib/util/python/wrapper_template.cc | 17 +-
src/lib/util/pyunittests/Makefile.am | 2 +-
src/lib/util/tests/.gitignore | 1 +
src/lib/util/tests/buffer_unittest.cc | 22 +-
src/lib/util/unittests/Makefile.am | 1 +
src/lib/util/unittests/resource.cc | 35 +
src/lib/util/unittests/resource.h | 39 +
src/lib/xfr/tests/.gitignore | 1 +
tests/lettuce/.gitignore | 2 +
tests/lettuce/README | 20 +-
tests/lettuce/README.tutorial | 4 +-
tests/lettuce/configurations/.gitignore | 2 +
.../configurations/bindctl/bindctl.config.orig | 22 +
.../configurations/bindctl_commands.config.orig | 34 +
tests/lettuce/configurations/default.config | 16 +
.../lettuce/configurations/example.org.config.orig | 8 +-
.../configurations/example.org.inmem.config | 8 +
tests/lettuce/configurations/example2.org.config | 10 +-
.../inmemory_over_sqlite3/secondary.conf | 32 +
.../configurations/ixfr-out/testset1-config.db | 12 +-
.../configurations/multi_instance/.gitignore | 1 +
.../multi_instance/multi_auth.config.orig | 24 +
tests/lettuce/configurations/no_db_file.config | 14 +
.../lettuce/configurations/nsec3/nsec3_auth.config | 1 +
.../nsec3/rfc5155-example.zone.signed | 72 +
tests/lettuce/configurations/resolver/.gitignore | 1 +
.../resolver/resolver_basic.config.orig | 1 +
.../configurations/xfrin/retransfer_master.conf | 12 +-
.../configurations/xfrin/retransfer_slave.conf | 10 +-
tests/lettuce/data/.gitignore | 2 +
tests/lettuce/data/commands/bad_command | 9 +
tests/lettuce/data/commands/directives | 19 +
AUTHORS => tests/lettuce/data/commands/empty | 0
tests/lettuce/data/commands/nested | 2 +
tests/lettuce/data/commands/nested1 | 2 +
tests/lettuce/data/empty_db.sqlite3 | Bin 11264 -> 14336 bytes
tests/lettuce/data/example.org | 12 +
tests/lettuce/data/example.org.sqlite3 | Bin 14336 -> 15360 bytes
tests/lettuce/data/ixfr-out/.gitignore | 1 +
tests/lettuce/data/ixfr-out/zones.slite3 | Bin 246784 -> 0 bytes
tests/lettuce/data/ixfr-out/zones.sqlite3 | Bin 0 -> 468992 bytes
tests/lettuce/features/bindctl_commands.feature | 156 ++
tests/lettuce/features/default.feature | 21 +
tests/lettuce/features/example.feature | 64 +-
.../lettuce/features/inmemory_over_sqlite3.feature | 10 +
tests/lettuce/features/ixfr_out_bind10.feature | 18 +-
tests/lettuce/features/multi_instance.feature | 59 +
tests/lettuce/features/nsec3_auth.feature | 466 +++++
tests/lettuce/features/queries.feature | 124 ++
tests/lettuce/features/resolver_basic.feature | 36 +
tests/lettuce/features/terrain/.gitignore | 1 +
tests/lettuce/features/terrain/bind10_control.py | 232 +++-
tests/lettuce/features/terrain/querying.py | 83 +-
tests/lettuce/features/terrain/steps.py | 18 +-
tests/lettuce/features/terrain/terrain.py | 63 +-
tests/lettuce/features/terrain/transfer.py | 4 +-
tests/lettuce/features/xfrin_bind10.feature | 24 +-
tests/lettuce/run_lettuce.sh | 25 +
tests/lettuce/setup_intree_bind10.sh.in | 4 +-
tests/system/.gitignore | 2 +
tests/system/bindctl/nsx1/.gitignore | 3 +
.../system/bindctl/nsx1/b10-config.db.template.in | 3 -
tests/system/bindctl/tests.sh | 106 +-
tests/system/glue/.gitignore | 1 +
tests/system/glue/nsx1/.gitignore | 3 +
tests/system/glue/nsx1/b10-config.db.in | 11 +-
tests/system/ixfr/.gitignore | 8 +
tests/system/ixfr/b10-config.db.in | 10 +
tests/system/ixfr/in-1/.gitignore | 1 +
tests/system/ixfr/in-2/.gitignore | 1 +
tests/system/ixfr/in-2/ns1/.gitignore | 1 +
tests/system/ixfr/in-2/nsx2/.gitignore | 1 +
tests/system/ixfr/in-2/tests.sh | 2 +-
tests/system/ixfr/in-3/.gitignore | 1 +
tests/system/ixfr/in-4/.gitignore | 1 +
tests/system/start.pl | 7 +-
tests/tools/badpacket/.gitignore | 1 +
tests/tools/badpacket/scan.cc | 3 +-
tests/tools/badpacket/tests/.gitignore | 1 +
tests/tools/perfdhcp/.gitignore | 1 +
tests/tools/perfdhcp/perfdhcp.c | 36 +
tools/git-obsolete-branch.py | 198 +++
tools/system_messages.py | 2 +-
672 files changed, 35896 insertions(+), 10206 deletions(-)
create mode 100644 .gitignore
create mode 100644 compatcheck/.gitignore
delete mode 100755 compatcheck/sqlite3-difftbl-check.py.in
delete mode 100755 depcomp
create mode 100644 dns++.pc.in
create mode 100644 doc/.gitignore
create mode 100644 doc/devel/01-dns.dox
create mode 100644 doc/devel/02-dhcp.dox
create mode 100644 doc/devel/mainpage.dox
create mode 100644 doc/images/isc-logo.png
create mode 100644 ext/LICENSE_1_0.txt
delete mode 100755 install-sh
create mode 100644 m4macros/.gitignore
delete mode 100755 missing
create mode 100644 src/bin/auth/.gitignore
create mode 100644 src/bin/auth/benchmarks/.gitignore
delete mode 100644 src/bin/auth/change_user.cc
delete mode 100644 src/bin/auth/change_user.h
create mode 100644 src/bin/auth/tests/.gitignore
delete mode 100644 src/bin/auth/tests/change_user_unittest.cc
create mode 100644 src/bin/auth/tests/config_syntax_unittest.cc
create mode 100644 src/bin/auth/tests/datasrc_util.cc
create mode 100644 src/bin/auth/tests/datasrc_util.h
create mode 100644 src/bin/bind10/.gitignore
create mode 100644 src/bin/bind10/tests/.gitignore
create mode 100644 src/bin/bindctl/.gitignore
create mode 100644 src/bin/bindctl/command_sets.py
create mode 100644 src/bin/bindctl/tests/.gitignore
create mode 100644 src/bin/cfgmgr/.gitignore
create mode 100644 src/bin/cfgmgr/tests/.gitignore
create mode 100644 src/bin/cmdctl/.gitignore
create mode 100644 src/bin/cmdctl/tests/.gitignore
create mode 100644 src/bin/dbutil/.gitignore
create mode 100644 src/bin/dbutil/Makefile.am
create mode 100644 src/bin/dbutil/b10-dbutil.8
create mode 100644 src/bin/dbutil/b10-dbutil.xml
create mode 100755 src/bin/dbutil/dbutil.py.in
create mode 100644 src/bin/dbutil/dbutil_messages.mes
create mode 100755 src/bin/dbutil/run_dbutil.sh.in
create mode 100644 src/bin/dbutil/tests/.gitignore
create mode 100644 src/bin/dbutil/tests/Makefile.am
create mode 100755 src/bin/dbutil/tests/dbutil_test.sh.in
create mode 100644 src/bin/dbutil/tests/testdata/Makefile.am
create mode 100644 src/bin/dbutil/tests/testdata/README
create mode 100644 src/bin/dbutil/tests/testdata/corrupt.sqlite3
create mode 100644 src/bin/dbutil/tests/testdata/empty_schema.sqlite3
create mode 100644 src/bin/dbutil/tests/testdata/empty_v1.sqlite3
create mode 100644 src/bin/dbutil/tests/testdata/empty_version.sqlite3
create mode 100644 src/bin/dbutil/tests/testdata/invalid_v1.sqlite3
create mode 100644 src/bin/dbutil/tests/testdata/new_v1.sqlite3
create mode 100644 src/bin/dbutil/tests/testdata/no_schema.sqlite3
create mode 100644 src/bin/dbutil/tests/testdata/old_v1.sqlite3
create mode 100644 src/bin/dbutil/tests/testdata/too_many_version.sqlite3
create mode 100644 src/bin/dbutil/tests/testdata/v2_0.sqlite3
create mode 100644 src/bin/ddns/.gitignore
create mode 100644 src/bin/dhcp4/.gitignore
create mode 100644 src/bin/dhcp4/tests/.gitignore
create mode 100644 src/bin/host/.gitignore
create mode 100644 src/bin/loadzone/.gitignore
create mode 100644 src/bin/loadzone/tests/correct/.gitignore
create mode 100644 src/bin/loadzone/tests/error/.gitignore
create mode 100644 src/bin/msgq/.gitignore
create mode 100644 src/bin/msgq/tests/.gitignore
create mode 100644 src/bin/resolver/.gitignore
create mode 100644 src/bin/resolver/common.cc
create mode 100644 src/bin/resolver/common.h
create mode 100644 src/bin/resolver/tests/.gitignore
create mode 100644 src/bin/sockcreator/.gitignore
create mode 100644 src/bin/sockcreator/tests/.gitignore
create mode 100644 src/bin/stats/.gitignore
create mode 100644 src/bin/tests/.gitignore
create mode 100644 src/bin/usermgr/.gitignore
create mode 100644 src/bin/xfrin/.gitignore
create mode 100644 src/bin/xfrin/tests/.gitignore
create mode 100644 src/bin/xfrout/.gitignore
create mode 100644 src/bin/xfrout/tests/.gitignore
create mode 100644 src/bin/zonemgr/.gitignore
create mode 100644 src/bin/zonemgr/tests/.gitignore
create mode 100644 src/lib/acl/tests/.gitignore
create mode 100644 src/lib/asiodns/.gitignore
create mode 100644 src/lib/asiodns/logger.cc
create mode 100644 src/lib/asiodns/logger.h
create mode 100644 src/lib/asiodns/sync_udp_server.cc
create mode 100644 src/lib/asiodns/sync_udp_server.h
create mode 100644 src/lib/asiodns/tests/.gitignore
create mode 100644 src/lib/asiodns/tests/dns_service_unittest.cc
delete mode 100644 src/lib/asiodns/tests/io_service_unittest.cc
create mode 100644 src/lib/asiolink/tests/.gitignore
create mode 100644 src/lib/bench/example/.gitignore
create mode 100644 src/lib/bench/tests/.gitignore
create mode 100644 src/lib/cache/.gitignore
create mode 100644 src/lib/cache/tests/.gitignore
create mode 100644 src/lib/cc/.gitignore
create mode 100644 src/lib/cc/tests/.gitignore
create mode 100644 src/lib/config/.gitignore
create mode 100644 src/lib/config/tests/.gitignore
create mode 100644 src/lib/config/tests/testdata/.gitignore
create mode 100644 src/lib/config/tests/testdata/spec39.spec
create mode 100644 src/lib/cryptolink/tests/.gitignore
create mode 100644 src/lib/datasrc/.gitignore
create mode 100644 src/lib/datasrc/memory_datasrc_link.cc
create mode 100644 src/lib/datasrc/rbnode_rrset.h
create mode 100644 src/lib/datasrc/sqlite3_accessor_link.cc
create mode 100644 src/lib/datasrc/tests/.gitignore
create mode 100644 src/lib/datasrc/tests/faked_nsec3.cc
create mode 100644 src/lib/datasrc/tests/faked_nsec3.h
create mode 100644 src/lib/datasrc/tests/rbnode_rrset_unittest.cc
create mode 100644 src/lib/datasrc/tests/test_client.cc
create mode 100644 src/lib/datasrc/tests/test_client.h
create mode 100644 src/lib/datasrc/tests/testdata/.gitignore
create mode 100644 src/lib/datasrc/tests/testdata/contexttest.zone
create mode 100644 src/lib/datasrc/tests/testdata/example.org.nsec3-signed
create mode 100644 src/lib/datasrc/tests/testdata/example.org.nsec3-signed-noparam
create mode 100644 src/lib/datasrc/tests/testdata/new_minor_schema.sqlite3
create mode 100644 src/lib/datasrc/tests/testdata/newschema.sqlite3
create mode 100644 src/lib/datasrc/tests/testdata/oldschema.sqlite3
copy src/lib/{dns => datasrc}/tests/testdata/rrset_toWire1 (100%)
create mode 100644 src/lib/datasrc/tests/testdata/rrset_toWire2
delete mode 100644 src/lib/datasrc/tests/testdata/rwtest.sqlite3
delete mode 100644 src/lib/datasrc/tests/testdata/test.sqlite3.nodiffs
create mode 100644 src/lib/datasrc/tests/zone_finder_context_unittest.cc
create mode 100644 src/lib/datasrc/zone_finder_context.cc
create mode 100644 src/lib/dhcp/tests/.gitignore
create mode 100644 src/lib/dns/.gitignore
create mode 100644 src/lib/dns/benchmarks/.gitignore
create mode 100644 src/lib/dns/benchmarks/message_renderer_bench.cc
create mode 100644 src/lib/dns/benchmarks/oldmessagerenderer.cc
create mode 100644 src/lib/dns/benchmarks/oldmessagerenderer.h
create mode 100644 src/lib/dns/labelsequence.cc
create mode 100644 src/lib/dns/labelsequence.h
create mode 100644 src/lib/dns/name_internal.h
create mode 100644 src/lib/dns/nsec3hash.cc
create mode 100644 src/lib/dns/nsec3hash.h
create mode 100644 src/lib/dns/python/nsec3hash_python.cc
create mode 100644 src/lib/dns/python/nsec3hash_python.h
create mode 100644 src/lib/dns/python/nsec3hash_python_inc.cc
create mode 100644 src/lib/dns/python/tests/nsec3hash_python_test.py
create mode 100644 src/lib/dns/rdata/generic/detail/nsec3param_common.cc
create mode 100644 src/lib/dns/rdata/generic/detail/nsec3param_common.h
create mode 100644 src/lib/dns/rdata/generic/sshfp_44.cc
create mode 100644 src/lib/dns/rdata/generic/sshfp_44.h
create mode 100644 src/lib/dns/tests/.gitignore
create mode 100644 src/lib/dns/tests/labelsequence_unittest.cc
create mode 100644 src/lib/dns/tests/nsec3hash_unittest.cc
create mode 100644 src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
create mode 100644 src/lib/dns/tests/rdata_sshfp_unittest.cc
create mode 100644 src/lib/dns/tests/testdata/.gitignore
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire16.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3param_fromWire11.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3param_fromWire13.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec_fromWire16.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_sshfp_fromWire
create mode 100644 src/lib/dns/tests/testdata/rdata_sshfp_fromWire1.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_sshfp_fromWire2
create mode 100644 src/lib/dns/tests/testdata/rdata_sshfp_fromWire2.spec
create mode 100644 src/lib/exceptions/tests/.gitignore
create mode 100644 src/lib/log/compiler/.gitignore
create mode 100644 src/lib/log/tests/.gitignore
create mode 100644 src/lib/log/tests/message_initializer_1_unittest.cc
create mode 100644 src/lib/log/tests/message_initializer_1a_unittest.cc
create mode 100644 src/lib/log/tests/message_initializer_2_unittest.cc
delete mode 100644 src/lib/log/tests/message_initializer_unittest.cc
delete mode 100644 src/lib/log/tests/message_initializer_unittest_2.cc
create mode 100644 src/lib/log/tests/run_initializer_unittests.cc
create mode 100644 src/lib/nsas/.gitignore
create mode 100644 src/lib/nsas/tests/.gitignore
create mode 100644 src/lib/python/.gitignore
create mode 100644 src/lib/python/isc/cc/tests/.gitignore
create mode 100644 src/lib/python/isc/config/tests/.gitignore
create mode 100644 src/lib/python/isc/datasrc/tests/.gitignore
create mode 100644 src/lib/python/isc/datasrc/tests/testdata/new_minor_schema.sqlite3
create mode 100644 src/lib/python/isc/datasrc/tests/testdata/newschema.sqlite3
create mode 100644 src/lib/python/isc/datasrc/tests/testdata/oldschema.sqlite3
delete mode 100644 src/lib/python/isc/datasrc/tests/testdata/test.sqlite3.nodiffs
create mode 100644 src/lib/python/isc/log/tests/.gitignore
create mode 100644 src/lib/python/isc/log_messages/dbutil_messages.py
create mode 100644 src/lib/python/isc/log_messages/server_common_messages.py
create mode 100644 src/lib/python/isc/log_messages/work/.gitignore
create mode 100644 src/lib/python/isc/notify/tests/.gitignore
create mode 100644 src/lib/python/isc/server_common/Makefile.am
copy src/lib/python/isc/{bind10 => server_common}/__init__.py (100%)
create mode 100644 src/lib/python/isc/server_common/server_common_messages.mes
create mode 100644 src/lib/python/isc/server_common/tests/Makefile.am
create mode 100644 src/lib/python/isc/server_common/tests/tsig_keyring_test.py
create mode 100644 src/lib/python/isc/server_common/tsig_keyring.py
create mode 100644 src/lib/python/isc/testutils/ccsession_mock.py
create mode 100644 src/lib/python/isc/util/cio/Makefile.am
rename src/lib/python/isc/util/{io => cio}/__init__.py (100%)
create mode 100644 src/lib/python/isc/util/cio/socketsession.py
rename src/lib/python/isc/util/{io => cio}/socketsession_inc.cc (100%)
create mode 100644 src/lib/python/isc/util/cio/socketsession_python.cc
rename src/lib/python/isc/util/{io => cio}/socketsession_python.h (100%)
rename src/lib/python/isc/util/{io => cio}/socketsessionforwarder_inc.cc (100%)
create mode 100644 src/lib/python/isc/util/cio/socketsessionforwarder_python.cc
rename src/lib/python/isc/util/{io => cio}/socketsessionforwarder_python.h (100%)
rename src/lib/python/isc/util/{io => cio}/socketsessionreceiver_inc.cc (100%)
create mode 100644 src/lib/python/isc/util/cio/socketsessionreceiver_python.cc
rename src/lib/python/isc/util/{io => cio}/socketsessionreceiver_python.h (100%)
rename src/lib/python/isc/util/{io => cio}/tests/Makefile.am (100%)
create mode 100644 src/lib/python/isc/util/cio/tests/socketsession_test.py
delete mode 100644 src/lib/python/isc/util/io/Makefile.am
delete mode 100644 src/lib/python/isc/util/io/socketsession.py
delete mode 100644 src/lib/python/isc/util/io/socketsession_python.cc
delete mode 100644 src/lib/python/isc/util/io/socketsessionforwarder_python.cc
delete mode 100644 src/lib/python/isc/util/io/socketsessionreceiver_python.cc
delete mode 100644 src/lib/python/isc/util/io/tests/socketsession_test.py
create mode 100644 src/lib/resolve/.gitignore
create mode 100644 src/lib/resolve/tests/.gitignore
create mode 100644 src/lib/server_common/.gitignore
create mode 100644 src/lib/server_common/socket_request.cc
create mode 100644 src/lib/server_common/socket_request.h
create mode 100644 src/lib/server_common/tests/.gitignore
create mode 100644 src/lib/server_common/tests/socket_requestor_test.cc
create mode 100644 src/lib/statistics/tests/.gitignore
create mode 100644 src/lib/testutils/socket_request.h
create mode 100644 src/lib/testutils/testdata/.gitignore
create mode 100755 src/lib/testutils/testdata/auth_test.sqlite3
create mode 100644 src/lib/testutils/testdata/nsec3query_fromWire.spec
create mode 100644 src/lib/testutils/testdata/nsec3query_nodnssec_fromWire.spec
create mode 100644 src/lib/testutils/testdata/rfc5155-example.zone.signed
create mode 100644 src/lib/testutils/testdata/rwtest.sqlite3
create mode 100644 src/lib/util/python/.gitignore
create mode 100644 src/lib/util/tests/.gitignore
create mode 100644 src/lib/util/unittests/resource.cc
create mode 100644 src/lib/util/unittests/resource.h
create mode 100644 src/lib/xfr/tests/.gitignore
create mode 100644 tests/lettuce/.gitignore
create mode 100644 tests/lettuce/configurations/.gitignore
create mode 100644 tests/lettuce/configurations/bindctl/bindctl.config.orig
create mode 100644 tests/lettuce/configurations/bindctl_commands.config.orig
create mode 100644 tests/lettuce/configurations/default.config
create mode 100644 tests/lettuce/configurations/example.org.inmem.config
create mode 100644 tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf
create mode 100644 tests/lettuce/configurations/multi_instance/.gitignore
create mode 100644 tests/lettuce/configurations/multi_instance/multi_auth.config.orig
create mode 100644 tests/lettuce/configurations/nsec3/nsec3_auth.config
create mode 100644 tests/lettuce/configurations/nsec3/rfc5155-example.zone.signed
create mode 100644 tests/lettuce/configurations/resolver/.gitignore
create mode 100644 tests/lettuce/configurations/resolver/resolver_basic.config.orig
create mode 100644 tests/lettuce/data/.gitignore
create mode 100644 tests/lettuce/data/commands/bad_command
create mode 100644 tests/lettuce/data/commands/directives
copy AUTHORS => tests/lettuce/data/commands/empty (100%)
create mode 100644 tests/lettuce/data/commands/nested
create mode 100644 tests/lettuce/data/commands/nested1
create mode 100644 tests/lettuce/data/example.org
create mode 100644 tests/lettuce/data/ixfr-out/.gitignore
delete mode 100644 tests/lettuce/data/ixfr-out/zones.slite3
create mode 100644 tests/lettuce/data/ixfr-out/zones.sqlite3
create mode 100644 tests/lettuce/features/bindctl_commands.feature
create mode 100644 tests/lettuce/features/default.feature
create mode 100644 tests/lettuce/features/inmemory_over_sqlite3.feature
create mode 100644 tests/lettuce/features/multi_instance.feature
create mode 100644 tests/lettuce/features/nsec3_auth.feature
create mode 100644 tests/lettuce/features/queries.feature
create mode 100644 tests/lettuce/features/resolver_basic.feature
create mode 100644 tests/lettuce/features/terrain/.gitignore
create mode 100755 tests/lettuce/run_lettuce.sh
mode change 100755 => 100644 tests/lettuce/setup_intree_bind10.sh.in
create mode 100644 tests/system/.gitignore
create mode 100644 tests/system/bindctl/nsx1/.gitignore
create mode 100644 tests/system/glue/.gitignore
create mode 100644 tests/system/glue/nsx1/.gitignore
create mode 100644 tests/system/ixfr/.gitignore
create mode 100644 tests/system/ixfr/in-1/.gitignore
create mode 100644 tests/system/ixfr/in-2/.gitignore
create mode 100644 tests/system/ixfr/in-2/ns1/.gitignore
create mode 100644 tests/system/ixfr/in-2/nsx2/.gitignore
create mode 100644 tests/system/ixfr/in-3/.gitignore
create mode 100644 tests/system/ixfr/in-4/.gitignore
create mode 100644 tests/tools/badpacket/.gitignore
create mode 100644 tests/tools/badpacket/tests/.gitignore
create mode 100644 tests/tools/perfdhcp/.gitignore
create mode 100755 tools/git-obsolete-branch.py
-----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..da9d3b4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,31 @@
+*.la
+*.lo
+*.o
+.deps/
+.libs/
+__pycache__/
+Makefile
+Makefile.in
+TAGS
+
+/aclocal.m4
+/autom4te.cache/
+/config.guess
+/config.h
+/config.h.in
+/config.log
+/config.report
+/config.status
+/config.sub
+/configure
+/cscope.files
+/cscope.out
+/depcomp
+/install-sh
+/libtool
+/ltmain.sh
+/missing
+/py-compile
+/stamp-h1
+
+/dns++.pc
diff --git a/COPYING b/COPYING
index 557bdfb..63717af 100644
--- a/COPYING
+++ b/COPYING
@@ -11,3 +11,9 @@ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER 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 ext/asio and ext/coroutine code is externally maintained and
+distributed under the Boost Software License, Version 1.0.
+(See accompanying file ext/LICENSE_1_0.txt.)
diff --git a/ChangeLog b/ChangeLog
index e09ab6d..eb9441c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,8 +1,511 @@
-3XX. [func] tomek
+433. [func] tomek
libdhcp++: Option6 and Pkt6 now follow the same design as
options and packet for DHCPv4. General code refactoring after
end of 2011 year release.
- (Trac #1540, git TBD)
+ (Trac #1540, git be560f4516ae9b0bbbad1740a519feed99c04170TBD)
+
+432. [bug]* muks
+ BIND 10 now installs its header files in a BIND 10 specific
+ sub-directory in the install prefix.
+ (Trac #1930, git fcf2f08db9ebc2198236bfa25cf73286821cba6b)
+
+431. [func]* muks
+ BIND 10 no longer starts b10-stats-httpd by default.
+ (Trac #1885, git 5c8bbd7ab648b6b7c48e366e7510dedca5386f6c)
+
+430. [bug] jelte
+ When displaying configuration data, bindctl no longer treats
+ optional list items as an error, but shows them as an empty list.
+ (Trac #1520, git 0f18039bc751a8f498c1f832196e2ecc7b997b2a)
+
+429. [func] jelte
+ Added an 'execute' component to bindctl, which executes either a set
+ of commands from a file or a built-in set of commands. Currently,
+ only 'init_authoritative_server' is provided as a built-in set, but
+ it is expected that more will be added later.
+ (Trac #1843, git 551657702a4197ef302c567b5c0eaf2fded3e121)
+
+428. [bug] marcin
+ perfdhcp: bind to local address to allow reception of replies from IPv6
+ DHCP servers.
+ (Trac #1908, git 597e059afaa4a89e767f8f10d2a4d78223af3940)
+
+427. [bug] jinmei
+ libdatasrc, b10-xfrin: the zone updater for database-based data
+ sources now correctly distinguishes NSEC3-related RRs (NSEC3 and
+ NSEC3-covering RRSIG) from others, and the SQLite3 implementation
+ now manipulates them in the separate table for the NSEC3 namespace.
+ As a result b10-xfrin now correctly updates NSEC3-signed zones by
+ inbound zone transfers.
+ (Trac #1891, git 672f129700dae33b701bb02069cf276238d66be3)
+
+426. [bug] vorner
+ The NSEC3 records are now included when transferring a signed zone out.
+ (Trac #1782, git 36efa7d10ecc4efd39d2ce4dfffa0cbdeffa74b0)
+
+425. [func]* muks
+ Don't autostart b10-auth, b10-xfrin, b10-xfrout and b10-zonemgr in
+ the default configuration.
+ (Trac #1818, git 31de885ba0409f54d9a1615eff5a4b03ed420393)
+
+424. [bug] jelte
+ Fixed a bug in bindctl where in some cases, configuration settings
+ in a named set could disappear, if a child element is modified.
+ (Trac #1491, git 00a36e752802df3cc683023d256687bf222e256a)
+
+423. [bug] jinmei
+ The database based zone iterator now correctly resets mixed TTLs
+ of the same RRset (when that happens) to the lowest one. The
+ previous implementation could miss lower ones if it appears in a
+ later part of the RRset.
+ (part of Trac #1791, git f1f0bc00441057e7050241415ee0367a09c35032)
+
+422. [bug] jinmei
+ The database based zone iterator now separates RRSIGs of the same
+ name and type but for different covered types.
+ (part of Trac #1791, git b4466188150a50872bc3c426242bc7bba4c5f38d)
+
+421. [build] jinmei
+ Made sure BIND 10 can be built with clang++ 3.1. (It failed on
+ MacOS 10.7 using Xcode 4.3, but it's more likely to be a matter of
+ clang version.)
+ (Trac #1773, git ceaa247d89ac7d97594572bc17f005144c5efb8d)
+
+420. [bug]* jinmei, stephen
+ Updated the DB schema used in the SQLite3 data source so it can
+ use SQL indices more effectively. The previous schema had several
+ issues in this sense and could be very slow for some queries on a
+ very large zone (especially for negative answers). This change
+ requires a major version up of the schema; use b10-dbutil to
+ upgrade existing database files. Note: 'make install' will fail
+ unless old DB files installed in the standard location have been
+ upgraded.
+ (Trac #324, git 8644866497053f91ada4e99abe444d7876ed00ff)
+
+419. [bug] jelte
+ JSON handler has been improved; escaping now works correctly
+ (including quotes in strings), and it now rejects more types of
+ malformed input.
+ (Trac #1626, git 3b09268518e4e90032218083bcfebf7821be7bd5)
+
+418. [bug] vorner
+ Fixed crash in bindctl when config unset was called.
+ (Trac #1715, git 098da24dddad497810aa2787f54126488bb1095c)
+
+417. [bug] jelte
+ The notify-out code now looks up notify targets in their correct
+ zones (and no longer just in the zone that the notify is about).
+ (Trac #1535, git 66300a3c4769a48b765f70e2d0dbf8bbb714435b)
+
+416. [func]* jelte
+ The implementations of ZoneFinder::find() now throw an OutOfZone
+ exception when the name argument is not in or below the zone this
+ zonefinder contains.
+ (Trac #1535, git 66300a3c4769a48b765f70e2d0dbf8bbb714435b)
+
+bind10-devel-20120329 released on March 29, 2012
+
+415. [doc] jinmei, jreed
+ BIND 10 Guide updated to now describe the in-memory data source
+ configurations for b10-auth.
+ (Trac #1732, git 434d8db8dfcd23a87b8e798e5702e91f0bbbdcf6)
+
+414. [bug] jinmei
+ b10-auth now correctly handles delegation from an unsigned zone
+ (defined in the in-memory data source) when the query has DNSSEC
+ DO bit on. It previously returned SERVFAIL.
+ (Trac #1836, git 78bb8f4b9676d6345f3fdd1e5cc89039806a9aba)
+
+413. [func] stephen, jelte
+ Created a new tool b10-dbutil, that can check and upgrade database
+ schemas, to be used when incompatible changes are introduced in the
+ backend database schema. Currently it only supports sqlite3 databases.
+ Note: there's no schema change that requires this utility as of
+ the March 29th release. While running it shouldn't break
+ an existing database file, it should be even more advisable not to
+ run it at the moment.
+ (Trac #963, git 49ba2cf8ac63246f389ab5e8ea3b3d081dba9adf)
+
+412. [func] jelte
+ Added a command-line option '--clear-config' to bind10, which causes
+ the system to create a backup of the existing configuration database
+ file, and start out with a clean default configuration. This can be
+ used if the configuration file is corrupted to the point where it
+ cannot be read anymore, and BIND 10 refuses to start. The name of
+ the backup file can be found in the logs (CFGMGR_RENAMED_CONFIG_FILE).
+ (Trac #1443, git 52b36c921ee59ec69deefb6123cbdb1b91dc3bc7)
+
+411. [func] muks
+ Add a -i/--no-kill command-line argument to bind10, which stops
+ it from sending SIGTERM and SIGKILL to other b10 processes when
+ they're shutting down.
+ (Trac #1819, git 774554f46b20ca5ec2ef6c6d5e608114f14e2102)
+
+410. [bug] jinmei
+ Python CC library now ensures write operations transmit all given
+ data (unless an error happens). Previously it didn't check the
+ size of transmitted data, which could result in partial write on
+ some systems (notably on OpenBSD) and subsequently cause system
+ hang up or other broken state. This fix specifically solves start
+ up failure on OpenBSD.
+ (Trac #1829, git 5e5a33213b60d89e146cd5e47d65f3f9833a9297)
+
+409. [bug] jelte
+ Fixed a parser bug in bindctl that could make bindctl crash. Also
+ improved 'command help' output; argument order is now shown
+ correctly, and parameter descriptions are shown as well.
+ (Trac #1172, git bec26c6137c9b0a59a3a8ca0f55a17cfcb8a23de)
+
+408. [bug] stephen, jinmei
+ b10-auth now filters out duplicate RRsets when building a
+ response message using the new query handling logic. It's
+ currently only used with the in-memory data source, but will
+ also be used for others soon.
+ (Trac #1688, git b77baca56ffb1b9016698c00ae0a1496d603d197)
+
+407. [build] haikuo
+ Remove "--enable-boost-threads" switch in configure command. This
+ thread lock mechanism is useless for bind10 and causes performance
+ hits.
+ (Trac #1680, git 9c4d0cadf4adc802cc41a2610dc2c30b25aad728)
+
+406. [bug] muks
+ On platforms such as OpenBSD where pselect() is not available,
+ make a wrapper around select() in perfdhcp.
+ (Trac #1639, git 6ea0b1d62e7b8b6596209291aa6c8b34b8e73191)
+
+405. [bug] jinmei
+ Make sure disabling Boost threads if the default configuration is
+ to disable it for the system. This fixes a crash and hang up
+ problem on OpenBSD, where the use of Boost thread could be
+ different in different program files depending on the order of
+ including various header files, and could introduce inconsistent
+ states between a library and a program. Explicitly forcing the
+ original default throughout the BIND 10 build environment will
+ prevent this from happening.
+ (Trac #1727, git 23f9c3670b544c5f8105958ff148aeba050bc1b4)
+
+404. [bug] naokikambe
+ The statistic counters are now properly accumulated across multiple
+ instances of b10-auth (if there are multiple instances), instead of
+ providing result for random instance.
+ (Trac #1751, git 3285353a660e881ec2b645e1bc10d94e5020f357)
+
+403. [build]* jelte
+ The configure option for botan (--with-botan=PATH) is replaced by
+ --with-botan-config=PATH, which takes a full path to a botan-config
+ script, instead of the botan 'install' directory. Also, if not
+ provided, configure will try out config scripts and pkg-config
+ options until it finds one that works.
+ (Trac #1640, git 582bcd66dbd8d39f48aef952902f797260280637)
+
+402. [func] jelte
+ b10-xfrout now has a visible command to send out notifies for
+ a given zone, callable from bindctl. Xfrout notify <zone> [class]
+ (Trac #1321, git 0bb258f8610620191d75cfd5d2308b6fc558c280)
+
+401. [func]* jinmei
+ libdns++: updated the internal implementation of the
+ MessageRenderer class. This is mostly a transparent change, but
+ the new version now doesn't allow changing compression mode in the
+ middle of rendering (which shouldn't be an issue in practice).
+ On the other hand, name compression performance was significantly
+ improved: depending on the number of names, micro benchmark tests
+ showed the new version is several times faster than the previous
+ version .
+ (Trac #1603, git 9a2a86f3f47b60ff017ce1a040941d0c145cfe16)
+
+400. [bug] stephen
+ Fix crash on Max OS X 10.7 by altering logging so as not to allocate
+ heap storage in the static initialization of logging objects.
+ (Trac #1698, git a8e53be7039ad50d8587c0972244029ff3533b6e)
+
+399. [func] muks
+ Add support for the SSHFP RR type (RFC 4255).
+ (Trac #1136, git ea5ac57d508a17611cfae9d9ea1c238f59d52c51)
+
+398. [func] jelte
+ The b10-xfrin module now logs more information on successful
+ incoming transfers. In the case of IXFR, it logs the number of
+ changesets, and the total number of added and deleted resource
+ records. For AXFR (or AXFR-style IXFR), it logs the number of
+ resource records. In both cases, the number of overhead DNS
+ messages, runtime, amount of wire data, and transfer speed are logged.
+ (Trac #1280, git 2b01d944b6a137f95d47673ea8367315289c205d)
+
+397. [func] muks
+ The boss process now gives more helpful description when a
+ sub-process exits due to a signal.
+ (Trac #1673, git 1cd0d0e4fc9324bbe7f8593478e2396d06337b1e)
+
+396. [func]* jinmei
+ libdatasrc: change the return type of ZoneFinder::find() so it can
+ contain more context of the search, which can be used for
+ optimizing post find() processing. A new method getAdditional()
+ is added to it for finding additional RRsets based on the result
+ of find(). External behavior shouldn't change. The query
+ handling code of b10-auth now uses the new interface.
+ (Trac #1607, git 2e940ea65d5b9f371c26352afd9e66719c38a6b9)
+
+395. [bug] jelte
+ The log message compiler now errors (resulting in build failures) if
+ duplicate log message identifiers are found in a single message file.
+ Renamed one duplicate that was found (RESOLVER_SHUTDOWN, renamed to
+ RESOLVER_SHUTDOWN_RECEIVED).
+ (Trac #1093, git f537c7e12fb7b25801408f93132ed33410edae76)
+ (Trac #1741, git b8960ab85c717fe70ad282e0052ac0858c5b57f7)
+
+394. [bug] jelte
+ b10-auth now catches any exceptions during response building; if any
+ datasource either throws an exception or causes an exception to be
+ thrown, the message processing code will now catch it, log a debug
+ message, and return a SERVFAIL response.
+ (Trac #1612, git b5740c6b3962a55e46325b3c8b14c9d64cf0d845)
+
+393. [func] jelte
+ Introduced a new class LabelSequence in libdns++, which provides
+ lightweight accessor functionality to the Name class, for more
+ efficient comparison of parts of names.
+ (Trac #1602, git b33929ed5df7c8f482d095e96e667d4a03180c78)
+
+392. [func]* jinmei
+ libdns++: revised the (Abstract)MessageRenderer class so that it
+ has a default internal buffer and the buffer can be temporarily
+ switched. The constructor interface was modified, and a new
+ method setBuffer() was added.
+ (Trac #1697, git 9cabc799f2bf9a3579dae7f1f5d5467c8bb1aa40)
+
+391. [bug]* vorner
+ The long time unused configuration options of Xfrout "log_name",
+ "log_file", "log_severity", "log_version" and "log_max_bytes" were
+ removed, as they had no effect (Xfrout uses the global logging
+ framework). However, if you have them set, you need to remove
+ them from the configuration file or the configuration will be
+ rejected.
+ (Trac #1090, git ef1eba02e4cf550e48e7318702cff6d67c1ec82e)
+
+bind10-devel-20120301 released on March 1, 2012
+
+390. [bug] vorner
+ The UDP IPv6 packets are now correctly fragmented for maximum
+ guaranteed MTU, so they won't get lost because being too large
+ for some hop.
+ (Trac #1534, git ff013364643f9bfa736b2d23fec39ac35872d6ad)
+
+389. [func]* vorner
+ Xfrout now uses the global TSIG keyring, instead of its own. This
+ means the keys need to be set only once (in tsig_keys/keys).
+ However, the old configuration of Xfrout/tsig_keys need to be
+ removed for Xfrout to work.
+ (Trac #1643, git 5a7953933a49a0ddd4ee1feaddc908cd2285522d)
+
+388. [func] jreed
+ Use prefix "sockcreator-" for the private temporary directory
+ used for b10-sockcreator communication.
+ (git b98523c1260637cb33436964dc18e9763622a242)
+
+387. [build] muks
+ Accept a --without-werror configure switch so that some builders can
+ disable the use of -Werror in CFLAGS when building.
+ (Trac #1671, git 8684a411d7718a71ad9fb616f56b26436c4f03e5)
+
+386. [bug] jelte
+ Upon initial sqlite3 database creation, the 'diffs' table is now
+ always created. This already happened most of the time, but there
+ are a few cases where it was skipped, resulting in potential errors
+ in xfrout later.
+ (Trac #1717, git 30d7686cb6e2fa64866c983e0cfb7b8fabedc7a2)
+
+385. [bug] jinmei
+ libdns++: masterLoad() didn't accept comments placed at the end of
+ an RR. Due to this the in-memory data source cannot load a master
+ file for a signed zone even if it's preprocessed with BIND 9's
+ named-compilezone.
+ Note: this fix is considered temporary and still only accepts some
+ limited form of such comments. The main purpose is to allow the
+ in-memory data source to load any signed or unsigned zone files as
+ long as they are at least normalized with named-compilezone.
+ (Trac #1667, git 6f771b28eea25c693fe93a0e2379af924464a562)
+
+384. [func] jinmei, jelte, vorner, haikuo, kevin
+ b10-auth now supports NSEC3-signed zones in the in-memory data
+ source.
+ (Trac #1580, #1581, #1582, #1583, #1584, #1585, #1587, and
+ other related changes to the in-memory data source)
+
+383. [build] jinmei
+ Fixed build failure on MacOS 10.7 (Lion) due to the use of
+ IPV6_PKTINFO; the OS requires a special definition to make it
+ visible to the compiler.
+ (Trac #1633, git 19ba70c7cc3da462c70e8c4f74b321b8daad0100)
+
+382. [func] jelte
+ b10-auth now also experimentally supports statistics counters of
+ the rcode responses it sends. The counters can be shown as
+ rcode.<code name>, where code name is the lowercase textual
+ representation of the rcode (e.g. "noerror", "formerr", etc.).
+ Same note applies as for opcodes, see changelog entry 364.
+ (Trac #1613, git e98da500d7b02e11347431a74f2efce5a7d622aa)
+
+381. [bug] jinmei
+ b10-auth: honor the DNSSEC DO bit in the new query handler.
+ (Trac #1695, git 61f4da5053c6a79fbc162fb16f195cdf8f94df64)
+
+380. [bug] jinmei
+ libdns++: miscellaneous bug fixes for the NSECPARAM RDATA
+ implementation, including incorrect handling for empty salt and
+ incorrect comparison logic.
+ (Trac #1638, git 966c129cc3c538841421f1e554167d33ef9bdf25)
+
+379. [bug] jelte
+ Configuration commands in bindctl now check for list indices if
+ the 'identifier' argument points to a child element of a list
+ item. Previously, it was possible to 'get' non-existent values
+ by leaving out the index, e.g. "config show Auth/listen_on/port,
+ which should be config show Auth/listen_on[<index>]/port, since
+ Auth/listen_on is a list. The command without an index will now
+ show an error. It is still possible to show/set the entire list
+ ("config show Auth/listen_on").
+ (Trac #1649, git 003ca8597c8d0eb558b1819dbee203fda346ba77)
+
+378. [func] vorner
+ It is possible to start authoritative server or resolver in multiple
+ instances, to use more than one core. Configuration is described in
+ the guide.
+ (Trac #1596, git 17f7af0d8a42a0a67a2aade5bc269533efeb840a)
+
+377. [bug] jinmei
+ libdns++: miscellaneous bug fixes for the NSEC and NSEC3 RDATA
+ implementation, including a crash in NSEC3::toText() for some RR
+ types, incorrect handling of empty NSEC3 salt, and incorrect
+ comparison logic in NSEC3::compare().
+ (Trac #1641, git 28ba8bd71ae4d100cb250fd8d99d80a17a6323a2)
+
+376. [bug] jinmei, vorner
+ The new query handling module of b10-auth did not handle type DS
+ query correctly: It didn't look for it in the parent zone, and
+ it incorrectly returned a DS from the child zone if it
+ happened to exist there. Both were corrected, and it now also
+ handles the case of having authority for the child and a grand
+ ancestor.
+ (Trac #1570, git 2858b2098a10a8cc2d34bf87463ace0629d3670e)
+
+375. [func] jelte
+ Modules now inform the system when they are stopping. As a result,
+ they are removed from the 'active modules' list in bindctl, which
+ can then inform the user directly when it tries to send them a
+ command or configuration update. Previously this would result
+ in a 'not responding' error instead of 'not running'.
+ (Trac #640, git 17e78fa1bb1227340aa9815e91ed5c50d174425d)
+
+374. [func]* stephen
+ Alter RRsetPtr and ConstRRsetPtr to point to AbstractRRset (instead
+ of RRset) to allow for specialised implementations of RRsets in
+ data sources.
+ (Trac #1604, git 3071211d2c537150a691120b0a5ce2b18d010239)
+
+373. [bug] jinmei
+ libdatasrc: the in-memory data source incorrectly rejected loading
+ a zone containing a CNAME RR with RRSIG and/or NSEC.
+ (Trac #1551, git 76f823d42af55ce3f30a0d741fc9297c211d8b38)
+
+372. [func] vorner
+ When the allocation of a socket fails for a different reason than the
+ socket not being provided by the OS, the b10-auth and b10-resolver
+ abort, as the system might be in inconsistent state after such error.
+ (Trac #1543, git 49ac4659f15c443e483922bf9c4f2de982bae25d)
+
+371. [bug] jelte
+ The new query handling module of b10-auth (currently only used with
+ the in-memory data source) now correctly includes the DS record (or
+ the denial of its existence if NSEC is used) when returning a
+ delegation from a signed zone.
+ (Trac #1573, git bd7a3ac98177573263950303d4b2ea7400781d0f)
+
+370. [func] jinmei
+ libdns++: a new class NSEC3Hash was introduced as a utility for
+ calculating NSEC3 hashes for various purposes. Python binding was
+ provided, too. Also fixed a small bug in the NSEC3PARAM RDATA
+ implementation that empty salt in text representation was
+ rejected.
+ (Trac #1575, git 2c421b58e810028b303d328e4e2f5b74ea124839)
+
+369. [func] vorner
+ The SocketRequestor provides more information about what error
+ happened when it throws, by using subclasses of the original
+ exception. This way a user not interested in the difference can
+ still use the original exception, while it can be recognized if
+ necessary.
+ (Trac #1542, git 2080e0316a339fa3cadea00e10b1ec4bc322ada0)
+
+368. [func]* jinmei
+ libdatasrc: the interface of ZoneFinder() was changed: WILDCARD
+ related result codes were deprecated and removed, and the
+ corresponding information is now provided via a separate accessor
+ method on FindResult. Other separate FindResult methods will
+ also tell the caller whether the zone is signed with NSEC or NSEC3
+ (when necessary and applicable).
+ (Trac #1611, git c175c9c06034b4118e0dfdbccd532c2ebd4ba7e8)
+
+367. [bug] jinmei
+ libdatasrc: in-memory data source could incorrectly reject to load
+ zones containing RRSIG records. For example, it didn't allow
+ RRSIG that covers a CNAME RR. This fix also makes sure find()
+ will return RRsets with RRSIGs if they are signed.
+ (Trac #1614, git e8241ea5a4adea1b42a60ee7f2c5cfb87301734c)
+
+366. [bug] vorner
+ Fixed problem where a directory named "io" conflicted with the python3
+ standard module "io" and caused the installation to fail. The
+ offending directory has been renamed to "cio".
+ (Trac #1561, git d81cf24b9e37773ba9a0d5061c779834ff7d62b9)
+
+365. [bug] jinmei
+ libdatasrc: in-memory datasource incorrectly returned delegation
+ for DS lookups.
+ (Trac #1571, git d22e90b5ef94880183cd652e112399b3efb9bd67)
+
+364. [func] jinmei
+ b10-auth experimentally supports statistics counters of incoming
+ requests per opcode. The counters can be (e.g.) shown as
+ opcode.<code name> in the output of the bindctl "Stats show"
+ command, where <code name> is lower-cased textual representation
+ of opcodes ("query", "notify", etc).
+ Note: This is an experimental attempt of supporting more
+ statistics counters for b10-auth, and the interface and output may
+ change in future versions.
+ (Trac #1399, git 07206ec76e2834de35f2e1304a274865f8f8c1a5)
+
+bind10-devel-20120119 released on January 19, 2012
+
+363. [func] jelte
+ Added dummy DDNS module b10-ddns. Currently it does not
+ provide any functionality, but it is a skeleton implementation
+ that will be expanded later.
+ (Trac #1451, git b0d0bf39fbdc29a7879315f9b8e6d602ef3afb1b)
+
+362. [func]* vorner
+ Due to the socket creator changes, b10-auth and b10-resolver
+ are no longer needed to start as root. They are started as
+ the user they should be running, so they no longer have
+ the -u flag for switching the user after initialization.
+ Note: this change broke backward compatibility to boss component
+ configuration. If your b10-config.db contains "setuid" for
+ Boss.components, you'll need to remove that entry by hand before
+ starting BIND 10.
+ (Trac #1508, #1509, #1510,
+ git edc5b3c12eb45437361484c843794416ad86bb00)
+
+361. [func] vorner,jelte,jinmei
+ The socket creator is now used to provide sockets. It means you can
+ reconfigure the ports and addresses at runtime even when the rest
+ of the bind10 runs as non root user.
+ (Trac #805,#1522, git 1830215f884e3b5efda52bd4dbb120bdca863a6a)
+
+360. [bug] vorner
+ Fixed problem where bindctl crashed when a duplicate non-string
+ item was added to a list. This error is now properly reported.
+ (Trac #1515, git a3cf5322a73e8a97b388c6f8025b92957e5d8986)
359. [bug] kevin
Corrected SOA serial check in xfrout. It now compares the SOA
@@ -92,7 +595,7 @@
(Trac #1405, git 2f0aa20b44604b671e6bde78815db39381e563bf)
347. [bug] jelte
- Fixed a bug where adding Zonemgr/secondary_zones without explicitely
+ Fixed a bug where adding Zonemgr/secondary_zones without explicitly
setting the class value of the added zone resulted in a cryptic
error in bindctl ("Error: class"). It will now correctly default to
IN if not set. This also adds better checks on the name and class
@@ -198,7 +701,7 @@
332. [bug] vorner
C++ exceptions in the isc.dns.Rdata wrapper are now converted
- to python ones instead of just aborting the interpretter.
+ to python ones instead of just aborting the interpreter.
(Trac #1407, git 5b64e839be2906b8950f5b1e42a3fadd72fca033)
bind10-devel-20111128 released on November 28, 2011
@@ -1307,7 +1810,7 @@ bind10-devel-20110224 released on February 24, 2011
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
+ side effect of being coroutine) and speeds applications of this
class (notably b10-auth) up by around 10%.
(Trac #537, git 94cb95b1d508541201fc064302ba836164d3cbe6)
diff --git a/INSTALL b/INSTALL
index 44c380a..839f120 100644
--- a/INSTALL
+++ b/INSTALL
@@ -6,4 +6,4 @@ To then build from source:
make
For detailed installation directions, see the guide
-at doc/guide/bind10-guide.html.
+at doc/guide/bind10-guide.txt or doc/guide/bind10-guide.html.
diff --git a/Makefile.am b/Makefile.am
index cc91a56..54216b6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,3 +1,7 @@
+ACLOCAL_AMFLAGS = -I m4macros ${ACLOCAL_FLAGS}
+# ^^^^^^^^ This has to be the first line and cannot come later in this
+# Makefile.am due to some bork in some versions of autotools.
+
SUBDIRS = compatcheck doc src tests
USE_LCOV=@USE_LCOV@
LCOV=@LCOV@
@@ -79,7 +83,7 @@ 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 \
+ cppcheck --enable=all --suppressions src/cppcheck-suppress.lst --inline-suppr \
--quiet --error-exitcode=1 \
--template '{file}:{line}: check_fail: {message} ({severity},{id})' \
src
@@ -398,3 +402,6 @@ EXTRA_DIST += ext/asio/asio/system_error.hpp
EXTRA_DIST += ext/asio/asio/deadline_timer.hpp
EXTRA_DIST += ext/asio/asio/stream_socket_service.hpp
EXTRA_DIST += ext/coroutine/coroutine.h
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = dns++.pc
diff --git a/README b/README
index 99e2ece..3f68923 100644
--- a/README
+++ b/README
@@ -22,8 +22,9 @@ AXFR inbound service, b10-xfrout outgoing AXFR service, b10-zonemgr
secondary manager, b10-stats statistics collection and reporting
daemon, b10-stats-httpd for HTTP access to XML-formatted stats,
b10-host DNS lookup utility, and a new libdns++ library for C++
-with a python wrapper. BIND 10 also provides an experimental DHCPv6
-echo server, b10-dhcp6.
+with a python wrapper. BIND 10 also provides experimental DHCPv4
+and DHCPv6 servers, b10-dhcp4 and b10-dhcp6, a portable DHCP library,
+libdhcp++, and a DHCP benchmarking tool, perfdhcp.
Documentation is included with the source. See doc/guide/bind10-guide.txt
(or bind10-guide.html) for installation instructions. The
diff --git a/compatcheck/.gitignore b/compatcheck/.gitignore
new file mode 100644
index 0000000..180a3ec
--- /dev/null
+++ b/compatcheck/.gitignore
@@ -0,0 +1 @@
+/sqlite3-difftbl-check.py
diff --git a/compatcheck/Makefile.am b/compatcheck/Makefile.am
index 029578d..15ef017 100644
--- a/compatcheck/Makefile.am
+++ b/compatcheck/Makefile.am
@@ -1,8 +1,12 @@
-noinst_SCRIPTS = sqlite3-difftbl-check.py
-
# We're going to abuse install-data-local for a pre-install check.
# This is to be considered a short term hack and is expected to be removed
# in a near future version.
install-data-local:
- $(PYTHON) sqlite3-difftbl-check.py \
- $(localstatedir)/$(PACKAGE)/zone.sqlite3
+ if test -e $(localstatedir)/$(PACKAGE)/zone.sqlite3; then \
+ $(SHELL) $(top_builddir)/src/bin/dbutil/run_dbutil.sh --check \
+ $(localstatedir)/$(PACKAGE)/zone.sqlite3 || \
+ (echo "\nSQLite3 DB file schema version is old. " \
+ "Please run: " \
+ "$(abs_top_builddir)/src/bin/dbutil/run_dbutil.sh --upgrade " \
+ "$(localstatedir)/$(PACKAGE)/zone.sqlite3"; exit 1) \
+ fi
diff --git a/compatcheck/sqlite3-difftbl-check.py.in b/compatcheck/sqlite3-difftbl-check.py.in
deleted file mode 100755
index e3b7b91..0000000
--- a/compatcheck/sqlite3-difftbl-check.py.in
+++ /dev/null
@@ -1,60 +0,0 @@
-#!@PYTHON@
-
-# 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.
-
-import os, sqlite3, sys
-from optparse import OptionParser
-
-usage = 'usage: %prog [options] db_file'
-parser = OptionParser(usage=usage)
-parser.add_option("-u", "--upgrade", action="store_true",
- dest="upgrade", default=False,
- help="Upgrade the database file [default: %default]")
-(options, args) = parser.parse_args()
-if len(args) == 0:
- parser.error('missing argument')
-
-db_file = args[0]
-
-# If the file doesn't exist, there's nothing to do
-if not os.path.exists(db_file):
- sys.exit(0)
-
-conn = sqlite3.connect(db_file)
-cur = conn.cursor()
-try:
- # This can be anything that works iff the "diffs" table exists
- cur.execute('SELECT name FROM diffs DESC LIMIT 1')
-except sqlite3.OperationalError as ex:
- # If it fails with 'no such table', create a new one or fail with
- # warning depending on the --upgrade command line option.
- if str(ex) == 'no such table: diffs':
- if options.upgrade:
- cur.execute('CREATE TABLE diffs (id INTEGER PRIMARY KEY, ' +
- 'zone_id INTEGER NOT NULL, ' +
- 'version INTEGER NOT NULL, ' +
- 'operation INTEGER NOT NULL, ' +
- 'name STRING NOT NULL COLLATE NOCASE, ' +
- 'rrtype STRING NOT NULL COLLATE NOCASE, ' +
- 'ttl INTEGER NOT NULL, rdata STRING NOT NULL)')
- else:
- sys.stdout.write('Found an older version of SQLite3 DB file: ' +
- db_file + '\n' + "Perform '" + os.getcwd() +
- "/sqlite3-difftbl-check.py --upgrade " +
- db_file + "'\n" +
- 'before continuing install.\n')
- sys.exit(1)
-conn.close()
diff --git a/configure.ac b/configure.ac
index 81ead0c..af9125f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,10 +2,12 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.59])
-AC_INIT(bind10-devel, 20111129, bind10-dev at isc.org)
+AC_INIT(bind10-devel, 20120405, bind10-dev at isc.org)
AC_CONFIG_SRCDIR(README)
AM_INIT_AUTOMAKE
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])dnl be backward compatible
AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_MACRO_DIR([m4macros])
# Checks for programs.
AC_PROG_CXX
@@ -48,16 +50,24 @@ AM_CONDITIONAL(USE_CLANGPP, test "X${CLANGPP}" = "Xyes")
# Linker options
-# check -R rather than gcc specific -rpath to be as portable as possible.
+# check -R and -Wl,-R rather than gcc specific -rpath to be as portable
+# as possible.
AC_MSG_CHECKING([whether -R flag is available in linker])
LDFLAGS_SAVED="$LDFLAGS"
LDFLAGS="$LDFLAGS -R/usr/lib"
AC_TRY_LINK([],[],
- [ AC_MSG_RESULT(yes)
- rpath_available=yes
- ],[ AC_MSG_RESULT(no)
- rpath_available=no
- ])
+ [ AC_MSG_RESULT(yes)
+ rpath_flag=-R
+ ],[ AC_MSG_RESULT(no)
+ AC_MSG_CHECKING([whether -Wl,-R flag is available in linker])
+ LDFLAGS="$LDFLAGS_SAVED -Wl,-R"
+ AC_TRY_LINK([], [],
+ [ AC_MSG_RESULT(yes)
+ rpath_flag=-Wl,-R
+ ],[ AC_MSG_RESULT(no)
+ rpath_flag=no
+ ])
+ ])
LDFLAGS=$LDFLAGS_SAVED
# allow building programs with static link. we need to make it selective
@@ -76,11 +86,6 @@ 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],
@@ -100,6 +105,10 @@ case "$host" in
LDFLAGS="$LDFLAGS -z now"
;;
*-apple-darwin*)
+ # Starting with OSX 10.7 (Lion) we must choose which IPv6 API to use
+ # (RFC2292 or RFC3542).
+ CPPFLAGS="$CPPFLAGS -D__APPLE_USE_RFC_3542"
+
# 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
@@ -115,6 +124,9 @@ case "$host" in
*-netbsd*)
SET_ENV_LIBRARY_PATH=yes
;;
+*-openbsd*)
+ SET_ENV_LIBRARY_PATH=yes
+ ;;
esac
AM_CONDITIONAL(SET_ENV_LIBRARY_PATH, test $SET_ENV_LIBRARY_PATH = yes)
AC_SUBST(SET_ENV_LIBRARY_PATH)
@@ -202,10 +214,10 @@ fi
# modules, we embed the path to the modules when possible. We do this even
# when the path is known in the common operational environment (e.g. when
# it's stored in a common "hint" file) for simplicity.
-if test $rpath_available = yes; then
+if test $rpath_flag != no; then
python_rpath=
for flag in ${PYTHON_LDFLAGS}; do
- python_rpath="${python_rpath} `echo $flag | sed -ne 's/^\(\-L\)/-R/p'`"
+ python_rpath="${python_rpath} `echo $flag | sed -ne "s/^\(\-L\)/${rpath_flag}/p"`"
done
PYTHON_LDFLAGS="${PYTHON_LDFLAGS} ${python_rpath}"
fi
@@ -262,7 +274,7 @@ AC_DEFUN([BIND10_CXX_TRY_FLAG], [
bind10_save_CXXFLAGS="$CXXFLAGS"
CXXFLAGS="$CXXFLAGS $1"
- AC_LINK_IFELSE([int main(void){ return 0;} ],
+ AC_LINK_IFELSE([AC_LANG_SOURCE([int main(void){ return 0;}])],
[bind10_cxx_flag=yes], [bind10_cxx_flag=no])
CXXFLAGS="$bind10_save_CXXFLAGS"
@@ -275,8 +287,6 @@ AC_DEFUN([BIND10_CXX_TRY_FLAG], [
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
@@ -284,7 +294,7 @@ CXXFLAGS="$CXXFLAGS -library=stlport4 -features=tmplife -features=tmplrefstatic"
MULTITHREADING_FLAG="-mt"
fi
-BIND10_CXX_TRY_FLAG(-Wno-missing-field-initializers,
+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)
@@ -302,19 +312,34 @@ case "$host" in
;;
esac
+# Don't use -Werror if configured not to
+AC_ARG_WITH(werror,
+ AC_HELP_STRING([--with-werror], [Compile using -Werror (default=yes)]),
+ [
+ case "${withval}" in
+ yes) with_werror=1 ;;
+ no) with_werror=0 ;;
+ *) AC_MSG_ERROR(bad value ${withval} for --with-werror) ;;
+ esac],
+ [with_werror=1])
+
+werror_ok=0
+
# 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
# translation unit. For these versions we have to disable -Werror.
-CXXFLAGS_SAVED="$CXXFLAGS"
-CXXFLAGS="$CXXFLAGS $B10_CXXFLAGS -Werror"
-AC_MSG_CHECKING(for in-TU anonymous namespace breakage)
-AC_TRY_COMPILE([namespace { class Foo {}; }
-namespace isc {class Bar {Foo foo_;};} ],,
+if test $with_werror = 1; then
+ CXXFLAGS_SAVED="$CXXFLAGS"
+ CXXFLAGS="$CXXFLAGS $B10_CXXFLAGS -Werror"
+ AC_MSG_CHECKING(for in-TU anonymous namespace breakage)
+ AC_TRY_COMPILE([namespace { class Foo {}; }
+ namespace isc {class Bar {Foo foo_;};} ],,
[AC_MSG_RESULT(no)
werror_ok=1
B10_CXXFLAGS="$B10_CXXFLAGS -Werror"],
[AC_MSG_RESULT(yes)])
-CXXFLAGS="$CXXFLAGS_SAVED"
+ CXXFLAGS="$CXXFLAGS_SAVED"
+fi
# Python 3.2 has an unused parameter in one of its headers. This
# has been reported, but not fixed as of yet, so we check if we need
@@ -469,61 +494,163 @@ if test "$lcov" != "no"; then
fi
AC_SUBST(USE_LCOV)
+# Simplified, non-caching AC_CHECK_PROG
+# Searches $PATH for the existence of argument 2,
+# and sets the full path to the variable in argument 1.
+# if not found, and a third argument is given, the value
+# is set to that. If not, the value is untouched.
+# Does not take absolute paths into account at this point,
+# and also works for single files only (arguments are not
+# stripped like in AC_CHECK_PROG)
+AC_DEFUN([ACX_CHECK_PROG_NONCACHE], [
+ RESULT=""
+ IFS_SAVED="$IFS"
+ IFS=${PATH_SEPARATOR}
+ for cur_path in ${PATH} ; do
+ if test -e "${cur_path}/$2" ; then
+ RESULT="${cur_path}/$2"
+ fi
+ done
+ if test "$RESULT" = "" ; then
+ :
+ m4_ifvaln([$3], [$1=$3])
+ else
+ $1=$RESULT
+ fi
+ IFS="$IFS_SAVED"
+])
+
+# Botan helper test function
+# Tries to compile a botan program, given the output of the given
+# config tool
+# Arguments:
+# - name of tool (checked for path), must support --libs and --cflags
+# - fixed argument(s) for tool
+# - action if successful
+AC_DEFUN([ACX_TRY_BOTAN_TOOL], [
+ TOOL=$1
+ TOOL_ARG=$2
+ BOTAN_TOOL=""
+ ACX_CHECK_PROG_NONCACHE([BOTAN_TOOL], [${TOOL}])
+ AC_MSG_CHECKING([usability of ${TOOL} ${TOOL_ARG}])
+ if test "$BOTAN_TOOL" != "" ; then
+ if test -x ${BOTAN_TOOL}; then
+ BOTAN_LIBS=`$BOTAN_TOOL $TOOL_ARG --libs`
+ LIBS_SAVED=${LIBS}
+ LIBS="$LIBS $BOTAN_LIBS"
+ BOTAN_INCLUDES=`$BOTAN_TOOL $TOOL_ARG --cflags`
+ CPPFLAGS_SAVED=${CPPFLAGS}
+ CPPFLAGS="$BOTAN_INCLUDES $CPPFLAGS"
+ #AC_MSG_RESULT([found])
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM([#include <botan/botan.h>
+ #include <botan/hash.h>
+ ],
+ [using namespace Botan;
+ LibraryInitializer::initialize();
+ HashFunction *h = get_hash("MD5");
+ ])],
+ [ AC_MSG_RESULT([ok])
+ $3
+ ],
+ [ AC_MSG_RESULT([not usable]) ]
+ )
+ LIBS=${LIBS_SAVED}
+ CPPFLAGS=${CPPFLAGS_SAVED}
+ else
+ AC_MSG_RESULT([not executable])
+ fi
+ else
+ AC_MSG_RESULT([not found])
+ fi
+ BOTAN_TOOL=""
+ AC_SUBST(BOTAN_TOOL)
+ ]
+)
+
# Check for Botan
-botan_path="yes"
-AC_ARG_WITH([botan],
- AC_HELP_STRING([--with-botan=PATH],
- [specify the path to botan-config (PATH/bin/botan-config will be used)]),
- [botan_path="$withval"])
-if test "${botan_path}" = "no" ; then
+#
+# Unless --with-botan-config is given, we first try to find these config
+# scripts ourselves. Unfortunately, on some systems, these scripts do not
+# provide the correct implementation, so for each script found, we try
+# a compilation test (ACX_TRY_BOTAN_TOOL). If none are found, or none of
+# them work, we see if pkg-config is available. If so, we try the several
+# potential pkg-config .pc files. Again, on some systems, these can return
+# incorrect information as well, so the try-compile test is repeated for
+# each.
+#
+# If a working config script or pkgconfig file is found, we then munge its
+# output for use in our Makefiles, and to make sure it works, another header
+# and compilation test is done (this should also check whether we can compile
+# against botan should neither -config scripts nor pkgconfig data exist).
+#
+botan_config="yes"
+AC_ARG_WITH([botan-config],
+ AC_HELP_STRING([--with-botan-config=PATH],
+ [specify the path to the botan-config script]),
+ [botan_config="$withval"])
+if test "${botan_config}" = "no" ; then
AC_MSG_ERROR([Need botan for libcryptolink])
fi
-if test "${botan_path}" != "yes" ; then
- if test -x "${botan_path}/bin/botan-config" ; then
- BOTAN_CONFIG="${botan_path}/bin/botan-config"
+if test "${botan_config}" != "yes" ; then
+ if test -x "${botan_config}" ; then
+ if test -d "${botan_config}" ; then
+ AC_MSG_ERROR([${botan_config} is a directory])
+ else
+ BOTAN_CONFIG="${botan_config}"
+ fi
else
- AC_MSG_ERROR([${botan_path}/bin/botan-config not found])
+ AC_MSG_ERROR([--with-botan-config should point to a botan-config program and not a directory (${botan_config})])
fi
else
- # First see if pkg-config knows of it.
- # Unfortunately, the botan.pc files have their minor version in them
- # too, so we need to try them one by one
BOTAN_CONFIG=""
- AC_PATH_PROG([PKG_CONFIG], [pkg-config])
- if test "$PKG_CONFIG" != "" ; then
- BOTAN_VERSIONS="botan-1.10 botan-1.9 botan-1.8"
- for version in $BOTAN_VERSIONS; do
- AC_MSG_CHECKING([Checking botan version with pkg-config $version])
-
- if [ $PKG_CONFIG --exists ${version} ]; then
- AC_MSG_RESULT([found])
- BOTAN_CONFIG="$PKG_CONFIG ${version}"
+ # first try several possible names of the config script
+ # (botan-config-1.8 is there just in case, the official name change
+ # came later)
+ BOTAN_CONFIG_VERSIONS="botan-config-1.10 botan-config-1.9 botan-config-1.8 botan-config"
+ for botan_config in $BOTAN_CONFIG_VERSIONS; do
+ ACX_TRY_BOTAN_TOOL([$botan_config],,
+ [ BOTAN_CONFIG="$botan_config" ]
+ )
+ if test "$BOTAN_CONFIG" != "" ; then
+ break
+ fi
+ done
+ if test "$BOTAN_CONFIG" = "" ; then
+ AC_PATH_PROG([PKG_CONFIG], [pkg-config])
+ if test "$PKG_CONFIG" != "" ; then
+ # Ok so no script found, see if pkg-config knows of it.
+ # Unfortunately, the botan.pc files also have their minor version
+ # in their name, so we need to try them one by one
+ BOTAN_VERSIONS="botan-1.10 botan-1.9 botan-1.8"
+ for version in $BOTAN_VERSIONS; do
+ ACX_TRY_BOTAN_TOOL([pkg-config], ["$version --silence-errors"],
+ [ BOTAN_CONFIG="$PKG_CONFIG $version" ]
+ )
+ if test "$BOTAN_CONFIG" != "" ; then
break
- else
- AC_MSG_RESULT([not found])
fi
- done
- fi
- # If we had no pkg-config, or it didn't know about botan, use botan-config
- if test "$BOTAN_CONFIG" = "" ; then
- AC_PATH_PROG([BOTAN_CONFIG], [botan-config])
+ done
+ fi
fi
fi
-BOTAN_LIBS=`${BOTAN_CONFIG} --libs`
-BOTAN_INCLUDES=`${BOTAN_CONFIG} --cflags`
-
-# We expect botan-config --libs to contain -L<path_to_libbotan>, but
-# this is not always the case. As a heuristics workaround we add
-# -L`botan-config --prefix/lib` in this case (if not present already).
-# Same for BOTAN_INCLUDES (but using include instead of lib) below.
-if [ $BOTAN_CONFIG --prefix >/dev/null 2>&1 ] ; then
- echo ${BOTAN_LIBS} | grep -- -L > /dev/null || \
- BOTAN_LIBS="-L`${BOTAN_CONFIG} --prefix`/lib ${BOTAN_LIBS}"
- echo ${BOTAN_INCLUDES} | grep -- -I > /dev/null || \
- BOTAN_INCLUDES="-I`${BOTAN_CONFIG} --prefix`/include ${BOTAN_INCLUDES}"
+if test "x${BOTAN_CONFIG}" != "x"
+then
+ BOTAN_LIBS=`${BOTAN_CONFIG} --libs`
+ BOTAN_INCLUDES=`${BOTAN_CONFIG} --cflags`
+
+ # We expect botan-config --libs to contain -L<path_to_libbotan>, but
+ # this is not always the case. As a heuristics workaround we add
+ # -L`botan-config --prefix/lib` in this case (if not present already).
+ # Same for BOTAN_INCLUDES (but using include instead of lib) below.
+ if [ ${BOTAN_CONFIG} --prefix >/dev/null 2>&1 ] ; then
+ echo ${BOTAN_LIBS} | grep -- -L > /dev/null || \
+ BOTAN_LIBS="-L`${BOTAN_CONFIG} --prefix`/lib ${BOTAN_LIBS}"
+ echo ${BOTAN_INCLUDES} | grep -- -I > /dev/null || \
+ BOTAN_INCLUDES="-I`${BOTAN_CONFIG} --prefix`/include ${BOTAN_INCLUDES}"
+ fi
fi
-
# botan-config script (and the way we call pkg-config) returns -L and -l
# as one string, but we need them in separate values
BOTAN_LDFLAGS=
@@ -534,10 +661,10 @@ for flag in ${BOTAN_LIBS}; do
done
# See python_rpath for some info on why we do this
-if test $rpath_available = yes; then
+if test $rpath_flag != no; then
BOTAN_RPATH=
for flag in ${BOTAN_LIBS}; do
- BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | sed -ne 's/^\(\-L\)/-R/p'`"
+ BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | sed -ne "s/^\(\-L\)/${rpath_flag}/p"`"
done
AC_SUBST(BOTAN_RPATH)
@@ -555,6 +682,9 @@ AC_SUBST(BOTAN_LDFLAGS)
AC_SUBST(BOTAN_LIBS)
AC_SUBST(BOTAN_INCLUDES)
+# Even though chances are high we already performed a real compilation check
+# in the search for the right (pkg)config data, we try again here, to
+# be sure.
CPPFLAGS_SAVED=$CPPFLAGS
CPPFLAGS="$BOTAN_INCLUDES $CPPFLAGS"
LIBS_SAVED="$LIBS"
@@ -570,7 +700,11 @@ AC_LINK_IFELSE(
])],
[AC_MSG_RESULT([checking for Botan library... yes])],
[AC_MSG_RESULT([checking for Botan library... no])
- AC_MSG_ERROR([Needs Botan library 1.8 or higher])]
+ AC_MSG_ERROR([Needs Botan library 1.8 or higher. On some systems,
+ the botan package has a few missing dependencies (libbz2 and
+ libgmp), if libbotan has been installed and you see this error,
+ try upgrading to a higher version of botan or installing libbz2
+ and libgmp.])]
)
CPPFLAGS=$CPPFLAGS_SAVED
LIBS=$LIBS_SAVED
@@ -650,70 +784,23 @@ if test "${boost_include_path}" ; then
fi
AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
AC_MSG_ERROR([Missing required header files.]))
-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
+# Detect whether Boost tries to use threads by default, and, if not,
+# make it sure explicitly. In some systems the automatic detection
+# may depend on preceding header files, and if inconsistency happens
+# it could lead to a critical disruption.
+AC_MSG_CHECKING([whether Boost tries to use threads])
+AC_TRY_COMPILE([
+#include <boost/config.hpp>
+#ifdef BOOST_HAS_THREADS
+#error "boost will use threads"
+#endif],,
+[AC_MSG_RESULT(no)
+ CPPFLAGS_BOOST_THREADCONF="-DBOOST_DISABLE_THREADS=1"],
+[AC_MSG_RESULT(yes)])
+
+CPPFLAGS="$CPPFLAGS_SAVES $CPPFLAGS_BOOST_THREADCONF"
+AC_SUBST(BOOST_INCLUDES)
# I can't get some of the #include <asio.hpp> right without this
# TODO: find the real cause of asio/boost wanting pthreads
@@ -838,28 +925,9 @@ CPPFLAGS="$CPPFLAGS -I\$(top_srcdir)/ext/coroutine"
#
# Disable threads: Currently we don't use them.
CPPFLAGS="$CPPFLAGS -DASIO_DISABLE_THREADS=1"
-#
-# kqueue portability: ASIO uses kqueue by default if it's available (it's
-# generally available in BSD variants). Unfortunately, some public
-# implementation of kqueue forces a conversion from a pointer to an integer,
-# which is prohibited in C++ unless reinterpret_cast, C++'s most evil beast
-# (and ASIO doesn't use it anyway) is used. This will cause build error for
-# some of our C++ files including ASIO header files. The following check
-# detects such cases and tells ASIO not to use kqueue if so.
-AC_CHECK_FUNC(kqueue, ac_cv_have_kqueue=yes, ac_cv_have_kqueue=no)
-if test "X$ac_cv_have_kqueue" = "Xyes"; then
- AC_MSG_CHECKING([whether kqueue EV_SET compiles in C++])
- AC_TRY_COMPILE([
-#include <sys/types.h>
-#include <sys/param.h>
-#include <sys/event.h>],
-[char* udata;
-EV_SET(NULL, 0, 0, 0, 0, 0, udata);],
- [AC_MSG_RESULT(yes)],
- [AC_MSG_RESULT([no, disable kqueue for ASIO])
- CPPFLAGS="$CPPFLAGS -DASIO_DISABLE_KQUEUE=1"
- ])
-fi
+
+# Check for functions that are not available on all platforms
+AC_CHECK_FUNCS([pselect])
# perfdhcp: If the clock_gettime() function does not exist on the system,
# use an alternative supplied in the code based on gettimeofday().
@@ -899,7 +967,7 @@ AC_PATH_PROGS(AWK, gawk awk)
AC_SUBST(AWK)
AC_ARG_ENABLE(man, [AC_HELP_STRING([--enable-man],
- [regenerate man pages [default=no]])], enable_man=yes, enable_man=no)
+ [regenerate man pages [default=no]])], enable_man=$enableval, enable_man=no)
AM_CONDITIONAL(ENABLE_MAN, test x$enable_man != xno)
@@ -909,6 +977,11 @@ AC_ARG_ENABLE(install-configurations,
AM_CONDITIONAL(INSTALL_CONFIGURATIONS, test x$install_configurations = xyes || test x$install_configurations = xtrue)
+AC_ARG_ENABLE(logger-checks, [AC_HELP_STRING([--enable-logger-checks],
+ [check logger messages [default=no]])], enable_logger_checks=$enableval, enable_logger_checks=no)
+AM_CONDITIONAL(ENABLE_LOGGER_CHECKS, test x$enable_logger_checks != xno)
+AM_COND_IF([ENABLE_LOGGER_CHECKS], [AC_DEFINE([ENABLE_LOGGER_CHECKS], [1], [Check logger messages?])])
+
AC_CONFIG_FILES([Makefile
doc/Makefile
doc/guide/Makefile
@@ -925,6 +998,9 @@ AC_CONFIG_FILES([Makefile
src/bin/cfgmgr/plugins/Makefile
src/bin/cfgmgr/plugins/tests/Makefile
src/bin/cfgmgr/tests/Makefile
+ src/bin/dbutil/Makefile
+ src/bin/dbutil/tests/Makefile
+ src/bin/dbutil/tests/testdata/Makefile
src/bin/host/Makefile
src/bin/loadzone/Makefile
src/bin/loadzone/tests/correct/Makefile
@@ -938,8 +1014,8 @@ AC_CONFIG_FILES([Makefile
src/bin/ddns/tests/Makefile
src/bin/dhcp6/Makefile
src/bin/dhcp6/tests/Makefile
- src/bin/dhcp4/Makefile
- src/bin/dhcp4/tests/Makefile
+ src/bin/dhcp4/Makefile
+ src/bin/dhcp4/tests/Makefile
src/bin/resolver/Makefile
src/bin/resolver/tests/Makefile
src/bin/sockcreator/Makefile
@@ -971,8 +1047,8 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/acl/tests/Makefile
src/lib/python/isc/util/Makefile
src/lib/python/isc/util/tests/Makefile
- src/lib/python/isc/util/io/Makefile
- src/lib/python/isc/util/io/tests/Makefile
+ src/lib/python/isc/util/cio/Makefile
+ src/lib/python/isc/util/cio/tests/Makefile
src/lib/python/isc/datasrc/Makefile
src/lib/python/isc/datasrc/tests/Makefile
src/lib/python/isc/dns/Makefile
@@ -993,6 +1069,8 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/bind10/tests/Makefile
src/lib/python/isc/xfrin/Makefile
src/lib/python/isc/xfrin/tests/Makefile
+ src/lib/python/isc/server_common/Makefile
+ src/lib/python/isc/server_common/tests/Makefile
src/lib/config/Makefile
src/lib/config/tests/Makefile
src/lib/config/tests/testdata/Makefile
@@ -1042,15 +1120,18 @@ AC_CONFIG_FILES([Makefile
tests/tools/badpacket/Makefile
tests/tools/badpacket/tests/Makefile
tests/tools/perfdhcp/Makefile
+ dns++.pc
])
AC_OUTPUT([doc/version.ent
- compatcheck/sqlite3-difftbl-check.py
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
src/bin/cmdctl/tests/cmdctl_test
src/bin/cmdctl/cmdctl.spec.pre
+ src/bin/dbutil/dbutil.py
+ src/bin/dbutil/run_dbutil.sh
+ src/bin/dbutil/tests/dbutil_test.sh
src/bin/ddns/ddns.py
src/bin/xfrin/tests/xfrin_test
src/bin/xfrin/xfrin.py
@@ -1127,13 +1208,14 @@ AC_OUTPUT([doc/version.ent
tests/system/ixfr/in-3/setup.sh
tests/system/ixfr/in-4/setup.sh
], [
- chmod +x compatcheck/sqlite3-difftbl-check.py
chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
chmod +x src/bin/xfrin/run_b10-xfrin.sh
chmod +x src/bin/xfrout/run_b10-xfrout.sh
chmod +x src/bin/zonemgr/run_b10-zonemgr.sh
chmod +x src/bin/bind10/run_bind10.sh
chmod +x src/bin/cmdctl/tests/cmdctl_test
+ chmod +x src/bin/dbutil/run_dbutil.sh
+ chmod +x src/bin/dbutil/tests/dbutil_test.sh
chmod +x src/bin/xfrin/tests/xfrin_test
chmod +x src/bin/xfrout/tests/xfrout_test
chmod +x src/bin/zonemgr/tests/zonemgr_test
diff --git a/depcomp b/depcomp
deleted file mode 100755
index df8eea7..0000000
--- a/depcomp
+++ /dev/null
@@ -1,630 +0,0 @@
-#! /bin/sh
-# depcomp - compile a program generating dependencies as side-effects
-
-scriptversion=2009-04-28.21; # UTC
-
-# Copyright (C) 1999, 2000, 2003, 2004, 2005, 2006, 2007, 2009 Free
-# Software Foundation, Inc.
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2, or (at your option)
-# any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-# As a special exception to the GNU General Public License, if you
-# distribute this file as part of a program that contains a
-# configuration script generated by Autoconf, you may include it under
-# the same distribution terms that you use for the rest of that program.
-
-# Originally written by Alexandre Oliva <oliva at dcc.unicamp.br>.
-
-case $1 in
- '')
- echo "$0: No command. Try \`$0 --help' for more information." 1>&2
- exit 1;
- ;;
- -h | --h*)
- cat <<\EOF
-Usage: depcomp [--help] [--version] PROGRAM [ARGS]
-
-Run PROGRAMS ARGS to compile a file, generating dependencies
-as side-effects.
-
-Environment variables:
- depmode Dependency tracking mode.
- source Source file read by `PROGRAMS ARGS'.
- object Object file output by `PROGRAMS ARGS'.
- DEPDIR directory where to store dependencies.
- depfile Dependency file to output.
- tmpdepfile Temporary file to use when outputing dependencies.
- libtool Whether libtool is used (yes/no).
-
-Report bugs to <bug-automake at gnu.org>.
-EOF
- exit $?
- ;;
- -v | --v*)
- echo "depcomp $scriptversion"
- exit $?
- ;;
-esac
-
-if test -z "$depmode" || test -z "$source" || test -z "$object"; then
- echo "depcomp: Variables source, object and depmode must be set" 1>&2
- exit 1
-fi
-
-# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po.
-depfile=${depfile-`echo "$object" |
- sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`}
-tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`}
-
-rm -f "$tmpdepfile"
-
-# Some modes work just like other modes, but use different flags. We
-# parameterize here, but still list the modes in the big case below,
-# to make depend.m4 easier to write. Note that we *cannot* use a case
-# here, because this file can only contain one case statement.
-if test "$depmode" = hp; then
- # HP compiler uses -M and no extra arg.
- gccflag=-M
- depmode=gcc
-fi
-
-if test "$depmode" = dashXmstdout; then
- # This is just like dashmstdout with a different argument.
- dashmflag=-xM
- depmode=dashmstdout
-fi
-
-cygpath_u="cygpath -u -f -"
-if test "$depmode" = msvcmsys; then
- # This is just like msvisualcpp but w/o cygpath translation.
- # Just convert the backslash-escaped backslashes to single forward
- # slashes to satisfy depend.m4
- cygpath_u="sed s,\\\\\\\\,/,g"
- depmode=msvisualcpp
-fi
-
-case "$depmode" in
-gcc3)
-## gcc 3 implements dependency tracking that does exactly what
-## we want. Yay! Note: for some reason libtool 1.4 doesn't like
-## it if -MD -MP comes after the -MF stuff. Hmm.
-## Unfortunately, FreeBSD c89 acceptance of flags depends upon
-## the command line argument order; so add the flags where they
-## appear in depend2.am. Note that the slowdown incurred here
-## affects only configure: in makefiles, %FASTDEP% shortcuts this.
- for arg
- do
- case $arg in
- -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;;
- *) set fnord "$@" "$arg" ;;
- esac
- shift # fnord
- shift # $arg
- done
- "$@"
- stat=$?
- if test $stat -eq 0; then :
- else
- rm -f "$tmpdepfile"
- exit $stat
- fi
- mv "$tmpdepfile" "$depfile"
- ;;
-
-gcc)
-## There are various ways to get dependency output from gcc. Here's
-## why we pick this rather obscure method:
-## - Don't want to use -MD because we'd like the dependencies to end
-## up in a subdir. Having to rename by hand is ugly.
-## (We might end up doing this anyway to support other compilers.)
-## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like
-## -MM, not -M (despite what the docs say).
-## - Using -M directly means running the compiler twice (even worse
-## than renaming).
- if test -z "$gccflag"; then
- gccflag=-MD,
- fi
- "$@" -Wp,"$gccflag$tmpdepfile"
- stat=$?
- if test $stat -eq 0; then :
- else
- rm -f "$tmpdepfile"
- exit $stat
- fi
- rm -f "$depfile"
- echo "$object : \\" > "$depfile"
- alpha=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
-## The second -e expression handles DOS-style file names with drive letters.
- sed -e 's/^[^:]*: / /' \
- -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile"
-## This next piece of magic avoids the `deleted header file' problem.
-## The problem is that when a header file which appears in a .P file
-## is deleted, the dependency causes make to die (because there is
-## typically no way to rebuild the header). We avoid this by adding
-## dummy dependencies for each header file. Too bad gcc doesn't do
-## this for us directly.
- tr ' ' '
-' < "$tmpdepfile" |
-## Some versions of gcc put a space before the `:'. On the theory
-## that the space means something, we add a space to the output as
-## well.
-## Some versions of the HPUX 10.20 sed can't process this invocation
-## correctly. Breaking it into two sed invocations is a workaround.
- sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
- rm -f "$tmpdepfile"
- ;;
-
-hp)
- # This case exists only to let depend.m4 do its work. It works by
- # looking at the text of this script. This case will never be run,
- # since it is checked for above.
- exit 1
- ;;
-
-sgi)
- if test "$libtool" = yes; then
- "$@" "-Wp,-MDupdate,$tmpdepfile"
- else
- "$@" -MDupdate "$tmpdepfile"
- fi
- stat=$?
- if test $stat -eq 0; then :
- else
- rm -f "$tmpdepfile"
- exit $stat
- fi
- rm -f "$depfile"
-
- if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files
- echo "$object : \\" > "$depfile"
-
- # Clip off the initial element (the dependent). Don't try to be
- # clever and replace this with sed code, as IRIX sed won't handle
- # lines with more than a fixed number of characters (4096 in
- # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines;
- # the IRIX cc adds comments like `#:fec' to the end of the
- # dependency line.
- tr ' ' '
-' < "$tmpdepfile" \
- | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' | \
- tr '
-' ' ' >> "$depfile"
- echo >> "$depfile"
-
- # The second pass generates a dummy entry for each header file.
- tr ' ' '
-' < "$tmpdepfile" \
- | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \
- >> "$depfile"
- else
- # The sourcefile does not contain any dependencies, so just
- # store a dummy comment line, to avoid errors with the Makefile
- # "include basename.Plo" scheme.
- echo "#dummy" > "$depfile"
- fi
- rm -f "$tmpdepfile"
- ;;
-
-aix)
- # The C for AIX Compiler uses -M and outputs the dependencies
- # in a .u file. In older versions, this file always lives in the
- # current directory. Also, the AIX compiler puts `$object:' at the
- # start of each line; $object doesn't have directory information.
- # Version 6 uses the directory in both cases.
- dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
- test "x$dir" = "x$object" && dir=
- base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
- if test "$libtool" = yes; then
- tmpdepfile1=$dir$base.u
- tmpdepfile2=$base.u
- tmpdepfile3=$dir.libs/$base.u
- "$@" -Wc,-M
- else
- tmpdepfile1=$dir$base.u
- tmpdepfile2=$dir$base.u
- tmpdepfile3=$dir$base.u
- "$@" -M
- fi
- stat=$?
-
- if test $stat -eq 0; then :
- else
- rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
- exit $stat
- fi
-
- for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
- do
- test -f "$tmpdepfile" && break
- done
- if test -f "$tmpdepfile"; then
- # Each line is of the form `foo.o: dependent.h'.
- # Do two passes, one to just change these to
- # `$object: dependent.h' and one to simply `dependent.h:'.
- sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile"
- # That's a tab and a space in the [].
- sed -e 's,^.*\.[a-z]*:[ ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile"
- else
- # The sourcefile does not contain any dependencies, so just
- # store a dummy comment line, to avoid errors with the Makefile
- # "include basename.Plo" scheme.
- echo "#dummy" > "$depfile"
- fi
- rm -f "$tmpdepfile"
- ;;
-
-icc)
- # Intel's C compiler understands `-MD -MF file'. However on
- # icc -MD -MF foo.d -c -o sub/foo.o sub/foo.c
- # ICC 7.0 will fill foo.d with something like
- # foo.o: sub/foo.c
- # foo.o: sub/foo.h
- # which is wrong. We want:
- # sub/foo.o: sub/foo.c
- # sub/foo.o: sub/foo.h
- # sub/foo.c:
- # sub/foo.h:
- # ICC 7.1 will output
- # foo.o: sub/foo.c sub/foo.h
- # and will wrap long lines using \ :
- # foo.o: sub/foo.c ... \
- # sub/foo.h ... \
- # ...
-
- "$@" -MD -MF "$tmpdepfile"
- stat=$?
- if test $stat -eq 0; then :
- else
- rm -f "$tmpdepfile"
- exit $stat
- fi
- rm -f "$depfile"
- # Each line is of the form `foo.o: dependent.h',
- # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'.
- # Do two passes, one to just change these to
- # `$object: dependent.h' and one to simply `dependent.h:'.
- sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile"
- # Some versions of the HPUX 10.20 sed can't process this invocation
- # correctly. Breaking it into two sed invocations is a workaround.
- sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" |
- sed -e 's/$/ :/' >> "$depfile"
- rm -f "$tmpdepfile"
- ;;
-
-hp2)
- # The "hp" stanza above does not work with aCC (C++) and HP's ia64
- # compilers, which have integrated preprocessors. The correct option
- # to use with these is +Maked; it writes dependencies to a file named
- # 'foo.d', which lands next to the object file, wherever that
- # happens to be.
- # Much of this is similar to the tru64 case; see comments there.
- dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
- test "x$dir" = "x$object" && dir=
- base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
- if test "$libtool" = yes; then
- tmpdepfile1=$dir$base.d
- tmpdepfile2=$dir.libs/$base.d
- "$@" -Wc,+Maked
- else
- tmpdepfile1=$dir$base.d
- tmpdepfile2=$dir$base.d
- "$@" +Maked
- fi
- stat=$?
- if test $stat -eq 0; then :
- else
- rm -f "$tmpdepfile1" "$tmpdepfile2"
- exit $stat
- fi
-
- for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2"
- do
- test -f "$tmpdepfile" && break
- done
- if test -f "$tmpdepfile"; then
- sed -e "s,^.*\.[a-z]*:,$object:," "$tmpdepfile" > "$depfile"
- # Add `dependent.h:' lines.
- sed -ne '2,${
- s/^ *//
- s/ \\*$//
- s/$/:/
- p
- }' "$tmpdepfile" >> "$depfile"
- else
- echo "#dummy" > "$depfile"
- fi
- rm -f "$tmpdepfile" "$tmpdepfile2"
- ;;
-
-tru64)
- # The Tru64 compiler uses -MD to generate dependencies as a side
- # effect. `cc -MD -o foo.o ...' puts the dependencies into `foo.o.d'.
- # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put
- # dependencies in `foo.d' instead, so we check for that too.
- # Subdirectories are respected.
- dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
- test "x$dir" = "x$object" && dir=
- base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
-
- if test "$libtool" = yes; then
- # With Tru64 cc, shared objects can also be used to make a
- # static library. This mechanism is used in libtool 1.4 series to
- # handle both shared and static libraries in a single compilation.
- # With libtool 1.4, dependencies were output in $dir.libs/$base.lo.d.
- #
- # With libtool 1.5 this exception was removed, and libtool now
- # generates 2 separate objects for the 2 libraries. These two
- # compilations output dependencies in $dir.libs/$base.o.d and
- # in $dir$base.o.d. We have to check for both files, because
- # one of the two compilations can be disabled. We should prefer
- # $dir$base.o.d over $dir.libs/$base.o.d because the latter is
- # automatically cleaned when .libs/ is deleted, while ignoring
- # the former would cause a distcleancheck panic.
- tmpdepfile1=$dir.libs/$base.lo.d # libtool 1.4
- tmpdepfile2=$dir$base.o.d # libtool 1.5
- tmpdepfile3=$dir.libs/$base.o.d # libtool 1.5
- tmpdepfile4=$dir.libs/$base.d # Compaq CCC V6.2-504
- "$@" -Wc,-MD
- else
- tmpdepfile1=$dir$base.o.d
- tmpdepfile2=$dir$base.d
- tmpdepfile3=$dir$base.d
- tmpdepfile4=$dir$base.d
- "$@" -MD
- fi
-
- stat=$?
- if test $stat -eq 0; then :
- else
- rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4"
- exit $stat
- fi
-
- for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4"
- do
- test -f "$tmpdepfile" && break
- done
- if test -f "$tmpdepfile"; then
- sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile"
- # That's a tab and a space in the [].
- sed -e 's,^.*\.[a-z]*:[ ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile"
- else
- echo "#dummy" > "$depfile"
- fi
- rm -f "$tmpdepfile"
- ;;
-
-#nosideeffect)
- # This comment above is used by automake to tell side-effect
- # dependency tracking mechanisms from slower ones.
-
-dashmstdout)
- # Important note: in order to support this mode, a compiler *must*
- # always write the preprocessed file to stdout, regardless of -o.
- "$@" || exit $?
-
- # Remove the call to Libtool.
- if test "$libtool" = yes; then
- while test "X$1" != 'X--mode=compile'; do
- shift
- done
- shift
- fi
-
- # Remove `-o $object'.
- IFS=" "
- for arg
- do
- case $arg in
- -o)
- shift
- ;;
- $object)
- shift
- ;;
- *)
- set fnord "$@" "$arg"
- shift # fnord
- shift # $arg
- ;;
- esac
- done
-
- test -z "$dashmflag" && dashmflag=-M
- # Require at least two characters before searching for `:'
- # in the target name. This is to cope with DOS-style filenames:
- # a dependency such as `c:/foo/bar' could be seen as target `c' otherwise.
- "$@" $dashmflag |
- sed 's:^[ ]*[^: ][^:][^:]*\:[ ]*:'"$object"'\: :' > "$tmpdepfile"
- rm -f "$depfile"
- cat < "$tmpdepfile" > "$depfile"
- tr ' ' '
-' < "$tmpdepfile" | \
-## Some versions of the HPUX 10.20 sed can't process this invocation
-## correctly. Breaking it into two sed invocations is a workaround.
- sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
- rm -f "$tmpdepfile"
- ;;
-
-dashXmstdout)
- # This case only exists to satisfy depend.m4. It is never actually
- # run, as this mode is specially recognized in the preamble.
- exit 1
- ;;
-
-makedepend)
- "$@" || exit $?
- # Remove any Libtool call
- if test "$libtool" = yes; then
- while test "X$1" != 'X--mode=compile'; do
- shift
- done
- shift
- fi
- # X makedepend
- shift
- cleared=no eat=no
- for arg
- do
- case $cleared in
- no)
- set ""; shift
- cleared=yes ;;
- esac
- if test $eat = yes; then
- eat=no
- continue
- fi
- case "$arg" in
- -D*|-I*)
- set fnord "$@" "$arg"; shift ;;
- # Strip any option that makedepend may not understand. Remove
- # the object too, otherwise makedepend will parse it as a source file.
- -arch)
- eat=yes ;;
- -*|$object)
- ;;
- *)
- set fnord "$@" "$arg"; shift ;;
- esac
- done
- obj_suffix=`echo "$object" | sed 's/^.*\././'`
- touch "$tmpdepfile"
- ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@"
- rm -f "$depfile"
- cat < "$tmpdepfile" > "$depfile"
- sed '1,2d' "$tmpdepfile" | tr ' ' '
-' | \
-## Some versions of the HPUX 10.20 sed can't process this invocation
-## correctly. Breaking it into two sed invocations is a workaround.
- sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
- rm -f "$tmpdepfile" "$tmpdepfile".bak
- ;;
-
-cpp)
- # Important note: in order to support this mode, a compiler *must*
- # always write the preprocessed file to stdout.
- "$@" || exit $?
-
- # Remove the call to Libtool.
- if test "$libtool" = yes; then
- while test "X$1" != 'X--mode=compile'; do
- shift
- done
- shift
- fi
-
- # Remove `-o $object'.
- IFS=" "
- for arg
- do
- case $arg in
- -o)
- shift
- ;;
- $object)
- shift
- ;;
- *)
- set fnord "$@" "$arg"
- shift # fnord
- shift # $arg
- ;;
- esac
- done
-
- "$@" -E |
- sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
- -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' |
- sed '$ s: \\$::' > "$tmpdepfile"
- rm -f "$depfile"
- echo "$object : \\" > "$depfile"
- cat < "$tmpdepfile" >> "$depfile"
- sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile"
- rm -f "$tmpdepfile"
- ;;
-
-msvisualcpp)
- # Important note: in order to support this mode, a compiler *must*
- # always write the preprocessed file to stdout.
- "$@" || exit $?
-
- # Remove the call to Libtool.
- if test "$libtool" = yes; then
- while test "X$1" != 'X--mode=compile'; do
- shift
- done
- shift
- fi
-
- IFS=" "
- for arg
- do
- case "$arg" in
- -o)
- shift
- ;;
- $object)
- shift
- ;;
- "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI")
- set fnord "$@"
- shift
- shift
- ;;
- *)
- set fnord "$@" "$arg"
- shift
- shift
- ;;
- esac
- done
- "$@" -E 2>/dev/null |
- sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile"
- rm -f "$depfile"
- echo "$object : \\" > "$depfile"
- sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s:: \1 \\:p' >> "$depfile"
- echo " " >> "$depfile"
- sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile"
- rm -f "$tmpdepfile"
- ;;
-
-msvcmsys)
- # This case exists only to let depend.m4 do its work. It works by
- # looking at the text of this script. This case will never be run,
- # since it is checked for above.
- exit 1
- ;;
-
-none)
- exec "$@"
- ;;
-
-*)
- echo "Unknown depmode $depmode" 1>&2
- exit 1
- ;;
-esac
-
-exit 0
-
-# Local Variables:
-# mode: shell-script
-# sh-indentation: 2
-# eval: (add-hook 'write-file-hooks 'time-stamp)
-# time-stamp-start: "scriptversion="
-# time-stamp-format: "%:y-%02m-%02d.%02H"
-# time-stamp-time-zone: "UTC"
-# time-stamp-end: "; # UTC"
-# End:
diff --git a/dns++.pc.in b/dns++.pc.in
new file mode 100644
index 0000000..d2a7e06
--- /dev/null
+++ b/dns++.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: dns++
+Description: BIND 10 DNS library
+Version: @PACKAGE_VERSION@
+Requires: botan-1.8
+Cflags: -I${includedir}/@PACKAGE_NAME@
+Libs: -L${libdir} -ldns++ -lcryptolink -lutil -lexceptions -lm
diff --git a/doc/.gitignore b/doc/.gitignore
new file mode 100644
index 0000000..fd8742e
--- /dev/null
+++ b/doc/.gitignore
@@ -0,0 +1,2 @@
+/version.ent
+html
diff --git a/doc/Doxyfile b/doc/Doxyfile
index c9c5c5a..8730ae4 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -25,13 +25,17 @@ DOXYFILE_ENCODING = UTF-8
# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
# by quotes) that should identify the project.
-PROJECT_NAME = BIND
+PROJECT_NAME = BIND10
# The PROJECT_NUMBER tag can be used to enter a project or revision number.
# This could be handy for archiving the generated documentation or
# if some version control system is used.
-PROJECT_NUMBER = 10.0.0
+# Currently this variable is overwritten (see devel target in Makefile.am)
+# If the number of parameters to overwrite increases, we should generate
+# Doxyfile (rename it to Doxyfile.in and generate during configure phase)
+
+PROJECT_NUMBER =
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
# base path where the generated documentation will be put.
@@ -47,7 +51,7 @@ OUTPUT_DIRECTORY = html
# source files, where putting all generated files in the same directory would
# otherwise cause performance problems for the file system.
-CREATE_SUBDIRS = NO
+CREATE_SUBDIRS = YES
# The OUTPUT_LANGUAGE tag is used to specify the language in which all
# documentation generated by doxygen is written. Doxygen will use this
@@ -281,7 +285,7 @@ TYPEDEF_HIDES_STRUCT = NO
# causing a significant performance penality.
# If the system has enough physical memory increasing the cache will improve the
# performance by keeping more symbols in memory. Note that the value works on
-# a logarithmic scale so increasing the size by one will rougly double the
+# a logarithmic scale so increasing the size by one will roughly double the
# memory usage. The cache size is given by this formula:
# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
# corresponding to a cache size of 2^16 = 65536 symbols
@@ -574,7 +578,8 @@ INPUT = ../src/lib/exceptions ../src/lib/cc \
../src/lib/log/compiler ../src/lib/asiolink/ ../src/lib/nsas \
../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
../src/bin/sockcreator/ ../src/lib/util/ ../src/lib/util/io/ \
- ../src/lib/resolve ../src/lib/acl ../src/bin/dhcp6 ../src/lib/dhcp
+ ../src/lib/resolve ../src/lib/acl ../src/bin/dhcp6 ../src/lib/dhcp \
+ ../src/bin/dhcp4 devel
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
@@ -591,7 +596,7 @@ INPUT_ENCODING = UTF-8
# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
-FILE_PATTERNS =
+FILE_PATTERNS = *.c *.cc *.h *.hpp *.dox
# The RECURSIVE tag can be used to turn specify whether or not subdirectories
# should be searched for input files as well. Possible values are YES and NO.
@@ -651,7 +656,7 @@ EXAMPLE_RECURSIVE = NO
# directories that contain image that are included in the documentation (see
# the \image command).
-IMAGE_PATH =
+IMAGE_PATH = ../doc/images
# The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program
@@ -773,7 +778,7 @@ GENERATE_HTML = YES
# If a relative path is entered the value of OUTPUT_DIRECTORY will be
# put in front of it. If left blank `html' will be used as the default path.
-HTML_OUTPUT = cpp
+HTML_OUTPUT = ../html
# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
@@ -954,7 +959,7 @@ ENUM_VALUES_PER_LINE = 4
# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
# Windows users are probably better off using the HTML help feature.
-GENERATE_TREEVIEW = NO
+GENERATE_TREEVIEW = YES
# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
# and Class Hierarchy pages using a tree view instead of an ordered list.
@@ -965,7 +970,7 @@ USE_INLINE_TREES = NO
# used to set the initial width (in pixels) of the frame in which the tree
# is shown.
-TREEVIEW_WIDTH = 250
+TREEVIEW_WIDTH = 180
# Use this tag to change the font size of Latex formulas included
# as images in the HTML documentation. The default is 10. Note that
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 31d0cfa..7642220 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,3 +1,16 @@
SUBDIRS = guide
EXTRA_DIST = version.ent.in
+
+devel:
+ mkdir -p html
+ (cat Doxyfile; echo PROJECT_NUMBER=$(PACKAGE_VERSION)) | doxygen - > html/doxygen.log 2> html/doxygen-error.log
+ echo `grep -i ": warning:" html/doxygen-error.log | wc -l` warnings/errors detected.
+
+clean:
+ rm -rf html
+
+# That's a bit of a hack, but we are making sure that devel target
+# is always valid. The alternative is to make devel depend on all
+# *.cc *.h files in the whole tree.
+.PHONY: devel
diff --git a/doc/devel/01-dns.dox b/doc/devel/01-dns.dox
new file mode 100644
index 0000000..e72dbbd
--- /dev/null
+++ b/doc/devel/01-dns.dox
@@ -0,0 +1,14 @@
+/**
+ *
+ * @page dns BIND10 DNS
+ *
+ * @section dns-auth b10-auth
+ *
+ * @todo: Describe b10-auth here.
+ *
+ * @section b10-cfgmgr b10-cfgmgr Overview
+ *
+ * @todo: Descibe b10-cfgmgr here.
+ *
+ *
+ */
diff --git a/doc/devel/02-dhcp.dox b/doc/devel/02-dhcp.dox
new file mode 100644
index 0000000..7ebd9b2
--- /dev/null
+++ b/doc/devel/02-dhcp.dox
@@ -0,0 +1,117 @@
+/**
+ * @page dhcpv4 DHCPv4 Server Component
+ *
+ * BIND10 offers DHCPv4 server implementation. It is implemented as
+ * b10-dhcp4 component. Its primary code is located in
+ * isc::dhcp::Dhcpv4Srv class. It uses \ref libdhcp extensively,
+ * especially isc::dhcp::Pkt4, isc::dhcp::Option and
+ * isc::dhcp::IfaceMgr classes. Currently this code offers skeleton
+ * functionality, i.e. it is able to receive and process incoming
+ * requests and trasmit responses. However, it does not have database
+ * management, so it returns only one, hardcoded lease to whoever asks
+ * for it.
+ *
+ * DHCPv4 server component does not support direct traffic (relayed
+ * only), as support for transmission to hosts without IPv4 address
+ * assigned is not implemented in IfaceMgr yet.
+ *
+ * DHCPv4 server component does not listen to BIND10 message queue.
+ *
+ * DHCPv4 server component does not use BIND10 logging yet.
+ *
+ * DHCPv4 server component is not integrated with boss yet.
+ *
+ * @page dhcpv6 DHCPv6 Server Component
+ *
+ * BIND10 offers DHCPv6 server implementation. It is implemented as
+ * b10-dhcp6 component. Its primary code is located in
+ * isc::dhcp::Dhcpv6Srv class. It uses \ref libdhcp extensively,
+ * especially lib::dhcp::Pkt6, isc::dhcp::Option and
+ * isc::dhcp::IfaceMgr classes. Currently this code offers skeleton
+ * functionality, i.e. it is able to receive and process incoming
+ * requests and trasmit responses. However, it does not have database
+ * management, so it returns only one, hardcoded lease to whoever asks
+ * for it.
+ *
+ * DHCPv6 server component does not support relayed traffic yet, as
+ * support for relay decapsulation is not implemented yet.
+ *
+ * DHCPv6 server component does not listen to BIND10 message queue.
+ *
+ * DHCPv6 server component does not use BIND10 logging yet.
+ *
+ * DHCPv6 server component is not integrated with boss yet.
+ *
+ * @page libdhcp libdhcp++ library
+ *
+ * @section libdhcpIntro Libdhcp++ Introduction
+ *
+ * libdhcp++ is an all-purpose DHCP-manipulation library, written in
+ * C++. It offers packet parsing and assembly, DHCPv4 and DHCPv6
+ * options parsing and ssembly, interface detection (currently on
+ * Linux systems only) and socket operations. Following classes are
+ * implemented:
+ *
+ * - isc::dhcp::Pkt4 - represents DHCPv4 packet.
+ * - isc::dhcp::Pkt6 - represents DHCPv6 packet.
+ *
+ * There are two pointer types defined: Pkt4Ptr and Pkt6Ptr. They are
+ * smart pointer and are using boost::shared_ptr. There are not const
+ * versions defined, as we assume that hooks can modify any aspect of
+ * the packet at almost any stage of processing.
+ *
+ * Both packets use collection of Option objects to represent DHCPv4
+ * and DHCPv6 options. The base class -- Option -- can be used to
+ * represent generic option that contains collection of
+ * bytes. Depending on if the option is instantiated as v4 or v6
+ * option, it will adjust its header (DHCPv4 options use 1 octet for
+ * type and 1 octet for length, while DHCPv6 options use 2 bytes for
+ * each).
+ *
+ * There are many specialized classes that are intended to handle options with
+ * specific content:
+ * - isc::dhcp::Option4AddrLst -- DHCPv4 option, contains one or more IPv4 addresses;
+ * - isc::dhcp::Option6AddrLst -- DHCPv6 option, contains one or more IPv6 addresses;
+ * - isc::dhcp::Option6IAAddr -- DHCPv6 option, represents IAADDR_OPTION (an option that
+ * contains IPv6 address with extra parameters);
+ * - isc::dhcp::Option6IA -- DHCPv6 option used to store IA_NA and its suboptions.
+ *
+ * All options can store sub-options (i.e. options that are stored within option
+ * rather than in a message directly). This functionality is commonly used in
+ * DHCPv6, but is rarely used in DHCPv4. isc::dhcp::Option::addOption(),
+ * isc::dhcp::Option::delOption(), isc::dhcp::Option::getOption() can be used
+ * for that purpose.
+ *
+ * @section lidhcpIfaceMgr Interface Manager
+ *
+ * Interface Manager (or IfaceMgr) is an abstraction layer about low-level
+ * network operations. In particlar, it provides information about existing
+ * network interfaces See isc::dhcp::IfaceMgr::Iface class and
+ * isc::dhcp::IfaceMgr::detectIfaces() and isc::dhcp::IfaceMgr::getIface().
+ *
+ * Currently there is interface detection is implemented in Linux only. There
+ * are plans to implement such support for other OSes, but they remain low
+ * priority for now.
+ *
+ * Generic parts of the code are isc::dhcp::IfaceMgr class in
+ * src/lib/dhcp/iface_mgr.cc file. OS-specific code is located in separate
+ * files, e.g. iface_mgr_linux.cc. Such separation should be maintained when
+ * additional code will be developed.
+ *
+ * For systems that interface detection is not supported on, there is a stub
+ * mechanism implemented. It assumes that interface name is read from a text
+ * file. This is a temporary solution and will be removed as soon as proper
+ * interface detection is implemented. It is not going to be developed further.
+ * To use this feature, store interfaces.txt file. It uses a simple syntax.
+ * Each line represents an interface name, followed by IPv4 or IPv6 address
+ * that follows it. This is usually link-local IPv6 address that the server
+ * should bind to. In theory this mechanism also supports IPv4, but it was
+ * never tested. The code currently supports only a single interface defined
+ * that way.
+ *
+ * Another useful methods are dedicated to transmission
+ * (isc::dhcp::IfaceMgr::send(), 2 overloads) and reception
+ * (isc::dhcp::IfaceMgr::receive4() and isc::dhcp::IfaceMgr::receive6()).
+ * Note that receive4() and receive6() methods may return NULL, e.g.
+ * when timeout is reached or if dhcp daemon receives a signal.
+ */
\ No newline at end of file
diff --git a/doc/devel/mainpage.dox b/doc/devel/mainpage.dox
new file mode 100644
index 0000000..a472add
--- /dev/null
+++ b/doc/devel/mainpage.dox
@@ -0,0 +1,36 @@
+/**
+ *
+ * @mainpage BIND10 Developer's Guide
+ *
+ * Welcome to BIND10 Developer's Guide. This documentation is addressed
+ * at existing and prospecting developers and programmers, who would like
+ * to gain insight into internal workings of BIND 10. It could also be useful
+ * for existing and prospective contributors.
+ *
+ * If you are a user or system administrator, rather than software engineer,
+ * you should read <a href="http://bind10.isc.org/docs/bind10-guide.html">BIND10
+ * Guide (Administrator Reference for BIND10)</a> instead.
+ *
+ * Regardless of your field of expertise, you are encouraged to visit
+ * <a href="http://bind10.isc.org/">BIND10 webpage (http://bind10.isc.org)</a>
+ *
+ * @section DNS
+ * - @subpage DataScrubbing
+ *
+ * @section DHCP
+ * - @subpage dhcpv4
+ * - @subpage dhcpv6
+ * - @subpage libdhcp
+ *
+ * @section misc Miscellaneous topics
+ * - @subpage LoggingApi
+ * - @subpage LoggingApiOverview
+ * - @subpage LoggingApiLoggerNames
+ * - @subpage LoggingApiLoggingMessages
+ * - @subpage SocketSessionUtility
+ * - <a href="./doxygen-error.log">Documentation warnings and errors</a>
+ *
+ * @todo: Move this logo to the right (and possibly up). Not sure what
+ * is the best way to do it in Doxygen, without using CSS hacks.
+ * @image html isc-logo.png
+ */
\ No newline at end of file
diff --git a/doc/guide/bind10-guide.html b/doc/guide/bind10-guide.html
index f6206a5..b879af5 100644
--- a/doc/guide/bind10-guide.html
+++ b/doc/guide/bind10-guide.html
@@ -1,51 +1,61 @@
-<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 framework that features Domain Name System (DNS) suite and Dynamic Host Configuration Protocol (DHCP) servers managed by Internet Systems Consortium (ISC). It includes DNS libraries, modular components for controlling authoritative and recursive DNS servers, and experimental DHCPv4 and DHCPv6 servers. This is the reference guide for BIND 10 version 20111129. The most up-to-date version of this document (in PDF, HTML, and plain text formats), 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="id2479
03"></a>BIND 10 Guide</h1></div><div><h2 class="subtitle">Administrator Reference for BIND 10</h2></div><div><p class="releaseinfo">This is the reference guide for BIND 10 version
- 20111129.</p></div><div><p class="copyright">Copyright © 2010-2011 Internet Systems Consortium, Inc.</p></div><div><div class="abstract" title="Abstract"><p class="title"><b>Abstract</b></p><p>BIND 10 is a framework that features Domain Name System
+<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 framework that features Domain Name System (DNS) suite and Dynamic Host Configuration Protocol (DHCP) servers managed by Internet Systems Consortium (ISC). It includes DNS libraries, modular components for controlling authoritative and recursive DNS servers, and experimental DHCPv4 and DHCPv6 servers. This is the reference guide for BIND 10 version 20120405. The most up-to-date version of this document (in PDF, HTML, and plain text formats), 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="idm146
04752"></a>BIND 10 Guide</h1></div><div><h2 class="subtitle">Administrator Reference for BIND 10</h2></div><div><p class="releaseinfo">This is the reference guide for BIND 10 version
+ 20120405.</p></div><div><p class="copyright">Copyright © 2010-2012 Internet Systems Consortium, Inc.</p></div><div><div class="abstract" title="Abstract"><p class="title"><b>Abstract</b></p><p>BIND 10 is a framework that features Domain Name System
(DNS) suite and Dynamic Host Configuration Protocol (DHCP)
servers managed by Internet Systems Consortium (ISC). It
includes DNS libraries, modular components for controlling
authoritative and recursive DNS servers, and experimental DHCPv4
and DHCPv6 servers.
</p><p>
- This is the reference guide for BIND 10 version 20111129.
+ This is the reference guide for BIND 10 version 20120405.
The most up-to-date version of this document (in PDF, HTML,
and plain text formats), 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="#id457902">1.1. Supported Platforms</a></span></dt><dt><span class="section"><a href="#id457914">1.2. Required Software</a></span></dt><dt><span class="section"><a href="#starting_stopping">1.3. Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">1.4. 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="#id458364">2.1. Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">2.2. Quick start</a></span></dt><dt><span class="section"><a href="#install">2.3. Installation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#id458582">2.3.1. Download Tar File</a></
span></dt><dt><span class="section"><a href="#id458605">2.3.2. Retrieve from Git</a></span></dt><dt><span class="section"><a href="#id458679">2.3.3. Configure before the build</a></span></dt><dt><span class="section"><a href="#id458789">2.3.4. Build</a></span></dt><dt><span class="section"><a href="#id458806">2.3.5. Install</a></span></dt><dt><span class="section"><a href="#id458832">2.3.6. 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">3.1. Starting BIND 10</a></span></dt><dt><span class="section"><a href="#bind10.config">3.2. Configuration of started processes</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">6.1. 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="#id459869">8.1. Server Configurations</a></span></dt><dt><span class="section"><a href="#id459942">8.2. Data Source Backends</a></span></dt><dt><span class="section"><a href="#id459978">8.3. Loading Master Zones Files</a></span></dt></dl></dd><dt><span class="chapter"><a href="#xfrin">9. Incoming Zone Transfers</a></span></dt><dd><dl><dt><span class="section"><a href="#id460133">9.1. Configuration for Incoming Zone Transfers</a></span></dt><dt><span class="section"><a href="#id460177">9.2. Enabling IXFR</a></span></dt><dt><span class="section"><a href="#zonemgr">9.3. Secondary Man
ager</a></span></dt><dt><span class="section"><a href="#id460320">9.4. Trigger an Incoming Zone Transfer Manually</a></span></dt></dl></dd><dt><span class="chapter"><a href="#xfrout">10. Outbound Zone Transfers</a></span></dt><dt><span class="chapter"><a href="#resolverserver">11. Recursive Name Server</a></span></dt><dd><dl><dt><span class="section"><a href="#id460633">11.1. Access Control</a></span></dt><dt><span class="section"><a href="#id460772">11.2. Forwarding</a></span></dt></dl></dd><dt><span class="chapter"><a href="#dhcp4">12. DHCPv4 Server</a></span></dt><dd><dl><dt><span class="section"><a href="#dhcp4-usage">12.1. DHCPv4 Server Usage</a></span></dt><dt><span class="section"><a href="#dhcp4-config">12.2. DHCPv4 Server Configuration</a></span></dt><dt><span class="section"><a href="#dhcp4-std">12.3. Supported standards</a></span></dt><dt><span class="section"><a href="#dhcp4-limit">12.4. DHCPv4 Server Limitations</a></span></dt></dl></dd><dt><span class="chapter"
><a href="#dhcp6">13. DHCPv6 Server</a></span></dt><dd><dl><dt><span class="section"><a href="#dhcp6-usage">13.1. DHCPv6 Server Usage</a></span></dt><dt><span class="section"><a href="#dhcp6-config">13.2. DHCPv6 Server Configuration</a></span></dt><dt><span class="section"><a href="#dhcp6-std">13.3. Supported DHCPv6 Standards</a></span></dt><dt><span class="section"><a href="#dhcp6-limit">13.4. DHCPv6 Server Limitations</a></span></dt></dl></dd><dt><span class="chapter"><a href="#libdhcp">14. libdhcp++ library</a></span></dt><dd><dl><dt><span class="section"><a href="#iface-detect">14.1. Interface detection</a></span></dt><dt><span class="section"><a href="#packet-handling">14.2. DHCPv4/DHCPv6 packet handling</a></span></dt></dl></dd><dt><span class="chapter"><a href="#statistics">15. Statistics</a></span></dt><dt><span class="chapter"><a href="#logging">16. Logging</a></span></dt><dd><dl><dt><span class="section"><a href="#id461660">16.1. Logging configuration</a></span></d
t><dd><dl><dt><span class="section"><a href="#id461675">16.1.1. Loggers</a></span></dt><dt><span class="section"><a href="#id461978">16.1.2. Output Options</a></span></dt><dt><span class="section"><a href="#id462172">16.1.3. Example session</a></span></dt></dl></dd><dt><span class="section"><a href="#id462428">16.2. Logging Message Format</a></span></dt></dl></dd><dt><span class="chapter"><a href="#id462551">17. Acknowledgements</a></span></dt></dl></div><div class="list-of-tables"><p><b>List of Tables</b></p><dl><dt>3.1. <a href="#id459146"></a></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="#id457902">1.1. Supported Platforms</a></span></dt><dt><span class="section"><a href="#id457914">1.2. Required Software</a></span></dt><dt><span class="section"><a
href="#starting_stopping">1.3. Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">1.4. Managing BIND 10</a></span></dt></dl></div><p>
+ </p></div></div></div><hr></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="preface"><a href="#idp17128">Preface</a></span></dt><dd><dl><dt><span class="section"><a href="#acknowledgements">1. Acknowledgements</a></span></dt></dl></dd><dt><span class="chapter"><a href="#intro">1. Introduction</a></span></dt><dd><dl><dt><span class="section"><a href="#idp20200">1.1. Supported Platforms</a></span></dt><dt><span class="section"><a href="#required-software">1.2. Required Software</a></span></dt><dt><span class="section"><a href="#starting_stopping">1.3. Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">1.4. 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="#build-requirements">2.1. Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">2.2. Quick
start</a></span></dt><dt><span class="section"><a href="#install">2.3. Installation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#idp70704">2.3.1. Download Tar File</a></span></dt><dt><span class="section"><a href="#idp72200">2.3.2. Retrieve from Git</a></span></dt><dt><span class="section"><a href="#idp77232">2.3.3. Configure before the build</a></span></dt><dt><span class="section"><a href="#idp84464">2.3.4. Build</a></span></dt><dt><span class="section"><a href="#idp85536">2.3.5. Install</a></span></dt><dt><span class="section"><a href="#idp87192">2.3.6. 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">3.1. Starting BIND 10</a></span></dt><dt><span class="section"><a href="#bind10.config">3.2. Configuration of started processes</a></span></dt></dl></dd><dt><sp
an 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">6.1. 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="#idp156456">8.1. Server Configurations</a></span></dt><dt><span class="section"><a href="#idp174000">8.2. Data Source Backends</a></span></dt><dd><dl><dt><span class="section"><a href="#in-memory-datasource">8.2.1. In-memory Data Source</a></span></dt></dl></dd><dt><span class="section"><a href="#idp185968">8.3. Loading Master Zones Files</a></span></dt></dl></dd><dt><span class="chapter">
<a href="#xfrin">9. Incoming Zone Transfers</a></span></dt><dd><dl><dt><span class="section"><a href="#idp196456">9.1. Configuration for Incoming Zone Transfers</a></span></dt><dt><span class="section"><a href="#idp199496">9.2. Enabling IXFR</a></span></dt><dt><span class="section"><a href="#zonemgr">9.3. Secondary Manager</a></span></dt><dt><span class="section"><a href="#idp209248">9.4. Trigger an Incoming Zone Transfer Manually</a></span></dt></dl></dd><dt><span class="chapter"><a href="#xfrout">10. Outbound Zone Transfers</a></span></dt><dt><span class="chapter"><a href="#resolverserver">11. Recursive Name Server</a></span></dt><dd><dl><dt><span class="section"><a href="#idp228136">11.1. Access Control</a></span></dt><dt><span class="section"><a href="#idp237328">11.2. Forwarding</a></span></dt></dl></dd><dt><span class="chapter"><a href="#dhcp4">12. DHCPv4 Server</a></span></dt><dd><dl><dt><span class="section"><a href="#dhcp4-usage">12.1. DHCPv4 Server Usage</a></span>
</dt><dt><span class="section"><a href="#dhcp4-config">12.2. DHCPv4 Server Configuration</a></span></dt><dt><span class="section"><a href="#dhcp4-std">12.3. Supported standards</a></span></dt><dt><span class="section"><a href="#dhcp4-limit">12.4. DHCPv4 Server Limitations</a></span></dt></dl></dd><dt><span class="chapter"><a href="#dhcp6">13. DHCPv6 Server</a></span></dt><dd><dl><dt><span class="section"><a href="#dhcp6-usage">13.1. DHCPv6 Server Usage</a></span></dt><dt><span class="section"><a href="#dhcp6-config">13.2. DHCPv6 Server Configuration</a></span></dt><dt><span class="section"><a href="#dhcp6-std">13.3. Supported DHCPv6 Standards</a></span></dt><dt><span class="section"><a href="#dhcp6-limit">13.4. DHCPv6 Server Limitations</a></span></dt></dl></dd><dt><span class="chapter"><a href="#libdhcp">14. libdhcp++ library</a></span></dt><dd><dl><dt><span class="section"><a href="#iface-detect">14.1. Interface detection</a></span></dt><dt><span class="section"><a href="#
packet-handling">14.2. DHCPv4/DHCPv6 packet handling</a></span></dt></dl></dd><dt><span class="chapter"><a href="#statistics">15. Statistics</a></span></dt><dt><span class="chapter"><a href="#logging">16. Logging</a></span></dt><dd><dl><dt><span class="section"><a href="#idp295312">16.1. Logging configuration</a></span></dt><dd><dl><dt><span class="section"><a href="#idp296304">16.1.1. Loggers</a></span></dt><dt><span class="section"><a href="#idp317512">16.1.2. Output Options</a></span></dt><dt><span class="section"><a href="#idp331704">16.1.3. Example session</a></span></dt></dl></dd><dt><span class="section"><a href="#idp349208">16.2. Logging Message Format</a></span></dt></dl></dd></dl></div><div class="list-of-tables"><p><b>List of Tables</b></p><dl><dt>3.1. <a href="#idp106648"></a></dt></dl></div><div class="preface" title="Preface"><div class="titlepage"><div><div><h2 class="title"><a name="idp17128"></a>Preface</h2></div></div></div><div class="toc"><p><b>Table of C
ontents</b></p><dl><dt><span class="section"><a href="#acknowledgements">1. Acknowledgements</a></span></dt></dl></div><div class="section" title="1. Acknowledgements"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="acknowledgements"></a>1. Acknowledgements</h2></div></div></div><p>ISC would like to acknowledge generous support for
+ BIND 10 development of DHCPv4 and DHCPv6 components provided
+ by <a class="ulink" href="http://www.comcast.com/" target="_top">Comcast</a>.</p></div></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="#idp20200">1.1. Supported Platforms</a></span></dt><dt><span class="section"><a href="#required-software">1.2. Required Software</a></span></dt><dt><span class="section"><a href="#starting_stopping">1.3. Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">1.4. Managing BIND 10</a></span></dt></dl></div><p>
BIND is the popular implementation of a DNS server, developer
interfaces, and DNS tools.
BIND 10 is a rewrite of BIND 9. BIND 10 is written in C++ and Python
and provides a modular environment for serving and maintaining DNS.
- </p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
- This guide covers the experimental prototype of
- BIND 10 version 20111129.
- </p></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
- BIND 10 provides a EDNS0- and DNSSEC-capable
- authoritative DNS server and a caching recursive name server
- which also provides forwarding.
- </p></div><div class="section" title="1.1. Supported Platforms"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id457902"></a>1.1. 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 8, and CentOS
- Linux 5.3.
+ BIND 10 provides a EDNS0- and DNSSEC-capable authoritative
+ DNS server and a caching recursive name server which also
+ provides forwarding.
+ </p><p>
+ This guide covers the experimental prototype of
+ BIND 10 version 20120405.
+ </p><div class="section" title="1.1. Supported Platforms"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="idp20200"></a>1.1. Supported Platforms</h2></div></div></div><p>
+ BIND 10 builds have been tested on Debian GNU/Linux 5 and unstable,
+ Ubuntu 9.10, NetBSD 5, Solaris 10, FreeBSD 7 and 8, CentOS
+ Linux 5.3, and MacOS 10.6.
It has been tested on Sparc, i386, and amd64 hardware
platforms.
It is planned for BIND 10 to build, install and run on
Windows and standard Unix-type platforms.
- </p></div><div class="section" title="1.2. Required Software"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id457914"></a>1.2. 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><div class="section" title="1.2. Required Software"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="required-software"></a>1.2. Required Software</h2></div></div></div><p>
+ BIND 10 requires at least Python 3.1
+ (<a class="ulink" href="http://www.python.org/" target="_top">http://www.python.org/</a>).
+ It has also been tested with Python 3.2.
</p><p>
- BIND 10 uses the Botan crypto library for C++. It requires
- at least Botan version 1.8.
+ BIND 10 uses the Botan crypto library for C++
+ (<a class="ulink" href="http://botan.randombit.net/" target="_top">http://botan.randombit.net/</a>).
+ It requires at least Botan version 1.8.
</p><p>
- BIND 10 uses the log4cplus C++ logging library. It requires
- at least log4cplus version 1.0.3.
+ BIND 10 uses the log4cplus C++ logging library
+ (<a class="ulink" href="http://log4cplus.sourceforge.net/" target="_top">http://log4cplus.sourceforge.net/</a>).
+ It requires at least log4cplus version 1.0.3.
+ </p><p>
+ The authoritative DNS server uses SQLite3
+ (<a class="ulink" href="http://www.sqlite.org/" target="_top">http://www.sqlite.org/</a>).
+
+ It needs at least SQLite version 3.3.9.
</p><p>
- 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.
+ and <span class="command"><strong>b10-zonemgr</strong></span> components require the
+ libpython3 library and the Python _sqlite3.so module
+ (which is included with Python).
+ The Python module needs to be built for the corresponding Python 3.
</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
Some operating systems do not provide these dependencies
in their default installation nor standard packages
@@ -68,11 +78,6 @@
</p><p>
</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem">
- <span class="command"><strong>b10-msgq</strong></span> —
- Message bus daemon.
- This process coordinates communication between all of the other
- BIND 10 processes.
- </li><li class="listitem">
<span class="command"><strong>b10-auth</strong></span> —
Authoritative DNS server.
This process serves DNS requests.
@@ -85,15 +90,29 @@
Command and control service.
This process allows external control of the BIND 10 system.
</li><li class="listitem">
+ <span class="command"><strong>b10-msgq</strong></span> —
+ Message bus daemon.
+ This process coordinates communication between all of the other
+ BIND 10 processes.
+ </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-sockcreator</strong></span> —
+ Socket creator daemon.
+ This process creates sockets used by
+ network-listening BIND 10 processes.
+ </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-stats-httpd</strong></span> —
+ HTTP server for statistics reporting.
+ This process reports statistics data in XML format over HTTP.
+ </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
@@ -119,8 +138,9 @@
</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem">
<span class="command"><strong>bindctl</strong></span> —
interactive administration interface.
- This is a command-line tool which allows an administrator
- to control BIND 10.
+ This is a low-level command-line tool which allows
+ a developer or an experienced administrator to control
+ BIND 10.
</li><li class="listitem">
<span class="command"><strong>b10-loadzone</strong></span> —
zone file loader.
@@ -142,7 +162,7 @@
and, of course, DNS. These include detailed developer
documentation and code examples.
- </p></div><div class="chapter" title="Chapter 2. Installation"><div class="titlepage"><div><div><h2 class="title"><a name="installation"></a>Chapter 2. Installation</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id458364">2.1. Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">2.2. Quick start</a></span></dt><dt><span class="section"><a href="#install">2.3. Installation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#id458582">2.3.1. Download Tar File</a></span></dt><dt><span class="section"><a href="#id458605">2.3.2. Retrieve from Git</a></span></dt><dt><span class="section"><a href="#id458679">2.3.3. Configure before the build</a></span></dt><dt><span class="section"><a href="#id458789">2.3.4. Build</a></span></dt><dt><span class="section"><a href="#id458806">2.3.5. Install</a></span></dt><dt><span class="section"><a href="#id458832">2.3.6. Install Hi
erarchy</a></span></dt></dl></dd></dl></div><div class="section" title="2.1. Building Requirements"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id458364"></a>2.1. Building Requirements</h2></div></div></div><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="#build-requirements">2.1. Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">2.2. Quick start</a></span></dt><dt><span class="section"><a href="#install">2.3. Installation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#idp70704">2.3.1. Download Tar File</a></span></dt><dt><span class="section"><a href="#idp72200">2.3.2. Retrieve from Git</a></span></dt><dt><span class="section"><a href="#idp77232">2.3.3. Configure before the build</a></span></dt><dt><span class="section"><a href="#idp84464">2.3.4. Build</a></span></dt><dt><span class="section"><a href="#idp85536">2.3.5. Install</a></span></dt><dt><span class="section"><a href="#idp87192">2.3.6.
Install Hierarchy</a></span></dt></dl></dd></dl></div><div class="section" title="2.1. Building Requirements"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="build-requirements"></a>2.1. Building Requirements</h2></div></div></div><p>
In addition to the run-time requirements, building BIND 10
from source code requires various development include headers.
</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
@@ -152,7 +172,9 @@
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.35 is required.
+ build-time headers
+ (<a class="ulink" href="http://www.boost.org/" target="_top">http://www.boost.org/</a>).
+ At least Boost version 1.35 is required.
</p><p>
@@ -160,17 +182,13 @@
1.8) and the log4cplus (at least version 1.0.3)
development include headers.
</p><p>
-
- The Python Library and Python _sqlite3 module are required to
- enable the Xfrout and Xfrin support.
- </p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
- The Python related libraries and modules need to be built
- for Python 3.1.
- </p></div><p>
Building BIND 10 also requires a C++ compiler and
standard development headers, make, and pkg-config.
BIND 10 builds have been tested with GCC g++ 3.4.3, 4.1.2,
4.1.3, 4.2.1, 4.3.2, and 4.4.1; Clang++ 2.8; and Sun C++ 5.10.
+ </p><p>
+ Visit the wiki at <a class="ulink" href="http://bind10.isc.org/wiki/SystemSpecificNotes" target="_top">http://bind10.isc.org/wiki/SystemSpecificNotes</a>
+ for system-specific installation tips.
</p></div><div class="section" title="2.2. Quick start"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="quickstart"></a>2.2. Quick start</h2></div></div></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
This quickly covers the standard steps for installing
and deploying BIND 10 as an authoritative name server using
@@ -179,7 +197,7 @@
</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">
- Install required build dependencies.
+ Install required run-time and build dependencies.
</li><li class="listitem">
Download the BIND 10 source tar file from
<a class="ulink" href="ftp://ftp.isc.org/isc/bind10/" target="_top">ftp://ftp.isc.org/isc/bind10/</a>.
@@ -206,14 +224,14 @@
the Git code revision control system or as a downloadable
tar file. It may also be available in pre-compiled ready-to-use
packages from operating system vendors.
- </p><div class="section" title="2.3.1. Download Tar File"><div class="titlepage"><div><div><h3 class="title"><a name="id458582"></a>2.3.1. Download Tar File</h3></div></div></div><p>
+ </p><div class="section" title="2.3.1. Download Tar File"><div class="titlepage"><div><div><h3 class="title"><a name="idp70704"></a>2.3.1. 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="2.3.2. Retrieve from Git"><div class="titlepage"><div><div><h3 class="title"><a name="id458605"></a>2.3.2. Retrieve from Git</h3></div></div></div><p>
+ </p></div><div class="section" title="2.3.2. Retrieve from Git"><div class="titlepage"><div><div><h3 class="title"><a name="idp72200"></a>2.3.2. 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.
@@ -230,10 +248,10 @@
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">git://bind10.isc.org/bind10</code>;
+ <code class="filename">git://git.bind10.isc.org/bind10</code>;
for example:
- </p><pre class="screen">$ <strong class="userinput"><code>git clone git://bind10.isc.org/bind10</code></strong></pre><p>
+ </p><pre class="screen">$ <strong class="userinput"><code>git clone git://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
@@ -247,7 +265,7 @@
<span class="command"><strong>autoheader</strong></span>,
<span class="command"><strong>automake</strong></span>,
and related commands.
- </p></div><div class="section" title="2.3.3. Configure before the build"><div class="titlepage"><div><div><h3 class="title"><a name="id458679"></a>2.3.3. Configure before the build</h3></div></div></div><p>
+ </p></div><div class="section" title="2.3.3. Configure before the build"><div class="titlepage"><div><div><h3 class="title"><a name="idp77232"></a>2.3.3. 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:
@@ -278,16 +296,16 @@
</p><p>
If the configure fails, it may be due to missing or old
dependencies.
- </p></div><div class="section" title="2.3.4. Build"><div class="titlepage"><div><div><h3 class="title"><a name="id458789"></a>2.3.4. Build</h3></div></div></div><p>
+ </p></div><div class="section" title="2.3.4. Build"><div class="titlepage"><div><div><h3 class="title"><a name="idp84464"></a>2.3.4. 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="2.3.5. Install"><div class="titlepage"><div><div><h3 class="title"><a name="id458806"></a>2.3.5. Install</h3></div></div></div><p>
+ </p></div><div class="section" title="2.3.5. Install"><div class="titlepage"><div><div><h3 class="title"><a name="idp85536"></a>2.3.5. 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="2.3.6. Install Hierarchy"><div class="titlepage"><div><div><h3 class="title"><a name="id458832"></a>2.3.6. 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="2.3.6. Install Hierarchy"><div class="titlepage"><div><div><h3 class="title"><a name="idp87192"></a>2.3.6. 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> —
@@ -341,12 +359,10 @@
</p><p>
In its default configuration, 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,
- <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.
+ <span class="command"><strong>b10-cmdctl</strong></span> for administration tools to
+ communicate with the system,
+ <span class="command"><strong>b10-stats</strong></span> for statistics collection, and
+ <span class="command"><strong>b10-stats-httpd</strong></span> for statistics reporting.
</p><div class="section" title="3.1. Starting BIND 10"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="start"></a>3.1. Starting BIND 10</h2></div></div></div><p>
To start the BIND 10 service, simply run <span class="command"><strong>bind10</strong></span>.
Run it with the <code class="option">--verbose</code> switch to
@@ -364,12 +380,7 @@
The configuration is in the Boss/components section. Each element
represents one component, which is an abstraction of a process
(currently there's also one component which doesn't represent
- a process). If you didn't want to transfer out at all (your server
- is a slave only), you would just remove the corresponding component
- from the set, like this and the process would be stopped immediately
- (and not started on the next startup):
- </p><pre class="screen">> <strong class="userinput"><code>config remove Boss/components b10-xfrout</code></strong>
-> <strong class="userinput"><code>config commit</code></strong></pre><p>
+ a process).
</p><p>
To add a process to the set, let's say the resolver (which not started
by default), you would do this:
@@ -387,7 +398,7 @@
during startup or shutdown. Unless specified, the component is started
in usual way. This is the list of components that need to be started
in a special way, with the value of special used for them:
- </p><div class="table"><a name="id459146"></a><p class="title"><b>Table 3.1. </b></p><div class="table-contents"><table border="1"><colgroup><col align="left"><col align="left"><col align="left"></colgroup><thead><tr><th align="left">Component</th><th align="left">Special</th><th align="left">Description</th></tr></thead><tbody><tr><td align="left">b10-auth</td><td align="left">auth</td><td align="left">Authoritative server</td></tr><tr><td align="left">b10-resolver</td><td align="left">resolver</td><td align="left">The resolver</td></tr><tr><td align="left">b10-cmdctl</td><td align="left">cmdctl</td><td align="left">The command control (remote control interface)</td></tr><tr><td align="left">setuid</td><td align="left">setuid</td><td align="left">Virtual component, see below</td></tr></tbody></table></div></div><p><br class="table-break">
+ </p><div class="table"><a name="idp106648"></a><p class="title"><b>Table 3.1. </b></p><div class="table-contents"><table border="1"><colgroup><col align="left"><col align="left"><col align="left"></colgroup><thead><tr><th align="left">Component</th><th align="left">Special</th><th align="left">Description</th></tr></thead><tbody><tr><td align="left">b10-auth</td><td align="left">auth</td><td align="left">Authoritative server</td></tr><tr><td align="left">b10-resolver</td><td align="left">resolver</td><td align="left">The resolver</td></tr><tr><td align="left">b10-cmdctl</td><td align="left">cmdctl</td><td align="left">The command control (remote control interface)</td></tr></tbody></table></div></div><p><br class="table-break">
</p><p>
The kind specifies how a failure of the component should
be handled. If it is set to <span class="quote">“<span class="quote">dispensable</span>”</span>
@@ -407,6 +418,7 @@
The priority defines order in which the components should start.
The ones with higher number are started sooner than the ones with
lower ones. If you don't set it, 0 (zero) is used as the priority.
+ Usually, leaving it at the default is enough.
</p><p>
There are other parameters we didn't use in our example.
One of them is <span class="quote">“<span class="quote">address</span>”</span>. It is the address
@@ -425,7 +437,7 @@
This system allows you to start the same component multiple times
(by including it in the configuration with different names, but the
same process setting). However, the rest of the system doesn't expect
- such situation, so it would probably not do what you want. Such
+ such a situation, so it would probably not do what you want. Such
support is yet to be implemented.
</p></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
The configuration is quite powerful, but that includes
@@ -433,29 +445,31 @@
<span class="command"><strong>b10-cmdctl</strong></span>, but then you couldn't
change it back the usual way, as it would require it to
be running (you would have to find and edit the configuration
- directly). Also, some modules might have dependencies
- -- <span class="command"><strong>b10-stats-httpd</strong></span> need
+ directly). Also, some modules might have dependencies:
+ <span class="command"><strong>b10-stats-httpd</strong></span> needs
<span class="command"><strong>b10-stats</strong></span>, <span class="command"><strong>b10-xfrout</strong></span>
- needs the <span class="command"><strong>b10-auth</strong></span> to be running, etc.
+ needs <span class="command"><strong>b10-auth</strong></span> to be running, etc.
</p><p>
In short, you should think twice before disabling something here.
</p></div><p>
- Now, to the mysterious setuid virtual component. If you
- use the <span class="command"><strong>-u</strong></span> option to start the
- <span class="command"><strong>bind10</strong></span> as root, but change the user
- later, we need to start the <span class="command"><strong>b10-auth</strong></span> or
- <span class="command"><strong>b10-resolver</strong></span> as root (until the socket
- creator is finished). So we need to specify
- the time when the switch from root do the given user happens
- and that's what the setuid component is for. The switch is
- done at the time the setuid component would be started, if
- it was a process. The default configuration contains the
- setuid component with priority 5, <span class="command"><strong>b10-auth</strong></span>
- has 10 to be started before the switch and everything else
- is without priority, so it is started after the switch.
+ It is possible to start some components multiple times (currently
+ <span class="command"><strong>b10-auth</strong></span> and <span class="command"><strong>b10-resolzer</strong></span>).
+ You might want to do that to gain more performance (each one uses only
+ single core). Just put multiple entries under different names, like
+ this, with the same config:
+ </p><pre class="screen">> <strong class="userinput"><code>config add Boss/components b10-resolver-2</code></strong>
+> <strong class="userinput"><code>config set Boss/components/b10-resolver-2/special resolver</code></strong>
+> <strong class="userinput"><code>config set Boss/components/b10-resolver-2/kind needed</code></strong>
+> <strong class="userinput"><code>config commit</code></strong></pre><p>
+ </p><p>
+ However, this is work in progress and the support is not yet complete.
+ For example, each resolver will have its own cache, each authoritative
+ server will keep its own copy of in-memory data and there could be
+ problems with locking the sqlite database, if used. The configuration
+ might be changed to something more convenient in future.
</p></div></div><div class="chapter" title="Chapter 4. Command channel"><div class="titlepage"><div><div><h2 class="title"><a name="msgq"></a>Chapter 4. Command channel</h2></div></div></div><p>
The BIND 10 components use the <span class="command"><strong>b10-msgq</strong></span>
message routing daemon to communicate with other BIND 10 components.
@@ -488,7 +502,7 @@
manager via <span class="command"><strong>b10-cmdctl</strong></span>'s REST-ful interface.
<span class="command"><strong>b10-cmdctl</strong></span> is covered in <a class="xref" href="#cmdctl" title="Chapter 6. Remote control daemon">Chapter 6, <i>Remote control daemon</i></a>.
</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
- The development prototype release only provides the
+ The development prototype release only provides
<span class="command"><strong>bindctl</strong></span> as a user interface to
<span class="command"><strong>b10-cmdctl</strong></span>.
Upcoming releases will provide another interactive command-line
@@ -575,7 +589,7 @@
The port can be set by using the <code class="option">--port</code> command line option.
The address to listen on can be set using the <code class="option">--address</code> command
line argument.
- Each HTTPS connection is stateless and timesout in 1200 seconds
+ Each HTTPS connection is stateless and times out in 1200 seconds
by default. This can be
redefined by using the <code class="option">--idle-timeout</code> command line argument.
</p><div class="section" title="6.1. Configuration specification for b10-cmdctl"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="cmdctl.spec"></a>6.1. Configuration specification for b10-cmdctl</h2></div></div></div><p>
@@ -612,32 +626,92 @@ 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="#id459869">8.1. Server Configurations</a></span></dt><dt><span class="section"><a href="#id459942">8.2. Data Source Backends</a></span></dt><dt><span class="section"><a href="#id459978">8.3. 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="#idp156456">8.1. Server Configurations</a></span></dt><dt><span class="section"><a href="#idp174000">8.2. Data Source Backends</a></span></dt><dd><dl><dt><span class="section"><a href="#in-memory-datasource">8.2.1. In-memory Data Source</a></span></dt></dl></dd><dt><span class="section"><a href="#idp185968">8.3. 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="section" title="8.1. Server Configurations"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id459869"></a>8.1. Server Configurations</h2></div></div></div><p>
+ </p><div class="section" title="8.1. Server Configurations"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="idp156456"></a>8.1. 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>.
- The configuration data item is:
+ The configuration data items are:
</p><div class="variablelist"><dl><dt><span class="term">database_file</span></dt><dd>This is an optional string to define the path to find
the SQLite3 database file.
Note: Later the DNS server will use various data source backends.
This may be a temporary setting until then.
+ </dd><dt><span class="term">datasources</span></dt><dd>
+ <code class="varname">datasources</code> configures data sources.
+ The list items include:
+ <code class="varname">type</code> to define the required data source type
+ (such as <span class="quote">“<span class="quote">memory</span>”</span>);
+ <code class="varname">class</code> to optionally select the class
+ (it defaults to <span class="quote">“<span class="quote">IN</span>”</span>);
+ and
+ <code class="varname">zones</code> to define the
+ <code class="varname">file</code> path name and the
+ <code class="varname">origin</code> (default domain).
+
+ By default, this is empty.
+
+ <div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
+ 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 <span class="command"><strong>named-compilezone -D</strong></span>.
+ </p></div>
+
+ </dd><dt><span class="term">listen_on</span></dt><dd>
+ <code class="varname">listen_on</code> is a list of addresses and ports for
+ <span class="command"><strong>b10-auth</strong></span> to listen on.
+ The list items are the <code class="varname">address</code> string
+ and <code class="varname">port</code> number.
+ By default, <span class="command"><strong>b10-auth</strong></span> listens on port 53
+ on the IPv6 (::) and IPv4 (0.0.0.0) wildcard addresses.
+ </dd><dt><span class="term">statistics-interval</span></dt><dd>
+ <code class="varname">statistics-interval</code> is the timer interval
+ in seconds for <span class="command"><strong>b10-auth</strong></span> to share its
+ statistics information to
+ <span class="citerefentry"><span class="refentrytitle">b10-stats</span>(8)</span>.
+ Statistics updates can be disabled by setting this to 0.
+ The default is 60.
</dd></dl></div><p>
</p><p>
- The configuration command is:
-
- </p><div class="variablelist"><dl><dt><span class="term">shutdown</span></dt><dd>Stop the authoritative DNS server.
+ The configuration commands are:
+
+ </p><div class="variablelist"><dl><dt><span class="term">loadzone</span></dt><dd>
+ <span class="command"><strong>loadzone</strong></span> tells <span class="command"><strong>b10-auth</strong></span>
+ to load or reload a zone file. The arguments include:
+ <code class="varname">class</code> which optionally defines the class
+ (it defaults to <span class="quote">“<span class="quote">IN</span>”</span>);
+ <code class="varname">origin</code> is the domain name of the zone;
+ and
+ <code class="varname">datasrc</code> optionally defines the type of datasource
+ (it defaults to <span class="quote">“<span class="quote">memory</span>”</span>).
+
+ <div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
+ In this development version, currently this only supports the
+ IN class and the memory data source.
+ </p></div>
+ </dd><dt><span class="term">sendstats</span></dt><dd>
+ <span class="command"><strong>sendstats</strong></span> tells <span class="command"><strong>b10-auth</strong></span>
+ to send its statistics data to
+ <span class="citerefentry"><span class="refentrytitle">b10-stats</span>(8)</span>
+ immediately.
+ </dd><dt><span class="term">shutdown</span></dt><dd>Stop the authoritative DNS server.
+ This has an optional <code class="varname">pid</code> argument to
+ select the process ID to stop.
+ (Note that the BIND 10 boss process may restart this service
+ if configured.)
</dd></dl></div><p>
- </p></div><div class="section" title="8.2. Data Source Backends"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id459942"></a>8.2. 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="8.2. Data Source Backends"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="idp174000"></a>8.2. Data Source Backends</h2></div></div></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
For the development prototype release, <span class="command"><strong>b10-auth</strong></span>
supports a SQLite3 data source backend and in-memory data source
backend.
@@ -649,11 +723,57 @@ This may be a temporary setting until then.
(The full path is what was defined at build configure time for
<code class="option">--localstatedir</code>.
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="8.3. Loading Master Zones Files"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id459978"></a>8.3. Loading Master Zones Files</h2></div></div></div><p>
+ This data file location may be changed by defining the
+ <span class="quote">“<span class="quote">database_file</span>”</span> configuration.
+ </p><div class="section" title="8.2.1. In-memory Data Source"><div class="titlepage"><div><div><h3 class="title"><a name="in-memory-datasource"></a>8.2.1. In-memory Data Source</h3></div></div></div><p>
+
+ The following commands to <span class="command"><strong>bindctl</strong></span>
+ provide an example of configuring an in-memory data
+ source containing the <span class="quote">“<span class="quote">example.com</span>”</span> zone
+ with the zone file named <span class="quote">“<span class="quote">example.com.zone</span>”</span>:
+
+
+
+ </p><pre class="screen">> <strong class="userinput"><code>config add Auth/datasources</code></strong>
+> <strong class="userinput"><code>config set Auth/datasources[0]/type "<code class="option">memory</code>"</code></strong>
+> <strong class="userinput"><code>config add Auth/datasources[0]/zones</code></strong>
+> <strong class="userinput"><code>config set Auth/datasources[0]/zones[0]/origin "<code class="option">example.com</code>"</code></strong>
+> <strong class="userinput"><code>config set Auth/datasources[0]/zones[0]/file "<code class="option">example.com.zone</code>"</code></strong>
+> <strong class="userinput"><code>config commit</code></strong></pre><p>
+
+ The authoritative server will begin serving it immediately
+ after it is loaded.
+ </p><p>
+ Use the <span class="command"><strong>Auth loadzone</strong></span> command in
+ <span class="command"><strong>bindctl</strong></span> to reload a changed master
+ file into memory; for example:
+
+ </p><pre class="screen">> <strong class="userinput"><code>Auth loadzone origin="example.com"</code></strong>
+</pre><p>
+
+ </p><p>
+ By default, the memory data source is disabled; it must be
+ configured explicitly. To disable all the in-memory zones,
+ specify a null list for <code class="varname">Auth/datasources</code>:
+
+
+
+ </p><pre class="screen">> <strong class="userinput"><code>config set Auth/datasources/ []</code></strong>
+> <strong class="userinput"><code>config commit</code></strong></pre><p>
+ </p><p>
+ The following example stops serving a specific zone:
+
+ </p><pre class="screen">> <strong class="userinput"><code>config remove Auth/datasources[<code class="option">0</code>]/zones[<code class="option">0</code>]</code></strong>
+> <strong class="userinput"><code>config commit</code></strong></pre><p>
+
+ (Replace the list number(s) in
+ <code class="varname">datasources[<em class="replaceable"><code>0</code></em>]</code>
+ and/or <code class="varname">zones[<em class="replaceable"><code>0</code></em>]</code>
+ for the relevant zone as needed.)
+
+ </p></div></div><div class="section" title="8.3. Loading Master Zones Files"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="idp185968"></a>8.3. 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
+ into a BIND 10 SQLite3 data source by using the
<span class="command"><strong>b10-loadzone</strong></span> utility.
</p><p>
<span class="command"><strong>b10-loadzone</strong></span> supports the following
@@ -670,7 +790,7 @@ This may be a temporary setting until then.
default origin for loaded zone file records.
</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
In the development prototype release, only the SQLite3 back
- end is used.
+ end is used by <span class="command"><strong>b10-loadzone</strong></span>.
By default, it stores the zone data in
<code class="filename">/usr/local/var/bind10-devel/zone.sqlite3</code>
unless the <code class="option">-d</code> switch is used to set the
@@ -680,7 +800,7 @@ This may be a temporary setting until then.
If you reload a zone already existing in the database,
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><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id460133">9.1. Configuration for Incoming Zone Transfers</a></span></dt><dt><span class="section"><a href="#id460177">9.2. Enabling IXFR</a></span></dt><dt><span class="section"><a href="#zonemgr">9.3. Secondary Manager</a></span></dt><dt><span class="section"><a href="#id460320">9.4. Trigger an Incoming Zone Transfer Manually</a></span></dt></dl></div><p>
+ </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><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#idp196456">9.1. Configuration for Incoming Zone Transfers</a></span></dt><dt><span class="section"><a href="#idp199496">9.2. Enabling IXFR</a></span></dt><dt><span class="section"><a href="#zonemgr">9.3. Secondary Manager</a></span></dt><dt><span class="section"><a href="#idp209248">9.4. Trigger an Incoming Zone Transfer Manually</a></span></dt></dl></div><p>
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 corresponding BIND 10
@@ -698,7 +818,7 @@ This may be a temporary setting until then.
In the current development release of BIND 10, incoming zone
transfers are only available for SQLite3-based data sources,
that is, they don't work for an in-memory data source.
- </p></div><div class="section" title="9.1. Configuration for Incoming Zone Transfers"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id460133"></a>9.1. Configuration for Incoming Zone Transfers</h2></div></div></div><p>
+ </p></div><div class="section" title="9.1. Configuration for Incoming Zone Transfers"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="idp196456"></a>9.1. Configuration for Incoming Zone Transfers</h2></div></div></div><p>
In practice, you need to specify a list of secondary zones to
enable incoming zone transfers for these zones (you can still
trigger a zone transfer manually, without a prior configuration
@@ -714,7 +834,7 @@ This may be a temporary setting until then.
> <strong class="userinput"><code>config commit</code></strong></pre><p>
(We assume there has been no zone configuration before).
- </p></div><div class="section" title="9.2. Enabling IXFR"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id460177"></a>9.2. Enabling IXFR</h2></div></div></div><p>
+ </p></div><div class="section" title="9.2. Enabling IXFR"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="idp199496"></a>9.2. Enabling IXFR</h2></div></div></div><p>
As noted above, <span class="command"><strong>b10-xfrin</strong></span> uses AXFR for
zone transfers by default. To enable IXFR for zone transfers
for a particular zone, set the <strong class="userinput"><code>use_ixfr</code></strong>
@@ -766,7 +886,7 @@ This may be a temporary setting until then.
(i.e. no SOA record for it), <span class="command"><strong>b10-zonemgr</strong></span>
will automatically tell <span class="command"><strong>b10-xfrin</strong></span>
to transfer the zone in.
- </p></div><div class="section" title="9.4. Trigger an Incoming Zone Transfer Manually"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id460320"></a>9.4. Trigger an Incoming Zone Transfer Manually</h2></div></div></div><p>
+ </p></div><div class="section" title="9.4. Trigger an Incoming Zone Transfer Manually"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="idp209248"></a>9.4. Trigger an Incoming Zone Transfer Manually</h2></div></div></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.
For example, at the <span class="command"><strong>bindctl</strong></span> prompt run:
@@ -804,41 +924,28 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)</pre><p>
for <code class="option">transfer_acl</code> were divided for
readability. In the actual input it must be in a single line.
</p></div><p>
- If you want to require TSIG in access control, a separate TSIG
- "key ring" must be configured specifically
- for <span class="command"><strong>b10-xfrout</strong></span> as well as a system wide
- key ring, both containing a consistent set of keys.
+ If you want to require TSIG in access control, a system wide TSIG
+ "key ring" must be configured.
For example, to change the previous example to allowing requests
from 192.0.2.1 signed by a TSIG with a key name of
"key.example", you'll need to do this:
</p><pre class="screen">> <strong class="userinput"><code>config set tsig_keys/keys ["key.example:<base64-key>"]</code></strong>
-> <strong class="userinput"><code>config set Xfrout/tsig_keys/keys ["key.example:<base64-key>"]</code></strong>
> <strong class="userinput"><code>config set Xfrout/zone_config[0]/transfer_acl [{"action": "ACCEPT", "from": "192.0.2.1", "key": "key.example"}]</code></strong>
-> <strong class="userinput"><code>config commit</code></strong></pre><p>
- The first line of configuration defines a system wide key ring.
- This is necessary because the <span class="command"><strong>b10-auth</strong></span> server
- also checks TSIGs and it uses the system wide configuration.
- </p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
- In a future version, <span class="command"><strong>b10-xfrout</strong></span> will also
- use the system wide TSIG configuration.
+> <strong class="userinput"><code>config commit</code></strong></pre><p>Both Xfrout and Auth will use the system wide keyring to check
+ TSIGs in the incoming messages and to sign responses.</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
The way to specify zone specific configuration (ACLs, etc) is
- likely to be changed, too.
- </p></div></div><div class="chapter" title="Chapter 11. Recursive Name Server"><div class="titlepage"><div><div><h2 class="title"><a name="resolverserver"></a>Chapter 11. Recursive Name Server</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id460633">11.1. Access Control</a></span></dt><dt><span class="section"><a href="#id460772">11.2. Forwarding</a></span></dt></dl></div><p>
+ likely to be changed.
+ </p></div></div><div class="chapter" title="Chapter 11. Recursive Name Server"><div class="titlepage"><div><div><h2 class="title"><a name="resolverserver"></a>Chapter 11. Recursive Name Server</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#idp228136">11.1. Access Control</a></span></dt><dt><span class="section"><a href="#idp237328">11.2. 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 or both.
- By default, it starts the authoritative service.
-
-
- You may change this using <span class="command"><strong>bindctl</strong></span>, for example:
+ By default, it doesn't start either one. You may change this using
+ <span class="command"><strong>bindctl</strong></span>, for example:
</p><pre class="screen">
-> <strong class="userinput"><code>config remove Boss/components b10-xfrout</code></strong>
-> <strong class="userinput"><code>config remove Boss/components b10-xfrin</code></strong>
-> <strong class="userinput"><code>config remove Boss/components b10-auth</code></strong>
> <strong class="userinput"><code>config add Boss/components b10-resolver</code></strong>
> <strong class="userinput"><code>config set Boss/components/b10-resolver/special resolver</code></strong>
> <strong class="userinput"><code>config set Boss/components/b10-resolver/kind needed</code></strong>
@@ -862,7 +969,7 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)</pre><p>
</pre><p>
</p><p>(Replace the <span class="quote">“<span class="quote"><em class="replaceable"><code>2</code></em></span>”</span>
as needed; run <span class="quote">“<span class="quote"><strong class="userinput"><code>config show
- Resolver/listen_on</code></strong></span>”</span> if needed.)</p><div class="section" title="11.1. Access Control"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id460633"></a>11.1. Access Control</h2></div></div></div><p>
+ Resolver/listen_on</code></strong></span>”</span> if needed.)</p><div class="section" title="11.1. Access Control"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="idp228136"></a>11.1. Access Control</h2></div></div></div><p>
By default, the <span class="command"><strong>b10-resolver</strong></span> daemon only accepts
DNS queries from the localhost (127.0.0.1 and ::1).
The <code class="option">Resolver/query_acl</code> configuration may
@@ -895,7 +1002,7 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)</pre><p>
</pre><p>(Replace the <span class="quote">“<span class="quote"><em class="replaceable"><code>2</code></em></span>”</span>
as needed; run <span class="quote">“<span class="quote"><strong class="userinput"><code>config show
Resolver/query_acl</code></strong></span>”</span> if needed.)</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>This prototype access control configuration
- syntax may be changed.</p></div></div><div class="section" title="11.2. Forwarding"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id460772"></a>11.2. Forwarding</h2></div></div></div><p>
+ syntax may be changed.</p></div></div><div class="section" title="11.2. Forwarding"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="idp237328"></a>11.2. Forwarding</h2></div></div></div><p>
To enable forwarding, the upstream address and port must be
configured to forward queries to, such as:
@@ -924,29 +1031,29 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)</pre><p>
clients. Even though principles of both DHCPv4 and DHCPv6 are
somewhat similar, these are two radically different
protocols. BIND10 offers server implementations for both DHCPv4
- and DHCPv6. This chapter is about DHCP for IPv4. For description of
- DHCPv6 server, see <a class="xref" href="#dhcp6" title="Chapter 13. DHCPv6 Server">Chapter 13, <i>DHCPv6 Server</i></a>.</p><p>DHCPv6 server component is currently under intense
+ and DHCPv6. This chapter is about DHCP for IPv4. For a description
+ of the DHCPv6 server, see <a class="xref" href="#dhcp6" title="Chapter 13. DHCPv6 Server">Chapter 13, <i>DHCPv6 Server</i></a>.</p><p>The DHCPv4 server component is currently under intense
development. You may want to check out <a class="ulink" href="http://bind10.isc.org/wiki/Kea" target="_top">BIND10 DHCP (Kea) wiki</a>
and recent posts on <a class="ulink" href="https://lists.isc.org/mailman/listinfo/bind10-dev" target="_top">BIND10
- developers mailing list</a>.</p><p>DHCPv4 and DHCPv6 components in BIND10 architecture are
+ developers mailing list</a>.</p><p>The DHCPv4 and DHCPv6 components in BIND10 architecture are
internally code named <span class="quote">“<span class="quote">Kea</span>”</span>.</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
As of December 2011, both DHCPv4 and DHCPv6 components are
skeleton servers. That means that while they are capable of
performing DHCP configuration, they are not fully functional
- yet. In particular, both do not have functional lease
+ yet. In particular, neither has functional lease
databases. This means that they will assign the same, fixed,
hardcoded addresses to any client that will ask. See <a class="xref" href="#dhcp4-limit" title="12.4. DHCPv4 Server Limitations">Section 12.4, “DHCPv4 Server Limitations”</a> and <a class="xref" href="#dhcp6-limit" title="13.4. DHCPv6 Server Limitations">Section 13.4, “DHCPv6 Server Limitations”</a> for
detailed description.
- </p></div><div class="section" title="12.1. DHCPv4 Server Usage"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="dhcp4-usage"></a>12.1. DHCPv4 Server Usage</h2></div></div></div><p>BIND10 provides DHCPv4 server component since December
+ </p></div><div class="section" title="12.1. DHCPv4 Server Usage"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="dhcp4-usage"></a>12.1. DHCPv4 Server Usage</h2></div></div></div><p>BIND10 provides the DHCPv4 server component since December
2011. It is a skeleton server and can be described as an early
prototype that is not fully functional yet. It is mature enough
to conduct first tests in lab environment, but it has
significant limitations. See <a class="xref" href="#dhcp4-limit" title="12.4. DHCPv4 Server Limitations">Section 12.4, “DHCPv4 Server Limitations”</a> for
details.
</p><p>
- DHCPv4 server is implemented as <span class="command"><strong>b10-dhcp4</strong></span>
+ The DHCPv4 server is implemented as <span class="command"><strong>b10-dhcp4</strong></span>
daemon. As it is not configurable yet, it is fully autonomous,
- i.e. it does not interact with <span class="command"><strong>b10-cfgmgr</strong></span>.
+ that is it does not interact with <span class="command"><strong>b10-cfgmgr</strong></span>.
To start DHCPv4 server, simply input:
</p><pre class="screen">
@@ -959,16 +1066,16 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)</pre><p>
directory, in /usr/local/bin/b10-dhcp4 or other directory
you specified during compilation.
- After start, server will detect available network interfaces
+ At start, the server will detect available network interfaces
and will attempt to open UDP sockets on all interfaces that
- are up, running, are not loopback and have IPv4 address
+ are up, running, are not loopback, and have IPv4 address
assigned.
- Server will then listen to incoming traffic. Currently
- supported client messages are DISCOVER and REQUEST. Server
+ The server will then listen to incoming traffic. Currently
+ supported client messages are DISCOVER and REQUEST. The server
will respond to them with OFFER and ACK, respectively.
- As DHCPv4 server opens privileged ports, it requires root
+ Since the DHCPv4 server opens privileged ports, it requires root
access. Make sure you run this daemon as root.</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
Integration with <span class="command"><strong>bind10</strong></span> is
planned. Ultimately, <span class="command"><strong>b10-dhcp4</strong></span> will not
@@ -976,12 +1083,12 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)</pre><p>
<span class="command"><strong>bind10</strong></span>. Please be aware of this planned
change.
</p></div></div><div class="section" title="12.2. DHCPv4 Server Configuration"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="dhcp4-config"></a>12.2. DHCPv4 Server Configuration</h2></div></div></div><p>
- DHCPv4 server does not have lease database implemented yet
- or any support for configuration, so every time the same set
+ The DHCPv4 server does not have a lease database implemented yet
+ nor any support for configuration, so every time the same set
of configuration options (including the same fixed address)
will be assigned every time.
</p><p>
- At this stage of development, the only way to alter server
+ At this stage of development, the only way to alter the server
configuration is to tweak its source code. To do so, please
edit src/bin/dhcp4/dhcp4_srv.cc file and modify following
parameters and recompile:
@@ -1000,22 +1107,23 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</pre><p>
REQUEST, and ACK.</li><li class="listitem">RFC2132: Supported options are: PAD (0),
END(255), Message Type(53), DHCP Server Identifier (54),
Domain Name (15), DNS Servers (6), IP Address Lease Time
- (51), Subnet mask (1), and Routers (3).</li></ul></div></div><div class="section" title="12.4. DHCPv4 Server Limitations"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="dhcp4-limit"></a>12.4. DHCPv4 Server Limitations</h2></div></div></div><p> These are the current limitations of DHCPv4 server
+ (51), Subnet mask (1), and Routers (3).</li></ul></div></div><div class="section" title="12.4. DHCPv4 Server Limitations"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="dhcp4-limit"></a>12.4. DHCPv4 Server Limitations</h2></div></div></div><p>These are the current limitations of the DHCPv4 server
software. Most of them are reflections of the early stage of
development and should be treated as <span class="quote">“<span class="quote">not implemented
- yet</span>”</span>, rather than actual limitations.</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem">During initial IPv4 node configuration, server is
- expected to send packets to a node that does not have IPv4
- address assigned yet. Server requires certain tricks (or
- hacks) to transmit such packets. This is not implemented
- yet, therefore DHCPv4 server supports relayed traffic only
- (that is normal point to point communication).</li><li class="listitem"><span class="command"><strong>b10-dhcp4</strong></span> provides a single,
+ yet</span>”</span>, rather than actual limitations.</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem">During initial IPv4 node configuration, the
+ server is expected to send packets to a node that does not
+ have IPv4 address assigned yet. The server requires
+ certain tricks (or hacks) to transmit such packets. This
+ is not implemented yet, therefore DHCPv4 server supports
+ relayed traffic only (that is, normal point to point
+ communication).</li><li class="listitem"><span class="command"><strong>b10-dhcp4</strong></span> provides a single,
fixed, hardcoded lease to any client that asks. There is
no lease manager implemented. If two clients request
addresses, they will both get the same fixed
address.</li><li class="listitem"><span class="command"><strong>b10-dhcp4</strong></span> does not support any
configuration mechanisms yet. The whole configuration is
currently hardcoded. The only way to tweak configuration
- is to directly modify source code. See see <a class="xref" href="#dhcp4-config" title="12.2. DHCPv4 Server Configuration">Section 12.2, “DHCPv4 Server Configuration”</a> for details.</li><li class="listitem">Upon start, server will open sockets on all
+ is to directly modify source code. See see <a class="xref" href="#dhcp4-config" title="12.2. DHCPv4 Server Configuration">Section 12.2, “DHCPv4 Server Configuration”</a> for details.</li><li class="listitem">Upon start, the server will open sockets on all
interfaces that are not loopback, are up and running and
have IPv4 address. Support for multiple interfaces is not
coded in reception routines yet, so if you are running
@@ -1036,33 +1144,33 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</pre><p>
sending ICMP echo request.</li><li class="listitem">Address renewal (RENEW), rebinding (REBIND),
confirmation (CONFIRM), duplication report (DECLINE) and
release (RELEASE) are not supported yet.</li><li class="listitem">DNS Update is not supported yet.</li><li class="listitem">-v (verbose) command line option is currently
- permanently enabled.</li></ul></div></div></div><div class="chapter" title="Chapter 13. DHCPv6 Server"><div class="titlepage"><div><div><h2 class="title"><a name="dhcp6"></a>Chapter 13. DHCPv6 Server</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#dhcp6-usage">13.1. DHCPv6 Server Usage</a></span></dt><dt><span class="section"><a href="#dhcp6-config">13.2. DHCPv6 Server Configuration</a></span></dt><dt><span class="section"><a href="#dhcp6-std">13.3. Supported DHCPv6 Standards</a></span></dt><dt><span class="section"><a href="#dhcp6-limit">13.4. DHCPv6 Server Limitations</a></span></dt></dl></div><p>Dynamic Host Configuration Protocol for IPv6 (DHCPv6) is
+ the default, and cannot be disabled.</li></ul></div></div></div><div class="chapter" title="Chapter 13. DHCPv6 Server"><div class="titlepage"><div><div><h2 class="title"><a name="dhcp6"></a>Chapter 13. DHCPv6 Server</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#dhcp6-usage">13.1. DHCPv6 Server Usage</a></span></dt><dt><span class="section"><a href="#dhcp6-config">13.2. DHCPv6 Server Configuration</a></span></dt><dt><span class="section"><a href="#dhcp6-std">13.3. Supported DHCPv6 Standards</a></span></dt><dt><span class="section"><a href="#dhcp6-limit">13.4. DHCPv6 Server Limitations</a></span></dt></dl></div><p>Dynamic Host Configuration Protocol for IPv6 (DHCPv6) is
specified in RFC3315. BIND10 provides DHCPv6 server implementation
- that is described in this chapter. For DHCPv4 server
- implementation, see <a class="xref" href="#dhcp4" title="Chapter 12. DHCPv4 Server">Chapter 12, <i>DHCPv4 Server</i></a>.
- </p><p>DHCPv6 server component is currently under intense
+ that is described in this chapter. For a description of the DHCPv4
+ server implementation, see <a class="xref" href="#dhcp4" title="Chapter 12. DHCPv4 Server">Chapter 12, <i>DHCPv4 Server</i></a>.
+ </p><p>The DHCPv6 server component is currently under intense
development. You may want to check out <a class="ulink" href="http://bind10.isc.org/wiki/Kea" target="_top">BIND10 DHCP (Kea) wiki</a>
and recent posts on <a class="ulink" href="https://lists.isc.org/mailman/listinfo/bind10-dev" target="_top">BIND10
- developers mailing list</a>.</p><p>DHCPv4 and DHCPv6 components in BIND10 architecture are
+ developers mailing list</a>.</p><p>The DHCPv4 and DHCPv6 components in BIND10 architecture are
internally code named <span class="quote">“<span class="quote">Kea</span>”</span>.</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
As of December 2011, both DHCPv4 and DHCPv6 components are
skeleton servers. That means that while they are capable of
performing DHCP configuration, they are not fully functional
- yet. In particular, both do not have functional lease
+ yet. In particular, neither has functional lease
databases. This means that they will assign the same, fixed,
hardcoded addresses to any client that will ask. See <a class="xref" href="#dhcp4-limit" title="12.4. DHCPv4 Server Limitations">Section 12.4, “DHCPv4 Server Limitations”</a> and <a class="xref" href="#dhcp6-limit" title="13.4. DHCPv6 Server Limitations">Section 13.4, “DHCPv6 Server Limitations”</a> for
detailed description.
</p></div><div class="section" title="13.1. DHCPv6 Server Usage"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="dhcp6-usage"></a>13.1. DHCPv6 Server Usage</h2></div></div></div><p>
- BIND10 provides DHCPv6 server component since September
+ BIND10 provides the DHCPv6 server component since September
2011. It is a skeleton server and can be described as an early
prototype that is not fully functional yet. It is mature
enough to conduct first tests in lab environment, but it has
significant limitations. See <a class="xref" href="#dhcp6-limit" title="13.4. DHCPv6 Server Limitations">Section 13.4, “DHCPv6 Server Limitations”</a> for
details.
</p><p>
- DHCPv6 server is implemented as <span class="command"><strong>b10-dhcp6</strong></span>
+ The DHCPv6 server is implemented as <span class="command"><strong>b10-dhcp6</strong></span>
daemon. As it is not configurable yet, it is fully autonomous,
- i.e. it does not interact with <span class="command"><strong>b10-cfgmgr</strong></span>.
+ that is it does not interact with <span class="command"><strong>b10-cfgmgr</strong></span>.
To start DHCPv6 server, simply input:
</p><pre class="screen">
@@ -1075,16 +1183,16 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</pre><p>
directory, in /usr/local/bin/b10-dhcp6 or other directory
you specified during compilation.
- After start, server will detect available network interfaces
+ At start, server will detect available network interfaces
and will attempt to open UDP sockets on all interfaces that
- are up, running, are not loopback, are multicast-capable and
+ are up, running, are not loopback, are multicast-capable, and
have IPv6 address assigned.
- Server will then listen to incoming traffic. Currently
- supported client messages are SOLICIT and REQUEST. Server
+ The server will then listen to incoming traffic. Currently
+ supported client messages are SOLICIT and REQUEST. The server
will respond to them with ADVERTISE and REPLY, respectively.
- As DHCPv6 server opens privileged ports, it requires root
+ Since the DHCPv6 server opens privileged ports, it requires root
access. Make sure you run this daemon as root.
</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
Integration with <span class="command"><strong>bind10</strong></span> is
@@ -1093,7 +1201,7 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</pre><p>
<span class="command"><strong>bind10</strong></span>. Please be aware of this planned
change.
</p></div></div><div class="section" title="13.2. DHCPv6 Server Configuration"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="dhcp6-config"></a>13.2. DHCPv6 Server Configuration</h2></div></div></div><p>
- DHCPv4 server does not have lease database implemented yet
+ The DHCPv6 server does not have lease database implemented yet
or any support for configuration, so every time the same set
of configuration options (including the same fixed address)
will be assigned every time.
@@ -1114,7 +1222,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";</pre><p>
</p></div><div class="section" title="13.3. Supported DHCPv6 Standards"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="dhcp6-std"></a>13.3. Supported DHCPv6 Standards</h2></div></div></div><p>The following standards and draft standards are currently
supported:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem">RFC3315: Supported messages are SOLICIT,
ADVERTISE, REQUEST, and REPLY. Supported options are
- SERVER_ID, CLIENT_ID, IA_NA, and IAADDRESS.</li><li class="listitem">RFC3646: Supported option is DNS_SERVERS.</li></ul></div></div><div class="section" title="13.4. DHCPv6 Server Limitations"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="dhcp6-limit"></a>13.4. DHCPv6 Server Limitations</h2></div></div></div><p> These are the current limitations of DHCPv6 server
+ SERVER_ID, CLIENT_ID, IA_NA, and IAADDRESS.</li><li class="listitem">RFC3646: Supported option is DNS_SERVERS.</li></ul></div></div><div class="section" title="13.4. DHCPv6 Server Limitations"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="dhcp6-limit"></a>13.4. DHCPv6 Server Limitations</h2></div></div></div><p> These are the current limitations of the DHCPv6 server
software. Most of them are reflections of the early stage of
development and should be treated as <span class="quote">“<span class="quote">not implemented
yet</span>”</span>, rather than actual limitations.</p><p>
@@ -1124,7 +1232,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";</pre><p>
they will both get the same fixed address.</li><li class="listitem"><span class="command"><strong>b10-dhcp6</strong></span> does not support any
configuration mechanisms yet. The whole configuration is
currently hardcoded. The only way to tweak configuration
- is to directly modify source code. See see <a class="xref" href="#dhcp6-config" title="13.2. DHCPv6 Server Configuration">Section 13.2, “DHCPv6 Server Configuration”</a> for details.</li><li class="listitem">Upon start, server will open sockets on all
+ is to directly modify source code. See see <a class="xref" href="#dhcp6-config" title="13.2. DHCPv6 Server Configuration">Section 13.2, “DHCPv6 Server Configuration”</a> for details.</li><li class="listitem">Upon start, the server will open sockets on all
interfaces that are not loopback, are up, running and are
multicast capable and have IPv6 address. Support for
multiple interfaces is not coded in reception routines yet,
@@ -1137,8 +1245,8 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";</pre><p>
assigns DNS SERVER option.</li><li class="listitem">Temporary addresses are not supported yet.</li><li class="listitem">Prefix delegation is not supported yet.</li><li class="listitem">Address renewal (RENEW), rebinding (REBIND),
confirmation (CONFIRM), duplication report (DECLINE) and
release (RELEASE) are not supported yet.</li><li class="listitem">DNS Update is not supported yet.</li><li class="listitem">Interface detection is currently working on Linux
- only. See <a class="xref" href="#iface-detect" title="14.1. Interface detection">Section 14.1, “Interface detection”</a> for details.</li><li class="listitem">-v (verbose) command line option is currently permanently
- enabled.</li></ul></div><p>
+ only. See <a class="xref" href="#iface-detect" title="14.1. Interface detection">Section 14.1, “Interface detection”</a> for details.</li><li class="listitem">-v (verbose) command line option is currently the
+ default, and cannot be disabled.</li></ul></div><p>
</p></div></div><div class="chapter" title="Chapter 14. libdhcp++ library"><div class="titlepage"><div><div><h2 class="title"><a name="libdhcp"></a>Chapter 14. libdhcp++ library</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#iface-detect">14.1. Interface detection</a></span></dt><dt><span class="section"><a href="#packet-handling">14.2. DHCPv4/DHCPv6 packet handling</a></span></dt></dl></div><p>libdhcp++ is a common library written in C++ that handles
many DHCP-related tasks, like DHCPv4 and DHCPv6 packets parsing,
manipulation and assembly, option parsing, manipulation and
@@ -1152,15 +1260,15 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";</pre><p>
any kind of DHCP-related software.
</p><div class="section" title="14.1. Interface detection"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="iface-detect"></a>14.1. Interface detection</h2></div></div></div><p>Both DHCPv4 and DHCPv6 components share network
interface detection routines. Interface detection is
- currently only supported on Linux systems.</p><p>For non-linux systems, there is currently stub
+ currently only supported on Linux systems.</p><p>For non-Linux systems, there is currently stub
implementation provided. As DHCP servers need to know available
addresses, there is a simple mechanism implemented to provide
that information. User is expected to create interfaces.txt
file. Format of this file is simple. It contains list of
interfaces along with available address on each interface. This
mechanism is temporary and is going to be removed as soon as
- interface detection becomes available on non-linux
- systems. Example of interfaces.txt file looks as follows:
+ interface detection becomes available on non-Linux
+ systems. Here is an example of the interfaces.txt file:
</p><pre class="screen">
# For DHCPv6, please specify link-local address (starts with fe80::)
# If in doubt, check output of 'ifconfig -a' command.
@@ -1187,6 +1295,10 @@ eth0 fe80::21e:8cff:fe9b:7349
> <strong class="userinput"><code>Stats show</code></strong>
{
"Auth": {
+ "opcode.iquery": 0,
+ "opcode.notify": 10,
+ "opcode.query": 869617,
+ ...
"queries.tcp": 1749,
"queries.udp": 867868
},
@@ -1202,7 +1314,7 @@ eth0 fe80::21e:8cff:fe9b:7349
}
}
</pre><p>
- </p></div><div class="chapter" title="Chapter 16. Logging"><div class="titlepage"><div><div><h2 class="title"><a name="logging"></a>Chapter 16. Logging</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id461660">16.1. Logging configuration</a></span></dt><dd><dl><dt><span class="section"><a href="#id461675">16.1.1. Loggers</a></span></dt><dt><span class="section"><a href="#id461978">16.1.2. Output Options</a></span></dt><dt><span class="section"><a href="#id462172">16.1.3. Example session</a></span></dt></dl></dd><dt><span class="section"><a href="#id462428">16.2. Logging Message Format</a></span></dt></dl></div><div class="section" title="16.1. Logging configuration"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id461660"></a>16.1. Logging configuration</h2></div></div></div><p>
+ </p></div><div class="chapter" title="Chapter 16. Logging"><div class="titlepage"><div><div><h2 class="title"><a name="logging"></a>Chapter 16. Logging</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#idp295312">16.1. Logging configuration</a></span></dt><dd><dl><dt><span class="section"><a href="#idp296304">16.1.1. Loggers</a></span></dt><dt><span class="section"><a href="#idp317512">16.1.2. Output Options</a></span></dt><dt><span class="section"><a href="#idp331704">16.1.3. Example session</a></span></dt></dl></dd><dt><span class="section"><a href="#idp349208">16.2. Logging Message Format</a></span></dt></dl></div><div class="section" title="16.1. Logging configuration"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="idp295312"></a>16.1. Logging configuration</h2></div></div></div><p>
The logging system in BIND 10 is configured through the
Logging module. All BIND 10 modules will look at the
@@ -1211,7 +1323,7 @@ eth0 fe80::21e:8cff:fe9b:7349
- </p><div class="section" title="16.1.1. Loggers"><div class="titlepage"><div><div><h3 class="title"><a name="id461675"></a>16.1.1. Loggers</h3></div></div></div><p>
+ </p><div class="section" title="16.1.1. Loggers"><div class="titlepage"><div><div><h3 class="title"><a name="idp296304"></a>16.1.1. Loggers</h3></div></div></div><p>
Within BIND 10, a message is logged through a component
called a "logger". Different parts of BIND 10 log messages
@@ -1222,7 +1334,7 @@ eth0 fe80::21e:8cff:fe9b:7349
In the Logging module, you can specify the configuration
for zero or more loggers; any that are not specified will
- take appropriate default values..
+ take appropriate default values.
</p><p>
@@ -1232,7 +1344,7 @@ eth0 fe80::21e:8cff:fe9b:7349
(what to log), and the <code class="option">output_options</code>
(where to log).
- </p><div class="section" title="16.1.1.1. name (string)"><div class="titlepage"><div><div><h4 class="title"><a name="id461706"></a>16.1.1.1. name (string)</h4></div></div></div><p>
+ </p><div class="section" title="16.1.1.1. name (string)"><div class="titlepage"><div><div><h4 class="title"><a name="idp298552"></a>16.1.1.1. name (string)</h4></div></div></div><p>
Each logger in the system has a name, the name being that
of the component using it to log messages. For instance,
if you want to configure logging for the resolver module,
@@ -1305,7 +1417,7 @@ eth0 fe80::21e:8cff:fe9b:7349
<span class="quote">“<span class="quote">Auth.cache</span>”</span> logger will appear in the output
with a logger name of <span class="quote">“<span class="quote">b10-auth.cache</span>”</span>).
- </p></div><div class="section" title="16.1.1.2. severity (string)"><div class="titlepage"><div><div><h4 class="title"><a name="id461840"></a>16.1.1.2. severity (string)</h4></div></div></div><p>
+ </p></div><div class="section" title="16.1.1.2. severity (string)"><div class="titlepage"><div><div><h4 class="title"><a name="idp308336"></a>16.1.1.2. severity (string)</h4></div></div></div><p>
This specifies the category of messages logged.
Each message is logged with an associated severity which
@@ -1321,7 +1433,7 @@ eth0 fe80::21e:8cff:fe9b:7349
- </p></div><div class="section" title="16.1.1.3. output_options (list)"><div class="titlepage"><div><div><h4 class="title"><a name="id461898"></a>16.1.1.3. output_options (list)</h4></div></div></div><p>
+ </p></div><div class="section" title="16.1.1.3. output_options (list)"><div class="titlepage"><div><div><h4 class="title"><a name="idp312128"></a>16.1.1.3. output_options (list)</h4></div></div></div><p>
Each logger can have zero or more
<code class="option">output_options</code>. These specify where log
@@ -1331,7 +1443,7 @@ eth0 fe80::21e:8cff:fe9b:7349
The other options for a logger are:
- </p></div><div class="section" title="16.1.1.4. debuglevel (integer)"><div class="titlepage"><div><div><h4 class="title"><a name="id461917"></a>16.1.1.4. debuglevel (integer)</h4></div></div></div><p>
+ </p></div><div class="section" title="16.1.1.4. debuglevel (integer)"><div class="titlepage"><div><div><h4 class="title"><a name="idp313352"></a>16.1.1.4. debuglevel (integer)</h4></div></div></div><p>
When a logger's severity is set to DEBUG, this value
specifies what debug messages should be printed. It ranges
@@ -1340,7 +1452,7 @@ eth0 fe80::21e:8cff:fe9b:7349
If severity for the logger is not DEBUG, this value is ignored.
- </p></div><div class="section" title="16.1.1.5. additive (true or false)"><div class="titlepage"><div><div><h4 class="title"><a name="id461937"></a>16.1.1.5. additive (true or false)</h4></div></div></div><p>
+ </p></div><div class="section" title="16.1.1.5. additive (true or false)"><div class="titlepage"><div><div><h4 class="title"><a name="idp314792"></a>16.1.1.5. additive (true or false)</h4></div></div></div><p>
If this is true, the <code class="option">output_options</code> from
the parent will be used. For example, if there are two
@@ -1354,45 +1466,53 @@ eth0 fe80::21e:8cff:fe9b:7349
- </p></div></div><div class="section" title="16.1.2. Output Options"><div class="titlepage"><div><div><h3 class="title"><a name="id461978"></a>16.1.2. Output Options</h3></div></div></div><p>
+ </p></div></div><div class="section" title="16.1.2. Output Options"><div class="titlepage"><div><div><h3 class="title"><a name="idp317512"></a>16.1.2. Output Options</h3></div></div></div><p>
The main settings for an output option are the
<code class="option">destination</code> and a value called
<code class="option">output</code>, the meaning of which depends on
the destination that is set.
- </p><div class="section" title="16.1.2.1. destination (string)"><div class="titlepage"><div><div><h4 class="title"><a name="id461995"></a>16.1.2.1. destination (string)</h4></div></div></div><p>
+ </p><div class="section" title="16.1.2.1. destination (string)"><div class="titlepage"><div><div><h4 class="title"><a name="idp318648"></a>16.1.2.1. destination (string)</h4></div></div></div><p>
The destination is the type of output. It can be one of:
- </p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"> console </li><li class="listitem"> file </li><li class="listitem"> syslog </li></ul></div></div><div class="section" title="16.1.2.2. output (string)"><div class="titlepage"><div><div><h4 class="title"><a name="id462029"></a>16.1.2.2. output (string)</h4></div></div></div><p>
+ </p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"> console </li><li class="listitem"> file </li><li class="listitem"> syslog </li></ul></div></div><div class="section" title="16.1.2.2. output (string)"><div class="titlepage"><div><div><h4 class="title"><a name="idp320736"></a>16.1.2.2. output (string)</h4></div></div></div><p>
Depending on what is set as the output destination, this
value is interpreted as follows:
- </p><div class="variablelist"><dl><dt><span class="term"><code class="option">destination</code> is <span class="quote">“<span class="quote">console</span>”</span></span></dt><dd>
+ </p><div class="variablelist"><dl><dt><span class="term"><code class="option">destination</code> is <span class="quote">“<span class="quote">console</span>”</span></span></dt><dd><p>
The value of output must be one of <span class="quote">“<span class="quote">stdout</span>”</span>
(messages printed to standard output) or
<span class="quote">“<span class="quote">stderr</span>”</span> (messages printed to standard
error).
- </dd><dt><span class="term"><code class="option">destination</code> is <span class="quote">“<span class="quote">file</span>”</span></span></dt><dd>
+ </p><p>
+ Note: if output is set to <span class="quote">“<span class="quote">stderr</span>”</span> and a lot of
+ messages are produced in a short time (e.g. if the logging
+ level is set to DEBUG), you may occasionally see some messages
+ jumbled up together. This is due to a combination of the way
+ that messages are written to the screen and the unbuffered
+ nature of the standard error stream. If this occurs, it is
+ recommended that output be set to <span class="quote">“<span class="quote">stdout</span>”</span>.
+ </p></dd><dt><span class="term"><code class="option">destination</code> is <span class="quote">“<span class="quote">file</span>”</span></span></dt><dd><p>
The value of output is interpreted as a file name;
log messages will be appended to this file.
- </dd><dt><span class="term"><code class="option">destination</code> is <span class="quote">“<span class="quote">syslog</span>”</span></span></dt><dd>
+ </p></dd><dt><span class="term"><code class="option">destination</code> is <span class="quote">“<span class="quote">syslog</span>”</span></span></dt><dd><p>
The value of output is interpreted as the
<span class="command"><strong>syslog</strong></span> facility (e.g.
<span class="emphasis"><em>local0</em></span>) that should be used
for log messages.
- </dd></dl></div><p>
+ </p></dd></dl></div><p>
The other options for <code class="option">output_options</code> are:
- </p><div class="section" title="16.1.2.2.1. flush (true of false)"><div class="titlepage"><div><div><h5 class="title"><a name="id462122"></a>16.1.2.2.1. flush (true of false)</h5></div></div></div><p>
+ </p><div class="section" title="16.1.2.2.1. flush (true of false)"><div class="titlepage"><div><div><h5 class="title"><a name="idp328280"></a>16.1.2.2.1. flush (true of false)</h5></div></div></div><p>
Flush buffers after each log message. Doing this will
reduce performance but will ensure that if the program
terminates abnormally, all messages up to the point of
termination are output.
- </p></div><div class="section" title="16.1.2.2.2. maxsize (integer)"><div class="titlepage"><div><div><h5 class="title"><a name="id462134"></a>16.1.2.2.2. maxsize (integer)</h5></div></div></div><p>
+ </p></div><div class="section" title="16.1.2.2.2. maxsize (integer)"><div class="titlepage"><div><div><h5 class="title"><a name="idp329144"></a>16.1.2.2.2. maxsize (integer)</h5></div></div></div><p>
Only relevant when destination is file, this is maximum
file size of output files in bytes. When the maximum
size is reached, the file is renamed and a new file opened.
@@ -1401,11 +1521,11 @@ eth0 fe80::21e:8cff:fe9b:7349
etc.)
</p><p>
If this is 0, no maximum file size is used.
- </p></div><div class="section" title="16.1.2.2.3. maxver (integer)"><div class="titlepage"><div><div><h5 class="title"><a name="id462151"></a>16.1.2.2.3. maxver (integer)</h5></div></div></div><p>
+ </p></div><div class="section" title="16.1.2.2.3. maxver (integer)"><div class="titlepage"><div><div><h5 class="title"><a name="idp330392"></a>16.1.2.2.3. maxver (integer)</h5></div></div></div><p>
Maximum number of old log files to keep around when
rolling the output file. Only relevant when
<code class="option">destination</code> is <span class="quote">“<span class="quote">file</span>”</span>.
- </p></div></div></div><div class="section" title="16.1.3. Example session"><div class="titlepage"><div><div><h3 class="title"><a name="id462172"></a>16.1.3. Example session</h3></div></div></div><p>
+ </p></div></div></div><div class="section" title="16.1.3. Example session"><div class="titlepage"><div><div><h3 class="title"><a name="idp331704"></a>16.1.3. Example session</h3></div></div></div><p>
In this example we want to set the global logging to
write to the file <code class="filename">/var/log/my_bind10.log</code>,
@@ -1499,7 +1619,7 @@ Logging/loggers[0]/output_options[0]/maxver 0 integer (default)
</p><pre class="screen">> <strong class="userinput"><code> config set Logging/loggers[0]/output_options[0]/destination file</code></strong>
> <strong class="userinput"><code> config set Logging/loggers[0]/output_options[0]/output /var/log/bind10.log</code></strong>
-> <strong class="userinput"><code> config set Logging/loggers[0]/output_options[0]/maxsize 30000</code></strong>
+> <strong class="userinput"><code> config set Logging/loggers[0]/output_options[0]/maxsize 204800</code></strong>
> <strong class="userinput"><code> config set Logging/loggers[0]/output_options[0]/maxver 8</code></strong>
</pre><p>
@@ -1518,7 +1638,7 @@ Logging/loggers[0]/additive false boolean (default)
Logging/loggers[0]/output_options[0]/destination "file" string (modified)
Logging/loggers[0]/output_options[0]/output "/var/log/bind10.log" string (modified)
Logging/loggers[0]/output_options[0]/flush false boolean (default)
-Logging/loggers[0]/output_options[0]/maxsize 30000 integer (modified)
+Logging/loggers[0]/output_options[0]/maxsize 204800 integer (modified)
Logging/loggers[0]/output_options[0]/maxver 8 integer (modified)
</pre><p>
@@ -1566,7 +1686,7 @@ Logging/loggers[0]/output_options[0]/maxver 8 integer (modified)
And every module will now be using the values from the
logger named <span class="quote">“<span class="quote">*</span>”</span>.
- </p></div></div><div class="section" title="16.2. Logging Message Format"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id462428"></a>16.2. Logging Message Format</h2></div></div></div><p>
+ </p></div></div><div class="section" title="16.2. Logging Message Format"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="idp349208"></a>16.2. Logging Message Format</h2></div></div></div><p>
Each message written by BIND 10 to the configured logging
destinations comprises a number of components that identify
the origin of the message and, if the message indicates
@@ -1612,5 +1732,4 @@ Logging/loggers[0]/output_options[0]/maxver 8 integer (modified)
consulting your system's documentation to identify
what error number 111 means.
</p></dd></dl></div><p>
- </p></div></div><div class="chapter" title="Chapter 17. Acknowledgements"><div class="titlepage"><div><div><h2 class="title"><a name="id462551"></a>Chapter 17. Acknowledgements</h2></div></div></div><p>ISC would like to acknowledge generous support for
- development of DHCPv4 and DHCPv6 components provided by <a class="ulink" href="http://www.comcast.com" target="_top">Comcast</a>.</p></div></div></body></html>
+ </p></div></div></div></body></html>
diff --git a/doc/guide/bind10-guide.txt b/doc/guide/bind10-guide.txt
index 9c8ffbe..cf81af6 100644
--- a/doc/guide/bind10-guide.txt
+++ b/doc/guide/bind10-guide.txt
@@ -2,17 +2,19 @@
Administrator Reference for BIND 10
- This is the reference guide for BIND 10 version 20111021.
+ This is the reference guide for BIND 10 version 20120405.
- Copyright (c) 2010-2011 Internet Systems Consortium, Inc.
+ Copyright © 2010-2012 Internet Systems Consortium, Inc.
Abstract
- 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.
+ BIND 10 is a framework that features Domain Name System (DNS) suite and
+ Dynamic Host Configuration Protocol (DHCP) servers managed by Internet
+ Systems Consortium (ISC). It includes DNS libraries, modular components
+ for controlling authoritative and recursive DNS servers, and experimental
+ DHCPv4 and DHCPv6 servers.
- This is the reference guide for BIND 10 version 20111021. The most
+ This is the reference guide for BIND 10 version 20120405. The most
up-to-date version of this document (in PDF, HTML, and plain text
formats), along with other documents for BIND 10, can be found at
http://bind10.isc.org/docs.
@@ -21,41 +23,45 @@ Administrator Reference for BIND 10
Table of Contents
+ Preface
+
+ 1. Acknowledgements
+
1. Introduction
- Supported Platforms
+ 1.1. Supported Platforms
- Required Software
+ 1.2. Required Software
- Starting and Stopping the Server
+ 1.3. Starting and Stopping the Server
- Managing BIND 10
+ 1.4. Managing BIND 10
2. Installation
- Building Requirements
+ 2.1. Building Requirements
- Quick start
+ 2.2. Quick start
- Installation from source
+ 2.3. Installation from source
- Download Tar File
+ 2.3.1. Download Tar File
- Retrieve from Git
+ 2.3.2. Retrieve from Git
- Configure before the build
+ 2.3.3. Configure before the build
- Build
+ 2.3.4. Build
- Install
+ 2.3.5. Install
- Install Hierarchy
+ 2.3.6. Install Hierarchy
3. Starting BIND10 with bind10
- Starting BIND 10
+ 3.1. Starting BIND 10
- Configuration of started processes
+ 3.2. Configuration of started processes
4. Command channel
@@ -63,101 +69,140 @@ Administrator Reference for BIND 10
6. Remote control daemon
- Configuration specification for b10-cmdctl
+ 6.1. Configuration specification for b10-cmdctl
7. Control and configure user interface
8. Authoritative Server
- Server Configurations
+ 8.1. Server Configurations
+
+ 8.2. Data Source Backends
- Data Source Backends
+ 8.2.1. In-memory Data Source
- Loading Master Zones Files
+ 8.3. Loading Master Zones Files
9. Incoming Zone Transfers
- Configuration for Incoming Zone Transfers
+ 9.1. Configuration for Incoming Zone Transfers
- Enabling IXFR
+ 9.2. Enabling IXFR
- Trigger an Incoming Zone Transfer Manually
+ 9.3. Secondary Manager
+
+ 9.4. Trigger an Incoming Zone Transfer Manually
10. Outbound Zone Transfers
- 11. Secondary Manager
+ 11. Recursive Name Server
+
+ 11.1. Access Control
+
+ 11.2. Forwarding
+
+ 12. DHCPv4 Server
+
+ 12.1. DHCPv4 Server Usage
+
+ 12.2. DHCPv4 Server Configuration
- 12. Recursive Name Server
+ 12.3. Supported standards
- Access Control
+ 12.4. DHCPv4 Server Limitations
- Forwarding
+ 13. DHCPv6 Server
- 13. Statistics
+ 13.1. DHCPv6 Server Usage
- 14. Logging
+ 13.2. DHCPv6 Server Configuration
- Logging configuration
+ 13.3. Supported DHCPv6 Standards
- Loggers
+ 13.4. DHCPv6 Server Limitations
- Output Options
+ 14. libdhcp++ library
- Example session
+ 14.1. Interface detection
- Logging Message Format
+ 14.2. DHCPv4/DHCPv6 packet handling
+
+ 15. Statistics
+
+ 16. Logging
+
+ 16.1. Logging configuration
+
+ 16.1.1. Loggers
+
+ 16.1.2. Output Options
+
+ 16.1.3. Example session
+
+ 16.2. Logging Message Format
List of Tables
3.1.
-Chapter 1. Introduction
+Preface
+
+ Table of Contents
+
+ 1. Acknowledgements
+
+1. Acknowledgements
+
+ ISC would like to acknowledge generous support for BIND 10 development of
+ DHCPv4 and DHCPv6 components provided by Comcast.
+
+Chapter 1. Introduction
Table of Contents
- Supported Platforms
+ 1.1. Supported Platforms
- Required Software
+ 1.2. Required Software
- Starting and Stopping the Server
+ 1.3. Starting and Stopping the Server
- Managing BIND 10
+ 1.4. Managing BIND 10
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.
+ DNS. BIND 10 provides a EDNS0- and DNSSEC-capable authoritative DNS server
+ and a caching recursive name server which also provides forwarding.
- Note
+ This guide covers the experimental prototype of BIND 10 version 20120405.
- This guide covers the experimental prototype of BIND 10 version 20111021.
+1.1. Supported Platforms
- Note
-
- BIND 10 provides a EDNS0- and DNSSEC-capable authoritative DNS server and
- a caching recursive name server which also provides forwarding.
+ BIND 10 builds have been tested on Debian GNU/Linux 5 and unstable, Ubuntu
+ 9.10, NetBSD 5, Solaris 10, FreeBSD 7 and 8, CentOS Linux 5.3, and MacOS
+ 10.6. It has been tested on Sparc, i386, and amd64 hardware platforms. It
+ is planned for BIND 10 to build, install and run on Windows and standard
+ Unix-type platforms.
-Supported Platforms
+1.2. Required Software
- BIND 10 builds have been tested on Debian GNU/Linux 5, 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 platforms. It is planned for BIND 10 to
- build, install and run on Windows and standard Unix-type platforms.
+ BIND 10 requires at least Python 3.1 (http://www.python.org/). It has also
+ been tested with Python 3.2.
-Required Software
+ BIND 10 uses the Botan crypto library for C++
+ (http://botan.randombit.net/). It requires at least Botan version 1.8.
- BIND 10 requires Python 3.1. Later versions may work, but Python 3.1 is
- the minimum version which will work.
+ BIND 10 uses the log4cplus C++ logging library
+ (http://log4cplus.sourceforge.net/). It requires at least log4cplus
+ version 1.0.3.
- BIND 10 uses the Botan crypto library for C++. It requires at least Botan
- version 1.8.
+ The authoritative DNS server uses SQLite3 (http://www.sqlite.org/). It
+ needs at least SQLite version 3.3.9.
- BIND 10 uses the log4cplus C++ logging library. It requires at least
- log4cplus version 1.0.3.
-
- The authoritative server requires SQLite 3.3.9 or newer. The b10-xfrin,
- b10-xfrout, and b10-zonemgr modules require the libpython3 library and the
- Python _sqlite3.so module.
+ The b10-xfrin, b10-xfrout, and b10-zonemgr components require the
+ libpython3 library and the Python _sqlite3.so module (which is included
+ with Python). The Python module needs to be built for the corresponding
+ Python 3.
Note
@@ -165,7 +210,7 @@ Required Software
installation nor standard packages collections. You may need to install
them separately.
-Starting and Stopping the Server
+1.3. Starting and Stopping the Server
BIND 10 is modular. Part of this modularity is accomplished using multiple
cooperating processes which, together, provide the server functionality.
@@ -178,39 +223,43 @@ Starting and Stopping the Server
processes as needed. The processes started by the bind10 command have
names starting with "b10-", including:
- o b10-msgq -- Message bus daemon. This process coordinates communication
- between all of the other BIND 10 processes.
- o b10-auth -- Authoritative DNS server. This process serves DNS
- requests.
- o b10-cfgmgr -- Configuration manager. This process maintains all of the
+ o b10-auth â Authoritative DNS server. This process serves DNS requests.
+ o b10-cfgmgr â Configuration manager. This process maintains all of the
configuration for BIND 10.
- o b10-cmdctl -- Command and control service. This process allows
- external control of the BIND 10 system.
- o b10-resolver -- Recursive name server. This process handles incoming
+ o b10-cmdctl â Command and control service. This process allows external
+ control of the BIND 10 system.
+ o b10-msgq â Message bus daemon. This process coordinates communication
+ between all of the other BIND 10 processes.
+ o b10-resolver â Recursive name server. This process handles incoming
queries.
- o b10-stats -- Statistics collection daemon. This process collects and
+ o b10-sockcreator â Socket creator daemon. This process creates sockets
+ used by network-listening BIND 10 processes.
+ o b10-stats â Statistics collection daemon. This process collects and
reports statistics data.
- o b10-xfrin -- Incoming zone transfer service. This process is used to
+ o b10-stats-httpd â HTTP server for statistics reporting. This process
+ reports statistics data in XML format over HTTP.
+ o b10-xfrin â Incoming zone transfer service. This process is used to
transfer a new copy of a zone into BIND 10, when acting as a secondary
server.
- o b10-xfrout -- Outgoing zone transfer service. This process is used to
+ o b10-xfrout â Outgoing zone transfer service. This process is used to
handle transfer requests to send a local zone to a remote secondary
server, when acting as a master server.
- o b10-zonemgr -- Secondary manager. This process keeps track of timers
+ o b10-zonemgr â Secondary manager. This process keeps track of timers
and other necessary information for BIND 10 to act as a slave server.
These are ran automatically by bind10 and do not need to be run manually.
-Managing BIND 10
+1.4. Managing BIND 10
Once BIND 10 is running, a few commands are used to interact directly with
the system:
- o bindctl -- interactive administration interface. This is a
- command-line tool which allows an administrator to control BIND 10.
- o b10-loadzone -- zone file loader. This tool will load standard
+ o bindctl â interactive administration interface. This is a low-level
+ command-line tool which allows a developer or an experienced
+ administrator to control BIND 10.
+ o b10-loadzone â zone file loader. This tool will load standard
masterfile-format zone files into BIND 10.
- o b10-cmdctl-usermgr -- user access control. This tool allows an
+ o b10-cmdctl-usermgr â user access control. This tool allows an
administrator to authorize additional users to manage BIND 10.
The tools and modules are covered in full detail in this guide. In
@@ -220,29 +269,29 @@ Managing BIND 10
Python for the message bus, configuration backend, and, of course, DNS.
These include detailed developer documentation and code examples.
-Chapter 2. Installation
+Chapter 2. Installation
Table of Contents
- Building Requirements
+ 2.1. Building Requirements
- Quick start
+ 2.2. Quick start
- Installation from source
+ 2.3. Installation from source
- Download Tar File
+ 2.3.1. Download Tar File
- Retrieve from Git
+ 2.3.2. Retrieve from Git
- Configure before the build
+ 2.3.3. Configure before the build
- Build
+ 2.3.4. Build
- Install
+ 2.3.5. Install
- Install Hierarchy
+ 2.3.6. Install Hierarchy
-Building Requirements
+2.1. Building Requirements
In addition to the run-time requirements, building BIND 10 from source
code requires various development include headers.
@@ -254,25 +303,21 @@ Building Requirements
development package versions, which include header files and libraries, to
build BIND 10 from source code.
- Building from source code requires the Boost build-time headers. At least
- Boost version 1.35 is required.
+ Building from source code requires the Boost build-time headers
+ (http://www.boost.org/). At least Boost version 1.35 is required.
To build BIND 10, also install the Botan (at least version 1.8) and the
log4cplus (at least version 1.0.3) development include headers.
- The Python Library and Python _sqlite3 module are required to enable the
- Xfrout and Xfrin support.
-
- Note
-
- The Python related libraries and modules need to be built for Python 3.1.
-
Building BIND 10 also requires a C++ compiler and standard development
headers, make, and pkg-config. BIND 10 builds have been tested with GCC
g++ 3.4.3, 4.1.2, 4.1.3, 4.2.1, 4.3.2, and 4.4.1; Clang++ 2.8; and Sun C++
5.10.
-Quick start
+ Visit the wiki at http://bind10.isc.org/wiki/SystemSpecificNotes for
+ system-specific installation tips.
+
+2.2. Quick start
Note
@@ -283,48 +328,48 @@ Quick start
To quickly get started with BIND 10, follow these steps.
- 1. Install required build dependencies.
- 2. Download the BIND 10 source tar file from
+  1. Install required run-time and build dependencies.
+  2. Download the BIND 10 source tar file from
ftp://ftp.isc.org/isc/bind10/.
- 3. Extract the tar file:
+  3. Extract the tar file:
$ gzcat bind10-VERSION.tar.gz | tar -xvf -
- 4. Go into the source and run configure:
+  4. Go into the source and run configure:
$ cd bind10-VERSION
$ ./configure
- 5. Build it:
+  5. Build it:
$ make
- 6. Install it (to default /usr/local):
+  6. Install it (to default /usr/local):
$ make install
- 7. Start the server:
+  7. Start the server:
$ /usr/local/sbin/bind10
- 8. Test it; for example:
+  8. Test it; for example:
$ dig @127.0.0.1 -c CH -t TXT authors.bind
- 9. Load desired zone file(s), for example:
+  9. Load desired zone file(s), for example:
$ b10-loadzone your.zone.example.org
- 10. Test the new zone.
+ 10. Test the new zone.
-Installation from source
+2.3. Installation from source
BIND 10 is open source software written in C++ and Python. It is freely
available in source code form from ISC via 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.
- Download Tar File
+ 2.3.1. Download Tar File
Downloading a release tar file is the recommended method to obtain the
source code.
@@ -333,7 +378,7 @@ Installation from source
ftp://ftp.isc.org/isc/bind10/. Periodic development snapshots may also be
available.
- Retrieve from Git
+ 2.3.2. Retrieve from Git
Downloading this "bleeding edge" code is recommended only for developers
or advanced users. Using development code in a production environment is
@@ -348,11 +393,12 @@ Installation from source
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 Git and all the BIND 10 development is public.
- The leading development is done in the "master".
+ The leading development is done in the âmasterâ.
- The code can be checked out from git://bind10.isc.org/bind10; for example:
+ The code can be checked out from git://git.bind10.isc.org/bind10; for
+ example:
- $ git clone git://bind10.isc.org/bind10
+ $ git clone git://git.bind10.isc.org/bind10
When checking out the code from the code version control system, it
doesn't include the generated configure script, Makefile.in files, nor the
@@ -360,7 +406,7 @@ Installation from source
the --install switch. This will run autoconf, aclocal, libtoolize,
autoheader, automake, and related commands.
- Configure before the build
+ 2.3.3. Configure before the build
BIND 10 uses the GNU Build System to discover build environment details.
To generate the makefiles using the defaults, simply run:
@@ -395,14 +441,14 @@ Installation from source
If the configure fails, it may be due to missing or old dependencies.
- Build
+ 2.3.4. Build
After the configure step is complete, to build the executables from the
C++ code and prepare the Python scripts, run:
$ make
- Install
+ 2.3.5. Install
To install the BIND 10 executables, support files, and documentation, run:
@@ -412,28 +458,28 @@ Installation from source
The install step may require superuser privileges.
- Install Hierarchy
+ 2.3.6. Install Hierarchy
The following is the layout of the complete BIND 10 installation:
- o bin/ -- general tools and diagnostic clients.
- o etc/bind10-devel/ -- configuration files.
- o lib/ -- libraries and python modules.
- o libexec/bind10-devel/ -- executables that a user wouldn't normally run
+ o bin/ â general tools and diagnostic clients.
+ o etc/bind10-devel/ â configuration files.
+ o lib/ â libraries and python modules.
+ o libexec/bind10-devel/ â executables that a user wouldn't normally run
directly and are not run independently. These are the BIND 10 modules
which are daemons started by the bind10 tool.
- o sbin/ -- commands used by the system administrator.
- o share/bind10-devel/ -- configuration specifications.
- o share/man/ -- manual pages (online documentation).
- o var/bind10-devel/ -- data source and configuration databases.
+ o sbin/ â commands used by the system administrator.
+ o share/bind10-devel/ â configuration specifications.
+ o share/man/ â manual pages (online documentation).
+ o var/bind10-devel/ â data source and configuration databases.
-Chapter 3. Starting BIND10 with bind10
+Chapter 3. Starting BIND10 with bind10
Table of Contents
- Starting BIND 10
+ 3.1. Starting BIND 10
- Configuration of started processes
+ 3.2. Configuration of started processes
BIND 10 provides the bind10 command which starts up the required
processes. bind10 will also restart some processes that exit unexpectedly.
@@ -451,12 +497,11 @@ Chapter 3. Starting BIND10 with bind10
b10-sockcreator will allocate sockets for the rest of the system.
In its default configuration, the bind10 master process will also start up
- b10-cmdctl for admins to communicate with the system, b10-auth for
- authoritative DNS service, b10-stats for statistics collection, b10-xfrin
- for inbound DNS zone transfers, b10-xfrout for outbound DNS zone
- transfers, and b10-zonemgr for secondary service.
+ b10-cmdctl for administration tools to communicate with the system,
+ b10-stats for statistics collection, and b10-stats-httpd for statistics
+ reporting.
-Starting BIND 10
+3.1. Starting BIND 10
To start the BIND 10 service, simply run bind10. Run it with the --verbose
switch to get additional debugging or diagnostic output.
@@ -465,23 +510,16 @@ Starting BIND 10
If the setproctitle Python module is detected at start up, the process
names for the Python-based daemons will be renamed to better identify them
- instead of just "python". This is not needed on some operating systems.
+ instead of just âpythonâ. This is not needed on some operating systems.
-Configuration of started processes
+3.2. Configuration of started processes
The processes to be started can be configured, with the exception of the
b10-sockcreator, b10-msgq and b10-cfgmgr.
The configuration is in the Boss/components section. Each element
represents one component, which is an abstraction of a process (currently
- there's also one component which doesn't represent a process). If you
- didn't want to transfer out at all (your server is a slave only), you
- would just remove the corresponding component from the set, like this and
- the process would be stopped immediately (and not started on the next
- startup):
-
- > config remove Boss/components b10-xfrout
- > config commit
+ there's also one component which doesn't represent a process).
To add a process to the set, let's say the resolver (which not started by
default), you would do this:
@@ -501,7 +539,7 @@ Configuration of started processes
usual way. This is the list of components that need to be started in a
special way, with the value of special used for them:
- Table 3.1.
+ Table 3.1.Â
+------------------------------------------------------------------------+
| Component | Special | Description |
@@ -512,16 +550,14 @@ Configuration of started processes
|--------------+----------+----------------------------------------------|
| b10-cmdctl | cmdctl | The command control (remote control |
| | | interface) |
- |--------------+----------+----------------------------------------------|
- | setuid | setuid | Virtual component, see below |
+------------------------------------------------------------------------+
The kind specifies how a failure of the component should be handled. If it
- is set to "dispensable" (the default unless you set something else), it
- will get started again if it fails. If it is set to "needed" and it fails
+ is set to âdispensableâ (the default unless you set something else), it
+ will get started again if it fails. If it is set to âneededâ and it fails
at startup, the whole bind10 shuts down and exits with error exit code.
But if it fails some time later, it is just started again. If you set it
- to "core", you indicate that the system is not usable without the
+ to âcoreâ, you indicate that the system is not usable without the
component and if such component fails, the system shuts down no matter
when the failure happened. This is the behaviour of the core components
(the ones you can't turn off), but you can declare any other components as
@@ -530,13 +566,14 @@ Configuration of started processes
The priority defines order in which the components should start. The ones
with higher number are started sooner than the ones with lower ones. If
- you don't set it, 0 (zero) is used as the priority.
+ you don't set it, 0 (zero) is used as the priority. Usually, leaving it at
+ the default is enough.
There are other parameters we didn't use in our example. One of them is
- "address". It is the address used by the component on the b10-msgq message
+ âaddressâ. It is the address used by the component on the b10-msgq message
bus. The special components already know their address, but the usual ones
don't. The address is by convention the thing after b10-, with the first
- letter capital (eg. b10-stats would have "Stats" as its address).
+ letter capital (eg. b10-stats would have âStatsâ as its address).
The last one is process. It is the name of the process to be started. It
defaults to the name of the component if not set, but you can use this to
@@ -546,7 +583,7 @@ Configuration of started processes
This system allows you to start the same component multiple times (by
including it in the configuration with different names, but the same
- process setting). However, the rest of the system doesn't expect such
+ process setting). However, the rest of the system doesn't expect such a
situation, so it would probably not do what you want. Such support is yet
to be implemented.
@@ -556,26 +593,32 @@ Configuration of started processes
mistakes. You could turn off the b10-cmdctl, but then you couldn't change
it back the usual way, as it would require it to be running (you would
have to find and edit the configuration directly). Also, some modules
- might have dependencies -- b10-stats-httpd need b10-stats, b10-xfrout
- needs the b10-auth to be running, etc.
+ might have dependencies: b10-stats-httpd needs b10-stats, b10-xfrout needs
+ b10-auth to be running, etc.
In short, you should think twice before disabling something here.
- Now, to the mysterious setuid virtual component. If you use the -u option
- to start the bind10 as root, but change the user later, we need to start
- the b10-auth or b10-resolver as root (until the socket creator is
- finished). So we need to specify the time when the switch from root do the
- given user happens and that's what the setuid component is for. The switch
- is done at the time the setuid component would be started, if it was a
- process. The default configuration contains the setuid component with
- priority 5, b10-auth has 10 to be started before the switch and everything
- else is without priority, so it is started after the switch.
+ It is possible to start some components multiple times (currently b10-auth
+ and b10-resolzer). You might want to do that to gain more performance
+ (each one uses only single core). Just put multiple entries under
+ different names, like this, with the same config:
-Chapter 4. Command channel
+ > config add Boss/components b10-resolver-2
+ > config set Boss/components/b10-resolver-2/special resolver
+ > config set Boss/components/b10-resolver-2/kind needed
+ > config commit
+
+ However, this is work in progress and the support is not yet complete. For
+ example, each resolver will have its own cache, each authoritative server
+ will keep its own copy of in-memory data and there could be problems with
+ locking the sqlite database, if used. The configuration might be changed
+ to something more convenient in future.
+
+Chapter 4. Command channel
The BIND 10 components use the b10-msgq message routing daemon to
communicate with other BIND 10 components. The b10-msgq implements what is
- called the "Command Channel". Processes intercommunicate by sending
+ called the âCommand Channelâ. Processes intercommunicate by sending
messages on the command channel. Example messages include shutdown, get
configurations, and set configurations. This Command Channel is not used
for DNS message passing. It is used only to control and monitor the BIND
@@ -585,7 +628,7 @@ Chapter 4. Command channel
default, BIND 10 uses port 9912 for the b10-msgq service. It listens on
127.0.0.1.
-Chapter 5. Configuration manager
+Chapter 5. Configuration manager
The configuration manager, b10-cfgmgr, handles all BIND 10 system
configuration. It provides persistent storage for configuration, and
@@ -597,12 +640,12 @@ Chapter 5. Configuration manager
The administrator doesn't connect to it directly, but uses a user
interface to communicate with the configuration manager via b10-cmdctl's
- REST-ful interface. b10-cmdctl is covered in Chapter 6, Remote control
+ REST-ful interface. b10-cmdctl is covered in Chapter 6, Remote control
daemon.
Note
- The development prototype release only provides the bindctl as a user
+ The development prototype release only provides bindctl as a user
interface to b10-cmdctl. Upcoming releases will provide another
interactive command-line interface and a web-based interface.
@@ -621,14 +664,14 @@ Chapter 5. Configuration manager
The configuration manager does not have any command line arguments.
Normally it is not started manually, but is automatically started using
- the bind10 master process (as covered in Chapter 3, Starting BIND10 with
+ the bind10 master process (as covered in Chapter 3, Starting BIND10 with
bind10).
-Chapter 6. Remote control daemon
+Chapter 6. Remote control daemon
Table of Contents
- Configuration specification for b10-cmdctl
+ 6.1. Configuration specification for b10-cmdctl
b10-cmdctl is the gateway between administrators and the BIND 10 system.
It is a HTTPS server that uses standard HTTP Digest Authentication for
@@ -637,7 +680,7 @@ Chapter 6. Remote control daemon
When b10-cmdctl starts, it firsts asks b10-cfgmgr about what modules are
running and what their configuration is (over the b10-msgq channel). Then
- it will start listening on HTTPS for clients -- the user interface -- such
+ it will start listening on HTTPS for clients â the user interface â such
as bindctl.
b10-cmdctl directly sends commands (received from the user interface) to
@@ -664,7 +707,7 @@ Chapter 6. Remote control daemon
/usr/local/etc/bind10-devel/cmdctl-accounts.csv. This comma-delimited file
lists the accounts with a user name, hashed password, and salt. (A sample
file is at /usr/local/share/bind10-devel/cmdctl-accounts.csv. It contains
- the user named "root" with the password "bind10".)
+ the user named ârootâ with the password âbind10â.)
The administrator may create a user account with the b10-cmdctl-usermgr
tool.
@@ -672,17 +715,17 @@ Chapter 6. Remote control daemon
By default the HTTPS server listens on the localhost port 8080. The port
can be set by using the --port command line option. The address to listen
on can be set using the --address command line argument. Each HTTPS
- connection is stateless and timesout in 1200 seconds by default. This can
+ connection is stateless and times out in 1200 seconds by default. This can
be redefined by using the --idle-timeout command line argument.
-Configuration specification for b10-cmdctl
+6.1. Configuration specification for b10-cmdctl
The configuration items for b10-cmdctl are: key_file cert_file
accounts_file
The control commands are: print_settings shutdown
-Chapter 7. Control and configure user interface
+Chapter 7. Control and configure user interface
Note
@@ -702,36 +745,82 @@ Chapter 7. Control and configure user interface
b10-cfgmgr which then stores the details and relays (over a b10-msgq
command channel) the configuration on to the specified module.
-Chapter 8. Authoritative Server
+Chapter 8. Authoritative Server
Table of Contents
- Server Configurations
+ 8.1. Server Configurations
- Data Source Backends
+ 8.2. Data Source Backends
- Loading Master Zones Files
+ 8.2.1. In-memory Data Source
+
+ 8.3. Loading Master Zones Files
The b10-auth is the authoritative DNS server. It supports EDNS0 and
DNSSEC. It supports IPv6. Normally it is started by the bind10 master
process.
-Server Configurations
+8.1. Server Configurations
b10-auth is configured via the b10-cfgmgr configuration manager. The
- module name is "Auth". The configuration data item is:
+ module name is âAuthâ. The configuration data items are:
database_file
This is an optional string to define the path to find the SQLite3
database file. Note: Later the DNS server will use various data
source backends. This may be a temporary setting until then.
- The configuration command is:
+ datasources
+ datasources configures data sources. The list items include: type
+ to define the required data source type (such as âmemoryâ); class
+ to optionally select the class (it defaults to âINâ); and zones to
+ define the file path name and the origin (default domain). By
+ default, this is empty.
+
+ Note
+
+ 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
+ named-compilezone -D.
+
+ listen_on
+ listen_on is a list of addresses and ports for b10-auth to listen
+ on. The list items are the address string and port number. By
+ default, b10-auth listens on port 53 on the IPv6 (::) and IPv4
+ (0.0.0.0) wildcard addresses.
+
+ statistics-interval
+ statistics-interval is the timer interval in seconds for b10-auth
+ to share its statistics information to b10-stats(8). Statistics
+ updates can be disabled by setting this to 0. The default is 60.
+
+ The configuration commands are:
+
+ loadzone
+ loadzone tells b10-auth to load or reload a zone file. The
+ arguments include: class which optionally defines the class (it
+ defaults to âINâ); origin is the domain name of the zone; and
+ datasrc optionally defines the type of datasource (it defaults to
+ âmemoryâ).
+
+ Note
+
+ In this development version, currently this only supports the IN
+ class and the memory data source.
+
+ sendstats
+ sendstats tells b10-auth to send its statistics data to
+ b10-stats(8) immediately.
shutdown
- Stop the authoritative DNS server.
+ Stop the authoritative DNS server. This has an optional pid
+ argument to select the process ID to stop. (Note that the BIND 10
+ boss process may restart this service if configured.)
-Data Source Backends
+8.2. Data Source Backends
Note
@@ -744,12 +833,48 @@ Data Source Backends
/usr/local/var/bind10-devel/zone.sqlite3. (The full path is what was
defined at build configure time for --localstatedir. The default is
/usr/local/var/.) This data file location may be changed by defining the
- "database_file" configuration.
+ âdatabase_fileâ configuration.
+
+ 8.2.1. In-memory Data Source
+
+ The following commands to bindctl provide an example of configuring an
+ in-memory data source containing the âexample.comâ zone with the zone file
+ named âexample.com.zoneâ:
+
+ > config add Auth/datasources
+ > config set Auth/datasources[0]/type "memory"
+ > config add Auth/datasources[0]/zones
+ > config set Auth/datasources[0]/zones[0]/origin "example.com"
+ > config set Auth/datasources[0]/zones[0]/file "example.com.zone"
+ > config commit
+
+ The authoritative server will begin serving it immediately after it is
+ loaded.
+
+ Use the Auth loadzone command in bindctl to reload a changed master file
+ into memory; for example:
+
+ > Auth loadzone origin="example.com"
+
+ By default, the memory data source is disabled; it must be configured
+ explicitly. To disable all the in-memory zones, specify a null list for
+ Auth/datasources:
+
+ > config set Auth/datasources/ []
+ > config commit
+
+ The following example stops serving a specific zone:
+
+ > config remove Auth/datasources[0]/zones[0]
+ > config commit
+
+ (Replace the list number(s) in datasources[0] and/or zones[0] for the
+ relevant zone as needed.)
-Loading Master Zones Files
+8.3. Loading Master Zones Files
- RFC 1035 style DNS master zone files may imported into a BIND 10 data
- source by using the b10-loadzone utility.
+ RFC 1035 style DNS master zone files may imported into a BIND 10 SQLite3
+ data source by using the b10-loadzone utility.
b10-loadzone supports the following special directives (control entries):
@@ -768,8 +893,8 @@ Loading Master Zones Files
Note
- In the development prototype release, only the SQLite3 back end is used.
- By default, it stores the zone data in
+ In the development prototype release, only the SQLite3 back end is used by
+ b10-loadzone. By default, it stores the zone data in
/usr/local/var/bind10-devel/zone.sqlite3 unless the -d switch is used to
set the database filename. Multiple zones are stored in a single SQLite3
zone database.
@@ -777,21 +902,23 @@ Loading Master Zones Files
If you reload a zone already existing in the database, all records from
that prior zone disappear and a whole new set appears.
-Chapter 9. Incoming Zone Transfers
+Chapter 9. Incoming Zone Transfers
Table of Contents
- Configuration for Incoming Zone Transfers
+ 9.1. Configuration for Incoming Zone Transfers
+
+ 9.2. Enabling IXFR
- Enabling IXFR
+ 9.3. Secondary Manager
- Trigger an Incoming Zone Transfer Manually
+ 9.4. Trigger an Incoming Zone Transfer Manually
Incoming zones are transferred using the b10-xfrin process which is
started by bind10. When received, the zone is stored in the corresponding
BIND 10 data source, and its records can be served by b10-auth. In
combination with b10-zonemgr (for automated SOA checks), this allows the
- BIND 10 server to provide "secondary" service.
+ BIND 10 server to provide âsecondaryâ service.
The b10-xfrin process supports both AXFR and IXFR. Due to some
implementation limitations of the current development release, however, it
@@ -803,7 +930,7 @@ Chapter 9. Incoming Zone Transfers
only available for SQLite3-based data sources, that is, they don't work
for an in-memory data source.
-Configuration for Incoming Zone Transfers
+9.1. Configuration for Incoming Zone Transfers
In practice, you need to specify a list of secondary zones to enable
incoming zone transfers for these zones (you can still trigger a zone
@@ -820,7 +947,7 @@ Configuration for Incoming Zone Transfers
(We assume there has been no zone configuration before).
-Enabling IXFR
+9.2. Enabling IXFR
As noted above, b10-xfrin uses AXFR for zone transfers by default. To
enable IXFR for zone transfers for a particular zone, set the use_ixfr
@@ -843,14 +970,40 @@ Enabling IXFR
be implemented in a near future version, at which point we will enable
IXFR by default.
-Trigger an Incoming Zone Transfer Manually
+9.3. Secondary Manager
+
+ The b10-zonemgr process is started by bind10. It keeps track of SOA
+ refresh, retry, and expire timers and other details for BIND 10 to perform
+ as a slave. When the b10-auth authoritative DNS server receives a NOTIFY
+ message, b10-zonemgr may tell b10-xfrin to do a refresh to start an
+ inbound zone transfer. The secondary manager resets its counters when a
+ new zone is transferred in.
+
+ Note
+
+ Access control (such as allowing notifies) is not yet provided. The
+ primary/secondary service is not yet complete.
+
+ The following example shows using bindctl to configure the server to be a
+ secondary for the example zone:
+
+ > config add Zonemgr/secondary_zones
+ > config set Zonemgr/secondary_zones[0]/name "example.com"
+ > config set Zonemgr/secondary_zones[0]/class "IN"
+ > config commit
+
+ If the zone does not exist in the data source already (i.e. no SOA record
+ for it), b10-zonemgr will automatically tell b10-xfrin to transfer the
+ zone in.
+
+9.4. Trigger an Incoming Zone Transfer Manually
To manually trigger a zone transfer to retrieve a remote zone, you may use
the bindctl utility. For example, at the bindctl prompt run:
> Xfrin retransfer zone_name="foo.example.org" master=192.0.2.99
-Chapter 10. Outbound Zone Transfers
+Chapter 10. Outbound Zone Transfers
The b10-xfrout process is started by bind10. When the b10-auth
authoritative DNS server receives an AXFR or IXFR request, b10-auth
@@ -882,58 +1035,37 @@ Chapter 10. Outbound Zone Transfers
In the above example the lines for transfer_acl were divided for
readability. In the actual input it must be in a single line.
- If you want to require TSIG in access control, a separate TSIG "key ring"
- must be configured specifically for b10-xfrout as well as a system wide
- key ring, both containing a consistent set of keys. For example, to change
- the previous example to allowing requests from 192.0.2.1 signed by a TSIG
- with a key name of "key.example", you'll need to do this:
+ If you want to require TSIG in access control, a system wide TSIG "key
+ ring" must be configured. For example, to change the previous example to
+ allowing requests from 192.0.2.1 signed by a TSIG with a key name of
+ "key.example", you'll need to do this:
> config set tsig_keys/keys ["key.example:<base64-key>"]
- > config set Xfrout/tsig_keys/keys ["key.example:<base64-key>"]
> config set Xfrout/zone_config[0]/transfer_acl [{"action": "ACCEPT", "from": "192.0.2.1", "key": "key.example"}]
> config commit
- The first line of configuration defines a system wide key ring. This is
- necessary because the b10-auth server also checks TSIGs and it uses the
- system wide configuration.
-
- Note
-
- In a future version, b10-xfrout will also use the system wide TSIG
- configuration. The way to specify zone specific configuration (ACLs, etc)
- is likely to be changed, too.
-
-Chapter 11. Secondary Manager
-
- The b10-zonemgr process is started by bind10. It keeps track of SOA
- refresh, retry, and expire timers and other details for BIND 10 to perform
- as a slave. When the b10-auth authoritative DNS server receives a NOTIFY
- message, b10-zonemgr may tell b10-xfrin to do a refresh to start an
- inbound zone transfer. The secondary manager resets its counters when a
- new zone is transferred in.
+ Both Xfrout and Auth will use the system wide keyring to check TSIGs in
+ the incoming messages and to sign responses.
Note
- Access control (such as allowing notifies) is not yet provided. The
- primary/secondary service is not yet complete.
+ The way to specify zone specific configuration (ACLs, etc) is likely to be
+ changed.
-Chapter 12. Recursive Name Server
+Chapter 11. Recursive Name Server
Table of Contents
- Access Control
+ 11.1. Access Control
- Forwarding
+ 11.2. Forwarding
The b10-resolver process is started by bind10.
The main bind10 process can be configured to select to run either the
- authoritative or resolver or both. By default, it starts the authoritative
- service. You may change this using bindctl, for example:
+ authoritative or resolver or both. By default, it doesn't start either
+ one. You may change this using bindctl, for example:
- > config remove Boss/components b10-xfrout
- > config remove Boss/components b10-xfrin
- > config remove Boss/components b10-auth
> config add Boss/components b10-resolver
> config set Boss/components/b10-resolver/special resolver
> config set Boss/components/b10-resolver/kind needed
@@ -951,26 +1083,26 @@ Chapter 12. Recursive Name Server
> config set Resolver/listen_on[2]/port 53
> config commit
- (Replace the "2" as needed; run "config show Resolver/listen_on" if
+ (Replace the â2â as needed; run âconfig show Resolver/listen_onâ if
needed.)
-Access Control
+11.1. Access Control
By default, the b10-resolver daemon only accepts DNS queries from the
localhost (127.0.0.1 and ::1). The Resolver/query_acl configuration may be
used to reject, drop, or allow specific IPs or networks. This
configuration list is first match.
- The configuration's action item may be set to "ACCEPT" to allow the
- incoming query, "REJECT" to respond with a DNS REFUSED return code, or
- "DROP" to ignore the query without any response (such as a blackhole). For
+ The configuration's action item may be set to âACCEPTâ to allow the
+ incoming query, âREJECTâ to respond with a DNS REFUSED return code, or
+ âDROPâ to ignore the query without any response (such as a blackhole). For
more information, see the respective debugging messages:
RESOLVER_QUERY_ACCEPTED, RESOLVER_QUERY_REJECTED, and
RESOLVER_QUERY_DROPPED.
The required configuration's from item is set to an IPv4 or IPv6 address,
addresses with an network mask, or to the special lowercase keywords
- "any6" (for any IPv6 address) or "any4" (for any IPv4 address).
+ âany6â (for any IPv6 address) or âany4â (for any IPv4 address).
For example to allow the 192.168.1.0/24 network to use your recursive name
server, at the bindctl prompt run:
@@ -980,14 +1112,14 @@ Access Control
> config set Resolver/query_acl[2]/from "192.168.1.0/24"
> config commit
- (Replace the "2" as needed; run "config show Resolver/query_acl" if
+ (Replace the â2â as needed; run âconfig show Resolver/query_aclâ if
needed.)
Note
This prototype access control configuration syntax may be changed.
-Forwarding
+11.2. Forwarding
To enable forwarding, the upstream address and port must be configured to
forward queries to, such as:
@@ -1003,7 +1135,326 @@ Forwarding
> config set Resolver/forward_addresses []
> config commit
-Chapter 13. Statistics
+Chapter 12. DHCPv4 Server
+
+ Table of Contents
+
+ 12.1. DHCPv4 Server Usage
+
+ 12.2. DHCPv4 Server Configuration
+
+ 12.3. Supported standards
+
+ 12.4. DHCPv4 Server Limitations
+
+ Dynamic Host Configuration Protocol for IPv4 (DHCP or DHCPv4) and Dynamic
+ Host Configuration Protocol for IPv6 (DHCPv6) are protocols that allow one
+ node (server) to provision configuration parameters to many hosts and
+ devices (clients). To ease deployment in larger networks, additional nodes
+ (relays) may be deployed that facilitate communication between servers and
+ clients. Even though principles of both DHCPv4 and DHCPv6 are somewhat
+ similar, these are two radically different protocols. BIND10 offers server
+ implementations for both DHCPv4 and DHCPv6. This chapter is about DHCP for
+ IPv4. For a description of the DHCPv6 server, see Chapter 13, DHCPv6
+ Server.
+
+ The DHCPv4 server component is currently under intense development. You
+ may want to check out BIND10 DHCP (Kea) wiki and recent posts on BIND10
+ developers mailing list.
+
+ The DHCPv4 and DHCPv6 components in BIND10 architecture are internally
+ code named âKeaâ.
+
+ Note
+
+ As of December 2011, both DHCPv4 and DHCPv6 components are skeleton
+ servers. That means that while they are capable of performing DHCP
+ configuration, they are not fully functional yet. In particular, neither
+ has functional lease databases. This means that they will assign the same,
+ fixed, hardcoded addresses to any client that will ask. See Section 12.4,
+ âDHCPv4 Server Limitationsâ and Section 13.4, âDHCPv6 Server Limitationsâ
+ for detailed description.
+
+12.1. DHCPv4 Server Usage
+
+ BIND10 provides the DHCPv4 server component since December 2011. It is a
+ skeleton server and can be described as an early prototype that is not
+ fully functional yet. It is mature enough to conduct first tests in lab
+ environment, but it has significant limitations. See Section 12.4, âDHCPv4
+ Server Limitationsâ for details.
+
+ The DHCPv4 server is implemented as b10-dhcp4 daemon. As it is not
+ configurable yet, it is fully autonomous, that is it does not interact
+ with b10-cfgmgr. To start DHCPv4 server, simply input:
+
+ #cd src/bin/dhcp4
+ #./b10-dhcp4
+
+ Depending on your installation, b10-dhcp4 binary may reside in
+ src/bin/dhcp4 in your source code directory, in /usr/local/bin/b10-dhcp4
+ or other directory you specified during compilation. At start, the server
+ will detect available network interfaces and will attempt to open UDP
+ sockets on all interfaces that are up, running, are not loopback, and have
+ IPv4 address assigned. The server will then listen to incoming traffic.
+ Currently supported client messages are DISCOVER and REQUEST. The server
+ will respond to them with OFFER and ACK, respectively. Since the DHCPv4
+ server opens privileged ports, it requires root access. Make sure you run
+ this daemon as root.
+
+ Note
+
+ Integration with bind10 is planned. Ultimately, b10-dhcp4 will not be
+ started directly, but rather via bind10. Please be aware of this planned
+ change.
+
+12.2. DHCPv4 Server Configuration
+
+ The DHCPv4 server does not have a lease database implemented yet nor any
+ support for configuration, so every time the same set of configuration
+ options (including the same fixed address) will be assigned every time.
+
+ At this stage of development, the only way to alter the server
+ configuration is to tweak its source code. To do so, please edit
+ src/bin/dhcp4/dhcp4_srv.cc file and modify following parameters and
+ recompile:
+
+ const std::string HARDCODED_LEASE = "192.0.2.222"; // assigned lease
+ const std::string HARDCODED_NETMASK = "255.255.255.0";
+ const uint32_t HARDCODED_LEASE_TIME = 60; // in seconds
+ const std::string HARDCODED_GATEWAY = "192.0.2.1";
+ const std::string HARDCODED_DNS_SERVER = "192.0.2.2";
+ const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
+ const std::string HARDCODED_SERVER_ID = "192.0.2.1";
+
+ Lease database and configuration support is planned for 2012.
+
+12.3. Supported standards
+
+ The following standards and draft standards are currently supported:
+
+ o RFC2131: Supported messages are DISCOVER, OFFER, REQUEST, and ACK.
+ o RFC2132: Supported options are: PAD (0), END(255), Message Type(53),
+ DHCP Server Identifier (54), Domain Name (15), DNS Servers (6), IP
+ Address Lease Time (51), Subnet mask (1), and Routers (3).
+
+12.4. DHCPv4 Server Limitations
+
+ These are the current limitations of the DHCPv4 server software. Most of
+ them are reflections of the early stage of development and should be
+ treated as ânot implemented yetâ, rather than actual limitations.
+
+ o During initial IPv4 node configuration, the server is expected to send
+ packets to a node that does not have IPv4 address assigned yet. The
+ server requires certain tricks (or hacks) to transmit such packets.
+ This is not implemented yet, therefore DHCPv4 server supports relayed
+ traffic only (that is, normal point to point communication).
+ o b10-dhcp4 provides a single, fixed, hardcoded lease to any client that
+ asks. There is no lease manager implemented. If two clients request
+ addresses, they will both get the same fixed address.
+ o b10-dhcp4 does not support any configuration mechanisms yet. The whole
+ configuration is currently hardcoded. The only way to tweak
+ configuration is to directly modify source code. See see Section 12.2,
+ âDHCPv4 Server Configurationâ for details.
+ o Upon start, the server will open sockets on all interfaces that are
+ not loopback, are up and running and have IPv4 address. Support for
+ multiple interfaces is not coded in reception routines yet, so if you
+ are running this code on a machine that has many interfaces and
+ b10-dhcp4 happens to listen on wrong interface, the easiest way to
+ work around this problem is to turn down other interfaces. This
+ limitation will be fixed shortly.
+ o PRL (Parameter Request List, a list of options requested by a client)
+ is currently ignored and server assigns DNS SERVER and DOMAIN NAME
+ options.
+ o b10-dhcp4 does not support BOOTP. That is a design choice. This
+ limitation is permanent. If you have legacy nodes that can't use DHCP
+ and require BOOTP support, please use latest version of ISC DHCP
+ http://www.isc.org/software/dhcp.
+ o Interface detection is currently working on Linux only. See
+ Section 14.1, âInterface detectionâ for details.
+ o b10-dhcp4 does not verify that assigned address is unused. According
+ to RFC2131, the allocating server should verify that address is no
+ used by sending ICMP echo request.
+ o Address renewal (RENEW), rebinding (REBIND), confirmation (CONFIRM),
+ duplication report (DECLINE) and release (RELEASE) are not supported
+ yet.
+ o DNS Update is not supported yet.
+ o -v (verbose) command line option is currently the default, and cannot
+ be disabled.
+
+Chapter 13. DHCPv6 Server
+
+ Table of Contents
+
+ 13.1. DHCPv6 Server Usage
+
+ 13.2. DHCPv6 Server Configuration
+
+ 13.3. Supported DHCPv6 Standards
+
+ 13.4. DHCPv6 Server Limitations
+
+ Dynamic Host Configuration Protocol for IPv6 (DHCPv6) is specified in
+ RFC3315. BIND10 provides DHCPv6 server implementation that is described in
+ this chapter. For a description of the DHCPv4 server implementation, see
+ Chapter 12, DHCPv4 Server.
+
+ The DHCPv6 server component is currently under intense development. You
+ may want to check out BIND10 DHCP (Kea) wiki and recent posts on BIND10
+ developers mailing list.
+
+ The DHCPv4 and DHCPv6 components in BIND10 architecture are internally
+ code named âKeaâ.
+
+ Note
+
+ As of December 2011, both DHCPv4 and DHCPv6 components are skeleton
+ servers. That means that while they are capable of performing DHCP
+ configuration, they are not fully functional yet. In particular, neither
+ has functional lease databases. This means that they will assign the same,
+ fixed, hardcoded addresses to any client that will ask. See Section 12.4,
+ âDHCPv4 Server Limitationsâ and Section 13.4, âDHCPv6 Server Limitationsâ
+ for detailed description.
+
+13.1. DHCPv6 Server Usage
+
+ BIND10 provides the DHCPv6 server component since September 2011. It is a
+ skeleton server and can be described as an early prototype that is not
+ fully functional yet. It is mature enough to conduct first tests in lab
+ environment, but it has significant limitations. See Section 13.4, âDHCPv6
+ Server Limitationsâ for details.
+
+ The DHCPv6 server is implemented as b10-dhcp6 daemon. As it is not
+ configurable yet, it is fully autonomous, that is it does not interact
+ with b10-cfgmgr. To start DHCPv6 server, simply input:
+
+ #cd src/bin/dhcp6
+ #./b10-dhcp6
+
+ Depending on your installation, b10-dhcp6 binary may reside in
+ src/bin/dhcp6 in your source code directory, in /usr/local/bin/b10-dhcp6
+ or other directory you specified during compilation. At start, server will
+ detect available network interfaces and will attempt to open UDP sockets
+ on all interfaces that are up, running, are not loopback, are
+ multicast-capable, and have IPv6 address assigned. The server will then
+ listen to incoming traffic. Currently supported client messages are
+ SOLICIT and REQUEST. The server will respond to them with ADVERTISE and
+ REPLY, respectively. Since the DHCPv6 server opens privileged ports, it
+ requires root access. Make sure you run this daemon as root.
+
+ Note
+
+ Integration with bind10 is planned. Ultimately, b10-dhcp6 will not be
+ started directly, but rather via bind10. Please be aware of this planned
+ change.
+
+13.2. DHCPv6 Server Configuration
+
+ The DHCPv6 server does not have lease database implemented yet or any
+ support for configuration, so every time the same set of configuration
+ options (including the same fixed address) will be assigned every time.
+
+ At this stage of development, the only way to alter server configuration
+ is to tweak its source code. To do so, please edit
+ src/bin/dhcp6/dhcp6_srv.cc file and modify following parameters and
+ recompile:
+
+ const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";
+ const uint32_t HARDCODED_T1 = 1500; // in seconds
+ const uint32_t HARDCODED_T2 = 2600; // in seconds
+ const uint32_t HARDCODED_PREFERRED_LIFETIME = 3600; // in seconds
+ const uint32_t HARDCODED_VALID_LIFETIME = 7200; // in seconds
+ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
+
+ Lease database and configuration support is planned for 2012.
+
+13.3. Supported DHCPv6 Standards
+
+ The following standards and draft standards are currently supported:
+
+ o RFC3315: Supported messages are SOLICIT, ADVERTISE, REQUEST, and
+ REPLY. Supported options are SERVER_ID, CLIENT_ID, IA_NA, and
+ IAADDRESS.
+ o RFC3646: Supported option is DNS_SERVERS.
+
+13.4. DHCPv6 Server Limitations
+
+ These are the current limitations of the DHCPv6 server software. Most of
+ them are reflections of the early stage of development and should be
+ treated as ânot implemented yetâ, rather than actual limitations.
+
+ o Relayed traffic is not supported.
+ o b10-dhcp6 provides a single, fixed, hardcoded lease to any client that
+ asks. There is no lease manager implemented. If two clients request
+ addresses, they will both get the same fixed address.
+ o b10-dhcp6 does not support any configuration mechanisms yet. The whole
+ configuration is currently hardcoded. The only way to tweak
+ configuration is to directly modify source code. See see Section 13.2,
+ âDHCPv6 Server Configurationâ for details.
+ o Upon start, the server will open sockets on all interfaces that are
+ not loopback, are up, running and are multicast capable and have IPv6
+ address. Support for multiple interfaces is not coded in reception
+ routines yet, so if you are running this code on a machine that has
+ many interfaces and b10-dhcp6 happens to listen on wrong interface,
+ the easiest way to work around this problem is to turn down other
+ interfaces. This limitation will be fixed shortly.
+ o ORO (Option Request Option, a list of options requested by a client)
+ is currently ignored and server assigns DNS SERVER option.
+ o Temporary addresses are not supported yet.
+ o Prefix delegation is not supported yet.
+ o Address renewal (RENEW), rebinding (REBIND), confirmation (CONFIRM),
+ duplication report (DECLINE) and release (RELEASE) are not supported
+ yet.
+ o DNS Update is not supported yet.
+ o Interface detection is currently working on Linux only. See
+ Section 14.1, âInterface detectionâ for details.
+ o -v (verbose) command line option is currently the default, and cannot
+ be disabled.
+
+Chapter 14. libdhcp++ library
+
+ Table of Contents
+
+ 14.1. Interface detection
+
+ 14.2. DHCPv4/DHCPv6 packet handling
+
+ libdhcp++ is a common library written in C++ that handles many
+ DHCP-related tasks, like DHCPv4 and DHCPv6 packets parsing, manipulation
+ and assembly, option parsing, manipulation and assembly, network interface
+ detection and socket operations, like socket creations, data transmission
+ and reception and socket closing.
+
+ While this library is currently used by b10-dhcp4 and b10-dhcp6 only, it
+ is designed to be portable, universal library useful for any kind of
+ DHCP-related software.
+
+14.1. Interface detection
+
+ Both DHCPv4 and DHCPv6 components share network interface detection
+ routines. Interface detection is currently only supported on Linux
+ systems.
+
+ For non-Linux systems, there is currently stub implementation provided. As
+ DHCP servers need to know available addresses, there is a simple mechanism
+ implemented to provide that information. User is expected to create
+ interfaces.txt file. Format of this file is simple. It contains list of
+ interfaces along with available address on each interface. This mechanism
+ is temporary and is going to be removed as soon as interface detection
+ becomes available on non-Linux systems. Here is an example of the
+ interfaces.txt file:
+
+ # For DHCPv6, please specify link-local address (starts with fe80::)
+ # If in doubt, check output of 'ifconfig -a' command.
+ eth0 fe80::21e:8cff:fe9b:7349
+
+ # For DHCPv4, please use following format:
+ #eth0 192.0.2.5
+
+14.2. DHCPv4/DHCPv6 packet handling
+
+ TODO: Describe packet handling here, with pointers to wiki
+
+Chapter 15. Statistics
The b10-stats process is started by bind10. It periodically collects
statistics data from various modules and aggregates it.
@@ -1015,6 +1466,10 @@ Chapter 13. Statistics
> Stats show
{
"Auth": {
+ "opcode.iquery": 0,
+ "opcode.notify": 10,
+ "opcode.query": 869617,
+ ...
"queries.tcp": 1749,
"queries.udp": 867868
},
@@ -1031,111 +1486,111 @@ Chapter 13. Statistics
}
-Chapter 14. Logging
+Chapter 16. Logging
Table of Contents
- Logging configuration
+ 16.1. Logging configuration
- Loggers
+ 16.1.1. Loggers
- Output Options
+ 16.1.2. Output Options
- Example session
+ 16.1.3. Example session
- Logging Message Format
+ 16.2. Logging Message Format
-Logging configuration
+16.1. Logging configuration
The logging system in BIND 10 is configured through the Logging module.
All BIND 10 modules will look at the configuration in Logging to see what
should be logged and to where.
- Loggers
+ 16.1.1. Loggers
Within BIND 10, a message is logged through a component called a "logger".
Different parts of BIND 10 log messages through different loggers, and
each logger can be configured independently of one another.
In the Logging module, you can specify the configuration for zero or more
- loggers; any that are not specified will take appropriate default values..
+ loggers; any that are not specified will take appropriate default values.
The three most important elements of a logger configuration are the name
(the component that is generating the messages), the severity (what to
log), and the output_options (where to log).
- name (string)
+ 16.1.1.1. name (string)
Each logger in the system has a name, the name being that of the component
using it to log messages. For instance, if you want to configure logging
- for the resolver module, you add an entry for a logger named "Resolver".
+ for the resolver module, you add an entry for a logger named âResolverâ.
This configuration will then be used by the loggers in the Resolver
module, and all the libraries used by it.
If you want to specify logging for one specific library within the module,
you set the name to module.library. For example, the logger used by the
- nameserver address store component has the full name of "Resolver.nsas".
+ nameserver address store component has the full name of âResolver.nsasâ.
If there is no entry in Logging for a particular library, it will use the
configuration given for the module.
To illustrate this, suppose you want the cache library to log messages of
severity DEBUG, and the rest of the resolver code to log messages of
severity INFO. To achieve this you specify two loggers, one with the name
- "Resolver" and severity INFO, and one with the name "Resolver.cache" with
+ âResolverâ and severity INFO, and one with the name âResolver.cacheâ with
severity DEBUG. As there are no entries for other libraries (e.g. the
- nsas), they will use the configuration for the module ("Resolver"), so
+ nsas), they will use the configuration for the module (âResolverâ), so
giving the desired behavior.
- One special case is that of a module name of "*" (asterisks), which is
+ One special case is that of a module name of â*â (asterisks), which is
interpreted as any module. You can set global logging options by using
this, including setting the logging configuration for a library that is
- used by multiple modules (e.g. "*.config" specifies the configuration
+ used by multiple modules (e.g. â*.configâ specifies the configuration
library code in whatever module is using it).
If there are multiple logger specifications in the configuration that
might match a particular logger, the specification with the more specific
logger name takes precedence. For example, if there are entries for for
- both "*" and "Resolver", the resolver module -- and all libraries it uses
- -- will log messages according to the configuration in the second entry
- ("Resolver"). All other modules will use the configuration of the first
- entry ("*"). If there was also a configuration entry for "Resolver.cache",
+ both â*â and âResolverâ, the resolver module â and all libraries it uses â
+ will log messages according to the configuration in the second entry
+ (âResolverâ). All other modules will use the configuration of the first
+ entry (â*â). If there was also a configuration entry for âResolver.cacheâ,
the cache library within the resolver would use that in preference to the
- entry for "Resolver".
+ entry for âResolverâ.
One final note about the naming. When specifying the module name within a
logger, use the name of the module as specified in bindctl, e.g.
- "Resolver" for the resolver module, "Xfrout" for the xfrout module, etc.
+ âResolverâ for the resolver module, âXfroutâ for the xfrout module, etc.
When the message is logged, the message will include the name of the
logger generating the message, but with the module name replaced by the
name of the process implementing the module (so for example, a message
- generated by the "Auth.cache" logger will appear in the output with a
- logger name of "b10-auth.cache").
+ generated by the âAuth.cacheâ logger will appear in the output with a
+ logger name of âb10-auth.cacheâ).
- severity (string)
+ 16.1.1.2. severity (string)
This specifies the category of messages logged. Each message is logged
with an associated severity which may be one of the following (in
descending order of severity):
- o FATAL
- o ERROR
- o WARN
- o INFO
- o DEBUG
+ o FATAL
+ o ERROR
+ o WARN
+ o INFO
+ o DEBUG
When the severity of a logger is set to one of these values, it will only
log messages of that severity, and the severities above it. The severity
may also be set to NONE, in which case all messages from that logger are
inhibited.
- output_options (list)
+ 16.1.1.3. output_options (list)
Each logger can have zero or more output_options. These specify where log
messages are sent to. These are explained in detail below.
The other options for a logger are:
- debuglevel (integer)
+ 16.1.1.4. debuglevel (integer)
When a logger's severity is set to DEBUG, this value specifies what debug
messages should be printed. It ranges from 0 (least verbose) to 99 (most
@@ -1143,69 +1598,80 @@ Logging configuration
If severity for the logger is not DEBUG, this value is ignored.
- additive (true or false)
+ 16.1.1.5. additive (true or false)
If this is true, the output_options from the parent will be used. For
- example, if there are two loggers configured; "Resolver" and
- "Resolver.cache", and additive is true in the second, it will write the
- log messages not only to the destinations specified for "Resolver.cache",
+ example, if there are two loggers configured; âResolverâ and
+ âResolver.cacheâ, and additive is true in the second, it will write the
+ log messages not only to the destinations specified for âResolver.cacheâ,
but also to the destinations as specified in the output_options in the
- logger named "Resolver".
+ logger named âResolverâ.
- Output Options
+ 16.1.2. Output Options
The main settings for an output option are the destination and a value
called output, the meaning of which depends on the destination that is
set.
- destination (string)
+ 16.1.2.1. destination (string)
The destination is the type of output. It can be one of:
- o console
- o file
- o syslog
+ o console
+ o file
+ o syslog
- output (string)
+ 16.1.2.2. output (string)
Depending on what is set as the output destination, this value is
interpreted as follows:
- destination is "console"
- The value of output must be one of "stdout" (messages printed to
- standard output) or "stderr" (messages printed to standard error).
+ destination is âconsoleâ
+
+ The value of output must be one of âstdoutâ (messages printed to
+ standard output) or âstderrâ (messages printed to standard error).
+
+ Note: if output is set to âstderrâ and a lot of messages are
+ produced in a short time (e.g. if the logging level is set to
+ DEBUG), you may occasionally see some messages jumbled up
+ together. This is due to a combination of the way that messages
+ are written to the screen and the unbuffered nature of the
+ standard error stream. If this occurs, it is recommended that
+ output be set to âstdoutâ.
+
+ destination is âfileâ
- destination is "file"
The value of output is interpreted as a file name; log messages
will be appended to this file.
- destination is "syslog"
+ destination is âsyslogâ
+
The value of output is interpreted as the syslog facility (e.g.
local0) that should be used for log messages.
The other options for output_options are:
- flush (true of false)
+ 16.1.2.2.1. flush (true of false)
Flush buffers after each log message. Doing this will reduce performance
but will ensure that if the program terminates abnormally, all messages up
to the point of termination are output.
- maxsize (integer)
+ 16.1.2.2.2. maxsize (integer)
Only relevant when destination is file, this is maximum file size of
output files in bytes. When the maximum size is reached, the file is
renamed and a new file opened. (For example, a ".1" is appended to the
- name -- if a ".1" file exists, it is renamed ".2", etc.)
+ name â if a ".1" file exists, it is renamed ".2", etc.)
If this is 0, no maximum file size is used.
- maxver (integer)
+ 16.1.2.2.3. maxver (integer)
Maximum number of old log files to keep around when rolling the output
- file. Only relevant when destination is "file".
+ file. Only relevant when destination is âfileâ.
- Example session
+ 16.1.3. Example session
In this example we want to set the global logging to write to the file
/var/log/my_bind10.log, at severity WARN. We want the authoritative server
@@ -1264,7 +1730,7 @@ Logging configuration
> config set Logging/loggers[0]/output_options[0]/destination file
> config set Logging/loggers[0]/output_options[0]/output /var/log/bind10.log
- > config set Logging/loggers[0]/output_options[0]/maxsize 30000
+ > config set Logging/loggers[0]/output_options[0]/maxsize 204800
> config set Logging/loggers[0]/output_options[0]/maxver 8
Which would make the entire configuration for this logger look like:
@@ -1277,7 +1743,7 @@ Logging configuration
Logging/loggers[0]/output_options[0]/destination "file" string (modified)
Logging/loggers[0]/output_options[0]/output "/var/log/bind10.log" string (modified)
Logging/loggers[0]/output_options[0]/flush false boolean (default)
- Logging/loggers[0]/output_options[0]/maxsize 30000 integer (modified)
+ Logging/loggers[0]/output_options[0]/maxsize 204800 integer (modified)
Logging/loggers[0]/output_options[0]/maxver 8 integer (modified)
That looks OK, so let's commit it before we add the configuration for the
@@ -1304,9 +1770,9 @@ Logging configuration
> config remove Logging/loggers[1]
> config commit
- And every module will now be using the values from the logger named "*".
+ And every module will now be using the values from the logger named â*â.
-Logging Message Format
+16.2. Logging Message Format
Each message written by BIND 10 to the configured logging destinations
comprises a number of components that identify the origin of the message
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index c58a97f..630c0c4 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -7,7 +7,7 @@
]>
<!--
- - Copyright (C) 2010-2011 Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2010-2012 Internet Systems Consortium, Inc. ("ISC")
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
@@ -30,7 +30,7 @@
<subtitle>Administrator Reference for BIND 10</subtitle>
<copyright>
- <year>2010-2011</year><holder>Internet Systems Consortium, Inc.</holder>
+ <year>2010-2012</year><holder>Internet Systems Consortium, Inc.</holder>
</copyright>
<abstract>
@@ -53,6 +53,20 @@
</bookinfo>
+ <preface>
+ <title>Preface</title>
+
+ <section id="acknowledgements">
+ <title>Acknowledgements</title>
+
+ <para>ISC would like to acknowledge generous support for
+ BIND 10 development of DHCPv4 and DHCPv6 components provided
+ by <ulink url="http://www.comcast.com/">Comcast</ulink>.</para>
+
+ </section>
+
+ </preface>
+
<chapter id="intro">
<title>Introduction</title>
<para>
@@ -60,29 +74,22 @@
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.
+ BIND 10 provides a EDNS0- and DNSSEC-capable authoritative
+ DNS server and a caching recursive name server which also
+ provides forwarding.
</para>
- <note>
- <para>
- This guide covers the experimental prototype of
- BIND 10 version &__VERSION__;.
- </para>
- </note>
-
- <note>
- <para>
- BIND 10 provides a EDNS0- and DNSSEC-capable
- authoritative DNS server and a caching recursive name server
- which also provides forwarding.
- </para>
- </note>
+ <para>
+ This guide covers the experimental prototype of
+ BIND 10 version &__VERSION__;.
+ </para>
<section>
<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 8, and CentOS
- Linux 5.3.
+ BIND 10 builds have been tested on Debian GNU/Linux 5 and unstable,
+ Ubuntu 9.10, NetBSD 5, Solaris 10, FreeBSD 7 and 8, CentOS
+ Linux 5.3, and MacOS 10.6.
It has been tested on Sparc, i386, and amd64 hardware
platforms.
@@ -92,33 +99,42 @@
</para>
</section>
- <section>
+ <section id="required-software">
<title>Required Software</title>
<para>
- BIND 10 requires Python 3.1. Later versions may work, but Python
- 3.1 is the minimum version which will work.
+ BIND 10 requires at least Python 3.1
+ (<ulink url="http://www.python.org/"/>).
+ It has also been tested with Python 3.2.
+ </para>
+
+ <para>
+ BIND 10 uses the Botan crypto library for C++
+ (<ulink url="http://botan.randombit.net/"/>).
+ It requires at least Botan version 1.8.
</para>
<para>
- BIND 10 uses the Botan crypto library for C++. It requires
- at least Botan version 1.8.
+ BIND 10 uses the log4cplus C++ logging library
+ (<ulink url="http://log4cplus.sourceforge.net/"/>).
+ It requires at least log4cplus version 1.0.3.
</para>
<para>
- BIND 10 uses the log4cplus C++ logging library. It requires
- at least log4cplus version 1.0.3.
+ The authoritative DNS server uses SQLite3
+ (<ulink url="http://www.sqlite.org/"/>).
+<!-- TODO: is this still required? -->
+ It needs at least SQLite version 3.3.9.
</para>
<para>
- 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.
+ and <command>b10-zonemgr</command> components require the
+ libpython3 library and the Python _sqlite3.so module
+ (which is included with Python).
+ The Python module needs to be built for the corresponding Python 3.
</para>
<!-- TODO: this will change ... -->
-<!-- TODO: list where to get these from -->
-
<note>
<para>
Some operating systems do not provide these dependencies
@@ -156,15 +172,6 @@
<listitem>
<simpara>
- <command>b10-msgq</command> —
- Message bus daemon.
- This process coordinates communication between all of the other
- BIND 10 processes.
- </simpara>
- </listitem>
-
- <listitem>
- <simpara>
<command>b10-auth</command> —
Authoritative DNS server.
This process serves DNS requests.
@@ -189,6 +196,15 @@
<listitem>
<simpara>
+ <command>b10-msgq</command> —
+ Message bus daemon.
+ This process coordinates communication between all of the other
+ BIND 10 processes.
+ </simpara>
+ </listitem>
+
+ <listitem>
+ <simpara>
<command>b10-resolver</command> —
Recursive name server.
This process handles incoming queries.
@@ -198,6 +214,15 @@
<listitem>
<simpara>
+ <command>b10-sockcreator</command> —
+ Socket creator daemon.
+ This process creates sockets used by
+ network-listening BIND 10 processes.
+ </simpara>
+ </listitem>
+
+ <listitem>
+ <simpara>
<command>b10-stats</command> —
Statistics collection daemon.
This process collects and reports statistics data.
@@ -206,6 +231,14 @@
<listitem>
<simpara>
+ <command>b10-stats-httpd</command> —
+ HTTP server for statistics reporting.
+ This process reports statistics data in XML format over HTTP.
+ </simpara>
+ </listitem>
+
+ <listitem>
+ <simpara>
<command>b10-xfrin</command> —
Incoming zone transfer service.
This process is used to transfer a new copy
@@ -253,8 +286,9 @@
<simpara>
<command>bindctl</command> —
interactive administration interface.
- This is a command-line tool which allows an administrator
- to control BIND 10.
+ This is a low-level command-line tool which allows
+ a developer or an experienced administrator to control
+ BIND 10.
</simpara>
</listitem>
<listitem>
@@ -319,7 +353,7 @@ var/
<chapter id="installation">
<title>Installation</title>
- <section>
+ <section id="build-requirements">
<title>Building Requirements</title>
<para>
@@ -338,7 +372,9 @@ var/
<para>
Building from source code requires the Boost
- build-time headers. At least Boost version 1.35 is required.
+ build-time headers
+ (<ulink url="http://www.boost.org/"/>).
+ 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>
@@ -355,16 +391,8 @@ Debian and Ubuntu:
libgmp3-dev and libbz2-dev required for botan too
-->
- <para>
-<!-- TODO: is this needed at build time? test time? -->
- The Python Library and Python _sqlite3 module are required to
- enable the Xfrout and Xfrin support.
- </para>
-
- <note><simpara>
- The Python related libraries and modules need to be built
- for Python 3.1.
- </simpara></note>
+<!-- NOTE: _sqlite3 is only needed at test time; it is already listed
+as a dependency earlier -->
<para>
Building BIND 10 also requires a C++ compiler and
@@ -372,6 +400,13 @@ Debian and Ubuntu:
BIND 10 builds have been tested with GCC g++ 3.4.3, 4.1.2,
4.1.3, 4.2.1, 4.3.2, and 4.4.1; Clang++ 2.8; and Sun C++ 5.10.
</para>
+
+ <para>
+ Visit the wiki at <ulink
+ url="http://bind10.isc.org/wiki/SystemSpecificNotes" />
+ for system-specific installation tips.
+ </para>
+
</section>
<section id="quickstart">
@@ -393,7 +428,7 @@ Debian and Ubuntu:
<listitem>
<simpara>
- Install required build dependencies.
+ Install required run-time and build dependencies.
</simpara>
</listitem>
@@ -509,10 +544,10 @@ Debian and Ubuntu:
</para>
<para>
The code can be checked out from
- <filename>git://bind10.isc.org/bind10</filename>;
+ <filename>git://git.bind10.isc.org/bind10</filename>;
for example:
- <screen>$ <userinput>git clone git://bind10.isc.org/bind10</userinput></screen>
+ <screen>$ <userinput>git clone git://git.bind10.isc.org/bind10</userinput></screen>
</para>
<para>
@@ -734,12 +769,9 @@ Debian and Ubuntu:
<para>
In its default configuration, 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,
- <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.
+ <command>b10-cmdctl</command> for administration tools to
+ communicate with the system, and
+ <command>b10-stats</command> for statistics collection.
</para>
<section id="start">
@@ -773,12 +805,7 @@ Debian and Ubuntu:
The configuration is in the Boss/components section. Each element
represents one component, which is an abstraction of a process
(currently there's also one component which doesn't represent
- a process). If you didn't want to transfer out at all (your server
- is a slave only), you would just remove the corresponding component
- from the set, like this and the process would be stopped immediately
- (and not started on the next startup):
- <screen>> <userinput>config remove Boss/components b10-xfrout</userinput>
-> <userinput>config commit</userinput></screen>
+ a process).
</para>
<para>
@@ -812,7 +839,6 @@ Debian and Ubuntu:
<row><entry>b10-auth</entry><entry>auth</entry><entry>Authoritative server</entry></row>
<row><entry>b10-resolver</entry><entry>resolver</entry><entry>The resolver</entry></row>
<row><entry>b10-cmdctl</entry><entry>cmdctl</entry><entry>The command control (remote control interface)</entry></row>
- <row><entry>setuid</entry><entry>setuid</entry><entry>Virtual component, see below</entry></row>
<!-- TODO Either add xfrin and xfrout as well or clean up the workarounds in boss before the release -->
</tbody>
</tgroup>
@@ -840,6 +866,7 @@ Debian and Ubuntu:
The priority defines order in which the components should start.
The ones with higher number are started sooner than the ones with
lower ones. If you don't set it, 0 (zero) is used as the priority.
+ Usually, leaving it at the default is enough.
</para>
<para>
@@ -872,7 +899,7 @@ address, but the usual ones don't." mean? -->
This system allows you to start the same component multiple times
(by including it in the configuration with different names, but the
same process setting). However, the rest of the system doesn't expect
- such situation, so it would probably not do what you want. Such
+ such a situation, so it would probably not do what you want. Such
support is yet to be implemented.
</para>
</note>
@@ -884,10 +911,10 @@ address, but the usual ones don't." mean? -->
<command>b10-cmdctl</command>, but then you couldn't
change it back the usual way, as it would require it to
be running (you would have to find and edit the configuration
- directly). Also, some modules might have dependencies
- -- <command>b10-stats-httpd</command> need
+ directly). Also, some modules might have dependencies:
+ <command>b10-stats-httpd</command> needs
<command>b10-stats</command>, <command>b10-xfrout</command>
- needs the <command>b10-auth</command> to be running, etc.
+ needs <command>b10-auth</command> to be running, etc.
<!-- TODO: should we define dependencies? -->
@@ -896,23 +923,24 @@ address, but the usual ones don't." mean? -->
In short, you should think twice before disabling something here.
</para>
</note>
-
<para>
- Now, to the mysterious setuid virtual component. If you
- use the <command>-u</command> option to start the
- <command>bind10</command> as root, but change the user
- later, we need to start the <command>b10-auth</command> or
- <command>b10-resolver</command> as root (until the socket
- creator is finished).<!-- TODO --> So we need to specify
- the time when the switch from root do the given user happens
- and that's what the setuid component is for. The switch is
- done at the time the setuid component would be started, if
- it was a process. The default configuration contains the
- setuid component with priority 5, <command>b10-auth</command>
- has 10 to be started before the switch and everything else
- is without priority, so it is started after the switch.
+ It is possible to start some components multiple times (currently
+ <command>b10-auth</command> and <command>b10-resolzer</command>).
+ You might want to do that to gain more performance (each one uses only
+ single core). Just put multiple entries under different names, like
+ this, with the same config:
+ <screen>> <userinput>config add Boss/components b10-resolver-2</userinput>
+> <userinput>config set Boss/components/b10-resolver-2/special resolver</userinput>
+> <userinput>config set Boss/components/b10-resolver-2/kind needed</userinput>
+> <userinput>config commit</userinput></screen>
+ </para>
+ <para>
+ However, this is work in progress and the support is not yet complete.
+ For example, each resolver will have its own cache, each authoritative
+ server will keep its own copy of in-memory data and there could be
+ problems with locking the sqlite database, if used. The configuration
+ might be changed to something more convenient in future.
</para>
-
</section>
</chapter>
@@ -981,7 +1009,7 @@ Unix domain sockets
<!-- TODO -->
<note>
<para>
- The development prototype release only provides the
+ The development prototype release only provides
<command>bindctl</command> as a user interface to
<command>b10-cmdctl</command>.
Upcoming releases will provide another interactive command-line
@@ -1172,7 +1200,7 @@ or accounts database -->
The port can be set by using the <option>--port</option> command line option.
The address to listen on can be set using the <option>--address</option> command
line argument.
- Each HTTPS connection is stateless and timesout in 1200 seconds
+ Each HTTPS connection is stateless and times out in 1200 seconds
by default. This can be
redefined by using the <option>--idle-timeout</option> command line argument.
</para>
@@ -1263,7 +1291,7 @@ since we used bind10 -->
<command>b10-auth</command> is configured via the
<command>b10-cfgmgr</command> configuration manager.
The module name is <quote>Auth</quote>.
- The configuration data item is:
+ The configuration data items are:
<variablelist>
@@ -1279,22 +1307,119 @@ This may be a temporary setting until then.
</listitem>
</varlistentry>
+<!-- NOTE: docs pulled in verbatim from the b10-auth.xml manual page.
+ TODO: automate this if want this or rewrite
+-->
+ <varlistentry>
+ <term>datasources</term>
+ <listitem>
+ <simpara>
+ <varname>datasources</varname> configures data sources.
+ The list items include:
+ <varname>type</varname> to define the required 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>
+
+ </simpara>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>listen_on</term>
+ <listitem>
+ <simpara>
+ <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.
+ </simpara>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>statistics-interval</term>
+ <listitem>
+ <simpara>
+ <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.
+ </simpara>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
<para>
- The configuration command is:
+ The configuration commands are:
<variablelist>
<varlistentry>
+ <term>loadzone</term>
+ <listitem>
+ <simpara>
+ <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>
+ </simpara>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>sendstats</term>
+ <listitem>
+ <simpara>
+ <command>sendstats</command> tells <command>b10-auth</command>
+ to send its statistics data to
+ <citerefentry><refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ immediately.
+ </simpara>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term>shutdown</term>
<listitem>
<simpara>Stop the authoritative DNS server.
+ This has an optional <varname>pid</varname> argument to
+ select the process ID to stop.
+ (Note that the BIND 10 boss process may restart this service
+ if configured.)
</simpara>
-<!-- TODO: what happens when this is sent, will bind10 restart? -->
</listitem>
</varlistentry>
@@ -1324,10 +1449,79 @@ This may be a temporary setting until then.
(The full path is what was defined at build configure time for
<option>--localstatedir</option>.
The default is <filename>/usr/local/var/</filename>.)
- This data file location may be changed by defining the
- <quote>database_file</quote> configuration.
+ This data file location may be changed by defining the
+ <quote>database_file</quote> configuration.
</para>
+ <section id="in-memory-datasource">
+ <title>In-memory Data Source</title>
+
+ <para>
+<!-- How to configure it. -->
+ The following commands to <command>bindctl</command>
+ provide an example of configuring an in-memory data
+ source containing the <quote>example.com</quote> zone
+ with the zone file named <quote>example.com.zone</quote>:
+
+<!--
+ <screen>> <userinput> config set Auth/datasources/ [{"type": "memory", "zones": [{"origin": "example.com", "file": "example.com.zone"}]}]</userinput></screen>
+-->
+
+ <screen>> <userinput>config add Auth/datasources</userinput>
+> <userinput>config set Auth/datasources[0]/type "<option>memory</option>"</userinput>
+> <userinput>config add Auth/datasources[0]/zones</userinput>
+> <userinput>config set Auth/datasources[0]/zones[0]/origin "<option>example.com</option>"</userinput>
+> <userinput>config set Auth/datasources[0]/zones[0]/file "<option>example.com.zone</option>"</userinput>
+> <userinput>config commit</userinput></screen>
+
+ The authoritative server will begin serving it immediately
+ after it is loaded.
+ </para>
+
+ <para>
+ Use the <command>Auth loadzone</command> command in
+ <command>bindctl</command> to reload a changed master
+ file into memory; for example:
+
+ <screen>> <userinput>Auth loadzone origin="example.com"</userinput>
+</screen>
+
+ </para>
+
+<!--
+ <para>
+ The <varname>file</varname> may be an absolute path to the
+ master zone file or it is relative to the directory BIND 10 is
+ started from.
+ </para>
+-->
+
+ <para>
+ By default, the memory data source is disabled; it must be
+ configured explicitly. To disable all the in-memory zones,
+ specify a null list for <varname>Auth/datasources</varname>:
+
+<!-- TODO: this assumes that Auth/datasources is for memory only -->
+
+ <screen>> <userinput>config set Auth/datasources/ []</userinput>
+> <userinput>config commit</userinput></screen>
+ </para>
+
+ <para>
+ The following example stops serving a specific zone:
+
+ <screen>> <userinput>config remove Auth/datasources[<option>0</option>]/zones[<option>0</option>]</userinput>
+> <userinput>config commit</userinput></screen>
+
+ (Replace the list number(s) in
+ <varname>datasources[<replaceable>0</replaceable>]</varname>
+ and/or <varname>zones[<replaceable>0</replaceable>]</varname>
+ for the relevant zone as needed.)
+
+ </para>
+
+ </section>
+
</section>
<section>
@@ -1335,7 +1529,7 @@ This may be a temporary setting until then.
<para>
RFC 1035 style DNS master zone files may imported
- into a BIND 10 data source by using the
+ into a BIND 10 SQLite3 data source by using the
<command>b10-loadzone</command> utility.
</para>
@@ -1382,7 +1576,7 @@ This may be a temporary setting until then.
<note>
<para>
In the development prototype release, only the SQLite3 back
- end is used.
+ end is used by <command>b10-loadzone</command>.
By default, it stores the zone data in
<filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>
unless the <option>-d</option> switch is used to set the
@@ -1611,31 +1805,23 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)</screen>
</simpara></note>
<para>
- If you want to require TSIG in access control, a separate TSIG
- "key ring" must be configured specifically
- for <command>b10-xfrout</command> as well as a system wide
- key ring, both containing a consistent set of keys.
+ If you want to require TSIG in access control, a system wide TSIG
+ "key ring" must be configured.
For example, to change the previous example to allowing requests
from 192.0.2.1 signed by a TSIG with a key name of
"key.example", you'll need to do this:
</para>
<screen>> <userinput>config set tsig_keys/keys ["key.example:<base64-key>"]</userinput>
-> <userinput>config set Xfrout/tsig_keys/keys ["key.example:<base64-key>"]</userinput>
> <userinput>config set Xfrout/zone_config[0]/transfer_acl [{"action": "ACCEPT", "from": "192.0.2.1", "key": "key.example"}]</userinput>
> <userinput>config commit</userinput></screen>
- <para>
- The first line of configuration defines a system wide key ring.
- This is necessary because the <command>b10-auth</command> server
- also checks TSIGs and it uses the system wide configuration.
- </para>
+ <para>Both Xfrout and Auth will use the system wide keyring to check
+ TSIGs in the incoming messages and to sign responses.</para>
<note><simpara>
- In a future version, <command>b10-xfrout</command> will also
- use the system wide TSIG configuration.
The way to specify zone specific configuration (ACLs, etc) is
- likely to be changed, too.
+ likely to be changed.
</simpara></note>
<!--
@@ -1665,15 +1851,10 @@ what is XfroutClient xfr_client??
<para>
The main <command>bind10</command> process can be configured
to select to run either the authoritative or resolver or both.
- By default, it starts the authoritative service.
-<!-- TODO: later both -->
-
- You may change this using <command>bindctl</command>, for example:
+ By default, it doesn't start either one. You may change this using
+ <command>bindctl</command>, for example:
<screen>
-> <userinput>config remove Boss/components b10-xfrout</userinput>
-> <userinput>config remove Boss/components b10-xfrin</userinput>
-> <userinput>config remove Boss/components b10-auth</userinput>
> <userinput>config add Boss/components b10-resolver</userinput>
> <userinput>config set Boss/components/b10-resolver/special resolver</userinput>
> <userinput>config set Boss/components/b10-resolver/kind needed</userinput>
@@ -2285,6 +2466,10 @@ eth0 fe80::21e:8cff:fe9b:7349
> <userinput>Stats show</userinput>
{
"Auth": {
+ "opcode.iquery": 0,
+ "opcode.notify": 10,
+ "opcode.query": 869617,
+ ...
"queries.tcp": 1749,
"queries.udp": 867868
},
@@ -2337,7 +2522,7 @@ eth0 fe80::21e:8cff:fe9b:7349
In the Logging module, you can specify the configuration
for zero or more loggers; any that are not specified will
- take appropriate default values..
+ take appropriate default values.
</para>
@@ -2623,34 +2808,43 @@ TODO; there's a ticket to determine these levels, see #1074
<varlistentry>
<term><option>destination</option> is <quote>console</quote></term>
<listitem>
- <simpara>
+ <para>
The value of output must be one of <quote>stdout</quote>
(messages printed to standard output) or
<quote>stderr</quote> (messages printed to standard
error).
- </simpara>
+ </para>
+ <para>
+ Note: if output is set to <quote>stderr</quote> and a lot of
+ messages are produced in a short time (e.g. if the logging
+ level is set to DEBUG), you may occasionally see some messages
+ jumbled up together. This is due to a combination of the way
+ that messages are written to the screen and the unbuffered
+ nature of the standard error stream. If this occurs, it is
+ recommended that output be set to <quote>stdout</quote>.
+ </para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>destination</option> is <quote>file</quote></term>
<listitem>
- <simpara>
+ <para>
The value of output is interpreted as a file name;
log messages will be appended to this file.
- </simpara>
+ </para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>destination</option> is <quote>syslog</quote></term>
<listitem>
- <simpara>
+ <para>
The value of output is interpreted as the
<command>syslog</command> facility (e.g.
<emphasis>local0</emphasis>) that should be used
for log messages.
- </simpara>
+ </para>
</listitem>
</varlistentry>
@@ -2831,7 +3025,7 @@ Logging/loggers[0]/output_options[0]/maxver 0 integer (default)
<screen>> <userinput> config set Logging/loggers[0]/output_options[0]/destination file</userinput>
> <userinput> config set Logging/loggers[0]/output_options[0]/output /var/log/bind10.log</userinput>
-> <userinput> config set Logging/loggers[0]/output_options[0]/maxsize 30000</userinput>
+> <userinput> config set Logging/loggers[0]/output_options[0]/maxsize 204800</userinput>
> <userinput> config set Logging/loggers[0]/output_options[0]/maxver 8</userinput>
</screen>
@@ -2854,7 +3048,7 @@ Logging/loggers[0]/additive false boolean (default)
Logging/loggers[0]/output_options[0]/destination "file" string (modified)
Logging/loggers[0]/output_options[0]/output "/var/log/bind10.log" string (modified)
Logging/loggers[0]/output_options[0]/flush false boolean (default)
-Logging/loggers[0]/output_options[0]/maxsize 30000 integer (modified)
+Logging/loggers[0]/output_options[0]/maxsize 204800 integer (modified)
Logging/loggers[0]/output_options[0]/maxver 8 integer (modified)
</screen>
@@ -3012,15 +3206,6 @@ Logging/loggers[0]/output_options[0]/maxver 8 integer (modified)
</chapter>
-<!-- TODO: acknolwedgements must be unnumbered -->
-
- <chapter>
- <title>Acknowledgements</title>
- <para>ISC would like to acknowledge generous support for
- development of DHCPv4 and DHCPv6 components provided by <ulink
- url="http://www.comcast.com">Comcast</ulink>.</para>
- </chapter>
-
<!-- TODO: Add bibliography section (mostly RFCs, probably) -->
diff --git a/doc/guide/bind10-messages.html b/doc/guide/bind10-messages.html
index f2f57f1..d3bcb7c 100644
--- a/doc/guide/bind10-messages.html
+++ b/doc/guide/bind10-messages.html
@@ -1,10 +1,10 @@
-<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>BIND 10 Messages Manual</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 messages manual for BIND 10 version 20111021. 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 Messages Manual"><div class="titlepage"><div><div><h1 class="title"><a name="id1168229451102"></a>BIND 10 Messages Manual</h1></div><div><p class="releaseinfo">This is the messages manual for BIND 10 version
- 20111021.</p></div><div><p class="copyright">Copyright © 2011 Internet Systems Consortium, Inc.</p></div><div><div class="abstract" title="Abstract"><p class="title"><b>Abstract</b></p><p>BIND 10 is a Domain Name System (DNS) suite managed by
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>BIND 10 Messages Manual</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 messages manual for BIND 10 version 20120127. 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 Messages Manual"><div class="titlepage"><div><div><h1 class="title"><a name="id1168229451102"></a>BIND 10 Messages Manual</h1></div><div><p class="releaseinfo">This is the messages manual for BIND 10 version
+ 20120127.</p></div><div><p class="copyright">Copyright © 2011-2012 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 messages manual for BIND 10 version 20111021.
+ This is the messages manual for BIND 10 version 20120127.
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>.
@@ -26,7 +26,13 @@
For information on configuring and using BIND 10 logging,
refer to the <a class="ulink" href="bind10-guide.html" target="_top">BIND 10 Guide</a>.
</p></div><div class="chapter" title="Chapter 2. BIND 10 Messages"><div class="titlepage"><div><div><h2 class="title"><a name="messages"></a>Chapter 2. BIND 10 Messages</h2></div></div></div><p>
- </p><div class="variablelist"><dl><dt><a name="ASIODNS_FETCH_COMPLETED"></a><span class="term">ASIODNS_FETCH_COMPLETED upstream fetch to %1(%2) has now completed</span></dt><dd><p>
+ </p><div class="variablelist"><dl><dt><a name="ASIODNS_FD_ADD_TCP"></a><span class="term">ASIODNS_FD_ADD_TCP adding a new TCP server by opened fd %1</span></dt><dd><p>
+A debug message informing about installing a file descriptor as a server.
+The file descriptor number is noted.
+</p></dd><dt><a name="ASIODNS_FD_ADD_UDP"></a><span class="term">ASIODNS_FD_ADD_UDP adding a new UDP server by opened fd %1</span></dt><dd><p>
+A debug message informing about installing a file descriptor as a server.
+The file descriptor number is noted.
+</p></dd><dt><a name="ASIODNS_FETCH_COMPLETED"></a><span class="term">ASIODNS_FETCH_COMPLETED upstream fetch to %1(%2) has now completed</span></dt><dd><p>
A debug message, this records that the upstream fetch (a query made by the
resolver on behalf of its client) to the specified address has completed.
</p></dd><dt><a name="ASIODNS_FETCH_STOPPED"></a><span class="term">ASIODNS_FETCH_STOPPED upstream fetch to %1(%2) has been stopped</span></dt><dd><p>
@@ -209,6 +215,9 @@ reason for the failure is included in the message.
</p></dd><dt><a name="AUTH_SERVER_STARTED"></a><span class="term">AUTH_SERVER_STARTED server started</span></dt><dd><p>
Initialization of the authoritative server has completed successfully
and it is entering the main loop, waiting for queries to arrive.
+</p></dd><dt><a name="AUTH_SHUTDOWN"></a><span class="term">AUTH_SHUTDOWN asked to stop, doing so</span></dt><dd><p>
+This is a debug message indicating the server was asked to shut down and it is
+complying to the request.
</p></dd><dt><a name="AUTH_SQLITE3"></a><span class="term">AUTH_SQLITE3 nothing to do for loading sqlite3</span></dt><dd><p>
This is a debug message indicating that the authoritative server has
found that the data source it is loading is an SQLite3 data source,
@@ -328,6 +337,11 @@ during startup, and will now kill the processes that did get started.
The boss module is sending a kill signal to process with the given name,
as part of the process of killing all started processes during a failed
startup, as described for BIND10_KILLING_ALL_PROCESSES
+</p></dd><dt><a name="BIND10_LOST_SOCKET_CONSUMER"></a><span class="term">BIND10_LOST_SOCKET_CONSUMER consumer %1 of sockets disconnected, considering all its sockets closed</span></dt><dd><p>
+A connection from one of the applications which requested a socket was
+closed. This means the application has terminated, so all the sockets it was
+using are now closed and bind10 process can release them as well, unless the
+same sockets are used by yet another application.
</p></dd><dt><a name="BIND10_MSGQ_ALREADY_RUNNING"></a><span class="term">BIND10_MSGQ_ALREADY_RUNNING msgq daemon already running, cannot start</span></dt><dd><p>
There already appears to be a message bus daemon running. Either an
old process was not shut down correctly, and needs to be killed, or
@@ -337,6 +351,10 @@ running, which needs to be stopped.
While listening on the message bus channel for messages, it suddenly
disappeared. The msgq daemon may have died. This might lead to an
inconsistent state of the system, and BIND 10 will now shut down.
+</p></dd><dt><a name="BIND10_NO_SOCKET"></a><span class="term">BIND10_NO_SOCKET couldn't send a socket for token %1 because of error: %2</span></dt><dd><p>
+An error occurred when the bind10 process was asked to send a socket file
+descriptor. The error is mentioned, most common reason is that the request
+is invalid and may not come from bind10 process at all.
</p></dd><dt><a name="BIND10_PROCESS_ENDED"></a><span class="term">BIND10_PROCESS_ENDED process %2 of %1 ended with status %3</span></dt><dd><p>
This indicates a process started previously terminated. The process id
and component owning the process are indicated, as well as the exit code.
@@ -739,6 +757,16 @@ but will not send back an answer.
</p><p>
The most likely cause of this error is a programming error. Please raise
a bug report.
+</p></dd><dt><a name="CONFIG_CCSESSION_STOPPING"></a><span class="term">CONFIG_CCSESSION_STOPPING error sending stopping message: %1</span></dt><dd><p>
+There was a problem when sending a message signaling that the module using
+this CCSession is stopping. This message is sent so that the rest of the
+system is aware that the module is no longer running. Apart from logging
+this message, the error itself is ignored, and the ModuleCCSession is
+still stopped. The specific exception message is printed.
+</p></dd><dt><a name="CONFIG_CCSESSION_STOPPING_UNKNOWN"></a><span class="term">CONFIG_CCSESSION_STOPPING_UNKNOWN unknown error sending stopping message</span></dt><dd><p>
+Similar to CONFIG_CCSESSION_STOPPING, but in this case the exception that
+is seen is not a standard exception, and further information is unknown.
+This is a bug.
</p></dd><dt><a name="CONFIG_GET_FAIL"></a><span class="term">CONFIG_GET_FAIL error getting configuration from cfgmgr: %1</span></dt><dd><p>
The configuration manager returned an error when this module requested
the configuration. The full error message answer from the configuration
@@ -791,6 +819,18 @@ manager.
</p></dd><dt><a name="CONFIG_OPEN_FAIL"></a><span class="term">CONFIG_OPEN_FAIL error opening %1: %2</span></dt><dd><p>
There was an error opening the given file. The reason for the failure
is included in the message.
+</p></dd><dt><a name="CONFIG_SESSION_STOPPING_FAILED"></a><span class="term">CONFIG_SESSION_STOPPING_FAILED error sending stopping message: %1</span></dt><dd><p>
+There was a problem when sending a message signaling that the module using
+this CCSession is stopping. This message is sent so that the rest of the
+system is aware that the module is no longer running. Apart from logging
+this message, the error itself is ignored, and the ModuleCCSession is
+still stopped. The specific exception message is printed.
+</p></dd><dt><a name="DATASRC_BAD_NSEC3_NAME"></a><span class="term">DATASRC_BAD_NSEC3_NAME NSEC3 record has a bad owner name '%1'</span></dt><dd><p>
+The software refuses to load NSEC3 records into a wildcard domain or
+the owner name has two or more labels below the zone origin.
+It isn't explicitly forbidden, but no sane zone wouldn have such names
+for NSEC3. BIND 9 also refuses NSEC3 at wildcard, so this behavior is
+compatible with BIND 9.
</p></dd><dt><a name="DATASRC_CACHE_CREATE"></a><span class="term">DATASRC_CACHE_CREATE creating the hotspot cache</span></dt><dd><p>
This is a debug message issued during startup when the hotspot cache
is created.
@@ -830,7 +870,7 @@ means no limit.
The datasource tried to provide an NSEC proof that the named domain does not
exist, but the database backend doesn't support DNSSEC. No proof is included
in the answer as a result.
-</p></dd><dt><a name="DATASRC_DATABASE_FIND_RECORDS"></a><span class="term">DATASRC_DATABASE_FIND_RECORDS looking in datasource %1 for record %2/%3</span></dt><dd><p>
+</p></dd><dt><a name="DATASRC_DATABASE_FIND_RECORDS"></a><span class="term">DATASRC_DATABASE_FIND_RECORDS looking in datasource %1 for record %2/%3/%4</span></dt><dd><p>
Debug information. The database data source is looking up records with the given
name and type in the database.
</p></dd><dt><a name="DATASRC_DATABASE_FIND_TTL_MISMATCH"></a><span class="term">DATASRC_DATABASE_FIND_TTL_MISMATCH TTL values differ in %1 for elements of %2/%3/%4, setting to %5</span></dt><dd><p>
@@ -838,10 +878,18 @@ The datasource backend provided resource records for the given RRset with
different TTL values. This isn't allowed on the wire and is considered
an error, so we set it to the lowest value we found (but we don't modify the
database). The data in database should be checked and fixed.
+</p></dd><dt><a name="DATASRC_DATABASE_FOUND_ANY"></a><span class="term">DATASRC_DATABASE_FOUND_ANY search in datasource %1 resulted in returning all records of %2</span></dt><dd><p>
+The data returned by the database backend contained data for the given domain
+name, so all the RRsets of the domain are returned.
+</p></dd><dt><a name="DATASRC_DATABASE_FOUND_CNAME"></a><span class="term">DATASRC_DATABASE_FOUND_CNAME search in datasource %1 for %2/%3/%4 found CNAME, resulting in %5</span></dt><dd><p>
+When searching the domain for a name a CNAME was found at that name.
+Even though it was not the RR type being sought, it is returned. (The
+caller may want to continue the lookup by replacing the query name with
+the canonical name and restarting the query with the original RR type.)
</p></dd><dt><a name="DATASRC_DATABASE_FOUND_DELEGATION"></a><span class="term">DATASRC_DATABASE_FOUND_DELEGATION Found delegation at %2 in %1</span></dt><dd><p>
When searching for a domain, the program met a delegation to a different zone
at the given domain name. It will return that one instead.
-</p></dd><dt><a name="DATASRC_DATABASE_FOUND_DELEGATION_EXACT"></a><span class="term">DATASRC_DATABASE_FOUND_DELEGATION_EXACT Found delegation at %2 (exact match) in %1</span></dt><dd><p>
+</p></dd><dt><a name="DATASRC_DATABASE_FOUND_DELEGATION_EXACT"></a><span class="term">DATASRC_DATABASE_FOUND_DELEGATION_EXACT search in datasource %1 for %2/%3/%4 found delegation at %5</span></dt><dd><p>
The program found the domain requested, but it is a delegation point to a
different zone, therefore it is not authoritative for this domain name.
It will return the NS record instead.
@@ -850,16 +898,21 @@ When searching for a domain, the program met a DNAME redirection to a different
place in the domain space at the given domain name. It will return that one
instead.
</p></dd><dt><a name="DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL"></a><span class="term">DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL empty non-terminal %2 in %1</span></dt><dd><p>
-The domain name doesn't have any RRs, so it doesn't exist in the database.
-However, it has a subdomain, so it exists in the DNS address space. So we
-return NXRRSET instead of NXDOMAIN.
+The domain name does not have any RRs associated with it, so it doesn't
+exist in the database. However, it has a subdomain, so it does exist
+in the DNS address space. This type of domain is known an an "empty
+non-terminal" and so we return NXRRSET instead of NXDOMAIN.
</p></dd><dt><a name="DATASRC_DATABASE_FOUND_NXDOMAIN"></a><span class="term">DATASRC_DATABASE_FOUND_NXDOMAIN search in datasource %1 resulted in NXDOMAIN for %2/%3/%4</span></dt><dd><p>
The data returned by the database backend did not contain any data for the given
domain name, class and type.
-</p></dd><dt><a name="DATASRC_DATABASE_FOUND_NXRRSET"></a><span class="term">DATASRC_DATABASE_FOUND_NXRRSET search in datasource %1 resulted in NXRRSET for %2/%3/%4</span></dt><dd><p>
+</p></dd><dt><a name="DATASRC_DATABASE_FOUND_NXRRSET"></a><span class="term">DATASRC_DATABASE_FOUND_NXRRSET search in datasource %1 for %2/%3/%4 resulted in NXRRSET</span></dt><dd><p>
The data returned by the database backend contained data for the given domain
name and class, but not for the given type.
-</p></dd><dt><a name="DATASRC_DATABASE_FOUND_RRSET"></a><span class="term">DATASRC_DATABASE_FOUND_RRSET search in datasource %1 resulted in RRset %2</span></dt><dd><p>
+</p></dd><dt><a name="DATASRC_DATABASE_FOUND_NXRRSET_NSEC"></a><span class="term">DATASRC_DATABASE_FOUND_NXRRSET_NSEC search in datasource %1 for %2/%3/%4 resulted in RRset %5</span></dt><dd><p>
+A search in the database for RRs for the specified name, type and class has
+located RRs that match the name and class but not the type. DNSSEC information
+has been requested and returned.
+</p></dd><dt><a name="DATASRC_DATABASE_FOUND_RRSET"></a><span class="term">DATASRC_DATABASE_FOUND_RRSET search in datasource %1 resulted in RRset %5</span></dt><dd><p>
The data returned by the database backend contained data for the given domain
name, and it either matches the type or has a relevant type. The RRset that is
returned is printed.
@@ -900,6 +953,9 @@ The zone's name and class, database name, and the start and end
serials, and an additional detail of the error are shown in the
message. The administrator should examine the diff in the database
to find any invalid data and fix it.
+</p></dd><dt><a name="DATASRC_DATABASE_NO_MATCH"></a><span class="term">DATASRC_DATABASE_NO_MATCH not match for %2/%3/%4 in %1</span></dt><dd><p>
+No match (not even a wildcard) was found in the named data source for the given
+name/type/class in the data source.
</p></dd><dt><a name="DATASRC_DATABASE_UPDATER_COMMIT"></a><span class="term">DATASRC_DATABASE_UPDATER_COMMIT updates committed for '%1/%2' on %3</span></dt><dd><p>
Debug information. A set of updates to a zone has been successfully
committed to the corresponding database backend. The zone name,
@@ -930,10 +986,11 @@ examine the underlying data source to see what exactly happens and
whether the data is still valid. The zone name, its class, and the
underlying database name as well as the error message thrown from the
database module are shown in the log message.
-</p></dd><dt><a name="DATASRC_DATABASE_WILDCARD"></a><span class="term">DATASRC_DATABASE_WILDCARD constructing RRset %3 from wildcard %2 in %1</span></dt><dd><p>
-The database doesn't contain directly matching domain, but it does contain a
-wildcard one which is being used to synthesize the answer.
-</p></dd><dt><a name="DATASRC_DATABASE_WILDCARD_CANCEL_NS"></a><span class="term">DATASRC_DATABASE_WILDCARD_CANCEL_NS canceled wildcard match on %2 because %3 contains NS in %1</span></dt><dd><p>
+</p></dd><dt><a name="DATASRC_DATABASE_WILDCARD_ANY"></a><span class="term">DATASRC_DATABASE_WILDCARD_ANY search in datasource %1 resulted in wildcard match type ANY on %2</span></dt><dd><p>
+The database doesn't contain directly matching name. When searching
+for a wildcard match, a wildcard record matching the name of the query
+containing some RRsets was found. All the RRsets of the node are returned.
+</p></dd><dt><a name="DATASRC_DATABASE_WILDCARD_CANCEL_NS"></a><span class="term">DATASRC_DATABASE_WILDCARD_CANCEL_NS canceled wildcard match on %3 because %2 contains NS (data source %1)</span></dt><dd><p>
The database was queried to provide glue data and it didn't find direct match.
It could create it from given wildcard, but matching wildcards is forbidden
under a zone cut, which was found. Therefore the delegation will be returned
@@ -943,11 +1000,27 @@ The answer could be constructed using the wildcard, but the given subdomain
exists, therefore this name is something like empty non-terminal (actually,
from the protocol point of view, it is empty non-terminal, but the code
discovers it differently).
-</p></dd><dt><a name="DATASRC_DATABASE_WILDCARD_EMPTY"></a><span class="term">DATASRC_DATABASE_WILDCARD_EMPTY implicit wildcard %2 used to construct %3 in %1</span></dt><dd><p>
-The given wildcard exists implicitly in the domainspace, as empty nonterminal
-(eg. there's something like subdomain.*.example.org, so *.example.org exists
-implicitly, but is empty). This will produce NXRRSET, because the constructed
-domain is empty as well as the wildcard.
+</p></dd><dt><a name="DATASRC_DATABASE_WILDCARD_CNAME"></a><span class="term">DATASRC_DATABASE_WILDCARD_CNAME search in datasource %1 for %2/%3/%4 found wildcard CNAME at %5, resulting in %6</span></dt><dd><p>
+The database doesn't contain directly matching name. When searching
+for a wildcard match, a CNAME RR was found at a wildcard record
+matching the name. This is returned as the result of the search.
+</p></dd><dt><a name="DATASRC_DATABASE_WILDCARD_EMPTY"></a><span class="term">DATASRC_DATABASE_WILDCARD_EMPTY found subdomains of %2 which is a wildcard match for %3 in %1</span></dt><dd><p>
+The given wildcard matches the name being sough but it as an empty
+nonterminal (e.g. there's nothing at *.example.com but something like
+subdomain.*.example.org, do exist: so *.example.org exists in the
+namespace but has no RRs assopciated with it). This will produce NXRRSET.
+</p></dd><dt><a name="DATASRC_DATABASE_WILDCARD_MATCH"></a><span class="term">DATASRC_DATABASE_WILDCARD_MATCH search in datasource %1 resulted in wildcard match at %5 with RRset %6</span></dt><dd><p>
+The database doesn't contain directly matching name. When searching
+for a wildcard match, a wildcard record matching the name and type of
+the query was found. The data at this point is returned.
+</p></dd><dt><a name="DATASRC_DATABASE_WILDCARD_NS"></a><span class="term">DATASRC_DATABASE_WILDCARD_NS search in datasource %1 for %2/%3/%4 found wildcard delegation at %5, resulting in %6</span></dt><dd><p>
+The database doesn't contain directly matching name. When searching
+for a wildcard match, an NS RR was found at a wildcard record matching
+the name. This is returned as the result of the search.
+</p></dd><dt><a name="DATASRC_DATABASE_WILDCARD_NXRRSET"></a><span class="term">DATASRC_DATABASE_WILDCARD_NXRRSET search in datasource %1 for %2/%3/%4 resulted in wildcard NXRRSET at %5</span></dt><dd><p>
+The database doesn't contain directly matching name. When searching
+for a wildcard match, a matching wildcard entry was found but it did
+not contain RRs the requested type. AN NXRRSET indication is returned.
</p></dd><dt><a name="DATASRC_DO_QUERY"></a><span class="term">DATASRC_DO_QUERY handling query for '%1/%2'</span></dt><dd><p>
A debug message indicating that a query for the given name and RR type is being
processed.
@@ -1002,6 +1075,26 @@ this zone is not authoritative for the requested domain, but a delegation
should be followed. The requested domain is an apex of some zone.
</p></dd><dt><a name="DATASRC_MEM_FIND"></a><span class="term">DATASRC_MEM_FIND find '%1/%2'</span></dt><dd><p>
Debug information. A search for the requested RRset is being started.
+</p></dd><dt><a name="DATASRC_MEM_FINDNSEC3"></a><span class="term">DATASRC_MEM_FINDNSEC3 finding NSEC3 for %1, mode %2</span></dt><dd><p>
+Debug information. A search in an in-memory data source for NSEC3 that
+matches or covers the given name is being started.
+</p></dd><dt><a name="DATASRC_MEM_FINDNSEC3_COVER"></a><span class="term">DATASRC_MEM_FINDNSEC3_COVER found a covering NSEC3 for %1: %2</span></dt><dd><p>
+Debug information. An NSEC3 that covers the given name is found and
+being returned. The found NSEC3 RRset is also displayed.
+</p></dd><dt><a name="DATASRC_MEM_FINDNSEC3_MATCH"></a><span class="term">DATASRC_MEM_FINDNSEC3_MATCH found a matching NSEC3 for %1 at label count %2: %3</span></dt><dd><p>
+Debug information. An NSEC3 that matches (a possibly superdomain of)
+the given name is found and being returned. When the shown label
+count is smaller than that of the given name, the matching NSEC3 is
+for a superdomain of the given name (see DATASRC_MEM_FINDNSEC3_TRYHASH).
+The found NSEC3 RRset is also displayed.
+</p></dd><dt><a name="DATASRC_MEM_FINDNSEC3_TRYHASH"></a><span class="term">DATASRC_MEM_FINDNSEC3_TRYHASH looking for NSEC3 for %1 at label count %2 (hash %3)</span></dt><dd><p>
+Debug information. In an attempt of finding an NSEC3 for the give name,
+(a possibly superdomain of) the name is hashed and searched for in the
+NSEC3 name space. When the shown label count is smaller than that of the
+shown name, the search tries the superdomain name that share the shown
+(higher) label count of the shown name (e.g., for
+www.example.com. with shown label count of 3, example.com. is being
+tried).
</p></dd><dt><a name="DATASRC_MEM_FIND_ZONE"></a><span class="term">DATASRC_MEM_FIND_ZONE looking for zone '%1'</span></dt><dd><p>
Debug information. A zone object for this zone is being searched for in the
in-memory data source.
@@ -1009,6 +1102,13 @@ in-memory data source.
Debug information. The content of master file is being loaded into the memory.
</p></dd><dt><a name="DATASRC_MEM_NOT_FOUND"></a><span class="term">DATASRC_MEM_NOT_FOUND requested domain '%1' not found</span></dt><dd><p>
Debug information. The requested domain does not exist.
+</p></dd><dt><a name="DATASRC_MEM_NO_NSEC3PARAM"></a><span class="term">DATASRC_MEM_NO_NSEC3PARAM NSEC3PARAM is missing for NSEC3-signed zone %1/%2</span></dt><dd><p>
+The in-memory data source has loaded a zone signed with NSEC3 RRs,
+but it doesn't have a NSEC3PARAM RR at the zone origin. It's likely that
+the zone is somehow broken, but this RR is not necessarily needed for
+handling lookups with NSEC3 in this data source, so it accepts the given
+content of the zone. Nevertheless the administrator should look into
+the integrity of the zone data.
</p></dd><dt><a name="DATASRC_MEM_NS_ENCOUNTERED"></a><span class="term">DATASRC_MEM_NS_ENCOUNTERED encountered a NS</span></dt><dd><p>
Debug information. While searching for the requested domain, a NS was
encountered on the way (a delegation). This may lead to stop of the search.
@@ -1029,10 +1129,12 @@ Some resource types are singletons -- only one is allowed in a domain
(for example CNAME or SOA). This indicates a problem with provided data.
</p></dd><dt><a name="DATASRC_MEM_SUCCESS"></a><span class="term">DATASRC_MEM_SUCCESS query for '%1/%2' successful</span></dt><dd><p>
Debug information. The requested record was found.
-</p></dd><dt><a name="DATASRC_MEM_SUPER_STOP"></a><span class="term">DATASRC_MEM_SUPER_STOP stopped at superdomain '%1', domain '%2' is empty</span></dt><dd><p>
-Debug information. The search stopped at a superdomain of the requested
-domain. The domain is a empty nonterminal, therefore it is treated as NXRRSET
-case (eg. the domain exists, but it doesn't have the requested record type).
+</p></dd><dt><a name="DATASRC_MEM_SUPER_STOP"></a><span class="term">DATASRC_MEM_SUPER_STOP stopped as '%1' is superdomain of a zone node, meaning it's empty</span></dt><dd><p>
+Debug information. The search stopped because the requested domain was
+detected to be a superdomain of some existing node of zone (while there
+was no exact match). This means that the domain is an empty nonterminal,
+therefore it is treated as NXRRSET case (eg. the domain exists, but it
+doesn't have the requested record type).
</p></dd><dt><a name="DATASRC_MEM_SWAP"></a><span class="term">DATASRC_MEM_SWAP swapping contents of two zone representations ('%1' and '%2')</span></dt><dd><p>
Debug information. The contents of two in-memory zones are being exchanged.
This is usual practice to do some manipulation in exception-safe manner -- the
@@ -1169,7 +1271,7 @@ to prove the nonexistence.
The underlying data source failed to answer the query for referral information.
1 means some error, 2 is not implemented. The data source should have logged
the specific error already.
-</p></dd><dt><a name="DATASRC_QUERY_RRSIG"></a><span class="term">DATASRC_QUERY_RRSIG unable to answer RRSIG query</span></dt><dd><p>
+</p></dd><dt><a name="DATASRC_QUERY_RRSIG"></a><span class="term">DATASRC_QUERY_RRSIG unable to answer RRSIG query for %1</span></dt><dd><p>
The server is unable to answer a direct query for RRSIG type, but was asked
to do so.
</p></dd><dt><a name="DATASRC_QUERY_SIMPLE_FAIL"></a><span class="term">DATASRC_QUERY_SIMPLE_FAIL the underlying data source failed with %1</span></dt><dd><p>
@@ -1287,10 +1389,66 @@ data source.
</p></dd><dt><a name="DATASRC_UNEXPECTED_QUERY_STATE"></a><span class="term">DATASRC_UNEXPECTED_QUERY_STATE unexpected query state</span></dt><dd><p>
This indicates a programming error. An internal task of unknown type was
generated.
-</p></dd><dt><a name="LIBXFRIN_DIFFERENT_TTL"></a><span class="term">LIBXFRIN_DIFFERENT_TTL multiple data with different TTLs (%1, %2) on %3/%4. Adjusting %2 -> %1.</span></dt><dd><p>
+</p></dd><dt><a name="DDNS_ACCEPT_FAILURE"></a><span class="term">DDNS_ACCEPT_FAILURE error accepting a connection: %1</span></dt><dd><p>
+There was a low-level error when we tried to accept an incoming connection
+(probably coming from b10-auth). We continue serving on whatever other
+connections we already have, but this connection is dropped. The reason
+is logged.
+</p></dd><dt><a name="DDNS_CC_SESSION_ERROR"></a><span class="term">DDNS_CC_SESSION_ERROR error reading from cc channel: %1</span></dt><dd><p>
+There was a problem reading from the command and control channel. The
+most likely cause is that the msgq process is not running.
+</p></dd><dt><a name="DDNS_CC_SESSION_TIMEOUT_ERROR"></a><span class="term">DDNS_CC_SESSION_TIMEOUT_ERROR timeout waiting for cc response</span></dt><dd><p>
+There was a problem reading a response from another module over the
+command and control channel. The most likely cause is that the
+configuration manager b10-cfgmgr is not running.
+</p></dd><dt><a name="DDNS_CONFIG_ERROR"></a><span class="term">DDNS_CONFIG_ERROR error found in configuration data: %1</span></dt><dd><p>
+The ddns process encountered an error when installing the configuration at
+startup time. Details of the error are included in the log message.
+</p></dd><dt><a name="DDNS_DROP_CONN"></a><span class="term">DDNS_DROP_CONN dropping connection on file descriptor %1 because of error %2</span></dt><dd><p>
+There was an error on a connection with the b10-auth server (or whatever
+connects to the ddns daemon). This might be OK, for example when the
+authoritative server shuts down, the connection would get closed. It also
+can mean the system is busy and can't keep up or that the other side got
+confused and sent bad data.
+</p></dd><dt><a name="DDNS_MODULECC_SESSION_ERROR"></a><span class="term">DDNS_MODULECC_SESSION_ERROR error encountered by configuration/command module: %1</span></dt><dd><p>
+There was a problem in the lower level module handling configuration and
+control commands. This could happen for various reasons, but the most likely
+cause is that the configuration database contains a syntax error and ddns
+failed to start at initialization. A detailed error message from the module
+will also be displayed.
+</p></dd><dt><a name="DDNS_NEW_CONN"></a><span class="term">DDNS_NEW_CONN new connection on file descriptor %1 from %2</span></dt><dd><p>
+Debug message. We received a connection and we are going to start handling
+requests from it. The file descriptor number and the address where the request
+comes from is logged. The connection is over a unix domain socket and is likely
+coming from a b10-auth process.
+</p></dd><dt><a name="DDNS_RECEIVED_SHUTDOWN_COMMAND"></a><span class="term">DDNS_RECEIVED_SHUTDOWN_COMMAND shutdown command received</span></dt><dd><p>
+The ddns process received a shutdown command from the command channel
+and will now shut down.
+</p></dd><dt><a name="DDNS_RUNNING"></a><span class="term">DDNS_RUNNING ddns server is running and listening for updates</span></dt><dd><p>
+The ddns process has successfully started and is now ready to receive commands
+and updates.
+</p></dd><dt><a name="DDNS_SESSION"></a><span class="term">DDNS_SESSION session arrived on file descriptor %1</span></dt><dd><p>
+A debug message, informing there's some activity on the given file descriptor.
+It will be either a request or the file descriptor will be closed. See
+following log messages to see what of it.
+</p></dd><dt><a name="DDNS_SHUTDOWN"></a><span class="term">DDNS_SHUTDOWN ddns server shutting down</span></dt><dd><p>
+The ddns process is shutting down. It will no longer listen for new commands
+or updates. Any command or update that is being addressed at this moment will
+be completed, after which the process will exit.
+</p></dd><dt><a name="DDNS_STOPPED"></a><span class="term">DDNS_STOPPED ddns server has stopped</span></dt><dd><p>
+The ddns process has successfully stopped and is no longer listening for or
+handling commands or updates, and will now exit.
+</p></dd><dt><a name="DDNS_STOPPED_BY_KEYBOARD"></a><span class="term">DDNS_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down</span></dt><dd><p>
+There was a keyboard interrupt signal to stop the ddns process. The
+process will now shut down.
+</p></dd><dt><a name="DDNS_UNCAUGHT_EXCEPTION"></a><span class="term">DDNS_UNCAUGHT_EXCEPTION uncaught exception of type %1: %2</span></dt><dd><p>
+The b10-ddns process encountered an uncaught exception and will now shut
+down. This is indicative of a programming error and should not happen under
+normal circumstances. The exception type and message are printed.
+</p></dd><dt><a name="LIBXFRIN_DIFFERENT_TTL"></a><span class="term">LIBXFRIN_DIFFERENT_TTL multiple data with different TTLs (%1, %2) on %3/%4/%5. Adjusting %2 -> %1.</span></dt><dd><p>
The xfrin module received an update containing multiple rdata changes for the
same RRset. But the TTLs of these don't match each other. As we combine them
-together, the later one get's overwritten to the earlier one in the sequence.
+together, the latter one gets overwritten to the earlier one in the sequence.
</p></dd><dt><a name="LIBXFRIN_NO_JOURNAL"></a><span class="term">LIBXFRIN_NO_JOURNAL disabled journaling for updates to %1 on %2</span></dt><dd><p>
An attempt was made to create a Diff object with journaling enabled, but
the underlying data source didn't support journaling (while still allowing
@@ -1494,6 +1652,14 @@ be sent to such a zone.
</p></dd><dt><a name="NOTIFY_OUT_ZONE_NO_NS"></a><span class="term">NOTIFY_OUT_ZONE_NO_NS Zone %1 doesn't have NS RR</span></dt><dd><p>
This is a warning issued when the notify_out module finds a zone that
doesn't have an NS RR. Notify message won't be sent to such a zone.
+</p></dd><dt><a name="NSAS_EMPTY_RESPONSE"></a><span class="term">NSAS_EMPTY_RESPONSE response to query for %1 returned an empty answer section</span></dt><dd><p>
+The NSAS (nameserver address store - part of the resolver) made a query
+for information it needed. The query completed successfully but the
+answer section in the response was empty.
+</p></dd><dt><a name="NSAS_ERROR_RESPONSE"></a><span class="term">NSAS_ERROR_RESPONSE error response of %1 returned in query for %2</span></dt><dd><p>
+The NSAS (nameserver address store - part of the resolver) made a query
+for information it needed. The query completed successfully but the
+RCODE in the response was something other than NOERROR.
</p></dd><dt><a name="NSAS_FIND_NS_ADDRESS"></a><span class="term">NSAS_FIND_NS_ADDRESS asking resolver to obtain A and AAAA records for %1</span></dt><dd><p>
A debug message issued when the NSAS (nameserver address store - part
of the resolver) is making a callback into the resolver to retrieve the
@@ -1502,16 +1668,6 @@ address records for the specified nameserver.
A debug message issued when the NSAS (nameserver address store - part
of the resolver) has retrieved the given address for the specified
nameserver through an external query.
-</p></dd><dt><a name="NSAS_INVALID_RESPONSE"></a><span class="term">NSAS_INVALID_RESPONSE queried for %1 but got invalid response</span></dt><dd><p>
-The NSAS (nameserver address store - part of the resolver) made a query
-for a RR for the specified nameserver but received an invalid response.
-Either the success function was called without a DNS message or the
-message was invalid on some way. (In the latter case, the error should
-have been picked up elsewhere in the processing logic, hence the raising
-of the error here.)
-</p><p>
-This message indicates an internal error in the NSAS. Please raise a
-bug report.
</p></dd><dt><a name="NSAS_LOOKUP_CANCEL"></a><span class="term">NSAS_LOOKUP_CANCEL lookup for zone %1 has been canceled</span></dt><dd><p>
A debug message issued when an NSAS (nameserver address store - part of
the resolver) lookup for a zone has been canceled.
@@ -1521,6 +1677,13 @@ the resolver) has been unable to retrieve the specified resource record
for the specified nameserver. This is not necessarily a problem - the
nameserver may be unreachable, in which case the NSAS will try other
nameservers in the zone.
+</p></dd><dt><a name="NSAS_NULL_RESPONSE"></a><span class="term">NSAS_NULL_RESPONSE got null message in success callback for query for %1</span></dt><dd><p>
+The NSAS (nameserver address store - part of the resolver) made a query
+for information it needed. The query completed successfully, but the
+message passed to the callback was null.
+</p><p>
+This message indicates an internal error in the NSAS. Please raise a
+bug report.
</p></dd><dt><a name="NSAS_SEARCH_ZONE_NS"></a><span class="term">NSAS_SEARCH_ZONE_NS searching NSAS for nameservers for zone %1</span></dt><dd><p>
A debug message output when a call is made to the NSAS (nameserver
address store - part of the resolver) to obtain the nameservers for
@@ -1540,26 +1703,90 @@ an answer with a different given type and class.
</p><p>
This message indicates an internal error in the NSAS. Please raise a
bug report.
+</p></dd><dt><a name="PYSERVER_COMMON_TSIG_KEYRING_DEINIT"></a><span class="term">PYSERVER_COMMON_TSIG_KEYRING_DEINIT Deinitializing global TSIG keyring</span></dt><dd><p>
+A debug message noting that the global TSIG keyring is being removed from
+memory. Most programs don't do that, they just exit, which is OK.
+</p></dd><dt><a name="PYSERVER_COMMON_TSIG_KEYRING_INIT"></a><span class="term">PYSERVER_COMMON_TSIG_KEYRING_INIT Initializing global TSIG keyring</span></dt><dd><p>
+A debug message noting the TSIG keyring storage is being prepared. It should
+appear at most once in the lifetime of a program. The keyring still needs
+to be loaded from configuration.
+</p></dd><dt><a name="PYSERVER_COMMON_TSIG_KEYRING_UPDATE"></a><span class="term">PYSERVER_COMMON_TSIG_KEYRING_UPDATE Updating global TSIG keyring</span></dt><dd><p>
+A debug message. The TSIG keyring is being (re)loaded from configuration.
+This happens at startup or when the configuration changes. The old keyring
+is removed and new one created with all the keys.
</p></dd><dt><a name="RESLIB_ANSWER"></a><span class="term">RESLIB_ANSWER answer received in response to query for <%1></span></dt><dd><p>
-A debug message recording that an answer has been received to an upstream
-query for the specified question. Previous debug messages will have indicated
-the server to which the question was sent.
+A debug message reporting that an answer has been received to an upstream
+query for the specified question. Previous debug messages will have
+indicated the server to which the question was sent.
</p></dd><dt><a name="RESLIB_CNAME"></a><span class="term">RESLIB_CNAME CNAME received in response to query for <%1></span></dt><dd><p>
-A debug message recording that CNAME response has been received to an upstream
-query for the specified question. Previous debug messages will have indicated
-the server to which the question was sent.
+A debug message recording that CNAME response has been received to an
+upstream query for the specified question. Previous debug messages will
+have indicated the server to which the question was sent.
</p></dd><dt><a name="RESLIB_DEEPEST"></a><span class="term">RESLIB_DEEPEST did not find <%1> in cache, deepest delegation found is %2</span></dt><dd><p>
-A debug message, a cache lookup did not find the specified <name, class,
-type> tuple in the cache; instead, the deepest delegation found is indicated.
+A debug message, a cache lookup did not find the specified <name,
+class, type> tuple in the cache; instead, the deepest delegation found
+is indicated.
+</p></dd><dt><a name="RESLIB_EMPTY_RESPONSE"></a><span class="term">RESLIB_EMPTY_RESPONSE empty response received to query for <%1></span></dt><dd><p>
+A debug message, the response to the specified query from an upstream
+nameserver did not contain anything in the answer or authority sections,
+although in all other respects it was a valid response. A SERVFAIL will
+be returned to the system making the original query.
+</p></dd><dt><a name="RESLIB_ERROR_RESPONSE"></a><span class="term">RESLIB_ERROR_RESPONSE unspecified error received in response to query for <%1></span></dt><dd><p>
+A debug message, the response to the specified query to an upstream
+nameserver indicated that the response was classified as an erroneous
+response, but that the nature of the error cannot be identified.
+A SERVFAIL will be returned to the system making the original query.
+</p></dd><dt><a name="RESLIB_EXTRADATA_RESPONSE"></a><span class="term">RESLIB_EXTRADATA_RESPONSE extra data in response to query for <%1></span></dt><dd><p>
+A debug message indicating that the response to the specified query
+from an upstream nameserver contained too much data. This can happen if
+an ANY query was sent and the answer section in the response contained
+multiple RRs with different names. A SERVFAIL will be returned to the
+system making the original query.
</p></dd><dt><a name="RESLIB_FOLLOW_CNAME"></a><span class="term">RESLIB_FOLLOW_CNAME following CNAME chain to <%1></span></dt><dd><p>
-A debug message, a CNAME response was received and another query is being issued
-for the <name, class, type> tuple.
+A debug message, a CNAME response was received and another query is
+being issued for the <name, class, type> tuple.
+</p></dd><dt><a name="RESLIB_INVALID_NAMECLASS_RESPONSE"></a><span class="term">RESLIB_INVALID_NAMECLASS_RESPONSE invalid name or class in response to query for <%1></span></dt><dd><p>
+A debug message, the response to the specified query from an upstream
+nameserver (as identified by the ID of the response) contained either
+an answer not matching the query name or an answer having a different
+class to that queried for. A SERVFAIL will be returned to the system
+making the original query.
+</p></dd><dt><a name="RESLIB_INVALID_QNAME_RESPONSE"></a><span class="term">RESLIB_INVALID_QNAME_RESPONSE invalid name or class in response to query for <%1></span></dt><dd><p>
+A debug message, the response to the specified query from an upstream
+nameserver (as identified by the ID of the response) contained a name
+in the question section that did not match that of the query. A SERVFAIL
+will be returned to the system making the original query.
+</p></dd><dt><a name="RESLIB_INVALID_TYPE_RESPONSE"></a><span class="term">RESLIB_INVALID_TYPE_RESPONSE invalid name or class in response to query for <%1></span></dt><dd><p>
+A debug message, the response to the specified query from an upstream
+nameserver (as identified by the ID of the response) contained an
+invalid type field. A SERVFAIL will be returned to the system making
+the original query.
</p></dd><dt><a name="RESLIB_LONG_CHAIN"></a><span class="term">RESLIB_LONG_CHAIN CNAME received in response to query for <%1>: CNAME chain length exceeded</span></dt><dd><p>
A debug message recording that a CNAME response has been received to an upstream
query for the specified question (Previous debug messages will have indicated
the server to which the question was sent). However, receipt of this CNAME
has meant that the resolver has exceeded the CNAME chain limit (a CNAME chain
is where on CNAME points to another) and so an error is being returned.
+</p></dd><dt><a name="RESLIB_MULTIPLE_CLASS_RESPONSE"></a><span class="term">RESLIB_MULTIPLE_CLASS_RESPONSE response to query for <%1> contained multiple RRsets with different classes</span></dt><dd><p>
+A debug message reporting that the response to an upstream query for
+the specified name contained multiple RRsets in the answer and not all
+were of the same class. This is a violation of the standard and so a
+SERVFAIL will be returned.
+</p></dd><dt><a name="RESLIB_NOTSINGLE_RESPONSE"></a><span class="term">RESLIB_NOTSINGLE_RESPONSE response to query for <%1> was not a response</span></dt><dd><p>
+A debug message, the response to the specified query from an upstream
+nameserver was a CNAME that had mutiple RRs in the RRset. This is
+an invalid response according to the standards so a SERVFAIL will be
+returned to the system making the original query.
+</p></dd><dt><a name="RESLIB_NOT_ONE_QNAME_RESPONSE"></a><span class="term">RESLIB_NOT_ONE_QNAME_RESPONSE not one question in response to query for <%1></span></dt><dd><p>
+A debug message, the response to the specified query from an upstream
+nameserver (as identified by the ID of the response) did not contain
+one name in the question section as required by the standard. A SERVFAIL
+will be returned to the system making the original query.
+</p></dd><dt><a name="RESLIB_NOT_RESPONSE"></a><span class="term">RESLIB_NOT_RESPONSE response to query for <%1> was not a response</span></dt><dd><p>
+A debug message, the response to the specified query from an upstream
+nameserver (as identified by the ID of the response) did not have the QR
+bit set (thus indicating that the packet was a query, not a response).
+A SERVFAIL will be returned to the system making the original query.
</p></dd><dt><a name="RESLIB_NO_NS_RRSET"></a><span class="term">RESLIB_NO_NS_RRSET no NS RRSet in referral response received to query for <%1></span></dt><dd><p>
A debug message, this indicates that a response was received for the specified
query and was categorized as a referral. However, the received message did
@@ -1572,6 +1799,12 @@ nameservers for the specified zone.
A debug message recording that either a NXDOMAIN or an NXRRSET response has
been received to an upstream query for the specified question. Previous debug
messages will have indicated the server to which the question was sent.
+</p></dd><dt><a name="RESLIB_OPCODE_RESPONSE"></a><span class="term">RESLIB_OPCODE_RESPONSE response to query for <%1> did not have query opcode</span></dt><dd><p>
+A debug message, the response to the specified query from an upstream
+nameserver was a response that did not have the opcode set to that of
+a query. According to the standards, this is an invalid response to
+the query that was made, so a SERVFAIL will be returned to the system
+making the original query.
</p></dd><dt><a name="RESLIB_PROTOCOL"></a><span class="term">RESLIB_PROTOCOL protocol error in answer for %1: %3</span></dt><dd><p>
A debug message indicating that a protocol error was received. As there
are no retries left, an error will be reported.
@@ -1579,7 +1812,7 @@ are no retries left, an error will be reported.
A debug message indicating that a protocol error was received and that
the resolver is repeating the query to the same nameserver. After this
repeated query, there will be the indicated number of retries left.
-</p></dd><dt><a name="RESLIB_RCODE_ERR"></a><span class="term">RESLIB_RCODE_ERR RCODE indicates error in response to query for <%1></span></dt><dd><p>
+</p></dd><dt><a name="RESLIB_RCODE_ERROR"></a><span class="term">RESLIB_RCODE_ERROR response to query for <%1> returns RCODE of %2</span></dt><dd><p>
A debug message, the response to the specified query indicated an error
that is not covered by a specific code path. A SERVFAIL will be returned.
</p></dd><dt><a name="RESLIB_RECQ_CACHE_FIND"></a><span class="term">RESLIB_RECQ_CACHE_FIND found <%1> in the cache (resolve() instance %2)</span></dt><dd><p>
@@ -1626,6 +1859,10 @@ called because all nameservers for the zone in question are unreachable.
A debug message indicating that a RunningQuery's success callback has been
called because a nameserver has been found, and that a query is being sent
to the specified nameserver.
+</p></dd><dt><a name="RESLIB_TCP_TRUNCATED"></a><span class="term">RESLIB_TCP_TRUNCATED TCP response to query for %1 was truncated</span></dt><dd><p>
+This is a debug message logged when a response to the specified query to an
+upstream nameserver returned a response with the TC (truncation) bit set. This
+is treated as an error by the code.
</p></dd><dt><a name="RESLIB_TEST_SERVER"></a><span class="term">RESLIB_TEST_SERVER setting test server to %1(%2)</span></dt><dd><p>
This is a warning message only generated in unit tests. It indicates
that all upstream queries from the resolver are being routed to the
@@ -1827,6 +2064,9 @@ resolver. It is output during startup and may appear multiple times,
once for each root server address.
</p></dd><dt><a name="RESOLVER_SHUTDOWN"></a><span class="term">RESOLVER_SHUTDOWN resolver shutdown complete</span></dt><dd><p>
This informational message is output when the resolver has shut down.
+</p></dd><dt><a name="RESOLVER_SHUTDOWN%20(1)"></a><span class="term">RESOLVER_SHUTDOWN (1) asked to shut down, doing so</span></dt><dd><p>
+A debug message noting that the server was asked to terminate and is
+complying to the request.
</p></dd><dt><a name="RESOLVER_STARTED"></a><span class="term">RESOLVER_STARTED resolver started</span></dt><dd><p>
This informational message is output by the resolver when all initialization
has been completed and it is entering its main loop.
@@ -1840,6 +2080,27 @@ has been ignored.
This is debug message output when the resolver received a message with an
unsupported opcode (it can only process QUERY opcodes). It will return
a message to the sender with the RCODE set to NOTIMP.
+</p></dd><dt><a name="SOCKETREQUESTOR_CREATED"></a><span class="term">SOCKETREQUESTOR_CREATED Socket requestor created for application %1</span></dt><dd><p>
+Debug message. A socket requesor (client of the socket creator) is created
+for the corresponding application. Normally this should happen at most
+one time throughout the lifetime of the application.
+</p></dd><dt><a name="SOCKETREQUESTOR_DESTROYED"></a><span class="term">SOCKETREQUESTOR_DESTROYED Socket requestor destoryed</span></dt><dd><p>
+Debug message. The socket requestor created at SOCKETREQUESTOR_CREATED
+has been destroyed. This event is generally unexpected other than in
+test cases.
+</p></dd><dt><a name="SOCKETREQUESTOR_GETSOCKET"></a><span class="term">SOCKETREQUESTOR_GETSOCKET Received a %1 socket for [%2]:%3, FD=%4, token=%5, path=%6</span></dt><dd><p>
+Debug message. The socket requestor for the corresponding application
+has requested a socket for a set of address, port and protocol (shown
+in the log message) and successfully got it from the creator. The
+corresponding file descriptor and the associated "token" (an internal
+ID used between the creator and requestor) are shown in the log
+message.
+</p></dd><dt><a name="SOCKETREQUESTOR_RELEASESOCKET"></a><span class="term">SOCKETREQUESTOR_RELEASESOCKET Released a socket of token %1</span></dt><dd><p>
+Debug message. The socket requestor has released a socket passed by
+the creator. The associated token of the socket is shown in the
+log message. If the corresponding SOCKETREQUESTOR_GETSOCKET was logged
+more detailed information of the socket can be identified by matching
+the token.
</p></dd><dt><a name="SRVCOMM_ADDRESSES_NOT_LIST"></a><span class="term">SRVCOMM_ADDRESSES_NOT_LIST the address and port specification is not a list in %1</span></dt><dd><p>
This points to an error in configuration. What was supposed to be a list of
IP address - port pairs isn't a list at all but something else.
@@ -1858,7 +2119,7 @@ configuration malformed. The specification causing the error is given in the
message. A valid specification contains an address part (which must be a string
and must represent a valid IPv4 or IPv6 address) and port (which must be an
integer in the range valid for TCP/UDP ports on your system).
-</p></dd><dt><a name="SRVCOMM_ADDRESS_UNRECOVERABLE"></a><span class="term">SRVCOMM_ADDRESS_UNRECOVERABLE failed to recover original addresses also (%2)</span></dt><dd><p>
+</p></dd><dt><a name="SRVCOMM_ADDRESS_UNRECOVERABLE"></a><span class="term">SRVCOMM_ADDRESS_UNRECOVERABLE failed to recover original addresses also (%1)</span></dt><dd><p>
The recovery of old addresses after SRVCOMM_ADDRESS_FAIL also failed for
the reason listed.
</p><p>
@@ -1871,6 +2132,16 @@ Debug message. This lists one address and port value of the set of
addresses we are going to listen on (eg. there will be one log message
per pair). This appears only after SRVCOMM_SET_LISTEN, but might
be hidden, as it has higher debug level.
+</p></dd><dt><a name="SRVCOMM_EXCEPTION_ALLOC"></a><span class="term">SRVCOMM_EXCEPTION_ALLOC exception when allocating a socket: %1</span></dt><dd><p>
+The process tried to allocate a socket using the socket creator, but an error
+occurred. But it is not one of the errors we are sure are "safe". In this case
+it is unclear if the unsuccessful communication left the process and the bind10
+process in inconsistent state, so the process is going to abort to prevent
+further problems in that area.
+</p><p>
+This is probably a bug in the code, but it could be caused by other unusual
+conditions (like insufficient memory, deleted socket file used for
+communication).
</p></dd><dt><a name="SRVCOMM_KEYS_DEINIT"></a><span class="term">SRVCOMM_KEYS_DEINIT deinitializing TSIG keyring</span></dt><dd><p>
Debug message indicating that the server is deinitializing the TSIG keyring.
</p></dd><dt><a name="SRVCOMM_KEYS_INIT"></a><span class="term">SRVCOMM_KEYS_INIT initializing TSIG keyring</span></dt><dd><p>
@@ -1885,6 +2156,10 @@ specification is outside the valid range of 0 to 65535.
</p></dd><dt><a name="SRVCOMM_SET_LISTEN"></a><span class="term">SRVCOMM_SET_LISTEN setting addresses to listen to</span></dt><dd><p>
Debug message, noting that the server is about to start listening on a
different set of IP addresses and ports than before.
+</p></dd><dt><a name="SRVCOMM_UNKNOWN_EXCEPTION_ALLOC"></a><span class="term">SRVCOMM_UNKNOWN_EXCEPTION_ALLOC unknown exception when allocating a socket</span></dt><dd><p>
+The situation is the same as in the SRVCOMM_EXCEPTION_ALLOC case, but further
+details about the error are unknown, because it was signaled by throwing
+something not being an exception. This is definitely a bug.
</p></dd><dt><a name="STATHTTPD_BAD_OPTION_VALUE"></a><span class="term">STATHTTPD_BAD_OPTION_VALUE bad command line argument: %1</span></dt><dd><p>
The stats-httpd module was called with a bad command-line argument
and will not start.
@@ -1995,10 +2270,6 @@ is unknown in the implementation. The most likely cause is an
installation problem, where the specification file stats.spec is
from a different version of BIND 10 than the stats module itself.
Please check your installation.
-</p></dd><dt><a name="XFRIN_AXFR_DATABASE_FAILURE"></a><span class="term">XFRIN_AXFR_DATABASE_FAILURE AXFR transfer of zone %1 failed: %2</span></dt><dd><p>
-The AXFR transfer for the given zone has failed due to a database problem.
-The error is shown in the log message. Note: due to the code structure
-this can only happen for AXFR.
</p></dd><dt><a name="XFRIN_AXFR_INCONSISTENT_SOA"></a><span class="term">XFRIN_AXFR_INCONSISTENT_SOA AXFR SOAs are inconsistent for %1: %2 expected, %3 received</span></dt><dd><p>
The serial fields of the first and last SOAs of AXFR (including AXFR-style
IXFR) are not the same. According to RFC 5936 these two SOAs must be the
@@ -2046,6 +2317,15 @@ is not equal to the requested SOA serial.
</p></dd><dt><a name="XFRIN_IMPORT_DNS"></a><span class="term">XFRIN_IMPORT_DNS error importing python DNS module: %1</span></dt><dd><p>
There was an error importing the python DNS module pydnspp. The most
likely cause is a PYTHONPATH problem.
+</p></dd><dt><a name="XFRIN_IXFR_UPTODATE"></a><span class="term">XFRIN_IXFR_UPTODATE IXFR requested serial for %1 is %2, master has %3, not updating</span></dt><dd><p>
+The first SOA record in an IXFR response indicates the zone's serial
+at the primary server is not newer than the client's. This is
+basically unexpected event because normally the client first checks
+the SOA serial by an SOA query, but can still happen if the transfer
+is manually invoked or (although unlikely) there is a rapid change at
+the primary server between the SOA and IXFR queries. The client
+implementation confirms the whole response is this single SOA, and
+aborts the transfer just like a successful case.
</p></dd><dt><a name="XFRIN_MSGQ_SEND_ERROR"></a><span class="term">XFRIN_MSGQ_SEND_ERROR error while contacting %1 and %2</span></dt><dd><p>
There was a problem sending a message to the xfrout module or the
zone manager. This most likely means that the msgq daemon has quit or
@@ -2089,19 +2369,64 @@ message continuously appears, system resource consumption should be
checked, and you may even want to disable the corresponding transfers.
You may also want to file a bug report if this message appears so
often.
-</p></dd><dt><a name="XFRIN_XFR_TRANSFER_FAILURE"></a><span class="term">XFRIN_XFR_TRANSFER_FAILURE %1 transfer of zone %2 failed: %3</span></dt><dd><p>
-The XFR transfer for the given zone has failed due to a protocol error.
+</p></dd><dt><a name="XFRIN_XFR_TRANSFER_FAILURE"></a><span class="term">XFRIN_XFR_TRANSFER_FAILURE %1 transfer of zone %2 with %3 failed: %4</span></dt><dd><p>
+The XFR transfer for the given zone has failed due to an internal error.
The error is shown in the log message.
</p></dd><dt><a name="XFRIN_XFR_TRANSFER_FALLBACK"></a><span class="term">XFRIN_XFR_TRANSFER_FALLBACK falling back from IXFR to AXFR for %1</span></dt><dd><p>
The IXFR transfer of the given zone failed. This might happen in many cases,
such that the remote server doesn't support IXFR, we don't have the SOA record
(or the zone at all), we are out of sync, etc. In many of these situations,
AXFR could still work. Therefore we try that one in case it helps.
+</p></dd><dt><a name="XFRIN_XFR_TRANSFER_PROTOCOL_ERROR"></a><span class="term">XFRIN_XFR_TRANSFER_PROTOCOL_ERROR %1 transfer of zone %2 with %3 failed: %4</span></dt><dd><p>
+The XFR transfer for the given zone has failed due to a protocol
+error, such as an unexpected response from the primary server. The
+error is shown in the log message. It may be because the primary
+server implementation is broken or (although less likely) there was
+some attack attempt, but it can also happen due to configuration
+mismatch such as the remote server does not have authority for the
+zone any more but the local configuration hasn't been updated. So it
+is recommended to check the primary server configuration.
</p></dd><dt><a name="XFRIN_XFR_TRANSFER_STARTED"></a><span class="term">XFRIN_XFR_TRANSFER_STARTED %1 transfer of zone %2 started</span></dt><dd><p>
A connection to the master server has been made, the serial value in
the SOA record has been checked, and a zone transfer has been started.
</p></dd><dt><a name="XFRIN_XFR_TRANSFER_SUCCESS"></a><span class="term">XFRIN_XFR_TRANSFER_SUCCESS %1 transfer of zone %2 succeeded</span></dt><dd><p>
The XFR transfer of the given zone was successfully completed.
+</p></dd><dt><a name="XFRIN_ZONE_CREATED"></a><span class="term">XFRIN_ZONE_CREATED Zone %1 not found in the given data source, newly created</span></dt><dd><p>
+On starting an xfrin session, it is identified that the zone to be
+transferred is not found in the data source. This can happen if a
+secondary DNS server first tries to perform AXFR from a primary server
+without creating the zone image beforehand (e.g. by b10-loadzone). As
+of this writing the xfrin process provides backward compatible
+behavior to previous versions: creating a new one in the data source
+not to surprise existing users too much. This is probably not a good
+idea, however, in terms of who should be responsible for managing
+zones at a higher level. In future it is more likely that a separate
+zone management framework is provided, and the situation where the
+given zone isn't found in xfrout will be treated as an error.
+</p></dd><dt><a name="XFRIN_ZONE_MULTIPLE_SOA"></a><span class="term">XFRIN_ZONE_MULTIPLE_SOA Zone %1 has %2 SOA RRs</span></dt><dd><p>
+On starting an xfrin session, it is identified that the zone to be
+transferred has multiple SOA RRs. Such a zone is broken, but could be
+accidentally configured especially in a data source using "non
+captive" backend database. The implementation ignores entire SOA RRs
+and tries to continue processing as if the zone were empty. This
+means subsequent AXFR can succeed and possibly replace the zone with
+valid content, but an IXFR attempt will fail.
+</p></dd><dt><a name="XFRIN_ZONE_NO_SOA"></a><span class="term">XFRIN_ZONE_NO_SOA Zone %1 does not have SOA</span></dt><dd><p>
+On starting an xfrin session, it is identified that the zone to be
+transferred does not have an SOA RR in the data source. This is not
+necessarily an error; if a secondary DNS server first tries to perform
+transfer from a primary server, the zone can be empty, and therefore
+doesn't have an SOA. Subsequent AXFR will fill in the zone; if the
+attempt is IXFR it will fail in query creation.
+</p></dd><dt><a name="XFRIN_ZONE_SERIAL_AHEAD"></a><span class="term">XFRIN_ZONE_SERIAL_AHEAD Serial number (%1) for %2 received from master %3 < ours (%4)</span></dt><dd><p>
+The response to an SOA query prior to xfr indicated that the zone's
+SOA serial at the primary server is smaller than that of the xfrin
+client. This is not necessarily an error especially if that
+particular primary server is another secondary server which hasn't got
+the latest version of the zone. But if the primary server is known to
+be the real source of the zone, some unexpected inconsistency may have
+happened, and you may want to take a closer look. In this case xfrin
+doesn't perform subsequent zone transfer.
</p></dd><dt><a name="XFROUT_BAD_TSIG_KEY_STRING"></a><span class="term">XFROUT_BAD_TSIG_KEY_STRING bad TSIG key string: %1</span></dt><dd><p>
The TSIG key string as read from the configuration does not represent
a valid TSIG key.
diff --git a/doc/guide/bind10-messages.xml b/doc/guide/bind10-messages.xml
index 4dc02d4..60f9665 100644
--- a/doc/guide/bind10-messages.xml
+++ b/doc/guide/bind10-messages.xml
@@ -18,7 +18,7 @@
<title>BIND 10 Messages Manual</title>
<copyright>
- <year>2011</year><holder>Internet Systems Consortium, Inc.</holder>
+ <year>2011-2012</year><holder>Internet Systems Consortium, Inc.</holder>
</copyright>
<abstract>
@@ -68,6 +68,22 @@
<para>
<variablelist>
+<varlistentry id="ASIODNS_FD_ADD_TCP">
+<term>ASIODNS_FD_ADD_TCP adding a new TCP server by opened fd %1</term>
+<listitem><para>
+A debug message informing about installing a file descriptor as a server.
+The file descriptor number is noted.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="ASIODNS_FD_ADD_UDP">
+<term>ASIODNS_FD_ADD_UDP adding a new UDP server by opened fd %1</term>
+<listitem><para>
+A debug message informing about installing a file descriptor as a server.
+The file descriptor number is noted.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="ASIODNS_FETCH_COMPLETED">
<term>ASIODNS_FETCH_COMPLETED upstream fetch to %1(%2) has now completed</term>
<listitem><para>
@@ -389,6 +405,27 @@ message associated with it has its own code.
</para></listitem>
</varlistentry>
+<varlistentry id="AUTH_RESPONSE_FAILURE">
+<term>AUTH_RESPONSE_FAILURE exception while building response to query: %1</term>
+<listitem><para>
+This is a debug message, generated by the authoritative server when an
+attempt to create a response to a received DNS packet has failed. The
+reason for the failure is given in the log message. A SERVFAIL response
+is sent back. The most likely cause of this is an error in the data
+source implementation; it is either creating bad responses or raising
+exceptions itself.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="AUTH_RESPONSE_FAILURE_UNKNOWN">
+<term>AUTH_RESPONSE_FAILURE_UNKNOWN unknown exception while building response to query</term>
+<listitem><para>
+This debug message is similar to AUTH_RESPONSE_FAILURE, but further
+details about the error are unknown, because it was signaled by something
+which is not an exception. This is definitely a bug.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="AUTH_RESPONSE_RECEIVED">
<term>AUTH_RESPONSE_RECEIVED received response message, ignoring</term>
<listitem><para>
@@ -451,6 +488,14 @@ and it is entering the main loop, waiting for queries to arrive.
</para></listitem>
</varlistentry>
+<varlistentry id="AUTH_SHUTDOWN">
+<term>AUTH_SHUTDOWN asked to stop, doing so</term>
+<listitem><para>
+This is a debug message indicating the server was asked to shut down and it is
+complying to the request.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="AUTH_SQLITE3">
<term>AUTH_SQLITE3 nothing to do for loading sqlite3</term>
<listitem><para>
@@ -574,7 +619,7 @@ needs a dedicated message bus.
</varlistentry>
<varlistentry id="BIND10_COMPONENT_FAILED">
-<term>BIND10_COMPONENT_FAILED component %1 (pid %2) failed with %3 exit status</term>
+<term>BIND10_COMPONENT_FAILED component %1 (pid %2) failed: %3</term>
<listitem><para>
The process terminated, but the bind10 boss didn't expect it to, which means
it must have failed.
@@ -720,6 +765,16 @@ startup, as described for BIND10_KILLING_ALL_PROCESSES
</para></listitem>
</varlistentry>
+<varlistentry id="BIND10_LOST_SOCKET_CONSUMER">
+<term>BIND10_LOST_SOCKET_CONSUMER consumer %1 of sockets disconnected, considering all its sockets closed</term>
+<listitem><para>
+A connection from one of the applications which requested a socket was
+closed. This means the application has terminated, so all the sockets it was
+using are now closed and bind10 process can release them as well, unless the
+same sockets are used by yet another application.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="BIND10_MSGQ_ALREADY_RUNNING">
<term>BIND10_MSGQ_ALREADY_RUNNING msgq daemon already running, cannot start</term>
<listitem><para>
@@ -739,6 +794,15 @@ inconsistent state of the system, and BIND 10 will now shut down.
</para></listitem>
</varlistentry>
+<varlistentry id="BIND10_NO_SOCKET">
+<term>BIND10_NO_SOCKET couldn't send a socket for token %1 because of error: %2</term>
+<listitem><para>
+An error occurred when the bind10 process was asked to send a socket file
+descriptor. The error is mentioned, most common reason is that the request
+is invalid and may not come from bind10 process at all.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="BIND10_PROCESS_ENDED">
<term>BIND10_PROCESS_ENDED process %2 of %1 ended with status %3</term>
<listitem><para>
@@ -1575,6 +1639,15 @@ configuration is not stored.
</para></listitem>
</varlistentry>
+<varlistentry id="CFGMGR_RENAMED_CONFIG_FILE">
+<term>CFGMGR_RENAMED_CONFIG_FILE renamed configuration file %1 to %2, will create new %1</term>
+<listitem><para>
+BIND 10 has been started with the command to clear the configuration file.
+The existing file is backed up to the given file name, so that data is not
+immediately lost if this was done by accident.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="CFGMGR_STOPPED_BY_KEYBOARD">
<term>CFGMGR_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down</term>
<listitem><para>
@@ -1731,6 +1804,26 @@ a bug report.
</para></listitem>
</varlistentry>
+<varlistentry id="CONFIG_CCSESSION_STOPPING">
+<term>CONFIG_CCSESSION_STOPPING error sending stopping message: %1</term>
+<listitem><para>
+There was a problem when sending a message signaling that the module using
+this CCSession is stopping. This message is sent so that the rest of the
+system is aware that the module is no longer running. Apart from logging
+this message, the error itself is ignored, and the ModuleCCSession is
+still stopped. The specific exception message is printed.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="CONFIG_CCSESSION_STOPPING_UNKNOWN">
+<term>CONFIG_CCSESSION_STOPPING_UNKNOWN unknown error sending stopping message</term>
+<listitem><para>
+Similar to CONFIG_CCSESSION_STOPPING, but in this case the exception that
+is seen is not a standard exception, and further information is unknown.
+This is a bug.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="CONFIG_GET_FAIL">
<term>CONFIG_GET_FAIL error getting configuration from cfgmgr: %1</term>
<listitem><para>
@@ -1838,6 +1931,28 @@ is included in the message.
</para></listitem>
</varlistentry>
+<varlistentry id="CONFIG_SESSION_STOPPING_FAILED">
+<term>CONFIG_SESSION_STOPPING_FAILED error sending stopping message: %1</term>
+<listitem><para>
+There was a problem when sending a message signaling that the module using
+this CCSession is stopping. This message is sent so that the rest of the
+system is aware that the module is no longer running. Apart from logging
+this message, the error itself is ignored, and the ModuleCCSession is
+still stopped. The specific exception message is printed.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DATASRC_BAD_NSEC3_NAME">
+<term>DATASRC_BAD_NSEC3_NAME NSEC3 record has a bad owner name '%1'</term>
+<listitem><para>
+The software refuses to load NSEC3 records into a wildcard domain or
+the owner name has two or more labels below the zone origin.
+It isn't explicitly forbidden, but no sane zone wouldn have such names
+for NSEC3. BIND 9 also refuses NSEC3 at wildcard, so this behavior is
+compatible with BIND 9.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="DATASRC_CACHE_CREATE">
<term>DATASRC_CACHE_CREATE creating the hotspot cache</term>
<listitem><para>
@@ -1943,7 +2058,7 @@ in the answer as a result.
</varlistentry>
<varlistentry id="DATASRC_DATABASE_FIND_RECORDS">
-<term>DATASRC_DATABASE_FIND_RECORDS looking in datasource %1 for record %2/%3</term>
+<term>DATASRC_DATABASE_FIND_RECORDS looking in datasource %1 for record %2/%3/%4</term>
<listitem><para>
Debug information. The database data source is looking up records with the given
name and type in the database.
@@ -1960,6 +2075,24 @@ database). The data in database should be checked and fixed.
</para></listitem>
</varlistentry>
+<varlistentry id="DATASRC_DATABASE_FOUND_ANY">
+<term>DATASRC_DATABASE_FOUND_ANY search in datasource %1 resulted in returning all records of %2</term>
+<listitem><para>
+The data returned by the database backend contained data for the given domain
+name, so all the RRsets of the domain are returned.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DATASRC_DATABASE_FOUND_CNAME">
+<term>DATASRC_DATABASE_FOUND_CNAME search in datasource %1 for %2/%3/%4 found CNAME, resulting in %5</term>
+<listitem><para>
+When searching the domain for a name a CNAME was found at that name.
+Even though it was not the RR type being sought, it is returned. (The
+caller may want to continue the lookup by replacing the query name with
+the canonical name and restarting the query with the original RR type.)
+</para></listitem>
+</varlistentry>
+
<varlistentry id="DATASRC_DATABASE_FOUND_DELEGATION">
<term>DATASRC_DATABASE_FOUND_DELEGATION Found delegation at %2 in %1</term>
<listitem><para>
@@ -1969,7 +2102,7 @@ at the given domain name. It will return that one instead.
</varlistentry>
<varlistentry id="DATASRC_DATABASE_FOUND_DELEGATION_EXACT">
-<term>DATASRC_DATABASE_FOUND_DELEGATION_EXACT Found delegation at %2 (exact match) in %1</term>
+<term>DATASRC_DATABASE_FOUND_DELEGATION_EXACT search in datasource %1 for %2/%3/%4 found delegation at %5</term>
<listitem><para>
The program found the domain requested, but it is a delegation point to a
different zone, therefore it is not authoritative for this domain name.
@@ -1989,9 +2122,10 @@ instead.
<varlistentry id="DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL">
<term>DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL empty non-terminal %2 in %1</term>
<listitem><para>
-The domain name doesn't have any RRs, so it doesn't exist in the database.
-However, it has a subdomain, so it exists in the DNS address space. So we
-return NXRRSET instead of NXDOMAIN.
+The domain name does not have any RRs associated with it, so it doesn't
+exist in the database. However, it has a subdomain, so it does exist
+in the DNS address space. This type of domain is known an an "empty
+non-terminal" and so we return NXRRSET instead of NXDOMAIN.
</para></listitem>
</varlistentry>
@@ -2004,15 +2138,24 @@ domain name, class and type.
</varlistentry>
<varlistentry id="DATASRC_DATABASE_FOUND_NXRRSET">
-<term>DATASRC_DATABASE_FOUND_NXRRSET search in datasource %1 resulted in NXRRSET for %2/%3/%4</term>
+<term>DATASRC_DATABASE_FOUND_NXRRSET search in datasource %1 for %2/%3/%4 resulted in NXRRSET</term>
<listitem><para>
The data returned by the database backend contained data for the given domain
name and class, but not for the given type.
</para></listitem>
</varlistentry>
+<varlistentry id="DATASRC_DATABASE_FOUND_NXRRSET_NSEC">
+<term>DATASRC_DATABASE_FOUND_NXRRSET_NSEC search in datasource %1 for %2/%3/%4 resulted in RRset %5</term>
+<listitem><para>
+A search in the database for RRs for the specified name, type and class has
+located RRs that match the name and class but not the type. DNSSEC information
+has been requested and returned.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="DATASRC_DATABASE_FOUND_RRSET">
-<term>DATASRC_DATABASE_FOUND_RRSET search in datasource %1 resulted in RRset %2</term>
+<term>DATASRC_DATABASE_FOUND_RRSET search in datasource %1 resulted in RRset %5</term>
<listitem><para>
The data returned by the database backend contained data for the given domain
name, and it either matches the type or has a relevant type. The RRset that is
@@ -2097,6 +2240,14 @@ to find any invalid data and fix it.
</para></listitem>
</varlistentry>
+<varlistentry id="DATASRC_DATABASE_NO_MATCH">
+<term>DATASRC_DATABASE_NO_MATCH not match for %2/%3/%4 in %1</term>
+<listitem><para>
+No match (not even a wildcard) was found in the named data source for the given
+name/type/class in the data source.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="DATASRC_DATABASE_UPDATER_COMMIT">
<term>DATASRC_DATABASE_UPDATER_COMMIT updates committed for '%1/%2' on %3</term>
<listitem><para>
@@ -2152,16 +2303,17 @@ database module are shown in the log message.
</para></listitem>
</varlistentry>
-<varlistentry id="DATASRC_DATABASE_WILDCARD">
-<term>DATASRC_DATABASE_WILDCARD constructing RRset %3 from wildcard %2 in %1</term>
+<varlistentry id="DATASRC_DATABASE_WILDCARD_ANY">
+<term>DATASRC_DATABASE_WILDCARD_ANY search in datasource %1 resulted in wildcard match type ANY on %2</term>
<listitem><para>
-The database doesn't contain directly matching domain, but it does contain a
-wildcard one which is being used to synthesize the answer.
+The database doesn't contain directly matching name. When searching
+for a wildcard match, a wildcard record matching the name of the query
+containing some RRsets was found. All the RRsets of the node are returned.
</para></listitem>
</varlistentry>
<varlistentry id="DATASRC_DATABASE_WILDCARD_CANCEL_NS">
-<term>DATASRC_DATABASE_WILDCARD_CANCEL_NS canceled wildcard match on %2 because %3 contains NS in %1</term>
+<term>DATASRC_DATABASE_WILDCARD_CANCEL_NS canceled wildcard match on %3 because %2 contains NS (data source %1)</term>
<listitem><para>
The database was queried to provide glue data and it didn't find direct match.
It could create it from given wildcard, but matching wildcards is forbidden
@@ -2180,13 +2332,49 @@ discovers it differently).
</para></listitem>
</varlistentry>
+<varlistentry id="DATASRC_DATABASE_WILDCARD_CNAME">
+<term>DATASRC_DATABASE_WILDCARD_CNAME search in datasource %1 for %2/%3/%4 found wildcard CNAME at %5, resulting in %6</term>
+<listitem><para>
+The database doesn't contain directly matching name. When searching
+for a wildcard match, a CNAME RR was found at a wildcard record
+matching the name. This is returned as the result of the search.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="DATASRC_DATABASE_WILDCARD_EMPTY">
-<term>DATASRC_DATABASE_WILDCARD_EMPTY implicit wildcard %2 used to construct %3 in %1</term>
+<term>DATASRC_DATABASE_WILDCARD_EMPTY found subdomains of %2 which is a wildcard match for %3 in %1</term>
+<listitem><para>
+The given wildcard matches the name being sough but it as an empty
+nonterminal (e.g. there's nothing at *.example.com but something like
+subdomain.*.example.org, do exist: so *.example.org exists in the
+namespace but has no RRs assopciated with it). This will produce NXRRSET.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DATASRC_DATABASE_WILDCARD_MATCH">
+<term>DATASRC_DATABASE_WILDCARD_MATCH search in datasource %1 resulted in wildcard match at %5 with RRset %6</term>
+<listitem><para>
+The database doesn't contain directly matching name. When searching
+for a wildcard match, a wildcard record matching the name and type of
+the query was found. The data at this point is returned.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DATASRC_DATABASE_WILDCARD_NS">
+<term>DATASRC_DATABASE_WILDCARD_NS search in datasource %1 for %2/%3/%4 found wildcard delegation at %5, resulting in %6</term>
+<listitem><para>
+The database doesn't contain directly matching name. When searching
+for a wildcard match, an NS RR was found at a wildcard record matching
+the name. This is returned as the result of the search.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DATASRC_DATABASE_WILDCARD_NXRRSET">
+<term>DATASRC_DATABASE_WILDCARD_NXRRSET search in datasource %1 for %2/%3/%4 resulted in wildcard NXRRSET at %5</term>
<listitem><para>
-The given wildcard exists implicitly in the domainspace, as empty nonterminal
-(eg. there's something like subdomain.*.example.org, so *.example.org exists
-implicitly, but is empty). This will produce NXRRSET, because the constructed
-domain is empty as well as the wildcard.
+The database doesn't contain directly matching name. When searching
+for a wildcard match, a matching wildcard entry was found but it did
+not contain RRs the requested type. AN NXRRSET indication is returned.
</para></listitem>
</varlistentry>
@@ -2334,6 +2522,46 @@ Debug information. A search for the requested RRset is being started.
</para></listitem>
</varlistentry>
+<varlistentry id="DATASRC_MEM_FINDNSEC3">
+<term>DATASRC_MEM_FINDNSEC3 finding NSEC3 for %1, mode %2</term>
+<listitem><para>
+Debug information. A search in an in-memory data source for NSEC3 that
+matches or covers the given name is being started.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DATASRC_MEM_FINDNSEC3_COVER">
+<term>DATASRC_MEM_FINDNSEC3_COVER found a covering NSEC3 for %1: %2</term>
+<listitem><para>
+Debug information. An NSEC3 that covers the given name is found and
+being returned. The found NSEC3 RRset is also displayed.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DATASRC_MEM_FINDNSEC3_MATCH">
+<term>DATASRC_MEM_FINDNSEC3_MATCH found a matching NSEC3 for %1 at label count %2: %3</term>
+<listitem><para>
+Debug information. An NSEC3 that matches (a possibly superdomain of)
+the given name is found and being returned. When the shown label
+count is smaller than that of the given name, the matching NSEC3 is
+for a superdomain of the given name (see DATASRC_MEM_FINDNSEC3_TRYHASH).
+The found NSEC3 RRset is also displayed.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DATASRC_MEM_FINDNSEC3_TRYHASH">
+<term>DATASRC_MEM_FINDNSEC3_TRYHASH looking for NSEC3 for %1 at label count %2 (hash %3)</term>
+<listitem><para>
+Debug information. In an attempt of finding an NSEC3 for the give name,
+(a possibly superdomain of) the name is hashed and searched for in the
+NSEC3 name space. When the shown label count is smaller than that of the
+shown name, the search tries the superdomain name that share the shown
+(higher) label count of the shown name (e.g., for
+www.example.com. with shown label count of 3, example.com. is being
+tried).
+</para></listitem>
+</varlistentry>
+
<varlistentry id="DATASRC_MEM_FIND_ZONE">
<term>DATASRC_MEM_FIND_ZONE looking for zone '%1'</term>
<listitem><para>
@@ -2356,6 +2584,18 @@ Debug information. The requested domain does not exist.
</para></listitem>
</varlistentry>
+<varlistentry id="DATASRC_MEM_NO_NSEC3PARAM">
+<term>DATASRC_MEM_NO_NSEC3PARAM NSEC3PARAM is missing for NSEC3-signed zone %1/%2</term>
+<listitem><para>
+The in-memory data source has loaded a zone signed with NSEC3 RRs,
+but it doesn't have a NSEC3PARAM RR at the zone origin. It's likely that
+the zone is somehow broken, but this RR is not necessarily needed for
+handling lookups with NSEC3 in this data source, so it accepts the given
+content of the zone. Nevertheless the administrator should look into
+the integrity of the zone data.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="DATASRC_MEM_NS_ENCOUNTERED">
<term>DATASRC_MEM_NS_ENCOUNTERED encountered a NS</term>
<listitem><para>
@@ -2407,11 +2647,13 @@ Debug information. The requested record was found.
</varlistentry>
<varlistentry id="DATASRC_MEM_SUPER_STOP">
-<term>DATASRC_MEM_SUPER_STOP stopped at superdomain '%1', domain '%2' is empty</term>
+<term>DATASRC_MEM_SUPER_STOP stopped as '%1' is superdomain of a zone node, meaning it's empty</term>
<listitem><para>
-Debug information. The search stopped at a superdomain of the requested
-domain. The domain is a empty nonterminal, therefore it is treated as NXRRSET
-case (eg. the domain exists, but it doesn't have the requested record type).
+Debug information. The search stopped because the requested domain was
+detected to be a superdomain of some existing node of zone (while there
+was no exact match). This means that the domain is an empty nonterminal,
+therefore it is treated as NXRRSET case (eg. the domain exists, but it
+doesn't have the requested record type).
</para></listitem>
</varlistentry>
@@ -2731,8 +2973,10 @@ not have any DS record. This indicates problem with the provided data.
<varlistentry id="DATASRC_QUERY_NO_ZONE">
<term>DATASRC_QUERY_NO_ZONE no zone containing '%1' in class '%2'</term>
<listitem><para>
-Lookup of domain failed because the data have no zone that contain the
-domain. Maybe someone sent a query to the wrong server for some reason.
+Debug information. Lookup of domain failed because the datasource
+has no zone that contains the domain. Maybe someone sent a query
+to the wrong server for some reason. This may also happen when
+looking in the datasource for addresses for NS records.
</para></listitem>
</varlistentry>
@@ -2762,7 +3006,7 @@ the specific error already.
</varlistentry>
<varlistentry id="DATASRC_QUERY_RRSIG">
-<term>DATASRC_QUERY_RRSIG unable to answer RRSIG query</term>
+<term>DATASRC_QUERY_RRSIG unable to answer RRSIG query for %1</term>
<listitem><para>
The server is unable to answer a direct query for RRSIG type, but was asked
to do so.
@@ -3069,12 +3313,332 @@ generated.
</para></listitem>
</varlistentry>
+<varlistentry id="DBUTIL_BACKUP">
+<term>DBUTIL_BACKUP created backup of %1 in %2</term>
+<listitem><para>
+A backup for the given database file was created. Same of original file and
+backup are given in the output message.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_CHECK_ERROR">
+<term>DBUTIL_CHECK_ERROR unable to check database version: %1</term>
+<listitem><para>
+There was an error while trying to check the current version of the database
+schema. The error is shown in the message.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_CHECK_NOCONFIRM">
+<term>DBUTIL_CHECK_NOCONFIRM --noconfirm is not compatible with --check</term>
+<listitem><para>
+b10-dbutil was called with --check and --noconfirm. --noconfirm only has
+meaning with --upgrade, so this is considered an error.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_CHECK_OK">
+<term>DBUTIL_CHECK_OK this is the latest version of the database schema. No upgrade is required</term>
+<listitem><para>
+The database schema version has been checked, and is up to date.
+No action is required.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_CHECK_UPGRADE_NEEDED">
+<term>DBUTIL_CHECK_UPGRADE_NEEDED re-run this program with the --upgrade switch to upgrade</term>
+<listitem><para>
+The database schema version is not up to date, and an update is required.
+Please run the dbutil tool again, with the --upgrade argument.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_COMMAND_NONE">
+<term>DBUTIL_COMMAND_NONE must select one of --check or --upgrade</term>
+<listitem><para>
+b10-dbutil was called with neither --check nor --upgrade. One action must be
+provided.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_COMMAND_UPGRADE_CHECK">
+<term>DBUTIL_COMMAND_UPGRADE_CHECK --upgrade is not compatible with --check</term>
+<listitem><para>
+b10-dbutil was called with both the commands --upgrade and --check. Only one
+action can be performed at a time.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_DATABASE_MAY_BE_CORRUPT">
+<term>DBUTIL_DATABASE_MAY_BE_CORRUPT database file %1 may be corrupt, restore it from backup (%2)</term>
+<listitem><para>
+The upgrade failed while it was in progress; the database may now be in an
+inconsistent state, and it is advised to restore it from the backup that was
+created when b10-dbutil started.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_EXECUTE">
+<term>DBUTIL_EXECUTE Executing SQL statement: %1</term>
+<listitem><para>
+Debug message; the given SQL statement is executed
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_FILE">
+<term>DBUTIL_FILE Database file: %1</term>
+<listitem><para>
+The database file that is being checked.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_NO_FILE">
+<term>DBUTIL_NO_FILE must supply name of the database file to upgrade</term>
+<listitem><para>
+b10-dbutil was called without a database file. Currently, it cannot find this
+file on its own, and it must be provided.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_STATEMENT_ERROR">
+<term>DBUTIL_STATEMENT_ERROR failed to execute %1: %2</term>
+<listitem><para>
+The given database statement failed to execute. The error is shown in the
+message.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_TOO_MANY_ARGUMENTS">
+<term>DBUTIL_TOO_MANY_ARGUMENTS too many arguments to the command, maximum of one expected</term>
+<listitem><para>
+There were too many command-line arguments to b10-dbutil
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_UPGRADE_CANCELED">
+<term>DBUTIL_UPGRADE_CANCELED upgrade canceled; database has not been changed</term>
+<listitem><para>
+The user aborted the upgrade, and b10-dbutil will now exit.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_UPGRADE_DBUTIL">
+<term>DBUTIL_UPGRADE_DBUTIL please get the latest version of b10-dbutil and re-run</term>
+<listitem><para>
+A database schema was found that was newer than this version of dbutil, which
+is apparently out of date and should be upgraded itself.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_UPGRADE_FAILED">
+<term>DBUTIL_UPGRADE_FAILED upgrade failed: %1</term>
+<listitem><para>
+While the upgrade was in progress, an unexpected error occurred. The error
+is shown in the message.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_UPGRADE_NOT_ATTEMPTED">
+<term>DBUTIL_UPGRADE_NOT_ATTEMPTED database upgrade was not attempted</term>
+<listitem><para>
+Due to the earlier failure, the database schema upgrade was not attempted,
+and b10-dbutil will now exit.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_UPGRADE_NOT_NEEDED">
+<term>DBUTIL_UPGRADE_NOT_NEEDED database already at latest version, no upgrade necessary</term>
+<listitem><para>
+b10-dbutil was told to upgrade the database schema, but it is already at the
+latest version.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_UPGRADE_NOT_POSSIBLE">
+<term>DBUTIL_UPGRADE_NOT_POSSIBLE database at a later version than this utility can support</term>
+<listitem><para>
+b10-dbutil was told to upgrade the database schema, but it is at a higher
+version than this tool currently supports. Please update b10-dbutil and try
+again.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_UPGRADE_PREPARATION_FAILED">
+<term>DBUTIL_UPGRADE_PREPARATION_FAILED upgrade preparation failed: %1</term>
+<listitem><para>
+An unexpected error occurred while b10-dbutil was preparing to upgrade the
+database schema. The error is shown in the message
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_UPGRADE_SUCCESFUL">
+<term>DBUTIL_UPGRADE_SUCCESFUL database upgrade successfully completed</term>
+<listitem><para>
+The database schema update was completed successfully.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_UPGRADING">
+<term>DBUTIL_UPGRADING upgrading database from %1 to %2</term>
+<listitem><para>
+An upgrade is in progress, the versions of the current upgrade action are shown.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_VERSION_CURRENT">
+<term>DBUTIL_VERSION_CURRENT database version %1</term>
+<listitem><para>
+The current version of the database schema.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_VERSION_HIGH">
+<term>DBUTIL_VERSION_HIGH database is at a later version (%1) than this program can cope with (%2)</term>
+<listitem><para>
+The database schema is at a higher version than b10-dbutil knows about.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DBUTIL_VERSION_LOW">
+<term>DBUTIL_VERSION_LOW database version %1, latest version is %2.</term>
+<listitem><para>
+The database schema is not up to date, the current version and the latest
+version are in the message.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DDNS_ACCEPT_FAILURE">
+<term>DDNS_ACCEPT_FAILURE error accepting a connection: %1</term>
+<listitem><para>
+There was a low-level error when we tried to accept an incoming connection
+(probably coming from b10-auth). We continue serving on whatever other
+connections we already have, but this connection is dropped. The reason
+is logged.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DDNS_CC_SESSION_ERROR">
+<term>DDNS_CC_SESSION_ERROR error reading from cc channel: %1</term>
+<listitem><para>
+There was a problem reading from the command and control channel. The
+most likely cause is that the msgq process is not running.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DDNS_CC_SESSION_TIMEOUT_ERROR">
+<term>DDNS_CC_SESSION_TIMEOUT_ERROR timeout waiting for cc response</term>
+<listitem><para>
+There was a problem reading a response from another module over the
+command and control channel. The most likely cause is that the
+configuration manager b10-cfgmgr is not running.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DDNS_CONFIG_ERROR">
+<term>DDNS_CONFIG_ERROR error found in configuration data: %1</term>
+<listitem><para>
+The ddns process encountered an error when installing the configuration at
+startup time. Details of the error are included in the log message.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DDNS_DROP_CONN">
+<term>DDNS_DROP_CONN dropping connection on file descriptor %1 because of error %2</term>
+<listitem><para>
+There was an error on a connection with the b10-auth server (or whatever
+connects to the ddns daemon). This might be OK, for example when the
+authoritative server shuts down, the connection would get closed. It also
+can mean the system is busy and can't keep up or that the other side got
+confused and sent bad data.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DDNS_MODULECC_SESSION_ERROR">
+<term>DDNS_MODULECC_SESSION_ERROR error encountered by configuration/command module: %1</term>
+<listitem><para>
+There was a problem in the lower level module handling configuration and
+control commands. This could happen for various reasons, but the most likely
+cause is that the configuration database contains a syntax error and ddns
+failed to start at initialization. A detailed error message from the module
+will also be displayed.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DDNS_NEW_CONN">
+<term>DDNS_NEW_CONN new connection on file descriptor %1 from %2</term>
+<listitem><para>
+Debug message. We received a connection and we are going to start handling
+requests from it. The file descriptor number and the address where the request
+comes from is logged. The connection is over a unix domain socket and is likely
+coming from a b10-auth process.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DDNS_RECEIVED_SHUTDOWN_COMMAND">
+<term>DDNS_RECEIVED_SHUTDOWN_COMMAND shutdown command received</term>
+<listitem><para>
+The ddns process received a shutdown command from the command channel
+and will now shut down.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DDNS_RUNNING">
+<term>DDNS_RUNNING ddns server is running and listening for updates</term>
+<listitem><para>
+The ddns process has successfully started and is now ready to receive commands
+and updates.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DDNS_SESSION">
+<term>DDNS_SESSION session arrived on file descriptor %1</term>
+<listitem><para>
+A debug message, informing there's some activity on the given file descriptor.
+It will be either a request or the file descriptor will be closed. See
+following log messages to see what of it.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DDNS_SHUTDOWN">
+<term>DDNS_SHUTDOWN ddns server shutting down</term>
+<listitem><para>
+The ddns process is shutting down. It will no longer listen for new commands
+or updates. Any command or update that is being addressed at this moment will
+be completed, after which the process will exit.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DDNS_STOPPED">
+<term>DDNS_STOPPED ddns server has stopped</term>
+<listitem><para>
+The ddns process has successfully stopped and is no longer listening for or
+handling commands or updates, and will now exit.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DDNS_STOPPED_BY_KEYBOARD">
+<term>DDNS_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down</term>
+<listitem><para>
+There was a keyboard interrupt signal to stop the ddns process. The
+process will now shut down.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DDNS_UNCAUGHT_EXCEPTION">
+<term>DDNS_UNCAUGHT_EXCEPTION uncaught exception of type %1: %2</term>
+<listitem><para>
+The b10-ddns process encountered an uncaught exception and will now shut
+down. This is indicative of a programming error and should not happen under
+normal circumstances. The exception type and message are printed.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="LIBXFRIN_DIFFERENT_TTL">
-<term>LIBXFRIN_DIFFERENT_TTL multiple data with different TTLs (%1, %2) on %3/%4. Adjusting %2 -> %1.</term>
+<term>LIBXFRIN_DIFFERENT_TTL multiple data with different TTLs (%1, %2) on %3/%4/%5. Adjusting %2 -> %1.</term>
<listitem><para>
The xfrin module received an update containing multiple rdata changes for the
same RRset. But the TTLs of these don't match each other. As we combine them
-together, the later one get's overwritten to the earlier one in the sequence.
+together, the latter one gets overwritten to the earlier one in the sequence.
</para></listitem>
</varlistentry>
@@ -3476,6 +4040,24 @@ doesn't have an NS RR. Notify message won't be sent to such a zone.
</para></listitem>
</varlistentry>
+<varlistentry id="NSAS_EMPTY_RESPONSE">
+<term>NSAS_EMPTY_RESPONSE response to query for %1 returned an empty answer section</term>
+<listitem><para>
+The NSAS (nameserver address store - part of the resolver) made a query
+for information it needed. The query completed successfully but the
+answer section in the response was empty.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="NSAS_ERROR_RESPONSE">
+<term>NSAS_ERROR_RESPONSE error response of %1 returned in query for %2</term>
+<listitem><para>
+The NSAS (nameserver address store - part of the resolver) made a query
+for information it needed. The query completed successfully but the
+RCODE in the response was something other than NOERROR.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="NSAS_FIND_NS_ADDRESS">
<term>NSAS_FIND_NS_ADDRESS asking resolver to obtain A and AAAA records for %1</term>
<listitem><para>
@@ -3494,21 +4076,6 @@ nameserver through an external query.
</para></listitem>
</varlistentry>
-<varlistentry id="NSAS_INVALID_RESPONSE">
-<term>NSAS_INVALID_RESPONSE queried for %1 but got invalid response</term>
-<listitem><para>
-The NSAS (nameserver address store - part of the resolver) made a query
-for a RR for the specified nameserver but received an invalid response.
-Either the success function was called without a DNS message or the
-message was invalid on some way. (In the latter case, the error should
-have been picked up elsewhere in the processing logic, hence the raising
-of the error here.)
-</para><para>
-This message indicates an internal error in the NSAS. Please raise a
-bug report.
-</para></listitem>
-</varlistentry>
-
<varlistentry id="NSAS_LOOKUP_CANCEL">
<term>NSAS_LOOKUP_CANCEL lookup for zone %1 has been canceled</term>
<listitem><para>
@@ -3528,6 +4095,18 @@ nameservers in the zone.
</para></listitem>
</varlistentry>
+<varlistentry id="NSAS_NULL_RESPONSE">
+<term>NSAS_NULL_RESPONSE got null message in success callback for query for %1</term>
+<listitem><para>
+The NSAS (nameserver address store - part of the resolver) made a query
+for information it needed. The query completed successfully, but the
+message passed to the callback was null.
+</para><para>
+This message indicates an internal error in the NSAS. Please raise a
+bug report.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="NSAS_SEARCH_ZONE_NS">
<term>NSAS_SEARCH_ZONE_NS searching NSAS for nameservers for zone %1</term>
<listitem><para>
@@ -3562,37 +4141,126 @@ bug report.
</para></listitem>
</varlistentry>
+<varlistentry id="PYSERVER_COMMON_TSIG_KEYRING_DEINIT">
+<term>PYSERVER_COMMON_TSIG_KEYRING_DEINIT Deinitializing global TSIG keyring</term>
+<listitem><para>
+A debug message noting that the global TSIG keyring is being removed from
+memory. Most programs don't do that, they just exit, which is OK.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="PYSERVER_COMMON_TSIG_KEYRING_INIT">
+<term>PYSERVER_COMMON_TSIG_KEYRING_INIT Initializing global TSIG keyring</term>
+<listitem><para>
+A debug message noting the TSIG keyring storage is being prepared. It should
+appear at most once in the lifetime of a program. The keyring still needs
+to be loaded from configuration.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="PYSERVER_COMMON_TSIG_KEYRING_UPDATE">
+<term>PYSERVER_COMMON_TSIG_KEYRING_UPDATE Updating global TSIG keyring</term>
+<listitem><para>
+A debug message. The TSIG keyring is being (re)loaded from configuration.
+This happens at startup or when the configuration changes. The old keyring
+is removed and new one created with all the keys.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="RESLIB_ANSWER">
<term>RESLIB_ANSWER answer received in response to query for <%1></term>
<listitem><para>
-A debug message recording that an answer has been received to an upstream
-query for the specified question. Previous debug messages will have indicated
-the server to which the question was sent.
+A debug message reporting that an answer has been received to an upstream
+query for the specified question. Previous debug messages will have
+indicated the server to which the question was sent.
</para></listitem>
</varlistentry>
<varlistentry id="RESLIB_CNAME">
<term>RESLIB_CNAME CNAME received in response to query for <%1></term>
<listitem><para>
-A debug message recording that CNAME response has been received to an upstream
-query for the specified question. Previous debug messages will have indicated
-the server to which the question was sent.
+A debug message recording that CNAME response has been received to an
+upstream query for the specified question. Previous debug messages will
+have indicated the server to which the question was sent.
</para></listitem>
</varlistentry>
<varlistentry id="RESLIB_DEEPEST">
<term>RESLIB_DEEPEST did not find <%1> in cache, deepest delegation found is %2</term>
<listitem><para>
-A debug message, a cache lookup did not find the specified <name, class,
-type> tuple in the cache; instead, the deepest delegation found is indicated.
+A debug message, a cache lookup did not find the specified <name,
+class, type> tuple in the cache; instead, the deepest delegation found
+is indicated.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="RESLIB_EMPTY_RESPONSE">
+<term>RESLIB_EMPTY_RESPONSE empty response received to query for <%1></term>
+<listitem><para>
+A debug message, the response to the specified query from an upstream
+nameserver did not contain anything in the answer or authority sections,
+although in all other respects it was a valid response. A SERVFAIL will
+be returned to the system making the original query.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="RESLIB_ERROR_RESPONSE">
+<term>RESLIB_ERROR_RESPONSE unspecified error received in response to query for <%1></term>
+<listitem><para>
+A debug message, the response to the specified query to an upstream
+nameserver indicated that the response was classified as an erroneous
+response, but that the nature of the error cannot be identified.
+A SERVFAIL will be returned to the system making the original query.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="RESLIB_EXTRADATA_RESPONSE">
+<term>RESLIB_EXTRADATA_RESPONSE extra data in response to query for <%1></term>
+<listitem><para>
+A debug message indicating that the response to the specified query
+from an upstream nameserver contained too much data. This can happen if
+an ANY query was sent and the answer section in the response contained
+multiple RRs with different names. A SERVFAIL will be returned to the
+system making the original query.
</para></listitem>
</varlistentry>
<varlistentry id="RESLIB_FOLLOW_CNAME">
<term>RESLIB_FOLLOW_CNAME following CNAME chain to <%1></term>
<listitem><para>
-A debug message, a CNAME response was received and another query is being issued
-for the <name, class, type> tuple.
+A debug message, a CNAME response was received and another query is
+being issued for the <name, class, type> tuple.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="RESLIB_INVALID_NAMECLASS_RESPONSE">
+<term>RESLIB_INVALID_NAMECLASS_RESPONSE invalid name or class in response to query for <%1></term>
+<listitem><para>
+A debug message, the response to the specified query from an upstream
+nameserver (as identified by the ID of the response) contained either
+an answer not matching the query name or an answer having a different
+class to that queried for. A SERVFAIL will be returned to the system
+making the original query.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="RESLIB_INVALID_QNAME_RESPONSE">
+<term>RESLIB_INVALID_QNAME_RESPONSE invalid name or class in response to query for <%1></term>
+<listitem><para>
+A debug message, the response to the specified query from an upstream
+nameserver (as identified by the ID of the response) contained a name
+in the question section that did not match that of the query. A SERVFAIL
+will be returned to the system making the original query.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="RESLIB_INVALID_TYPE_RESPONSE">
+<term>RESLIB_INVALID_TYPE_RESPONSE invalid name or class in response to query for <%1></term>
+<listitem><para>
+A debug message, the response to the specified query from an upstream
+nameserver (as identified by the ID of the response) contained an
+invalid type field. A SERVFAIL will be returned to the system making
+the original query.
</para></listitem>
</varlistentry>
@@ -3607,6 +4275,46 @@ is where on CNAME points to another) and so an error is being returned.
</para></listitem>
</varlistentry>
+<varlistentry id="RESLIB_MULTIPLE_CLASS_RESPONSE">
+<term>RESLIB_MULTIPLE_CLASS_RESPONSE response to query for <%1> contained multiple RRsets with different classes</term>
+<listitem><para>
+A debug message reporting that the response to an upstream query for
+the specified name contained multiple RRsets in the answer and not all
+were of the same class. This is a violation of the standard and so a
+SERVFAIL will be returned.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="RESLIB_NOTSINGLE_RESPONSE">
+<term>RESLIB_NOTSINGLE_RESPONSE response to query for <%1> was not a response</term>
+<listitem><para>
+A debug message, the response to the specified query from an upstream
+nameserver was a CNAME that had mutiple RRs in the RRset. This is
+an invalid response according to the standards so a SERVFAIL will be
+returned to the system making the original query.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="RESLIB_NOT_ONE_QNAME_RESPONSE">
+<term>RESLIB_NOT_ONE_QNAME_RESPONSE not one question in response to query for <%1></term>
+<listitem><para>
+A debug message, the response to the specified query from an upstream
+nameserver (as identified by the ID of the response) did not contain
+one name in the question section as required by the standard. A SERVFAIL
+will be returned to the system making the original query.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="RESLIB_NOT_RESPONSE">
+<term>RESLIB_NOT_RESPONSE response to query for <%1> was not a response</term>
+<listitem><para>
+A debug message, the response to the specified query from an upstream
+nameserver (as identified by the ID of the response) did not have the QR
+bit set (thus indicating that the packet was a query, not a response).
+A SERVFAIL will be returned to the system making the original query.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="RESLIB_NO_NS_RRSET">
<term>RESLIB_NO_NS_RRSET no NS RRSet in referral response received to query for <%1></term>
<listitem><para>
@@ -3634,6 +4342,17 @@ messages will have indicated the server to which the question was sent.
</para></listitem>
</varlistentry>
+<varlistentry id="RESLIB_OPCODE_RESPONSE">
+<term>RESLIB_OPCODE_RESPONSE response to query for <%1> did not have query opcode</term>
+<listitem><para>
+A debug message, the response to the specified query from an upstream
+nameserver was a response that did not have the opcode set to that of
+a query. According to the standards, this is an invalid response to
+the query that was made, so a SERVFAIL will be returned to the system
+making the original query.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="RESLIB_PROTOCOL">
<term>RESLIB_PROTOCOL protocol error in answer for %1: %3</term>
<listitem><para>
@@ -3651,8 +4370,8 @@ repeated query, there will be the indicated number of retries left.
</para></listitem>
</varlistentry>
-<varlistentry id="RESLIB_RCODE_ERR">
-<term>RESLIB_RCODE_ERR RCODE indicates error in response to query for <%1></term>
+<varlistentry id="RESLIB_RCODE_ERROR">
+<term>RESLIB_RCODE_ERROR response to query for <%1> returns RCODE of %2</term>
<listitem><para>
A debug message, the response to the specified query indicated an error
that is not covered by a specific code path. A SERVFAIL will be returned.
@@ -3758,6 +4477,15 @@ to the specified nameserver.
</para></listitem>
</varlistentry>
+<varlistentry id="RESLIB_TCP_TRUNCATED">
+<term>RESLIB_TCP_TRUNCATED TCP response to query for %1 was truncated</term>
+<listitem><para>
+This is a debug message logged when a response to the specified query to an
+upstream nameserver returned a response with the TC (truncation) bit set. This
+is treated as an error by the code.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="RESLIB_TEST_SERVER">
<term>RESLIB_TEST_SERVER setting test server to %1(%2)</term>
<listitem><para>
@@ -4184,6 +4912,14 @@ This informational message is output when the resolver has shut down.
</para></listitem>
</varlistentry>
+<varlistentry id="RESOLVER_SHUTDOWN_RECEIVED">
+<term>RESOLVER_SHUTDOWN_RECEIVED received command to shut down</term>
+<listitem><para>
+A debug message noting that the server was asked to terminate and is
+complying to the request.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="RESOLVER_STARTED">
<term>RESOLVER_STARTED resolver started</term>
<listitem><para>
@@ -4217,6 +4953,47 @@ a message to the sender with the RCODE set to NOTIMP.
</para></listitem>
</varlistentry>
+<varlistentry id="SOCKETREQUESTOR_CREATED">
+<term>SOCKETREQUESTOR_CREATED Socket requestor created for application %1</term>
+<listitem><para>
+Debug message. A socket requesor (client of the socket creator) is created
+for the corresponding application. Normally this should happen at most
+one time throughout the lifetime of the application.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="SOCKETREQUESTOR_DESTROYED">
+<term>SOCKETREQUESTOR_DESTROYED Socket requestor destoryed</term>
+<listitem><para>
+Debug message. The socket requestor created at SOCKETREQUESTOR_CREATED
+has been destroyed. This event is generally unexpected other than in
+test cases.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="SOCKETREQUESTOR_GETSOCKET">
+<term>SOCKETREQUESTOR_GETSOCKET Received a %1 socket for [%2]:%3, FD=%4, token=%5, path=%6</term>
+<listitem><para>
+Debug message. The socket requestor for the corresponding application
+has requested a socket for a set of address, port and protocol (shown
+in the log message) and successfully got it from the creator. The
+corresponding file descriptor and the associated "token" (an internal
+ID used between the creator and requestor) are shown in the log
+message.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="SOCKETREQUESTOR_RELEASESOCKET">
+<term>SOCKETREQUESTOR_RELEASESOCKET Released a socket of token %1</term>
+<listitem><para>
+Debug message. The socket requestor has released a socket passed by
+the creator. The associated token of the socket is shown in the
+log message. If the corresponding SOCKETREQUESTOR_GETSOCKET was logged
+more detailed information of the socket can be identified by matching
+the token.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="SRVCOMM_ADDRESSES_NOT_LIST">
<term>SRVCOMM_ADDRESSES_NOT_LIST the address and port specification is not a list in %1</term>
<listitem><para>
@@ -4256,7 +5033,7 @@ integer in the range valid for TCP/UDP ports on your system).
</varlistentry>
<varlistentry id="SRVCOMM_ADDRESS_UNRECOVERABLE">
-<term>SRVCOMM_ADDRESS_UNRECOVERABLE failed to recover original addresses also (%2)</term>
+<term>SRVCOMM_ADDRESS_UNRECOVERABLE failed to recover original addresses also (%1)</term>
<listitem><para>
The recovery of old addresses after SRVCOMM_ADDRESS_FAIL also failed for
the reason listed.
@@ -4278,6 +5055,21 @@ be hidden, as it has higher debug level.
</para></listitem>
</varlistentry>
+<varlistentry id="SRVCOMM_EXCEPTION_ALLOC">
+<term>SRVCOMM_EXCEPTION_ALLOC exception when allocating a socket: %1</term>
+<listitem><para>
+The process tried to allocate a socket using the socket creator, but an error
+occurred. But it is not one of the errors we are sure are "safe". In this case
+it is unclear if the unsuccessful communication left the process and the bind10
+process in inconsistent state, so the process is going to abort to prevent
+further problems in that area.
+</para><para>
+This is probably a bug in the code, but it could be caused by other unusual
+conditions (like insufficient memory, deleted socket file used for
+communication).
+</para></listitem>
+</varlistentry>
+
<varlistentry id="SRVCOMM_KEYS_DEINIT">
<term>SRVCOMM_KEYS_DEINIT deinitializing TSIG keyring</term>
<listitem><para>
@@ -4317,6 +5109,15 @@ different set of IP addresses and ports than before.
</para></listitem>
</varlistentry>
+<varlistentry id="SRVCOMM_UNKNOWN_EXCEPTION_ALLOC">
+<term>SRVCOMM_UNKNOWN_EXCEPTION_ALLOC unknown exception when allocating a socket</term>
+<listitem><para>
+The situation is the same as in the SRVCOMM_EXCEPTION_ALLOC case, but further
+details about the error are unknown, because it was signaled by throwing
+something not being an exception. This is definitely a bug.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="STATHTTPD_BAD_OPTION_VALUE">
<term>STATHTTPD_BAD_OPTION_VALUE bad command line argument: %1</term>
<listitem><para>
@@ -4587,15 +5388,6 @@ Please check your installation.
</para></listitem>
</varlistentry>
-<varlistentry id="XFRIN_AXFR_DATABASE_FAILURE">
-<term>XFRIN_AXFR_DATABASE_FAILURE AXFR transfer of zone %1 failed: %2</term>
-<listitem><para>
-The AXFR transfer for the given zone has failed due to a database problem.
-The error is shown in the log message. Note: due to the code structure
-this can only happen for AXFR.
-</para></listitem>
-</varlistentry>
-
<varlistentry id="XFRIN_AXFR_INCONSISTENT_SOA">
<term>XFRIN_AXFR_INCONSISTENT_SOA AXFR SOAs are inconsistent for %1: %2 expected, %3 received</term>
<listitem><para>
@@ -4698,6 +5490,49 @@ likely cause is a PYTHONPATH problem.
</para></listitem>
</varlistentry>
+<varlistentry id="XFRIN_IXFR_TRANSFER_SUCCESS">
+<term>XFRIN_IXFR_TRANSFER_SUCCESS incremental IXFR transfer of zone %1 succeeded (messages: %2, changesets: %3, deletions: %4, additions: %5, bytes: %6, run time: %7 seconds, %8 bytes/second)</term>
+<listitem><para>
+The IXFR transfer for the given zone was successful.
+The provided information contains the following values:
+</para><para>
+messages: Number of overhead DNS messages in the transfer.
+</para><para>
+changesets: Number of difference sequences.
+</para><para>
+deletions: Number of Resource Records deleted by all the changesets combined,
+including the SOA records.
+</para><para>
+additions: Number of Resource Records added by all the changesets combined,
+including the SOA records.
+</para><para>
+bytes: Full size of the transfer data on the wire.
+</para><para>
+run time: Time (in seconds) the complete ixfr took.
+</para><para>
+bytes/second: Transfer speed.
+</para><para>
+Note that there is no cross-checking of additions and deletions; if the same
+RR gets added and deleted in multiple changesets, it is counted each time;
+therefore, for each changeset, there should at least be 1 deletion and 1
+addition (the updated SOA record).
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="XFRIN_IXFR_UPTODATE">
+<term>XFRIN_IXFR_UPTODATE IXFR requested serial for %1 is %2, master has %3, not updating</term>
+<listitem><para>
+The first SOA record in an IXFR response indicates the zone's serial
+at the primary server is not newer than the client's. This is
+basically unexpected event because normally the client first checks
+the SOA serial by an SOA query, but can still happen if the transfer
+is manually invoked or (although unlikely) there is a rapid change at
+the primary server between the SOA and IXFR queries. The client
+implementation confirms the whole response is this single SOA, and
+aborts the transfer just like a successful case.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="XFRIN_MSGQ_SEND_ERROR">
<term>XFRIN_MSGQ_SEND_ERROR error while contacting %1 and %2</term>
<listitem><para>
@@ -4750,6 +5585,25 @@ daemon will now shut down.
</para></listitem>
</varlistentry>
+<varlistentry id="XFRIN_TRANSFER_SUCCESS">
+<term>XFRIN_TRANSFER_SUCCESS full %1 transfer of zone %2 succeeded (messages: %3, records: %4, bytes: %5, run time: %6 seconds, %7 bytes/second)</term>
+<listitem><para>
+The AXFR transfer of the given zone was successful.
+The provided information contains the following values:
+</para><para>
+messages: Number of overhead DNS messages in the transfer
+</para><para>
+records: Number of Resource Records in the full transfer, excluding the
+final SOA record that marks the end of the AXFR.
+</para><para>
+bytes: Full size of the transfer data on the wire.
+</para><para>
+run time: Time (in seconds) the complete axfr took
+</para><para>
+bytes/second: Transfer speed
+</para></listitem>
+</varlistentry>
+
<varlistentry id="XFRIN_UNKNOWN_ERROR">
<term>XFRIN_UNKNOWN_ERROR unknown error: %1</term>
<listitem><para>
@@ -4787,9 +5641,9 @@ often.
</varlistentry>
<varlistentry id="XFRIN_XFR_TRANSFER_FAILURE">
-<term>XFRIN_XFR_TRANSFER_FAILURE %1 transfer of zone %2 failed: %3</term>
+<term>XFRIN_XFR_TRANSFER_FAILURE %1 transfer of zone %2 with %3 failed: %4</term>
<listitem><para>
-The XFR transfer for the given zone has failed due to a protocol error.
+The XFR transfer for the given zone has failed due to an internal error.
The error is shown in the log message.
</para></listitem>
</varlistentry>
@@ -4804,6 +5658,20 @@ AXFR could still work. Therefore we try that one in case it helps.
</para></listitem>
</varlistentry>
+<varlistentry id="XFRIN_XFR_TRANSFER_PROTOCOL_ERROR">
+<term>XFRIN_XFR_TRANSFER_PROTOCOL_ERROR %1 transfer of zone %2 with %3 failed: %4</term>
+<listitem><para>
+The XFR transfer for the given zone has failed due to a protocol
+error, such as an unexpected response from the primary server. The
+error is shown in the log message. It may be because the primary
+server implementation is broken or (although less likely) there was
+some attack attempt, but it can also happen due to configuration
+mismatch such as the remote server does not have authority for the
+zone any more but the local configuration hasn't been updated. So it
+is recommended to check the primary server configuration.
+</para></listitem>
+</varlistentry>
+
<varlistentry id="XFRIN_XFR_TRANSFER_STARTED">
<term>XFRIN_XFR_TRANSFER_STARTED %1 transfer of zone %2 started</term>
<listitem><para>
@@ -4812,10 +5680,59 @@ the SOA record has been checked, and a zone transfer has been started.
</para></listitem>
</varlistentry>
-<varlistentry id="XFRIN_XFR_TRANSFER_SUCCESS">
-<term>XFRIN_XFR_TRANSFER_SUCCESS %1 transfer of zone %2 succeeded</term>
+<varlistentry id="XFRIN_ZONE_CREATED">
+<term>XFRIN_ZONE_CREATED Zone %1 not found in the given data source, newly created</term>
+<listitem><para>
+On starting an xfrin session, it is identified that the zone to be
+transferred is not found in the data source. This can happen if a
+secondary DNS server first tries to perform AXFR from a primary server
+without creating the zone image beforehand (e.g. by b10-loadzone). As
+of this writing the xfrin process provides backward compatible
+behavior to previous versions: creating a new one in the data source
+not to surprise existing users too much. This is probably not a good
+idea, however, in terms of who should be responsible for managing
+zones at a higher level. In future it is more likely that a separate
+zone management framework is provided, and the situation where the
+given zone isn't found in xfrout will be treated as an error.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="XFRIN_ZONE_MULTIPLE_SOA">
+<term>XFRIN_ZONE_MULTIPLE_SOA Zone %1 has %2 SOA RRs</term>
+<listitem><para>
+On starting an xfrin session, it is identified that the zone to be
+transferred has multiple SOA RRs. Such a zone is broken, but could be
+accidentally configured especially in a data source using "non
+captive" backend database. The implementation ignores entire SOA RRs
+and tries to continue processing as if the zone were empty. This
+means subsequent AXFR can succeed and possibly replace the zone with
+valid content, but an IXFR attempt will fail.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="XFRIN_ZONE_NO_SOA">
+<term>XFRIN_ZONE_NO_SOA Zone %1 does not have SOA</term>
+<listitem><para>
+On starting an xfrin session, it is identified that the zone to be
+transferred does not have an SOA RR in the data source. This is not
+necessarily an error; if a secondary DNS server first tries to perform
+transfer from a primary server, the zone can be empty, and therefore
+doesn't have an SOA. Subsequent AXFR will fill in the zone; if the
+attempt is IXFR it will fail in query creation.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="XFRIN_ZONE_SERIAL_AHEAD">
+<term>XFRIN_ZONE_SERIAL_AHEAD Serial number (%1) for %2 received from master %3 < ours (%4)</term>
<listitem><para>
-The XFR transfer of the given zone was successfully completed.
+The response to an SOA query prior to xfr indicated that the zone's
+SOA serial at the primary server is smaller than that of the xfrin
+client. This is not necessarily an error especially if that
+particular primary server is another secondary server which hasn't got
+the latest version of the zone. But if the primary server is known to
+be the real source of the zone, some unexpected inconsistency may have
+happened, and you may want to take a closer look. In this case xfrin
+doesn't perform subsequent zone transfer.
</para></listitem>
</varlistentry>
@@ -5362,9 +6279,11 @@ a bug report.
<term>ZONEMGR_UNKNOWN_ZONE_FAIL zone %1 (class %2) is not known to the zone manager</term>
<listitem><para>
An XFRIN operation has failed but the zone that was the subject of the
-operation is not being managed by the zone manager. This may indicate
-an error in the program (as the operation should not have been initiated
-if this were the case). Please submit a bug report.
+operation is not being managed by the zone manager. This can be either the
+result of a bindctl command to transfer in a currently unknown (or mistyped)
+zone, or, if this error appears without the administrator giving transfer
+commands, it can indicate an error in the program, as it should not have
+initiated transfers of unknown zones on its own.
</para></listitem>
</varlistentry>
diff --git a/doc/images/isc-logo.png b/doc/images/isc-logo.png
new file mode 100644
index 0000000..1f7af96
Binary files /dev/null and b/doc/images/isc-logo.png differ
diff --git a/ext/LICENSE_1_0.txt b/ext/LICENSE_1_0.txt
new file mode 100644
index 0000000..36b7cd9
--- /dev/null
+++ b/ext/LICENSE_1_0.txt
@@ -0,0 +1,23 @@
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/ext/asio/asio/detail/impl/kqueue_reactor.ipp b/ext/asio/asio/detail/impl/kqueue_reactor.ipp
index 8db77cb..00ebfeb 100644
--- a/ext/asio/asio/detail/impl/kqueue_reactor.ipp
+++ b/ext/asio/asio/detail/impl/kqueue_reactor.ipp
@@ -301,12 +301,14 @@ void kqueue_reactor::run(bool block, op_queue<operation>& ops)
EV_ADD | EV_ONESHOT, EV_OOBAND, 0, descriptor_data);
else
continue;
+ break;
case EVFILT_WRITE:
if (!descriptor_data->op_queue_[write_op].empty())
ASIO_KQUEUE_EV_SET(&event, descriptor, EVFILT_WRITE,
EV_ADD | EV_ONESHOT, 0, 0, descriptor_data);
else
continue;
+ break;
default:
break;
}
diff --git a/ext/asio/asio/detail/impl/socket_ops.ipp b/ext/asio/asio/detail/impl/socket_ops.ipp
index 66006ea..a1b0ec6 100644
--- a/ext/asio/asio/detail/impl/socket_ops.ipp
+++ b/ext/asio/asio/detail/impl/socket_ops.ipp
@@ -282,15 +282,26 @@ int close(socket_type s, state_type& state,
int result = 0;
if (s != invalid_socket)
{
-#if defined(BOOST_WINDOWS) || defined(__CYGWIN__)
- if ((state & non_blocking) && (state & user_set_linger))
+ if (destruction && (state & user_set_linger))
{
- ioctl_arg_type arg = 0;
- ::ioctlsocket(s, FIONBIO, &arg);
- state &= ~non_blocking;
+ ::linger opt;
+ opt.l_onoff = 0;
+ opt.l_linger = 0;
+ asio::error_code ignored_ec;
+ socket_ops::setsockopt(s, state, SOL_SOCKET,
+ SO_LINGER, &opt, sizeof(opt), ignored_ec);
}
+
+ clear_last_error();
+#if defined(BOOST_WINDOWS) || defined(__CYGWIN__)
+ result = error_wrapper(::closesocket(s), ec);
#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
- if (state & non_blocking)
+ result = error_wrapper(::close(s), ec);
+#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
+
+ if (result != 0
+ && (ec == asio::error::would_block
+ || ec == asio::error::try_again))
{
#if defined(__SYMBIAN32__)
int flags = ::fcntl(s, F_GETFL, 0);
@@ -301,18 +312,6 @@ int close(socket_type s, state_type& state,
::ioctl(s, FIONBIO, &arg);
#endif // defined(__SYMBIAN32__)
state &= ~non_blocking;
- }
-#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
-
- if (destruction && (state & user_set_linger))
- {
- ::linger opt;
- opt.l_onoff = 0;
- opt.l_linger = 0;
- asio::error_code ignored_ec;
- socket_ops::setsockopt(s, state, SOL_SOCKET,
- SO_LINGER, &opt, sizeof(opt), ignored_ec);
- }
clear_last_error();
#if defined(BOOST_WINDOWS) || defined(__CYGWIN__)
@@ -320,6 +319,7 @@ int close(socket_type s, state_type& state,
#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
result = error_wrapper(::close(s), ec);
#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
+ }
}
if (result == 0)
diff --git a/install-sh b/install-sh
deleted file mode 100755
index 6781b98..0000000
--- a/install-sh
+++ /dev/null
@@ -1,520 +0,0 @@
-#!/bin/sh
-# install - install a program, script, or datafile
-
-scriptversion=2009-04-28.21; # UTC
-
-# This originates from X11R5 (mit/util/scripts/install.sh), which was
-# later released in X11R6 (xc/config/util/install.sh) with the
-# following copyright and license.
-#
-# Copyright (C) 1994 X Consortium
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to
-# deal in the Software without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-# sell copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
-# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
-# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-# Except as contained in this notice, the name of the X Consortium shall not
-# be used in advertising or otherwise to promote the sale, use or other deal-
-# ings in this Software without prior written authorization from the X Consor-
-# tium.
-#
-#
-# FSF changes to this file are in the public domain.
-#
-# Calling this script install-sh is preferred over install.sh, to prevent
-# `make' implicit rules from creating a file called install from it
-# when there is no Makefile.
-#
-# This script is compatible with the BSD install script, but was written
-# from scratch.
-
-nl='
-'
-IFS=" "" $nl"
-
-# set DOITPROG to echo to test this script
-
-# Don't use :- since 4.3BSD and earlier shells don't like it.
-doit=${DOITPROG-}
-if test -z "$doit"; then
- doit_exec=exec
-else
- doit_exec=$doit
-fi
-
-# Put in absolute file names if you don't have them in your path;
-# or use environment vars.
-
-chgrpprog=${CHGRPPROG-chgrp}
-chmodprog=${CHMODPROG-chmod}
-chownprog=${CHOWNPROG-chown}
-cmpprog=${CMPPROG-cmp}
-cpprog=${CPPROG-cp}
-mkdirprog=${MKDIRPROG-mkdir}
-mvprog=${MVPROG-mv}
-rmprog=${RMPROG-rm}
-stripprog=${STRIPPROG-strip}
-
-posix_glob='?'
-initialize_posix_glob='
- test "$posix_glob" != "?" || {
- if (set -f) 2>/dev/null; then
- posix_glob=
- else
- posix_glob=:
- fi
- }
-'
-
-posix_mkdir=
-
-# Desired mode of installed file.
-mode=0755
-
-chgrpcmd=
-chmodcmd=$chmodprog
-chowncmd=
-mvcmd=$mvprog
-rmcmd="$rmprog -f"
-stripcmd=
-
-src=
-dst=
-dir_arg=
-dst_arg=
-
-copy_on_change=false
-no_target_directory=
-
-usage="\
-Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
- or: $0 [OPTION]... SRCFILES... DIRECTORY
- or: $0 [OPTION]... -t DIRECTORY SRCFILES...
- or: $0 [OPTION]... -d DIRECTORIES...
-
-In the 1st form, copy SRCFILE to DSTFILE.
-In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
-In the 4th, create DIRECTORIES.
-
-Options:
- --help display this help and exit.
- --version display version info and exit.
-
- -c (ignored)
- -C install only if different (preserve the last data modification time)
- -d create directories instead of installing files.
- -g GROUP $chgrpprog installed files to GROUP.
- -m MODE $chmodprog installed files to MODE.
- -o USER $chownprog installed files to USER.
- -s $stripprog installed files.
- -t DIRECTORY install into DIRECTORY.
- -T report an error if DSTFILE is a directory.
-
-Environment variables override the default commands:
- CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
- RMPROG STRIPPROG
-"
-
-while test $# -ne 0; do
- case $1 in
- -c) ;;
-
- -C) copy_on_change=true;;
-
- -d) dir_arg=true;;
-
- -g) chgrpcmd="$chgrpprog $2"
- shift;;
-
- --help) echo "$usage"; exit $?;;
-
- -m) mode=$2
- case $mode in
- *' '* | *' '* | *'
-'* | *'*'* | *'?'* | *'['*)
- echo "$0: invalid mode: $mode" >&2
- exit 1;;
- esac
- shift;;
-
- -o) chowncmd="$chownprog $2"
- shift;;
-
- -s) stripcmd=$stripprog;;
-
- -t) dst_arg=$2
- shift;;
-
- -T) no_target_directory=true;;
-
- --version) echo "$0 $scriptversion"; exit $?;;
-
- --) shift
- break;;
-
- -*) echo "$0: invalid option: $1" >&2
- exit 1;;
-
- *) break;;
- esac
- shift
-done
-
-if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
- # When -d is used, all remaining arguments are directories to create.
- # When -t is used, the destination is already specified.
- # Otherwise, the last argument is the destination. Remove it from $@.
- for arg
- do
- if test -n "$dst_arg"; then
- # $@ is not empty: it contains at least $arg.
- set fnord "$@" "$dst_arg"
- shift # fnord
- fi
- shift # arg
- dst_arg=$arg
- done
-fi
-
-if test $# -eq 0; then
- if test -z "$dir_arg"; then
- echo "$0: no input file specified." >&2
- exit 1
- fi
- # It's OK to call `install-sh -d' without argument.
- # This can happen when creating conditional directories.
- exit 0
-fi
-
-if test -z "$dir_arg"; then
- trap '(exit $?); exit' 1 2 13 15
-
- # Set umask so as not to create temps with too-generous modes.
- # However, 'strip' requires both read and write access to temps.
- case $mode in
- # Optimize common cases.
- *644) cp_umask=133;;
- *755) cp_umask=22;;
-
- *[0-7])
- if test -z "$stripcmd"; then
- u_plus_rw=
- else
- u_plus_rw='% 200'
- fi
- cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
- *)
- if test -z "$stripcmd"; then
- u_plus_rw=
- else
- u_plus_rw=,u+rw
- fi
- cp_umask=$mode$u_plus_rw;;
- esac
-fi
-
-for src
-do
- # Protect names starting with `-'.
- case $src in
- -*) src=./$src;;
- esac
-
- if test -n "$dir_arg"; then
- dst=$src
- dstdir=$dst
- test -d "$dstdir"
- dstdir_status=$?
- else
-
- # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
- # might cause directories to be created, which would be especially bad
- # if $src (and thus $dsttmp) contains '*'.
- if test ! -f "$src" && test ! -d "$src"; then
- echo "$0: $src does not exist." >&2
- exit 1
- fi
-
- if test -z "$dst_arg"; then
- echo "$0: no destination specified." >&2
- exit 1
- fi
-
- dst=$dst_arg
- # Protect names starting with `-'.
- case $dst in
- -*) dst=./$dst;;
- esac
-
- # If destination is a directory, append the input filename; won't work
- # if double slashes aren't ignored.
- if test -d "$dst"; then
- if test -n "$no_target_directory"; then
- echo "$0: $dst_arg: Is a directory" >&2
- exit 1
- fi
- dstdir=$dst
- dst=$dstdir/`basename "$src"`
- dstdir_status=0
- else
- # Prefer dirname, but fall back on a substitute if dirname fails.
- dstdir=`
- (dirname "$dst") 2>/dev/null ||
- expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
- X"$dst" : 'X\(//\)[^/]' \| \
- X"$dst" : 'X\(//\)$' \| \
- X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
- echo X"$dst" |
- sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
- s//\1/
- q
- }
- /^X\(\/\/\)[^/].*/{
- s//\1/
- q
- }
- /^X\(\/\/\)$/{
- s//\1/
- q
- }
- /^X\(\/\).*/{
- s//\1/
- q
- }
- s/.*/./; q'
- `
-
- test -d "$dstdir"
- dstdir_status=$?
- fi
- fi
-
- obsolete_mkdir_used=false
-
- if test $dstdir_status != 0; then
- case $posix_mkdir in
- '')
- # Create intermediate dirs using mode 755 as modified by the umask.
- # This is like FreeBSD 'install' as of 1997-10-28.
- umask=`umask`
- case $stripcmd.$umask in
- # Optimize common cases.
- *[2367][2367]) mkdir_umask=$umask;;
- .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
-
- *[0-7])
- mkdir_umask=`expr $umask + 22 \
- - $umask % 100 % 40 + $umask % 20 \
- - $umask % 10 % 4 + $umask % 2
- `;;
- *) mkdir_umask=$umask,go-w;;
- esac
-
- # With -d, create the new directory with the user-specified mode.
- # Otherwise, rely on $mkdir_umask.
- if test -n "$dir_arg"; then
- mkdir_mode=-m$mode
- else
- mkdir_mode=
- fi
-
- posix_mkdir=false
- case $umask in
- *[123567][0-7][0-7])
- # POSIX mkdir -p sets u+wx bits regardless of umask, which
- # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
- ;;
- *)
- tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
- trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
-
- if (umask $mkdir_umask &&
- exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
- then
- if test -z "$dir_arg" || {
- # Check for POSIX incompatibilities with -m.
- # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
- # other-writeable bit of parent directory when it shouldn't.
- # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
- ls_ld_tmpdir=`ls -ld "$tmpdir"`
- case $ls_ld_tmpdir in
- d????-?r-*) different_mode=700;;
- d????-?--*) different_mode=755;;
- *) false;;
- esac &&
- $mkdirprog -m$different_mode -p -- "$tmpdir" && {
- ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
- test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
- }
- }
- then posix_mkdir=:
- fi
- rmdir "$tmpdir/d" "$tmpdir"
- else
- # Remove any dirs left behind by ancient mkdir implementations.
- rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
- fi
- trap '' 0;;
- esac;;
- esac
-
- if
- $posix_mkdir && (
- umask $mkdir_umask &&
- $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
- )
- then :
- else
-
- # The umask is ridiculous, or mkdir does not conform to POSIX,
- # or it failed possibly due to a race condition. Create the
- # directory the slow way, step by step, checking for races as we go.
-
- case $dstdir in
- /*) prefix='/';;
- -*) prefix='./';;
- *) prefix='';;
- esac
-
- eval "$initialize_posix_glob"
-
- oIFS=$IFS
- IFS=/
- $posix_glob set -f
- set fnord $dstdir
- shift
- $posix_glob set +f
- IFS=$oIFS
-
- prefixes=
-
- for d
- do
- test -z "$d" && continue
-
- prefix=$prefix$d
- if test -d "$prefix"; then
- prefixes=
- else
- if $posix_mkdir; then
- (umask=$mkdir_umask &&
- $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
- # Don't fail if two instances are running concurrently.
- test -d "$prefix" || exit 1
- else
- case $prefix in
- *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
- *) qprefix=$prefix;;
- esac
- prefixes="$prefixes '$qprefix'"
- fi
- fi
- prefix=$prefix/
- done
-
- if test -n "$prefixes"; then
- # Don't fail if two instances are running concurrently.
- (umask $mkdir_umask &&
- eval "\$doit_exec \$mkdirprog $prefixes") ||
- test -d "$dstdir" || exit 1
- obsolete_mkdir_used=true
- fi
- fi
- fi
-
- if test -n "$dir_arg"; then
- { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
- { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
- { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
- test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
- else
-
- # Make a couple of temp file names in the proper directory.
- dsttmp=$dstdir/_inst.$$_
- rmtmp=$dstdir/_rm.$$_
-
- # Trap to clean up those temp files at exit.
- trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
-
- # Copy the file name to the temp name.
- (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
-
- # and set any options; do chmod last to preserve setuid bits.
- #
- # If any of these fail, we abort the whole thing. If we want to
- # ignore errors from any of these, just make sure not to ignore
- # errors from the above "$doit $cpprog $src $dsttmp" command.
- #
- { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
- { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
- { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
- { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
-
- # If -C, don't bother to copy if it wouldn't change the file.
- if $copy_on_change &&
- old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` &&
- new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` &&
-
- eval "$initialize_posix_glob" &&
- $posix_glob set -f &&
- set X $old && old=:$2:$4:$5:$6 &&
- set X $new && new=:$2:$4:$5:$6 &&
- $posix_glob set +f &&
-
- test "$old" = "$new" &&
- $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
- then
- rm -f "$dsttmp"
- else
- # Rename the file to the real destination.
- $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
-
- # The rename failed, perhaps because mv can't rename something else
- # to itself, or perhaps because mv is so ancient that it does not
- # support -f.
- {
- # Now remove or move aside any old file at destination location.
- # We try this two ways since rm can't unlink itself on some
- # systems and the destination file might be busy for other
- # reasons. In this case, the final cleanup might fail but the new
- # file should still install successfully.
- {
- test ! -f "$dst" ||
- $doit $rmcmd -f "$dst" 2>/dev/null ||
- { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
- { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
- } ||
- { echo "$0: cannot unlink or rename $dst" >&2
- (exit 1); exit 1
- }
- } &&
-
- # Now rename the file to the real destination.
- $doit $mvcmd "$dsttmp" "$dst"
- }
- fi || exit 1
-
- trap '' 0
- fi
-done
-
-# Local variables:
-# eval: (add-hook 'write-file-hooks 'time-stamp)
-# time-stamp-start: "scriptversion="
-# time-stamp-format: "%:y-%02m-%02d.%02H"
-# time-stamp-time-zone: "UTC"
-# time-stamp-end: "; # UTC"
-# End:
diff --git a/m4macros/.gitignore b/m4macros/.gitignore
new file mode 100644
index 0000000..764b428
--- /dev/null
+++ b/m4macros/.gitignore
@@ -0,0 +1,5 @@
+/libtool.m4
+/lt~obsolete.m4
+/ltoptions.m4
+/ltsugar.m4
+/ltversion.m4
diff --git a/missing b/missing
deleted file mode 100755
index 28055d2..0000000
--- a/missing
+++ /dev/null
@@ -1,376 +0,0 @@
-#! /bin/sh
-# Common stub for a few missing GNU programs while installing.
-
-scriptversion=2009-04-28.21; # UTC
-
-# Copyright (C) 1996, 1997, 1999, 2000, 2002, 2003, 2004, 2005, 2006,
-# 2008, 2009 Free Software Foundation, Inc.
-# Originally by Fran,cois Pinard <pinard at iro.umontreal.ca>, 1996.
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2, or (at your option)
-# any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-# As a special exception to the GNU General Public License, if you
-# distribute this file as part of a program that contains a
-# configuration script generated by Autoconf, you may include it under
-# the same distribution terms that you use for the rest of that program.
-
-if test $# -eq 0; then
- echo 1>&2 "Try \`$0 --help' for more information"
- exit 1
-fi
-
-run=:
-sed_output='s/.* --output[ =]\([^ ]*\).*/\1/p'
-sed_minuso='s/.* -o \([^ ]*\).*/\1/p'
-
-# In the cases where this matters, `missing' is being run in the
-# srcdir already.
-if test -f configure.ac; then
- configure_ac=configure.ac
-else
- configure_ac=configure.in
-fi
-
-msg="missing on your system"
-
-case $1 in
---run)
- # Try to run requested program, and just exit if it succeeds.
- run=
- shift
- "$@" && exit 0
- # Exit code 63 means version mismatch. This often happens
- # when the user try to use an ancient version of a tool on
- # a file that requires a minimum version. In this case we
- # we should proceed has if the program had been absent, or
- # if --run hadn't been passed.
- if test $? = 63; then
- run=:
- msg="probably too old"
- fi
- ;;
-
- -h|--h|--he|--hel|--help)
- echo "\
-$0 [OPTION]... PROGRAM [ARGUMENT]...
-
-Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an
-error status if there is no known handling for PROGRAM.
-
-Options:
- -h, --help display this help and exit
- -v, --version output version information and exit
- --run try to run the given command, and emulate it if it fails
-
-Supported PROGRAM values:
- aclocal touch file \`aclocal.m4'
- autoconf touch file \`configure'
- autoheader touch file \`config.h.in'
- autom4te touch the output file, or create a stub one
- automake touch all \`Makefile.in' files
- bison create \`y.tab.[ch]', if possible, from existing .[ch]
- flex create \`lex.yy.c', if possible, from existing .c
- help2man touch the output file
- lex create \`lex.yy.c', if possible, from existing .c
- makeinfo touch the output file
- tar try tar, gnutar, gtar, then tar without non-portable flags
- yacc create \`y.tab.[ch]', if possible, from existing .[ch]
-
-Version suffixes to PROGRAM as well as the prefixes \`gnu-', \`gnu', and
-\`g' are ignored when checking the name.
-
-Send bug reports to <bug-automake at gnu.org>."
- exit $?
- ;;
-
- -v|--v|--ve|--ver|--vers|--versi|--versio|--version)
- echo "missing $scriptversion (GNU Automake)"
- exit $?
- ;;
-
- -*)
- echo 1>&2 "$0: Unknown \`$1' option"
- echo 1>&2 "Try \`$0 --help' for more information"
- exit 1
- ;;
-
-esac
-
-# normalize program name to check for.
-program=`echo "$1" | sed '
- s/^gnu-//; t
- s/^gnu//; t
- s/^g//; t'`
-
-# Now exit if we have it, but it failed. Also exit now if we
-# don't have it and --version was passed (most likely to detect
-# the program). This is about non-GNU programs, so use $1 not
-# $program.
-case $1 in
- lex*|yacc*)
- # Not GNU programs, they don't have --version.
- ;;
-
- tar*)
- if test -n "$run"; then
- echo 1>&2 "ERROR: \`tar' requires --run"
- exit 1
- elif test "x$2" = "x--version" || test "x$2" = "x--help"; then
- exit 1
- fi
- ;;
-
- *)
- if test -z "$run" && ($1 --version) > /dev/null 2>&1; then
- # We have it, but it failed.
- exit 1
- elif test "x$2" = "x--version" || test "x$2" = "x--help"; then
- # Could not run --version or --help. This is probably someone
- # running `$TOOL --version' or `$TOOL --help' to check whether
- # $TOOL exists and not knowing $TOOL uses missing.
- exit 1
- fi
- ;;
-esac
-
-# If it does not exist, or fails to run (possibly an outdated version),
-# try to emulate it.
-case $program in
- aclocal*)
- echo 1>&2 "\
-WARNING: \`$1' is $msg. You should only need it if
- you modified \`acinclude.m4' or \`${configure_ac}'. You might want
- to install the \`Automake' and \`Perl' packages. Grab them from
- any GNU archive site."
- touch aclocal.m4
- ;;
-
- autoconf*)
- echo 1>&2 "\
-WARNING: \`$1' is $msg. You should only need it if
- you modified \`${configure_ac}'. You might want to install the
- \`Autoconf' and \`GNU m4' packages. Grab them from any GNU
- archive site."
- touch configure
- ;;
-
- autoheader*)
- echo 1>&2 "\
-WARNING: \`$1' is $msg. You should only need it if
- you modified \`acconfig.h' or \`${configure_ac}'. You might want
- to install the \`Autoconf' and \`GNU m4' packages. Grab them
- from any GNU archive site."
- files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}`
- test -z "$files" && files="config.h"
- touch_files=
- for f in $files; do
- case $f in
- *:*) touch_files="$touch_files "`echo "$f" |
- sed -e 's/^[^:]*://' -e 's/:.*//'`;;
- *) touch_files="$touch_files $f.in";;
- esac
- done
- touch $touch_files
- ;;
-
- automake*)
- echo 1>&2 "\
-WARNING: \`$1' is $msg. You should only need it if
- you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'.
- You might want to install the \`Automake' and \`Perl' packages.
- Grab them from any GNU archive site."
- find . -type f -name Makefile.am -print |
- sed 's/\.am$/.in/' |
- while read f; do touch "$f"; done
- ;;
-
- autom4te*)
- echo 1>&2 "\
-WARNING: \`$1' is needed, but is $msg.
- You might have modified some files without having the
- proper tools for further handling them.
- You can get \`$1' as part of \`Autoconf' from any GNU
- archive site."
-
- file=`echo "$*" | sed -n "$sed_output"`
- test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
- if test -f "$file"; then
- touch $file
- else
- test -z "$file" || exec >$file
- echo "#! /bin/sh"
- echo "# Created by GNU Automake missing as a replacement of"
- echo "# $ $@"
- echo "exit 0"
- chmod +x $file
- exit 1
- fi
- ;;
-
- bison*|yacc*)
- echo 1>&2 "\
-WARNING: \`$1' $msg. You should only need it if
- you modified a \`.y' file. You may need the \`Bison' package
- in order for those modifications to take effect. You can get
- \`Bison' from any GNU archive site."
- rm -f y.tab.c y.tab.h
- if test $# -ne 1; then
- eval LASTARG="\${$#}"
- case $LASTARG in
- *.y)
- SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'`
- if test -f "$SRCFILE"; then
- cp "$SRCFILE" y.tab.c
- fi
- SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'`
- if test -f "$SRCFILE"; then
- cp "$SRCFILE" y.tab.h
- fi
- ;;
- esac
- fi
- if test ! -f y.tab.h; then
- echo >y.tab.h
- fi
- if test ! -f y.tab.c; then
- echo 'main() { return 0; }' >y.tab.c
- fi
- ;;
-
- lex*|flex*)
- echo 1>&2 "\
-WARNING: \`$1' is $msg. You should only need it if
- you modified a \`.l' file. You may need the \`Flex' package
- in order for those modifications to take effect. You can get
- \`Flex' from any GNU archive site."
- rm -f lex.yy.c
- if test $# -ne 1; then
- eval LASTARG="\${$#}"
- case $LASTARG in
- *.l)
- SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'`
- if test -f "$SRCFILE"; then
- cp "$SRCFILE" lex.yy.c
- fi
- ;;
- esac
- fi
- if test ! -f lex.yy.c; then
- echo 'main() { return 0; }' >lex.yy.c
- fi
- ;;
-
- help2man*)
- echo 1>&2 "\
-WARNING: \`$1' is $msg. You should only need it if
- you modified a dependency of a manual page. You may need the
- \`Help2man' package in order for those modifications to take
- effect. You can get \`Help2man' from any GNU archive site."
-
- file=`echo "$*" | sed -n "$sed_output"`
- test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
- if test -f "$file"; then
- touch $file
- else
- test -z "$file" || exec >$file
- echo ".ab help2man is required to generate this page"
- exit $?
- fi
- ;;
-
- makeinfo*)
- echo 1>&2 "\
-WARNING: \`$1' is $msg. You should only need it if
- you modified a \`.texi' or \`.texinfo' file, or any other file
- indirectly affecting the aspect of the manual. The spurious
- call might also be the consequence of using a buggy \`make' (AIX,
- DU, IRIX). You might want to install the \`Texinfo' package or
- the \`GNU make' package. Grab either from any GNU archive site."
- # The file to touch is that specified with -o ...
- file=`echo "$*" | sed -n "$sed_output"`
- test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
- if test -z "$file"; then
- # ... or it is the one specified with @setfilename ...
- infile=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'`
- file=`sed -n '
- /^@setfilename/{
- s/.* \([^ ]*\) *$/\1/
- p
- q
- }' $infile`
- # ... or it is derived from the source name (dir/f.texi becomes f.info)
- test -z "$file" && file=`echo "$infile" | sed 's,.*/,,;s,.[^.]*$,,'`.info
- fi
- # If the file does not exist, the user really needs makeinfo;
- # let's fail without touching anything.
- test -f $file || exit 1
- touch $file
- ;;
-
- tar*)
- shift
-
- # We have already tried tar in the generic part.
- # Look for gnutar/gtar before invocation to avoid ugly error
- # messages.
- if (gnutar --version > /dev/null 2>&1); then
- gnutar "$@" && exit 0
- fi
- if (gtar --version > /dev/null 2>&1); then
- gtar "$@" && exit 0
- fi
- firstarg="$1"
- if shift; then
- case $firstarg in
- *o*)
- firstarg=`echo "$firstarg" | sed s/o//`
- tar "$firstarg" "$@" && exit 0
- ;;
- esac
- case $firstarg in
- *h*)
- firstarg=`echo "$firstarg" | sed s/h//`
- tar "$firstarg" "$@" && exit 0
- ;;
- esac
- fi
-
- echo 1>&2 "\
-WARNING: I can't seem to be able to run \`tar' with the given arguments.
- You may want to install GNU tar or Free paxutils, or check the
- command line arguments."
- exit 1
- ;;
-
- *)
- echo 1>&2 "\
-WARNING: \`$1' is needed, and is $msg.
- You might have modified some files without having the
- proper tools for further handling them. Check the \`README' file,
- it often tells you about the needed prerequisites for installing
- this package. You may also peek at any GNU archive site, in case
- some other package would contain this missing \`$1' program."
- exit 1
- ;;
-esac
-
-exit 0
-
-# Local variables:
-# eval: (add-hook 'write-file-hooks 'time-stamp)
-# time-stamp-start: "scriptversion="
-# time-stamp-format: "%:y-%02m-%02d.%02H"
-# time-stamp-time-zone: "UTC"
-# time-stamp-end: "; # UTC"
-# End:
diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am
index 7c6cdb8..499e209 100644
--- a/src/bin/Makefile.am
+++ b/src/bin/Makefile.am
@@ -1,4 +1,4 @@
SUBDIRS = bind10 bindctl cfgmgr ddns loadzone msgq host cmdctl auth xfrin \
- xfrout usermgr zonemgr stats tests resolver sockcreator dhcp4 dhcp6
+ xfrout usermgr zonemgr stats tests resolver sockcreator dhcp4 dhcp6 dbutil
check-recursive: all-recursive
diff --git a/src/bin/auth/.gitignore b/src/bin/auth/.gitignore
new file mode 100644
index 0000000..64c3fd7
--- /dev/null
+++ b/src/bin/auth/.gitignore
@@ -0,0 +1,7 @@
+/auth.spec
+/auth.spec.pre
+/auth_messages.cc
+/auth_messages.h
+/b10-auth
+/spec_config.h
+/spec_config.h.pre
diff --git a/src/bin/auth/Makefile.am b/src/bin/auth/Makefile.am
index 3d60432..66abbe2 100644
--- a/src/bin/auth/Makefile.am
+++ b/src/bin/auth/Makefile.am
@@ -44,7 +44,6 @@ pkglibexec_PROGRAMS = b10-auth
b10_auth_SOURCES = query.cc query.h
b10_auth_SOURCES += auth_srv.cc auth_srv.h
b10_auth_SOURCES += auth_log.cc auth_log.h
-b10_auth_SOURCES += change_user.cc change_user.h
b10_auth_SOURCES += auth_config.cc auth_config.h
b10_auth_SOURCES += command.cc command.h
b10_auth_SOURCES += common.h common.cc
diff --git a/src/bin/auth/auth.spec.pre.in b/src/bin/auth/auth.spec.pre.in
index 2ce044e..3eeb35e 100644
--- a/src/bin/auth/auth.spec.pre.in
+++ b/src/bin/auth/auth.spec.pre.in
@@ -47,6 +47,10 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
+ },
+ { "item_name": "filetype",
+ "item_type": "string",
+ "item_optional": true
}]
}
}]
@@ -97,7 +101,13 @@
{
"command_name": "shutdown",
"command_description": "Shut down authoritative DNS server",
- "command_args": []
+ "command_args": [
+ {
+ "item_name": "pid",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
},
{
"command_name": "sendstats",
@@ -139,6 +149,270 @@
"item_default": 0,
"item_title": "Queries UDP",
"item_description": "A number of total query counts which all auth servers receive over UDP since they started initially"
+ },
+ {
+ "item_name": "opcode.query",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received query requests",
+ "item_description": "The number of total request counts whose opcode is query"
+ },
+ {
+ "item_name": "opcode.iquery",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received inverse query requests",
+ "item_description": "The number of total request counts whose opcode is inverse query"
+ },
+ {
+ "item_name": "opcode.status",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received status requests",
+ "item_description": "The number of total request counts whose opcode is status"
+ },
+ {
+ "item_name": "opcode.reserved3",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received requests opcode 3",
+ "item_description": "The number of total request counts whose opcode is 3 (reserved)"
+ },
+ {
+ "item_name": "opcode.notify",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received notify requests",
+ "item_description": "The number of total request counts whose opcode is notify"
+ },
+ {
+ "item_name": "opcode.update",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received update requests",
+ "item_description": "The number of total request counts whose opcode is update"
+ },
+ {
+ "item_name": "opcode.reserved6",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received requests opcode 6",
+ "item_description": "The number of total request counts whose opcode is 6 (reserved)"
+ },
+ {
+ "item_name": "opcode.reserved7",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received requests opcode 7",
+ "item_description": "The number of total request counts whose opcode is 7 (reserved)"
+ },
+ {
+ "item_name": "opcode.reserved8",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received requests opcode 8",
+ "item_description": "The number of total request counts whose opcode is 8 (reserved)"
+ },
+ {
+ "item_name": "opcode.reserved9",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received requests opcode 9",
+ "item_description": "The number of total request counts whose opcode is 9 (reserved)"
+ },
+ {
+ "item_name": "opcode.reserved10",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received requests opcode 10",
+ "item_description": "The number of total request counts whose opcode is 10 (reserved)"
+ },
+ {
+ "item_name": "opcode.reserved11",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received requests opcode 11",
+ "item_description": "The number of total request counts whose opcode is 11 (reserved)"
+ },
+ {
+ "item_name": "opcode.reserved12",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received requests opcode 12",
+ "item_description": "The number of total request counts whose opcode is 12 (reserved)"
+ },
+ {
+ "item_name": "opcode.reserved13",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received requests opcode 13",
+ "item_description": "The number of total request counts whose opcode is 13 (reserved)"
+ },
+ {
+ "item_name": "opcode.reserved14",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received requests opcode 14",
+ "item_description": "The number of total request counts whose opcode is 14 (reserved)"
+ },
+ {
+ "item_name": "opcode.reserved15",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Received requests opcode 15",
+ "item_description": "The number of total request counts whose opcode is 15 (reserved)"
+ },
+ {
+ "item_name": "rcode.noerror",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent success response",
+ "item_description": "The number of total responses with rcode 0 (NOERROR)"
+ },
+ {
+ "item_name": "rcode.formerr",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent 'format error' response",
+ "item_description": "The number of total responses with rcode 1 (FORMERR)"
+ },
+ {
+ "item_name": "rcode.servfail",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent 'server failure' response",
+ "item_description": "The number of total responses with rcode 2 (SERVFAIL)"
+ },
+ {
+ "item_name": "rcode.nxdomain",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent 'name error' response",
+ "item_description": "The number of total responses with rcode 3 (NXDOMAIN)"
+ },
+ {
+ "item_name": "rcode.notimp",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent 'not implemented' response",
+ "item_description": "The number of total responses with rcode 4 (NOTIMP)"
+ },
+ {
+ "item_name": "rcode.refused",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent 'refused' response",
+ "item_description": "The number of total responses with rcode 5 (REFUSED)"
+ },
+ {
+ "item_name": "rcode.yxdomain",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent 'name unexpectedly exists' response",
+ "item_description": "The number of total responses with rcode 6 (YXDOMAIN)"
+ },
+ {
+ "item_name": "rcode.yxrrset",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent 'rrset unexpectedly exists' response",
+ "item_description": "The number of total responses with rcode 7 (YXRRSET)"
+ },
+ {
+ "item_name": "rcode.nxrrset",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent 'no such rrset' response",
+ "item_description": "The number of total responses with rcode 8 (NXRRSET)"
+ },
+ {
+ "item_name": "rcode.notauth",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent 'not authoritative' response",
+ "item_description": "The number of total responses with rcode 9 (NOTAUTH)"
+ },
+ {
+ "item_name": "rcode.notzone",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent 'name not in zone' response",
+ "item_description": "The number of total responses with rcode 10 (NOTZONE)"
+ },
+ {
+ "item_name": "rcode.reserved11",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent response with rcode 11",
+ "item_description": "The number of total responses with rcode 11 (reserved)"
+ },
+ {
+ "item_name": "rcode.reserved12",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent response with rcode 12",
+ "item_description": "The number of total responses with rcode 12 (reserved)"
+ },
+ {
+ "item_name": "rcode.reserved13",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent response with rcode 13",
+ "item_description": "The number of total responses with rcode 13 (reserved)"
+ },
+ {
+ "item_name": "rcode.reserved14",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent response with rcode 14",
+ "item_description": "The number of total responses with rcode 14 (reserved)"
+ },
+ {
+ "item_name": "rcode.reserved15",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent response with rcode 15",
+ "item_description": "The number of total responses with rcode 15 (reserved)"
+ },
+ {
+ "item_name": "rcode.badvers",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 0,
+ "item_title": "Sent 'EDNS version not implemented' response",
+ "item_description": "The number of total responses with rcode 16 (BADVERS)"
}
]
}
diff --git a/src/bin/auth/auth_config.cc b/src/bin/auth/auth_config.cc
index 2ae520c..3a04dc8 100644
--- a/src/bin/auth/auth_config.cc
+++ b/src/bin/auth/auth_config.cc
@@ -12,14 +12,6 @@
// 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>
@@ -27,6 +19,7 @@
#include <datasrc/memory_datasrc.h>
#include <datasrc/zonetable.h>
+#include <datasrc/factory.h>
#include <auth/auth_srv.h>
#include <auth/auth_config.h>
@@ -34,6 +27,15 @@
#include <server_common/portconfig.h>
+#include <boost/foreach.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
using namespace std;
using namespace isc::dns;
using namespace isc::data;
@@ -155,17 +157,48 @@ MemoryDatasourceConfig::build(ConstElementPtr config_value) {
BOOST_FOREACH(ConstElementPtr zone_config, zones_config->listValue()) {
ConstElementPtr origin = zone_config->get("origin");
- if (!origin) {
+ const string origin_txt = origin ? origin->stringValue() : "";
+ if (origin_txt.empty()) {
isc_throw(AuthConfigError, "Missing zone origin");
}
ConstElementPtr file = zone_config->get("file");
- if (!file) {
+ const string file_txt = file ? file->stringValue() : "";
+ if (file_txt.empty()) {
isc_throw(AuthConfigError, "Missing zone file for zone: "
- << origin->str());
+ << origin_txt);
}
- boost::shared_ptr<InMemoryZoneFinder> zone_finder(new
- InMemoryZoneFinder(rrclass_,
- Name(origin->stringValue())));
+
+ // We support the traditional text type and SQLite3 backend. For the
+ // latter we create a client for the underlying SQLite3 data source,
+ // and build the in-memory zone using an iterator of the underlying
+ // zone.
+ ConstElementPtr filetype = zone_config->get("filetype");
+ const string filetype_txt = filetype ? filetype->stringValue() :
+ "text";
+ boost::scoped_ptr<DataSourceClientContainer> container;
+ if (filetype_txt == "sqlite3") {
+ container.reset(new DataSourceClientContainer(
+ "sqlite3",
+ Element::fromJSON("{\"database_file\": \"" +
+ file_txt + "\"}")));
+ } else if (filetype_txt != "text") {
+ isc_throw(AuthConfigError, "Invalid filetype for zone "
+ << origin_txt << ": " << filetype_txt);
+ }
+
+ // Note: we don't want to have such small try-catch blocks for each
+ // specific error. We may eventually want to introduce some unified
+ // error handling framework as we have more configuration parameters.
+ // See bug #1627 for the relevant discussion.
+ InMemoryZoneFinder* imzf = NULL;
+ try {
+ imzf = new InMemoryZoneFinder(rrclass_, Name(origin_txt));
+ } catch (const isc::dns::NameParserException& ex) {
+ isc_throw(AuthConfigError, "unable to parse zone's origin: " <<
+ ex.what());
+ }
+
+ boost::shared_ptr<InMemoryZoneFinder> zone_finder(imzf);
const result::Result result = memory_client_->addZone(zone_finder);
if (result == result::EXIST) {
isc_throw(AuthConfigError, "zone "<< origin->str()
@@ -178,7 +211,12 @@ MemoryDatasourceConfig::build(ConstElementPtr config_value) {
* need the load method to be split into some kind of build and
* commit/abort parts.
*/
- zone_finder->load(file->stringValue());
+ if (filetype_txt == "text") {
+ zone_finder->load(file_txt);
+ } else {
+ zone_finder->load(*container->getInstance().getIterator(
+ Name(origin_txt)));
+ }
}
}
diff --git a/src/bin/auth/auth_log.h b/src/bin/auth/auth_log.h
index e0cae0f..33d4432 100644
--- a/src/bin/auth/auth_log.h
+++ b/src/bin/auth/auth_log.h
@@ -29,6 +29,8 @@ namespace auth {
// Debug messages indicating normal startup are logged at this debug level.
const int DBG_AUTH_START = DBGLVL_START_SHUT;
+// Debug messages upon shutdown
+const int DBG_AUTH_SHUT = DBGLVL_START_SHUT;
// Debug level used to log setting information (such as configuration changes).
const int DBG_AUTH_OPS = DBGLVL_COMMAND;
diff --git a/src/bin/auth/auth_messages.mes b/src/bin/auth/auth_messages.mes
index 4706690..b18feb1 100644
--- a/src/bin/auth/auth_messages.mes
+++ b/src/bin/auth/auth_messages.mes
@@ -73,6 +73,10 @@ attempt to parse the header of a received DNS packet has failed. (The
reason for the failure is given in the message.) The server will drop the
packet.
+% AUTH_INVALID_STATISTICS_DATA invalid specification of statistics data specified
+An error was encountered when the authoritiative server specified
+statistics data which is invalid for the auth specification file.
+
% AUTH_LOAD_TSIG loading TSIG keys
This is a debug message indicating that the authoritative server
has requested the keyring holding TSIG keys from the configuration
@@ -92,6 +96,18 @@ discovered that the memory data source is disabled for the given class.
This is a debug message reporting that the authoritative server has
discovered that the memory data source is enabled for the given class.
+% AUTH_NOTIFY_QUESTIONS invalid number of questions (%1) in incoming NOTIFY
+This debug message is logged by the authoritative server when it receives
+a NOTIFY packet that contains zero or more than one question. (A valid
+NOTIFY packet contains one question.) The server will return a FORMERR
+error to the sender.
+
+% AUTH_NOTIFY_RRTYPE invalid question RR type (%1) in incoming NOTIFY
+This debug message is logged by the authoritative server when it receives
+a NOTIFY packet that an RR type of something other than SOA in the
+question section. (The RR type received is included in the message.) The
+server will return a FORMERR error to the sender.
+
% AUTH_NO_STATS_SESSION session interface for statistics is not available
The authoritative server had no session with the statistics module at the
time it attempted to send it data: the attempt has been abandoned. This
@@ -102,18 +118,6 @@ This is a debug message produced by the authoritative server when it receives
a NOTIFY packet but the XFRIN process is not running. The packet will be
dropped and nothing returned to the sender.
-% AUTH_NOTIFY_RRTYPE invalid question RR type (%1) in incoming NOTIFY
-This debug message is logged by the authoritative server when it receives
-a NOTIFY packet that an RR type of something other than SOA in the
-question section. (The RR type received is included in the message.) The
-server will return a FORMERR error to the sender.
-
-% AUTH_NOTIFY_QUESTIONS invalid number of questions (%1) in incoming NOTIFY
-This debug message is logged by the authoritative server when it receives
-a NOTIFY packet that contains zero or more than one question. (A valid
-NOTIFY packet contains one question.) The server will return a FORMERR
-error to the sender.
-
% AUTH_PACKET_PARSE_ERROR unable to parse received DNS packet: %1
This is a debug message, generated by the authoritative server when an
attempt to parse a received DNS packet has failed due to something other
@@ -154,6 +158,19 @@ a command from the statistics module to send it data. The 'sendstats'
command is handled differently to other commands, which is why the debug
message associated with it has its own code.
+% AUTH_RESPONSE_FAILURE exception while building response to query: %1
+This is a debug message, generated by the authoritative server when an
+attempt to create a response to a received DNS packet has failed. The
+reason for the failure is given in the log message. A SERVFAIL response
+is sent back. The most likely cause of this is an error in the data
+source implementation; it is either creating bad responses or raising
+exceptions itself.
+
+% AUTH_RESPONSE_FAILURE_UNKNOWN unknown exception while building response to query
+This debug message is similar to AUTH_RESPONSE_FAILURE, but further
+details about the error are unknown, because it was signaled by something
+which is not an exception. This is definitely a bug.
+
% AUTH_RESPONSE_RECEIVED received response message, ignoring
This is a debug message, this is output if the authoritative server
receives a DNS packet with the QR bit set, i.e. a DNS response. The
@@ -192,6 +209,10 @@ reason for the failure is included in the message.
Initialization of the authoritative server has completed successfully
and it is entering the main loop, waiting for queries to arrive.
+% AUTH_SHUTDOWN asked to stop, doing so
+This is a debug message indicating the server was asked to shut down and it is
+complying to the request.
+
% AUTH_SQLITE3 nothing to do for loading sqlite3
This is a debug message indicating that the authoritative server has
found that the data source it is loading is an SQLite3 data source,
@@ -256,7 +277,3 @@ This is a debug message output during the processing of a NOTIFY
request. The zone manager component has been informed of the request,
but has returned an error response (which is included in the message). The
NOTIFY request will not be honored.
-
-% AUTH_INVALID_STATISTICS_DATA invalid specification of statistics data specified
-An error was encountered when the authoritiative server specified
-statistics data which is invalid for the auth specification file.
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index 9c5e594..9f5642e 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -14,6 +14,7 @@
#include <config.h>
+#include <sys/types.h>
#include <netinet/in.h>
#include <algorithm>
@@ -46,6 +47,8 @@
#include <dns/message.h>
#include <dns/tsig.h>
+#include <asiodns/dns_service.h>
+
#include <datasrc/query.h>
#include <datasrc/data_source.h>
#include <datasrc/memory_datasrc.h>
@@ -77,6 +80,35 @@ using namespace isc::asiolink;
using namespace isc::asiodns;
using namespace isc::server_common::portconfig;
+namespace {
+// A helper class for cleaning up message renderer.
+//
+// A temporary object of this class is expected to be created before starting
+// response message rendering. On construction, it (re)initialize the given
+// message renderer with the given buffer. On destruction, it releases
+// the previously set buffer and then release any internal resource in the
+// renderer, no matter what happened during the rendering, especially even
+// when it resulted in an exception.
+//
+// Note: if we need this helper in many other places we might consider making
+// it visible to other modules. As of this implementation this is the only
+// user of this class, so we hide it within the implementation.
+class RendererHolder {
+public:
+ RendererHolder(MessageRenderer& renderer, OutputBuffer* buffer) :
+ renderer_(renderer)
+ {
+ renderer.setBuffer(buffer);
+ }
+ ~RendererHolder() {
+ renderer_.setBuffer(NULL);
+ renderer_.clear();
+ }
+private:
+ MessageRenderer& renderer_;
+};
+}
+
class AuthSrvImpl {
private:
// prohibit copy
@@ -87,18 +119,19 @@ public:
~AuthSrvImpl();
isc::data::ConstElementPtr setDbFile(isc::data::ConstElementPtr config);
- bool processNormalQuery(const IOMessage& io_message, MessagePtr message,
- OutputBufferPtr buffer,
+ bool processNormalQuery(const IOMessage& io_message, Message& message,
+ OutputBuffer& buffer,
auto_ptr<TSIGContext> tsig_context);
- bool processXfrQuery(const IOMessage& io_message, MessagePtr message,
- OutputBufferPtr buffer,
+ bool processXfrQuery(const IOMessage& io_message, Message& message,
+ OutputBuffer& buffer,
auto_ptr<TSIGContext> tsig_context);
- bool processNotify(const IOMessage& io_message, MessagePtr message,
- OutputBufferPtr buffer,
+ bool processNotify(const IOMessage& io_message, Message& message,
+ OutputBuffer& buffer,
auto_ptr<TSIGContext> tsig_context);
IOService io_service_;
+ MessageRenderer renderer_;
/// Currently non-configurable, but will be.
static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
@@ -128,6 +161,22 @@ public:
/// Bind the ModuleSpec object in config_session_ with
/// isc:config::ModuleSpec::validateStatistics.
void registerStatisticsValidator();
+
+ /// \brief Resume the server
+ ///
+ /// This is a wrapper call for DNSServer::resume(done), if 'done' is true,
+ /// the Rcode set in the given Message is counted in the statistics
+ /// counter.
+ ///
+ /// This method is expected to be called by processMessage()
+ ///
+ /// \param server The DNSServer as passed to processMessage()
+ /// \param message The response as constructed by processMessage()
+ /// \param done If true, the Rcode from the given message is counted,
+ /// this value is then passed to server->resume(bool)
+ void resumeServer(isc::asiodns::DNSServer* server,
+ isc::dns::Message& message,
+ bool done);
private:
std::string db_file_;
@@ -145,6 +194,8 @@ private:
// validateStatistics
bool validateStatistics(isc::data::ConstElementPtr data) const;
+
+ auth::Query query_;
};
AuthSrvImpl::AuthSrvImpl(const bool use_cache,
@@ -184,12 +235,11 @@ public:
MessageLookup(AuthSrv* srv) : server_(srv) {}
virtual void operator()(const IOMessage& io_message,
MessagePtr message,
- MessagePtr answer_message,
+ MessagePtr, // Not used here
OutputBufferPtr buffer,
DNSServer* server) const
{
- (void) answer_message;
- server_->processMessage(io_message, message, buffer, server);
+ server_->processMessage(io_message, *message, *buffer, server);
}
private:
AuthSrv* server_;
@@ -250,55 +300,56 @@ AuthSrv::~AuthSrv() {
namespace {
class QuestionInserter {
public:
- QuestionInserter(MessagePtr message) : message_(message) {}
+ QuestionInserter(Message& message) : message_(message) {}
void operator()(const QuestionPtr question) {
- message_->addQuestion(question);
+ message_.addQuestion(question);
}
- MessagePtr message_;
+ Message& message_;
};
void
-makeErrorMessage(MessagePtr message, OutputBufferPtr buffer,
- const Rcode& rcode,
+makeErrorMessage(MessageRenderer& renderer, Message& message,
+ OutputBuffer& buffer, const Rcode& rcode,
std::auto_ptr<TSIGContext> tsig_context =
std::auto_ptr<TSIGContext>())
{
// 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();
+ 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(Message::HEADERFLAG_QR);
+ message.clear(Message::RENDER);
+ message.setQid(qid);
+ message.setOpcode(opcode);
+ message.setHeaderFlag(Message::HEADERFLAG_QR);
if (rd) {
- message->setHeaderFlag(Message::HEADERFLAG_RD);
+ message.setHeaderFlag(Message::HEADERFLAG_RD);
}
if (cd) {
- message->setHeaderFlag(Message::HEADERFLAG_CD);
+ message.setHeaderFlag(Message::HEADERFLAG_CD);
}
for_each(questions.begin(), questions.end(), QuestionInserter(message));
- message->setRcode(rcode);
- MessageRenderer renderer(*buffer);
+ message.setRcode(rcode);
+
+ RendererHolder holder(renderer, &buffer);
if (tsig_context.get() != NULL) {
- message->toWire(renderer, *tsig_context);
+ message.toWire(renderer, *tsig_context);
} else {
- message->toWire(renderer);
+ message.toWire(renderer);
}
LOG_DEBUG(auth_logger, DBG_AUTH_MESSAGES, AUTH_SEND_ERROR_RESPONSE)
- .arg(renderer.getLength()).arg(*message);
+ .arg(renderer.getLength()).arg(message);
}
}
@@ -396,54 +447,54 @@ AuthSrv::setStatisticsTimerInterval(uint32_t interval) {
}
void
-AuthSrv::processMessage(const IOMessage& io_message, MessagePtr message,
- OutputBufferPtr buffer, DNSServer* server)
+AuthSrv::processMessage(const IOMessage& io_message, Message& message,
+ OutputBuffer& 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(Message::HEADERFLAG_QR)) {
+ if (message.getHeaderFlag(Message::HEADERFLAG_QR)) {
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RESPONSE_RECEIVED);
- server->resume(false);
+ impl_->resumeServer(server, message, false);
return;
}
} catch (const Exception& ex) {
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_HEADER_PARSE_FAIL)
.arg(ex.what());
- server->resume(false);
+ impl_->resumeServer(server, message, false);
return;
}
try {
// Parse the message.
- message->fromWire(request_buffer);
+ message.fromWire(request_buffer);
} catch (const DNSProtocolError& error) {
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PROTOCOL_ERROR)
.arg(error.getRcode().toText()).arg(error.what());
- makeErrorMessage(message, buffer, error.getRcode());
- server->resume(true);
+ makeErrorMessage(impl_->renderer_, message, buffer, error.getRcode());
+ impl_->resumeServer(server, message, true);
return;
} catch (const Exception& ex) {
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PARSE_ERROR)
.arg(ex.what());
- makeErrorMessage(message, buffer, Rcode::SERVFAIL());
- server->resume(true);
+ makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL());
+ impl_->resumeServer(server, message, true);
return;
} // other exceptions will be handled at a higher layer.
LOG_DEBUG(auth_logger, DBG_AUTH_MESSAGES, AUTH_PACKET_RECEIVED)
- .arg(message->toText());
+ .arg(message);
// Perform further protocol-level validation.
// TSIG first
// If this is set to something, we know we need to answer with TSIG as well
std::auto_ptr<TSIGContext> tsig_context;
- const TSIGRecord* tsig_record(message->getTSIGRecord());
+ const TSIGRecord* tsig_record(message.getTSIGRecord());
TSIGError tsig_error(TSIGError::NOERROR());
// Do we do TSIG?
@@ -457,49 +508,68 @@ AuthSrv::processMessage(const IOMessage& io_message, MessagePtr message,
io_message.getDataSize());
}
- bool sendAnswer = true;
if (tsig_error != TSIGError::NOERROR()) {
- makeErrorMessage(message, buffer, tsig_error.toRcode(), tsig_context);
- } else if (message->getOpcode() == Opcode::NOTIFY()) {
- sendAnswer = impl_->processNotify(io_message, message, buffer,
- tsig_context);
- } else if (message->getOpcode() != Opcode::QUERY()) {
- LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_UNSUPPORTED_OPCODE)
- .arg(message->getOpcode().toText());
- makeErrorMessage(message, buffer, Rcode::NOTIMP(), tsig_context);
- } else if (message->getRRCount(Message::SECTION_QUESTION) != 1) {
- makeErrorMessage(message, buffer, Rcode::FORMERR(), tsig_context);
- } else {
- ConstQuestionPtr question = *message->beginQuestion();
- const RRType &qtype = question->getType();
- if (qtype == RRType::AXFR()) {
- sendAnswer = impl_->processXfrQuery(io_message, message, buffer,
- tsig_context);
- } else if (qtype == RRType::IXFR()) {
- sendAnswer = impl_->processXfrQuery(io_message, message, buffer,
- tsig_context);
+ makeErrorMessage(impl_->renderer_, message, buffer,
+ tsig_error.toRcode(), tsig_context);
+ impl_->resumeServer(server, message, true);
+ return;
+ }
+
+ bool send_answer = true;
+ try {
+ // update per opcode statistics counter. This can only be reliable
+ // after TSIG check succeeds.
+ impl_->counters_.inc(message.getOpcode());
+
+ if (message.getOpcode() == Opcode::NOTIFY()) {
+ send_answer = impl_->processNotify(io_message, message, buffer,
+ tsig_context);
+ } else if (message.getOpcode() != Opcode::QUERY()) {
+ LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_UNSUPPORTED_OPCODE)
+ .arg(message.getOpcode().toText());
+ makeErrorMessage(impl_->renderer_, message, buffer,
+ Rcode::NOTIMP(), tsig_context);
+ } else if (message.getRRCount(Message::SECTION_QUESTION) != 1) {
+ makeErrorMessage(impl_->renderer_, message, buffer,
+ Rcode::FORMERR(), tsig_context);
} else {
- sendAnswer = impl_->processNormalQuery(io_message, message, buffer,
- tsig_context);
+ ConstQuestionPtr question = *message.beginQuestion();
+ const RRType &qtype = question->getType();
+ if (qtype == RRType::AXFR()) {
+ send_answer = impl_->processXfrQuery(io_message, message,
+ buffer, tsig_context);
+ } else if (qtype == RRType::IXFR()) {
+ send_answer = impl_->processXfrQuery(io_message, message,
+ buffer, tsig_context);
+ } else {
+ send_answer = impl_->processNormalQuery(io_message, message,
+ buffer, tsig_context);
+ }
}
+ } catch (const std::exception& ex) {
+ LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RESPONSE_FAILURE)
+ .arg(ex.what());
+ makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL());
+ } catch (...) {
+ LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RESPONSE_FAILURE_UNKNOWN);
+ makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL());
}
-
- server->resume(sendAnswer);
+ impl_->resumeServer(server, message, send_answer);
}
bool
-AuthSrvImpl::processNormalQuery(const IOMessage& io_message, MessagePtr message,
- OutputBufferPtr buffer,
+AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
+ OutputBuffer& buffer,
auto_ptr<TSIGContext> tsig_context)
{
- 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->makeResponse();
- message->setHeaderFlag(Message::HEADERFLAG_AA);
- message->setRcode(Rcode::NOERROR());
+ message.makeResponse();
+ message.setHeaderFlag(Message::HEADERFLAG_AA);
+ message.setRcode(Rcode::NOERROR());
// Increment query counter.
incCounter(io_message.getSocket().getProtocol());
@@ -508,45 +578,44 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, MessagePtr message,
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 {
// If a memory data source is configured call the separate
// Query::process()
- const ConstQuestionPtr question = *message->beginQuestion();
+ const ConstQuestionPtr question = *message.beginQuestion();
if (memory_client_ && memory_client_class_ == question->getClass()) {
const RRType& qtype = question->getType();
const Name& qname = question->getName();
- auth::Query(*memory_client_, qname, qtype, *message).process();
+ query_.process(*memory_client_, qname, qtype, message, dnssec_ok);
} else {
- datasrc::Query query(*message, cache_, dnssec_ok);
+ datasrc::Query query(message, cache_, dnssec_ok);
data_sources_.doQuery(query);
}
} catch (const Exception& ex) {
LOG_ERROR(auth_logger, AUTH_PROCESS_FAIL).arg(ex.what());
- makeErrorMessage(message, buffer, Rcode::SERVFAIL());
+ makeErrorMessage(renderer_, message, buffer, Rcode::SERVFAIL());
return (true);
}
- MessageRenderer renderer(*buffer);
+ RendererHolder holder(renderer_, &buffer);
const bool udp_buffer =
(io_message.getSocket().getProtocol() == IPPROTO_UDP);
- renderer.setLengthLimit(udp_buffer ? remote_bufsize : 65535);
+ renderer_.setLengthLimit(udp_buffer ? remote_bufsize : 65535);
if (tsig_context.get() != NULL) {
- message->toWire(renderer, *tsig_context);
+ message.toWire(renderer_, *tsig_context);
} else {
- message->toWire(renderer);
+ message.toWire(renderer_);
}
LOG_DEBUG(auth_logger, DBG_AUTH_MESSAGES, AUTH_SEND_NORMAL_RESPONSE)
- .arg(renderer.getLength()).arg(message->toText());
-
+ .arg(renderer_.getLength()).arg(message);
return (true);
}
bool
-AuthSrvImpl::processXfrQuery(const IOMessage& io_message, MessagePtr message,
- OutputBufferPtr buffer,
+AuthSrvImpl::processXfrQuery(const IOMessage& io_message, Message& message,
+ OutputBuffer& buffer,
auto_ptr<TSIGContext> tsig_context)
{
// Increment query counter.
@@ -554,7 +623,8 @@ AuthSrvImpl::processXfrQuery(const IOMessage& io_message, MessagePtr message,
if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_AXFR_UDP);
- makeErrorMessage(message, buffer, Rcode::FORMERR(), tsig_context);
+ makeErrorMessage(renderer_, message, buffer, Rcode::FORMERR(),
+ tsig_context);
return (true);
}
@@ -579,7 +649,8 @@ AuthSrvImpl::processXfrQuery(const IOMessage& io_message, MessagePtr message,
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_AXFR_ERROR)
.arg(err.what());
- makeErrorMessage(message, buffer, Rcode::SERVFAIL(), tsig_context);
+ makeErrorMessage(renderer_, message, buffer, Rcode::SERVFAIL(),
+ tsig_context);
return (true);
}
@@ -587,23 +658,25 @@ AuthSrvImpl::processXfrQuery(const IOMessage& io_message, MessagePtr message,
}
bool
-AuthSrvImpl::processNotify(const IOMessage& io_message, MessagePtr message,
- OutputBufferPtr buffer,
+AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
+ OutputBuffer& buffer,
std::auto_ptr<TSIGContext> tsig_context)
{
// The incoming notify must contain exactly one question for SOA of the
// zone name.
- if (message->getRRCount(Message::SECTION_QUESTION) != 1) {
+ if (message.getRRCount(Message::SECTION_QUESTION) != 1) {
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_NOTIFY_QUESTIONS)
- .arg(message->getRRCount(Message::SECTION_QUESTION));
- makeErrorMessage(message, buffer, Rcode::FORMERR(), tsig_context);
+ .arg(message.getRRCount(Message::SECTION_QUESTION));
+ makeErrorMessage(renderer_, message, buffer, Rcode::FORMERR(),
+ tsig_context);
return (true);
}
- ConstQuestionPtr question = *message->beginQuestion();
+ ConstQuestionPtr question = *message.beginQuestion();
if (question->getType() != RRType::SOA()) {
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_NOTIFY_RRTYPE)
.arg(question->getType().toText());
- makeErrorMessage(message, buffer, Rcode::FORMERR(), tsig_context);
+ makeErrorMessage(renderer_, message, buffer, Rcode::FORMERR(),
+ tsig_context);
return (true);
}
@@ -654,15 +727,15 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, MessagePtr message,
return (false);
}
- message->makeResponse();
- message->setHeaderFlag(Message::HEADERFLAG_AA);
- message->setRcode(Rcode::NOERROR());
+ message.makeResponse();
+ message.setHeaderFlag(Message::HEADERFLAG_AA);
+ message.setRcode(Rcode::NOERROR());
- MessageRenderer renderer(*buffer);
+ RendererHolder holder(renderer_, &buffer);
if (tsig_context.get() != NULL) {
- message->toWire(renderer, *tsig_context);
+ message.toWire(renderer_, *tsig_context);
} else {
- message->toWire(renderer);
+ message.toWire(renderer_);
}
return (true);
}
@@ -746,6 +819,14 @@ AuthSrvImpl::setDbFile(ConstElementPtr config) {
return (answer);
}
+void
+AuthSrvImpl::resumeServer(DNSServer* server, Message& message, bool done) {
+ if (done) {
+ counters_.inc(message.getRcode());
+ }
+ server->resume(done);
+}
+
ConstElementPtr
AuthSrv::updateConfig(ConstElementPtr new_config) {
try {
@@ -770,6 +851,16 @@ AuthSrv::getCounter(const AuthCounters::ServerCounterType type) const {
return (impl_->counters_.getCounter(type));
}
+uint64_t
+AuthSrv::getCounter(const Opcode opcode) const {
+ return (impl_->counters_.getCounter(opcode));
+}
+
+uint64_t
+AuthSrv::getCounter(const Rcode rcode) const {
+ return (impl_->counters_.getCounter(rcode));
+}
+
const AddressList&
AuthSrv::getListenAddresses() const {
return (impl_->listen_addresses_);
@@ -777,11 +868,14 @@ AuthSrv::getListenAddresses() const {
void
AuthSrv::setListenAddresses(const AddressList& addresses) {
- installListenAddresses(addresses, impl_->listen_addresses_, *dnss_);
+ // For UDP servers we specify the "SYNC_OK" option because in our usage
+ // it can act in the synchronous mode.
+ installListenAddresses(addresses, impl_->listen_addresses_, *dnss_,
+ DNSService::SERVER_SYNC_OK);
}
void
-AuthSrv::setDNSService(isc::asiodns::DNSService& dnss) {
+AuthSrv::setDNSService(isc::asiodns::DNSServiceBase& dnss) {
dnss_ = &dnss;
}
diff --git a/src/bin/auth/auth_srv.h b/src/bin/auth/auth_srv.h
index a50e427..3be711b 100644
--- a/src/bin/auth/auth_srv.h
+++ b/src/bin/auth/auth_srv.h
@@ -24,9 +24,11 @@
#include <cc/data.h>
#include <config/ccsession.h>
#include <dns/message.h>
+#include <dns/opcode.h>
#include <util/buffer.h>
#include <asiodns/dns_server.h>
+#include <asiodns/dns_service.h>
#include <asiodns/dns_lookup.h>
#include <asiodns/dns_answer.h>
#include <asiolink/io_message.h>
@@ -114,14 +116,14 @@ public:
/// 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 message the \c Message object
+ /// \param buffer 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 isc::asiolink::IOMessage& io_message,
- isc::dns::MessagePtr message,
- isc::util::OutputBufferPtr buffer,
+ isc::dns::Message& message,
+ isc::util::OutputBuffer& buffer,
isc::asiodns::DNSServer* server);
/// \brief Updates the data source for the \c AuthSrv object.
@@ -275,7 +277,7 @@ public:
/// 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 InMemoryClient to be set.
+ /// \param memory_client A (shared) pointer to \c InMemoryClient to be set.
void setInMemoryClient(const isc::dns::RRClass& rrclass,
InMemoryClientPtr memory_client);
@@ -343,8 +345,37 @@ public:
/// \param type Type of a counter to get the value of
///
/// \return the value of the counter.
+
uint64_t getCounter(const AuthCounters::ServerCounterType type) const;
+ /// \brief Get the value of per Opcode counter in the Auth Counters.
+ ///
+ /// This function calls AuthCounters::getCounter(isc::dns::Opcode) and
+ /// returns its return value.
+ ///
+ /// \note This is a tentative interface as an attempt of experimentally
+ /// supporting more statistics counters. This should eventually be more
+ /// generalized. In any case, this method is mainly for testing.
+ ///
+ /// \throw None
+ /// \param opcode The opcode of the counter to get the value of
+ /// \return the value of the counter.
+ uint64_t getCounter(const isc::dns::Opcode opcode) const;
+
+ /// \brief Get the value of per Rcode counter in the Auth Counters.
+ ///
+ /// This function calls AuthCounters::getCounter(isc::dns::Rcode) and
+ /// returns its return value.
+ ///
+ /// \note This is a tentative interface as an attempt of experimentally
+ /// supporting more statistics counters. This should eventually be more
+ /// generalized. In any case, this method is mainly for testing.
+ ///
+ /// \throw None
+ /// \param rcode The rcode of the counter to get the value of
+ /// \return the value of the counter.
+ uint64_t getCounter(const isc::dns::Rcode rcode) const;
+
/**
* \brief Set and get the addresses we listen on.
*/
@@ -354,7 +385,7 @@ public:
const;
/// \brief Assign an ASIO DNS Service queue to this Auth object
- void setDNSService(isc::asiodns::DNSService& dnss);
+ void setDNSService(isc::asiodns::DNSServiceBase& dnss);
/// \brief Sets the keyring used for verifying and signing
///
@@ -370,7 +401,7 @@ private:
isc::asiolink::SimpleCallback* checkin_;
isc::asiodns::DNSLookup* dns_lookup_;
isc::asiodns::DNSAnswer* dns_answer_;
- isc::asiodns::DNSService* dnss_;
+ isc::asiodns::DNSServiceBase* dnss_;
};
#endif // __AUTH_SRV_H
diff --git a/src/bin/auth/b10-auth.8 b/src/bin/auth/b10-auth.8
index aedadee..a5ef4fb 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: August 11, 2011
+.\" Date: March 28, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-AUTH" "8" "August 11, 2011" "BIND10" "BIND10"
+.TH "B10\-AUTH" "8" "March 28, 2012" "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\-n\fR] [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR]
+\fBb10\-auth\fR [\fB\-n\fR] [\fB\-v\fR]
.SH "DESCRIPTION"
.PP
The
@@ -47,15 +47,6 @@ The arguments are as follows:
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
\fB\-v\fR
.RS 4
Enabled verbose mode\&. This enables diagnostic messages to STDERR\&.
@@ -73,7 +64,7 @@ defines the path to the SQLite3 zone file when using the sqlite datasource\&. Th
\fIdatasources\fR
configures data sources\&. The list items include:
\fItype\fR
-to optionally choose the data source type (such as
+to define the required data source type (such as
\(lqmemory\(rq);
\fIclass\fR
to optionally select the class (it defaults to
@@ -163,21 +154,25 @@ immediately\&.
\fBshutdown\fR
exits
-\fBb10\-auth\fR\&. (Note that the BIND 10 boss process will restart this service\&.)
+\fBb10\-auth\fR\&. This has an optional
+\fIpid\fR
+argument to select the process ID to stop\&. (Note that the BIND 10 boss process may restart this service if configured\&.)
.SH "STATISTICS DATA"
.PP
The statistics data collected by the
\fBb10\-stats\fR
-daemon include:
+daemon for
+\(lqAuth\(rq
+include:
.PP
-auth\&.queries\&.tcp
+queries\&.tcp
.RS 4
Total count of queries received by the
\fBb10\-auth\fR
server over TCP since startup\&.
.RE
.PP
-auth\&.queries\&.udp
+queries\&.udp
.RS 4
Total count of queries received by the
\fBb10\-auth\fR
@@ -207,5 +202,5 @@ The
daemon was first coded in October 2009\&.
.SH "COPYRIGHT"
.br
-Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")
+Copyright \(co 2010-2012 Internet Systems Consortium, Inc. ("ISC")
.br
diff --git a/src/bin/auth/b10-auth.xml b/src/bin/auth/b10-auth.xml
index 636f437..7f3a492 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-2011 Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2010-2012 Internet Systems Consortium, Inc. ("ISC")
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>August 11, 2011</date>
+ <date>March 28, 2012</date>
</refentryinfo>
<refmeta>
@@ -36,7 +36,7 @@
<docinfo>
<copyright>
- <year>2010</year>
+ <year>2010-2012</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
@@ -45,7 +45,6 @@
<cmdsynopsis>
<command>b10-auth</command>
<arg><option>-n</option></arg>
- <arg><option>-u <replaceable>username</replaceable></option></arg>
<arg><option>-v</option></arg>
</cmdsynopsis>
</refsynopsisdiv>
@@ -93,20 +92,6 @@
</varlistentry>
<varlistentry>
- <term><option>-u <replaceable>username</replaceable></option></term>
- <listitem>
- <para>
- The user name of the <command>b10-auth</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
@@ -134,7 +119,7 @@
<para>
<varname>datasources</varname> configures data sources.
The list items include:
- <varname>type</varname> to optionally choose the data source type
+ <varname>type</varname> to define the required data source type
(such as <quote>memory</quote>);
<varname>class</varname> to optionally select the class
(it defaults to <quote>IN</quote>);
@@ -203,7 +188,10 @@
<para>
<command>shutdown</command> exits <command>b10-auth</command>.
- (Note that the BIND 10 boss process will restart this service.)
+ This has an optional <varname>pid</varname> argument to
+ select the process ID to stop.
+ (Note that the BIND 10 boss process may restart this service
+ if configured.)
</para>
</refsect1>
@@ -213,20 +201,20 @@
<para>
The statistics data collected by the <command>b10-stats</command>
- daemon include:
+ daemon for <quote>Auth</quote> include:
</para>
<variablelist>
<varlistentry>
- <term>auth.queries.tcp</term>
+ <term>queries.tcp</term>
<listitem><simpara>Total count of queries received by the
<command>b10-auth</command> server over TCP since startup.
</simpara></listitem>
</varlistentry>
<varlistentry>
- <term>auth.queries.udp</term>
+ <term>queries.udp</term>
<listitem><simpara>Total count of queries received by the
<command>b10-auth</command> server over UDP since startup.
</simpara></listitem>
@@ -234,6 +222,8 @@
</variablelist>
+<!-- TODO: missing stats docs. See ticket #1721 -->
+
</refsect1>
<refsect1>
diff --git a/src/bin/auth/benchmarks/.gitignore b/src/bin/auth/benchmarks/.gitignore
new file mode 100644
index 0000000..24e9944
--- /dev/null
+++ b/src/bin/auth/benchmarks/.gitignore
@@ -0,0 +1 @@
+/query_bench
diff --git a/src/bin/auth/benchmarks/Makefile.am b/src/bin/auth/benchmarks/Makefile.am
index da6a5c8..2de01ff 100644
--- a/src/bin/auth/benchmarks/Makefile.am
+++ b/src/bin/auth/benchmarks/Makefile.am
@@ -4,6 +4,10 @@ AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
CLEANFILES = *.gcno *.gcda
noinst_PROGRAMS = query_bench
diff --git a/src/bin/auth/benchmarks/query_bench.cc b/src/bin/auth/benchmarks/query_bench.cc
index ba2e7b2..aa238c0 100644
--- a/src/bin/auth/benchmarks/query_bench.cc
+++ b/src/bin/auth/benchmarks/query_bench.cc
@@ -30,6 +30,8 @@
#include <dns/question.h>
#include <dns/rrclass.h>
+#include <log/logger_support.h>
+
#include <xfr/xfrout_client.h>
#include <auth/auth_srv.h>
@@ -44,6 +46,7 @@ using namespace isc;
using namespace isc::data;
using namespace isc::auth;
using namespace isc::dns;
+using namespace isc::log;
using namespace isc::util;
using namespace isc::xfr;
using namespace isc::bench;
@@ -73,8 +76,8 @@ private:
typedef boost::shared_ptr<const IOEndpoint> IOEndpointPtr;
protected:
QueryBenchMark(const bool enable_cache,
- const BenchQueries& queries, MessagePtr query_message,
- OutputBufferPtr buffer) :
+ const BenchQueries& queries, Message& query_message,
+ OutputBuffer& buffer) :
server_(new AuthSrv(enable_cache, xfrout_client)),
queries_(queries),
query_message_(query_message),
@@ -92,8 +95,8 @@ public:
for (query = queries_.begin(); query != query_end; ++query) {
IOMessage io_message(&(*query)[0], (*query).size(), dummy_socket,
*dummy_endpoint);
- query_message_->clear(Message::PARSE);
- buffer_->clear();
+ query_message_.clear(Message::PARSE);
+ buffer_.clear();
server_->processMessage(io_message, query_message_, buffer_,
&server);
}
@@ -104,8 +107,8 @@ protected:
AuthSrvPtr server_;
private:
const BenchQueries& queries_;
- MessagePtr query_message_;
- OutputBufferPtr buffer_;
+ Message& query_message_;
+ OutputBuffer& buffer_;
IOSocket& dummy_socket;
IOEndpointPtr dummy_endpoint;
};
@@ -115,8 +118,8 @@ public:
Sqlite3QueryBenchMark(const int cache_slots,
const char* const datasrc_file,
const BenchQueries& queries,
- MessagePtr query_message,
- OutputBufferPtr buffer) :
+ Message& query_message,
+ OutputBuffer& buffer) :
QueryBenchMark(cache_slots >= 0 ? true : false, queries,
query_message, buffer)
{
@@ -133,8 +136,8 @@ public:
MemoryQueryBenchMark(const char* const zone_file,
const char* const zone_origin,
const BenchQueries& queries,
- MessagePtr query_message,
- OutputBufferPtr buffer) :
+ Message& query_message,
+ OutputBuffer& buffer) :
QueryBenchMark(false, queries, query_message, buffer)
{
configureAuthServer(*server_,
@@ -231,6 +234,10 @@ main(int argc, char* argv[]) {
const char* const datasrc_file = argv[0];
const char* const query_data_file = argv[1];
+ // We disable logging to avoid unwanted noise. (We may eventually want to
+ // make it more configurable)
+ initLogger("query-bench", isc::log::NONE);
+
DataSrcType datasrc_type = SQLITE3;
if (strcmp(opt_datasrc_type, "sqlite3") == 0) {
; // no need to override
@@ -248,8 +255,8 @@ main(int argc, char* argv[]) {
BenchQueries queries;
loadQueryData(query_data_file, queries, RRClass::IN());
- OutputBufferPtr buffer(new OutputBuffer(4096));
- MessagePtr message(new Message(Message::PARSE));
+ OutputBuffer buffer(4096);
+ Message message(Message::PARSE);
cout << "Parameters:" << endl;
cout << " Iterations: " << iteration << endl;
diff --git a/src/bin/auth/change_user.cc b/src/bin/auth/change_user.cc
deleted file mode 100644
index 253b8fb..0000000
--- a/src/bin/auth/change_user.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <errno.h>
-#include <string.h>
-#include <pwd.h>
-#include <unistd.h>
-
-#include <boost/lexical_cast.hpp>
-
-#include <exceptions/exceptions.h>
-
-#include <auth/common.h>
-
-using namespace boost;
-using namespace std;
-
-void
-changeUser(const char* const username) {
- const struct passwd *runas_pw = NULL;
-
- runas_pw = getpwnam(username);
- endpwent();
- if (runas_pw == NULL) {
- try {
- runas_pw = getpwuid(lexical_cast<uid_t>(username));
- endpwent();
- } catch (const bad_lexical_cast&) {
- ; // fall through to isc_throw below.
- }
- }
- if (runas_pw == NULL) {
- throw FatalError("Unknown user name or UID:" + string(username));
- }
-
- if (setgid(runas_pw->pw_gid) < 0) {
- throw FatalError("setgid() failed: " + string(strerror(errno)));
- }
-
- if (setuid(runas_pw->pw_uid) < 0) {
- throw FatalError("setuid() failed: " + string(strerror(errno)));
- }
-}
diff --git a/src/bin/auth/change_user.h b/src/bin/auth/change_user.h
deleted file mode 100644
index e4fc5ee..0000000
--- a/src/bin/auth/change_user.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef __CHANGE_USER_H
-#define __CHANGE_USER_H 1
-
-/// \brief Change the run time user.
-///
-/// This function changes the user and its group of the authoritative server
-/// process.
-///
-/// On success the user ID of the process is changed to the specified user,
-/// and the group is changed to that of the new user.
-///
-/// This is considered a short term workaround until we develop clearer
-/// privilege separation, where the server won't even have to open privileged
-/// ports and can be started by a non privileged user from the beginning.
-/// This function therefore ignores some corner case problems (see below)
-/// which we would address otherwise.
-///
-/// \c username can be either a textual user name or its numeric ID.
-/// If the specified user name (or ID) doesn't specify a local user ID
-/// or the user originally starting the process doesn't have a permission
-/// of changing the user to \c username, this function throws an exception
-/// of class \c FatalError.
-///
-/// This function internally uses system libraries that do not guarantee
-/// reentrancy. In fact, it doesn't even expect to be called more than once.
-/// The behavior is undefined if this function is called from multiple threads
-/// simultaneously or more generally called multiple times.
-///
-/// This function only offers the basic exception guarantee, that is, if
-/// an exception is thrown from this function, it's possible that an exception
-/// is thrown after changing the group ID. This function doesn't recover
-/// from that situation. In practice, the process is expected to consider
-/// this event a fatal error and will immediately exit, and shouldn't cause
-/// a real trouble.
-///
-/// \param username User name or ID of the new effective user.
-void changeUser(const char* const username);
-
-#endif // __CHANGE_USER_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/bin/auth/command.cc b/src/bin/auth/command.cc
index 280558d..055c73a 100644
--- a/src/bin/auth/command.cc
+++ b/src/bin/auth/command.cc
@@ -12,24 +12,24 @@
// 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 <auth/command.h>
+#include <auth/auth_log.h>
+#include <auth/auth_srv.h>
+#include <cc/data.h>
+#include <datasrc/memory_datasrc.h>
+#include <datasrc/factory.h>
+#include <config/ccsession.h>
#include <exceptions/exceptions.h>
-
#include <dns/rrclass.h>
-#include <cc/data.h>
-
-#include <datasrc/memory_datasrc.h>
+#include <string>
-#include <config/ccsession.h>
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
-#include <auth/auth_log.h>
-#include <auth/auth_srv.h>
-#include <auth/command.h>
+#include <sys/types.h>
+#include <unistd.h>
using boost::scoped_ptr;
using namespace isc::auth;
@@ -104,10 +104,30 @@ public:
virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) = 0;
};
-// Handle the "shutdown" command. No argument is assumed.
+// Handle the "shutdown" command. An optional parameter "pid" is used to
+// see if it is really for our instance.
class ShutdownCommand : public AuthCommand {
public:
- virtual void exec(AuthSrv& server, isc::data::ConstElementPtr) {
+ virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) {
+ // Is the pid argument provided?
+ if (args && args->contains("pid")) {
+ // If it is, we check it is the same as our PID
+
+ // This might throw in case the type is not an int, but that's
+ // OK, as it'll get converted to an error on higher level.
+ const int pid(args->get("pid")->intValue());
+ const pid_t my_pid(getpid());
+ if (my_pid != pid) {
+ // It is not for us
+ //
+ // Note that this is completely expected situation, if
+ // there are multiple instances of the server running and
+ // another instance is being shut down, we get the message
+ // too, due to the multicast nature of our message bus.
+ return;
+ }
+ }
+ LOG_DEBUG(auth_logger, DBG_AUTH_SHUT, AUTH_SHUTDOWN);
server.stop();
}
};
@@ -130,26 +150,39 @@ public:
return;
}
+ const ConstElementPtr zone_config = getZoneConfig(server);
+
// 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.
+ const ConstElementPtr type(zone_config->get("filetype"));
boost::shared_ptr<InMemoryZoneFinder> zone_finder(
- new InMemoryZoneFinder(old_zone_finder->getClass(),
- old_zone_finder->getOrigin()));
- zone_finder->load(old_zone_finder->getFileName());
- old_zone_finder->swap(*zone_finder);
+ new InMemoryZoneFinder(old_zone_finder_->getClass(),
+ old_zone_finder_->getOrigin()));
+ if (type && type->stringValue() == "sqlite3") {
+ scoped_ptr<DataSourceClientContainer>
+ container(new DataSourceClientContainer("sqlite3",
+ Element::fromJSON(
+ "{\"database_file\": \"" +
+ zone_config->get("file")->stringValue() + "\"}")));
+ zone_finder->load(*container->getInstance().getIterator(
+ old_zone_finder_->getOrigin()));
+ } else {
+ zone_finder->load(old_zone_finder_->getFileName());
+ }
+ old_zone_finder_->swap(*zone_finder);
LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_LOAD_ZONE)
.arg(zone_finder->getOrigin()).arg(zone_finder->getClass());
}
private:
// zone finder to be updated with the new file.
- boost::shared_ptr<InMemoryZoneFinder> old_zone_finder;
+ boost::shared_ptr<InMemoryZoneFinder> old_zone_finder_;
// A helper private method to parse and validate command parameters.
- // On success, it sets 'old_zone_finder' to the zone to be updated.
+ // On success, it sets 'old_zone_finder_' 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) {
@@ -174,10 +207,11 @@ private:
}
ConstElementPtr class_elem = args->get("class");
- const RRClass zone_class = class_elem ?
- RRClass(class_elem->stringValue()) : RRClass::IN();
+ const RRClass zone_class =
+ class_elem ? RRClass(class_elem->stringValue()) : RRClass::IN();
- AuthSrv::InMemoryClientPtr datasrc(server.getInMemoryClient(zone_class));
+ AuthSrv::InMemoryClientPtr datasrc(server.
+ getInMemoryClient(zone_class));
if (datasrc == NULL) {
isc_throw(AuthCommandError, "Memory data source is disabled");
}
@@ -186,7 +220,7 @@ private:
if (!origin_elem) {
isc_throw(AuthCommandError, "Zone origin is missing");
}
- const Name origin(origin_elem->stringValue());
+ const Name origin = Name(origin_elem->stringValue());
// Get the current zone
const InMemoryClient::FindResult result = datasrc->findZone(origin);
@@ -195,11 +229,70 @@ private:
" is not found in data source");
}
- old_zone_finder = boost::dynamic_pointer_cast<InMemoryZoneFinder>(
+ old_zone_finder_ = boost::dynamic_pointer_cast<InMemoryZoneFinder>(
result.zone_finder);
return (true);
}
+
+ ConstElementPtr getZoneConfig(const AuthSrv &server) {
+ if (!server.getConfigSession()) {
+ // FIXME: This is a hack to make older tests pass. We should
+ // update these tests as well sometime and remove this hack.
+ // (note that under normal situation, the
+ // server.getConfigSession() does not return NULL)
+
+ // We provide an empty map, which means no configuration --
+ // defaults.
+ return (ConstElementPtr(new MapElement()));
+ }
+
+ // Find the config corresponding to the zone.
+ // We expect the configuration to be valid, as we have it and we
+ // accepted it before, therefore it must be validated.
+ const ConstElementPtr config(server.getConfigSession()->
+ getValue("datasources"));
+ ConstElementPtr zone_list;
+ // Unfortunately, we need to walk the list to find the correct data
+ // source.
+ // TODO: Make it named sets. These lists are uncomfortable.
+ for (size_t i(0); i < config->size(); ++i) {
+ // We use the getValue to get defaults as well
+ const ConstElementPtr dsrc_config(config->get(i));
+ const ConstElementPtr class_config(dsrc_config->get("class"));
+ const string class_type(class_config ?
+ class_config->stringValue() : "IN");
+ // It is in-memory and our class matches.
+ // FIXME: Is it allowed to have two datasources for the same
+ // type and class at once? It probably would not work now
+ // anyway and we may want to change the configuration of
+ // datasources somehow.
+ if (dsrc_config->get("type")->stringValue() == "memory" &&
+ RRClass(class_type) == old_zone_finder_->getClass()) {
+ zone_list = dsrc_config->get("zones");
+ break;
+ }
+ }
+
+ if (!zone_list) {
+ isc_throw(AuthCommandError,
+ "Corresponding data source configuration was not found");
+ }
+
+ // Now we need to walk the zones and find the correct one.
+ for (size_t i(0); i < zone_list->size(); ++i) {
+ const ConstElementPtr zone_config(zone_list->get(i));
+ if (Name(zone_config->get("origin")->stringValue()) ==
+ old_zone_finder_->getOrigin()) {
+ // The origins are the same, so we consider this config to be
+ // for the zone.
+ return (zone_config);
+ }
+ }
+
+ isc_throw(AuthCommandError,
+ "Corresponding zone configuration was not found");
+ }
};
// The factory of command objects.
diff --git a/src/bin/auth/common.cc b/src/bin/auth/common.cc
index a7031f3..1602a1a 100644
--- a/src/bin/auth/common.cc
+++ b/src/bin/auth/common.cc
@@ -37,3 +37,5 @@ getXfroutSocketPath() {
}
}
}
+
+const char* const AUTH_NAME = "b10-auth";
diff --git a/src/bin/auth/common.h b/src/bin/auth/common.h
index b913593..cf71214 100644
--- a/src/bin/auth/common.h
+++ b/src/bin/auth/common.h
@@ -38,6 +38,11 @@ public:
/// The logic should be the same as in b10-xfrout, so they find each other.
std::string getXfroutSocketPath();
+/// \brief The name used when identifieng the process
+///
+/// This is currently b10-auth, but it can be changed easily in one place.
+extern const char* const AUTH_NAME;
+
#endif // __COMMON_H
// Local Variables:
diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc
index c8f6762..fc2f751 100644
--- a/src/bin/auth/main.cc
+++ b/src/bin/auth/main.cc
@@ -42,13 +42,13 @@
#include <auth/common.h>
#include <auth/auth_config.h>
#include <auth/command.h>
-#include <auth/change_user.h>
#include <auth/auth_srv.h>
#include <auth/auth_log.h>
#include <asiodns/asiodns.h>
#include <asiolink/asiolink.h>
#include <log/logger_support.h>
#include <server_common/keyring.h>
+#include <server_common/socket_request.h>
using namespace std;
using namespace isc::asiodns;
@@ -85,7 +85,6 @@ usage() {
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);
}
@@ -95,7 +94,6 @@ usage() {
int
main(int argc, char* argv[]) {
int ch;
- const char* uid = NULL;
bool cache = true;
bool verbose = false;
@@ -104,9 +102,6 @@ main(int argc, char* argv[]) {
case 'n':
cache = false;
break;
- case 'u':
- uid = optarg;
- break;
case 'v':
verbose = true;
break;
@@ -121,7 +116,7 @@ main(int argc, char* argv[]) {
}
// Initialize logging. If verbose, we'll use maximum verbosity.
- isc::log::initLogger("b10-auth",
+ isc::log::initLogger(AUTH_NAME,
(verbose ? isc::log::DEBUG : isc::log::INFO),
isc::log::MAX_DEBUG_LEVEL, NULL);
@@ -158,12 +153,16 @@ main(int argc, char* argv[]) {
cc_session = new Session(io_service.get_io_service());
LOG_DEBUG(auth_logger, DBG_AUTH_START, AUTH_CONFIG_CHANNEL_CREATED);
+ // Initialize the Socket Requestor
+ isc::server_common::initSocketRequestor(*cc_session, AUTH_NAME);
// We delay starting listening to new commands/config just before we
// go into the main loop to avoid confusion due to mixture of
// synchronous and asynchronous operations (this would happen in
- // initializing TSIG keys below). Until then all operations on the
- // CC session will take place synchronously.
+ // initial communication with the boss that takes place in
+ // updateConfig() for listen_on and in initializing TSIG keys below).
+ // Until then all operations on the CC session will take place
+ // synchronously.
config_session = new ModuleCCSession(specfile, *cc_session,
my_config_handler,
my_command_handler, false);
@@ -198,10 +197,6 @@ main(int argc, char* argv[]) {
LOG_ERROR(auth_logger, AUTH_CONFIG_LOAD_FAIL).arg(ex.what());
}
- if (uid != NULL) {
- changeUser(uid);
- }
-
LOG_DEBUG(auth_logger, DBG_AUTH_START, AUTH_LOAD_TSIG);
isc::server_common::initKeyring(*config_session);
auth_server->setTSIGKeyRing(&isc::server_common::keyring);
diff --git a/src/bin/auth/query.cc b/src/bin/auth/query.cc
index 2bf8f5e..f215c04 100644
--- a/src/bin/auth/query.cc
+++ b/src/bin/auth/query.cc
@@ -12,98 +12,103 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <algorithm> // for std::max
-#include <vector>
-#include <boost/foreach.hpp>
-#include <boost/bind.hpp>
-#include <boost/function.hpp>
-
#include <dns/message.h>
#include <dns/rcode.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
#include <dns/rdataclass.h>
#include <datasrc/client.h>
#include <auth/query.h>
+#include <boost/foreach.hpp>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+
+#include <cassert>
+#include <algorithm> // for std::max
+#include <functional>
+#include <vector>
+
+using namespace std;
using namespace isc::dns;
using namespace isc::datasrc;
using namespace isc::dns::rdata;
+// This is a "constant" vector storing desired RR types for the additional
+// section. The vector is filled first time it's used.
+namespace {
+const vector<RRType>&
+A_AND_AAAA() {
+ static vector<RRType> needed_types;
+ if (needed_types.empty()) {
+ needed_types.push_back(RRType::A());
+ needed_types.push_back(RRType::AAAA());
+ }
+ return (needed_types);
+}
+}
+
namespace isc {
namespace auth {
void
-Query::addAdditional(ZoneFinder& zone, const RRset& rrset) {
- 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);
- addAdditionalAddrs(zone, ns.getNSName(), ZoneFinder::FIND_GLUE_OK);
- } else if (rrset.getType() == RRType::MX()) {
- const generic::MX& mx(dynamic_cast<const generic::MX&>(rdata));
- addAdditionalAddrs(zone, mx.getMXName());
- }
+Query::ResponseCreator::addRRset(isc::dns::Message& message,
+ const isc::dns::Message::Section section,
+ const ConstRRsetPtr& rrset, const bool dnssec)
+{
+ /// Is this RRset already in the list of RRsets added to the message?
+ const std::vector<const AbstractRRset*>::const_iterator i =
+ std::find_if(added_.begin(), added_.end(),
+ std::bind1st(Query::ResponseCreator::IsSameKind(),
+ rrset.get()));
+ if (i == added_.end()) {
+ // No - add it to both the message and the list of RRsets processed.
+ // The const-cast is wrong, but the message interface seems to insist.
+ message.addRRset(section,
+ boost::const_pointer_cast<AbstractRRset>(rrset),
+ dnssec);
+ added_.push_back(rrset.get());
}
}
void
-Query::addAdditionalAddrs(ZoneFinder& zone, const Name& qname,
- const ZoneFinder::FindOptions options)
+Query::ResponseCreator::create(Message& response,
+ const vector<ConstRRsetPtr>& answers,
+ const vector<ConstRRsetPtr>& authorities,
+ const vector<ConstRRsetPtr>& additionals,
+ const bool dnssec)
{
- // 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;
+ // Inserter should be reset each time the query is reset, so should be
+ // empty at this point.
+ assert(added_.empty());
- // Find A rrset
- if (qname_ != qname || qtype_ != RRType::A()) {
- ZoneFinder::FindResult a_result = zone.find(qname, RRType::A(),
- options | dnssec_opt_);
- if (a_result.code == ZoneFinder::SUCCESS) {
- response_.addRRset(Message::SECTION_ADDITIONAL,
- boost::const_pointer_cast<RRset>(a_result.rrset), dnssec_);
- }
+ // Add the RRsets to the message. The order of sections is important,
+ // as the ResponseCreator remembers RRsets added and will not add
+ // duplicates. Adding in the order answer, authory, additional will
+ // guarantee that if there are duplicates, the single RRset added will
+ // appear in the most important section.
+ BOOST_FOREACH(const ConstRRsetPtr& rrset, answers) {
+ addRRset(response, Message::SECTION_ANSWER, rrset, dnssec);
}
-
- // Find AAAA rrset
- if (qname_ != qname || qtype_ != RRType::AAAA()) {
- ZoneFinder::FindResult aaaa_result =
- zone.find(qname, RRType::AAAA(), options | dnssec_opt_);
- if (aaaa_result.code == ZoneFinder::SUCCESS) {
- response_.addRRset(Message::SECTION_ADDITIONAL,
- boost::const_pointer_cast<RRset>(aaaa_result.rrset),
- dnssec_);
- }
+ BOOST_FOREACH(const ConstRRsetPtr& rrset, authorities) {
+ addRRset(response, Message::SECTION_AUTHORITY, rrset, dnssec);
+ }
+ BOOST_FOREACH(const ConstRRsetPtr& rrset, additionals) {
+ addRRset(response, Message::SECTION_ADDITIONAL, rrset, dnssec);
}
}
void
Query::addSOA(ZoneFinder& finder) {
- ZoneFinder::FindResult soa_result(finder.find(finder.getOrigin(),
- RRType::SOA(), dnssec_opt_));
- if (soa_result.code != ZoneFinder::SUCCESS) {
+ ZoneFinderContextPtr soa_ctx = finder.find(finder.getOrigin(),
+ RRType::SOA(), dnssec_opt_);
+ if (soa_ctx->code != ZoneFinder::SUCCESS) {
isc_throw(NoSOA, "There's no SOA record in zone " <<
finder.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), dnssec_);
+ authorities_.push_back(soa_ctx->rrset);
}
}
@@ -116,14 +121,13 @@ Query::addSOA(ZoneFinder& finder) {
// either an SERVFAIL response or just ignoring the query. We at least prevent
// a complete crash due to such broken behavior.
void
-Query::addNXDOMAINProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
+Query::addNXDOMAINProofByNSEC(ZoneFinder& finder, ConstRRsetPtr nsec) {
if (nsec->getRdataCount() == 0) {
isc_throw(BadNSEC, "NSEC for NXDOMAIN is empty");
}
// Add the NSEC proving NXDOMAIN to the authority section.
- response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(nsec), dnssec_);
+ authorities_.push_back(nsec);
// Next, identify the best possible wildcard name that would match
// the query name. It's the longer common suffix with the qname
@@ -134,54 +138,112 @@ Query::addNXDOMAINProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
// and the best possible wildcard is *.b.example.com. If the NXDOMAIN
// NSEC is a.example.com. NSEC c.b.example.com., the longer suffix
// is the next domain of the NSEC, and we get the same wildcard name.
- const int qlabels = qname_.getLabelCount();
- const int olabels = qname_.compare(nsec->getName()).getCommonLabels();
- const int nlabels = qname_.compare(
+ const int qlabels = qname_->getLabelCount();
+ const int olabels = qname_->compare(nsec->getName()).getCommonLabels();
+ const int nlabels = qname_->compare(
dynamic_cast<const generic::NSEC&>(nsec->getRdataIterator()->
getCurrent()).
getNextName()).getCommonLabels();
const int common_labels = std::max(olabels, nlabels);
- const Name wildname(Name("*").concatenate(qname_.split(qlabels -
+ const Name wildname(Name("*").concatenate(qname_->split(qlabels -
common_labels)));
// Confirm the wildcard doesn't exist (this should result in NXDOMAIN;
// otherwise we shouldn't have got NXDOMAIN for the original query in
// the first place).
- const ZoneFinder::FindResult fresult = finder.find(wildname,
- RRType::NSEC(),
- dnssec_opt_);
- if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
- fresult.rrset->getRdataCount() == 0) {
+ ConstZoneFinderContextPtr fcontext =
+ finder.find(wildname, RRType::NSEC(), dnssec_opt_);
+ if (fcontext->code != ZoneFinder::NXDOMAIN || !fcontext->rrset ||
+ fcontext->rrset->getRdataCount() == 0) {
isc_throw(BadNSEC, "Unexpected result for wildcard NXDOMAIN proof");
}
- // Add the (no-) wildcard proof only when it's different from the NSEC
- // that proves NXDOMAIN; sometimes they can be the same.
- // Note: name comparison is relatively expensive. When we are at the
- // stage of performance optimization, we should consider optimizing this
- // for some optimized data source implementations.
- if (nsec->getName() != fresult.rrset->getName()) {
- response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(fresult.rrset),
- dnssec_);
+ // Add the (no-) wildcard proof. This can be the same NSEC we already
+ // added, but we'd add it here anyway; duplicate checks will take place
+ // later in a unified manner.
+ authorities_.push_back(fcontext->rrset);
+}
+
+uint8_t
+Query::addClosestEncloserProof(ZoneFinder& finder, const Name& name,
+ bool exact_ok, bool add_closest)
+{
+ const ZoneFinder::FindNSEC3Result result = finder.findNSEC3(name, true);
+
+ // Validity check (see the method description). Note that a completely
+ // broken findNSEC3 implementation could even return NULL RRset in
+ // closest_proof. We don't explicitly check such case; addRRset() will
+ // throw an exception, and it will be converted to SERVFAIL at the caller.
+ if (!exact_ok && !result.next_proof) {
+ isc_throw(BadNSEC3, "Matching NSEC3 found for a non existent name: "
+ << qname_);
}
+
+ if (add_closest) {
+ authorities_.push_back(result.closest_proof);
+ }
+ if (result.next_proof) {
+ authorities_.push_back(result.next_proof);
+ }
+ return (result.closest_labels);
}
void
-Query::addWildcardProof(ZoneFinder& finder) {
- // The query name shouldn't exist in the zone if there were no wildcard
- // substitution. Confirm that by specifying NO_WILDCARD. It should result
- // in NXDOMAIN and an NSEC RR that proves it should be returned.
- const ZoneFinder::FindResult fresult =
- finder.find(qname_, RRType::NSEC(),
- dnssec_opt_ | ZoneFinder::NO_WILDCARD);
- if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
- fresult.rrset->getRdataCount() == 0) {
- isc_throw(BadNSEC, "Unexpected result for wildcard proof");
+Query::addNSEC3ForName(ZoneFinder& finder, const Name& name, bool match) {
+ const ZoneFinder::FindNSEC3Result result = finder.findNSEC3(name, false);
+
+ // See the comment for addClosestEncloserProof(). We don't check a
+ // totally bogus case where closest_proof is NULL here.
+ if (match != result.matched) {
+ isc_throw(BadNSEC3, "Unexpected "
+ << (result.matched ? "matching" : "covering")
+ << " NSEC3 found for " << name);
+ }
+ authorities_.push_back(result.closest_proof);
+}
+
+void
+Query::addNXDOMAINProofByNSEC3(ZoneFinder& finder) {
+ // Firstly get the NSEC3 proves for Closest Encloser Proof
+ // See Section 7.2.1 of RFC 5155.
+ const uint8_t closest_labels =
+ addClosestEncloserProof(finder, *qname_, false);
+
+ // Next, construct the wildcard name at the closest encloser, i.e.,
+ // '*' followed by the closest encloser, and add NSEC3 for it.
+ const Name wildname(Name("*").concatenate(
+ qname_->split(qname_->getLabelCount() - closest_labels)));
+ addNSEC3ForName(finder, wildname, false);
+}
+
+void
+Query::addWildcardProof(ZoneFinder& finder,
+ const ZoneFinder::Context& db_context)
+{
+ if (db_context.isNSECSigned()) {
+ // Case for RFC4035 Section 3.1.3.3.
+ //
+ // The query name shouldn't exist in the zone if there were no wildcard
+ // substitution. Confirm that by specifying NO_WILDCARD. It should
+ // result in NXDOMAIN and an NSEC RR that proves it should be returned.
+ ConstZoneFinderContextPtr fcontext =
+ finder.find(*qname_, RRType::NSEC(),
+ dnssec_opt_ | ZoneFinder::NO_WILDCARD);
+ if (fcontext->code != ZoneFinder::NXDOMAIN || !fcontext->rrset ||
+ fcontext->rrset->getRdataCount() == 0) {
+ isc_throw(BadNSEC,
+ "Unexpected NSEC result for wildcard proof");
+ }
+ authorities_.push_back(fcontext->rrset);
+ } else if (db_context.isNSEC3Signed()) {
+ // Case for RFC 5155 Section 7.2.6.
+ //
+ // Note that the closest encloser must be the immediate ancestor
+ // of the matching wildcard, so NSEC3 for its next closer (and only
+ // that NSEC3) is what we are expected to provided per the RFC (if
+ // this assumption isn't met the zone is broken anyway).
+ addClosestEncloserProof(finder, *qname_, false, false);
}
- response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(fresult.rrset),
- dnssec_);
}
void
@@ -191,50 +253,118 @@ Query::addWildcardNXRRSETProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
if (nsec->getRdataCount() == 0) {
isc_throw(BadNSEC, "NSEC for WILDCARD_NXRRSET is empty");
}
- // Add this NSEC RR to authority section.
- response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(nsec), dnssec_);
- const ZoneFinder::FindResult fresult =
- finder.find(qname_, RRType::NSEC(),
+ ConstZoneFinderContextPtr fcontext =
+ finder.find(*qname_, RRType::NSEC(),
dnssec_opt_ | ZoneFinder::NO_WILDCARD);
- if (fresult.code != ZoneFinder::NXDOMAIN || !fresult.rrset ||
- fresult.rrset->getRdataCount() == 0) {
+ if (fcontext->code != ZoneFinder::NXDOMAIN || !fcontext->rrset ||
+ fcontext->rrset->getRdataCount() == 0) {
isc_throw(BadNSEC, "Unexpected result for no match QNAME proof");
}
-
- if (nsec->getName() != fresult.rrset->getName()) {
- // one NSEC RR proves wildcard_nxrrset that no matched QNAME.
- response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(fresult.rrset),
- dnssec_);
+
+ authorities_.push_back(fcontext->rrset);
+}
+
+void
+Query::addDS(ZoneFinder& finder, const Name& dname) {
+ ConstZoneFinderContextPtr ds_context =
+ finder.find(dname, RRType::DS(), dnssec_opt_);
+ if (ds_context->code == ZoneFinder::SUCCESS) {
+ authorities_.push_back(ds_context->rrset);
+ } else if (ds_context->code == ZoneFinder::NXRRSET &&
+ ds_context->isNSECSigned()) {
+ addNXRRsetProof(finder, *ds_context);
+ } else if (ds_context->code == ZoneFinder::NXRRSET &&
+ ds_context->isNSEC3Signed()) {
+ // Add no DS proof with NSEC3 as specified in RFC 5155 Section 7.2.7.
+ addClosestEncloserProof(finder, dname, true);
+ } else if (ds_context->code != ZoneFinder::NXRRSET) {
+ // We know this domain should exist, so the result must be NXRRSET.
+ // If not, the zone is broken, so we'll return SERVFAIL by triggering
+ // an exception.
+ isc_throw(BadDS, "Unexpected result for DS lookup for delegation");
+ }
+}
+
+void
+Query::addNXRRsetProof(ZoneFinder& finder,
+ const ZoneFinder::Context& db_context)
+{
+ if (db_context.isNSECSigned() && db_context.rrset) {
+ authorities_.push_back(db_context.rrset);
+ if (db_context.isWildcard()) {
+ addWildcardNXRRSETProof(finder, db_context.rrset);
+ }
+ } else if (db_context.isNSEC3Signed() && !db_context.isWildcard()) {
+ if (*qtype_ == RRType::DS()) {
+ // RFC 5155, Section 7.2.4. Add either NSEC3 for the qname or
+ // closest (provable) encloser proof in case of optout.
+ addClosestEncloserProof(finder, *qname_, true);
+ } else {
+ // RFC 5155, Section 7.2.3. Just add NSEC3 for the qname.
+ addNSEC3ForName(finder, *qname_, true);
+ }
+ } else if (db_context.isNSEC3Signed() && db_context.isWildcard()) {
+ // Case for RFC 5155 Section 7.2.5: add closest encloser proof for the
+ // qname, construct the matched wildcard name and add NSEC3 for it.
+ const uint8_t closest_labels =
+ addClosestEncloserProof(finder, *qname_, false);
+ const Name wname = Name("*").concatenate(
+ qname_->split(qname_->getLabelCount() - closest_labels));
+ addNSEC3ForName(finder, wname, true);
}
}
-
+
void
-Query::addAuthAdditional(ZoneFinder& finder) {
+Query::addAuthAdditional(ZoneFinder& finder,
+ vector<ConstRRsetPtr>& additionals)
+{
+ const Name& origin = finder.getOrigin();
+
// Fill in authority and addtional sections.
- ZoneFinder::FindResult ns_result = finder.find(finder.getOrigin(),
- RRType::NS(), dnssec_opt_);
+ ConstZoneFinderContextPtr ns_context = finder.find(origin, RRType::NS(),
+ dnssec_opt_);
+
// zone origin name should have NS records
- if (ns_result.code != ZoneFinder::SUCCESS) {
+ if (ns_context->code != ZoneFinder::SUCCESS) {
isc_throw(NoApexNS, "There's no apex NS records in zone " <<
- finder.getOrigin().toText());
- } else {
- response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(ns_result.rrset), dnssec_);
- // Handle additional for authority section
- addAdditional(finder, *ns_result.rrset);
+ finder.getOrigin().toText());
+ }
+ authorities_.push_back(ns_context->rrset);
+ ns_context->getAdditional(A_AND_AAAA(), additionals);
+}
+
+namespace {
+// A simple wrapper for DataSourceClient::findZone(). Normally we can simply
+// check the closest zone to the qname, but for type DS query we need to
+// look into the parent zone. Nevertheless, if there is no "parent" (i.e.,
+// the qname consists of a single label, which also means it's the root name),
+// we should search the deepest zone we have (which should be the root zone;
+// otherwise it's a query error).
+DataSourceClient::FindResult
+findZone(const DataSourceClient& client, const Name& qname, RRType qtype) {
+ if (qtype != RRType::DS() || qname.getLabelCount() == 1) {
+ return (client.findZone(qname));
}
+ return (client.findZone(qname.split(1)));
+}
}
void
-Query::process() {
- const bool qtype_is_any = (qtype_ == RRType::ANY());
+Query::process(datasrc::DataSourceClient& datasrc_client,
+ const isc::dns::Name& qname, const isc::dns::RRType& qtype,
+ isc::dns::Message& response, bool dnssec)
+{
+ // Set up the cleaner object so internal pointers and vectors are
+ // always reset after scope leaves this method
+ QueryCleaner cleaner(*this);
- response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
- const DataSourceClient::FindResult result =
- datasrc_client_.findZone(qname_);
+ // Set up query parameters for the rest of the (internal) methods
+ initialize(datasrc_client, qname, qtype, response, dnssec);
+
+ // Found a zone which is the nearest ancestor to QNAME
+ const DataSourceClient::FindResult result = findZone(*datasrc_client_,
+ *qname_, *qtype_);
// 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
@@ -243,44 +373,51 @@ Query::process() {
// 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());
+ // If we tried to find a "parent zone" for a DS query and failed,
+ // we may still have authority at the child side. If we do, the query
+ // has to be handled there.
+ if (*qtype_ == RRType::DS() && qname_->getLabelCount() > 1 &&
+ processDSAtChild()) {
+ return;
+ }
+ response_->setHeaderFlag(Message::HEADERFLAG_AA, false);
+ response_->setRcode(Rcode::REFUSED());
return;
}
ZoneFinder& zfinder = *result.zone_finder;
- // Found a zone which is the nearest ancestor to QNAME, set the AA bit
- response_.setHeaderFlag(Message::HEADERFLAG_AA);
- response_.setRcode(Rcode::NOERROR());
- std::vector<ConstRRsetPtr> target;
- boost::function0<ZoneFinder::FindResult> find;
+ // We have authority for a zone that contain the query name (possibly
+ // indirectly via delegation). Look into the zone.
+ response_->setHeaderFlag(Message::HEADERFLAG_AA);
+ response_->setRcode(Rcode::NOERROR());
+ boost::function0<ZoneFinderContextPtr> find;
+ const bool qtype_is_any = (*qtype_ == RRType::ANY());
if (qtype_is_any) {
- find = boost::bind(&ZoneFinder::findAll, &zfinder, qname_,
- boost::ref(target), dnssec_opt_);
+ find = boost::bind(&ZoneFinder::findAll, &zfinder, *qname_,
+ boost::ref(answers_), dnssec_opt_);
} else {
- find = boost::bind(&ZoneFinder::find, &zfinder, qname_, qtype_,
+ find = boost::bind(&ZoneFinder::find, &zfinder, *qname_, *qtype_,
dnssec_opt_);
}
- ZoneFinder::FindResult db_result(find());
- switch (db_result.code) {
+ ZoneFinderContextPtr db_context(find());
+ switch (db_context->code) {
case ZoneFinder::DNAME: {
// First, put the dname into the answer
- response_.addRRset(Message::SECTION_ANSWER,
- boost::const_pointer_cast<RRset>(db_result.rrset),
- dnssec_);
+ answers_.push_back(db_context->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);
+ assert(db_context->rrset->getRdataCount() > 0);
// Get the data of DNAME
const rdata::generic::DNAME& dname(
dynamic_cast<const rdata::generic::DNAME&>(
- db_result.rrset->getRdataIterator()->getCurrent()));
+ db_context->rrset->getRdataIterator()->getCurrent()));
// The yet unmatched prefix dname
- const Name prefix(qname_.split(0, qname_.getLabelCount() -
- db_result.rrset->getName().getLabelCount()));
+ const Name prefix(qname_->split(0, qname_->getLabelCount() -
+ db_context->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() +
@@ -289,24 +426,23 @@ Query::process() {
* In case the synthesized name is too long, section 4.1
* of RFC 2672 mandates we return YXDOMAIN.
*/
- response_.setRcode(Rcode::YXDOMAIN());
- return;
+ response_->setRcode(Rcode::YXDOMAIN());
+ break;
}
// 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()));
+ RRsetPtr cname(new RRset(*qname_, db_context->rrset->getClass(),
+ RRType::CNAME(), db_context->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()).
+ cname->addRdata(rdata::generic::CNAME(qname_->split(0,
+ qname_->getLabelCount() -
+ db_context->rrset->getName().getLabelCount()).
concatenate(dname.getDname())));
- response_.addRRset(Message::SECTION_ANSWER, cname, dnssec_);
+ answers_.push_back(cname);
break;
}
case ZoneFinder::CNAME:
- case ZoneFinder::WILDCARD_CNAME:
/*
* We don't do chaining yet. Therefore handling a CNAME is
* mostly the same as handling SUCCESS, but we didn't get
@@ -316,78 +452,77 @@ Query::process() {
*
* So, just put it there.
*/
- response_.addRRset(Message::SECTION_ANSWER,
- boost::const_pointer_cast<RRset>(db_result.rrset),
- dnssec_);
+ answers_.push_back(db_context->rrset);
// If the answer is a result of wildcard substitution,
// add a proof that there's no closer name.
- if (dnssec_ && db_result.code == ZoneFinder::WILDCARD_CNAME) {
- addWildcardProof(*result.zone_finder);
+ if (dnssec_ && db_context->isWildcard()) {
+ addWildcardProof(*result.zone_finder, *db_context);
}
break;
case ZoneFinder::SUCCESS:
- case ZoneFinder::WILDCARD:
- if (qtype_is_any) {
- // If quety type is ANY, insert all RRs under the domain
- // into answer section.
- BOOST_FOREACH(ConstRRsetPtr rrset, target) {
- response_.addRRset(Message::SECTION_ANSWER,
- boost::const_pointer_cast<RRset>(rrset), dnssec_);
- // Handle additional for answer section
- addAdditional(*result.zone_finder, *rrset.get());
- }
- } else {
- response_.addRRset(Message::SECTION_ANSWER,
- boost::const_pointer_cast<RRset>(db_result.rrset),
- dnssec_);
- // Handle additional for answer section
- addAdditional(*result.zone_finder, *db_result.rrset);
+ // If query type is ANY, the rrs have already been added
+ if (!qtype_is_any) {
+ answers_.push_back(db_context->rrset);
}
+
+ // Retrieve additional records for the answer
+ db_context->getAdditional(A_AND_AAAA(), additionals_);
+
// 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_finder->getOrigin() ||
- db_result.code != ZoneFinder::SUCCESS ||
- (qtype_ != RRType::NS() && !qtype_is_any))
+ // Checking the findZone() is a lightweight check to see if
+ // qname is the zone origin.
+ if (result.code != result::SUCCESS ||
+ db_context->code != ZoneFinder::SUCCESS ||
+ (*qtype_ != RRType::NS() && !qtype_is_any))
{
- addAuthAdditional(*result.zone_finder);
+ addAuthAdditional(*result.zone_finder, additionals_);
}
// If the answer is a result of wildcard substitution,
// add a proof that there's no closer name.
- if (dnssec_ && db_result.code == ZoneFinder::WILDCARD) {
- addWildcardProof(*result.zone_finder);
+ if (dnssec_ && db_context->isWildcard()) {
+ addWildcardProof(*result.zone_finder, *db_context);
}
break;
case ZoneFinder::DELEGATION:
- response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
- response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(db_result.rrset),
- dnssec_);
- addAdditional(*result.zone_finder, *db_result.rrset);
+ // If a DS query resulted in delegation, we also need to check
+ // if we are an authority of the child, too. If so, we need to
+ // complete the process in the child as specified in Section
+ // 2.2.1.2. of RFC3658.
+ if (*qtype_ == RRType::DS() && processDSAtChild()) {
+ return;
+ }
+
+ response_->setHeaderFlag(Message::HEADERFLAG_AA, false);
+ authorities_.push_back(db_context->rrset);
+ // Retrieve additional records for the name servers
+ db_context->getAdditional(A_AND_AAAA(), additionals_);
+
+ // If DNSSEC is requested, see whether there is a DS
+ // record for this delegation.
+ if (dnssec_) {
+ addDS(*result.zone_finder, db_context->rrset->getName());
+ }
break;
case ZoneFinder::NXDOMAIN:
- response_.setRcode(Rcode::NXDOMAIN());
+ response_->setRcode(Rcode::NXDOMAIN());
addSOA(*result.zone_finder);
- if (dnssec_ && db_result.rrset) {
- addNXDOMAINProof(zfinder, db_result.rrset);
+ if (dnssec_) {
+ if (db_context->isNSECSigned() && db_context->rrset) {
+ addNXDOMAINProofByNSEC(zfinder, db_context->rrset);
+ } else if (db_context->isNSEC3Signed()) {
+ addNXDOMAINProofByNSEC3(zfinder);
+ }
}
break;
case ZoneFinder::NXRRSET:
addSOA(*result.zone_finder);
- if (dnssec_ && db_result.rrset) {
- response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(
- db_result.rrset),
- dnssec_);
- }
- break;
- case ZoneFinder::WILDCARD_NXRRSET:
- addSOA(*result.zone_finder);
- if (dnssec_ && db_result.rrset) {
- addWildcardNXRRSETProof(zfinder, db_result.rrset);
+ if (dnssec_) {
+ addNXRRsetProof(zfinder, *db_context);
}
break;
default:
@@ -397,6 +532,69 @@ Query::process() {
isc_throw(isc::NotImplemented, "Unknown result code");
break;
}
+
+ response_creator_.create(*response_, answers_, authorities_, additionals_,
+ dnssec_);
+}
+
+void
+Query::initialize(datasrc::DataSourceClient& datasrc_client,
+ const isc::dns::Name& qname, const isc::dns::RRType& qtype,
+ isc::dns::Message& response, bool dnssec)
+{
+ datasrc_client_ = &datasrc_client;
+ qname_ = &qname;
+ qtype_ = &qtype;
+ response_ = &response;
+ dnssec_ = dnssec;
+ dnssec_opt_ = (dnssec ? isc::datasrc::ZoneFinder::FIND_DNSSEC :
+ isc::datasrc::ZoneFinder::FIND_DEFAULT);
+}
+
+void
+Query::reset() {
+ datasrc_client_ = NULL;
+ qname_ = NULL;
+ qtype_ = NULL;
+ response_ = NULL;
+ answers_.clear();
+ authorities_.clear();
+ additionals_.clear();
+ response_creator_.clear();
+}
+
+bool
+Query::processDSAtChild() {
+ const DataSourceClient::FindResult zresult =
+ datasrc_client_->findZone(*qname_);
+
+ if (zresult.code != result::SUCCESS) {
+ return (false);
+ }
+
+ // We are receiving a DS query at the child side of the owner name,
+ // where the DS isn't supposed to belong. We should return a "no data"
+ // response as described in Section 3.1.4.1 of RFC4035 and Section
+ // 2.2.1.1 of RFC 3658. find(DS) should result in NXRRSET, in which
+ // case (and if DNSSEC is required) we also add the proof for that,
+ // but even if find() returns an unexpected result, we don't bother.
+ // The important point in this case is to return SOA so that the resolver
+ // that happens to contact us can hunt for the appropriate parent zone
+ // by seeing the SOA.
+ response_->setHeaderFlag(Message::HEADERFLAG_AA);
+ response_->setRcode(Rcode::NOERROR());
+ addSOA(*zresult.zone_finder);
+ ConstZoneFinderContextPtr ds_context =
+ zresult.zone_finder->find(*qname_, RRType::DS(), dnssec_opt_);
+ if (ds_context->code == ZoneFinder::NXRRSET) {
+ if (dnssec_) {
+ addNXRRsetProof(*zresult.zone_finder, *ds_context);
+ }
+ }
+
+ response_creator_.create(*response_, answers_, authorities_, additionals_,
+ dnssec_);
+ return (true);
}
}
diff --git a/src/bin/auth/query.h b/src/bin/auth/query.h
index 43a8b6b..dce19f7 100644
--- a/src/bin/auth/query.h
+++ b/src/bin/auth/query.h
@@ -15,8 +15,14 @@
*/
#include <exceptions/exceptions.h>
+#include <dns/rrset.h>
#include <datasrc/zone.h>
+#include <boost/noncopyable.hpp>
+
+#include <functional>
+#include <vector>
+
namespace isc {
namespace dns {
class Message;
@@ -61,8 +67,14 @@ namespace auth {
/// 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 {
+class Query : boost::noncopyable {
private:
+ /// \brief Initial reserved size for the vectors in Query
+ ///
+ /// The value is larger than we expect the vectors to even become, and
+ /// has been chosen arbitrarily. The reason to set them quite high is
+ /// to prevent reallocation on addition.
+ static const size_t RESERVE_RRSETS = 64;
/// \brief Adds a SOA.
///
@@ -71,16 +83,51 @@ private:
///
void addSOA(isc::datasrc::ZoneFinder& finder);
+ /// \brief Adds the DS rrset for the given name, if available
+ ///
+ /// This is intended to be called when returning a delegation, and
+ /// if DNSSEC data is requested. If the DS record is not found
+ /// (signaled by find() returning NXRRSET), and the zone is signed
+ /// with NSEC, an NSEC denial of existence proof is added.
+ ///
+ /// \exception BadDS raised if find() returns anything other than
+ /// SUCCESS or NXRRSET when searching for the DS
+ /// record.
+ /// \param finder The ZoneFinder where the delegation was found
+ /// \param ds_name The name of the delegation RRset
+ void addDS(isc::datasrc::ZoneFinder& finder,
+ const isc::dns::Name& ds_name);
+
+ /// \brief Adds NSEC(3) denial proof for the given NXRRset result
+ ///
+ /// If available, NSEC or NSEC3 records are added to the authority
+ /// section (depending on whether isNSECSigned() or isNSEC3Signed()
+ /// returns true).
+ ///
+ /// \param finder The ZoneFinder that was used to search for the missing
+ /// data
+ /// \param db_result The ZoneFinder::FindResult returned by find()
+ void addNXRRsetProof(isc::datasrc::ZoneFinder& finder,
+ const isc::datasrc::ZoneFinder::Context& db_context);
+
/// Add NSEC RRs that prove an NXDOMAIN result.
///
/// This corresponds to Section 3.1.3.2 of RFC 4035.
- void addNXDOMAINProof(isc::datasrc::ZoneFinder& finder,
- isc::dns::ConstRRsetPtr nsec);
+ void addNXDOMAINProofByNSEC(isc::datasrc::ZoneFinder& finder,
+ isc::dns::ConstRRsetPtr nsec);
- /// Add NSEC RRs that prove a wildcard answer is the best one.
+ /// Add NSEC3 RRs that prove an NXDOMAIN result.
///
- /// This corresponds to Section 3.1.3.3 of RFC 4035.
- void addWildcardProof(isc::datasrc::ZoneFinder& finder);
+ /// This corresponds to Section 7.2.2 of RFC 5155.
+ void addNXDOMAINProofByNSEC3(isc::datasrc::ZoneFinder& finder);
+
+ /// Add NSEC or NSEC3 RRs that prove a wildcard answer is the best one.
+ ///
+ /// This corresponds to Section 3.1.3.3 of RFC 4035 and Section 7.2.6
+ /// of RFC5155.
+ void addWildcardProof(
+ isc::datasrc::ZoneFinder& finder,
+ const isc::datasrc::ZoneFinder::Context& db_context);
/// \brief Adds one NSEC RR proved no matched QNAME,one NSEC RR proved no
/// matched <QNAME,QTYPE> through wildcard extension.
@@ -93,44 +140,6 @@ private:
/// <QNAME,QTTYPE>.
void addWildcardNXRRSETProof(isc::datasrc::ZoneFinder& finder,
isc::dns::ConstRRsetPtr nsec);
-
- /// \brief Look up additional data (i.e., address records for the names
- /// included in NS or MX records) and add them to the additional section.
- ///
- /// 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 ZoneFinder through which the additional data for the
- /// query is to be found.
- /// \param rrset The RRset (i.e., NS or MX rrset) which require additional
- /// processing.
- void addAdditional(isc::datasrc::ZoneFinder& zone,
- const isc::dns::RRset& rrset);
-
- /// \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 ZoneFinder through which the address records is to
- /// be found.
- /// \param qname The name in rrset RDATA.
- /// \param options The search options.
- void addAdditionalAddrs(isc::datasrc::ZoneFinder& zone,
- const isc::dns::Name& qname,
- const isc::datasrc::ZoneFinder::FindOptions options
- = isc::datasrc::ZoneFinder::FIND_DEFAULT);
/// \brief Look up a zone's NS RRset and their address records for an
/// authoritative answer, and add them to the additional section.
@@ -150,12 +159,86 @@ private:
///
/// \param finder The \c ZoneFinder through which the NS and additional
/// data for the query are to be found.
- void addAuthAdditional(isc::datasrc::ZoneFinder& finder);
+ void addAuthAdditional(isc::datasrc::ZoneFinder& finder,
+ std::vector<isc::dns::ConstRRsetPtr>& additionals);
-public:
- /// Constructor from query parameters.
+ /// \brief Process a DS query possible at the child side of zone cut.
+ ///
+ /// This private method is a subroutine of process(), and is called if
+ /// there's a possibility that this server has authority for the child
+ /// side of the DS's owner name (and it's detected that the server at
+ /// least doesn't have authority at the parent side). This method
+ /// first checks if it has authority for the child, and if does,
+ /// just build a "no data" response with SOA for the zone origin
+ /// (possibly with a proof for the no data) as specified in Section
+ /// 2.2.1.1 of RFC3658.
+ ///
+ /// It returns true if this server has authority of the child; otherwise
+ /// it returns false. In the former case, the caller is expected to
+ /// terminate the query processing, because it should have been completed
+ /// within this method.
+ bool processDSAtChild();
+
+ /// \brief Add NSEC3 to the response for a closest encloser proof for a
+ /// given name.
+ ///
+ /// This method calls \c findNSEC3() of the given zone finder for the
+ /// given name in the recursive mode, and adds the returned NSEC3(s) to
+ /// the authority section of the response message associated with the
+ /// \c Query object.
+ ///
+ /// It returns the number of labels of the closest encloser (returned via
+ /// the \c findNSEC3() call) in case the caller needs to use that value
+ /// for subsequent processing, i.e, constructing the best possible wildcard
+ /// name that (would) match the query name.
+ ///
+ /// Unless \c exact_ok is true, \c name is expected to be non existent,
+ /// in which case findNSEC3() in the recursive mode must return both
+ /// closest and next proofs. If the latter is NULL, it means a run time
+ /// collision (or the zone is broken in other way), and this method throws
+ /// a BadNSEC3 exception.
+ ///
+ /// If \c exact_ok is true, this method takes into account the case
+ /// where the name exists and may or may not be at a zone cut to an
+ /// optout zone. In this case, depending on whether the zone is optout
+ /// or not, findNSEC3() may return non-NULL or NULL next_proof
+ /// (respectively). This method adds the next proof if and only if
+ /// findNSEC3() returns non NULL value for it. The Opt-Out flag
+ /// must be set or cleared accordingly, but this method doesn't check that
+ /// in this level (as long as the zone is signed validly and findNSEC3()
+ /// for the data source is implemented as documented, the condition
+ /// should be met; otherwise we'd let the validator detect the error).
+ ///
+ /// By default this method always adds the closest proof.
+ /// If \c add_closest is false, it only adds the next proof to the message.
+ /// This correspond to the case of "wildcard answer responses" as described
+ /// in Section 7.2.6 of RFC5155.
+ uint8_t addClosestEncloserProof(isc::datasrc::ZoneFinder& finder,
+ const isc::dns::Name& name, bool exact_ok,
+ bool add_closest = true);
+
+ /// \brief Add matching or covering NSEC3 to the response for a give name.
+ ///
+ /// This method calls \c findNSEC3() of the given zone finder for the
+ /// given name in the non recursive mode, and adds the returned NSEC3 to
+ /// the authority section of the response message associated with the
+ /// \c Query object.
+ ///
+ /// Depending on the caller's context, the returned NSEC3 is one and
+ /// only one of matching or covering NSEC3. If \c match is true the
+ /// returned NSEC3 must be a matching one; otherwise it must be a covering
+ /// one. If this assumption isn't met this method throws a BadNSEC3
+ /// exception (if it must be a matching NSEC3 but is not, it means a broken
+ /// zone, maybe with incorrect optout NSEC3s; if it must be a covering
+ /// NSEC3 but is not, it means a run time collision; or the \c findNSEC3()
+ /// implementation is broken for both cases.)
+ void addNSEC3ForName(isc::datasrc::ZoneFinder& finder,
+ const isc::dns::Name& name, bool match);
+
+ /// Set up the Query object for a new query lookup
///
- /// This constructor never throws an exception.
+ /// This is the first step of the process() method, and initializes
+ /// the member data
///
/// \param datasrc_client The datasource wherein the answer to the query is
/// to be found.
@@ -164,14 +247,49 @@ public:
/// \param response The response message to store the answer to the query.
/// \param dnssec If the answer should include signatures and NSEC/NSEC3 if
/// possible.
- Query(const isc::datasrc::DataSourceClient& datasrc_client,
- const isc::dns::Name& qname, const isc::dns::RRType& qtype,
- isc::dns::Message& response, bool dnssec = false) :
- datasrc_client_(datasrc_client), qname_(qname), qtype_(qtype),
- response_(response), dnssec_(dnssec),
- dnssec_opt_(dnssec ? isc::datasrc::ZoneFinder::FIND_DNSSEC :
- isc::datasrc::ZoneFinder::FIND_DEFAULT)
- {}
+ void initialize(datasrc::DataSourceClient& datasrc_client,
+ const isc::dns::Name& qname, const isc::dns::RRType& qtype,
+ isc::dns::Message& response, bool dnssec = false);
+
+ /// \brief Resets any partly built response data, and internal pointers
+ ///
+ /// Called by the QueryCleaner object upon its destruction
+ void reset();
+
+ /// \brief Internal class used for cleanup of Query members
+ ///
+ /// The process() call creates an object of this class, which
+ /// upon its destruction, calls Query::reset(), so that outside
+ /// of single calls to process(), the query state is always clean.
+ class QueryCleaner {
+ public:
+ QueryCleaner(isc::auth::Query& query) : query_(query) {}
+ ~QueryCleaner() { query_.reset(); }
+ private:
+ isc::auth::Query& query_;
+ };
+
+protected:
+ // Following methods declared protected so they can be accessed
+ // by unit tests.
+
+ void createResponse();
+
+public:
+ /// Default constructor.
+ ///
+ /// Query parameters will be set by the call to process()
+ ///
+ Query() :
+ datasrc_client_(NULL), qname_(NULL), qtype_(NULL),
+ dnssec_(false), dnssec_opt_(isc::datasrc::ZoneFinder::FIND_DEFAULT),
+ response_(NULL)
+ {
+ answers_.reserve(RESERVE_RRSETS);
+ authorities_.reserve(RESERVE_RRSETS);
+ additionals_.reserve(RESERVE_RRSETS);
+ }
+
/// Process the query.
///
@@ -199,7 +317,17 @@ public:
/// 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();
+ ///
+ /// \param datasrc_client The 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.
+ /// \param dnssec If the answer should include signatures and NSEC/NSEC3 if
+ /// possible.
+ void process(datasrc::DataSourceClient& datasrc_client,
+ const isc::dns::Name& qname, const isc::dns::RRType& qtype,
+ isc::dns::Message& response, bool dnssec = false);
/// \short Bad zone data encountered.
///
@@ -235,7 +363,7 @@ public:
/// An invalid result is given when a valid NSEC is expected
///
- // This can only happen when the underlying data source implementation or
+ /// This can only happen when the underlying data source implementation or
/// the zone is broken. By throwing an exception we treat such cases
/// as SERVFAIL.
struct BadNSEC : public BadZone {
@@ -244,13 +372,128 @@ public:
{}
};
+ /// An invalid result is given when a valid NSEC3 is expected
+ ///
+ /// This can only happen when the underlying data source implementation or
+ /// the zone is broken. By throwing an exception we treat such cases
+ /// as SERVFAIL.
+ struct BadNSEC3 : public BadZone {
+ BadNSEC3(const char* file, size_t line, const char* what) :
+ BadZone(file, line, what)
+ {}
+ };
+
+ /// An invalid result is given when a valid DS records (or NXRRSET) is
+ /// expected
+ ///
+ /// This can only happen when the underlying data source implementation
+ /// or the zone is broken. A DS query for a known delegation point should
+ /// either result in SUCCESS (if available) or NXRRSET
+ struct BadDS : public BadZone {
+ BadDS(const char* file, size_t line, const char* what) :
+ BadZone(file, line, what)
+ {}
+ };
+
+ /// \brief Response Creator Class
+ ///
+ /// This is a helper class of Query, and is expected to be used during the
+ /// construction of the response message. This class performs the
+ /// duplicate RRset detection check. It keeps a list of RRsets added
+ /// to the message and does not add an RRset if it is the same as one
+ /// already added.
+ ///
+ /// This class is essentially private to Query, but is visible to public
+ /// for testing purposes. It's not expected to be used from a normal
+ /// application.
+ class ResponseCreator {
+ public:
+ /// \brief Constructor
+ ///
+ /// Reserves space for the list of RRsets. Although the
+ /// ResponseCreator will be used to create a message from the
+ /// contents of the Query object's answers_, authorities_ and
+ /// additionals_ elements, and each of these are sized to
+ /// RESERVE_RRSETS, it is _extremely_ unlikely that all three will be
+ /// filled to capacity. So we reserve more elements than in each of
+ /// these components, but not three times the amount.
+ ///
+ /// As with the answers_, authorities_ and additionals_ elements, the
+ /// reservation is made in the constructor to avoid dynamic allocation
+ /// of memory. The ResponseCreator is a member variable of the Query
+ /// object so is constructed once and lasts as long as that object.
+ /// Internal state is cleared through the clear() method.
+ ResponseCreator() {
+ added_.reserve(2 * RESERVE_RRSETS);
+ }
+
+ /// \brief Reset internal state
+ void clear() {
+ added_.clear();
+ }
+
+ /// \brief Complete the response message with filling in the
+ /// response sections.
+ ///
+ /// This is the final step of the Query::process() method, and within
+ /// that method, it should be called before it returns (if any
+ /// response data is to be added)
+ ///
+ /// This will take a message to build and each RRsets for the answer,
+ /// authority, and additional sections, and add them to their
+ /// corresponding sections in the given message. The RRsets are
+ /// filtered such that a particular RRset appears only once in the
+ /// message.
+ ///
+ /// If \c dnssec is true, it tells the message to include any RRSIGs
+ /// attached to the RRsets.
+ void create(
+ isc::dns::Message& message,
+ const std::vector<isc::dns::ConstRRsetPtr>& answers_,
+ const std::vector<isc::dns::ConstRRsetPtr>& authorities_,
+ const std::vector<isc::dns::ConstRRsetPtr>& additionals_,
+ const bool dnssec);
+
+ private:
+ // \brief RRset comparison functor.
+ struct IsSameKind : public std::binary_function<
+ const isc::dns::AbstractRRset*,
+ const isc::dns::AbstractRRset*,
+ bool> {
+ bool operator()(const isc::dns::AbstractRRset* r1,
+ const isc::dns::AbstractRRset* r2) const {
+ return (r1->isSameKind(*r2));
+ }
+ };
+
+ /// Insertion operation
+ ///
+ /// \param message Message to which the RRset is to be added
+ /// \param section Section of the message in which the RRset is put
+ /// \param rrset Pointer to RRset to be added to the message
+ /// \param dnssec Whether RRSIG records should be added as well
+ void addRRset(isc::dns::Message& message,
+ const isc::dns::Message::Section section,
+ const isc::dns::ConstRRsetPtr& rrset, const bool dnssec);
+
+
+ private:
+ /// List of RRsets already added to the message
+ std::vector<const isc::dns::AbstractRRset*> added_;
+ };
+
private:
- const isc::datasrc::DataSourceClient& datasrc_client_;
- const isc::dns::Name& qname_;
- const isc::dns::RRType& qtype_;
- isc::dns::Message& response_;
- const bool dnssec_;
- const isc::datasrc::ZoneFinder::FindOptions dnssec_opt_;
+ const isc::datasrc::DataSourceClient* datasrc_client_;
+ const isc::dns::Name* qname_;
+ const isc::dns::RRType* qtype_;
+ bool dnssec_;
+ isc::datasrc::ZoneFinder::FindOptions dnssec_opt_;
+ ResponseCreator response_creator_;
+
+ isc::dns::Message* response_;
+ std::vector<isc::dns::ConstRRsetPtr> answers_;
+ std::vector<isc::dns::ConstRRsetPtr> authorities_;
+ std::vector<isc::dns::ConstRRsetPtr> additionals_;
};
}
diff --git a/src/bin/auth/statistics.cc b/src/bin/auth/statistics.cc
index 7397a50..427f59e 100644
--- a/src/bin/auth/statistics.cc
+++ b/src/bin/auth/statistics.cc
@@ -15,17 +15,24 @@
#include <auth/statistics.h>
#include <auth/auth_log.h>
+#include <dns/opcode.h>
+
#include <cc/data.h>
#include <cc/session.h>
#include <statistics/counter.h>
#include <statistics/counter_dict.h>
+#include <algorithm>
+#include <cctype>
+#include <cassert>
+#include <string>
#include <sstream>
#include <iostream>
#include <boost/noncopyable.hpp>
+using namespace isc::dns;
using namespace isc::auth;
using namespace isc::statistics;
@@ -40,6 +47,12 @@ public:
AuthCountersImpl();
~AuthCountersImpl();
void inc(const AuthCounters::ServerCounterType type);
+ void inc(const Opcode opcode) {
+ opcode_counter_.inc(opcode.getCode());
+ }
+ void inc(const Rcode rcode) {
+ rcode_counter_.inc(rcode.getCode());
+ }
void inc(const std::string& zone,
const AuthCounters::PerZoneCounterType type);
bool submitStatistics() const;
@@ -48,8 +61,18 @@ public:
(AuthCounters::validator_type validator);
// Currently for testing purpose only
uint64_t getCounter(const AuthCounters::ServerCounterType type) const;
+ uint64_t getCounter(const Opcode opcode) const {
+ return (opcode_counter_.get(opcode.getCode()));
+ }
+ uint64_t getCounter(const Rcode rcode) const {
+ return (rcode_counter_.get(rcode.getCode()));
+ }
private:
Counter server_counter_;
+ Counter opcode_counter_;
+ static const size_t NUM_OPCODES = 16;
+ Counter rcode_counter_;
+ static const size_t NUM_RCODES = 17;
CounterDictionary per_zone_counter_;
isc::cc::AbstractSession* statistics_session_;
AuthCounters::validator_type validator_;
@@ -60,6 +83,7 @@ AuthCountersImpl::AuthCountersImpl() :
// size of server_counter_: AuthCounters::SERVER_COUNTER_TYPES
// size of per_zone_counter_: AuthCounters::PER_ZONE_COUNTER_TYPES
server_counter_(AuthCounters::SERVER_COUNTER_TYPES),
+ opcode_counter_(NUM_OPCODES), rcode_counter_(NUM_RCODES),
per_zone_counter_(AuthCounters::PER_ZONE_COUNTER_TYPES),
statistics_session_(NULL)
{
@@ -88,14 +112,45 @@ AuthCountersImpl::submitStatistics() const {
return (false);
}
std::stringstream statistics_string;
+ // add pid in order for stats to identify which auth sends
+ // statistics in the situation that multiple auth instances are
+ // working
statistics_string << "{\"command\": [\"set\","
<< "{ \"owner\": \"Auth\","
- << " \"data\":"
+ << " \"pid\":" << getpid()
+ << ", \"data\":"
<< "{ \"queries.udp\": "
<< server_counter_.get(AuthCounters::SERVER_UDP_QUERY)
<< ", \"queries.tcp\": "
- << server_counter_.get(AuthCounters::SERVER_TCP_QUERY)
- << " }"
+ << server_counter_.get(
+ AuthCounters::SERVER_TCP_QUERY);
+ // Insert non 0 Opcode counters.
+ for (int i = 0; i < NUM_OPCODES; ++i) {
+ const Counter::Type counter = opcode_counter_.get(i);
+ if (counter != 0) {
+ // The counter item name should be derived lower-cased textual
+ // representation of the code.
+ std::string opcode_txt = Opcode(i).toText();
+ std::transform(opcode_txt.begin(), opcode_txt.end(),
+ opcode_txt.begin(), ::tolower);
+ statistics_string << ", \"opcode." << opcode_txt << "\": "
+ << counter;
+ }
+ }
+ // Insert non 0 Rcode counters.
+ for (int i = 0; i < NUM_RCODES; ++i) {
+ const Counter::Type counter = rcode_counter_.get(i);
+ if (counter != 0) {
+ // The counter item name should be derived lower-cased textual
+ // representation of the code.
+ std::string rcode_txt = Rcode(i).toText();
+ std::transform(rcode_txt.begin(), rcode_txt.end(),
+ rcode_txt.begin(), ::tolower);
+ statistics_string << ", \"rcode." << rcode_txt << "\": "
+ << counter;
+ }
+ }
+ statistics_string << " }"
<< "}"
<< "]}";
isc::data::ConstElementPtr statistics_element =
@@ -159,6 +214,16 @@ AuthCounters::inc(const AuthCounters::ServerCounterType type) {
impl_->inc(type);
}
+void
+AuthCounters::inc(const Opcode opcode) {
+ impl_->inc(opcode);
+}
+
+void
+AuthCounters::inc(const Rcode rcode) {
+ impl_->inc(rcode);
+}
+
bool
AuthCounters::submitStatistics() const {
return (impl_->submitStatistics());
@@ -176,6 +241,16 @@ AuthCounters::getCounter(const AuthCounters::ServerCounterType type) const {
return (impl_->getCounter(type));
}
+uint64_t
+AuthCounters::getCounter(const Opcode opcode) const {
+ return (impl_->getCounter(opcode));
+}
+
+uint64_t
+AuthCounters::getCounter(const Rcode rcode) const {
+ return (impl_->getCounter(rcode));
+}
+
void
AuthCounters::registerStatisticsValidator
(AuthCounters::validator_type validator) const
diff --git a/src/bin/auth/statistics.h b/src/bin/auth/statistics.h
index 280b4a5..0c92605 100644
--- a/src/bin/auth/statistics.h
+++ b/src/bin/auth/statistics.h
@@ -15,6 +15,9 @@
#ifndef __STATISTICS_H
#define __STATISTICS_H 1
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+
#include <cc/session.h>
#include <stdint.h>
#include <boost/scoped_ptr.hpp>
@@ -87,6 +90,24 @@ public:
///
void inc(const ServerCounterType type);
+ /// \brief Increment the counter of a per opcode counter.
+ ///
+ /// \note This is a tentative interface. See \c getCounter().
+ ///
+ /// \param opcode The opcode of the counter to increment.
+ ///
+ /// \throw None
+ void inc(const isc::dns::Opcode opcode);
+
+ /// \brief Increment the counter of a per rcode counter.
+ ///
+ /// \note This is a tentative interface. See \c getCounter().
+ ///
+ /// \param rcode The rcode of the counter to increment.
+ ///
+ /// \throw None
+ void inc(const isc::dns::Rcode rcode);
+
/// \brief Submit statistics counters to statistics module.
///
/// This method is desinged to be called periodically
@@ -125,7 +146,7 @@ public:
///
void setStatisticsSession(isc::cc::AbstractSession* statistics_session);
- /// \brief Get a value of a counter in the AuthCounters.
+ /// \brief Get the 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.
@@ -135,9 +156,37 @@ public:
/// \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::ServerCounterType type) const;
+ /// \brief Get the value of a per opcode counter.
+ ///
+ /// This method returns the value of the per opcode counter for the
+ /// specified \c opcode.
+ ///
+ /// \note This is a tentative interface as an attempt of experimentally
+ /// supporting more statistics counters. This should eventually be more
+ /// generalized. In any case, this method is mainly for testing.
+ ///
+ /// \throw None
+ /// \param opcode The opcode of the counter to get the value of
+ /// \return the value of the counter.
+ uint64_t getCounter(const isc::dns::Opcode opcode) const;
+
+ /// \brief Get the value of a per rcode counter.
+ ///
+ /// This method returns the value of the per rcode counter for the
+ /// specified \c rcode.
+ ///
+ /// \note As mentioned in getCounter(const isc::dns::Opcode opcode),
+ /// This is a tentative interface as an attempt of experimentally
+ /// supporting more statistics counters. This should eventually be more
+ /// generalized. In any case, this method is mainly for testing.
+ ///
+ /// \throw None
+ /// \param rcode The rcode of the counter to get the value of
+ /// \return the value of the counter.
+ uint64_t getCounter(const isc::dns::Rcode rcode) const;
+
/// \brief A type of validation function for the specification in
/// isc::config::ModuleSpec.
///
diff --git a/src/bin/auth/tests/.gitignore b/src/bin/auth/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/bin/auth/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index b5b96d7..f9fac2f 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -3,6 +3,7 @@ AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin
AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DAUTH_OBJ_DIR=\"$(abs_top_builddir)/src/bin/auth\"
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
@@ -11,30 +12,34 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
if USE_STATIC_LINK
AM_LDFLAGS = -static
+# Some test cases cannot work with static link. To selectively disable such
+# tests we signal it via a definition.
+AM_CPPFLAGS += -DUSE_STATIC_LINK=1
endif
CLEANFILES = *.gcno *.gcda
+# Do not define global tests, use check-local so
+# environment can be set (needed for dynamic loading)
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 += ../auth_srv.h ../auth_srv.cc
run_unittests_SOURCES += ../auth_log.h ../auth_log.cc
run_unittests_SOURCES += ../query.h ../query.cc
-run_unittests_SOURCES += ../change_user.h ../change_user.cc
run_unittests_SOURCES += ../auth_config.h ../auth_config.cc
run_unittests_SOURCES += ../command.h ../command.cc
run_unittests_SOURCES += ../common.h ../common.cc
run_unittests_SOURCES += ../statistics.h ../statistics.cc
+run_unittests_SOURCES += datasrc_util.h datasrc_util.cc
run_unittests_SOURCES += auth_srv_unittest.cc
run_unittests_SOURCES += config_unittest.cc
+run_unittests_SOURCES += config_syntax_unittest.cc
run_unittests_SOURCES += command_unittest.cc
run_unittests_SOURCES += common_unittest.cc
run_unittests_SOURCES += query_unittest.cc
-run_unittests_SOURCES += change_user_unittest.cc
run_unittests_SOURCES += statistics_unittest.cc
run_unittests_SOURCES += run_unittests.cc
# This is a temporary workaround for #1206, where the InMemoryClient has been
@@ -66,6 +71,10 @@ run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
run_unittests_LDADD += $(top_builddir)/src/lib/statistics/libstatistics.la
-endif
-noinst_PROGRAMS = $(TESTS)
+check-local:
+ B10_FROM_BUILD=${abs_top_builddir} ./run_unittests
+
+noinst_PROGRAMS = run_unittests
+
+endif
diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc
index 329a2dc..a8815da 100644
--- a/src/bin/auth/tests/auth_srv_unittest.cc
+++ b/src/bin/auth/tests/auth_srv_unittest.cc
@@ -23,6 +23,7 @@
#include <dns/message.h>
#include <dns/messagerenderer.h>
#include <dns/name.h>
+#include <dns/opcode.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>
#include <dns/rrttl.h>
@@ -40,7 +41,9 @@
#include <dns/tests/unittest_util.h>
#include <testutils/dnsmessage_test.h>
#include <testutils/srv_test.h>
+#include <testutils/mockups.h>
#include <testutils/portconfig.h>
+#include <testutils/socket_request.h>
using namespace std;
using namespace isc::cc;
@@ -63,28 +66,84 @@ const char* const CONFIG_TESTDB =
const char* const BADCONFIG_TESTDB =
"{ \"database_file\": \"" TEST_DATA_DIR "/nodir/notexist\"}";
+// This is a configuration that uses the in-memory data source containing
+// a signed example zone.
+const char* const CONFIG_INMEMORY_EXAMPLE =
+ "{\"datasources\": [{\"type\": \"memory\","
+ "\"zones\": [{\"origin\": \"example\","
+ "\"file\": \"" TEST_DATA_DIR "/rfc5155-example.zone.signed\"}]}]}";
+
class AuthSrvTest : public SrvTestBase {
protected:
AuthSrvTest() :
- dnss_(ios_, NULL, NULL, NULL),
+ dnss_(),
server(true, xfrout),
- rrclass(RRClass::IN())
+ rrclass(RRClass::IN()),
+ // The empty string is expected value of the parameter of
+ // requestSocket, not the app_name (there's no fallback, it checks
+ // the empty string is passed).
+ sock_requestor_(dnss_, address_store_, 53210, "")
{
server.setDNSService(dnss_);
server.setXfrinSession(¬ify_session);
server.setStatisticsSession(&statistics_session);
}
+
virtual void processMessage() {
- server.processMessage(*io_message, parse_message, response_obuffer,
+ // If processMessage has been called before, parse_message needs
+ // to be reset. If it hasn't, there's no harm in doing so
+ parse_message->clear(Message::PARSE);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
&dnsserv);
}
- IOService ios_;
- DNSService dnss_;
+
+ // Helper for checking Rcode statistic counters;
+ // Checks for one specific Rcode statistics counter value
+ void checkRcodeCounter(const Rcode& rcode, int expected_value) const {
+ EXPECT_EQ(expected_value, server.getCounter(rcode)) <<
+ "Expected Rcode count for " << rcode.toText() <<
+ " " << expected_value << ", was: " <<
+ server.getCounter(rcode);
+ }
+
+ // Checks whether all Rcode counters are set to zero
+ void checkAllRcodeCountersZero() const {
+ for (int i = 0; i < 17; i++) {
+ checkRcodeCounter(Rcode(i), 0);
+ }
+ }
+
+ // Checks whether all Rcode counters are set to zero except the given
+ // rcode (it is checked to be set to 'value')
+ void checkAllRcodeCountersZeroExcept(const Rcode& rcode, int value) const {
+ for (int i = 0; i < 17; i++) {
+ const Rcode rc(i);
+ if (rc == rcode) {
+ checkRcodeCounter(Rcode(i), value);
+ } else {
+ checkRcodeCounter(Rcode(i), 0);
+ }
+ }
+ }
+
+ // Convenience method for tests that expect to return SERVFAIL
+ // It calls processMessage, checks if there is an answer, and
+ // check the header for default SERVFAIL data
+ void processAndCheckSERVFAIL() {
+ processMessage();
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
+ opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+ }
+
+ MockDNSService dnss_;
MockSession statistics_session;
MockXfroutClient xfrout;
AuthSrv server;
const RRClass rrclass;
vector<uint8_t> response_data;
+ AddressList address_store_;
+ TestSocketRequestor sock_requestor_;
};
// A helper function that builds a response to version.bind/TXT/CH that
@@ -110,8 +169,7 @@ createBuiltinVersionResponse(const qid_t qid, vector<uint8_t>& data) {
rrset_version_ns->addRdata(generic::NS(version_name));
message.addRRset(Message::SECTION_AUTHORITY, rrset_version_ns);
- OutputBuffer obuffer(0);
- MessageRenderer renderer(obuffer);
+ MessageRenderer renderer;
message.toWire(renderer);
data.clear();
@@ -130,13 +188,14 @@ TEST_F(AuthSrvTest, builtInQuery) {
default_qid, Name("version.bind"),
RRClass::CH(), RRType::TXT());
createRequestPacket(request_message, IPPROTO_UDP);
- server.processMessage(*io_message, parse_message, response_obuffer,
+ 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());
+ checkAllRcodeCountersZeroExcept(Rcode::NOERROR(), 1);
}
// Same test emulating the UDPServer class behavior (defined in libasiolink).
@@ -187,38 +246,46 @@ TEST_F(AuthSrvTest, iqueryViaDNSServer) {
// Unsupported requests. Should result in NOTIMP.
TEST_F(AuthSrvTest, unsupportedRequest) {
unsupportedRequest();
+ // unsupportedRequest tries 14 different opcodes
+ checkAllRcodeCountersZeroExcept(Rcode::NOTIMP(), 14);
}
// Multiple questions. Should result in FORMERR.
TEST_F(AuthSrvTest, multiQuestion) {
multiQuestion();
+ checkAllRcodeCountersZeroExcept(Rcode::FORMERR(), 1);
}
// Incoming data doesn't even contain the complete header. Must be silently
// dropped.
TEST_F(AuthSrvTest, shortMessage) {
shortMessage();
+ checkAllRcodeCountersZero();
}
// 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) {
response();
+ checkAllRcodeCountersZero();
}
// Query with a broken question
TEST_F(AuthSrvTest, shortQuestion) {
shortQuestion();
+ checkAllRcodeCountersZeroExcept(Rcode::FORMERR(), 1);
}
// Query with a broken answer section
TEST_F(AuthSrvTest, shortAnswer) {
shortAnswer();
+ checkAllRcodeCountersZeroExcept(Rcode::FORMERR(), 1);
}
// Query with unsupported version of EDNS.
TEST_F(AuthSrvTest, ednsBadVers) {
ednsBadVers();
+ checkAllRcodeCountersZeroExcept(Rcode::BADVERS(), 1);
}
TEST_F(AuthSrvTest, AXFROverUDP) {
@@ -233,9 +300,11 @@ TEST_F(AuthSrvTest, AXFRSuccess) {
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);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
EXPECT_FALSE(dnsserv.hasAnswer());
EXPECT_TRUE(xfrout.isConnected());
+ checkAllRcodeCountersZero();
}
// Try giving the server a TSIG signed request and see it can anwer signed as
@@ -253,7 +322,7 @@ TEST_F(AuthSrvTest, TSIGSigned) {
boost::shared_ptr<TSIGKeyRing> keyring(new TSIGKeyRing);
keyring->add(key);
server.setTSIGKeyRing(&keyring);
- server.processMessage(*io_message, parse_message, response_obuffer,
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
&dnsserv);
// What did we get?
@@ -271,6 +340,8 @@ TEST_F(AuthSrvTest, TSIGSigned) {
response_obuffer->getLength()));
EXPECT_EQ(TSIGError::NOERROR(), error) <<
"The server signed the response, but it doesn't seem to be valid";
+
+ checkAllRcodeCountersZeroExcept(Rcode::NOERROR(), 1);
}
// Give the server a signed request, but don't give it the key. It will
@@ -286,7 +357,7 @@ TEST_F(AuthSrvTest, TSIGSignedBadKey) {
// Process the message, but use a different key there
boost::shared_ptr<TSIGKeyRing> keyring(new TSIGKeyRing);
server.setTSIGKeyRing(&keyring);
- server.processMessage(*io_message, parse_message, response_obuffer,
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
&dnsserv);
EXPECT_TRUE(dnsserv.hasAnswer());
@@ -303,6 +374,8 @@ TEST_F(AuthSrvTest, TSIGSignedBadKey) {
EXPECT_EQ(TSIGError::BAD_KEY_CODE, tsig->getRdata().getError());
EXPECT_EQ(0, tsig->getRdata().getMACSize()) <<
"It should be unsigned with this error";
+
+ checkAllRcodeCountersZeroExcept(Rcode::NOTAUTH(), 1);
}
// Give the server a signed request, but signed by a different key
@@ -319,7 +392,7 @@ TEST_F(AuthSrvTest, TSIGBadSig) {
boost::shared_ptr<TSIGKeyRing> keyring(new TSIGKeyRing);
keyring->add(TSIGKey("key:QkFECg==:hmac-sha1"));
server.setTSIGKeyRing(&keyring);
- server.processMessage(*io_message, parse_message, response_obuffer,
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
&dnsserv);
EXPECT_TRUE(dnsserv.hasAnswer());
@@ -336,6 +409,8 @@ TEST_F(AuthSrvTest, TSIGBadSig) {
EXPECT_EQ(TSIGError::BAD_SIG_CODE, tsig->getRdata().getError());
EXPECT_EQ(0, tsig->getRdata().getMACSize()) <<
"It should be unsigned with this error";
+
+ checkAllRcodeCountersZeroExcept(Rcode::NOTAUTH(), 1);
}
// Give the server a signed unsupported request with a bad signature.
@@ -355,7 +430,7 @@ TEST_F(AuthSrvTest, TSIGCheckFirst) {
boost::shared_ptr<TSIGKeyRing> keyring(new TSIGKeyRing);
keyring->add(TSIGKey("key:QkFECg==:hmac-sha1"));
server.setTSIGKeyRing(&keyring);
- server.processMessage(*io_message, parse_message, response_obuffer,
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
&dnsserv);
EXPECT_TRUE(dnsserv.hasAnswer());
@@ -372,6 +447,11 @@ TEST_F(AuthSrvTest, TSIGCheckFirst) {
EXPECT_EQ(TSIGError::BAD_SIG_CODE, tsig->getRdata().getError());
EXPECT_EQ(0, tsig->getRdata().getMACSize()) <<
"It should be unsigned with this error";
+ // TSIG should have failed, and so the per opcode counter shouldn't be
+ // incremented.
+ EXPECT_EQ(0, server.getCounter(Opcode::RESERVED14()));
+
+ checkAllRcodeCountersZeroExcept(Rcode::NOTAUTH(), 1);
}
TEST_F(AuthSrvTest, AXFRConnectFail) {
@@ -381,7 +461,8 @@ TEST_F(AuthSrvTest, AXFRConnectFail) {
Name("example.com"), RRClass::IN(),
RRType::AXFR());
createRequestPacket(request_message, IPPROTO_TCP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ 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);
@@ -395,7 +476,8 @@ TEST_F(AuthSrvTest, AXFRSendFail) {
Name("example.com"), RRClass::IN(),
RRType::AXFR());
createRequestPacket(request_message, IPPROTO_TCP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
EXPECT_TRUE(xfrout.isConnected());
xfrout.disableSend();
@@ -405,7 +487,8 @@ TEST_F(AuthSrvTest, AXFRSendFail) {
Name("example.com"), RRClass::IN(),
RRType::AXFR());
createRequestPacket(request_message, IPPROTO_TCP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ 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);
@@ -415,17 +498,17 @@ TEST_F(AuthSrvTest, AXFRSendFail) {
}
TEST_F(AuthSrvTest, AXFRDisconnectFail) {
- // In our usage disconnect() shouldn't fail. So we'll see the exception
- // should it be thrown.
+ // In our usage disconnect() shouldn't fail. But even if it does,
+ // it should not disrupt service (so processMessage should have caught it)
xfrout.disableSend();
xfrout.disableDisconnect();
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_obuffer, &dnsserv),
- XfroutError);
+ EXPECT_NO_THROW(server.processMessage(*io_message, *parse_message,
+ *response_obuffer, &dnsserv));
+ // Since the disconnect failed, we should still be 'connected'
EXPECT_TRUE(xfrout.isConnected());
// XXX: we need to re-enable disconnect. otherwise an exception would be
// thrown via the destructor of the server.
@@ -439,7 +522,8 @@ TEST_F(AuthSrvTest, IXFRConnectFail) {
Name("example.com"), RRClass::IN(),
RRType::IXFR());
createRequestPacket(request_message, IPPROTO_TCP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ 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);
@@ -453,7 +537,8 @@ TEST_F(AuthSrvTest, IXFRSendFail) {
Name("example.com"), RRClass::IN(),
RRType::IXFR());
createRequestPacket(request_message, IPPROTO_TCP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
EXPECT_TRUE(xfrout.isConnected());
xfrout.disableSend();
@@ -463,7 +548,8 @@ TEST_F(AuthSrvTest, IXFRSendFail) {
Name("example.com"), RRClass::IN(),
RRType::IXFR());
createRequestPacket(request_message, IPPROTO_TCP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ 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);
@@ -473,17 +559,16 @@ TEST_F(AuthSrvTest, IXFRSendFail) {
}
TEST_F(AuthSrvTest, IXFRDisconnectFail) {
- // In our usage disconnect() shouldn't fail. So we'll see the exception
- // should it be thrown.
+ // In our usage disconnect() shouldn't fail, but even if it does,
+ // procesMessage() should catch it.
xfrout.disableSend();
xfrout.disableDisconnect();
UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
Name("example.com"), RRClass::IN(),
RRType::IXFR());
createRequestPacket(request_message, IPPROTO_TCP);
- EXPECT_THROW(server.processMessage(*io_message, parse_message,
- response_obuffer, &dnsserv),
- XfroutError);
+ EXPECT_NO_THROW(server.processMessage(*io_message, *parse_message,
+ *response_obuffer, &dnsserv));
EXPECT_TRUE(xfrout.isConnected());
// XXX: we need to re-enable disconnect. otherwise an exception would be
// thrown via the destructor of the server.
@@ -496,7 +581,8 @@ TEST_F(AuthSrvTest, notify) {
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ 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
@@ -520,6 +606,8 @@ TEST_F(AuthSrvTest, notify) {
EXPECT_EQ(Name("example.com"), question->getName());
EXPECT_EQ(RRClass::IN(), question->getClass());
EXPECT_EQ(RRType::SOA(), question->getType());
+
+ checkAllRcodeCountersZeroExcept(Rcode::NOERROR(), 1);
}
TEST_F(AuthSrvTest, notifyForCHClass) {
@@ -529,7 +617,8 @@ TEST_F(AuthSrvTest, notifyForCHClass) {
RRClass::CH(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ 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
@@ -547,7 +636,8 @@ TEST_F(AuthSrvTest, notifyEmptyQuestion) {
request_message.setQid(default_qid);
request_message.toWire(request_renderer);
createRequestPacket(request_message, IPPROTO_UDP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ 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);
@@ -562,7 +652,8 @@ TEST_F(AuthSrvTest, notifyMultiQuestions) {
RRType::SOA()));
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ 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);
@@ -574,7 +665,8 @@ TEST_F(AuthSrvTest, notifyNonSOAQuestion) {
RRClass::IN(), RRType::NS());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ 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);
@@ -586,7 +678,8 @@ TEST_F(AuthSrvTest, notifyWithoutAA) {
default_qid, Name("example.com"),
RRClass::IN(), RRType::SOA());
createRequestPacket(request_message, IPPROTO_UDP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ 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);
@@ -599,7 +692,8 @@ TEST_F(AuthSrvTest, notifyWithErrorRcode) {
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
request_message.setRcode(Rcode::SERVFAIL());
createRequestPacket(request_message, IPPROTO_UDP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ 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);
@@ -616,7 +710,8 @@ TEST_F(AuthSrvTest, notifyWithoutSession) {
// we simply ignore the notify and let it be resent if an internal error
// happens.
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
EXPECT_FALSE(dnsserv.hasAnswer());
}
@@ -629,7 +724,8 @@ TEST_F(AuthSrvTest, notifySendFail) {
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
EXPECT_FALSE(dnsserv.hasAnswer());
}
@@ -641,7 +737,8 @@ TEST_F(AuthSrvTest, notifyReceiveFail) {
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
EXPECT_FALSE(dnsserv.hasAnswer());
}
@@ -653,7 +750,8 @@ TEST_F(AuthSrvTest, notifyWithBogusSessionMessage) {
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
EXPECT_FALSE(dnsserv.hasAnswer());
}
@@ -666,7 +764,8 @@ TEST_F(AuthSrvTest, notifyWithSessionMessageError) {
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
EXPECT_FALSE(dnsserv.hasAnswer());
}
@@ -681,7 +780,8 @@ updateConfig(AuthSrv* server, const char* const config_data,
ConstElementPtr result = config_answer->get("result");
EXPECT_EQ(Element::list, result->getType());
- EXPECT_EQ(expect_success ? 0 : 1, result->get(0)->intValue());
+ EXPECT_EQ(expect_success ? 0 : 1, result->get(0)->intValue()) <<
+ "Bad result from updateConfig: " << result->str();
}
// Install a Sqlite3 data source with testing data.
@@ -692,7 +792,8 @@ 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");
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ 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);
@@ -706,7 +807,8 @@ TEST_F(AuthSrvTest, datasourceFail) {
// in a SERVFAIL response, and the answer and authority sections should
// be empty.
createDataFromFile("badExampleQuery_fromWire.wire");
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ 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);
@@ -721,7 +823,8 @@ TEST_F(AuthSrvTest, updateConfigFail) {
// The original data source should still exist.
createDataFromFile("examplequery_fromWire.wire");
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ 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,13 +844,48 @@ TEST_F(AuthSrvTest, updateWithInMemoryClient) {
// The memory data source is empty, should return REFUSED rcode.
createDataFromFile("examplequery_fromWire.wire");
- server.processMessage(*io_message, parse_message, response_obuffer,
+ 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, queryWithInMemoryClientNoDNSSEC) {
+ // In this example, we do simple check that query is handled from the
+ // query handler class, and confirm it returns no error and a non empty
+ // answer section. Detailed examination on the response content
+ // for various types of queries are tested in the query tests.
+ updateConfig(&server, CONFIG_INMEMORY_EXAMPLE, true);
+ ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+ EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
+
+ createDataFromFile("nsec3query_nodnssec_fromWire.wire");
+ 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, 2, 1);
+}
+
+TEST_F(AuthSrvTest, queryWithInMemoryClientDNSSEC) {
+ // Similar to the previous test, but the query has the DO bit on.
+ // The response should contain RRSIGs, and should have more RRs than
+ // the previous case.
+ updateConfig(&server, CONFIG_INMEMORY_EXAMPLE, true);
+ ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+ EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
+
+ createDataFromFile("nsec3query_fromWire.wire");
+ 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, 2, 3, 3);
+}
+
TEST_F(AuthSrvTest, chQueryWithInMemoryClient) {
// Configure memory data source for class IN
updateConfig(&server, "{\"datasources\": "
@@ -758,7 +896,7 @@ TEST_F(AuthSrvTest, chQueryWithInMemoryClient) {
default_qid, Name("version.bind"),
RRClass::CH(), RRType::TXT());
createRequestPacket(request_message, IPPROTO_UDP);
- server.processMessage(*io_message, parse_message, response_obuffer,
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
&dnsserv);
EXPECT_TRUE(dnsserv.hasAnswer());
headerCheck(*parse_message, default_qid, Rcode::NOERROR(),
@@ -784,10 +922,14 @@ TEST_F(AuthSrvTest, queryCounterUDPNormal) {
default_qid, Name("example.com"),
RRClass::IN(), RRType::NS());
createRequestPacket(request_message, IPPROTO_UDP);
- server.processMessage(*io_message, parse_message, response_obuffer,
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
&dnsserv);
// After processing UDP query, the counter should be 1.
EXPECT_EQ(1, server.getCounter(AuthCounters::SERVER_UDP_QUERY));
+ // The counter for opcode Query should also be one
+ EXPECT_EQ(1, server.getCounter(Opcode::QUERY()));
+ // The counter for REFUSED responses should also be one, the rest zero
+ checkAllRcodeCountersZeroExcept(Rcode::REFUSED(), 1);
}
// Submit TCP normal query and check query counter
@@ -799,10 +941,14 @@ TEST_F(AuthSrvTest, queryCounterTCPNormal) {
default_qid, Name("example.com"),
RRClass::IN(), RRType::NS());
createRequestPacket(request_message, IPPROTO_TCP);
- server.processMessage(*io_message, parse_message, response_obuffer,
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
&dnsserv);
// After processing TCP query, the counter should be 1.
EXPECT_EQ(1, server.getCounter(AuthCounters::SERVER_TCP_QUERY));
+ // The counter for SUCCESS responses should also be one
+ EXPECT_EQ(1, server.getCounter(Opcode::QUERY()));
+ // The counter for REFUSED responses should also be one, the rest zero
+ checkAllRcodeCountersZeroExcept(Rcode::REFUSED(), 1);
}
// Submit TCP AXFR query and check query counter
@@ -814,10 +960,13 @@ TEST_F(AuthSrvTest, queryCounterTCPAXFR) {
createRequestPacket(request_message, IPPROTO_TCP);
// On success, the AXFR query has been passed to a separate process,
// so auth itself shouldn't respond.
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
EXPECT_FALSE(dnsserv.hasAnswer());
// After processing TCP AXFR query, the counter should be 1.
EXPECT_EQ(1, server.getCounter(AuthCounters::SERVER_TCP_QUERY));
+ // No rcodes should be incremented
+ checkAllRcodeCountersZero();
}
// Submit TCP IXFR query and check query counter
@@ -829,12 +978,38 @@ TEST_F(AuthSrvTest, queryCounterTCPIXFR) {
createRequestPacket(request_message, IPPROTO_TCP);
// On success, the IXFR query has been passed to a separate process,
// so auth itself shouldn't respond.
- server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
EXPECT_FALSE(dnsserv.hasAnswer());
// After processing TCP IXFR query, the counter should be 1.
EXPECT_EQ(1, server.getCounter(AuthCounters::SERVER_TCP_QUERY));
}
+TEST_F(AuthSrvTest, queryCounterOpcodes) {
+ for (int i = 0; i < 16; ++i) {
+ // The counter should be initialized to 0.
+ EXPECT_EQ(0, server.getCounter(Opcode(i)));
+
+ // For each possible opcode, create a request message and send it
+ UnitTestUtil::createRequestMessage(request_message, Opcode(i),
+ default_qid, Name("example.com"),
+ RRClass::IN(), RRType::NS());
+ createRequestPacket(request_message, IPPROTO_UDP);
+
+ // "send" the request N-th times where N is i + 1 for i-th code.
+ // we intentionally use different values for each code
+ for (int j = 0; j <= i; ++j) {
+ parse_message->clear(Message::PARSE);
+ server.processMessage(*io_message, *parse_message,
+ *response_obuffer,
+ &dnsserv);
+ }
+
+ // Confirm the counter.
+ EXPECT_EQ(i + 1, server.getCounter(Opcode(i)));
+ }
+}
+
// class for queryCounterUnexpected test
// getProtocol() returns IPPROTO_IP
class DummyUnknownSocket : public IOSocket {
@@ -852,11 +1027,10 @@ getDummyUnknownSocket() {
return (socket);
}
-// Submit unexpected type of query and check it throws isc::Unexpected
+// Submit unexpected type of query and check it is ignored
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.
+ // simpler and more readable as this is only for tests
// Create UDP query packet.
UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
@@ -872,9 +1046,7 @@ TEST_F(AuthSrvTest, queryCounterUnexpected) {
request_renderer.getLength(),
getDummyUnknownSocket(), *endpoint);
- EXPECT_THROW(server.processMessage(*io_message, parse_message,
- response_obuffer, &dnsserv),
- isc::Unexpected);
+ EXPECT_FALSE(dnsserv.hasAnswer());
}
TEST_F(AuthSrvTest, stop) {
@@ -887,6 +1059,330 @@ TEST_F(AuthSrvTest, stop) {
TEST_F(AuthSrvTest, listenAddresses) {
isc::testutils::portconfig::listenAddresses(server);
+ // Check it requests the correct addresses
+ const char* tokens[] = {
+ "TCP:127.0.0.1:53210:1",
+ "UDP:127.0.0.1:53210:2",
+ "TCP:::1:53210:3",
+ "UDP:::1:53210:4",
+ NULL
+ };
+ sock_requestor_.checkTokens(tokens, sock_requestor_.given_tokens_,
+ "Given tokens");
+ // It returns back to empty set of addresses afterwards, so
+ // they should be released
+ sock_requestor_.checkTokens(tokens, sock_requestor_.released_tokens_,
+ "Released tokens");
+}
+
+TEST_F(AuthSrvTest, processNormalQuery_reuseRenderer1) {
+ UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+ 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_NE(request_message.getRcode(), parse_message->getRcode());
+}
+
+TEST_F(AuthSrvTest, processNormalQuery_reuseRenderer2) {
+ UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+ 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);
+ ConstQuestionPtr question = *parse_message->beginQuestion();
+ EXPECT_STRNE(question->getType().toText().c_str(),
+ RRType::NS().toText().c_str());
+}
+//
+// Tests for catching exceptions in various stages of the query processing
+//
+// These tests work by defining two proxy classes, that act as an in-memory
+// client by default, but can throw exceptions at various points.
+//
+namespace {
+
+/// A the possible methods to throw in, either in FakeInMemoryClient or
+/// FakeZoneFinder
+enum ThrowWhen {
+ THROW_NEVER,
+ THROW_AT_FIND_ZONE,
+ THROW_AT_GET_ORIGIN,
+ THROW_AT_GET_CLASS,
+ THROW_AT_FIND,
+ THROW_AT_FIND_ALL,
+ THROW_AT_FIND_NSEC3
+};
+
+/// convenience function to check whether and what to throw
+void
+checkThrow(ThrowWhen method, ThrowWhen throw_at, bool isc_exception) {
+ if (method == throw_at) {
+ if (isc_exception) {
+ isc_throw(isc::Exception, "foo");
+ } else {
+ throw std::exception();
+ }
+ }
+}
+
+/// \brief proxy class for the ZoneFinder returned by the InMemoryClient
+/// proxied by FakeInMemoryClient
+///
+/// See the documentation for FakeInMemoryClient for more information,
+/// all methods simply check whether they should throw, and if not, call
+/// their proxied equivalent.
+class FakeZoneFinder : public isc::datasrc::ZoneFinder {
+public:
+ FakeZoneFinder(isc::datasrc::ZoneFinderPtr zone_finder,
+ ThrowWhen throw_when, bool isc_exception,
+ ConstRRsetPtr fake_rrset) :
+ real_zone_finder_(zone_finder),
+ throw_when_(throw_when),
+ isc_exception_(isc_exception),
+ fake_rrset_(fake_rrset)
+ {}
+
+ virtual isc::dns::Name
+ getOrigin() const {
+ checkThrow(THROW_AT_GET_ORIGIN, throw_when_, isc_exception_);
+ return (real_zone_finder_->getOrigin());
+ }
+
+ virtual isc::dns::RRClass
+ getClass() const {
+ checkThrow(THROW_AT_GET_CLASS, throw_when_, isc_exception_);
+ return (real_zone_finder_->getClass());
+ }
+
+ virtual isc::datasrc::ZoneFinderContextPtr
+ find(const isc::dns::Name& name,
+ const isc::dns::RRType& type,
+ isc::datasrc::ZoneFinder::FindOptions options)
+ {
+ using namespace isc::datasrc;
+ checkThrow(THROW_AT_FIND, throw_when_, isc_exception_);
+ // If faked RRset was specified on construction and it matches the
+ // query, return it instead of searching the real data source.
+ if (fake_rrset_ && fake_rrset_->getName() == name &&
+ fake_rrset_->getType() == type)
+ {
+ return (ZoneFinderContextPtr(new ZoneFinder::Context(
+ *this, options,
+ ResultContext(SUCCESS,
+ fake_rrset_))));
+ }
+ return (real_zone_finder_->find(name, type, options));
+ }
+
+ virtual isc::datasrc::ZoneFinderContextPtr
+ findAll(const isc::dns::Name& name,
+ std::vector<isc::dns::ConstRRsetPtr> &target,
+ const FindOptions options = FIND_DEFAULT)
+ {
+ checkThrow(THROW_AT_FIND_ALL, throw_when_, isc_exception_);
+ return (real_zone_finder_->findAll(name, target, options));
+ }
+
+ virtual FindNSEC3Result
+ findNSEC3(const isc::dns::Name& name, bool recursive) {
+ checkThrow(THROW_AT_FIND_NSEC3, throw_when_, isc_exception_);
+ return (real_zone_finder_->findNSEC3(name, recursive));
+ }
+
+ virtual isc::dns::Name
+ findPreviousName(const isc::dns::Name& query) const {
+ return (real_zone_finder_->findPreviousName(query));
+ }
+
+private:
+ isc::datasrc::ZoneFinderPtr real_zone_finder_;
+ ThrowWhen throw_when_;
+ bool isc_exception_;
+ ConstRRsetPtr fake_rrset_;
+};
+
+/// \brief Proxy InMemoryClient that can throw exceptions at specified times
+///
+/// It is based on the memory client since that one is easy to override
+/// (with setInMemoryClient) with the current design of AuthSrv.
+class FakeInMemoryClient : public isc::datasrc::InMemoryClient {
+public:
+ /// \brief Create a proxy memory client
+ ///
+ /// \param real_client The real in-memory client to proxy
+ /// \param throw_when if set to any value other than never, that is
+ /// the method that will throw an exception (either in this
+ /// class or the related FakeZoneFinder)
+ /// \param isc_exception if true, throw isc::Exception, otherwise,
+ /// throw std::exception
+ /// \param fake_rrset If non NULL, it will be used as an answer to
+ /// find() for that name and type.
+ FakeInMemoryClient(AuthSrv::InMemoryClientPtr real_client,
+ ThrowWhen throw_when, bool isc_exception,
+ ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
+ real_client_(real_client),
+ throw_when_(throw_when),
+ isc_exception_(isc_exception),
+ fake_rrset_(fake_rrset)
+ {}
+
+ /// \brief proxy call for findZone
+ ///
+ /// if this instance was constructed with throw_when set to find_zone,
+ /// this method will throw. Otherwise, it will return a FakeZoneFinder
+ /// instance which will throw at the method specified at the
+ /// construction of this instance.
+ virtual FindResult
+ findZone(const isc::dns::Name& name) const {
+ checkThrow(THROW_AT_FIND_ZONE, throw_when_, isc_exception_);
+ const FindResult result = real_client_->findZone(name);
+ return (FindResult(result.code, isc::datasrc::ZoneFinderPtr(
+ new FakeZoneFinder(result.zone_finder,
+ throw_when_,
+ isc_exception_,
+ fake_rrset_))));
+ }
+
+private:
+ AuthSrv::InMemoryClientPtr real_client_;
+ ThrowWhen throw_when_;
+ bool isc_exception_;
+ ConstRRsetPtr fake_rrset_;
+};
+
+} // end anonymous namespace for throwing proxy classes
+
+// Test for the tests
+//
+// Set the proxies to never throw, this should have the same result as
+// queryWithInMemoryClientNoDNSSEC, and serves to test the two proxy classes
+TEST_F(AuthSrvTest, queryWithInMemoryClientProxy) {
+ // Set real inmem client to proxy
+ updateConfig(&server, CONFIG_INMEMORY_EXAMPLE, true);
+
+ AuthSrv::InMemoryClientPtr fake_client(
+ new FakeInMemoryClient(server.getInMemoryClient(rrclass),
+ THROW_NEVER, false));
+ ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+ server.setInMemoryClient(rrclass, fake_client);
+
+ createDataFromFile("nsec3query_nodnssec_fromWire.wire");
+ 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, 2, 1);
+}
+
+// Convenience function for the rest of the tests, set up a proxy
+// to throw in the given method
+// If isc_exception is true, it will throw isc::Exception, otherwise
+// it will throw std::exception
+// If non null rrset is given, it will be passed to the proxy so it can
+// return some faked response.
+void
+setupThrow(AuthSrv* server, const char *config, ThrowWhen throw_when,
+ bool isc_exception, ConstRRsetPtr rrset = ConstRRsetPtr())
+{
+ // Set real inmem client to proxy
+ updateConfig(server, config, true);
+
+ // Set it to throw on findZone(), this should result in
+ // SERVFAIL on any exception
+ AuthSrv::InMemoryClientPtr fake_client(
+ new FakeInMemoryClient(
+ server->getInMemoryClient(isc::dns::RRClass::IN()),
+ throw_when, isc_exception, rrset));
+
+ ASSERT_NE(AuthSrv::InMemoryClientPtr(),
+ server->getInMemoryClient(isc::dns::RRClass::IN()));
+ server->setInMemoryClient(isc::dns::RRClass::IN(), fake_client);
+}
+
+TEST_F(AuthSrvTest, queryWithThrowingProxyServfails) {
+ // Test the common cases, all of which should simply return SERVFAIL
+ // Use THROW_NEVER as end marker
+ ThrowWhen throws[] = { THROW_AT_FIND_ZONE,
+ THROW_AT_GET_ORIGIN,
+ THROW_AT_FIND,
+ THROW_AT_FIND_NSEC3,
+ THROW_NEVER };
+ UnitTestUtil::createDNSSECRequestMessage(request_message, opcode,
+ default_qid, Name("foo.example."),
+ RRClass::IN(), RRType::TXT());
+ for (ThrowWhen* when(throws); *when != THROW_NEVER; ++when) {
+ createRequestPacket(request_message, IPPROTO_UDP);
+ setupThrow(&server, CONFIG_INMEMORY_EXAMPLE, *when, true);
+ processAndCheckSERVFAIL();
+ // To be sure, check same for non-isc-exceptions
+ createRequestPacket(request_message, IPPROTO_UDP);
+ setupThrow(&server, CONFIG_INMEMORY_EXAMPLE, *when, false);
+ processAndCheckSERVFAIL();
+ }
+}
+
+// Throw isc::Exception in getClass(). (Currently?) getClass is not called
+// in the processMessage path, so this should result in a normal answer
+TEST_F(AuthSrvTest, queryWithInMemoryClientProxyGetClass) {
+ createDataFromFile("nsec3query_nodnssec_fromWire.wire");
+ setupThrow(&server, CONFIG_INMEMORY_EXAMPLE, THROW_AT_GET_CLASS, true);
+
+ // getClass is not called so it should just answer
+ 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, 2, 1);
+}
+
+TEST_F(AuthSrvTest, queryWithThrowingInToWire) {
+ // Set up a faked data source. It will return an empty RRset for the
+ // query.
+ ConstRRsetPtr empty_rrset(new RRset(Name("foo.example"),
+ RRClass::IN(), RRType::TXT(),
+ RRTTL(0)));
+ setupThrow(&server, CONFIG_INMEMORY_EXAMPLE, THROW_NEVER, true,
+ empty_rrset);
+
+ // Repeat the query processing two times. Due to the faked RRset,
+ // toWire() should throw, and it should result in SERVFAIL.
+ OutputBufferPtr orig_buffer;
+ for (int i = 0; i < 2; ++i) {
+ UnitTestUtil::createDNSSECRequestMessage(request_message, opcode,
+ default_qid,
+ Name("foo.example."),
+ RRClass::IN(), RRType::TXT());
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
+ headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
+ opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+
+ // Make a backup of the original buffer for latest tests and replace
+ // it with a new one
+ if (!orig_buffer) {
+ orig_buffer = response_obuffer;
+ response_obuffer.reset(new OutputBuffer(0));
+ }
+ request_message.clear(Message::RENDER);
+ parse_message->clear(Message::PARSE);
+ }
+
+ // Now check if the original buffer is intact
+ parse_message->clear(Message::PARSE);
+ InputBuffer ibuffer(orig_buffer->getData(), orig_buffer->getLength());
+ parse_message->fromWire(ibuffer);
+ headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
+ opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
}
}
diff --git a/src/bin/auth/tests/change_user_unittest.cc b/src/bin/auth/tests/change_user_unittest.cc
deleted file mode 100644
index 33897b6..0000000
--- a/src/bin/auth/tests/change_user_unittest.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <stdlib.h>
-#include <unistd.h> // for getuid
-
-#include <string>
-
-#include <boost/lexical_cast.hpp>
-
-#include <gtest/gtest.h>
-
-#include <auth/common.h>
-#include <auth/change_user.h>
-
-using namespace std;
-
-namespace {
-TEST(ChangeUserTest, changeToTheSameUser) {
- const char* const my_username = getenv("USER");
-
- // normally the USER environment variable should be set to the name
- // of the local user running this test, but it's not always the case.
- if (my_username == NULL) {
- cerr << "Environment variable USER is undefined, skipping the test"
- << endl;
- return;
- }
-
- // changing to the run time user should succeed.
- EXPECT_NO_THROW(changeUser(my_username));
-}
-
-TEST(ChangeUserTest, changeToTheSameUserId) {
- // same as above, but using numeric user ID
- EXPECT_NO_THROW(changeUser(
- (boost::lexical_cast<string>(getuid())).c_str()));
-}
-
-TEST(ChangeUserTest, badUID) {
- // -1 should be an invalid numeric UID, and (hopefully) shouldn't be
- // a valid textual username.
- EXPECT_THROW(changeUser("-1"), FatalError);
-}
-
-TEST(ChangeUserTest, promotionAttempt) {
- // change to root should fail unless the running user is a super user.
- if (getuid() == 0) {
- cerr << "Already a super user, skipping the test" << endl;
- return;
- }
- EXPECT_THROW(changeUser("root"), FatalError);
-}
-}
diff --git a/src/bin/auth/tests/command_unittest.cc b/src/bin/auth/tests/command_unittest.cc
index 8a82367..bcaf4b1 100644
--- a/src/bin/auth/tests/command_unittest.cc
+++ b/src/bin/auth/tests/command_unittest.cc
@@ -14,14 +14,11 @@
#include <config.h>
-#include <cassert>
-#include <cstdlib>
-#include <string>
-#include <stdexcept>
-
-#include <boost/bind.hpp>
+#include "datasrc_util.h"
-#include <gtest/gtest.h>
+#include <auth/auth_srv.h>
+#include <auth/auth_config.h>
+#include <auth/command.h>
#include <dns/name.h>
#include <dns/rrclass.h>
@@ -33,75 +30,147 @@
#include <datasrc/memory_datasrc.h>
-#include <auth/auth_srv.h>
-#include <auth/auth_config.h>
-#include <auth/command.h>
-
#include <asiolink/asiolink.h>
#include <testutils/mockups.h>
+#include <cassert>
+#include <cstdlib>
+#include <string>
+#include <stdexcept>
+
+#include <boost/bind.hpp>
+
+#include <gtest/gtest.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+
using namespace std;
using namespace isc::dns;
using namespace isc::data;
using namespace isc::datasrc;
using namespace isc::config;
+using namespace isc::testutils;
+using namespace isc::auth::unittest;
namespace {
+
class AuthCommandTest : public ::testing::Test {
protected:
- AuthCommandTest() : server(false, xfrout), rcode(-1) {
- server.setStatisticsSession(&statistics_session);
+ AuthCommandTest() :
+ server_(false, xfrout_),
+ rcode_(-1),
+ expect_rcode_(0),
+ itimer_(server_.getIOService())
+ {
+ server_.setStatisticsSession(&statistics_session_);
}
void checkAnswer(const int expected_code) {
- parseAnswer(rcode, result);
- EXPECT_EQ(expected_code, rcode);
+ parseAnswer(rcode_, result_);
+ EXPECT_EQ(expected_code, rcode_);
}
- MockSession statistics_session;
- MockXfroutClient xfrout;
- AuthSrv server;
- ConstElementPtr result;
- int rcode;
+ MockSession statistics_session_;
+ MockXfroutClient xfrout_;
+ AuthSrv server_;
+ ConstElementPtr result_;
+ // The shutdown command parameter
+ ConstElementPtr param_;
+ int rcode_, expect_rcode_;
+ isc::asiolink::IntervalTimer itimer_;
public:
void stopServer(); // need to be public for boost::bind
+ void dontStopServer(); // need to be public for boost::bind
};
TEST_F(AuthCommandTest, unknownCommand) {
- result = execAuthServerCommand(server, "no_such_command",
- ConstElementPtr());
- parseAnswer(rcode, result);
- EXPECT_EQ(1, rcode);
+ result_ = execAuthServerCommand(server_, "no_such_command",
+ ConstElementPtr());
+ parseAnswer(rcode_, result_);
+ EXPECT_EQ(1, rcode_);
}
TEST_F(AuthCommandTest, 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",
+ EXPECT_THROW(execAuthServerCommand(server_, "_throw_exception",
ConstElementPtr()),
runtime_error);
}
TEST_F(AuthCommandTest, sendStatistics) {
- result = execAuthServerCommand(server, "sendstats", ConstElementPtr());
+ 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());
+ EXPECT_EQ("Stats", statistics_session_.getMessageDest());
checkAnswer(0);
}
void
AuthCommandTest::stopServer() {
- result = execAuthServerCommand(server, "shutdown", ConstElementPtr());
- parseAnswer(rcode, result);
- assert(rcode == 0); // make sure the test stops when something is wrong
+ result_ = execAuthServerCommand(server_, "shutdown", param_);
+ parseAnswer(rcode_, result_);
+ assert(rcode_ == 0); // make sure the test stops when something is wrong
}
TEST_F(AuthCommandTest, shutdown) {
- isc::asiolink::IntervalTimer itimer(server.getIOService());
- itimer.setup(boost::bind(&AuthCommandTest::stopServer, this), 1);
- server.getIOService().run();
- EXPECT_EQ(0, rcode);
+ // Param defaults to empty/null pointer on creation
+ itimer_.setup(boost::bind(&AuthCommandTest::stopServer, this), 1);
+ server_.getIOService().run();
+ EXPECT_EQ(0, rcode_);
+}
+
+TEST_F(AuthCommandTest, shutdownCorrectPID) {
+ // Put the pid parameter there
+ const pid_t pid(getpid());
+ ElementPtr param(new isc::data::MapElement());
+ param->set("pid", ConstElementPtr(new isc::data::IntElement(pid)));
+ param_ = param;
+ // With the correct PID, it should act exactly the same as in case
+ // of no parameter
+ itimer_.setup(boost::bind(&AuthCommandTest::stopServer, this), 1);
+ server_.getIOService().run();
+ EXPECT_EQ(0, rcode_);
+}
+
+// This is like stopServer, but the server should not stop after the
+// command, it should be running
+void
+AuthCommandTest::dontStopServer() {
+ result_ = execAuthServerCommand(server_, "shutdown", param_);
+ parseAnswer(rcode_, result_);
+ EXPECT_EQ(expect_rcode_, rcode_);
+ rcode_ = -1;
+ // We run the stopServer now, to really stop the server.
+ // If it had stopped already, it won't be run and the rcode -1 will
+ // be left here.
+ param_ = ConstElementPtr();
+ itimer_.cancel();
+ itimer_.setup(boost::bind(&AuthCommandTest::stopServer, this), 1);
+}
+
+// If we provide something not an int, the PID is not really specified, so
+// act as if nothing came.
+TEST_F(AuthCommandTest, shutdownNotInt) {
+ // Put the pid parameter there
+ ElementPtr param(new isc::data::MapElement());
+ param->set("pid", ConstElementPtr(new isc::data::StringElement("pid")));
+ param_ = param;
+ expect_rcode_ = 1;
+ // It should reject to stop if the PID is not an int.
+ itimer_.setup(boost::bind(&AuthCommandTest::dontStopServer, this), 1);
+ server_.getIOService().run();
+ EXPECT_EQ(0, rcode_);
+}
+
+TEST_F(AuthCommandTest, shutdownIncorrectPID) {
+ // The PID = 0 should be taken by init, so we are not init and the
+ // PID should be different
+ param_ = Element::fromJSON("{\"pid\": 0}");
+ itimer_.setup(boost::bind(&AuthCommandTest::dontStopServer, this), 1);
+ server_.getIOService().run();
+ EXPECT_EQ(0, rcode_);
}
// A helper function commonly used for the "loadzone" command tests.
@@ -112,23 +181,23 @@ zoneChecks(AuthSrv& server) {
EXPECT_TRUE(server.getInMemoryClient(RRClass::IN()));
EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test1.example")).zone_finder->
- find(Name("ns.test1.example"), RRType::A()).code);
+ find(Name("ns.test1.example"), RRType::A())->code);
EXPECT_EQ(ZoneFinder::NXRRSET, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test1.example")).zone_finder->
- find(Name("ns.test1.example"), RRType::AAAA()).code);
+ find(Name("ns.test1.example"), RRType::AAAA())->code);
EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test2.example")).zone_finder->
- find(Name("ns.test2.example"), RRType::A()).code);
+ find(Name("ns.test2.example"), RRType::A())->code);
EXPECT_EQ(ZoneFinder::NXRRSET, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test2.example")).zone_finder->
- find(Name("ns.test2.example"), RRType::AAAA()).code);
+ find(Name("ns.test2.example"), RRType::AAAA())->code);
}
void
configureZones(AuthSrv& server) {
- ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR "/test1.zone.in "
+ ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR "/test1.zone.in "
TEST_DATA_BUILDDIR "/test1.zone.copied"));
- ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR "/test2.zone.in "
+ ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR "/test2.zone.in "
TEST_DATA_BUILDDIR "/test2.zone.copied"));
configureAuthServer(server, Element::fromJSON(
"{\"datasources\": "
@@ -149,143 +218,266 @@ newZoneChecks(AuthSrv& server) {
EXPECT_TRUE(server.getInMemoryClient(RRClass::IN()));
EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test1.example")).zone_finder->
- find(Name("ns.test1.example"), RRType::A()).code);
+ find(Name("ns.test1.example"), RRType::A())->code);
// now test1.example should have ns/AAAA
EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test1.example")).zone_finder->
- find(Name("ns.test1.example"), RRType::AAAA()).code);
+ find(Name("ns.test1.example"), RRType::AAAA())->code);
// test2.example shouldn't change
EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test2.example")).zone_finder->
- find(Name("ns.test2.example"), RRType::A()).code);
+ find(Name("ns.test2.example"), RRType::A())->code);
EXPECT_EQ(ZoneFinder::NXRRSET, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test2.example")).zone_finder->
- find(Name("ns.test2.example"), RRType::AAAA()).code);
+ find(Name("ns.test2.example"), RRType::AAAA())->code);
}
TEST_F(AuthCommandTest, loadZone) {
- configureZones(server);
+ configureZones(server_);
- ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
+ ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
"/test1-new.zone.in "
TEST_DATA_BUILDDIR "/test1.zone.copied"));
- ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
+ ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
"/test2-new.zone.in "
TEST_DATA_BUILDDIR "/test2.zone.copied"));
- result = execAuthServerCommand(server, "loadzone",
- Element::fromJSON(
- "{\"origin\": \"test1.example\"}"));
+ result_ = execAuthServerCommand(server_, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\"}"));
checkAnswer(0);
- newZoneChecks(server);
+ newZoneChecks(server_);
+}
+
+// This test uses dynamic load of a data source module, and won't work when
+// statically linked.
+TEST_F(AuthCommandTest,
+#ifdef USE_STATIC_LINK
+ DISABLED_loadZoneSQLite3
+#else
+ loadZoneSQLite3
+#endif
+ )
+{
+ const char* const SPEC_FILE = AUTH_OBJ_DIR "/auth.spec";
+
+ // Prepare the database first
+ const string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied";
+ const string bad_db = TEST_DATA_BUILDDIR "/does-not-exist.sqlite3";
+ stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n");
+ createSQLite3DB(RRClass::IN(), Name("example.org"), test_db.c_str(), ss);
+
+ // Then store a config of the zone to the auth server
+ // This omits many config options of the auth server, but these are
+ // not read now.
+ isc::testutils::MockSession session;
+ // The session should not take care of anything or start anything, we
+ // need it only to hold the config we're going to put into it.
+ ModuleCCSession module_session(SPEC_FILE, session, NULL, NULL, false,
+ false);
+ // This describes the data source in the configuration
+ const ElementPtr
+ map(Element::fromJSON("{\"datasources\": ["
+ " {"
+ " \"type\": \"memory\","
+ " \"zones\": ["
+ " {"
+ " \"origin\": \"example.org\","
+ " \"file\": \"" + test_db + "\","
+ " \"filetype\": \"sqlite3\""
+ " }"
+ " ]"
+ " }"
+ "]}"));
+ module_session.setLocalConfig(map);
+ server_.setConfigSession(&module_session);
+
+ // The loadzone command needs the zone to be already loaded, because
+ // it is used for reloading only
+ AuthSrv::InMemoryClientPtr dsrc(new InMemoryClient());
+ dsrc->addZone(ZoneFinderPtr(new InMemoryZoneFinder(RRClass::IN(),
+ Name("example.org"))));
+ server_.setInMemoryClient(RRClass::IN(), dsrc);
+
+ // Now send the command to reload it
+ result_ = execAuthServerCommand(server_, "loadzone",
+ Element::fromJSON("{\"origin\": \"example.org\"}"));
+ checkAnswer(0);
+
+ // Get the zone and look if there are data in it (the original one was
+ // empty)
+ ASSERT_TRUE(server_.getInMemoryClient(RRClass::IN()));
+ EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
+ findZone(Name("example.org")).zone_finder->
+ find(Name("example.org"), RRType::SOA())->code);
+
+ // Some error cases. First, the zone has no configuration.
+ dsrc->addZone(ZoneFinderPtr(new InMemoryZoneFinder(RRClass::IN(),
+ Name("example.com"))));
+ result_ = execAuthServerCommand(server_, "loadzone",
+ Element::fromJSON("{\"origin\": \"example.com\"}"));
+ checkAnswer(1);
+
+ // The previous zone is not hurt in any way
+ EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
+ findZone(Name("example.org")).zone_finder->
+ find(Name("example.org"), RRType::SOA())->code);
+
+ module_session.setLocalConfig(Element::fromJSON("{\"datasources\": []}"));
+ result_ = execAuthServerCommand(server_, "loadzone",
+ Element::fromJSON("{\"origin\": \"example.org\"}"));
+ checkAnswer(1);
+
+ // The previous zone is not hurt in any way
+ EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
+ findZone(Name("example.org")).zone_finder->
+ find(Name("example.org"), RRType::SOA())->code);
+ // Configure an unreadable zone. Should fail, but leave the original zone
+ // data there
+ const ElementPtr
+ mapBad(Element::fromJSON("{\"datasources\": ["
+ " {"
+ " \"type\": \"memory\","
+ " \"zones\": ["
+ " {"
+ " \"origin\": \"example.org\","
+ " \"file\": \"" + bad_db + "\","
+ " \"filetype\": \"sqlite3\""
+ " }"
+ " ]"
+ " }"
+ "]}"));
+ module_session.setLocalConfig(mapBad);
+ result_ = execAuthServerCommand(server_, "loadzone",
+ Element::fromJSON("{\"origin\": \"example.com\"}"));
+ checkAnswer(1);
+ // The previous zone is not hurt in any way
+ EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
+ findZone(Name("example.org")).zone_finder->
+ find(Name("example.org"), RRType::SOA())->code);
+
+ // Broken configuration (not valid against the spec)
+ const ElementPtr
+ broken(Element::fromJSON("{\"datasources\": ["
+ " {"
+ " \"type\": \"memory\","
+ " \"zones\": [[]]"
+ " }"
+ "]}"));
+ module_session.setLocalConfig(broken);
+ checkAnswer(1);
+ // The previous zone is not hurt in any way
+ EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
+ findZone(Name("example.org")).zone_finder->
+ find(Name("example.org"), RRType::SOA())->code);
}
TEST_F(AuthCommandTest, loadBrokenZone) {
- configureZones(server);
+ configureZones(server_);
- ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
+ ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
"/test1-broken.zone.in "
TEST_DATA_BUILDDIR "/test1.zone.copied"));
- result = execAuthServerCommand(server, "loadzone",
- Element::fromJSON(
- "{\"origin\": \"test1.example\"}"));
+ result_ = execAuthServerCommand(server_, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\"}"));
checkAnswer(1);
- zoneChecks(server); // zone shouldn't be replaced
+ zoneChecks(server_); // zone shouldn't be replaced
}
TEST_F(AuthCommandTest, loadUnreadableZone) {
- configureZones(server);
+ configureZones(server_);
// install the zone file as unreadable
- ASSERT_EQ(0, system(INSTALL_PROG " -m 000 " TEST_DATA_DIR
+ ASSERT_EQ(0, system(INSTALL_PROG " -c -m 000 " TEST_DATA_DIR
"/test1.zone.in "
TEST_DATA_BUILDDIR "/test1.zone.copied"));
- result = execAuthServerCommand(server, "loadzone",
- Element::fromJSON(
- "{\"origin\": \"test1.example\"}"));
+ result_ = execAuthServerCommand(server_, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\"}"));
checkAnswer(1);
- zoneChecks(server); // zone shouldn't be replaced
+ zoneChecks(server_); // zone shouldn't be replaced
}
TEST_F(AuthCommandTest, loadZoneWithoutDataSrc) {
// try to execute load command without configuring the zone beforehand.
// it should fail.
- result = execAuthServerCommand(server, "loadzone",
- Element::fromJSON(
- "{\"origin\": \"test1.example\"}"));
+ result_ = execAuthServerCommand(server_, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\"}"));
checkAnswer(1);
}
TEST_F(AuthCommandTest, 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\"}"));
+ result_ = execAuthServerCommand(server_, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\","
+ " \"datasrc\": \"sqlite3\"}"));
checkAnswer(0);
}
TEST_F(AuthCommandTest, loadZoneInvalidParams) {
- configureZones(server);
+ configureZones(server_);
// null arg
- result = execAuthServerCommand(server, "loadzone", ElementPtr());
+ result_ = execAuthServerCommand(server_, "loadzone", ElementPtr());
checkAnswer(1);
// zone class is bogus
- result = execAuthServerCommand(server, "loadzone",
- Element::fromJSON(
- "{\"origin\": \"test1.example\","
- " \"class\": \"no_such_class\"}"));
+ 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}"));
+ 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\"}"));
+ 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\"}"));
+ 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}"));
+ result_ = execAuthServerCommand(server_, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\","
+ " \"datasrc\": 0}"));
checkAnswer(1);
// origin is missing
- result = execAuthServerCommand(server, "loadzone",
- Element::fromJSON("{}"));
+ result_ = execAuthServerCommand(server_, "loadzone",
+ Element::fromJSON("{}"));
checkAnswer(1);
// zone doesn't exist in the data source
- result = execAuthServerCommand(server, "loadzone",
- Element::fromJSON("{\"origin\": \"xx\"}"));
+ result_ = execAuthServerCommand(server_, "loadzone",
+ Element::fromJSON("{\"origin\": \"xx\"}"));
checkAnswer(1);
// origin is bogus
- result = execAuthServerCommand(server, "loadzone",
- Element::fromJSON(
- "{\"origin\": \"...\"}"));
+ result_ = execAuthServerCommand(server_, "loadzone",
+ Element::fromJSON(
+ "{\"origin\": \"...\"}"));
checkAnswer(1);
- result = execAuthServerCommand(server, "loadzone",
- Element::fromJSON("{\"origin\": 10}"));
+ result_ = execAuthServerCommand(server_, "loadzone",
+ Element::fromJSON("{\"origin\": 10}"));
checkAnswer(1);
}
}
diff --git a/src/bin/auth/tests/common_unittest.cc b/src/bin/auth/tests/common_unittest.cc
index 9b18142..184988d 100644
--- a/src/bin/auth/tests/common_unittest.cc
+++ b/src/bin/auth/tests/common_unittest.cc
@@ -36,7 +36,7 @@ public:
BOOST_FOREACH(const Environ &env, restoreEnviron) {
if (env.second == NULL) {
EXPECT_EQ(0, unsetenv(env.first.c_str())) <<
- "Couldn't restore environment, results of other tests"
+ "Couldn't restore environment, results of other tests "
"are uncertain";
} else {
EXPECT_EQ(0, setenv(env.first.c_str(), env.second->c_str(),
diff --git a/src/bin/auth/tests/config_syntax_unittest.cc b/src/bin/auth/tests/config_syntax_unittest.cc
new file mode 100644
index 0000000..8caedfd
--- /dev/null
+++ b/src/bin/auth/tests/config_syntax_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <cc/data.h>
+#include <config/module_spec.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+using namespace isc::config;
+
+namespace {
+
+const char* const SPEC_FILE = AUTH_OBJ_DIR "/auth.spec";
+
+class AuthConfigSyntaxTest : public ::testing::Test {
+protected:
+ AuthConfigSyntaxTest() : mspec_(moduleSpecFromFile(SPEC_FILE))
+ {}
+ ModuleSpec mspec_;
+};
+
+TEST_F(AuthConfigSyntaxTest, inmemoryDefaultFileType) {
+ // filetype is optional
+ EXPECT_TRUE(
+ mspec_.validateConfig(
+ Element::fromJSON(
+ "{\"listen_on\": [], \"datasources\": "
+ " [{\"type\": \"memory\", \"class\": \"IN\", "
+ " \"zones\": [{\"origin\": \"example.com\","
+ " \"file\": \""
+ TEST_DATA_DIR "/example.zone\"}]}]}"), true));
+}
+
+TEST_F(AuthConfigSyntaxTest, inmemorySQLite3Backend) {
+ // Specifying non-default in-memory filetype
+ EXPECT_TRUE(
+ mspec_.validateConfig(
+ Element::fromJSON(
+ "{\"datasources\": "
+ " [{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example.com\","
+ " \"file\": \""
+ TEST_DATA_DIR "/example.zone\","
+ " \"filetype\": \"sqlite3\"}]}]}"), false));
+}
+
+TEST_F(AuthConfigSyntaxTest, badInmemoryFileType) {
+ // filetype must be a string
+ EXPECT_FALSE(
+ mspec_.validateConfig(
+ Element::fromJSON(
+ "{\"datasources\": "
+ " [{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example.com\","
+ " \"file\": \""
+ TEST_DATA_DIR "/example.zone\","
+ " \"filetype\": 42}]}]}"), false));
+}
+}
diff --git a/src/bin/auth/tests/config_unittest.cc b/src/bin/auth/tests/config_unittest.cc
index dadb0ee..d471a53 100644
--- a/src/bin/auth/tests/config_unittest.cc
+++ b/src/bin/auth/tests/config_unittest.cc
@@ -21,6 +21,7 @@
#include <cc/data.h>
+#include <datasrc/data_source.h>
#include <datasrc/memory_datasrc.h>
#include <xfr/xfrout_client.h>
@@ -29,30 +30,43 @@
#include <auth/auth_config.h>
#include <auth/common.h>
+#include "datasrc_util.h"
+
#include <testutils/mockups.h>
#include <testutils/portconfig.h>
+#include <testutils/socket_request.h>
+
+#include <sstream>
+using namespace std;
using namespace isc::dns;
using namespace isc::data;
using namespace isc::datasrc;
using namespace isc::asiodns;
-using namespace isc::asiolink;
+using namespace isc::auth::unittest;
+using namespace isc::testutils;
namespace {
class AuthConfigTest : public ::testing::Test {
protected:
AuthConfigTest() :
- dnss_(ios_, NULL, NULL, NULL),
+ dnss_(),
rrclass(RRClass::IN()),
- server(true, xfrout)
+ server(true, xfrout),
+ // The empty string is expected value of the parameter of
+ // requestSocket, not the app_name (there's no fallback, it checks
+ // the empty string is passed).
+ sock_requestor_(dnss_, address_store_, 53210, "")
{
server.setDNSService(dnss_);
}
- IOService ios_;
- DNSService dnss_;
+ MockDNSService dnss_;
const RRClass rrclass;
MockXfroutClient xfrout;
AuthSrv server;
+ isc::server_common::portconfig::AddressList address_store_;
+private:
+ isc::testutils::TestSocketRequestor sock_requestor_;
};
TEST_F(AuthConfigTest, datasourceConfig) {
@@ -138,6 +152,14 @@ TEST_F(AuthConfigTest, invalidListenAddressConfig) {
// Try setting addresses trough config
TEST_F(AuthConfigTest, listenAddressConfig) {
isc::testutils::portconfig::listenAddressConfig(server);
+
+ // listenAddressConfig should have attempted to create 4 DNS server
+ // objects: two IP addresses, TCP and UDP for each. For UDP, the "SYNC_OK"
+ // option should have been specified.
+ EXPECT_EQ(2, dnss_.getTCPFdParams().size());
+ EXPECT_EQ(2, dnss_.getUDPFdParams().size());
+ EXPECT_EQ(DNSService::SERVER_SYNC_OK, dnss_.getUDPFdParams().at(0).options);
+ EXPECT_EQ(DNSService::SERVER_SYNC_OK, dnss_.getUDPFdParams().at(1).options);
}
class MemoryDatasrcConfigTest : public AuthConfigTest {
@@ -183,7 +205,56 @@ TEST_F(MemoryDatasrcConfigTest, addOneZone) {
// Check it actually loaded something
EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(rrclass)->findZone(
Name("ns.example.com.")).zone_finder->find(Name("ns.example.com."),
- RRType::A()).code);
+ RRType::A())->code);
+}
+
+// This test uses dynamic load of a data source module, and won't work when
+// statically linked.
+TEST_F(MemoryDatasrcConfigTest,
+#ifdef USE_STATIC_LINK
+ DISABLED_addOneWithFiletypeSQLite3
+#else
+ addOneWithFiletypeSQLite3
+#endif
+ )
+{
+ const string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied";
+ stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n");
+ createSQLite3DB(rrclass, Name("example.org"), test_db.c_str(), ss);
+
+ // In-memory with an SQLite3 data source as the backend.
+ parser->build(Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example.org\","
+ " \"file\": \""
+ + test_db + "\","
+ " \"filetype\": \"sqlite3\"}]}]"));
+ parser->commit();
+ EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
+
+ // Failure case: the specified zone doesn't exist in the DB file.
+ delete parser;
+ parser = createAuthConfigParser(server, "datasources");
+ EXPECT_THROW(parser->build(
+ Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example.com\","
+ " \"file\": \""
+ + test_db + "\","
+ " \"filetype\": \"sqlite3\"}]}]")),
+ DataSourceError);
+}
+
+TEST_F(MemoryDatasrcConfigTest, addOneWithFiletypeText) {
+ // Explicitly specifying "text" is okay.
+ parser->build(Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example.com\","
+ " \"file\": \""
+ TEST_DATA_DIR "/example.zone\","
+ " \"filetype\": \"text\"}]}]"));
+ parser->commit();
+ EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
}
TEST_F(MemoryDatasrcConfigTest, addMultiZones) {
@@ -284,7 +355,7 @@ TEST_F(MemoryDatasrcConfigTest, remove) {
EXPECT_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
}
-TEST_F(MemoryDatasrcConfigTest, adDuplicateZones) {
+TEST_F(MemoryDatasrcConfigTest, addDuplicateZones) {
EXPECT_THROW(parser->build(
Element::fromJSON(
"[{\"type\": \"memory\","
@@ -298,6 +369,13 @@ TEST_F(MemoryDatasrcConfigTest, adDuplicateZones) {
}
TEST_F(MemoryDatasrcConfigTest, addBadZone) {
+ // origin and file are missing
+ EXPECT_THROW(parser->build(
+ Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{}]}]")),
+ AuthConfigError);
+
// origin is missing
EXPECT_THROW(parser->build(
Element::fromJSON(
@@ -305,6 +383,13 @@ TEST_F(MemoryDatasrcConfigTest, addBadZone) {
" \"zones\": [{\"file\": \"example.zone\"}]}]")),
AuthConfigError);
+ // file is missing
+ EXPECT_THROW(parser->build(
+ Element::fromJSON(
+ "[{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"example.com\"}]}]")),
+ AuthConfigError);
+
// missing zone file
EXPECT_THROW(parser->build(
Element::fromJSON(
@@ -317,7 +402,7 @@ TEST_F(MemoryDatasrcConfigTest, addBadZone) {
"[{\"type\": \"memory\","
" \"zones\": [{\"origin\": \"example..com\","
" \"file\": \"example.zone\"}]}]")),
- EmptyLabel);
+ AuthConfigError);
// bogus RR class name
EXPECT_THROW(parser->build(
diff --git a/src/bin/auth/tests/datasrc_util.cc b/src/bin/auth/tests/datasrc_util.cc
new file mode 100644
index 0000000..664ae8c
--- /dev/null
+++ b/src/bin/auth/tests/datasrc_util.cc
@@ -0,0 +1,77 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+
+#include <dns/masterload.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <cc/data.h>
+
+#include <datasrc/client.h>
+#include <datasrc/zone.h>
+#include <datasrc/factory.h>
+
+#include "datasrc_util.h"
+
+#include <boost/bind.hpp>
+
+#include <istream>
+
+#include <cstdlib>
+
+using namespace std;
+
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::datasrc;
+
+namespace isc {
+namespace auth {
+namespace unittest {
+
+namespace {
+void
+addRRset(ZoneUpdaterPtr updater, ConstRRsetPtr rrset) {
+ updater->addRRset(*rrset);
+}
+}
+
+void
+createSQLite3DB(RRClass zclass, const Name& zname,
+ const char* const db_file, istream& rr_stream)
+{
+ // We always begin with an empty template SQLite3 DB file and install
+ // the zone data from the zone file.
+ const char* const install_cmd_prefix = INSTALL_PROG " -c " TEST_DATA_DIR
+ "/rwtest.sqlite3 ";
+ const string install_cmd = string(install_cmd_prefix) + db_file;
+ if (system(install_cmd.c_str()) != 0) {
+ isc_throw(isc::Unexpected,
+ "Error setting up; command failed: " << install_cmd);
+ }
+
+ DataSourceClientContainer container("sqlite3",
+ Element::fromJSON(
+ "{\"database_file\": \"" +
+ string(db_file) + "\"}"));
+ ZoneUpdaterPtr updater = container.getInstance().getUpdater(zname, true);
+ masterLoad(rr_stream, zname, zclass, boost::bind(addRRset, updater, _1));
+ updater->commit();
+}
+
+} // end of unittest
+} // end of auth
+} // end of isc
diff --git a/src/bin/auth/tests/datasrc_util.h b/src/bin/auth/tests/datasrc_util.h
new file mode 100644
index 0000000..07ebc0c
--- /dev/null
+++ b/src/bin/auth/tests/datasrc_util.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __AUTH_DATA_SOURCE_UTIL_H
+#define __AUTH_DATA_SOURCE_UTIL_H 1
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <istream>
+
+namespace isc {
+namespace auth {
+namespace unittest {
+
+/// \brief Create an SQLite3 database file for a given zone from a stream.
+///
+/// This function creates an SQLite3 DB file for the specified zone
+/// with specified content. The zone will be created in the given
+/// SQLite3 database file. The database file does not have to exist;
+/// this function will automatically create a new file for the test
+/// based on a template that only contains the necessary schema. If
+/// the given file already exists this function overrides the content
+/// (so basically the file must be an ephemeral one only for that test
+/// case).
+///
+/// The input stream must produce strings as the corresponding
+/// \c dns::masterLoad() function expects.
+///
+/// \param zclass The RR class of the zone
+/// \param zname The origin name of the zone
+/// \param db_file The SQLite3 data base file in which the zone data should be
+/// installed.
+/// \param rr_stream An input stream that produces zone data.
+void
+createSQLite3DB(dns::RRClass zclass, const dns::Name& zname,
+ const char* const db_file, std::istream& rr_stream);
+
+} // end of unittest
+} // end of auth
+} // end of isc
+
+#endif // __AUTH_DATA_SOURCE_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 858759e..63429ae 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -12,12 +12,15 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <map>
#include <sstream>
#include <vector>
-#include <map>
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>
+#include <boost/static_assert.hpp>
+
+#include <exceptions/exceptions.h>
#include <dns/masterload.h>
#include <dns/message.h>
@@ -50,11 +53,23 @@ namespace {
// 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";
+//
+// For readability consistency, all strings are placed in a separate line,
+// even if they are very short and can reasonably fit in a single line with
+// the corresponding variable. For example, we write
+// const char* const foo_txt =
+// "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
+// instead of
+// const char* const foo_txt = "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
+const char* const soa_txt =
+ "example.com. 3600 IN SOA . . 0 0 0 0 0\n";
const char* const zone_ns_txt =
"example.com. 3600 IN NS glue.delegation.example.com.\n"
"example.com. 3600 IN NS noglue.example.com.\n"
"example.com. 3600 IN NS example.net.\n";
+const char* const zone_ds_txt =
+ "example.com. 3600 IN DS 57855 5 1 "
+ "B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
const char* const ns_addrs_txt =
"glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
"glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n"
@@ -64,11 +79,16 @@ const char* const delegation_txt =
"delegation.example.com. 3600 IN NS noglue.example.com.\n"
"delegation.example.com. 3600 IN NS cname.example.com.\n"
"delegation.example.com. 3600 IN NS example.org.\n";
+// Borrowed from the RFC4035
+const char* const delegation_ds_txt =
+ "delegation.example.com. 3600 IN DS 57855 5 1 "
+ "B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
const char* const mx_txt =
"mx.example.com. 3600 IN MX 10 www.example.com.\n"
"mx.example.com. 3600 IN MX 20 mailer.example.org.\n"
"mx.example.com. 3600 IN MX 30 mx.delegation.example.com.\n";
-const char* const www_a_txt = "www.example.com. 3600 IN A 192.0.2.80\n";
+const char* const 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 =
@@ -93,13 +113,15 @@ const char* const other_zone_rrs =
"cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.\n"
"mx.delegation.example.com. 3600 IN A 192.0.2.100\n";
// Wildcards
-const char* const wild_txt = "*.wild.example.com. 3600 IN A 192.0.2.7\n";
+const char* const wild_txt =
+ "*.wild.example.com. 3600 IN A 192.0.2.7\n";
const char* const nsec_wild_txt =
"*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG\n";
const char* const cnamewild_txt =
"*.cnamewild.example.com. 3600 IN CNAME www.example.org.\n";
-const char* const nsec_cnamewild_txt = "*.cnamewild.example.com. "
- "3600 IN NSEC delegation.example.com. CNAME NSEC RRSIG\n";
+const char* const nsec_cnamewild_txt =
+ "*.cnamewild.example.com. 3600 IN NSEC "
+ "delegation.example.com. CNAME NSEC RRSIG\n";
// Wildcard_nxrrset
const char* const wild_txt_nxrrset =
"*.uwild.example.com. 3600 IN A 192.0.2.9\n";
@@ -110,10 +132,12 @@ const char* const wild_txt_next =
const char* const nsec_wild_txt_next =
"www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG\n";
// Wildcard empty
-const char* const empty_txt = "b.*.t.example.com. 3600 IN A 192.0.2.13\n";
+const char* const empty_txt =
+ "b.*.t.example.com. 3600 IN A 192.0.2.13\n";
const char* const nsec_empty_txt =
"b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG\n";
-const char* const empty_prev_txt = "t.example.com. 3600 IN A 192.0.2.15\n";
+const char* const empty_prev_txt =
+ "t.example.com. 3600 IN A 192.0.2.15\n";
const char* const nsec_empty_prev_txt =
"t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG\n";
// Used in NXDOMAIN proof test. We are going to test some unusual case where
@@ -151,7 +175,74 @@ const char* const nsec_www_txt =
"www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG\n";
// Authoritative data without NSEC
-const char* const nonsec_a_txt = "nonsec.example.com. 3600 IN A 192.0.2.0\n";
+const char* const nonsec_a_txt =
+ "nonsec.example.com. 3600 IN A 192.0.2.0\n";
+
+// NSEC3 RRs. You may also need to add mapping to MockZoneFinder::hash_map_.
+const char* const nsec3_apex_txt =
+ "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 "
+ "aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG\n";
+const char* const nsec3_www_txt =
+ "q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 "
+ "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";
+
+// NSEC3 for wild.example.com (used in wildcard tests, will be added on
+// demand not to confuse other tests)
+const char* const nsec3_atwild_txt =
+ "ji6neoaepv8b5o6k4ev33abha8ht9fgc.example.com. 3600 IN NSEC3 1 1 12 "
+ "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en\n";
+
+// NSEC3 for cnamewild.example.com (used in wildcard tests, will be added on
+// demand not to confuse other tests)
+const char* const nsec3_atcnamewild_txt =
+ "k8udemvp1j2f7eg6jebps17vp3n8i58h.example.com. 3600 IN NSEC3 1 1 12 "
+ "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en\n";
+
+// NSEC3 for *.uwild.example.com (will be added on demand not to confuse
+// other tests)
+const char* const nsec3_wild_txt =
+ "b4um86eghhds6nea196smvmlo4ors995.example.com. 3600 IN NSEC3 1 1 12 "
+ "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";
+// NSEC3 for uwild.example.com. (will be added on demand)
+const char* const nsec3_uwild_txt =
+ "t644ebqk9bibcna874givr6joj62mlhv.example.com. 3600 IN NSEC3 1 1 12 "
+ "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";
+
+// (Secure) delegation data; Delegation with DS record
+const char* const signed_delegation_txt =
+ "signed-delegation.example.com. 3600 IN NS ns.example.net.\n";
+const char* const signed_delegation_ds_txt =
+ "signed-delegation.example.com. 3600 IN DS 12345 8 2 "
+ "764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA\n";
+
+// (Secure) delegation data; Delegation without DS record (and both NSEC
+// and NSEC3 denying its existence)
+const char* const unsigned_delegation_txt =
+ "unsigned-delegation.example.com. 3600 IN NS ns.example.net.\n";
+const char* const unsigned_delegation_nsec_txt =
+ "unsigned-delegation.example.com. 3600 IN NSEC "
+ "unsigned-delegation-optout.example.com. NS RRSIG NSEC\n";
+// This one will be added on demand
+const char* const unsigned_delegation_nsec3_txt =
+ "q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 "
+ "aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG\n";
+
+// Delegation without DS record, and no direct matching NSEC3 record
+const char* const unsigned_delegation_optout_txt =
+ "unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.\n";
+const char* const unsigned_delegation_optout_nsec_txt =
+ "unsigned-delegation-optout.example.com. 3600 IN NSEC "
+ "*.uwild.example.com. NS RRSIG NSEC\n";
+
+// (Secure) delegation data; Delegation where the DS lookup will raise an
+// exception.
+const char* const bad_delegation_txt =
+ "bad-delegation.example.com. 3600 IN NS ns.example.net.\n";
+
+// Delegation from an unsigned parent. There's no DS, and there's no NSEC
+// or NSEC3 that proves it.
+const char* const nosec_delegation_txt =
+ "nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.\n";
// A helper function that generates a textual representation of RRSIG RDATA
// for the given covered type. The resulting RRSIG may not necessarily make
@@ -164,13 +255,34 @@ getCommonRRSIGText(const string& type) {
"example.com. FAKEFAKEFAKE"));
}
+// A helper callback of masterLoad() used in InMemoryZoneFinderTest.
+void
+setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
+ *(*it) = rrset;
+ ++it;
+}
+
+// A helper function that converts a textual form of a single RR into a
+// RRsetPtr object. If it's SOA, origin must be set to its owner name;
+// otherwise masterLoad() will reject it.
+RRsetPtr
+textToRRset(const string& text_rrset, const Name& origin = Name::ROOT_NAME()) {
+ stringstream ss(text_rrset);
+ RRsetPtr rrset;
+ vector<RRsetPtr*> rrsets;
+ rrsets.push_back(&rrset);
+ masterLoad(ss, origin, RRClass::IN(),
+ boost::bind(setRRset, _1, rrsets.begin()));
+ return (rrset);
+}
+
// This is a mock Zone Finder class for testing.
// It is a derived class of ZoneFinder for the convenient of tests.
// Its find() method emulates the common behavior of protocol compliant
// ZoneFinder classes, but simplifies some minor cases and also supports broken
// behavior.
-// For simplicity, most names are assumed to be "in zone"; there's only
-// one zone cut at the point of name "delegation.example.com".
+// For simplicity, most names are assumed to be "in zone"; delegations
+// to child zones are identified by the existence of non origin NS records.
// Another special name is "dname.example.com". Query names under this name
// will result in DNAME.
// This mock zone doesn't handle empty non terminal nodes (if we need to test
@@ -179,25 +291,34 @@ class MockZoneFinder : public ZoneFinder {
public:
MockZoneFinder() :
origin_(Name("example.com")),
- delegation_name_("delegation.example.com"),
+ bad_signed_delegation_name_("bad-delegation.example.com"),
dname_name_("dname.example.com"),
has_SOA_(true),
has_apex_NS_(true),
rrclass_(RRClass::IN()),
include_rrsig_anyway_(false),
- nsec_name_(origin_)
+ use_nsec3_(false),
+ nsec_name_(origin_),
+ nsec3_fake_(NULL),
+ nsec3_name_(NULL)
{
stringstream zone_stream;
zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
- delegation_txt << mx_txt << www_a_txt << cname_txt <<
- cname_nxdom_txt << cname_out_txt << dname_txt << dname_a_txt <<
- other_zone_rrs << no_txt << nz_txt <<
+ delegation_txt << delegation_ds_txt << mx_txt << www_a_txt <<
+ cname_txt << cname_nxdom_txt << cname_out_txt << dname_txt <<
+ dname_a_txt << other_zone_rrs << no_txt << nz_txt <<
nsec_apex_txt << nsec_mx_txt << nsec_no_txt << nsec_nz_txt <<
nsec_nxdomain_txt << nsec_www_txt << nonsec_a_txt <<
wild_txt << nsec_wild_txt << cnamewild_txt << nsec_cnamewild_txt <<
wild_txt_nxrrset << nsec_wild_txt_nxrrset << wild_txt_next <<
nsec_wild_txt_next << empty_txt << nsec_empty_txt <<
- empty_prev_txt << nsec_empty_prev_txt;
+ empty_prev_txt << nsec_empty_prev_txt <<
+ nsec3_apex_txt << nsec3_www_txt <<
+ signed_delegation_txt << signed_delegation_ds_txt <<
+ unsigned_delegation_txt << unsigned_delegation_nsec_txt <<
+ unsigned_delegation_optout_txt <<
+ unsigned_delegation_optout_nsec_txt <<
+ bad_delegation_txt << nosec_delegation_txt;
masterLoad(zone_stream, origin_, rrclass_,
boost::bind(&MockZoneFinder::loadRRset, this, _1));
@@ -206,15 +327,65 @@ public:
RRClass::IN(),
RRType::NSEC(),
RRTTL(3600)));
+
+ // (Faked) NSEC3 hash map. For convenience we use hardcoded built-in
+ // map instead of calculating and using actual hash.
+ // The used hash values are borrowed from RFC5155 examples (they are
+ // based on the query name, not that they would correspond directly
+ // to the name).
+ hash_map_[Name("example.com")] = "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom";
+ hash_map_[Name("www.example.com")] =
+ "q04jkcevqvmu85r014c7dkba38o0ji5r";
+ hash_map_[Name("nxdomain.example.com")] =
+ "v644ebqk9bibcna874givr6joj62mlhv";
+ hash_map_[Name("nx.domain.example.com")] =
+ "v644ebqk9bibcna874givr6joj62mlhv";
+ hash_map_[Name("domain.example.com")] =
+ "v644ebqk9bibcna874givr6joj62mlhv";
+ hash_map_[Name("nxdomain2.example.com")] =
+ "q00jkcevqvmu85r014c7dkba38o0ji5r";
+ hash_map_[Name("nxdomain3.example.com")] =
+ "009mhaveqvm6t7vbl5lop2u3t2rp3tom";
+ hash_map_[Name("*.example.com")] =
+ "r53bq7cc2uvmubfu5ocmm6pers9tk9en";
+ hash_map_[Name("unsigned-delegation.example.com")] =
+ "q81r598950igr1eqvc60aedlq66425b5"; // a bit larger than H(www)
+ hash_map_[Name("*.uwild.example.com")] =
+ "b4um86eghhds6nea196smvmlo4ors995";
+ hash_map_[Name("unsigned-delegation-optout.example.com")] =
+ "vld46lphhasfapj8og1pglgiasa5o5gt";
+
+ // For wildcard proofs
+ hash_map_[Name("wild.example.com")] =
+ "ji6neoaepv8b5o6k4ev33abha8ht9fgc";
+ hash_map_[Name("y.wild.example.com")] =
+ "0p9mhaveqvm6t7vbl5lop2u3t2rp3ton"; // a bit larger than H(<apex>)
+ hash_map_[Name("x.y.wild.example.com")] =
+ "q04jkcevqvmu85r014c7dkba38o0ji6r"; // a bit larger than H(www)
+ hash_map_[Name("cnamewild.example.com")] =
+ "k8udemvp1j2f7eg6jebps17vp3n8i58h";
+ hash_map_[Name("www.cnamewild.example.com")] =
+ "q04jkcevqvmu85r014c7dkba38o0ji6r"; // a bit larger than H(www)
+
+ // For closest encloser proof for www1.uwild.example.com:
+ hash_map_[Name("uwild.example.com")] =
+ "t644ebqk9bibcna874givr6joj62mlhv";
+ hash_map_[Name("www1.uwild.example.com")] =
+ "q04jkcevqvmu85r014c7dkba38o0ji6r"; // a bit larger than H(www)
}
virtual isc::dns::Name getOrigin() const { return (origin_); }
virtual isc::dns::RRClass getClass() const { return (rrclass_); }
- virtual FindResult find(const isc::dns::Name& name,
- const isc::dns::RRType& type,
- const FindOptions options = FIND_DEFAULT);
- virtual FindResult findAll(const isc::dns::Name& name,
- std::vector<ConstRRsetPtr>& target,
- const FindOptions options = FIND_DEFAULT);
+ virtual ZoneFinderContextPtr find(const isc::dns::Name& name,
+ const isc::dns::RRType& type,
+ const FindOptions options =
+ FIND_DEFAULT);
+ virtual ZoneFinderContextPtr findAll(const isc::dns::Name& name,
+ std::vector<ConstRRsetPtr>& target,
+ const FindOptions options =
+ FIND_DEFAULT);
+
+ virtual ZoneFinder::FindNSEC3Result
+ findNSEC3(const Name& name, bool recursive);
// If false is passed, it makes the zone broken as if it didn't have the
// SOA.
@@ -233,41 +404,111 @@ public:
ConstRRsetPtr rrset)
{
nsec_name_ = nsec_name;
- nsec_result_.reset(new ZoneFinder::FindResult(code, rrset));
+ nsec_context_.reset(new Context(*this,
+ FIND_DEFAULT, // a fake value
+ ResultContext(code, rrset,
+ RESULT_NSEC_SIGNED)));
+ }
+
+ // Once called, the findNSEC3 will return the provided result for the next
+ // query. After that, it'll return to operate normally.
+ // NULL disables. Does not take ownership of the pointer (it is generally
+ // expected to be a local variable in the test function).
+ void setNSEC3Result(const FindNSEC3Result* result,
+ const Name* name = NULL)
+ {
+ nsec3_fake_ = result;
+ nsec3_name_ = name;
}
- Name findPreviousName(const Name&) const {
+ // If true is passed return an empty NSEC3 RRset for some negative
+ // answers when DNSSEC is required.
+ void setNSEC3Flag(bool on) { use_nsec3_ = on; }
+
+ virtual Name findPreviousName(const Name&) const {
isc_throw(isc::NotImplemented, "Mock doesn't support previous name");
}
+ // This method allows tests to insert new record in the middle of the test.
+ //
+ // \param record_txt textual RR representation of RR (such as soa_txt, etc)
+ void addRecord(const string& record_txt) {
+ stringstream record_stream;
+ record_stream << record_txt;
+ masterLoad(record_stream, origin_, rrclass_,
+ boost::bind(&MockZoneFinder::loadRRset, this, _1));
+ }
+
public:
// We allow the tests to use these for convenience
- ConstRRsetPtr delegation_rrset_;
+ ConstRRsetPtr dname_rrset_; // could be used as an arbitrary bogus RRset
ConstRRsetPtr empty_nsec_rrset_;
+protected:
+ // A convenient shortcut. Will also be used by further derived mocks.
+ ZoneFinderContextPtr createContext(FindOptions options,
+ Result code,
+ isc::dns::ConstRRsetPtr rrset,
+ FindResultFlags flags = RESULT_DEFAULT)
+ {
+ return (ZoneFinderContextPtr(
+ new Context(*this, options,
+ ResultContext(code, rrset, flags))));
+ }
+
private:
typedef map<RRType, ConstRRsetPtr> RRsetStore;
typedef map<Name, RRsetStore> Domains;
Domains domains_;
+ Domains delegations_;
+ Domains nsec3_domains_;
+
+ // This is used to identify delegation to a child zone, and used to
+ // find a matching entry in delegations_. Note that first found entry
+ // is returned, so it's not a longest match. Test data must be set up
+ // to ensure the first match is always the longest match.
+ struct SubdomainMatch {
+ SubdomainMatch(const Name& name) : name_(name) {}
+ bool operator()(const pair<Name, RRsetStore>& domain_elem) const {
+ return (name_ == domain_elem.first ||
+ name_.compare(domain_elem.first).getRelation() ==
+ NameComparisonResult::SUBDOMAIN);
+ }
+ private:
+ const Name& name_;
+ };
+
void loadRRset(RRsetPtr rrset) {
+ if (rrset->getType() == RRType::NSEC3()) {
+ // NSEC3 should go to the dedicated table
+ nsec3_domains_[rrset->getName()][rrset->getType()] = rrset;
+
+ // By nature it should have RRSIG. (We may want to selectively
+ // omit this to test pathological cases).
+ rrset->addRRsig(RdataPtr(new generic::RRSIG(
+ getCommonRRSIGText(rrset->getType().
+ toText()))));
+ return;
+ }
domains_[rrset->getName()][rrset->getType()] = rrset;
- if (rrset->getName() == delegation_name_ &&
- rrset->getType() == RRType::NS()) {
- delegation_rrset_ = rrset;
+
+ // Remember delegation (NS/DNAME) related RRsets separately.
+ if (rrset->getType() == RRType::NS() && rrset->getName() != origin_) {
+ delegations_[rrset->getName()][rrset->getType()] = rrset;
} else if (rrset->getName() == dname_name_ &&
- rrset->getType() == RRType::DNAME()) {
+ rrset->getType() == RRType::DNAME()) {
dname_rrset_ = rrset;
- // Add some signatures
- } else if (rrset->getName() == Name("example.com.") &&
- rrset->getType() == RRType::NS()) {
- // For NS, we only have RRSIG for the origin name.
- rrset->addRRsig(RdataPtr(new generic::RRSIG(
- getCommonRRSIGText("NS"))));
- } else {
- // For others generate RRSIG unconditionally. Technically this
- // is wrong because we shouldn't have it for names under a zone
- // cut. But in our tests that doesn't matter, so we add them
- // just for simplicity.
+ }
+
+ // Add some signatures. For NS, we only have RRSIG for the origin
+ // name. For others generate RRSIG unconditionally. Technically this
+ // is wrong because we shouldn't have it for names under a zone
+ // cut. But in our tests that doesn't matter, so we add them
+ // just for simplicity.
+ // Note that this includes RRSIG for DS with secure delegations.
+ // They should have RRSIGs, so that's actually expected data, not just
+ // for simplicity.
+ if (rrset->getType() != RRType::NS() || rrset->getName() == origin_) {
rrset->addRRsig(RdataPtr(new generic::RRSIG(
getCommonRRSIGText(rrset->getType().
toText()))));
@@ -276,22 +517,29 @@ private:
const Name origin_;
// Names where we delegate somewhere else
- const Name delegation_name_;
+ const Name bad_signed_delegation_name_;
const Name dname_name_;
bool has_SOA_;
bool has_apex_NS_;
- ConstRRsetPtr dname_rrset_;
const RRClass rrclass_;
bool include_rrsig_anyway_;
+ bool use_nsec3_;
// The following two will be used for faked NSEC cases
Name nsec_name_;
- boost::scoped_ptr<ZoneFinder::FindResult> nsec_result_;
+ ZoneFinderContextPtr nsec_context_;
+ // The following two are for faking bad NSEC3 responses
+ // Enabled when not NULL
+ const FindNSEC3Result* nsec3_fake_;
+ const Name* nsec3_name_;
+public:
+ // Public, to allow tests looking up the right names for something
+ map<Name, string> hash_map_;
};
// A helper function that generates a new RRset based on "wild_rrset",
// replacing its owner name with 'real_name'.
ConstRRsetPtr
-substituteWild(const RRset& wild_rrset, const Name& real_name) {
+substituteWild(const AbstractRRset& wild_rrset, const Name& real_name) {
RRsetPtr rrset(new RRset(real_name, wild_rrset.getClass(),
wild_rrset.getType(), wild_rrset.getTTL()));
// For simplicity we only consider the case with one RDATA (for now)
@@ -306,12 +554,12 @@ substituteWild(const RRset& wild_rrset, const Name& real_name) {
return (rrset);
}
-ZoneFinder::FindResult
+ZoneFinderContextPtr
MockZoneFinder::findAll(const Name& name, std::vector<ConstRRsetPtr>& target,
const FindOptions options)
{
- ZoneFinder::FindResult result(find(name, RRType::ANY(), options));
- if (result.code == NXRRSET) {
+ ZoneFinderContextPtr result(find(name, RRType::ANY(), options));
+ if (result->code == NXRRSET) {
const Domains::const_iterator found_domain = domains_.find(name);
if (!found_domain->second.empty()) {
for (RRsetStore::const_iterator found_rrset =
@@ -320,35 +568,101 @@ MockZoneFinder::findAll(const Name& name, std::vector<ConstRRsetPtr>& target,
// Insert RRs under the domain name into target
target.push_back(found_rrset->second);
}
- return (FindResult(SUCCESS, RRsetPtr()));
+ return (ZoneFinderContextPtr(
+ new Context(*this, options,
+ ResultContext(SUCCESS, RRsetPtr()),
+ target)));
}
}
return (result);
}
-ZoneFinder::FindResult
+ZoneFinder::FindNSEC3Result
+MockZoneFinder::findNSEC3(const Name& name, bool recursive) {
+ // Do we have a fake result set? If so, use it.
+ if (nsec3_fake_ != NULL &&
+ (nsec3_name_ == NULL || *nsec3_name_ == name)) {
+ const FindNSEC3Result* result(nsec3_fake_);
+ return (*result);
+ }
+
+ ConstRRsetPtr covering_proof;
+ const int labels = name.getLabelCount();
+
+ // For brevity, we assume several things below: maps should have an
+ // expected entry when operator[] is used; maps are not empty.
+ for (int i = 0; i < labels; ++i) {
+ const string hlabel = hash_map_[name.split(i, labels - i)];
+ if (hlabel.empty()) {
+ isc_throw(isc::Unexpected, "findNSEC3() hash failure for " <<
+ name.split(i, labels - i));
+ }
+ const Name hname = Name(hlabel + ".example.com");
+ // We don't use const_iterator so that we can use operator[] below
+ Domains::iterator found_domain = nsec3_domains_.lower_bound(hname);
+
+ // If the given hash is larger than the largest stored hash or
+ // the first label doesn't match the target, identify the "previous"
+ // hash value and remember it as the candidate next closer proof.
+ if (found_domain == nsec3_domains_.end() ||
+ found_domain->first.split(0, 1).toText(true) != hlabel) {
+ // If the given hash is larger or smaller than everything,
+ // the covering proof is the NSEC3 that has the largest hash.
+ if (found_domain == nsec3_domains_.end() ||
+ found_domain == nsec3_domains_.begin()) {
+ covering_proof =
+ nsec3_domains_.rbegin()->second[RRType::NSEC3()];
+ } else {
+ // Otherwise, H(found_domain-1) < given_hash < H(found_domain)
+ // The covering proof is the first one.
+ covering_proof = (--found_domain)->second[RRType::NSEC3()];
+ }
+ if (!recursive) { // in non recursive mode, we are done.
+ return (ZoneFinder::FindNSEC3Result(false,
+ name.getLabelCount(),
+ covering_proof,
+ ConstRRsetPtr()));
+ }
+ } else { // exact match
+ return (ZoneFinder::FindNSEC3Result(
+ true, name.getLabelCount() - i,
+ found_domain->second[RRType::NSEC3()],
+ covering_proof));
+ }
+ }
+ isc_throw(isc::Unexpected, "findNSEC3() isn't expected to fail");
+}
+
+ZoneFinderContextPtr
MockZoneFinder::find(const Name& name, const RRType& type,
const FindOptions options)
{
// 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()));
+ return (createContext(options, NXDOMAIN, RRsetPtr()));
} else if (name == origin_ && type == RRType::NS() && !has_apex_NS_) {
- return (FindResult(NXDOMAIN, RRsetPtr()));
+ return (createContext(options, NXDOMAIN, RRsetPtr()));
}
- // Special case for names on or under a zone cut
+ // Special case for names on or under a zone cut and under DNAME
+ Domains::iterator it;
if ((options & FIND_GLUE_OK) == 0 &&
- (name == delegation_name_ ||
- name.compare(delegation_name_).getRelation() ==
- NameComparisonResult::SUBDOMAIN)) {
- return (FindResult(DELEGATION, delegation_rrset_));
- // And under DNAME
+ (it = find_if(delegations_.begin(), delegations_.end(),
+ SubdomainMatch(name))) != delegations_.end()) {
+ ConstRRsetPtr delegation_ns = it->second[RRType::NS()];
+ assert(delegation_ns); // should be ensured by how we construct it
+
+ // DS query for the delegated domain (i.e. an exact match) will be
+ // handled just like an in-zone case below. Others result in
+ // DELEGATION.
+ if (type != RRType::DS() || it->first != name) {
+ return (createContext(options, DELEGATION, delegation_ns));
+ }
} else if (name.compare(dname_name_).getRelation() ==
- NameComparisonResult::SUBDOMAIN) {
- return (FindResult(DNAME, dname_rrset_));
+ NameComparisonResult::SUBDOMAIN) {
+ return (createContext(options, DNAME, dname_rrset_));
}
// normal cases. names are searched for only per exact-match basis
@@ -362,7 +676,7 @@ MockZoneFinder::find(const Name& name, const RRType& type,
ConstRRsetPtr rrset;
// Strip whatever signature there is in case DNSSEC is not required
// Just to make sure the Query asks for it when it is needed
- if (options & ZoneFinder::FIND_DNSSEC ||
+ if ((options & ZoneFinder::FIND_DNSSEC) != 0 ||
include_rrsig_anyway_ ||
!found_rrset->second->getRRsig()) {
rrset = found_rrset->second;
@@ -378,23 +692,36 @@ MockZoneFinder::find(const Name& name, const RRType& type,
}
rrset = noconst;
}
- return (FindResult(SUCCESS, rrset));
+ return (createContext(options, SUCCESS, rrset));
}
// 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));
+ return (createContext(options, CNAME, found_rrset->second));
}
- // Otherwise it's NXRRSET case.
+ // Otherwise it's NXRRSET case...
+ // ...but a special pathological case first:
+ if (found_domain->first == bad_signed_delegation_name_ &&
+ type == RRType::DS()) {
+ return (createContext(options, NXDOMAIN, RRsetPtr()));
+ }
+ // normal cases follow.
if ((options & FIND_DNSSEC) != 0) {
+ if (use_nsec3_) {
+ return (createContext(options, NXRRSET, RRsetPtr(),
+ RESULT_NSEC3_SIGNED));
+ }
found_rrset = found_domain->second.find(RRType::NSEC());
if (found_rrset != found_domain->second.end()) {
- return (FindResult(NXRRSET, found_rrset->second));
+ return (createContext(options, NXRRSET, found_rrset->second,
+ RESULT_NSEC_SIGNED));
}
}
- return (FindResult(NXRRSET, RRsetPtr()));
+ // If no NSEC is found or DNSSEC isn't specified, behave as if the
+ // zone is unsigned.
+ return (createContext(options, NXRRSET, RRsetPtr()));
}
// query name isn't found in our domains.
@@ -413,59 +740,88 @@ MockZoneFinder::find(const Name& name, const RRType& type,
// the origin of the zone)
--domain; // reset domain to the "previous name"
if ((options & FIND_DNSSEC) != 0) {
+ if (use_nsec3_) {
+ return (createContext(options, NXRRSET, RRsetPtr(),
+ RESULT_NSEC3_SIGNED));
+ }
RRsetStore::const_iterator found_rrset =
(*domain).second.find(RRType::NSEC());
if (found_rrset != (*domain).second.end()) {
- return (FindResult(NXRRSET, found_rrset->second));
+ return (createContext(options, NXRRSET, found_rrset->second,
+ RESULT_NSEC_SIGNED));
}
}
- return (FindResult(NXRRSET, RRsetPtr()));
+ return (createContext(options, NXRRSET, RRsetPtr()));
}
// Another possibility is wildcard. For simplicity we only check
// hardcoded specific cases, ignoring other details such as canceling
// due to the existence of closer name.
if ((options & NO_WILDCARD) == 0) {
- const Name wild_suffix(name.split(1));
+ const Name wild_suffix(name == Name("x.y.wild.example.com") ?
+ Name("wild.example.com") : name.split(1));
// Unit Tests use those domains for Wildcard test.
- if (name.equals(Name("www.wild.example.com"))||
- name.equals(Name("www1.uwild.example.com"))||
- name.equals(Name("a.t.example.com"))) {
+ if (name.equals(Name("www.wild.example.com")) ||
+ name.equals(Name("x.y.wild.example.com")) ||
+ name.equals(Name("www1.uwild.example.com")) ||
+ name.equals(Name("a.t.example.com"))) {
if (name.compare(wild_suffix).getRelation() ==
NameComparisonResult::SUBDOMAIN) {
domain = domains_.find(Name("*").concatenate(wild_suffix));
// Matched the QNAME
if (domain != domains_.end()) {
- RRsetStore::const_iterator found_rrset =
- domain->second.find(type);
- // Matched the QTYPE
- if(found_rrset != domain->second.end()) {
- return (FindResult(WILDCARD,
- substituteWild(*found_rrset->second, name)));
- } else {
- // No matched QTYPE, this case is for WILDCARD_NXRRSET
- found_rrset = domain->second.find(RRType::NSEC());
- assert(found_rrset != domain->second.end());
- Name newName = Name("*").concatenate(wild_suffix);
- return (FindResult(WILDCARD_NXRRSET,
- substituteWild(*found_rrset->second,newName)));
- }
- } else {
+ RRsetStore::const_iterator found_rrset =
+ domain->second.find(type);
+ // Matched the QTYPE
+ if(found_rrset != domain->second.end()) {
+ return (createContext(options,SUCCESS, substituteWild(
+ *found_rrset->second, name),
+ RESULT_WILDCARD |
+ (use_nsec3_ ?
+ RESULT_NSEC3_SIGNED :
+ RESULT_NSEC_SIGNED)));
+ } else {
+ // No matched QTYPE, this case is for NXRRSET with
+ // WILDCARD
+ if (use_nsec3_) {
+ return (createContext(options, NXRRSET, RRsetPtr(),
+ RESULT_WILDCARD |
+ RESULT_NSEC3_SIGNED));
+ }
+ const Name new_name =
+ Name("*").concatenate(wild_suffix);
+ found_rrset = domain->second.find(RRType::NSEC());
+ assert(found_rrset != domain->second.end());
+ return (createContext(options, NXRRSET, substituteWild(
+ *found_rrset->second,
+ new_name),
+ RESULT_WILDCARD |
+ RESULT_NSEC_SIGNED));
+ }
+ } else {
// This is empty non terminal name case on wildcard.
- Name emptyName = Name("*").concatenate(wild_suffix);
+ const Name empty_name = Name("*").concatenate(wild_suffix);
+ if (use_nsec3_) {
+ return (createContext(options, NXRRSET, RRsetPtr(),
+ RESULT_WILDCARD |
+ RESULT_NSEC3_SIGNED));
+ }
for (Domains::reverse_iterator it = domains_.rbegin();
- it != domains_.rend();
- ++it) {
- RRsetStore::const_iterator nsec_it;
- if ((*it).first < emptyName &&
+ it != domains_.rend();
+ ++it) {
+ RRsetStore::const_iterator nsec_it;
+ if ((*it).first < empty_name &&
(nsec_it = (*it).second.find(RRType::NSEC()))
!= (*it).second.end()) {
- return (FindResult(WILDCARD_NXRRSET,
- (*nsec_it).second));
- }
+ return (createContext(options, NXRRSET,
+ (*nsec_it).second,
+ RESULT_WILDCARD |
+ RESULT_NSEC_SIGNED));
}
+ }
}
- return (FindResult(WILDCARD_NXRRSET,RRsetPtr()));
+ return (createContext(options, NXRRSET, RRsetPtr(),
+ RESULT_WILDCARD));
}
}
const Name cnamewild_suffix("cnamewild.example.com");
@@ -476,8 +832,11 @@ MockZoneFinder::find(const Name& name, const RRType& type,
RRsetStore::const_iterator found_rrset =
domain->second.find(RRType::CNAME());
assert(found_rrset != domain->second.end());
- return (FindResult(WILDCARD_CNAME,
- substituteWild(*found_rrset->second, name)));
+ return (createContext(options, CNAME,
+ substituteWild(*found_rrset->second, name),
+ RESULT_WILDCARD |
+ (use_nsec3_ ? RESULT_NSEC3_SIGNED :
+ RESULT_NSEC_SIGNED)));
}
}
@@ -489,9 +848,14 @@ MockZoneFinder::find(const Name& name, const RRType& type,
// we don't care about pathological cases such as the name is "smaller"
// than the origin)
if ((options & FIND_DNSSEC) != 0) {
+ if (use_nsec3_) {
+ return (createContext(options, NXDOMAIN, RRsetPtr(),
+ RESULT_NSEC3_SIGNED));
+ }
+
// Emulate a broken DataSourceClient for some special names.
- if (nsec_result_ && nsec_name_ == name) {
- return (*nsec_result_);
+ if (nsec_context_ && nsec_name_ == name) {
+ return (nsec_context_);
}
// Normal case
@@ -504,11 +868,12 @@ MockZoneFinder::find(const Name& name, const RRType& type,
if ((*it).first < name &&
(nsec_it = (*it).second.find(RRType::NSEC()))
!= (*it).second.end()) {
- return (FindResult(NXDOMAIN, (*nsec_it).second));
+ return (createContext(options, NXDOMAIN, (*nsec_it).second,
+ RESULT_NSEC_SIGNED));
}
}
}
- return (FindResult(NXDOMAIN, RRsetPtr()));
+ return (createContext(options,NXDOMAIN, RRsetPtr()));
}
class QueryTest : public ::testing::Test {
@@ -516,7 +881,14 @@ protected:
QueryTest() :
qname(Name("www.example.com")), qclass(RRClass::IN()),
qtype(RRType::A()), response(Message::RENDER),
- qid(response.getQid()), query_code(Opcode::QUERY().getCode())
+ qid(response.getQid()), query_code(Opcode::QUERY().getCode()),
+ ns_addrs_and_sig_txt(string(ns_addrs_txt) +
+ "glue.delegation.example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("A") + "\n" +
+ "glue.delegation.example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("AAAA") + "\n" +
+ "noglue.example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("A"))
{
response.setRcode(Rcode::NOERROR());
response.setOpcode(Opcode::QUERY());
@@ -536,6 +908,8 @@ protected:
Message response;
const qid_t qid;
const uint16_t query_code;
+ const string ns_addrs_and_sig_txt; // convenient shortcut
+ Query query;
};
// A wrapper to check resulting response message commonly used in
@@ -579,15 +953,31 @@ TEST_F(QueryTest, noZone) {
// There's no zone in the memory datasource. So the response should have
// REFUSED.
InMemoryClient empty_memory_client;
- Query nozone_query(empty_memory_client, qname, qtype, response);
- EXPECT_NO_THROW(nozone_query.process());
+ EXPECT_NO_THROW(query.process(empty_memory_client, qname, qtype,
+ response));
EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
}
TEST_F(QueryTest, exactMatch) {
- Query query(memory_client, qname, qtype, response);
- EXPECT_NO_THROW(query.process());
+ EXPECT_NO_THROW(query.process(memory_client, qname, qtype, response));
+ // find match rrset
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ www_a_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, exactMatchMultipleQueries) {
+ EXPECT_NO_THROW(query.process(memory_client, qname, qtype, response));
+ // find match rrset
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ www_a_txt, zone_ns_txt, ns_addrs_txt);
+
+ // clean up response for second query
+ response.clear(isc::dns::Message::RENDER);
+ response.setRcode(Rcode::NOERROR());
+ response.setOpcode(Opcode::QUERY());
+ EXPECT_NO_THROW(query.process(memory_client, qname, qtype, response));
// find match rrset
+ SCOPED_TRACE("Second query");
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
@@ -596,8 +986,7 @@ TEST_F(QueryTest, exactMatchIgnoreSIG) {
// Check that we do not include the RRSIG when not requested even when
// we receive it from the data source.
mock_finder->setIncludeRRSIGAnyway(true);
- Query query(memory_client, qname, qtype, response);
- EXPECT_NO_THROW(query.process());
+ EXPECT_NO_THROW(query.process(memory_client, qname, qtype, response));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
@@ -605,13 +994,9 @@ TEST_F(QueryTest, exactMatchIgnoreSIG) {
TEST_F(QueryTest, dnssecPositive) {
// Just like exactMatch, but the signatures should be included as well
- Query query(memory_client, qname, qtype, response, true);
- EXPECT_NO_THROW(query.process());
+ EXPECT_NO_THROW(query.process(memory_client, qname, qtype, response,
+ true));
// find match rrset
- // We can't let responseCheck to check the additional section as well,
- // it gets confused by the two RRs for glue.delegation.../RRSIG due
- // to it's design and fixing it would be hard. Therefore we simply
- // check manually this one time.
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 4, 6,
(www_a_txt + std::string("www.example.com. 3600 IN RRSIG "
"A 5 3 3600 20000101000000 "
@@ -621,34 +1006,16 @@ TEST_F(QueryTest, dnssecPositive) {
"3 3600 20000101000000 "
"20000201000000 12345 "
"example.com. FAKEFAKEFAKE\n")).
- c_str(), NULL);
- RRsetIterator iterator(response.beginSection(Message::SECTION_ADDITIONAL));
- const char* additional[] = {
- "glue.delegation.example.com. 3600 IN A 192.0.2.153\n",
- "glue.delegation.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 "
- "20000201000000 12345 example.com. FAKEFAKEFAKE\n",
- "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n",
- "glue.delegation.example.com. 3600 IN RRSIG AAAA 5 3 3600 "
- "20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE\n",
- "noglue.example.com. 3600 IN A 192.0.2.53\n",
- "noglue.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 "
- "20000201000000 12345 example.com. FAKEFAKEFAKE\n",
- NULL
- };
- for (const char** rr(additional); *rr != NULL; ++ rr) {
- ASSERT_FALSE(iterator ==
- response.endSection(Message::SECTION_ADDITIONAL));
- EXPECT_EQ(*rr, (*iterator)->toText());
- iterator ++;
- }
- EXPECT_TRUE(iterator == response.endSection(Message::SECTION_ADDITIONAL));
+ c_str(),
+ ns_addrs_and_sig_txt.c_str());
}
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_client, Name("noglue.example.com"), qtype,
- response).process());
+ EXPECT_NO_THROW(query.process(memory_client,
+ Name("noglue.example.com"),
+ qtype, response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 2,
"noglue.example.com. 3600 IN A 192.0.2.53\n", zone_ns_txt,
@@ -659,8 +1026,8 @@ TEST_F(QueryTest, exactAddrMatch) {
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_client, Name("example.com"), RRType::NS(),
- response).process());
+ EXPECT_NO_THROW(query.process(memory_client, Name("example.com"),
+ RRType::NS(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 0, 3,
zone_ns_txt, NULL, ns_addrs_txt);
@@ -670,8 +1037,8 @@ TEST_F(QueryTest, apexNSMatch) {
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_client, Name("noglue.example.com"),
- RRType::ANY(), response).process());
+ EXPECT_NO_THROW(query.process(memory_client, Name("noglue.example.com"),
+ RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 3, 2,
(string("noglue.example.com. 3600 IN A 192.0.2.53\n") +
@@ -684,8 +1051,8 @@ TEST_F(QueryTest, exactAnyMatch) {
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_client, Name("example.com"),
- RRType::ANY(), response).process());
+ EXPECT_NO_THROW(query.process(memory_client, Name("example.com"),
+ RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 5, 0, 3,
(string(soa_txt) + string(zone_ns_txt) +
string(nsec_apex_txt)).c_str(),
@@ -693,23 +1060,23 @@ TEST_F(QueryTest, apexAnyMatch) {
}
TEST_F(QueryTest, mxANYMatch) {
- EXPECT_NO_THROW(Query(memory_client, Name("mx.example.com"),
- RRType::ANY(), response).process());
+ EXPECT_NO_THROW(query.process(memory_client, Name("mx.example.com"),
+ RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 4, 3, 4,
(string(mx_txt) + string(nsec_mx_txt)).c_str(), zone_ns_txt,
(string(ns_addrs_txt) + string(www_a_txt)).c_str());
}
TEST_F(QueryTest, glueANYMatch) {
- EXPECT_NO_THROW(Query(memory_client, Name("delegation.example.com"),
- RRType::ANY(), response).process());
+ EXPECT_NO_THROW(query.process(memory_client, Name("delegation.example.com"),
+ RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
NULL, delegation_txt, ns_addrs_txt);
}
TEST_F(QueryTest, nodomainANY) {
- EXPECT_NO_THROW(Query(memory_client, Name("nxdomain.example.com"),
- RRType::ANY(), response).process());
+ EXPECT_NO_THROW(query.process(memory_client, Name("nxdomain.example.com"),
+ RRType::ANY(), response));
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
}
@@ -721,22 +1088,128 @@ TEST_F(QueryTest, noApexNS) {
// Disable apex NS record
mock_finder->setApexNSFlag(false);
- EXPECT_THROW(Query(memory_client, Name("noglue.example.com"), qtype,
- response).process(), Query::NoApexNS);
+ EXPECT_THROW(query.process(memory_client, Name("noglue.example.com"), qtype,
+ response), Query::NoApexNS);
// We don't look into the response, as it threw
}
TEST_F(QueryTest, delegation) {
- EXPECT_NO_THROW(Query(memory_client, Name("delegation.example.com"),
- qtype, response).process());
+ EXPECT_NO_THROW(query.process(memory_client,
+ Name("delegation.example.com"),
+ qtype, response));
responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
NULL, delegation_txt, ns_addrs_txt);
}
+TEST_F(QueryTest, delegationWithDNSSEC) {
+ // Similar to the previous one, but with requesting DNSSEC.
+ // In this case the parent zone would behave as unsigned, so the result
+ // should be just like non DNSSEC delegation.
+ query.process(memory_client, Name("www.nosec-delegation.example.com"),
+ qtype, response, true);
+
+ responseCheck(response, Rcode::NOERROR(), 0, 0, 1, 0,
+ NULL, nosec_delegation_txt, NULL);
+}
+
+TEST_F(QueryTest, secureDelegation) {
+ EXPECT_NO_THROW(query.process(memory_client,
+ Name("foo.signed-delegation.example.com"),
+ qtype, response, true));
+
+ // Should now contain RRSIG and DS record as well.
+ responseCheck(response, Rcode::NOERROR(), 0, 0, 3, 0,
+ NULL,
+ (string(signed_delegation_txt) +
+ string(signed_delegation_ds_txt) +
+ string("signed-delegation.example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("DS")).c_str(),
+ NULL);
+}
+
+TEST_F(QueryTest, secureUnsignedDelegation) {
+ EXPECT_NO_THROW(query.process(memory_client,
+ Name("foo.unsigned-delegation.example.com"),
+ qtype, response, true));
+
+ // Should now contain RRSIG and NSEC record as well.
+ responseCheck(response, Rcode::NOERROR(), 0, 0, 3, 0,
+ NULL,
+ (string(unsigned_delegation_txt) +
+ string(unsigned_delegation_nsec_txt) +
+ string("unsigned-delegation.example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("NSEC")).c_str(),
+ NULL);
+}
+
+TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3) {
+ // Similar to the previous case, but the zone is signed with NSEC3,
+ // and this delegation is NOT an optout.
+ const Name insecurechild_name("unsigned-delegation.example.com");
+ mock_finder->setNSEC3Flag(true);
+ mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+
+ query.process(memory_client,
+ Name("foo.unsigned-delegation.example.com"),
+ qtype, response, true);
+
+ // The response should contain the NS and matching NSEC3 with its RRSIG
+ responseCheck(response, Rcode::NOERROR(), 0, 0, 3, 0,
+ NULL,
+ (string(unsigned_delegation_txt) +
+ string(unsigned_delegation_nsec3_txt) +
+ mock_finder->hash_map_[insecurechild_name] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3")).c_str(),
+ NULL);
+}
+
+TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
+ // Similar to the previous case, but the delegation is an optout.
+ mock_finder->setNSEC3Flag(true);
+
+ query.process(memory_client,
+ Name("foo.unsigned-delegation.example.com"),
+ qtype, response, true);
+
+ // The response should contain the NS and the closest provable encloser
+ // proof (and their RRSIGs). The closest encloser is the apex (origin),
+ // and with our faked hash the covering NSEC3 for the next closer
+ // (= child zone name) is that for www.example.com.
+ responseCheck(response, Rcode::NOERROR(), 0, 0, 5, 0,
+ NULL,
+ (string(unsigned_delegation_txt) +
+ string(nsec3_apex_txt) +
+ mock_finder->hash_map_[mock_finder->getOrigin()] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3") + "\n" +
+ string(nsec3_www_txt) +
+ mock_finder->hash_map_[Name("www.example.com")] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3")).c_str(),
+ NULL);
+}
+
+TEST_F(QueryTest, badSecureDelegation) {
+ // Test whether exception is raised if DS query at delegation results in
+ // something different than SUCCESS or NXRRSET
+ EXPECT_THROW(query.process(memory_client,
+ Name("bad-delegation.example.com"),
+ qtype, response, true), Query::BadDS);
+
+ // But only if DNSSEC is requested (it shouldn't even try to look for
+ // the DS otherwise)
+ EXPECT_NO_THROW(query.process(memory_client,
+ Name("bad-delegation.example.com"),
+ qtype, response));
+}
+
+
TEST_F(QueryTest, nxdomain) {
- EXPECT_NO_THROW(Query(memory_client, Name("nxdomain.example.com"), qtype,
- response).process());
+ EXPECT_NO_THROW(query.process(memory_client,
+ Name("nxdomain.example.com"), qtype,
+ response));
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
}
@@ -745,8 +1218,9 @@ TEST_F(QueryTest, nxdomainWithNSEC) {
// NXDOMAIN with DNSSEC proof. We should have SOA, NSEC that proves
// NXDOMAIN and NSEC that proves nonexistence of matching wildcard,
// as well as their RRSIGs.
- EXPECT_NO_THROW(Query(memory_client, Name("nxdomain.example.com"), qtype,
- response, true).process());
+ EXPECT_NO_THROW(query.process(memory_client,
+ Name("nxdomain.example.com"), qtype,
+ response, true));
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
NULL, (string(soa_txt) +
string("example.com. 3600 IN RRSIG ") +
@@ -765,8 +1239,8 @@ TEST_F(QueryTest, nxdomainWithNSEC2) {
// is derived from the next domain of the NSEC that proves NXDOMAIN, and
// the NSEC to provide the non existence of wildcard is different from
// the first NSEC.
- Query(memory_client, Name("(.no.example.com"), qtype,
- response, true).process();
+ query.process(memory_client, Name("(.no.example.com"), qtype, response,
+ true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
NULL, (string(soa_txt) +
string("example.com. 3600 IN RRSIG ") +
@@ -783,8 +1257,8 @@ TEST_F(QueryTest, nxdomainWithNSEC2) {
TEST_F(QueryTest, nxdomainWithNSECDuplicate) {
// See comments about nz_txt. In this case we only need one NSEC,
// which proves both NXDOMAIN and the non existence of wildcard.
- Query(memory_client, Name("nx.no.example.com"), qtype,
- response, true).process();
+ query.process(memory_client, Name("nx.no.example.com"), qtype, response,
+ true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 4, 0,
NULL, (string(soa_txt) +
string("example.com. 3600 IN RRSIG ") +
@@ -799,9 +1273,9 @@ TEST_F(QueryTest, nxdomainBadNSEC1) {
// ZoneFinder::find() returns NXDOMAIN with non NSEC RR.
mock_finder->setNSECResult(Name("badnsec.example.com"),
ZoneFinder::NXDOMAIN,
- mock_finder->delegation_rrset_);
- EXPECT_THROW(Query(memory_client, Name("badnsec.example.com"), qtype,
- response, true).process(),
+ mock_finder->dname_rrset_);
+ EXPECT_THROW(query.process(memory_client, Name("badnsec.example.com"),
+ qtype, response, true),
std::bad_cast);
}
@@ -810,8 +1284,8 @@ TEST_F(QueryTest, nxdomainBadNSEC2) {
mock_finder->setNSECResult(Name("emptynsec.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->empty_nsec_rrset_);
- EXPECT_THROW(Query(memory_client, Name("emptynsec.example.com"), qtype,
- response, true).process(),
+ EXPECT_THROW(query.process(memory_client, Name("emptynsec.example.com"),
+ qtype, response, true),
Query::BadNSEC);
}
@@ -819,9 +1293,9 @@ TEST_F(QueryTest, nxdomainBadNSEC3) {
// "no-wildcard proof" returns SUCCESS. it should be NXDOMAIN.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::SUCCESS,
- mock_finder->delegation_rrset_);
- EXPECT_THROW(Query(memory_client, Name("nxdomain.example.com"), qtype,
- response, true).process(),
+ mock_finder->dname_rrset_);
+ EXPECT_THROW(query.process(memory_client, Name("nxdomain.example.com"),
+ qtype, response, true),
Query::BadNSEC);
}
@@ -829,8 +1303,8 @@ TEST_F(QueryTest, nxdomainBadNSEC4) {
// "no-wildcard proof" doesn't return RRset.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::NXDOMAIN, ConstRRsetPtr());
- EXPECT_THROW(Query(memory_client, Name("nxdomain.example.com"), qtype,
- response, true).process(),
+ EXPECT_THROW(query.process(memory_client, Name("nxdomain.example.com"),
+ qtype, response, true),
Query::BadNSEC);
}
@@ -838,18 +1312,20 @@ TEST_F(QueryTest, nxdomainBadNSEC5) {
// "no-wildcard proof" returns non NSEC.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::NXDOMAIN,
- mock_finder->delegation_rrset_);
+ mock_finder->dname_rrset_);
// This is a bit odd, but we'll simply include the returned RRset.
- Query(memory_client, Name("nxdomain.example.com"), qtype,
- response, true).process();
- responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 8, 0,
+ query.process(memory_client, Name("nxdomain.example.com"), qtype,
+ response, true);
+ responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
NULL, (string(soa_txt) +
string("example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("SOA") + "\n" +
string(nsec_nxdomain_txt) + "\n" +
string("noglue.example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("NSEC") + "\n" +
- delegation_txt).c_str(),
+ dname_txt + "\n" +
+ string("dname.example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("DNAME")).c_str(),
NULL, mock_finder->getOrigin());
}
@@ -858,14 +1334,14 @@ TEST_F(QueryTest, nxdomainBadNSEC6) {
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->empty_nsec_rrset_);
- EXPECT_THROW(Query(memory_client, Name("nxdomain.example.com"), qtype,
- response, true).process(),
+ EXPECT_THROW(query.process(memory_client, Name("nxdomain.example.com"),
+ qtype, response, true),
Query::BadNSEC);
}
TEST_F(QueryTest, nxrrset) {
- EXPECT_NO_THROW(Query(memory_client, Name("www.example.com"),
- RRType::TXT(), response).process());
+ EXPECT_NO_THROW(query.process(memory_client, Name("www.example.com"),
+ RRType::TXT(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
@@ -874,8 +1350,8 @@ TEST_F(QueryTest, nxrrset) {
TEST_F(QueryTest, nxrrsetWithNSEC) {
// NXRRSET with DNSSEC proof. We should have SOA, NSEC that proves the
// NXRRSET and their RRSIGs.
- Query(memory_client, Name("www.example.com"), RRType::TXT(), response,
- true).process();
+ query.process(memory_client, Name("www.example.com"), RRType::TXT(),
+ response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
@@ -895,8 +1371,8 @@ TEST_F(QueryTest, emptyNameWithNSEC) {
// exact match), so we only need one NSEC.
// From the point of the Query::process(), this is actually no different
// from the other NXRRSET case, but we check that explicitly just in case.
- Query(memory_client, Name("no.example.com"), RRType::A(), response,
- true).process();
+ query.process(memory_client, Name("no.example.com"), RRType::A(),
+ response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
@@ -911,8 +1387,8 @@ TEST_F(QueryTest, nxrrsetWithoutNSEC) {
// NXRRSET with DNSSEC proof requested, but there's no NSEC at that node.
// This is an unexpected event (if the zone is supposed to be properly
// signed with NSECs), but we accept and ignore the oddity.
- Query(memory_client, Name("nonsec.example.com"), RRType::TXT(), response,
- true).process();
+ query.process(memory_client, Name("nonsec.example.com"), RRType::TXT(),
+ response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
@@ -923,8 +1399,8 @@ TEST_F(QueryTest, nxrrsetWithoutNSEC) {
TEST_F(QueryTest, wildcardNSEC) {
// The qname matches *.wild.example.com. The response should contain
// an NSEC that proves the non existence of a closer name.
- Query(memory_client, Name("www.wild.example.com"), RRType::A(), response,
- true).process();
+ query.process(memory_client, Name("www.wild.example.com"), RRType::A(),
+ response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 6, 6,
(string(wild_txt).replace(0, 1, "www") +
string("www.wild.example.com. 3600 IN RRSIG ") +
@@ -943,8 +1419,8 @@ TEST_F(QueryTest, wildcardNSEC) {
TEST_F(QueryTest, CNAMEwildNSEC) {
// Similar to the previous case, but the matching wildcard record is
// CNAME.
- Query(memory_client, Name("www.cnamewild.example.com"), RRType::A(),
- response, true).process();
+ query.process(memory_client, Name("www.cnamewild.example.com"),
+ RRType::A(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
(string(cnamewild_txt).replace(0, 1, "www") +
string("www.cnamewild.example.com. 3600 IN RRSIG ") +
@@ -956,14 +1432,77 @@ TEST_F(QueryTest, CNAMEwildNSEC) {
mock_finder->getOrigin());
}
+TEST_F(QueryTest, wildcardNSEC3) {
+ // Similar to wildcardNSEC, but the zone is signed with NSEC3.
+ // The next closer is y.wild.example.com, the covering NSEC3 for it
+ // is (in our setup) the NSEC3 for the apex.
+ mock_finder->setNSEC3Flag(true);
+
+ // This is NSEC3 for wild.example.com, which will be used in the middle
+ // of identifying the next closer name.
+ mock_finder->addRecord(nsec3_atwild_txt);
+
+ query.process(memory_client, Name("x.y.wild.example.com"), RRType::A(),
+ response, true);
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 6, 6,
+ (string(wild_txt).replace(0, 1, "x.y") +
+ string("x.y.wild.example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("A") + "\n").c_str(),
+ // 3 NSes and their RRSIG
+ (zone_ns_txt + string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("NS") + "\n" +
+ // NSEC3 for the wildcard proof and its RRSIG
+ string(nsec3_apex_txt) +
+ mock_finder->hash_map_[Name("example.com.")] +
+ string(".example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("NSEC3")).c_str(),
+ NULL, // we are not interested in additionals in this test
+ mock_finder->getOrigin());
+}
+
+TEST_F(QueryTest, CNAMEwildNSEC3) {
+ // Similar to CNAMEwildNSEC, but with NSEC3.
+ // The next closer is qname itself, the covering NSEC3 for it
+ // is (in our setup) the NSEC3 for the www.example.com.
+ mock_finder->setNSEC3Flag(true);
+ mock_finder->addRecord(nsec3_atcnamewild_txt);
+
+ query.process(memory_client, Name("www.cnamewild.example.com"),
+ RRType::A(), response, true);
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
+ (string(cnamewild_txt).replace(0, 1, "www") +
+ string("www.cnamewild.example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("CNAME") + "\n").c_str(),
+ (string(nsec3_www_txt) +
+ mock_finder->hash_map_[Name("www.example.com.")] +
+ string(".example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("NSEC3")).c_str(),
+ NULL, // we are not interested in additionals in this test
+ mock_finder->getOrigin());
+}
+
+TEST_F(QueryTest, badWildcardNSEC3) {
+ // Similar to wildcardNSEC3, but emulating run time collision by
+ // returning NULL in the next closer proof for the closest encloser
+ // proof.
+ mock_finder->setNSEC3Flag(true);
+ ZoneFinder::FindNSEC3Result nsec3(true, 0, textToRRset(nsec3_apex_txt),
+ ConstRRsetPtr());
+ mock_finder->setNSEC3Result(&nsec3);
+
+ EXPECT_THROW(query.process(memory_client, Name("www.wild.example.com"),
+ RRType::A(), response, true),
+ Query::BadNSEC3);
+}
+
TEST_F(QueryTest, badWildcardProof1) {
// Unexpected case in wildcard proof: ZoneFinder::find() returns SUCCESS
// when NXDOMAIN is expected.
mock_finder->setNSECResult(Name("www.wild.example.com"),
ZoneFinder::SUCCESS,
- mock_finder->delegation_rrset_);
- EXPECT_THROW(Query(memory_client, Name("www.wild.example.com"),
- RRType::A(), response, true).process(),
+ mock_finder->dname_rrset_);
+ EXPECT_THROW(query.process(memory_client, Name("www.wild.example.com"),
+ RRType::A(), response, true),
Query::BadNSEC);
}
@@ -971,8 +1510,8 @@ TEST_F(QueryTest, badWildcardProof2) {
// "wildcard proof" doesn't return RRset.
mock_finder->setNSECResult(Name("www.wild.example.com"),
ZoneFinder::NXDOMAIN, ConstRRsetPtr());
- EXPECT_THROW(Query(memory_client, Name("www.wild.example.com"),
- RRType::A(), response, true).process(),
+ EXPECT_THROW(query.process(memory_client, Name("www.wild.example.com"),
+ RRType::A(), response, true),
Query::BadNSEC);
}
@@ -981,17 +1520,17 @@ TEST_F(QueryTest, badWildcardProof3) {
mock_finder->setNSECResult(Name("www.wild.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->empty_nsec_rrset_);
- EXPECT_THROW(Query(memory_client, Name("www.wild.example.com"),
- RRType::A(), response, true).process(),
+ EXPECT_THROW(query.process(memory_client, Name("www.wild.example.com"),
+ RRType::A(), response, true),
Query::BadNSEC);
}
TEST_F(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
- // WILDCARD_NXRRSET with DNSSEC proof. We should have SOA, NSEC that proves the
- // NXRRSET and their RRSIGs. In this case we only need one NSEC,
+ // NXRRSET on WILDCARD with DNSSEC proof. We should have SOA, NSEC that
+ // proves the NXRRSET and their RRSIGs. In this case we only need one NSEC,
// which proves both NXDOMAIN and the non existence RRSETs of wildcard.
- Query(memory_client, Name("www.wild.example.com"), RRType::TXT(), response,
- true).process();
+ query.process(memory_client, Name("www.wild.example.com"), RRType::TXT(),
+ response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
@@ -1003,11 +1542,12 @@ TEST_F(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
}
TEST_F(QueryTest, wildcardNxrrsetWithNSEC) {
- // WILDCARD_NXRRSET with DNSSEC proof. We should have SOA, NSEC that proves the
- // NXRRSET and their RRSIGs. In this case we need two NSEC RRs,
- // one proves NXDOMAIN and the other proves non existence RRSETs of wildcard.
- Query(memory_client, Name("www1.uwild.example.com"), RRType::TXT(), response,
- true).process();
+ // WILDCARD + NXRRSET with DNSSEC proof. We should have SOA, NSEC that
+ // proves the NXRRSET and their RRSIGs. In this case we need two NSEC RRs,
+ // one proves NXDOMAIN and the other proves non existence RRSETs of
+ // wildcard.
+ query.process(memory_client, Name("www1.uwild.example.com"),
+ RRType::TXT(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
@@ -1021,12 +1561,76 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC) {
NULL, mock_finder->getOrigin());
}
+TEST_F(QueryTest, wildcardNxrrsetWithNSEC3) {
+ // Similar to the previous case, but providing NSEC3 proofs according to
+ // RFC5155 Section 7.2.5.
+
+ mock_finder->addRecord(nsec3_wild_txt);
+ mock_finder->addRecord(nsec3_uwild_txt);
+ mock_finder->setNSEC3Flag(true);
+
+ query.process(memory_client, Name("www1.uwild.example.com"),
+ RRType::TXT(), response, true);
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 8, 0, NULL,
+ // SOA + its RRSIG
+ (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("SOA") + "\n" +
+ // NSEC3 for the closest encloser + its RRSIG
+ string(nsec3_uwild_txt) +
+ mock_finder->hash_map_[Name("uwild.example.com.")] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3") + "\n" +
+ // NSEC3 for the next closer + its RRSIG
+ string(nsec3_www_txt) +
+ mock_finder->hash_map_[Name("www.example.com.")] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3") + "\n" +
+ // NSEC3 for the wildcard + its RRSIG
+ string(nsec3_wild_txt) +
+ mock_finder->hash_map_[Name("*.uwild.example.com.")] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3")).c_str(),
+ NULL, mock_finder->getOrigin());
+}
+
+TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Collision) {
+ // Similar to the previous case, but emulating run time collision by
+ // returning NULL in the next closer proof for the closest encloser
+ // proof.
+ mock_finder->setNSEC3Flag(true);
+ ZoneFinder::FindNSEC3Result nsec3(true, 0, textToRRset(nsec3_apex_txt),
+ ConstRRsetPtr());
+ mock_finder->setNSEC3Result(&nsec3);
+
+ EXPECT_THROW(query.process(memory_client, Name("www1.uwild.example.com"),
+ RRType::TXT(), response, true),
+ Query::BadNSEC3);
+}
+
+TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Broken) {
+ // Similar to wildcardNxrrsetWithNSEC3, but no matching NSEC3 for the
+ // wildcard name will be returned. This shouldn't happen in a reasonably
+ // NSEC-signed zone, and should result in an exception.
+ mock_finder->setNSEC3Flag(true);
+ const Name wname("*.uwild.example.com.");
+ ZoneFinder::FindNSEC3Result nsec3(false, 0, textToRRset(nsec3_apex_txt),
+ ConstRRsetPtr());
+ mock_finder->setNSEC3Result(&nsec3, &wname);
+ mock_finder->addRecord(nsec3_wild_txt);
+ mock_finder->addRecord(nsec3_uwild_txt);
+
+ EXPECT_THROW(query.process(memory_client, Name("www1.uwild.example.com"),
+ RRType::TXT(), response, true),
+ Query::BadNSEC3);
+}
+
TEST_F(QueryTest, wildcardEmptyWithNSEC) {
- // WILDCARD_EMPTY with DNSSEC proof. We should have SOA, NSEC that proves the
- // NXDOMAIN and their RRSIGs. In this case we need two NSEC RRs,
+ // Empty WILDCARD with DNSSEC proof. We should have SOA, NSEC that proves
+ // the NXDOMAIN and their RRSIGs. In this case we need two NSEC RRs,
// one proves NXDOMAIN and the other proves non existence wildcard.
- Query(memory_client, Name("a.t.example.com"), RRType::A(), response,
- true).process();
+ query.process(memory_client, Name("a.t.example.com"), RRType::A(),
+ response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
@@ -1049,19 +1653,19 @@ TEST_F(QueryTest, noSOA) {
mock_finder->setSOAFlag(false);
// The NX Domain
- EXPECT_THROW(Query(memory_client, Name("nxdomain.example.com"),
- qtype, response).process(), Query::NoSOA);
+ EXPECT_THROW(query.process(memory_client, Name("nxdomain.example.com"),
+ qtype, response), Query::NoSOA);
// Of course, we don't look into the response, as it throwed
// NXRRSET
- EXPECT_THROW(Query(memory_client, Name("nxrrset.example.com"),
- qtype, response).process(), Query::NoSOA);
+ EXPECT_THROW(query.process(memory_client, Name("nxrrset.example.com"),
+ qtype, response), 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_client, Name("example.org"), qtype, response).process();
+ query.process(memory_client, Name("example.org"), qtype, response);
EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
}
@@ -1072,8 +1676,8 @@ TEST_F(QueryTest, noMatchZone) {
* A record, other to unknown out of zone one.
*/
TEST_F(QueryTest, MX) {
- Query(memory_client, Name("mx.example.com"), RRType::MX(),
- response).process();
+ query.process(memory_client, Name("mx.example.com"), RRType::MX(),
+ response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 3, 4,
mx_txt, NULL,
@@ -1086,8 +1690,8 @@ TEST_F(QueryTest, MX) {
* This should not trigger the additional processing for the exchange.
*/
TEST_F(QueryTest, MXAlias) {
- Query(memory_client, Name("cnamemx.example.com"), RRType::MX(),
- response).process();
+ query.process(memory_client, Name("cnamemx.example.com"), RRType::MX(),
+ response);
// 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
@@ -1106,8 +1710,8 @@ TEST_F(QueryTest, MXAlias) {
* returned.
*/
TEST_F(QueryTest, CNAME) {
- Query(memory_client, Name("cname.example.com"), RRType::A(),
- response).process();
+ query.process(memory_client, Name("cname.example.com"), RRType::A(),
+ response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_txt, NULL, NULL);
@@ -1116,8 +1720,8 @@ TEST_F(QueryTest, CNAME) {
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_client, Name("cname.example.com"), RRType::CNAME(),
- response).process();
+ query.process(memory_client, Name("cname.example.com"), RRType::CNAME(),
+ response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_txt, zone_ns_txt, ns_addrs_txt);
@@ -1128,8 +1732,8 @@ TEST_F(QueryTest, CNAME_NX_RRSET) {
// 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_client, Name("cname.example.com"), RRType::TXT(),
- response).process();
+ query.process(memory_client, Name("cname.example.com"), RRType::TXT(),
+ response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_txt, NULL, NULL);
@@ -1137,8 +1741,8 @@ TEST_F(QueryTest, CNAME_NX_RRSET) {
TEST_F(QueryTest, explicitCNAME_NX_RRSET) {
// same owner name as the NXRRSET test but explicitly query for CNAME RR.
- Query(memory_client, Name("cname.example.com"), RRType::CNAME(),
- response).process();
+ query.process(memory_client, Name("cname.example.com"), RRType::CNAME(),
+ response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_txt, zone_ns_txt, ns_addrs_txt);
@@ -1151,8 +1755,8 @@ TEST_F(QueryTest, CNAME_NX_DOMAIN) {
// RCODE being NXDOMAIN.
// NSD returns the CNAME, NS in authority, A/AAAA for NS in additional,
// RCODE being NOERROR.
- Query(memory_client, Name("cnamenxdom.example.com"), RRType::A(),
- response).process();
+ query.process(memory_client, Name("cnamenxdom.example.com"), RRType::A(),
+ response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_nxdom_txt, NULL, NULL);
@@ -1160,8 +1764,8 @@ TEST_F(QueryTest, CNAME_NX_DOMAIN) {
TEST_F(QueryTest, explicitCNAME_NX_DOMAIN) {
// same owner name as the NXDOMAIN test but explicitly query for CNAME RR.
- Query(memory_client, Name("cnamenxdom.example.com"), RRType::CNAME(),
- response).process();
+ query.process(memory_client, Name("cnamenxdom.example.com"),
+ RRType::CNAME(), response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_nxdom_txt, zone_ns_txt, ns_addrs_txt);
@@ -1176,8 +1780,8 @@ TEST_F(QueryTest, CNAME_OUT) {
* Then the same test should be done with .org included there and
* see what it does (depends on what we want to do)
*/
- Query(memory_client, Name("cnameout.example.com"), RRType::A(),
- response).process();
+ query.process(memory_client, Name("cnameout.example.com"), RRType::A(),
+ response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_out_txt, NULL, NULL);
@@ -1185,8 +1789,8 @@ TEST_F(QueryTest, CNAME_OUT) {
TEST_F(QueryTest, explicitCNAME_OUT) {
// same owner name as the OUT test but explicitly query for CNAME RR.
- Query(memory_client, Name("cnameout.example.com"), RRType::CNAME(),
- response).process();
+ query.process(memory_client, Name("cnameout.example.com"), RRType::CNAME(),
+ response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_out_txt, zone_ns_txt, ns_addrs_txt);
@@ -1201,8 +1805,8 @@ TEST_F(QueryTest, explicitCNAME_OUT) {
* pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
*/
TEST_F(QueryTest, DNAME) {
- Query(memory_client, Name("www.dname.example.com"), RRType::A(),
- response).process();
+ query.process(memory_client, Name("www.dname.example.com"), RRType::A(),
+ response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
(string(dname_txt) + synthetized_cname_txt).c_str(),
@@ -1217,8 +1821,8 @@ TEST_F(QueryTest, DNAME) {
* DNAME.
*/
TEST_F(QueryTest, DNAME_ANY) {
- Query(memory_client, Name("www.dname.example.com"), RRType::ANY(),
- response).process();
+ query.process(memory_client, Name("www.dname.example.com"), RRType::ANY(),
+ response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
(string(dname_txt) + synthetized_cname_txt).c_str(), NULL, NULL);
@@ -1226,8 +1830,8 @@ TEST_F(QueryTest, DNAME_ANY) {
// Test when we ask for DNAME explicitly, it does no synthetizing.
TEST_F(QueryTest, explicitDNAME) {
- Query(memory_client, Name("dname.example.com"), RRType::DNAME(),
- response).process();
+ query.process(memory_client, Name("dname.example.com"), RRType::DNAME(),
+ response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
dname_txt, zone_ns_txt, ns_addrs_txt);
@@ -1238,8 +1842,8 @@ TEST_F(QueryTest, explicitDNAME) {
* the CNAME, it should return the RRset.
*/
TEST_F(QueryTest, DNAME_A) {
- Query(memory_client, Name("dname.example.com"), RRType::A(),
- response).process();
+ query.process(memory_client, Name("dname.example.com"), RRType::A(),
+ response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
dname_a_txt, zone_ns_txt, ns_addrs_txt);
@@ -1250,8 +1854,8 @@ TEST_F(QueryTest, DNAME_A) {
* It should not synthetize the CNAME.
*/
TEST_F(QueryTest, DNAME_NX_RRSET) {
- EXPECT_NO_THROW(Query(memory_client, Name("dname.example.com"),
- RRType::TXT(), response).process());
+ EXPECT_NO_THROW(query.process(memory_client, Name("dname.example.com"),
+ RRType::TXT(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
@@ -1270,8 +1874,8 @@ TEST_F(QueryTest, LongDNAME) {
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"dname.example.com.");
- EXPECT_NO_THROW(Query(memory_client, longname, RRType::A(),
- response).process());
+ EXPECT_NO_THROW(query.process(memory_client, longname, RRType::A(),
+ response));
responseCheck(response, Rcode::YXDOMAIN(), AA_FLAG, 1, 0, 0,
dname_txt, NULL, NULL);
@@ -1289,8 +1893,8 @@ TEST_F(QueryTest, MaxLenDNAME) {
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"dname.example.com.");
- EXPECT_NO_THROW(Query(memory_client, longname, RRType::A(),
- response).process());
+ EXPECT_NO_THROW(query.process(memory_client, longname, RRType::A(),
+ response));
// Check the answer is OK
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
@@ -1317,4 +1921,608 @@ TEST_F(QueryTest, MaxLenDNAME) {
EXPECT_TRUE(ok) << "The synthetized CNAME not found";
}
+// Test for this test module itself
+void
+nsec3Check(bool expected_matched, uint8_t expected_labels,
+ const string& expected_rrsets_txt,
+ const ZoneFinder::FindNSEC3Result& result)
+{
+ vector<ConstRRsetPtr> actual_rrsets;
+ EXPECT_EQ(expected_matched, result.matched);
+ // Convert to int so the error messages would be more readable:
+ EXPECT_EQ(static_cast<int>(expected_labels),
+ static_cast<int>(result.closest_labels));
+ if (result.closest_proof) {
+ actual_rrsets.push_back(result.closest_proof);
+ }
+ if (result.next_proof) {
+ actual_rrsets.push_back(result.next_proof);
+ }
+ rrsetsCheck(expected_rrsets_txt, actual_rrsets.begin(),
+ actual_rrsets.end());
+}
+
+TEST_F(QueryTest, findNSEC3) {
+ // In all test cases in the recursive mode, the closest encloser is the
+ // apex, and result's closest_labels should be the number of apex labels.
+ // (In non recursive mode closest_labels should be the # labels of the
+ // query name)
+ const uint8_t expected_closest_labels =
+ Name("example.com").getLabelCount();
+
+ // Apex name. It should have a matching NSEC3
+ {
+ SCOPED_TRACE("apex, non recursive");
+ nsec3Check(true, expected_closest_labels, nsec3_apex_txt,
+ mock_finder->findNSEC3(Name("example.com"), false));
+ }
+
+ // Recursive mode doesn't change the result in this case.
+ {
+ SCOPED_TRACE("apex, recursive");
+ nsec3Check(true, expected_closest_labels, nsec3_apex_txt,
+ mock_finder->findNSEC3(Name("example.com"), true));
+ }
+
+ // Non existent name. Disabling recursion, a covering NSEC3 should be
+ // returned.
+ {
+ SCOPED_TRACE("nxdomain, non recursive");
+ nsec3Check(false, 4, nsec3_www_txt,
+ mock_finder->findNSEC3(Name("nxdomain.example.com"),
+ false));
+ }
+
+ // Non existent name. The closest provable encloser is the apex,
+ // and next closer is the query name.
+ {
+ SCOPED_TRACE("nxdomain, recursive");
+ nsec3Check(true, expected_closest_labels,
+ string(nsec3_apex_txt) + string(nsec3_www_txt),
+ mock_finder->findNSEC3(Name("nxdomain.example.com"), true));
+ }
+
+ // Similar to the previous case, but next closer name is different
+ // (is the parent) of the non existent name.
+ {
+ SCOPED_TRACE("nxdomain, next closer != qname");
+ nsec3Check(true, expected_closest_labels,
+ string(nsec3_apex_txt) + string(nsec3_www_txt),
+ mock_finder->findNSEC3(Name("nx.domain.example.com"),
+ true));
+ }
+
+ // In the rest of test we check hash comparison for wrap around cases.
+ {
+ SCOPED_TRACE("largest");
+ nsec3Check(false, 4, nsec3_apex_txt,
+ mock_finder->findNSEC3(Name("nxdomain2.example.com"),
+ false));
+ }
+ {
+ SCOPED_TRACE("smallest");
+ nsec3Check(false, 4, nsec3_www_txt,
+ mock_finder->findNSEC3(Name("nxdomain3.example.com"),
+ false));
+ }
+}
+
+// This tests that the DS is returned above the delegation point as
+// an authoritative answer, not a delegation. This is as described in
+// RFC 4035, section 3.1.4.1.
+
+// This mock finder is used for some DS-query tests to support the cases
+// where the query is expected to be handled in a different zone than our
+// main test zone, example.com. Only limited methods are expected to called
+// (and for limited purposes) on this class object in these tests, which
+// are overridden below.
+class AlternateZoneFinder : public MockZoneFinder {
+public:
+ // This zone is expected not to have a DS by default and return NXRRSET
+ // for a DS query. If have_ds is set to true on construction, it will
+ // return a faked DS answer.
+ AlternateZoneFinder(const Name& origin, bool have_ds = false) :
+ MockZoneFinder(), origin_(origin), have_ds_(have_ds)
+ {}
+ virtual isc::dns::Name getOrigin() const { return (origin_); }
+ virtual ZoneFinderContextPtr find(const isc::dns::Name&,
+ const isc::dns::RRType& type,
+ const FindOptions options)
+ {
+ if (type == RRType::SOA()) {
+ RRsetPtr soa = textToRRset(origin_.toText() + " 3600 IN SOA . . "
+ "0 0 0 0 0\n", origin_);
+ soa->addRRsig(RdataPtr(new generic::RRSIG(
+ getCommonRRSIGText("SOA"))));
+ return (createContext(options, SUCCESS, soa));
+ }
+ if (type == RRType::NS()) {
+ RRsetPtr ns = textToRRset(origin_.toText() + " 3600 IN NS " +
+ Name("ns").concatenate(origin_).toText());
+ ns->addRRsig(RdataPtr(new generic::RRSIG(
+ getCommonRRSIGText("NS"))));
+ return (createContext(options, SUCCESS, ns));
+ }
+ if (type == RRType::DS()) {
+ if (have_ds_) {
+ RRsetPtr ds = textToRRset(origin_.toText() +
+ " 3600 IN DS 57855 5 1 " +
+ "49FD46E6C4B45C55D4AC69CBD"
+ "3CD34AC1AFE51DE");
+ ds->addRRsig(RdataPtr(new generic::RRSIG(
+ getCommonRRSIGText("DS"))));
+ return (createContext(options, SUCCESS, ds));
+ } else {
+ RRsetPtr nsec = textToRRset(origin_.toText() +
+ " 3600 IN NSEC " +
+ origin_.toText() +
+ " SOA NSEC RRSIG");
+ nsec->addRRsig(RdataPtr(new generic::RRSIG(
+ getCommonRRSIGText("NSEC"))));
+ return (createContext(options, NXRRSET, nsec,
+ RESULT_NSEC_SIGNED));
+ }
+ }
+
+ // Returning NXDOMAIN is not correct, but doesn't matter for our tests.
+ return (createContext(options, NXDOMAIN, ConstRRsetPtr()));
+ }
+private:
+ const Name origin_;
+ const bool have_ds_;
+};
+
+TEST_F(QueryTest, dsAboveDelegation) {
+ // Pretending to have authority for the child zone, too.
+ memory_client.addZone(ZoneFinderPtr(new AlternateZoneFinder(
+ Name("delegation.example.com"))));
+
+ // The following will succeed only if the search goes to the parent
+ // zone, not the child one we added above.
+ EXPECT_NO_THROW(query.process(memory_client,
+ Name("delegation.example.com"),
+ RRType::DS(), response, true));
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 4, 6,
+ (string(delegation_ds_txt) + "\n" +
+ "delegation.example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("DS")).c_str(),
+ (string(zone_ns_txt) + "\n" +
+ "example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NS")).c_str(),
+ ns_addrs_and_sig_txt.c_str());
+}
+
+TEST_F(QueryTest, dsAboveDelegationNoData) {
+ // Similar to the previous case, but the query is for an unsigned zone
+ // (which doesn't have a DS at the parent). The response should be a
+ // "no data" error. The query should still be handled at the parent.
+ memory_client.addZone(ZoneFinderPtr(
+ new AlternateZoneFinder(
+ Name("unsigned-delegation.example.com"))));
+
+ // The following will succeed only if the search goes to the parent
+ // zone, not the child one we added above.
+ EXPECT_NO_THROW(query.process(memory_client,
+ Name("unsigned-delegation.example.com"),
+ RRType::DS(), response, true));
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+ (string(soa_txt) +
+ string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("SOA") + "\n" +
+ string(unsigned_delegation_nsec_txt) +
+ "unsigned-delegation.example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC")).c_str(),
+ NULL, mock_finder->getOrigin());
+}
+
+// This one checks that type-DS query results in a "no data" response
+// when it happens to be sent to the child zone, as described in RFC 4035,
+// section 3.1.4.1. The example is inspired by the B.8. example from the RFC.
+TEST_F(QueryTest, dsBelowDelegation) {
+ EXPECT_NO_THROW(query.process(memory_client, Name("example.com"),
+ RRType::DS(), response, true));
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+ (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("SOA") + "\n" +
+ string(nsec_apex_txt) + "\n" +
+ string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("NSEC")).c_str(), NULL,
+ mock_finder->getOrigin());
+}
+
+// Similar to the previous case, but even more pathological: the DS somehow
+// exists in the child zone. The Query module should still return SOA.
+// In our implementation NSEC/NSEC3 isn't attached in this case.
+TEST_F(QueryTest, dsBelowDelegationWithDS) {
+ mock_finder->addRecord(zone_ds_txt); // add the DS to the child's apex
+ EXPECT_NO_THROW(query.process(memory_client, Name("example.com"),
+ RRType::DS(), response, true));
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
+ (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("SOA")).c_str(), NULL,
+ mock_finder->getOrigin());
+}
+
+// DS query received at a completely irrelevant (neither parent nor child)
+// server. It should just like the "noZone" test case, but DS query involves
+// special processing, so we test it explicitly.
+TEST_F(QueryTest, dsNoZone) {
+ query.process(memory_client, Name("example"), RRType::DS(), response,
+ true);
+ responseCheck(response, Rcode::REFUSED(), 0, 0, 0, 0, NULL, NULL, NULL);
+}
+
+// DS query for a "grandchild" zone. This should result in normal
+// delegation (unless this server also has authority of the grandchild zone).
+TEST_F(QueryTest, dsAtGrandParent) {
+ query.process(memory_client, Name("grand.delegation.example.com"),
+ RRType::DS(), response, true);
+ responseCheck(response, Rcode::NOERROR(), 0, 0, 6, 6, NULL,
+ (string(delegation_txt) + string(delegation_ds_txt) +
+ "delegation.example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("DS")).c_str(),
+ ns_addrs_and_sig_txt.c_str());
+}
+
+// DS query sent to a "grandparent" server that also has authority for the
+// child zone. In this case the query should be handled in the child
+// side and should result in no data with SOA. Note that the server doesn't
+// have authority for the "parent". Unlike the dsAboveDelegation test case
+// the query should be handled in the child zone, not in the grandparent.
+TEST_F(QueryTest, dsAtGrandParentAndChild) {
+ // Pretending to have authority for the child zone, too.
+ const Name childname("grand.delegation.example.com");
+ memory_client.addZone(ZoneFinderPtr(
+ new AlternateZoneFinder(childname)));
+ query.process(memory_client, childname, RRType::DS(), response, true);
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+ (childname.toText() + " 3600 IN SOA . . 0 0 0 0 0\n" +
+ childname.toText() + " 3600 IN RRSIG " +
+ getCommonRRSIGText("SOA") + "\n" +
+ childname.toText() + " 3600 IN NSEC " +
+ childname.toText() + " SOA NSEC RRSIG\n" +
+ childname.toText() + " 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC")).c_str(), NULL, childname);
+}
+
+// DS query for the root name (quite pathological). Since there's no "parent",
+// the query will be handled in the root zone anyway, and should (normally)
+// result in no data.
+TEST_F(QueryTest, dsAtRoot) {
+ // Pretend to be a root server.
+ memory_client.addZone(ZoneFinderPtr(
+ new AlternateZoneFinder(Name::ROOT_NAME())));
+ query.process(memory_client, Name::ROOT_NAME(), RRType::DS(), response,
+ true);
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+ (string(". 3600 IN SOA . . 0 0 0 0 0\n") +
+ ". 3600 IN RRSIG " + getCommonRRSIGText("SOA") + "\n" +
+ ". 3600 IN NSEC " + ". SOA NSEC RRSIG\n" +
+ ". 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC")).c_str(), NULL);
+}
+
+// Even more pathological case: A faked root zone actually has its own DS
+// query. How we respond wouldn't matter much in practice, but check if
+// it behaves as it's intended. This implementation should return the DS.
+TEST_F(QueryTest, dsAtRootWithDS) {
+ memory_client.addZone(ZoneFinderPtr(
+ new AlternateZoneFinder(Name::ROOT_NAME(),
+ true)));
+ query.process(memory_client, Name::ROOT_NAME(), RRType::DS(), response,
+ true);
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
+ (string(". 3600 IN DS 57855 5 1 49FD46E6C4B45C55D4AC69CBD"
+ "3CD34AC1AFE51DE\n") +
+ ". 3600 IN RRSIG " + getCommonRRSIGText("DS")).c_str(),
+ (string(". 3600 IN NS ns.\n") +
+ ". 3600 IN RRSIG " + getCommonRRSIGText("NS")).c_str(),
+ NULL);
+}
+
+// Check the signature is present when an NXRRSET is returned
+TEST_F(QueryTest, nxrrsetWithNSEC3) {
+ mock_finder->setNSEC3Flag(true);
+
+ // NXRRSET with DNSSEC proof. We should have SOA, NSEC3 that proves the
+ // NXRRSET and their RRSIGs.
+ query.process(memory_client, Name("www.example.com"), RRType::TXT(),
+ response, true);
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+ (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("SOA") + "\n" +
+ string(nsec3_www_txt) + "\n" +
+ mock_finder->hash_map_[Name("www.example.com.")] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3") + "\n").c_str(),
+ NULL, mock_finder->getOrigin());
+}
+
+// Check the exception is correctly raised when the NSEC3 thing isn't in the
+// zone
+TEST_F(QueryTest, nxrrsetMissingNSEC3) {
+ mock_finder->setNSEC3Flag(true);
+ // We just need it to return false for "matched". This indicates
+ // there's no exact match for NSEC3 on www.example.com.
+ ZoneFinder::FindNSEC3Result nsec3(false, 0, ConstRRsetPtr(),
+ ConstRRsetPtr());
+ mock_finder->setNSEC3Result(&nsec3);
+
+ EXPECT_THROW(query.process(memory_client, Name("www.example.com"),
+ RRType::TXT(), response, true),
+ Query::BadNSEC3);
+}
+
+TEST_F(QueryTest, nxrrsetWithNSEC3_ds_exact) {
+ mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+ mock_finder->setNSEC3Flag(true);
+
+ // This delegation has no DS, but does have a matching NSEC3 record
+ // (See RFC5155 section 7.2.4)
+ query.process(memory_client, Name("unsigned-delegation.example.com."),
+ RRType::DS(), response, true);
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+ (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("SOA") + "\n" +
+ string(unsigned_delegation_nsec3_txt) + "\n" +
+ mock_finder->
+ hash_map_[Name("unsigned-delegation.example.com.")] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3") + "\n").c_str(),
+ NULL, mock_finder->getOrigin());
+}
+
+TEST_F(QueryTest, nxrrsetWithNSEC3_ds_no_exact) {
+ mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+ mock_finder->setNSEC3Flag(true);
+
+ // This delegation has no DS, and no directly matching NSEC3 record
+ // So the response should contain closest encloser proof (and the
+ // 'next closer' should have opt-out set, though that is not
+ // actually checked)
+ // (See RFC5155 section 7.2.4)
+ query.process(memory_client, Name("unsigned-delegation-optout.example.com."),
+ RRType::DS(), response, true);
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
+ (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("SOA") + "\n" +
+ string(nsec3_apex_txt) + "\n" +
+ mock_finder->hash_map_[Name("example.com.")] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3") + "\n" +
+ string(unsigned_delegation_nsec3_txt) + "\n" +
+ mock_finder->
+ hash_map_[Name("unsigned-delegation.example.com.")] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3") + "\n").c_str(),
+ NULL, mock_finder->getOrigin());
+}
+
+TEST_F(QueryTest, nxdomainWithNSEC3Proof) {
+ // Name Error (NXDOMAIN) case with NSEC3 proof per RFC5155 Section 7.2.2.
+
+ // Enable NSEC3
+ mock_finder->setNSEC3Flag(true);
+ // This will be the covering NSEC3 for the next closer
+ mock_finder->addRecord(nsec3_uwild_txt);
+ // This will be the covering NSEC3 for the possible wildcard
+ mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+
+ query.process(memory_client, Name("nxdomain.example.com"), qtype,
+ response, true);
+ responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 8, 0, NULL,
+ // SOA + its RRSIG
+ (string(soa_txt) +
+ string("example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("SOA") + "\n" +
+ // NSEC3 for the closest encloser + its RRSIG
+ string(nsec3_apex_txt) + "\n" +
+ mock_finder->hash_map_[mock_finder->getOrigin()] +
+ string(".example.com. 3600 IN RRSIG ") +
+ getCommonRRSIGText("NSEC3") + "\n" +
+ // NSEC3 for the next closer + its RRSIG
+ string(nsec3_uwild_txt) + "\n" +
+ mock_finder->hash_map_[Name("uwild.example.com")] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3") + "\n" +
+ // NSEC3 for the wildcard + its RRSIG
+ string(unsigned_delegation_nsec3_txt) +
+ mock_finder->hash_map_[
+ Name("unsigned-delegation.example.com")] +
+ ".example.com. 3600 IN RRSIG " +
+ getCommonRRSIGText("NSEC3")).c_str(),
+ NULL, mock_finder->getOrigin());
+}
+
+TEST_F(QueryTest, nxdomainWithBadNextNSEC3Proof) {
+ // Similar to the previous case, but emulating run time collision by
+ // returning NULL in the next closer proof for the closest encloser
+ // proof.
+ mock_finder->setNSEC3Flag(true);
+ ZoneFinder::FindNSEC3Result nsec3(true, 0, textToRRset(nsec3_apex_txt),
+ ConstRRsetPtr());
+ mock_finder->setNSEC3Result(&nsec3);
+
+ EXPECT_THROW(query.process(memory_client, Name("nxdomain.example.com"),
+ RRType::TXT(), response, true),
+ Query::BadNSEC3);
+}
+
+TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
+ // Similar to nxdomainWithNSEC3Proof, but let findNSEC3() return a matching
+ // NSEC3 for the possible wildcard name, emulating run-time collision.
+ // This should result in BadNSEC3 exception.
+
+ mock_finder->setNSEC3Flag(true);
+ mock_finder->addRecord(nsec3_uwild_txt);
+ mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+
+ const Name wname("*.example.com");
+ ZoneFinder::FindNSEC3Result nsec3(true, 0, textToRRset(nsec3_apex_txt),
+ ConstRRsetPtr());
+ mock_finder->setNSEC3Result(&nsec3, &wname);
+
+ EXPECT_THROW(query.process(memory_client, Name("nxdomain.example.com"), qtype,
+ response, true),
+ Query::BadNSEC3);
+}
+
+// The following are tentative tests until we really add tests for the
+// query logic for these cases. At that point it's probably better to
+// clean them up.
+TEST_F(QueryTest, emptyNameWithNSEC3) {
+ mock_finder->setNSEC3Flag(true);
+ ZoneFinderContextPtr result = mock_finder->find(
+ Name("no.example.com"), RRType::A(), ZoneFinder::FIND_DNSSEC);
+ EXPECT_EQ(ZoneFinder::NXRRSET, result->code);
+ EXPECT_FALSE(result->rrset);
+ EXPECT_TRUE(result->isNSEC3Signed());
+ EXPECT_FALSE(result->isWildcard());
+}
+
+// Vector of RRsets used for the test. Having this external to functions and
+// classes used for the testing simplifies the code.
+std::vector<RRsetPtr> rrset_vector;
+
+// Callback function for masterLoad.
+void
+loadRRsetVectorCallback(RRsetPtr rrsetptr) {
+ rrset_vector.push_back(rrsetptr);
+}
+
+// Load a set of RRsets into a vector for use in the duplicate RRset test.
+// They don't make a lot of sense as a zone, they are just different. The
+// variables used in the stringstream input have been chosen so that each
+// represents just one RRset.
+void
+loadRRsetVector() {
+ stringstream ss;
+
+ // Comments indicate offset in the rrset_vector (when loaded) and the
+ // number of RRs in that RRset.
+ ss << soa_txt // 0(1)
+ << zone_ns_txt // 1(3)
+ << delegation_txt // 2(4)
+ << delegation_ds_txt // 3(1)
+ << mx_txt // 4(3)
+ << www_a_txt // 5(1)
+ << cname_txt // 6(1)
+ << cname_nxdom_txt // 7(1)
+ << cname_out_txt; // 8(1)
+ rrset_vector.clear();
+ masterLoad(ss, Name("example.com."), RRClass::IN(),
+ loadRRsetVectorCallback);
+}
+
+TEST_F(QueryTest, DuplicateNameRemoval) {
+
+ // Load some RRsets into the master vector.
+ loadRRsetVector();
+ EXPECT_EQ(9, rrset_vector.size());
+
+ // Create an answer, authority and additional vector with some overlapping
+ // entries. The following indicate which elements from rrset_vector
+ // go into each section vector. (The values have been separated to show
+ // the overlap.)
+ //
+ // answer = 0 1 2 3
+ // authority = 2 3 4 5 6 7...
+ // ...5 (duplicate in the same section)
+ // additional = 0 3 7 8
+ //
+ // If the duplicate removal works, we should end up with the following in
+ // the message created from the three vectors:
+ //
+ // answer = 0 1 2 3
+ // authority = 4 5 6 7
+ // additional = 8
+ //
+ // The expected section into which each RRset is placed is indicated in the
+ // array below.
+ const Message::Section expected_section[] = {
+ Message::SECTION_ANSWER,
+ Message::SECTION_ANSWER,
+ Message::SECTION_ANSWER,
+ Message::SECTION_ANSWER,
+ Message::SECTION_AUTHORITY,
+ Message::SECTION_AUTHORITY,
+ Message::SECTION_AUTHORITY,
+ Message::SECTION_AUTHORITY,
+ Message::SECTION_ADDITIONAL
+ };
+ EXPECT_EQ(rrset_vector.size(),
+ (sizeof(expected_section) / sizeof(Message::Section)));
+
+ // Create the vectors of RRsets (with the RRsets in a semi-random order).
+ std::vector<ConstRRsetPtr> answer;
+ answer.insert(answer.end(), rrset_vector.begin() + 2,
+ rrset_vector.begin() + 4);
+ answer.insert(answer.end(), rrset_vector.begin() + 0,
+ rrset_vector.begin() + 2);
+
+ std::vector<ConstRRsetPtr> authority;
+ authority.insert(authority.end(), rrset_vector.begin() + 3,
+ rrset_vector.begin() + 8);
+ authority.push_back(rrset_vector[2]);
+ authority.push_back(rrset_vector[5]);
+
+ std::vector<ConstRRsetPtr> additional;
+ additional.insert(additional.end(), rrset_vector.begin() + 7,
+ rrset_vector.end());
+ additional.push_back(rrset_vector[3]);
+ additional.push_back(rrset_vector[0]);
+
+ // Create the message object into which the RRsets are put
+ Message message(Message::RENDER);
+ EXPECT_EQ(0, message.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, message.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, message.getRRCount(Message::SECTION_ADDITIONAL));
+
+ // ... and fill it.
+ Query::ResponseCreator().create(message, answer, authority, additional,
+ false);
+
+ // Check counts in each section. Note that these are RR counts,
+ // not RRset counts.
+ EXPECT_EQ(9, message.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(6, message.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(1, message.getRRCount(Message::SECTION_ADDITIONAL));
+
+ // ... and check that the RRsets are in the correct section
+ BOOST_STATIC_ASSERT(Message::SECTION_QUESTION == 0);
+ BOOST_STATIC_ASSERT(Message::SECTION_ANSWER == 1);
+ BOOST_STATIC_ASSERT(Message::SECTION_AUTHORITY == 2);
+ BOOST_STATIC_ASSERT(Message::SECTION_ADDITIONAL == 3);
+ Message::Section sections[] = {
+ Message::SECTION_QUESTION,
+ Message::SECTION_ANSWER,
+ Message::SECTION_AUTHORITY,
+ Message::SECTION_ADDITIONAL
+ };
+ for (int section = 1; section <= 3; ++section) {
+ for (int vecindex = 0; vecindex < rrset_vector.size(); ++vecindex) {
+ // Prepare error message in case an assertion fails (as the default
+ // message will only refer to the loop indexes).
+ stringstream ss;
+ ss << "section " << section << ", name "
+ << rrset_vector[vecindex]->getName();
+ SCOPED_TRACE(ss.str());
+
+ // Check RRset is in the right section and not in the wrong
+ // section.
+ if (sections[section] == expected_section[vecindex]) {
+ EXPECT_TRUE(message.hasRRset(sections[section],
+ rrset_vector[vecindex]));
+ } else {
+ EXPECT_FALSE(message.hasRRset(sections[section],
+ rrset_vector[vecindex]));
+ }
+ }
+ }
+}
}
diff --git a/src/bin/auth/tests/statistics_unittest.cc b/src/bin/auth/tests/statistics_unittest.cc
index 3f19f91..2c25591 100644
--- a/src/bin/auth/tests/statistics_unittest.cc
+++ b/src/bin/auth/tests/statistics_unittest.cc
@@ -14,10 +14,15 @@
#include <config.h>
+#include <string>
+
#include <gtest/gtest.h>
#include <boost/bind.hpp>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+
#include <cc/data.h>
#include <cc/session.h>
@@ -170,6 +175,26 @@ TEST_F(AuthCountersTest, incrementInvalidCounter) {
isc::OutOfRange);
}
+TEST_F(AuthCountersTest, incrementOpcodeCounter) {
+ // The counter should be initialized to 0. If we increment it by 1
+ // the counter should be 1.
+ for (int i = 0; i < 16; ++i) {
+ EXPECT_EQ(0, counters.getCounter(Opcode(i)));
+ counters.inc(Opcode(i));
+ EXPECT_EQ(1, counters.getCounter(Opcode(i)));
+ }
+}
+
+TEST_F(AuthCountersTest, incrementRcodeCounter) {
+ // The counter should be initialized to 0. If we increment it by 1
+ // the counter should be 1.
+ for (int i = 0; i < 17; ++i) {
+ EXPECT_EQ(0, counters.getCounter(Rcode(i)));
+ counters.inc(Rcode(i));
+ EXPECT_EQ(1, counters.getCounter(Rcode(i)));
+ }
+}
+
TEST_F(AuthCountersTest, submitStatisticsWithoutSession) {
// Set statistics_session to NULL and call submitStatistics().
// Expect to return false.
@@ -189,6 +214,51 @@ TEST_F(AuthCountersTest, submitStatisticsWithException) {
statistics_session_.setThrowSessionTimeout(false);
}
+void
+opcodeDataCheck(ConstElementPtr data, const int expected[16]) {
+ const char* item_names[] = {
+ "query", "iquery", "status", "reserved3", "notify", "update",
+ "reserved6", "reserved7", "reserved8", "reserved9", "reserved10",
+ "reserved11", "reserved12", "reserved13", "reserved14", "reserved15",
+ NULL
+ };
+ int i;
+ for (i = 0; i < 16; ++i) {
+ ASSERT_NE(static_cast<const char*>(NULL), item_names[i]);
+ const string item_name = "opcode." + string(item_names[i]);
+ if (expected[i] == 0) {
+ EXPECT_FALSE(data->get(item_name));
+ } else {
+ EXPECT_EQ(expected[i], data->get(item_name)->intValue());
+ }
+ }
+ // We should have examined all names
+ ASSERT_EQ(static_cast<const char*>(NULL), item_names[i]);
+}
+
+void
+rcodeDataCheck(ConstElementPtr data, const int expected[17]) {
+ const char* item_names[] = {
+ "noerror", "formerr", "servfail", "nxdomain", "notimp", "refused",
+ "yxdomain", "yxrrset", "nxrrset", "notauth", "notzone", "reserved11",
+ "reserved12", "reserved13", "reserved14", "reserved15", "badvers",
+ NULL
+ };
+ int i;
+ for (i = 0; i < 17; ++i) {
+ ASSERT_NE(static_cast<const char*>(NULL), item_names[i]);
+ const string item_name = "rcode." + string(item_names[i]);
+ if (expected[i] == 0) {
+ EXPECT_FALSE(data->get(item_name));
+ } else {
+ EXPECT_EQ(expected[i], data->get(item_name)->intValue());
+ }
+ }
+ // We should have examined all names
+ ASSERT_EQ(static_cast<const char*>(NULL), item_names[i]);
+}
+
+
TEST_F(AuthCountersTest, submitStatisticsWithoutValidator) {
// Submit statistics data.
// Validate if it submits correct data.
@@ -211,12 +281,89 @@ TEST_F(AuthCountersTest, submitStatisticsWithoutValidator) {
->get(0)->stringValue());
EXPECT_EQ("Auth", statistics_session_.sent_msg->get("command")
->get(1)->get("owner")->stringValue());
+ EXPECT_EQ(statistics_session_.sent_msg->get("command")
+ ->get(1)->get("pid")->intValue(), getpid());
ConstElementPtr statistics_data = statistics_session_.sent_msg
->get("command")->get(1)
->get("data");
// UDP query counter is 2 and TCP query counter is 1.
EXPECT_EQ(2, statistics_data->get("queries.udp")->intValue());
EXPECT_EQ(1, statistics_data->get("queries.tcp")->intValue());
+
+ // By default opcode counters are all 0 and omitted
+ const int opcode_results[16] = { 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0 };
+ opcodeDataCheck(statistics_data, opcode_results);
+ // By default rcode counters are all 0 and omitted
+ const int rcode_results[17] = { 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0 };
+ rcodeDataCheck(statistics_data, rcode_results);
+}
+
+void
+updateOpcodeCounters(AuthCounters &counters, const int expected[16]) {
+ for (int i = 0; i < 16; ++i) {
+ for (int j = 0; j < expected[i]; ++j) {
+ counters.inc(Opcode(i));
+ }
+ }
+}
+
+void
+updateRcodeCounters(AuthCounters &counters, const int expected[17]) {
+ for (int i = 0; i < 17; ++i) {
+ for (int j = 0; j < expected[i]; ++j) {
+ counters.inc(Rcode(i));
+ }
+ }
+}
+
+TEST_F(AuthCountersTest, submitStatisticsWithOpcodeCounters) {
+ // Increment some of the opcode counters. Then they should appear in the
+ // submitted data; others shouldn't
+ const int opcode_results[16] = { 1, 2, 3, 0, 4, 5, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0 };
+ updateOpcodeCounters(counters, opcode_results);
+ counters.submitStatistics();
+ ConstElementPtr statistics_data = statistics_session_.sent_msg
+ ->get("command")->get(1)->get("data");
+ opcodeDataCheck(statistics_data, opcode_results);
+}
+
+TEST_F(AuthCountersTest, submitStatisticsWithAllOpcodeCounters) {
+ // Increment all opcode counters. Then they should appear in the
+ // submitted data.
+ const int opcode_results[16] = { 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1 };
+ updateOpcodeCounters(counters, opcode_results);
+ counters.submitStatistics();
+ ConstElementPtr statistics_data = statistics_session_.sent_msg
+ ->get("command")->get(1)->get("data");
+ opcodeDataCheck(statistics_data, opcode_results);
+}
+
+TEST_F(AuthCountersTest, submitStatisticsWithRcodeCounters) {
+ // Increment some of the rcode counters. Then they should appear in the
+ // submitted data; others shouldn't
+ const int rcode_results[17] = { 1, 2, 3, 4, 5, 6, 7, 8, 9,
+ 10, 0, 0, 0, 0, 0, 0, 11 };
+ updateRcodeCounters(counters, rcode_results);
+ counters.submitStatistics();
+ ConstElementPtr statistics_data = statistics_session_.sent_msg
+ ->get("command")->get(1)->get("data");
+ rcodeDataCheck(statistics_data, rcode_results);
+}
+
+TEST_F(AuthCountersTest, submitStatisticsWithAllRcodeCounters) {
+ // Increment all rcode counters. Then they should appear in the
+ // submitted data.
+ const int rcode_results[17] = { 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1 };
+ updateOpcodeCounters(counters, rcode_results);
+ counters.submitStatistics();
+ ConstElementPtr statistics_data = statistics_session_.sent_msg
+ ->get("command")->get(1)->get("data");
+ opcodeDataCheck(statistics_data, rcode_results);
}
TEST_F(AuthCountersTest, submitStatisticsWithValidator) {
diff --git a/src/bin/auth/tests/testdata/example.sqlite3 b/src/bin/auth/tests/testdata/example.sqlite3
index e8e255b..0f6ee02 100644
Binary files a/src/bin/auth/tests/testdata/example.sqlite3 and b/src/bin/auth/tests/testdata/example.sqlite3 differ
diff --git a/src/bin/bind10/.gitignore b/src/bin/bind10/.gitignore
new file mode 100644
index 0000000..8dc8a04
--- /dev/null
+++ b/src/bin/bind10/.gitignore
@@ -0,0 +1,3 @@
+/bind10
+/bind10_src.py
+/run_bind10.sh
diff --git a/src/bin/bind10/bind10.8 b/src/bin/bind10/bind10.8
index c2e44e7..2dafaab 100644
--- a/src/bin/bind10/bind10.8
+++ b/src/bin/bind10/bind10.8
@@ -2,12 +2,12 @@
.\" Title: bind10
.\" Author: [see the "AUTHORS" section]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: November 23, 2011
+.\" Date: March 1, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "BIND10" "8" "November 23, 2011" "BIND10" "BIND10"
+.TH "BIND10" "8" "March 1, 2012" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -22,7 +22,7 @@
bind10 \- BIND 10 boss process
.SH "SYNOPSIS"
.HP \w'\fBbind10\fR\ 'u
-\fBbind10\fR [\fB\-c\ \fR\fB\fIconfig\-filename\fR\fR] [\fB\-m\ \fR\fB\fIfile\fR\fR] [\fB\-n\fR] [\fB\-p\ \fR\fB\fIdata_path\fR\fR] [\fB\-u\ \fR\fB\fIuser\fR\fR] [\fB\-v\fR] [\fB\-w\ \fR\fB\fIwait_time\fR\fR] [\fB\-\-cmdctl\-port\fR\ \fIport\fR] [\fB\-\-config\-file\fR\ \fIconfig\-filename\fR] [\fB\-\-data\-path\fR\ \fIdirectory\fR] [\fB\-\-msgq\-socket\-file\ \fR\fB\fIfile\fR\fR] [\fB\-\-no\-cache\fR] [\fB\-\-pid\-file\fR\ \fIfilename\fR] [\fB\-\-pretty\-name\ \fR\fB\fIname\fR\fR] [\fB\-\-user\ \fR\fB\fIuser\fR\fR] [\fB\-\-verbose\fR] [\fB\-\-wait\ \fR\fB\fIwait_time\fR\fR]
+\fBbind10\fR [\fB\-c\ \fR\fB\fIconfig\-filename\fR\fR] [\fB\-i\fR] [\fB\-m\ \fR\fB\fIfile\fR\fR] [\fB\-n\fR] [\fB\-p\ \fR\fB\fIdata_path\fR\fR] [\fB\-u\ \fR\fB\fIuser\fR\fR] [\fB\-v\fR] [\fB\-w\ \fR\fB\fIwait_time\fR\fR] [\fB\-\-cmdctl\-port\fR\ \fIport\fR] [\fB\-\-config\-file\fR\ \fIconfig\-filename\fR] [\fB\-\-data\-path\fR\ \fIdirectory\fR] [\fB\-\-msgq\-socket\-file\ \fR\fB\fIfile\fR\fR] [\fB\-\-no\-cache\fR] [\fB\-\-no\-kill\fR] [\fB\-\-pid\-file\fR\ \fIfilename\fR] [\fB\-\-pretty\-name\ \fR\fB\fIname\fR\fR] [\fB\-\-user\ \fR\fB\fIuser\fR\fR] [\fB\-\-verbose\fR] [\fB\-\-wait\ \fR\fB\fIwait_time\fR\fR]
.SH "DESCRIPTION"
.PP
The
@@ -34,9 +34,8 @@ The arguments are as follows:
.PP
\fB\-c\fR \fIconfig\-filename\fR, \fB\-\-config\-file\fR \fIconfig\-filename\fR
.RS 4
-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\&.
-.sp
-Defaults to b10\-config\&.db\&.
+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\&. Defaults to
+b10\-config\&.db\&.
.RE
.PP
\fB\-\-cmdctl\-port\fR \fIport\fR
@@ -50,7 +49,9 @@ for the default\&.)
.PP
\fB\-p\fR \fIdirectory\fR, \fB\-\-data\-path\fR \fIdirectory\fR
.RS 4
-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\&.
+The path where BIND 10 programs look for various data files\&. Currently only
+\fBb10-cfgmgr\fR(8)
+uses it to locate the configuration file, but the usage might be extended for other programs and other types of files\&.
.RE
.PP
\fB\-m\fR \fIfile\fR, \fB\-\-msgq\-socket\-file\fR \fIfile\fR
@@ -68,12 +69,18 @@ Disables the hot\-spot caching used by the
daemon\&.
.RE
.PP
+\fB\-i\fR, \fB\-\-no\-kill\fR
+.RS 4
+When this option is passed,
+\fBbind10\fR
+does not send SIGTERM and SIGKILL signals to modules during shutdown\&. (This option was introduced for use during testing\&.)
+.RE
+.PP
\fB\-u\fR \fIuser\fR, \fB\-\-user\fR \fIname\fR
.RS 4
The username for
\fBbind10\fR
to run as\&.
-
\fBbind10\fR
must be initially ran as the root user to use this option\&. The default is to run as the current user\&.
.RE
@@ -82,7 +89,7 @@ must be initially ran as the root user to use this option\&. The default is to r
.RS 4
If defined, the PID of the
\fBbind10\fR
-is stored in this file\&. This is used for testing purposes\&.
+is stored in this file\&.
.RE
.PP
\fB\-\-pretty\-name \fR\fB\fIname\fR\fR
@@ -103,7 +110,9 @@ and its child processes\&.
.PP
\fB\-w\fR \fIwait_time\fR, \fB\-\-wait\fR \fIwait_time\fR
.RS 4
-Sets the amount of time that BIND 10 will wait for the configuration manager (a key component of BIND 10) to initialize itself before abandoning the start up and terminating with an error\&. The wait_time is specified in seconds and has a default value of 10\&.
+Sets the amount of time that BIND 10 will wait for the configuration manager (a key component of BIND 10) to initialize itself before abandoning the start up and terminating with an error\&. The
+\fIwait_time\fR
+is specified in seconds and has a default value of 10\&.
.RE
.SH "CONFIGURATION AND COMMANDS"
.PP
@@ -145,18 +154,6 @@ to manage under
.IP \(bu 2.3
.\}
-\fI/Boss/components/setuid\fR
-.RE
-.sp
-.RS 4
-.ie n \{\
-\h'-04'\(bu\h'+03'\c
-.\}
-.el \{\
-.sp -1
-.IP \(bu 2.3
-.\}
-
\fI/Boss/components/b10\-stats\fR
.RE
.sp
@@ -212,11 +209,11 @@ to manage under
\fBb10\-sockcreator\fR,
\fBb10\-cfgmgr\fR, and
\fBb10\-msgq\fR
-is not configurable\&. It is hardcoded and
+is not configurable\&. They are hardcoded and
\fBbind10\fR
will not run without them\&.)
.PP
-These named sets (listed above) contain the following settings:
+The named sets for components contain the following settings:
.PP
\fIaddress\fR
.RS 4
@@ -258,7 +255,7 @@ will use the component name instead\&.
.PP
\fIspecial\fR
.RS 4
-This defines if the component is started a special way\&.
+This defines if the component is started a special, hardcoded way\&.
.RE
.PP
The
@@ -307,14 +304,22 @@ will exit\&.
.PP
The statistics data collected by the
\fBb10\-stats\fR
-daemon include:
+daemon for
+\(lqBoss\(rq
+include:
.PP
-bind10\&.boot_time
+boot_time
.RS 4
The date and time that the
\fBbind10\fR
process started\&. This is represented in ISO 8601 format\&.
.RE
+.SH "FILES"
+.PP
+sockcreator\-XXXXXX/sockcreator
+\(em the Unix Domain socket located in a temporary file directory for
+\fBb10\-sockcreator\fR
+communication\&.
.SH "SEE ALSO"
.PP
@@ -339,5 +344,5 @@ The
daemon was initially designed by Shane Kerr of ISC\&.
.SH "COPYRIGHT"
.br
-Copyright \(co 2011 Internet Systems Consortium, Inc. ("ISC")
+Copyright \(co 2010-2012 Internet Systems Consortium, Inc. ("ISC")
.br
diff --git a/src/bin/bind10/bind10.xml b/src/bin/bind10/bind10.xml
index 6705760..2501fee 100644
--- a/src/bin/bind10/bind10.xml
+++ b/src/bin/bind10/bind10.xml
@@ -2,7 +2,7 @@
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[<!ENTITY mdash "—">]>
<!--
- - Copyright (C) 2010-2011 Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2010-2012 Internet Systems Consortium, Inc. ("ISC")
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>November 23, 2011</date>
+ <date>March 1, 2012</date>
</refentryinfo>
<refmeta>
@@ -36,7 +36,7 @@
<docinfo>
<copyright>
- <year>2011</year>
+ <year>2010-2012</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
@@ -45,6 +45,7 @@
<cmdsynopsis>
<command>bind10</command>
<arg><option>-c <replaceable>config-filename</replaceable></option></arg>
+ <arg><option>-i</option></arg>
<arg><option>-m <replaceable>file</replaceable></option></arg>
<arg><option>-n</option></arg>
<arg><option>-p <replaceable>data_path</replaceable></option></arg>
@@ -56,6 +57,7 @@
<arg><option>--data-path</option> <replaceable>directory</replaceable></arg>
<arg><option>--msgq-socket-file <replaceable>file</replaceable></option></arg>
<arg><option>--no-cache</option></arg>
+ <arg><option>--no-kill</option></arg>
<arg><option>--pid-file</option> <replaceable>filename</replaceable></arg>
<arg><option>--pretty-name <replaceable>name</replaceable></option></arg>
<arg><option>--user <replaceable>user</replaceable></option></arg>
@@ -97,8 +99,8 @@
<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>
+ not considered.
+ Defaults to <filename>b10-config.db</filename>.</para>
</listitem>
</varlistentry>
@@ -123,9 +125,11 @@
</term>
<listitem>
<para>The path where BIND 10 programs look for various data files.
- Currently only b10-cfgmgr uses it to locate the configuration file,
- but the usage might be extended for other programs and other types
- of files.</para>
+ Currently only
+ <citerefentry><refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ uses it to locate the configuration file, but the usage
+ might be extended for other programs and other types of
+ files.</para>
</listitem>
</varlistentry>
@@ -154,10 +158,20 @@
</varlistentry>
<varlistentry>
+ <term><option>-i</option>, <option>--no-kill</option></term>
+ <listitem>
+ <para>When this option is passed, <command>bind10</command>
+ does not send SIGTERM and SIGKILL signals to modules during
+ shutdown. (This option was introduced for use during
+ testing.)</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>-u</option> <replaceable>user</replaceable>, <option>--user</option> <replaceable>name</replaceable></term>
+<!-- TODO: example more detail. -->
<listitem>
<para>The username for <command>bind10</command> to run as.
-<!-- TODO: example more detail. -->
<command>bind10</command> must be initially ran as the
root user to use this option.
The default is to run as the current user.</para>
@@ -169,7 +183,6 @@
<listitem>
<para>If defined, the PID of the <command>bind10</command> is stored
in this file.
- This is used for testing purposes.
</para>
</listitem>
</varlistentry>
@@ -201,11 +214,12 @@ The default is the basename of ARG 0.
<varlistentry>
<term><option>-w</option> <replaceable>wait_time</replaceable>, <option>--wait</option> <replaceable>wait_time</replaceable></term>
<listitem>
- <para>Sets the amount of time that BIND 10 will wait for
- the configuration manager (a key component of BIND 10) to
- initialize itself before abandoning the start up and
- terminating with an error. The wait_time is specified in
- seconds and has a default value of 10.
+ <para>Sets the amount of time that BIND 10 will wait for
+ the configuration manager (a key component of BIND 10)
+ to initialize itself before abandoning the start up and
+ terminating with an error. The
+ <replaceable>wait_time</replaceable> is specified in
+ seconds and has a default value of 10.
</para>
</listitem>
</varlistentry>
@@ -230,48 +244,24 @@ TODO: configuration section
<itemizedlist>
<listitem>
- <para> <varname>/Boss/components/b10-auth</varname> </para>
- </listitem>
-
- <listitem>
<para> <varname>/Boss/components/b10-cmdctl</varname> </para>
</listitem>
<listitem>
- <para> <varname>/Boss/components/setuid</varname> </para>
- </listitem>
-
- <listitem>
<para> <varname>/Boss/components/b10-stats</varname> </para>
</listitem>
- <listitem>
- <para> <varname>/Boss/components/b10-stats-httpd</varname> </para>
- </listitem>
-
- <listitem>
- <para> <varname>/Boss/components/b10-xfrin</varname> </para>
- </listitem>
-
- <listitem>
- <para> <varname>/Boss/components/b10-xfrout</varname> </para>
- </listitem>
-
- <listitem>
- <para> <varname>/Boss/components/b10-zonemgr</varname> </para>
- </listitem>
-
</itemizedlist>
<para>
(Note that the startup of <command>b10-sockcreator</command>,
<command>b10-cfgmgr</command>, and <command>b10-msgq</command>
- is not configurable. It is hardcoded and <command>bind10</command>
+ is not configurable. They are hardcoded and <command>bind10</command>
will not run without them.)
</para>
<para>
- These named sets (listed above) contain the following settings:
+ The named sets for components contain the following settings:
</para>
<variablelist>
@@ -346,7 +336,7 @@ list
<term> <varname>special</varname> </term>
<listitem>
<para>
- This defines if the component is started a special
+ This defines if the component is started a special, hardcoded
way.
<!--
TODO: document this ... but maybe some of these will be removed
@@ -357,7 +347,6 @@ cfgmgr
cmdctl
msgq
resolver
-setuid
sockcreator
xfrin
-->
@@ -374,6 +363,22 @@ xfrin
</para>
<!-- TODO: let's just let bind10 be known as bind10 and not Boss -->
+<!-- TODO -->
+<!--
+ <para>
+ <command>drop_socket</command>
+ This is an internal command and not exposed to the administrator.
+ </para>
+-->
+
+<!-- TODO -->
+<!--
+ <para>
+ <command>get_socket</command>
+ This is an internal command and not exposed to the administrator.
+ </para>
+-->
+
<para>
<command>getstats</command> tells <command>bind10</command>
to send its statistics data to the <command>b10-stats</command>
@@ -420,13 +425,13 @@ xfrin
<para>
The statistics data collected by the <command>b10-stats</command>
- daemon include:
+ daemon for <quote>Boss</quote> include:
</para>
<variablelist>
<varlistentry>
- <term>bind10.boot_time</term>
+ <term>boot_time</term>
<listitem><para>
The date and time that the <command>bind10</command>
process started.
@@ -438,13 +443,16 @@ xfrin
</refsect1>
-<!--
<refsect1>
<title>FILES</title>
- <para><filename></filename>
+ <para><filename>sockcreator-XXXXXX/sockcreator</filename>
+ —
+ the Unix Domain socket located in a temporary file directory for
+ <command>b10-sockcreator</command>
+<!-- <citerefentry><refentrytitle>b10-sockcreator</refentrytitle><manvolnum>8</manvolnum></citerefentry> -->
+ communication.
</para>
</refsect1>
--->
<refsect1>
<title>SEE ALSO</title>
@@ -476,6 +484,9 @@ xfrin
<citetitle>BIND 10 Guide</citetitle>.
</para>
</refsect1>
+<!-- <citerefentry>
+ <refentrytitle>b10-sockcreator</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>, -->
<refsect1 id='history'><title>HISTORY</title>
<para>The development of <command>bind10</command>
diff --git a/src/bin/bind10/bind10_messages.mes b/src/bin/bind10/bind10_messages.mes
index 79635fd..3dd938f 100644
--- a/src/bin/bind10/bind10_messages.mes
+++ b/src/bin/bind10/bind10_messages.mes
@@ -24,7 +24,7 @@ needs a dedicated message bus.
An error was encountered when the boss module specified
statistics data which is invalid for the boss specification file.
-% BIND10_COMPONENT_FAILED component %1 (pid %2) failed with %3 exit status
+% BIND10_COMPONENT_FAILED component %1 (pid %2) failed: %3
The process terminated, but the bind10 boss didn't expect it to, which means
it must have failed.
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 00858d8..37b845d 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/src/bin/bind10/bind10_src.py.in
@@ -44,12 +44,10 @@ import os
# installed on the system
if "B10_FROM_SOURCE" in os.environ:
SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + "/src/bin/bind10/bob.spec"
- ADD_LIBEXEC_PATH = False
else:
PREFIX = "@prefix@"
DATAROOTDIR = "@datarootdir@"
SPECFILE_LOCATION = "@datadir@/@PACKAGE@/bob.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
- ADD_LIBEXEC_PATH = True
import subprocess
import signal
@@ -65,6 +63,7 @@ import pwd
import posix
import copy
+from bind10_config import LIBEXECPATH
import isc.cc
import isc.util.process
import isc.net.parse
@@ -85,8 +84,12 @@ DBG_PROCESS = logger.DBGLVL_TRACE_BASIC
DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL
# Messages sent over the unix domain socket to indicate if it is followed by a real socket
-CREATOR_SOCKET_OK = "1\n"
-CREATOR_SOCKET_UNAVAILABLE = "0\n"
+CREATOR_SOCKET_OK = b"1\n"
+CREATOR_SOCKET_UNAVAILABLE = b"0\n"
+
+# RCodes of known exceptions for the get_token command
+CREATOR_SOCKET_ERROR = 2
+CREATOR_SHARE_ERROR = 3
# Assign this process some longer name
isc.util.process.rename(sys.argv[0])
@@ -107,14 +110,12 @@ class ProcessInfo:
dev_null = open(os.devnull, "w")
def __init__(self, name, args, env={}, dev_null_stdout=False,
- dev_null_stderr=False, uid=None, username=None):
- self.name = name
+ dev_null_stderr=False):
+ self.name = name
self.args = args
self.env = env
self.dev_null_stdout = dev_null_stdout
self.dev_null_stderr = dev_null_stderr
- self.uid = uid
- self.username = username
self.process = None
self.pid = None
@@ -125,17 +126,6 @@ class ProcessInfo:
# SIGINT signals on Ctrl-C (the boss will shut everthing down by
# other means).
os.setpgrp()
- # Second, set the user ID if one has been specified
- if self.uid is not None:
- try:
- posix.setuid(self.uid)
- except OSError as e:
- if e.errno == errno.EPERM:
- # if we failed to change user due to permission report that
- raise ProcessInfoError("Unable to change to user %s (uid %d)" % (self.username, self.uid))
- else:
- # otherwise simply re-raise whatever error we found
- raise
def _spawn(self):
if self.dev_null_stdout:
@@ -151,8 +141,7 @@ class ProcessInfo:
# on construction (self.env).
spawn_env = copy.deepcopy(os.environ)
spawn_env.update(self.env)
- if ADD_LIBEXEC_PATH:
- spawn_env['PATH'] = "@@LIBEXECDIR@@:" + spawn_env['PATH']
+ spawn_env['PATH'] = LIBEXECPATH + ':' + spawn_env['PATH']
self.process = subprocess.Popen(self.args,
stdin=subprocess.PIPE,
stdout=spawn_stdout,
@@ -178,8 +167,9 @@ class BoB:
"""Boss of BIND class."""
def __init__(self, msgq_socket_file=None, data_path=None,
- config_filename=None, nocache=False, verbose=False, setuid=None,
- username=None, cmdctl_port=None, wait_time=10):
+ config_filename=None, clear_config=False, nocache=False,
+ verbose=False, nokill=False, setuid=None, username=None,
+ cmdctl_port=None, wait_time=10):
"""
Initialize the Boss of BIND. This is a singleton (only one can run).
@@ -204,9 +194,13 @@ class BoB:
self.nocache = nocache
self.component_config = {}
# Some time in future, it may happen that a single component has
- # multple processes. If so happens, name "components" may be
- # inapropriate. But as the code isn't probably completely ready
- # for it, we leave it at components for now.
+ # multple processes (like a pipeline-like component). If so happens,
+ # name "components" may be inapropriate. But as the code isn't probably
+ # completely ready for it, we leave it at components for now. We also
+ # want to support multiple instances of a single component. If it turns
+ # out that we'll have a single component with multiple same processes
+ # or if we start multiple components with the same configuration (we do
+ # this now, but it might change) is an open question.
self.components = {}
# Simply list of components that died and need to wait for a
# restart. Components manage their own restart schedule now
@@ -215,8 +209,10 @@ class BoB:
self.uid = setuid
self.username = username
self.verbose = verbose
+ self.nokill = nokill
self.data_path = data_path
self.config_filename = config_filename
+ self.clear_config = clear_config
self.cmdctl_port = cmdctl_port
self.wait_time = wait_time
self._component_configurator = isc.bind10.component.Configurator(self,
@@ -439,8 +435,7 @@ class BoB:
"""
self.log_starting("b10-msgq")
msgq_proc = ProcessInfo("b10-msgq", ["b10-msgq"], self.c_channel_env,
- True, not self.verbose, uid=self.uid,
- username=self.username)
+ True, not self.verbose)
msgq_proc.spawn()
self.log_started(msgq_proc.pid)
@@ -473,9 +468,10 @@ class BoB:
args.append("--data-path=" + self.data_path)
if self.config_filename is not None:
args.append("--config-filename=" + self.config_filename)
+ if self.clear_config:
+ args.append("--clear-config")
bind_cfgd = ProcessInfo("b10-cfgmgr", args,
- self.c_channel_env, uid=self.uid,
- username=self.username)
+ self.c_channel_env)
bind_cfgd.spawn()
self.log_started(bind_cfgd.pid)
@@ -572,8 +568,6 @@ class BoB:
authargs = ['b10-auth']
if self.nocache:
authargs += ['-n']
- if self.uid:
- authargs += ['-u', str(self.uid)]
if self.verbose:
authargs += ['-v']
@@ -591,8 +585,6 @@ class BoB:
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:
resargs += ['-v']
@@ -666,14 +658,17 @@ class BoB:
self.__started = True
return None
- def stop_process(self, process, recipient):
+ def stop_process(self, process, recipient, pid):
"""
Stop the given process, friendly-like. The process is the name it has
- (in logs, etc), the recipient is the address on msgq.
+ (in logs, etc), the recipient is the address on msgq. The pid is the
+ pid of the process (if we have multiple processes of the same name,
+ it might want to choose if it is for this one).
"""
logger.info(BIND10_STOP_PROCESS, process)
- self.cc_session.group_sendmsg({'command': ['shutdown']}, recipient,
- recipient)
+ self.cc_session.group_sendmsg(isc.config.ccsession.
+ create_command('shutdown', {'pid': pid}),
+ recipient, recipient)
def component_shutdown(self, exitcode=0):
"""
@@ -696,7 +691,13 @@ class BoB:
def shutdown(self):
"""Stop the BoB instance."""
logger.info(BIND10_SHUTDOWN)
- # first try using the BIND 10 request to stop
+ # If ccsession is still there, inform rest of the system this module
+ # is stopping. Since everything will be stopped shortly, this is not
+ # really necessary, but this is done to reflect that boss is also
+ # 'just' a module.
+ self.ccs.send_stopping()
+
+ # try using the BIND 10 request to stop
try:
self._component_configurator.shutdown()
except:
@@ -706,32 +707,36 @@ class BoB:
# still not enough.
time.sleep(1)
self.reap_children()
- # next try sending a SIGTERM
- components_to_stop = list(self.components.values())
- for component in components_to_stop:
- logger.info(BIND10_SEND_SIGTERM, component.name(), component.pid())
- try:
- component.kill()
- except OSError:
- # ignore these (usually ESRCH because the child
- # finally exited)
- pass
- # finally, send SIGKILL (unmaskable termination) until everybody dies
- while self.components:
- # XXX: some delay probably useful... how much is uncertain
- time.sleep(0.1)
- self.reap_children()
+
+ # Send TERM and KILL signals to modules if we're not prevented
+ # from doing so
+ if not self.nokill:
+ # next try sending a SIGTERM
components_to_stop = list(self.components.values())
for component in components_to_stop:
- logger.info(BIND10_SEND_SIGKILL, component.name(),
- component.pid())
+ logger.info(BIND10_SEND_SIGTERM, component.name(), component.pid())
try:
- component.kill(True)
+ component.kill()
except OSError:
# ignore these (usually ESRCH because the child
# finally exited)
pass
- logger.info(BIND10_SHUTDOWN_COMPLETE)
+ # finally, send SIGKILL (unmaskable termination) until everybody dies
+ while self.components:
+ # XXX: some delay probably useful... how much is uncertain
+ time.sleep(0.1)
+ self.reap_children()
+ components_to_stop = list(self.components.values())
+ for component in components_to_stop:
+ logger.info(BIND10_SEND_SIGKILL, component.name(),
+ component.pid())
+ try:
+ component.kill(True)
+ except OSError:
+ # ignore these (usually ESRCH because the child
+ # finally exited)
+ pass
+ logger.info(BIND10_SHUTDOWN_COMPLETE)
def _get_process_exit_status(self):
return os.waitpid(-1, os.WNOHANG)
@@ -826,6 +831,12 @@ class BoB:
'token': token,
'path': self._socket_path
})
+ except isc.bind10.socket_cache.SocketError as e:
+ return isc.config.ccsession.create_answer(CREATOR_SOCKET_ERROR,
+ str(e))
+ except isc.bind10.socket_cache.ShareError as e:
+ return isc.config.ccsession.create_answer(CREATOR_SHARE_ERROR,
+ str(e))
except Exception as e:
return isc.config.ccsession.create_answer(1, str(e))
@@ -836,6 +847,7 @@ class BoB:
identified by the token back over the unix_socket.
"""
try:
+ token = str(token, 'ASCII') # Convert from bytes to str
fd = self._socket_cache.get_socket(token, unix_socket.fileno())
# FIXME: These two calls are blocking in their nature. An OS-level
# buffer is likely to be large enough to hold all these data, but
@@ -889,7 +901,7 @@ class BoB:
# the need to find the place ourself or bother users. Also, this
# secures the socket on some platforms, as it creates a private
# directory.
- self._tmpdir = tempfile.mkdtemp()
+ self._tmpdir = tempfile.mkdtemp(prefix='sockcreator-')
# Get the name
self._socket_path = os.path.join(self._tmpdir, "sockcreator")
# And bind the socket to the name
@@ -914,7 +926,7 @@ class BoB:
Accept a socket from the unix domain socket server and put it to the
others we care about.
"""
- socket = self._srv_socket.accept()
+ (socket, conn) = self._srv_socket.accept()
self._unix_sockets[socket.fileno()] = (socket, b'')
def _socket_data(self, socket_fileno):
@@ -1040,6 +1052,8 @@ def parse_args(args=sys.argv[1:], Parser=OptionParser):
help="UNIX domain socket file the b10-msgq daemon will use")
parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
default=False, help="disable hot-spot cache in authoritative DNS server")
+ parser.add_option("-i", "--no-kill", action="store_true", dest="nokill",
+ default=False, help="do not send SIGTERM and SIGKILL signals to modules during shutdown")
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",
@@ -1050,6 +1064,10 @@ def parse_args(args=sys.argv[1:], Parser=OptionParser):
parser.add_option("-c", "--config-file", action="store",
dest="config_file", default=None,
help="Configuration database filename")
+ parser.add_option("--clear-config", action="store_true",
+ dest="clear_config", default=False,
+ help="Create backup of the configuration file and " +
+ "start with a clean configuration")
parser.add_option("-p", "--data-path", dest="data_path",
help="Directory to search for configuration files",
default=None)
@@ -1162,9 +1180,10 @@ def main():
try:
# Go bob!
boss_of_bind = BoB(options.msgq_socket_file, options.data_path,
- options.config_file, options.nocache,
- options.verbose, setuid, username,
- options.cmdctl_port, options.wait_time)
+ options.config_file, options.clear_config,
+ options.nocache, options.verbose, options.nokill,
+ setuid, username, options.cmdctl_port,
+ options.wait_time)
startup_result = boss_of_bind.startup()
if startup_result:
logger.fatal(BIND10_STARTUP_ERROR, startup_result)
diff --git a/src/bin/bind10/bob.spec b/src/bin/bind10/bob.spec
index adc9798..8b75640 100644
--- a/src/bin/bind10/bob.spec
+++ b/src/bin/bind10/bob.spec
@@ -8,20 +8,7 @@
"item_type": "named_set",
"item_optional": false,
"item_default": {
- "b10-auth": { "special": "auth", "kind": "needed", "priority": 10 },
- "setuid": {
- "special": "setuid",
- "priority": 5,
- "kind": "dispensable"
- },
- "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
- "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
- "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
"b10-stats": { "address": "Stats", "kind": "dispensable" },
- "b10-stats-httpd": {
- "address": "StatsHttpd",
- "kind": "dispensable"
- },
"b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
},
"named_set_item_spec": {
diff --git a/src/bin/bind10/tests/.gitignore b/src/bin/bind10/tests/.gitignore
new file mode 100644
index 0000000..5e54716
--- /dev/null
+++ b/src/bin/bind10/tests/.gitignore
@@ -0,0 +1 @@
+/bind10_test.py
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index f9537fd..84a9da9 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -36,6 +36,7 @@ import isc.bind10.socket_cache
import errno
from isc.testutils.parse_args import TestOptParser, OptsError
+from isc.testutils.ccsession_mock import MockModuleCCSession
class TestProcessInfo(unittest.TestCase):
def setUp(self):
@@ -146,7 +147,7 @@ class TestCacheCommands(unittest.TestCase):
socket.
"""
def __init__(self):
- self.send = ""
+ self.send = b""
def fileno(self):
"""
The file number. Used for identifying the remote application.
@@ -207,17 +208,17 @@ class TestCacheCommands(unittest.TestCase):
socket = self.FalseSocket()
# An exception from the cache
self.__raise_exception = ValueError("Test value error")
- self.__boss.socket_request_handler("token", socket)
+ self.__boss.socket_request_handler(b"token", socket)
# It was called, but it threw, so it is not noted here
self.assertIsNone(self.__get_socket_called)
- self.assertEqual("0\n", socket.send)
+ self.assertEqual(b"0\n", socket.send)
# It should not have sent any socket.
self.assertIsNone(self.__send_fd_called)
# Now prepare a valid scenario
self.__raise_exception = None
- socket.send = ""
- self.__boss.socket_request_handler("token", socket)
- self.assertEqual("1\n", socket.send)
+ socket.send = b""
+ self.__boss.socket_request_handler(b"token", socket)
+ self.assertEqual(b"1\n", socket.send)
self.assertEqual((42, 13), self.__send_fd_called)
self.assertEqual(("token", 42), self.__get_socket_called)
@@ -263,10 +264,11 @@ class TestCacheCommands(unittest.TestCase):
"""
[rcode, ranswer] = self.__boss._get_socket(args)['result']
self.assertEqual(code, rcode)
- if code == 1:
+ if code != 0:
# This should be an error message. The exact formatting
# is unknown, but we check it is string at least
self.assertTrue(isinstance(ranswer, str))
+
def mod_args(name, value):
"""
Override a parameter in the args.
@@ -296,6 +298,13 @@ class TestCacheCommands(unittest.TestCase):
# to an error, not propagated
self.__raise_exception = Exception("Test exception")
check_code(1, self.__socket_args)
+ # The special "expected" exceptions
+ self.__raise_exception = \
+ isc.bind10.socket_cache.ShareError("Not shared")
+ check_code(3, self.__socket_args)
+ self.__raise_exception = \
+ isc.bind10.socket_cache.SocketError("Not shared", 13)
+ check_code(2, self.__socket_args)
def drop_socket(self, token):
"""
@@ -451,6 +460,22 @@ class TestBoB(unittest.TestCase):
# The drop_socket is not tested here, but in TestCacheCommands.
# It needs the cache mocks to be in place and they are there.
+ def test_stop_process(self):
+ """
+ Test checking the stop_process method sends the right message over
+ the message bus.
+ """
+ class DummySession():
+ def group_sendmsg(self, msg, group, instance="*"):
+ (self.msg, self.group, self.instance) = (msg, group, instance)
+ bob = BoB()
+ bob.cc_session = DummySession()
+ bob.stop_process('process', 'address', 42)
+ self.assertEqual('address', bob.cc_session.group)
+ self.assertEqual('address', bob.cc_session.instance)
+ self.assertEqual({'command': ['shutdown', {'pid': 42}]},
+ bob.cc_session.msg)
+
# Class for testing the BoB without actually starting processes.
# This is used for testing the start/stop components routines and
# the BoB commands.
@@ -589,7 +614,7 @@ class MockBob(BoB):
procinfo.pid = 14
return procinfo
- def stop_process(self, process, recipient):
+ def stop_process(self, process, recipient, pid):
procmap = { 'b10-auth': self.stop_auth,
'b10-resolver': self.stop_resolver,
'b10-xfrout': self.stop_xfrout,
@@ -987,6 +1012,22 @@ class TestParseArgs(unittest.TestCase):
options = parse_args(['--config-file=config-file'], TestOptParser)
self.assertEqual('config-file', options.config_file)
+ def test_clear_config(self):
+ options = parse_args([], TestOptParser)
+ self.assertEqual(False, options.clear_config)
+ options = parse_args(['--clear-config'], TestOptParser)
+ self.assertEqual(True, options.clear_config)
+
+ def test_nokill(self):
+ options = parse_args([], TestOptParser)
+ self.assertEqual(False, options.nokill)
+ options = parse_args(['--no-kill'], TestOptParser)
+ self.assertEqual(True, options.nokill)
+ options = parse_args([], TestOptParser)
+ self.assertEqual(False, options.nokill)
+ options = parse_args(['-i'], TestOptParser)
+ self.assertEqual(True, options.nokill)
+
def test_cmdctl_port(self):
"""
Test it can parse the command control port.
@@ -1135,11 +1176,13 @@ class TestBossComponents(unittest.TestCase):
# We check somewhere else that the shutdown is actually called
# from there (the test_kills).
- def test_kills(self):
+ def __real_test_kill(self, nokill = False):
"""
- Test that the boss kills components which don't want to stop.
+ Helper function that does the actual kill functionality testing.
"""
bob = MockBob()
+ bob.nokill = nokill
+
killed = []
class ImmortalComponent:
"""
@@ -1163,13 +1206,39 @@ class TestBossComponents(unittest.TestCase):
bob._component_configurator.shutdown = self.__nullary_hook
self.__called = False
+ bob.ccs = MockModuleCCSession()
+ self.assertFalse(bob.ccs.stopped)
+
bob.shutdown()
- self.assertEqual([False, True], killed)
+ self.assertTrue(bob.ccs.stopped)
+
+ # Here, killed is an array where False is added if SIGTERM
+ # should be sent, or True if SIGKILL should be sent, in order in
+ # which they're sent.
+ if nokill:
+ self.assertEqual([], killed)
+ else:
+ self.assertEqual([False, True], killed)
+
self.assertTrue(self.__called)
bob._component_configurator.shutdown = orig
+ def test_kills(self):
+ """
+ Test that the boss kills components which don't want to stop.
+ """
+ self.__real_test_kill()
+
+ def test_nokill(self):
+ """
+ Test that the boss *doesn't* kill components which don't want to
+ stop, when asked not to (by passing the --no-kill option which
+ sets bob.nokill to True).
+ """
+ self.__real_test_kill(True)
+
def test_component_shutdown(self):
"""
Test the component_shutdown sets all variables accordingly.
@@ -1235,7 +1304,7 @@ class SocketSrvTest(unittest.TestCase):
return self.__fileno
def accept(self):
- return self.__class__(self.__owner, 13)
+ return (self.__class__(self.__owner, 13), "/path/to/socket")
def recv(self, bufsize, flags=0):
self.__owner.assertEqual(1, bufsize)
diff --git a/src/bin/bindctl/.gitignore b/src/bin/bindctl/.gitignore
new file mode 100644
index 0000000..71fba03
--- /dev/null
+++ b/src/bin/bindctl/.gitignore
@@ -0,0 +1,3 @@
+/bindctl
+/bindctl_main.py
+/run_bindctl.sh
diff --git a/src/bin/bindctl/Makefile.am b/src/bin/bindctl/Makefile.am
index 700f26e..52a5145 100644
--- a/src/bin/bindctl/Makefile.am
+++ b/src/bin/bindctl/Makefile.am
@@ -8,7 +8,7 @@ EXTRA_DIST = $(man_MANS) bindctl.xml
noinst_SCRIPTS = run_bindctl.sh
python_PYTHON = __init__.py bindcmd.py cmdparse.py exception.py moduleinfo.py \
- mycollections.py
+ mycollections.py command_sets.py
pythondir = $(pyexecdir)/bindctl
bindctldir = $(pkgdatadir)
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index b67bc4b..f1a622e 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -23,6 +23,7 @@ from cmd import Cmd
from bindctl.exception import *
from bindctl.moduleinfo import *
from bindctl.cmdparse import BindCmdParse
+from bindctl import command_sets
from xml.dom import minidom
import isc
import isc.cc.data
@@ -37,6 +38,7 @@ from hashlib import sha1
import csv
import pwd
import getpass
+import copy
try:
from collections import OrderedDict
@@ -319,6 +321,8 @@ class BindCmdInterpreter(Cmd):
param_spec = arg)
if ("item_default" in arg):
param.default = arg["item_default"]
+ if ("item_description" in arg):
+ param.desc = arg["item_description"]
cmd.add_param(param)
module.add_command(cmd)
self.add_module_info(module)
@@ -361,12 +365,12 @@ class BindCmdInterpreter(Cmd):
if type(name) == int:
# lump all extraneous arguments together as one big final one
# todo: check if last param type is a string?
- if (param_count > 2):
- while (param_count > len(command_info.params) - 1):
- params[param_count - 2] += params[param_count - 1]
- del(params[param_count - 1])
- param_count = len(params)
- cmd.params = params.copy()
+ while (param_count > 2 and
+ param_count > len(command_info.params) - 1):
+ params[param_count - 2] += " " + params[param_count - 1]
+ del(params[param_count - 1])
+ param_count = len(params)
+ cmd.params = params.copy()
# (-1, help is always in the all_params list)
if name >= len(all_params) - 1:
@@ -391,8 +395,9 @@ class BindCmdInterpreter(Cmd):
param_nr += 1
# Convert parameter value according parameter spec file.
- # Ignore check for commands belongs to module 'config'
- if cmd.module != CONFIG_MODULE_NAME:
+ # Ignore check for commands belongs to module 'config' or 'execute
+ if cmd.module != CONFIG_MODULE_NAME and\
+ cmd.module != command_sets.EXECUTE_MODULE_NAME:
for param_name in cmd.params:
param_spec = command_info.get_param_with_name(param_name).param_spec
try:
@@ -406,16 +411,9 @@ class BindCmdInterpreter(Cmd):
if cmd.command == "help" or ("help" in cmd.params.keys()):
self._handle_help(cmd)
elif cmd.module == CONFIG_MODULE_NAME:
- 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 isc.cc.data.DataAlreadyPresentError as dape:
- print("Error: " + str(dape))
- except KeyError as ke:
- print("Error: missing " + str(ke))
+ self.apply_config_cmd(cmd)
+ elif cmd.module == command_sets.EXECUTE_MODULE_NAME:
+ self.apply_execute_cmd(cmd)
else:
self.apply_cmd(cmd)
@@ -574,6 +572,14 @@ class BindCmdInterpreter(Cmd):
self._print_correct_usage(err)
except isc.cc.data.DataTypeError as err:
print("Error! ", err)
+ except isc.cc.data.DataTypeError as dte:
+ print("Error: " + str(dte))
+ except isc.cc.data.DataNotFoundError as dnfe:
+ print("Error: " + str(dnfe))
+ except isc.cc.data.DataAlreadyPresentError as dape:
+ print("Error: " + str(dape))
+ except KeyError as ke:
+ print("Error: missing " + str(ke))
def _print_correct_usage(self, ept):
if isinstance(ept, CmdUnknownModuleSyntaxError):
@@ -726,6 +732,84 @@ class BindCmdInterpreter(Cmd):
self.location = new_location
+ def apply_execute_cmd(self, command):
+ '''Handles the 'execute' command, which executes a number of
+ (preset) statements. The command set to execute is either
+ read from a file (e.g. 'execute file <file>'.) or one
+ of the sets as defined in command_sets.py'''
+ if command.command == 'file':
+ try:
+ with open(command.params['filename']) as command_file:
+ commands = command_file.readlines()
+ except IOError as ioe:
+ print("Error: " + str(ioe))
+ return
+ elif command_sets.has_command_set(command.command):
+ commands = command_sets.get_commands(command.command)
+ else:
+ # Should not be reachable; parser should've caught this
+ raise Exception("Unknown execute command type " + command.command)
+
+ # We have our set of commands now, depending on whether 'show' was
+ # specified, show or execute them
+ if 'show' in command.params and command.params['show'] == 'show':
+ self.__show_execute_commands(commands)
+ else:
+ self.__apply_execute_commands(commands)
+
+ def __show_execute_commands(self, commands):
+ '''Prints the command list without executing them'''
+ for line in commands:
+ print(line.strip())
+
+ def __apply_execute_commands(self, commands):
+ '''Applies the configuration commands from the given iterator.
+ This is the method that catches, comments, echo statements, and
+ other directives. All commands not filtered by this method are
+ interpreted as if they are directly entered in an active session.
+ Lines starting with any of the following characters are not
+ passed directly:
+ # - These are comments
+ ! - These are directives
+ !echo: print the rest of the line
+ !verbose on/off: print the commands themselves too
+ Unknown directives are ignored (with a warning)
+ The execution is stopped if there are any errors.
+ '''
+ verbose = False
+ try:
+ for line in commands:
+ line = line.strip()
+ if verbose:
+ print(line)
+ if line.startswith('#') or len(line) == 0:
+ continue
+ elif line.startswith('!'):
+ if re.match('^!echo ', line, re.I) and len(line) > 6:
+ print(line[6:])
+ elif re.match('^!verbose\s+on\s*$', line, re.I):
+ verbose = True
+ elif re.match('^!verbose\s+off$', line, re.I):
+ verbose = False
+ else:
+ print("Warning: ignoring unknown directive: " + line)
+ else:
+ cmd = BindCmdParse(line)
+ self._validate_cmd(cmd)
+ self._handle_cmd(cmd)
+ except (isc.config.ModuleCCSessionError,
+ IOError, http.client.HTTPException,
+ BindCtlException, isc.cc.data.DataTypeError,
+ isc.cc.data.DataNotFoundError,
+ isc.cc.data.DataAlreadyPresentError,
+ KeyError) as err:
+ print('Error: ', err)
+ print()
+ print('Depending on the contents of the script, and which')
+ print('commands it has called, there can be committed and')
+ print('local changes. It is advised to check your settings,')
+ print('and revert local changes with "config revert".')
+
def apply_cmd(self, cmd):
'''Handles a general module command'''
url = '/' + cmd.module + '/' + cmd.command
diff --git a/src/bin/bindctl/bindctl_main.py.in b/src/bin/bindctl/bindctl_main.py.in
index 58c03eb..1685355 100755
--- a/src/bin/bindctl/bindctl_main.py.in
+++ b/src/bin/bindctl/bindctl_main.py.in
@@ -22,6 +22,7 @@ import sys; sys.path.append ('@@PYTHONPATH@@')
from bindctl.moduleinfo import *
from bindctl.bindcmd import *
+from bindctl import command_sets
import pprint
from optparse import OptionParser, OptionValueError
import isc.util.process
@@ -146,5 +147,6 @@ if __name__ == '__main__':
tool = BindCmdInterpreter(server_addr, pem_file=options.cert_chain,
csv_file_dir=options.csv_file_dir)
prepare_config_commands(tool)
+ command_sets.prepare_execute_commands(tool)
result = tool.run()
sys.exit(result)
diff --git a/src/bin/bindctl/command_sets.py b/src/bin/bindctl/command_sets.py
new file mode 100644
index 0000000..9e2c2ef
--- /dev/null
+++ b/src/bin/bindctl/command_sets.py
@@ -0,0 +1,95 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# This file provides a built-in set of 'execute' commands, for common
+# functions, such as adding an initial auth server.
+# By calling the function prepare_execute_commands, the
+# commands in the command_sets map are added to the virtual
+# component called 'execute'. This is done in bindctl_main.
+
+from bindctl.moduleinfo import *
+# The name of the 'virtual' command set execution module in bindctl
+EXECUTE_MODULE_NAME = 'execute'
+
+# This is a map of command names to lists
+# Each element in the set should itself be a dict containing:
+# 'description': A string with a description of the command set
+# 'commands': A list of bindctl commands
+command_sets = {
+ 'init_authoritative_server': {
+ 'description':
+ 'Configure and run a basic Authoritative server, with default '+
+ 'SQLite3 backend, and xfrin and xfrout functionality',
+ 'commands':
+ [
+ '!echo adding Authoritative server component',
+ 'config add /Boss/components b10-auth',
+ 'config set /Boss/components/b10-auth/kind needed',
+ 'config set /Boss/components/b10-auth/special auth',
+ '!echo adding Xfrin component',
+ 'config add /Boss/components b10-xfrin',
+ 'config set /Boss/components/b10-xfrin/address Xfrin',
+ 'config set /Boss/components/b10-xfrin/kind dispensable',
+ '!echo adding Xfrout component',
+ 'config add /Boss/components b10-xfrout',
+ 'config set /Boss/components/b10-xfrout/address Xfrout',
+ 'config set /Boss/components/b10-xfrout/kind dispensable',
+ '!echo adding Zone Manager component',
+ 'config add /Boss/components b10-zonemgr',
+ 'config set /Boss/components/b10-zonemgr/address Zonemgr',
+ 'config set /Boss/components/b10-zonemgr/kind dispensable',
+ '!echo Components added. Please enter "config commit" to',
+ '!echo finalize initial setup and run the components.'
+ ]
+ }
+}
+
+def has_command_set(name):
+ return name in command_sets
+
+def get_commands(name):
+ return command_sets[name]['commands']
+
+def get_description(name):
+ return command_sets[name]['description']
+
+# For each
+def prepare_execute_commands(tool):
+ """This function is called by bindctl_main, and sets up the commands
+ defined here for use in bindctl."""
+ # common parameter
+ param_show = ParamInfo(name="show", type="string", optional=True,
+ desc="Show the list of commands without executing them")
+
+ # The command module
+ module = ModuleInfo(name=EXECUTE_MODULE_NAME,
+ desc="Execute a given set of commands")
+
+ # Command to execute a file
+ cmd = CommandInfo(name="file", desc="Read commands from file")
+ param = ParamInfo(name="filename", type="string", optional=False,
+ desc="File to read the set of commands from.")
+ cmd.add_param(param)
+ cmd.add_param(param_show)
+ module.add_command(cmd)
+
+ # and loop through all command sets defined above
+ for name in command_sets:
+ cmd = CommandInfo(name=name, desc=get_description(name))
+ cmd.add_param(param_show)
+ module.add_command(cmd)
+
+ tool.add_module_info(module)
+
diff --git a/src/bin/bindctl/moduleinfo.py b/src/bin/bindctl/moduleinfo.py
index 6e41dce..6c3a304 100644
--- a/src/bin/bindctl/moduleinfo.py
+++ b/src/bin/bindctl/moduleinfo.py
@@ -57,8 +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_basic_info(self):
+ if self.is_optional:
+ opt_str = "optional"
+ else:
+ opt_str = "mandatory"
+ return "%s (%s, %s)" % (self.name, self.type, opt_str)
def get_desc(self):
return self.desc
@@ -155,37 +159,24 @@ class CommandInfo:
"""Prints the help info for this command to stdout"""
print("Command ", self)
print("\t\thelp (Get help for command)")
-
+
params = self.params.copy()
del params["help"]
if len(params) == 0:
- print("No parameters for the command")
+ print("This command has no parameters")
return
-
- print("\nMandatory parameters:")
- mandatory_infos = []
- for info in params.values():
- if not info.is_optional:
- 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("\nOptional parameters:")
- for info in optional_infos:
- print(" %s" % info.get_name())
- print(textwrap.fill(info.get_desc(),
+ print("Parameters:")
+ for info in params.values():
+ print(" %s" % info.get_basic_info())
+ description = info.get_desc()
+ if description != "":
+ print(textwrap.fill(description,
initial_indent=" ",
subsequent_indent=" ",
width=70))
-
class ModuleInfo:
"""Define the information of one module, include module name,
module supporting commands.
diff --git a/src/bin/bindctl/tests/.gitignore b/src/bin/bindctl/tests/.gitignore
new file mode 100644
index 0000000..284bacc
--- /dev/null
+++ b/src/bin/bindctl/tests/.gitignore
@@ -0,0 +1 @@
+/bindctl_test
diff --git a/src/bin/bindctl/tests/bindctl_test.py b/src/bin/bindctl/tests/bindctl_test.py
index cef35dc..1ddb916 100644
--- a/src/bin/bindctl/tests/bindctl_test.py
+++ b/src/bin/bindctl/tests/bindctl_test.py
@@ -180,12 +180,10 @@ class TestCmdSyntax(unittest.TestCase):
self.my_assert_raise(isc.cc.data.DataTypeError, "zone set zone_name ='cn', port='cn'")
self.no_assert_raise("zone reload_all")
-
def testCmdUnknownModuleSyntaxError(self):
self.my_assert_raise(CmdUnknownModuleSyntaxError, "zoned d")
self.my_assert_raise(CmdUnknownModuleSyntaxError, "dd dd ")
-
def testCmdUnknownCmdSyntaxError(self):
self.my_assert_raise(CmdUnknownCmdSyntaxError, "zone dd")
@@ -198,6 +196,7 @@ class TestCmdSyntax(unittest.TestCase):
def testCmdUnknownParamSyntaxError(self):
self.my_assert_raise(CmdUnknownParamSyntaxError, "zone load zone_d='cn'")
self.my_assert_raise(CmdUnknownParamSyntaxError, "zone reload_all zone_name = 'cn'")
+ self.my_assert_raise(CmdUnknownParamSyntaxError, "zone help a b c")
class TestModuleInfo(unittest.TestCase):
@@ -366,10 +365,20 @@ class TestConfigCommands(unittest.TestCase):
self.assertEqual((5, MultiConfigData.LOCAL),
self.tool.config_data.get_value("/foo/an_int"))
+ cmd = cmdparse.BindCmdParse("config unset identifier=\"foo/an_int\"")
+ self.tool.apply_config_cmd(cmd)
+
+ self.assertEqual((1, MultiConfigData.DEFAULT),
+ 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)
+ cmd = cmdparse.BindCmdParse("config unset identifier=\"foo/bar\"")
+ 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)
diff --git a/src/bin/cfgmgr/.gitignore b/src/bin/cfgmgr/.gitignore
new file mode 100644
index 0000000..aad54f4
--- /dev/null
+++ b/src/bin/cfgmgr/.gitignore
@@ -0,0 +1,2 @@
+/b10-cfgmgr
+/b10-cfgmgr.py
diff --git a/src/bin/cfgmgr/b10-cfgmgr.py.in b/src/bin/cfgmgr/b10-cfgmgr.py.in
index 2ccc430..760b6d8 100755
--- a/src/bin/cfgmgr/b10-cfgmgr.py.in
+++ b/src/bin/cfgmgr/b10-cfgmgr.py.in
@@ -49,6 +49,10 @@ def parse_options(args=sys.argv[1:], Parser=OptionParser):
help="Configuration database filename " +
"(default=" + DEFAULT_CONFIG_FILE + ")",
default=DEFAULT_CONFIG_FILE)
+ parser.add_option("--clear-config", action="store_true",
+ dest="clear_config", default=False,
+ help="Back up the configuration file and start with " +
+ "a clean one")
(options, args) = parser.parse_args(args)
if args:
parser.error("No non-option arguments allowed")
@@ -85,7 +89,8 @@ def main():
options = parse_options()
global cm
try:
- cm = ConfigManager(options.data_path, options.config_file)
+ cm = ConfigManager(options.data_path, options.config_file,
+ None, options.clear_config)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
cm.read_config()
diff --git a/src/bin/cfgmgr/tests/.gitignore b/src/bin/cfgmgr/tests/.gitignore
new file mode 100644
index 0000000..dcdab2d
--- /dev/null
+++ b/src/bin/cfgmgr/tests/.gitignore
@@ -0,0 +1 @@
+/b10-cfgmgr_test.py
diff --git a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
index ea5fc8b..ca91c9c 100644
--- a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
+++ b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
@@ -24,12 +24,13 @@ import bind10_config
from isc.testutils.parse_args import OptsError, TestOptParser
class MyConfigManager:
- def __init__(self, path, filename):
+ def __init__(self, path, filename, session=None, rename_config_file=False):
self._path = path
self.read_config_called = False
self.notify_boss_called = False
self.run_called = False
self.write_config_called = False
+ self.rename_config_called = False
self.running = True
self.virtual_modules = []
@@ -45,6 +46,9 @@ class MyConfigManager:
def write_config(self):
self.write_config_called = True
+ def rename_config_file(self, ofile, nfile):
+ self.rename_config_called = True
+
def set_virtual_module(self, spec, function):
self.virtual_modules.append((spec, function))
@@ -90,6 +94,7 @@ class TestConfigManagerStartup(unittest.TestCase):
self.assertTrue(self.loaded_plugins)
# if there are no changes, config is not written
self.assertFalse(b.cm.write_config_called)
+ self.assertFalse(b.cm.rename_config_called)
self.assertTrue(b.cm.running)
b.signal_handler(None, None)
@@ -187,6 +192,14 @@ class TestParseArgs(unittest.TestCase):
self.assertRaises(OptsError, b.parse_options, ['--config-filename'],
TestOptParser)
+ def test_clear_config(self):
+ b = __import__("b10-cfgmgr")
+ parsed = b.parse_options([], TestOptParser)
+ self.assertFalse(parsed.clear_config)
+ parsed = b.parse_options(['--clear-config'], TestOptParser)
+ self.assertTrue(parsed.clear_config)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/src/bin/cmdctl/.gitignore b/src/bin/cmdctl/.gitignore
new file mode 100644
index 0000000..a194135
--- /dev/null
+++ b/src/bin/cmdctl/.gitignore
@@ -0,0 +1,5 @@
+/b10-cmdctl
+/cmdctl.py
+/cmdctl.spec
+/cmdctl.spec.pre
+/run_b10-cmdctl.sh
diff --git a/src/bin/cmdctl/b10-cmdctl.8 b/src/bin/cmdctl/b10-cmdctl.8
index c8c938b..0b478fe 100644
--- a/src/bin/cmdctl/b10-cmdctl.8
+++ b/src/bin/cmdctl/b10-cmdctl.8
@@ -2,12 +2,12 @@
.\" Title: b10-cmdctl
.\" Author: [see the "AUTHORS" section]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: March 9, 2010
+.\" Date: February 28, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-CMDCTL" "8" "March 9, 2010" "BIND10" "BIND10"
+.TH "B10\-CMDCTL" "8" "February 28, 2012" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -70,6 +70,33 @@ Enable verbose mode\&.
.RS 4
Display the version number and exit\&.
.RE
+.SH "CONFIGURATION AND COMMANDS"
+.PP
+The configurable settings are:
+.PP
+
+\fIaccounts_file\fR
+defines the path to the user accounts database\&. The default is
+/usr/local/etc/bind10\-devel/cmdctl\-accounts\&.csv\&.
+.PP
+
+\fIcert_file\fR
+defines the path to the PEM certificate file\&. The default is
+/usr/local/etc/bind10\-devel/cmdctl\-certfile\&.pem\&.
+.PP
+
+\fIkey_file\fR
+defines the path to the PEM private key file\&. The default is
+/usr/local/etc/bind10\-devel/cmdctl\-keyfile\&.pem\&.
+.PP
+The configuration command is:
+.PP
+
+\fBshutdown\fR
+exits
+\fBb10\-cmdctl\fR\&. This has an optional
+\fIpid\fR
+argument to select the process ID to stop\&. (Note that the BIND 10 boss process may restart this service if configured\&.)
.SH "FILES"
.PP
/usr/local/etc/bind10\-devel/cmdctl\-accounts\&.csv
@@ -93,5 +120,5 @@ The
daemon was initially designed and coded by Zhang Likun of CNNIC\&.
.SH "COPYRIGHT"
.br
-Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")
+Copyright \(co 2010-2012 Internet Systems Consortium, Inc. ("ISC")
.br
diff --git a/src/bin/cmdctl/b10-cmdctl.xml b/src/bin/cmdctl/b10-cmdctl.xml
index 06953a4..e01d5a2 100644
--- a/src/bin/cmdctl/b10-cmdctl.xml
+++ b/src/bin/cmdctl/b10-cmdctl.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-2012 Internet Systems Consortium, Inc. ("ISC")
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>March 9, 2010</date>
+ <date>February 28, 2012</date>
</refentryinfo>
<refmeta>
@@ -37,7 +37,7 @@
<docinfo>
<copyright>
- <year>2010</year>
+ <year>2010-2012</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
@@ -138,6 +138,50 @@
</refsect1>
<refsect1>
+ <title>CONFIGURATION AND COMMANDS</title>
+ <para>
+ The configurable settings are:
+ </para>
+
+ <para>
+ <varname>accounts_file</varname> defines the path to the
+ user accounts database.
+ The default is
+ <filename>/usr/local/etc/bind10-devel/cmdctl-accounts.csv</filename>.
+ </para>
+
+ <para>
+ <varname>cert_file</varname> defines the path to the
+ PEM certificate file.
+ The default is
+ <filename>/usr/local/etc/bind10-devel/cmdctl-certfile.pem</filename>.
+ </para>
+
+ <para>
+ <varname>key_file</varname> defines the path to the PEM private key
+ file.
+ The default is
+ <filename>/usr/local/etc/bind10-devel/cmdctl-keyfile.pem</filename>.
+ </para>
+
+<!-- TODO: formating -->
+ <para>
+ The configuration command is:
+ </para>
+
+<!-- NOTE: print_settings is not documented since I think will be removed -->
+
+ <para>
+ <command>shutdown</command> exits <command>b10-cmdctl</command>.
+ This has an optional <varname>pid</varname> argument to
+ select the process ID to stop.
+ (Note that the BIND 10 boss process may restart this service
+ if configured.)
+ </para>
+
+ </refsect1>
+
+ <refsect1>
<title>FILES</title>
<!-- TODO: replace /usr/local -->
<!-- TODO: permissions -->
diff --git a/src/bin/cmdctl/cmdctl.py.in b/src/bin/cmdctl/cmdctl.py.in
index ff221db..74fc364 100755
--- a/src/bin/cmdctl/cmdctl.py.in
+++ b/src/bin/cmdctl/cmdctl.py.in
@@ -310,12 +310,25 @@ class CommandControl():
def command_handler(self, command, args):
answer = ccsession.create_answer(0)
if command == ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE:
+ # The 'value' of a specification update can be either
+ # a specification, or None. In the first case, simply
+ # set it. If it is None, delete the module if it is
+ # known.
with self._lock:
- self.modules_spec[args[0]] = args[1]
+ if args[1] is None:
+ if args[0] in self.modules_spec:
+ del self.modules_spec[args[0]]
+ else:
+ answer = ccsession.create_answer(1,
+ 'No such module: ' +
+ args[0])
+ else:
+ self.modules_spec[args[0]] = args[1]
elif command == ccsession.COMMAND_SHUTDOWN:
#When cmdctl get 'shutdown' command from boss,
#shutdown the outer httpserver.
+ self._module_cc.send_stopping()
self._httpserver.shutdown()
self._serving = False
diff --git a/src/bin/cmdctl/cmdctl.spec.pre.in b/src/bin/cmdctl/cmdctl.spec.pre.in
index 537b678..d04e2e3 100644
--- a/src/bin/cmdctl/cmdctl.spec.pre.in
+++ b/src/bin/cmdctl/cmdctl.spec.pre.in
@@ -31,7 +31,13 @@
{
"command_name": "shutdown",
"command_description": "shutdown cmdctl",
- "command_args": []
+ "command_args": [
+ {
+ "item_name": "pid",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
}
]
}
diff --git a/src/bin/cmdctl/tests/.gitignore b/src/bin/cmdctl/tests/.gitignore
new file mode 100644
index 0000000..ab9dfef
--- /dev/null
+++ b/src/bin/cmdctl/tests/.gitignore
@@ -0,0 +1 @@
+/cmdctl_test
diff --git a/src/bin/cmdctl/tests/cmdctl_test.py b/src/bin/cmdctl/tests/cmdctl_test.py
index 3103f47..5fdabb4 100644
--- a/src/bin/cmdctl/tests/cmdctl_test.py
+++ b/src/bin/cmdctl/tests/cmdctl_test.py
@@ -345,6 +345,40 @@ class TestCommandControl(unittest.TestCase):
self.assertEqual(rcode, 0)
self.assertTrue(msg != None)
+ def test_command_handler_spec_update(self):
+ # Should not be present
+ self.assertFalse("foo" in self.cmdctl.modules_spec)
+
+ answer = self.cmdctl.command_handler(
+ ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE, [ "foo", {} ])
+ rcode, msg = ccsession.parse_answer(answer)
+ self.assertEqual(rcode, 0)
+ self.assertEqual(msg, None)
+
+ # Should now be present
+ self.assertTrue("foo" in self.cmdctl.modules_spec)
+
+ # When sending specification 'None', it should be removed
+ answer = self.cmdctl.command_handler(
+ ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE, [ "foo", None ])
+ rcode, msg = ccsession.parse_answer(answer)
+ self.assertEqual(rcode, 0)
+ self.assertEqual(msg, None)
+
+ # Should no longer be present
+ self.assertFalse("foo" in self.cmdctl.modules_spec)
+
+ # Don't store 'None' if it wasn't there in the first place!
+ answer = self.cmdctl.command_handler(
+ ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE, [ "foo", None ])
+ rcode, msg = ccsession.parse_answer(answer)
+ self.assertEqual(rcode, 1)
+ self.assertEqual(msg, "No such module: foo")
+
+ # Should still not present
+ self.assertFalse("foo" in self.cmdctl.modules_spec)
+
+
def test_check_config_handler(self):
answer = self.cmdctl.config_handler({'non-exist': 123})
self._check_answer(answer, 1, 'unknown config item: non-exist')
diff --git a/src/bin/dbutil/.gitignore b/src/bin/dbutil/.gitignore
new file mode 100644
index 0000000..abb63d5
--- /dev/null
+++ b/src/bin/dbutil/.gitignore
@@ -0,0 +1,3 @@
+/b10-dbutil
+/dbutil.py
+/run_dbutil.sh
diff --git a/src/bin/dbutil/Makefile.am b/src/bin/dbutil/Makefile.am
new file mode 100644
index 0000000..e05055f
--- /dev/null
+++ b/src/bin/dbutil/Makefile.am
@@ -0,0 +1,39 @@
+SUBDIRS = . tests
+
+bin_SCRIPTS = b10-dbutil
+man_MANS = b10-dbutil.8
+
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/dbutil_messages.py
+pylogmessagedir = $(pyexecdir)/isc/log_messages/
+
+EXTRA_DIST = $(man_MANS) b10-dbutil.xml dbutil_messages.mes
+
+noinst_SCRIPTS = run_dbutil.sh
+
+CLEANFILES = b10-dbutil b10-dbutil.pyc
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/dbutil_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/dbutil_messages.pyc
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/dbutil_messages.pyo
+
+if ENABLE_MAN
+
+b10-dbutil.8: b10-dbutil.xml
+ xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-dbutil.xml
+
+endif
+
+# Define rule to build logging source files from message file
+$(PYTHON_LOGMSGPKG_DIR)/work/dbutil_messages.py : dbutil_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message \
+ -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/dbutil_messages.mes
+
+b10-dbutil: dbutil.py $(PYTHON_LOGMSGPKG_DIR)/work/dbutil_messages.py
+ $(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
+ -e "s|@@SYSCONFDIR@@|@sysconfdir@|" \
+ -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" dbutil.py >$@
+ chmod a+x $@
+
+CLEANDIRS = __pycache__
+
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/bin/dbutil/b10-dbutil.8 b/src/bin/dbutil/b10-dbutil.8
new file mode 100644
index 0000000..437a69d
--- /dev/null
+++ b/src/bin/dbutil/b10-dbutil.8
@@ -0,0 +1,92 @@
+'\" t
+.\" Title: b10-dbutil
+.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
+.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
+.\" Date: March 20, 2012
+.\" Manual: BIND10
+.\" Source: BIND10
+.\" Language: English
+.\"
+.TH "B10\-DBUTIL" "8" "March 20, 2012" "BIND10" "BIND10"
+.\" -----------------------------------------------------------------
+.\" * Define some portability stuff
+.\" -----------------------------------------------------------------
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.\" http://bugs.debian.org/507673
+.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\" -----------------------------------------------------------------
+.\" * set default formatting
+.\" -----------------------------------------------------------------
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.\" -----------------------------------------------------------------
+.\" * MAIN CONTENT STARTS HERE *
+.\" -----------------------------------------------------------------
+.SH "NAME"
+b10-dbutil \- Zone Database Maintenance Utility
+.SH "SYNOPSIS"
+.HP \w'\fBb10\-dbutil\ \-\-check\fR\ 'u
+\fBb10\-dbutil \-\-check\fR [\-\-verbose] [\-\-quiet] [\fIdbfile\fR]
+.HP \w'\fBb10\-dbutil\ \-\-upgrade\fR\ 'u
+\fBb10\-dbutil \-\-upgrade\fR [\-\-noconfirm] [\-\-verbose] [\-\-quiet] [\fIdbfile\fR]
+.SH "DESCRIPTION"
+.PP
+The
+\fBb10\-dbutil\fR
+utility is a general administration utility for SQL databases\&. (Currently only SQLite is supported by BIND 10\&.) It can report the current verion of the schema, and upgrade an existing database to the latest version of the schema\&.
+.PP
+
+\fBb10\-dbutil\fR
+operates in one of two modes, check mode or upgrade mode\&.
+.PP
+In check mode (\fBb10\-dbutil \-\-check\fR), the utility reads the version of the database schema from the database and prints it\&. It will tell you whether the schema is at the latest version supported by BIND 10\&. Exit status is 0 if the schema is at the correct version, 1 if the schema is at an older version, 2 if the schema is at a version not yet supported by this version of b10\-dbutil\&. Any higher value indicates an error during command\-line parsing or execution\&.
+.PP
+When the upgrade function is selected (\fBb10\-dbutil \-\-upgrade\fR), the utility takes a copy of the database, then upgrades it to the latest version of the schema\&. The contents of the database remain intact\&. (The backup file is a file in the same directory as the database file\&. It has the same name, with "\&.backup" appended to it\&. If a file of that name already exists, the file will have the suffix "\&.backup\-1"\&. If that exists, the file will be suffixed "\&.backup\-2", and so on)\&. Exit status is 0 if the upgrade is either succesful or aborted by the user, and non\-zero if there is an error\&.
+.PP
+When upgrading the database, it is
+\fIstrongly\fR
+recommended that BIND 10 not be running while the upgrade is in progress\&.
+.SH "ARGUMENTS"
+.PP
+The arguments are as follows:
+.PP
+\fB\-\-check\fR
+.RS 4
+Selects the version check function, which reports the current version of the database\&. This is incompatible with the \-\-upgrade option\&.
+.RE
+.PP
+\fB\-\-noconfirm\fR
+.RS 4
+Only valid with \-\-upgrade, this disables the prompt\&. Normally the utility will print a warning that an upgrade is about to take place and request that you type "Yes" to continue\&. If this switch is given on the command line, no prompt will be issued: the utility will just perform the upgrade\&.
+.RE
+.PP
+\fB\-\-upgrade\fR
+.RS 4
+Selects the upgrade function, which upgrades the database to the latest version of the schema\&. This is incompatible with the \-\-upgrade option\&.
+.sp
+The upgrade function will upgrade a BIND 10 database \- no matter how old the schema \- preserving all data\&. A backup file is created before the upgrade (with the same name as the database, but with "\&.backup" suffixed to it)\&. If the upgrade fails, this file can be copied back to restore the original database\&.
+.RE
+.PP
+\fB\-\-verbose\fR
+.RS 4
+Enable verbose mode\&. Each SQL command issued by the utility will be printed to stderr before it is executed\&.
+.RE
+.PP
+\fB\-\-quiet\fR
+.RS 4
+Enable quiet mode\&. No output is printed, except errors during command\-line argument parsing, or the user confirmation dialog\&.
+.RE
+.PP
+\fB\fIdbfile\fR\fR
+.RS 4
+Name of the database file to check of upgrade\&.
+.RE
+.SH "COPYRIGHT"
+.br
+Copyright \(co 2012 Internet Systems Consortium, Inc. ("ISC")
+.br
diff --git a/src/bin/dbutil/b10-dbutil.xml b/src/bin/dbutil/b10-dbutil.xml
new file mode 100644
index 0000000..c1c0dee
--- /dev/null
+++ b/src/bin/dbutil/b10-dbutil.xml
@@ -0,0 +1,192 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+ [<!ENTITY mdash "—">]>
+<!--
+ - Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+ -
+ - Permission to use, copy, modify, and/or distribute this software for any
+ - purpose with or without fee is hereby granted, provided that the above
+ - copyright notice and this permission notice appear in all copies.
+ -
+ - THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ - PERFORMANCE OF THIS SOFTWARE.
+-->
+
+<refentry>
+
+ <refentryinfo>
+ <date>March 20, 2012</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>b10-dbutil</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo>BIND10</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>b10-dbutil</refname>
+ <refpurpose>Zone Database Maintenance Utility</refpurpose>
+ </refnamediv>
+
+ <docinfo>
+ <copyright>
+ <year>2012</year>
+ <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+ </copyright>
+ </docinfo>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>b10-dbutil --check</command>
+ <arg>--verbose</arg>
+ <arg>--quiet</arg>
+ <arg><replaceable choice='req'>dbfile</replaceable></arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>b10-dbutil --upgrade</command>
+ <arg>--noconfirm</arg>
+ <arg>--verbose</arg>
+ <arg>--quiet</arg>
+ <arg><replaceable choice='req'>dbfile</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ The <command>b10-dbutil</command> utility is a general administration
+ utility for SQL databases. (Currently only SQLite is supported by
+ BIND 10.) It can report the current verion of the schema, and upgrade
+ an existing database to the latest version of the schema.
+ </para>
+
+ <para>
+ <command>b10-dbutil</command> operates in one of two modes, check mode
+ or upgrade mode.
+ </para>
+
+ <para>
+ In check mode (<command>b10-dbutil --check</command>), the
+ utility reads the version of the database schema from the database
+ and prints it. It will tell you whether the schema is at the latest
+ version supported by BIND 10. Exit status is 0 if the schema is at
+ the correct version, 1 if the schema is at an older version, 2 if
+ the schema is at a version not yet supported by this version of
+ b10-dbutil. Any higher value indicates an error during command-line
+ parsing or execution.
+ </para>
+
+ <para>
+ When the upgrade function is selected
+ (<command>b10-dbutil --upgrade</command>), the
+ utility takes a copy of the database, then upgrades it to the latest
+ version of the schema. The contents of the database remain intact.
+ (The backup file is a file in the same directory as the database
+ file. It has the same name, with ".backup" appended to it. If a
+ file of that name already exists, the file will have the suffix
+ ".backup-1". If that exists, the file will be suffixed ".backup-2",
+ and so on). Exit status is 0 if the upgrade is either succesful or
+ aborted by the user, and non-zero if there is an error.
+ </para>
+
+ <para>
+ When upgrading the database, it is <emphasis>strongly</emphasis>
+ recommended that BIND 10 not be running while the upgrade is in
+ progress.
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>ARGUMENTS</title>
+
+ <para>The arguments are as follows:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <option>--check</option>
+ </term>
+ <listitem>
+ <para>Selects the version check function, which reports the
+ current version of the database. This is incompatible
+ with the --upgrade option.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>--noconfirm</option>
+ </term>
+ <listitem>
+ <para>Only valid with --upgrade, this disables the prompt.
+ Normally the utility will print a warning that an upgrade is
+ about to take place and request that you type "Yes" to continue.
+ If this switch is given on the command line, no prompt will
+ be issued: the utility will just perform the upgrade.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>--upgrade</option>
+ </term>
+ <listitem>
+ <para>Selects the upgrade function, which upgrades the database
+ to the latest version of the schema. This is incompatible
+ with the --upgrade option.
+ </para>
+ <para>
+ The upgrade function will upgrade a BIND 10 database - no matter how
+ old the schema - preserving all data. A backup file is created
+ before the upgrade (with the same name as the database, but with
+ ".backup" suffixed to it). If the upgrade fails, this file can
+ be copied back to restore the original database.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>--verbose</option>
+ </term>
+ <listitem>
+ <para>Enable verbose mode. Each SQL command issued by the
+ utility will be printed to stderr before it is executed.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>--quiet</option>
+ </term>
+ <listitem>
+ <para>Enable quiet mode. No output is printed, except errors during
+ command-line argument parsing, or the user confirmation dialog.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option><replaceable choice='req'>dbfile</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Name of the database file to check of upgrade.
+ </para>
+ </listitem>
+ </varlistentry>
+
+
+ </variablelist>
+ </refsect1>
+</refentry>
diff --git a/src/bin/dbutil/dbutil.py.in b/src/bin/dbutil/dbutil.py.in
new file mode 100755
index 0000000..81f351e
--- /dev/null
+++ b/src/bin/dbutil/dbutil.py.in
@@ -0,0 +1,608 @@
+#!@PYTHON@
+
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+ at file Dabase Utilities
+
+This file holds the "dbutil" program, a general utility program for doing
+management of the BIND 10 database. There are two modes of operation:
+
+ b10-dbutil --check [--verbose] database
+ b10-dbutil --upgrade [--noconfirm] [--verbose] database
+
+The first form checks the version of the given database. The second form
+upgrades the database to the latest version of the schema, omitting the
+warning prompt if --noconfirm is given.
+
+For maximum safety, prior to the upgrade a backup database is created.
+The is the database name with ".backup" appended to it (or ".backup-n" if
+".backup" already exists). This is used to restore the database if the
+upgrade fails.
+"""
+
+# Exit codes
+# These are defined here because one of them is already used before most
+# of the import statements.
+EXIT_SUCCESS = 0
+EXIT_NEED_UPDATE = 1
+EXIT_VERSION_TOO_HIGH = 2
+EXIT_COMMAND_ERROR = 3
+EXIT_READ_ERROR = 4
+EXIT_UPGRADE_ERROR = 5
+EXIT_UNCAUGHT_EXCEPTION = 6
+
+import sys; sys.path.append("@@PYTHONPATH@@")
+
+# Normally, python exits with a status code of 1 on uncaught exceptions
+# Since we reserve exit status 1 for 'database needs upgrade', we
+# override the excepthook to exit with a different status
+def my_except_hook(a, b, c):
+ sys.__excepthook__(a,b,c)
+ sys.exit(EXIT_UNCAUGHT_EXCEPTION)
+sys.excepthook = my_except_hook
+
+import os, sqlite3, shutil
+from optparse import OptionParser
+import isc.util.process
+import isc.log
+from isc.log_messages.dbutil_messages import *
+
+isc.log.init("b10-dbutil")
+logger = isc.log.Logger("dbutil")
+isc.util.process.rename()
+
+TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
+
+
+# @brief Version String
+# This is the version displayed to the user. It comprises the module name,
+# the module version number, and the overall BIND 10 version number (set in
+# configure.ac)
+VERSION = "b10-dbutil 20120319 (BIND 10 @PACKAGE_VERSION@)"
+
+# @brief Statements to Update the Database
+# These are in the form of a list of dictionaries, each of which contains the
+# information to perform an incremental upgrade from one version of the
+# database to the next. The information is:
+#
+# a) from: (major, minor) version that the database is expected to be at
+# to perform this upgrade.
+# b) to: (major, minor) version of the database to which this set of statements
+# upgrades the database to. (This is used for documentation purposes,
+# and to update the schema_version table when the upgrade is complete.)
+# c) statements: List of SQL statments to perform the upgrade.
+#
+# The incremental upgrades are performed one after the other. If the version
+# of the database does not exactly match that required for the incremental
+# upgrade, the upgrade is skipped. For this reason, the list must be in
+# ascending order (e.g. upgrade 1.0 to 2.0, 2.0 to 2.1, 2.1 to 2.2 etc.).
+#
+# Note that apart from the 1.0 to 2.0 upgrade, no upgrade need alter the
+# schema_version table: that is done by the upgrade process using the
+# information in the "to" field.
+UPGRADES = [
+ {'from': (1, 0), 'to': (2, 0),
+ 'statements': [
+
+ # Move to the latest "V1" state of the database if not there
+ # already.
+ "CREATE TABLE IF NOT EXISTS diffs (" +
+ "id INTEGER PRIMARY KEY, " +
+ "zone_id INTEGER NOT NULL," +
+ "version INTEGER NOT NULL, " +
+ "operation INTEGER NOT NULL, " +
+ "name STRING NOT NULL COLLATE NOCASE, " +
+ "rrtype STRING NOT NULL COLLATE NOCASE, " +
+ "ttl INTEGER NOT NULL, " +
+ "rdata STRING NOT NULL)",
+
+ # Within SQLite with can only rename tables and add columns; we
+ # can't drop columns nor can we alter column characteristics.
+ # So the strategy is to rename the table, create the new table,
+ # then copy all data across. This means creating new indexes
+ # as well; these are created after the data has been copied.
+
+ # zones table
+ "DROP INDEX zones_byname",
+ "ALTER TABLE zones RENAME TO old_zones",
+ "CREATE TABLE zones (" +
+ "id INTEGER PRIMARY KEY, " +
+ "name TEXT NOT NULL COLLATE NOCASE, " +
+ "rdclass TEXT NOT NULL COLLATE NOCASE DEFAULT 'IN', " +
+ "dnssec BOOLEAN NOT NULL DEFAULT 0)",
+ "INSERT INTO ZONES " +
+ "SELECT id, name, rdclass, dnssec FROM old_zones",
+ "CREATE INDEX zones_byname ON zones (name)",
+ "DROP TABLE old_zones",
+
+ # records table
+ "DROP INDEX records_byname",
+ "DROP INDEX records_byrname",
+ "ALTER TABLE records RENAME TO old_records",
+ "CREATE TABLE records (" +
+ "id INTEGER PRIMARY KEY, " +
+ "zone_id INTEGER NOT NULL, " +
+ "name TEXT NOT NULL COLLATE NOCASE, " +
+ "rname TEXT NOT NULL COLLATE NOCASE, " +
+ "ttl INTEGER NOT NULL, " +
+ "rdtype TEXT NOT NULL COLLATE NOCASE, " +
+ "sigtype TEXT COLLATE NOCASE, " +
+ "rdata TEXT NOT NULL)",
+ "INSERT INTO records " +
+ "SELECT id, zone_id, name, rname, ttl, rdtype, sigtype, " +
+ "rdata FROM old_records",
+ "CREATE INDEX records_byname ON records (name)",
+ "CREATE INDEX records_byrname ON records (rname)",
+ "CREATE INDEX records_bytype_and_rname ON records (rdtype, rname)",
+ "DROP TABLE old_records",
+
+ # nsec3 table
+ "DROP INDEX nsec3_byhash",
+ "ALTER TABLE nsec3 RENAME TO old_nsec3",
+ "CREATE TABLE nsec3 (" +
+ "id INTEGER PRIMARY KEY, " +
+ "zone_id INTEGER NOT NULL, " +
+ "hash TEXT NOT NULL COLLATE NOCASE, " +
+ "owner TEXT NOT NULL COLLATE NOCASE, " +
+ "ttl INTEGER NOT NULL, " +
+ "rdtype TEXT NOT NULL COLLATE NOCASE, " +
+ "rdata TEXT NOT NULL)",
+ "INSERT INTO nsec3 " +
+ "SELECT id, zone_id, hash, owner, ttl, rdtype, rdata " +
+ "FROM old_nsec3",
+ "CREATE INDEX nsec3_byhash ON nsec3 (hash)",
+ "DROP TABLE old_nsec3",
+
+ # diffs table
+ "ALTER TABLE diffs RENAME TO old_diffs",
+ "CREATE TABLE diffs (" +
+ "id INTEGER PRIMARY KEY, " +
+ "zone_id INTEGER NOT NULL, " +
+ "version INTEGER NOT NULL, " +
+ "operation INTEGER NOT NULL, " +
+ "name TEXT NOT NULL COLLATE NOCASE, " +
+ "rrtype TEXT NOT NULL COLLATE NOCASE, " +
+ "ttl INTEGER NOT NULL, " +
+ "rdata TEXT NOT NULL)",
+ "INSERT INTO diffs " +
+ "SELECT id, zone_id, version, operation, name, rrtype, " +
+ "ttl, rdata FROM old_diffs",
+ "DROP TABLE old_diffs",
+
+ # Schema table. This is updated to include a second column for
+ # future changes. The idea is that if a version of BIND 10 is
+ # written for schema M.N, it should be able to work for all
+ # versions of N; if not, M must be incremented.
+ #
+ # For backwards compatibility, the column holding the major
+ # version number is left named "version".
+ "ALTER TABLE schema_version " +
+ "ADD COLUMN minor INTEGER NOT NULL DEFAULT 0"
+ ]
+ }
+
+# To extend this, leave the above statements in place and add another
+# dictionary to the list. The "from" version should be (2, 0), the "to"
+# version whatever the version the update is to, and the SQL statements are
+# the statements required to perform the upgrade. This way, the upgrade
+# program will be able to upgrade both a V1.0 and a V2.0 database.
+]
+
+class DbutilException(Exception):
+ """
+ @brief Exception class to indicate error exit
+ """
+ pass
+
+class Database:
+ """
+ @brief Database Encapsulation
+
+ Encapsulates the SQL database, both the connection and the cursor. The
+ methods will cause a program exit on any error.
+ """
+ def __init__(self, db_file):
+ """
+ @brief Constructor
+
+ @param db_file Name of the database file
+ """
+ self.connection = None
+ self.cursor = None
+ self.db_file = db_file
+ self.backup_file = None
+
+ def open(self):
+ """
+ @brief Open Database
+
+ Opens the passed file as an sqlite3 database and stores a connection
+ and a cursor.
+ """
+ if not os.path.exists(self.db_file):
+ raise DbutilException("database " + self.db_file +
+ " does not exist");
+
+ try:
+ self.connection = sqlite3.connect(self.db_file)
+ self.connection.isolation_level = None # set autocommit
+ self.cursor = self.connection.cursor()
+ except sqlite3.OperationalError as ex:
+ raise DbutilException("unable to open " + self.db_file +
+ " - " + str(ex))
+
+ def close(self):
+ """
+ @brief Closes the database
+ """
+ if self.connection is not None:
+ self.connection.close()
+
+ def execute(self, statement):
+ """
+ @brief Execute Statement
+
+ Executes the given statement, exiting the program on error.
+
+ @param statement SQL statement to execute
+ """
+ logger.debug(TRACE_BASIC, DBUTIL_EXECUTE, statement)
+
+ try:
+ self.cursor.execute(statement)
+ except Exception as ex:
+ logger.error(DBUTIL_STATEMENT_ERROR, statement, ex)
+ raise DbutilException(str(ex))
+
+ def result(self):
+ """
+ @brief Return result of last execute
+
+ Returns a single row that is the result of the last "execute".
+ """
+ return self.cursor.fetchone()
+
+ def backup(self):
+ """
+ @brief Backup Database
+
+ Attempts to copy the given database file to a backup database, the
+ backup database file being the file name with ".backup" appended.
+ If the ".backup" file exists, a new name is constructed by appending
+ ".backup-n" (n starting at 1) and the action repeated until an
+ unused filename is found.
+
+ @param db_file Database file to backup
+ """
+ if not os.path.exists(self.db_file):
+ raise DbutilException("database " + self.db_file +
+ " does not exist");
+
+ self.backup_file = self.db_file + ".backup"
+ count = 0
+ while os.path.exists(self.backup_file):
+ count = count + 1
+ self.backup_file = self.db_file + ".backup-" + str(count)
+
+ # Do the backup
+ shutil.copyfile(self.db_file, self.backup_file)
+ logger.info(DBUTIL_BACKUP, self.db_file, self.backup_file)
+
+def prompt_user():
+ """
+ @brief Prompt the User
+
+ Explains about the upgrade and requests authorisation to continue.
+
+ @return True if user entered 'Yes', False if 'No'
+ """
+ sys.stdout.write(
+"""You have selected the upgrade option. This will upgrade the schema of the
+selected BIND 10 zone database to the latest version.
+
+The utility will take a copy of the zone database file before executing so, in
+the event of a problem, you will be able to restore the zone database from
+the backup. To ensure that the integrity of this backup, please ensure that
+BIND 10 is not running before continuing.
+""")
+ yes_entered = False
+ no_entered = False
+ while (not yes_entered) and (not no_entered):
+ sys.stdout.write("Enter 'Yes' to proceed with the upgrade, " +
+ "'No' to exit the program: \n")
+ response = sys.stdin.readline()
+ if response.lower() == "yes\n":
+ yes_entered = True
+ elif response.lower() == "no\n":
+ no_entered = True
+ else:
+ sys.stdout.write("Please enter 'Yes' or 'No'\n")
+
+ return yes_entered
+
+
+def version_string(version):
+ """
+ @brief Format Database Version
+
+ Converts a (major, minor) tuple into a 'Vn.m' string.
+
+ @param version Version tuple to convert
+
+ @return Version string
+ """
+ return "V" + str(version[0]) + "." + str(version[1])
+
+
+def compare_versions(first, second):
+ """
+ @brief Compare Versions
+
+ Compares two database version numbers.
+
+ @param first First version number to check (in the form of a
+ "(major, minor)" tuple).
+ @param second Second version number to check (in the form of a
+ "(major, minor)" tuple).
+
+ @return -1, 0, +1 if "first" is <, ==, > "second"
+ """
+ if first == second:
+ return 0
+
+ elif ((first[0] < second[0]) or
+ ((first[0] == second[0]) and (first[1] < second[1]))):
+ return -1
+
+ else:
+ return 1
+
+
+def get_latest_version():
+ """
+ @brief Returns the version to which this utility can upgrade the database
+
+ This is the 'to' version held in the last element of the upgrades list
+ """
+ return UPGRADES[-1]['to']
+
+
+def get_version(db):
+ """
+ @brief Return version of database
+
+ @return Version of database in form (major version, minor version)
+ """
+
+ # Get the version information.
+ db.execute("SELECT * FROM schema_version")
+ result = db.result()
+ if result is None:
+ raise DbutilException("nothing in schema_version table")
+
+ major = result[0]
+ if (major == 1):
+ # If the version number is 1, there will be no "minor" column, so
+ # assume a minor version number of 0.
+ minor = 0
+ else:
+ minor = result[1]
+
+ result = db.result()
+ if result is not None:
+ raise DbutilException("too many rows in schema_version table")
+
+ return (major, minor)
+
+
+def check_version(db):
+ """
+ @brief Check the version
+
+ Checks the version of the database and the latest version, and advises if
+ an upgrade is needed.
+
+ @param db Database object
+
+ returns 0 if the database is up to date
+ returns EXIT_NEED_UPDATE if the database needs updating
+ returns EXIT_VERSION_TOO_HIGH if the database is at a later version
+ than this program knows about
+ These return values are intended to be passed on to sys.exit.
+ """
+ current = get_version(db)
+ latest = get_latest_version()
+
+ match = compare_versions(current, latest)
+ if match == 0:
+ logger.info(DBUTIL_VERSION_CURRENT, version_string(current))
+ logger.info(DBUTIL_CHECK_OK)
+ return EXIT_SUCCESS
+ elif match < 0:
+ logger.info(DBUTIL_VERSION_LOW, version_string(current),
+ version_string(latest))
+ logger.info(DBUTIL_CHECK_UPGRADE_NEEDED)
+ return EXIT_NEED_UPDATE
+ else:
+ logger.warn(DBUTIL_VERSION_HIGH, version_string(current),
+ version_string(get_latest_version()))
+ logger.info(DBUTIL_UPGRADE_DBUTIL)
+ return EXIT_VERSION_TOO_HIGH
+
+def perform_upgrade(db, upgrade):
+ """
+ @brief Perform upgrade
+
+ Performs the upgrade. At the end of the upgrade, updates the schema_version
+ table with the expected version.
+
+ @param db Database object
+ @param upgrade Upgrade dictionary, holding "from", "to" and "statements".
+ """
+ logger.info(DBUTIL_UPGRADING, version_string(upgrade['from']),
+ version_string(upgrade['to']))
+ for statement in upgrade['statements']:
+ db.execute(statement)
+
+ # Update the version information
+ db.execute("DELETE FROM schema_version")
+ db.execute("INSERT INTO schema_version VALUES (" +
+ str(upgrade['to'][0]) + "," + str(upgrade['to'][1]) + ")")
+
+
+def perform_all_upgrades(db):
+ """
+ @brief Performs all the upgrades
+
+ @brief db Database object
+
+ For each upgrade, checks that the database is at the expected version.
+ If so, calls perform_upgrade to update the database.
+ """
+ match = compare_versions(get_version(db), get_latest_version())
+ if match == 0:
+ logger.info(DBUTIL_UPGRADE_NOT_NEEDED)
+
+ elif match > 0:
+ logger.warn(DBUTIL_UPGRADE_NOT_POSSIBLE)
+
+ else:
+ # Work our way through all upgrade increments
+ count = 0
+ for upgrade in UPGRADES:
+ if compare_versions(get_version(db), upgrade['from']) == 0:
+ perform_upgrade(db, upgrade)
+ count = count + 1
+
+ if count > 0:
+ logger.info(DBUTIL_UPGRADE_SUCCESFUL)
+ else:
+ # Should not get here, as we established earlier that the database
+ # was not at the latest version so we should have upgraded.
+ raise DbutilException("internal error in upgrade tool - no " +
+ "upgrade was performed on an old version " +
+ "the database")
+
+
+def parse_command():
+ """
+ @brief Parse Command
+
+ Parses the command line and sets the global command options.
+
+ @return Tuple of parser options and parser arguments
+ """
+ usage = ("usage: %prog --check [options] db_file\n" +
+ " %prog --upgrade [--noconfirm] [options] db_file")
+ parser = OptionParser(usage = usage, version = VERSION)
+ parser.add_option("-c", "--check", action="store_true",
+ dest="check", default=False,
+ help="Print database version and check if it " +
+ "needs upgrading")
+ parser.add_option("-n", "--noconfirm", action="store_true",
+ dest="noconfirm", default=False,
+ help="Do not prompt for confirmation before upgrading")
+ parser.add_option("-u", "--upgrade", action="store_true",
+ dest="upgrade", default=False,
+ help="Upgrade the database file to the latest version")
+ parser.add_option("-v", "--verbose", action="store_true",
+ dest="verbose", default=False,
+ help="Print SQL statements as they are executed")
+ parser.add_option("-q", "--quiet", action="store_true",
+ dest="quiet", default=False,
+ help="Don't print any info, warnings or errors")
+ (options, args) = parser.parse_args()
+
+ # Set the database file on which to operate
+ if (len(args) > 1):
+ logger.error(DBUTIL_TOO_MANY_ARGUMENTS)
+ parser.print_usage()
+ sys.exit(EXIT_COMMAND_ERROR)
+ elif len(args) == 0:
+ logger.error(DBUTIL_NO_FILE)
+ parser.print_usage()
+ sys.exit(EXIT_COMMAND_ERROR)
+
+ # Check for conflicting options. If some are found, output a suitable
+ # error message and print the usage.
+ if options.check and options.upgrade:
+ logger.error(DBUTIL_COMMAND_UPGRADE_CHECK)
+ elif (not options.check) and (not options.upgrade):
+ logger.error(DBUTIL_COMMAND_NONE)
+ elif (options.check and options.noconfirm):
+ logger.error(DBUTIL_CHECK_NOCONFIRM)
+ else:
+ return (options, args)
+
+ # Only get here on conflicting options
+ parser.print_usage()
+ sys.exit(EXIT_COMMAND_ERROR)
+
+
+if __name__ == "__main__":
+ (options, args) = parse_command()
+
+ if options.verbose:
+ isc.log.init("b10-dbutil", "DEBUG", 99)
+ logger = isc.log.Logger("dbutil")
+ elif options.quiet:
+ # We don't use FATAL, so setting the logger to use
+ # it should essentially make it silent.
+ isc.log.init("b10-dbutil", "FATAL")
+ logger = isc.log.Logger("dbutil")
+
+ db = Database(args[0])
+ exit_code = EXIT_SUCCESS
+
+ logger.info(DBUTIL_FILE, args[0])
+ if options.check:
+ # Check database - open, report, and close
+ try:
+ db.open()
+ exit_code = check_version(db)
+ db.close()
+ except Exception as ex:
+ logger.error(DBUTIL_CHECK_ERROR, ex)
+ exit_code = EXIT_READ_ERROR
+
+ elif options.upgrade:
+ # Upgrade. Check if this is what they really want to do
+ if not options.noconfirm:
+ proceed = prompt_user()
+ if not proceed:
+ logger.info(DBUTIL_UPGRADE_CANCELED)
+ sys.exit(EXIT_SUCCESS)
+
+ # It is. Do a backup then do the upgrade.
+ in_progress = False
+ try:
+ db.backup()
+ db.open()
+ in_progress = True
+ perform_all_upgrades(db)
+ db.close()
+ except Exception as ex:
+ if in_progress:
+ logger.error(DBUTIL_UPGRADE_FAILED, ex)
+ logger.warn(DBUTIL_DATABASE_MAY_BE_CORRUPT, db.db_file,
+ db.backup_file)
+ else:
+ logger.error(DBUTIL_UPGRADE_PREPARATION_FAILED, ex)
+ logger.info(DBUTIL_UPGRADE_NOT_ATTEMPTED)
+ exit_code = EXIT_UPGRADE_ERROR
+
+ sys.exit(exit_code)
diff --git a/src/bin/dbutil/dbutil_messages.mes b/src/bin/dbutil/dbutil_messages.mes
new file mode 100644
index 0000000..90ede92
--- /dev/null
+++ b/src/bin/dbutil/dbutil_messages.mes
@@ -0,0 +1,114 @@
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# No namespace declaration - these constants go in the global namespace
+# of the ddns messages python module.
+
+# When you add a message to this file, it is a good idea to run
+# <topsrcdir>/tools/reorder_message_file.py to make sure the
+# messages are in the correct order.
+
+% DBUTIL_BACKUP created backup of %1 in %2
+A backup for the given database file was created. Same of original file and
+backup are given in the output message.
+
+% DBUTIL_CHECK_ERROR unable to check database version: %1
+There was an error while trying to check the current version of the database
+schema. The error is shown in the message.
+
+% DBUTIL_CHECK_NOCONFIRM --noconfirm is not compatible with --check
+b10-dbutil was called with --check and --noconfirm. --noconfirm only has
+meaning with --upgrade, so this is considered an error.
+
+% DBUTIL_CHECK_OK this is the latest version of the database schema. No upgrade is required
+The database schema version has been checked, and is up to date.
+No action is required.
+
+% DBUTIL_CHECK_UPGRADE_NEEDED re-run this program with the --upgrade switch to upgrade
+The database schema version is not up to date, and an update is required.
+Please run the dbutil tool again, with the --upgrade argument.
+
+% DBUTIL_COMMAND_NONE must select one of --check or --upgrade
+b10-dbutil was called with neither --check nor --upgrade. One action must be
+provided.
+
+% DBUTIL_COMMAND_UPGRADE_CHECK --upgrade is not compatible with --check
+b10-dbutil was called with both the commands --upgrade and --check. Only one
+action can be performed at a time.
+
+% DBUTIL_DATABASE_MAY_BE_CORRUPT database file %1 may be corrupt, restore it from backup (%2)
+The upgrade failed while it was in progress; the database may now be in an
+inconsistent state, and it is advised to restore it from the backup that was
+created when b10-dbutil started.
+
+% DBUTIL_EXECUTE Executing SQL statement: %1
+Debug message; the given SQL statement is executed
+
+% DBUTIL_FILE Database file: %1
+The database file that is being checked.
+
+% DBUTIL_NO_FILE must supply name of the database file to upgrade
+b10-dbutil was called without a database file. Currently, it cannot find this
+file on its own, and it must be provided.
+
+% DBUTIL_STATEMENT_ERROR failed to execute %1: %2
+The given database statement failed to execute. The error is shown in the
+message.
+
+% DBUTIL_TOO_MANY_ARGUMENTS too many arguments to the command, maximum of one expected
+There were too many command-line arguments to b10-dbutil
+
+% DBUTIL_UPGRADE_CANCELED upgrade canceled; database has not been changed
+The user aborted the upgrade, and b10-dbutil will now exit.
+
+% DBUTIL_UPGRADE_DBUTIL please get the latest version of b10-dbutil and re-run
+A database schema was found that was newer than this version of dbutil, which
+is apparently out of date and should be upgraded itself.
+
+% DBUTIL_UPGRADE_FAILED upgrade failed: %1
+While the upgrade was in progress, an unexpected error occurred. The error
+is shown in the message.
+
+% DBUTIL_UPGRADE_NOT_ATTEMPTED database upgrade was not attempted
+Due to the earlier failure, the database schema upgrade was not attempted,
+and b10-dbutil will now exit.
+
+% DBUTIL_UPGRADE_NOT_NEEDED database already at latest version, no upgrade necessary
+b10-dbutil was told to upgrade the database schema, but it is already at the
+latest version.
+
+% DBUTIL_UPGRADE_NOT_POSSIBLE database at a later version than this utility can support
+b10-dbutil was told to upgrade the database schema, but it is at a higher
+version than this tool currently supports. Please update b10-dbutil and try
+again.
+
+% DBUTIL_UPGRADE_PREPARATION_FAILED upgrade preparation failed: %1
+An unexpected error occurred while b10-dbutil was preparing to upgrade the
+database schema. The error is shown in the message
+
+% DBUTIL_UPGRADE_SUCCESFUL database upgrade successfully completed
+The database schema update was completed successfully.
+
+% DBUTIL_UPGRADING upgrading database from %1 to %2
+An upgrade is in progress, the versions of the current upgrade action are shown.
+
+% DBUTIL_VERSION_CURRENT database version %1
+The current version of the database schema.
+
+% DBUTIL_VERSION_HIGH database is at a later version (%1) than this program can cope with (%2)
+The database schema is at a higher version than b10-dbutil knows about.
+
+% DBUTIL_VERSION_LOW database version %1, latest version is %2.
+The database schema is not up to date, the current version and the latest
+version are in the message.
diff --git a/src/bin/dbutil/run_dbutil.sh.in b/src/bin/dbutil/run_dbutil.sh.in
new file mode 100755
index 0000000..fea7482
--- /dev/null
+++ b/src/bin/dbutil/run_dbutil.sh.in
@@ -0,0 +1,40 @@
+#! /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
+
+DBUTIL_PATH=@abs_top_builddir@/src/bin/dbutil
+
+PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python
+export PYTHONPATH
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
+if test $SET_ENV_LIBRARY_PATH = yes; then
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ export @ENV_LIBRARY_PATH@
+fi
+
+B10_FROM_SOURCE=@abs_top_srcdir@
+export B10_FROM_SOURCE
+
+BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
+export BIND10_MSGQ_SOCKET_FILE
+
+exec ${PYTHON_EXEC} -O ${DBUTIL_PATH}/b10-dbutil "$@"
diff --git a/src/bin/dbutil/tests/.gitignore b/src/bin/dbutil/tests/.gitignore
new file mode 100644
index 0000000..8248611
--- /dev/null
+++ b/src/bin/dbutil/tests/.gitignore
@@ -0,0 +1,2 @@
+/dbutil_test.sh
+/dbutil_test_verfile_*
diff --git a/src/bin/dbutil/tests/Makefile.am b/src/bin/dbutil/tests/Makefile.am
new file mode 100644
index 0000000..c03b262
--- /dev/null
+++ b/src/bin/dbutil/tests/Makefile.am
@@ -0,0 +1,6 @@
+SUBDIRS = . testdata
+
+# Tests of the update script.
+
+check-local:
+ $(SHELL) $(abs_builddir)/dbutil_test.sh
diff --git a/src/bin/dbutil/tests/dbutil_test.sh.in b/src/bin/dbutil/tests/dbutil_test.sh.in
new file mode 100755
index 0000000..f82eeb0
--- /dev/null
+++ b/src/bin/dbutil/tests/dbutil_test.sh.in
@@ -0,0 +1,481 @@
+#!/bin/sh
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# Checks that the logger will limit the output of messages less severe than
+# the severity/debug setting.
+
+testname="Database Upgrade Test"
+echo $testname
+
+failcount=0
+tempfile=@abs_builddir@/dbutil_test_tempfile_$$
+backupfile=${tempfile}.backup
+testdata=@abs_srcdir@/testdata
+verfile=@abs_builddir@/dbutil_test_verfile_$$
+
+# @brief Record a success
+succeed() {
+ echo "--- PASS"
+}
+
+
+# @brief Record a fail
+#
+# @param $1 Optional additional reason to output
+fail() {
+ if [ "$1" != "" ]
+ then
+ echo "ERROR: $1"
+ fi
+ echo "*** FAIL"
+ failcount=`expr $failcount + 1`
+}
+
+
+# @brief Record a pass if the argument is zero
+#
+# @param $1 Value to test
+passzero() {
+ if [ $1 -eq 0 ]; then
+ succeed
+ else
+ fail
+ fi
+}
+
+
+# @brief Record a fail if the argument is non-zero
+#
+# @param $1 Value to test
+failzero() {
+ if [ $1 -ne 0 ]; then
+ succeed
+ else
+ fail
+ fi
+}
+
+
+# @brief Copy File
+#
+# Executes a "cp" operation followed by a "chmod" to make the target writeable.
+#
+# @param $1 Source file
+# @param $2 Target file
+copy_file () {
+ cp $1 $2
+ chmod a+w $2
+}
+
+
+
+# @brief Check backup file
+#
+# Record a failure if the backup file does not exist or if it is different
+# to the data file. (N.B. No success is recorded if they are the same.)
+#
+# @param $1 Source database file
+# @param $2 Backup file
+check_backup() {
+ if [ ! -e $1 ]
+ then
+ fail "database file $1 not found"
+
+ elif [ ! -e $2 ]
+ then
+ fail "backup file $2 not found"
+
+ else
+ diff $1 $2 > /dev/null
+ if [ $? -ne 0 ]
+ then
+ fail "database file $1 different to backup file $2"
+ fi
+ fi
+}
+
+
+# @brief Check No Backup File
+#
+# Record a failure if the backup file exists. (N.B. No success is recorded if
+# it does not.)
+#
+# @param $1 Source database file (unused, present for symmetry)
+# @param $2 Backup file
+check_no_backup() {
+ if [ -e $2 ]
+ then
+ fail "backup of database $2 exists when it should not"
+ fi
+}
+
+
+# @brief Get Database Schema
+#
+# As the schema stored in the database is format-dependent - how it is printed
+# depends on how the commands were entered (on one line, split across two
+# lines etc.) - comparing schema is awkward.
+#
+# The function sets the local variable db_schema to the output of the
+# .schema command, with spaces removed and upper converted to lowercase.
+#
+# The database is copied before the schema is taken (and removed after)
+# as SQLite3 assummes a writeable database, which may not be the case if
+# getting the schema from a reference copy.
+#
+# @param $1 Database for which the schema is required
+get_schema() {
+ db1=@abs_builddir@/dbutil_test_schema_$$
+ copy_file $1 $db1
+
+ db_schema=`sqlite3 $db1 '.schema' | \
+ awk '{line = line $0} END {print line}' | \
+ sed -e 's/ //g' | \
+ tr [:upper:] [:lower:]`
+ rm -f $db1
+}
+
+
+# @brief Successful Schema Upgrade Test
+#
+# This test is done where the upgrade is expected to be successful - when
+# the end result of the test is that the test database is upgraded to a
+# database of the expected schema.
+#
+# Note: the caller must ensure that $tempfile and $backupfile do not exist
+# on entry, and is responsible for removing them afterwards.
+#
+# @param $1 Database to upgrade
+# @param $2 Expected backup file
+upgrade_ok_test() {
+ copy_file $1 $tempfile
+ ../run_dbutil.sh --upgrade --noconfirm $tempfile
+ if [ $? -eq 0 ]
+ then
+ # Compare schema with the reference
+ get_schema $testdata/v2_0.sqlite3
+ expected_schema=$db_schema
+ get_schema $tempfile
+ actual_schema=$db_schema
+ if [ "$expected_schema" = "$actual_schema" ]
+ then
+ succeed
+ else
+ fail "upgraded schema not as expected"
+ fi
+
+ # Check the version is set correctly
+ check_version $tempfile "V2.0"
+
+ # Check that a backup was made
+ check_backup $1 $2
+ else
+ # Error should have been output already
+ fail
+ fi
+}
+
+
+# @brief Unsuccessful Upgrade Test
+#
+# Checks that an upgrade of the specified database fails.
+#
+# Note: the caller must ensure that $tempfile and $backupfile do not exist
+# on entry, and is responsible for removing them afterwards.
+#
+# @param $1 Database to upgrade
+# @param $2 Expected backup file
+upgrade_fail_test() {
+ copy_file $1 $tempfile
+ ../run_dbutil.sh --upgrade --noconfirm $tempfile
+ failzero $?
+ check_backup $1 $backupfile
+}
+
+
+# @brief Record Count Test
+#
+# Checks that the count of records in each table is preserved in the upgrade.
+#
+# Note 1: This test assumes that the "diffs" table is present.
+# Note 2: The caller must ensure that $tempfile and $backupfile do not exist
+# on entry, and is responsible for removing them afterwards.
+#
+# @brief $1 Database to upgrade
+record_count_test() {
+ copy_file $1 $tempfile
+
+ diffs_count=`sqlite3 $tempfile 'select count(*) from diffs'`
+ nsec3_count=`sqlite3 $tempfile 'select count(*) from nsec3'`
+ records_count=`sqlite3 $tempfile 'select count(*) from records'`
+ zones_count=`sqlite3 $tempfile 'select count(*) from zones'`
+
+ ../run_dbutil.sh --upgrade --noconfirm $tempfile
+ if [ $? -ne 0 ]
+ then
+ # Reason for failure should already have been output
+ fail
+ else
+ new_diffs_count=`sqlite3 $tempfile 'select count(*) from diffs'`
+ new_nsec3_count=`sqlite3 $tempfile 'select count(*) from nsec3'`
+ new_records_count=`sqlite3 $tempfile 'select count(*) from records'`
+ new_zones_count=`sqlite3 $tempfile 'select count(*) from zones'`
+
+ if [ $diffs_count -ne $new_diffs_count ]
+ then
+ fail "diffs table was not completely copied"
+ fi
+
+ if [ $nsec3_count -ne $new_nsec3_count ]
+ then
+ fail "nsec3 table was not completely copied"
+ fi
+
+ if [ $records_count -ne $new_records_count ]
+ then
+ fail "records table was not completely copied"
+ fi
+
+ if [ $zones_count -ne $new_zones_count ]
+ then
+ fail "zones table was not completely copied"
+ fi
+
+ # As an extra check, test that the backup was successful
+ check_backup $1 $backupfile
+ fi
+}
+
+
+# @brief Version Check
+#
+# Checks that the database is at the specified version (and so checks the
+# --check function). On success, a pass is recorded.
+#
+# @param $1 Database to check
+# @param $2 Expected version string
+check_version() {
+ copy_file $1 $verfile
+ ../run_dbutil.sh --check $verfile
+ if [ $? -gt 2 ]
+ then
+ fail "version check failed on database $1; return code $?"
+ else
+ ../run_dbutil.sh --check $verfile 2>&1 | grep "$2" > /dev/null
+ if [ $? -ne 0 ]
+ then
+ fail "database $1 not at expected version $2 (output: $?)"
+ else
+ succeed
+ fi
+ fi
+ rm -f $verfile
+}
+
+
+# @brief Version Check Fail
+#
+# Does a version check but expected the check to fail
+#
+# @param $1 Database to check
+# @param $2 Backup file
+check_version_fail() {
+ copy_file $1 $verfile
+ ../run_dbutil.sh --check $verfile
+ failzero $?
+ check_no_backup $tempfile $backupfile
+}
+
+
+# Main test sequence
+
+rm -f $tempfile $backupfile
+
+# Test 1 - check that the utility fails if the database does not exist
+echo "1.1. Non-existent database - check"
+../run_dbutil.sh --check $tempfile
+failzero $?
+check_no_backup $tempfile $backupfile
+
+echo "1.2. Non-existent database - upgrade"
+../run_dbutil.sh --upgrade --noconfirm $tempfile
+failzero $?
+check_no_backup $tempfile $backupfile
+rm -f $tempfile $backupfile
+
+
+# Test 2 - should fail to check an empty file and fail to upgrade it
+echo "2.1. Database is an empty file - check"
+touch $tempfile
+check_version_fail $tempfile $backupfile
+rm -f $tempfile $backupfile
+
+echo "2.2. Database is an empty file - upgrade"
+touch $tempfile
+../run_dbutil.sh --upgrade --noconfirm $tempfile
+failzero $?
+# A backup is performed before anything else, so the backup should exist.
+check_backup $tempfile $backupfile
+rm -f $tempfile $backupfile
+
+
+echo "3.1. Database is not an SQLite file - check"
+echo "This is not an sqlite3 database" > $tempfile
+check_version_fail $tempfile $backupfile
+rm -f $tempfile $backupfile
+
+echo "3.2. Database is not an SQLite file - upgrade"
+echo "This is not an sqlite3 database" > $tempfile
+../run_dbutil.sh --upgrade --noconfirm $tempfile
+failzero $?
+# ...and as before, a backup should have been created
+check_backup $tempfile $backupfile
+rm -f $tempfile $backupfile
+
+
+echo "4.1. Database is an SQLite3 file without the schema table - check"
+check_version_fail $testdata/no_schema.sqlite3 $backupfile
+rm -f $tempfile $backupfile
+
+echo "4.1. Database is an SQLite3 file without the schema table - upgrade"
+upgrade_fail_test $testdata/no_schema.sqlite3 $backupfile
+rm -f $tempfile $backupfile
+
+
+echo "5.1. Database is an old V1 database - check"
+check_version $testdata/old_v1.sqlite3 "V1.0"
+check_no_backup $tempfile $backupfile
+rm -f $tempfile $backupfile
+
+echo "5.2. Database is an old V1 database - upgrade"
+upgrade_ok_test $testdata/old_v1.sqlite3 $backupfile
+rm -f $tempfile $backupfile
+
+
+echo "6.1. Database is new V1 database - check"
+check_version $testdata/new_v1.sqlite3 "V1.0"
+check_no_backup $tempfile $backupfile
+rm -f $tempfile $backupfile
+
+echo "6.2. Database is a new V1 database - upgrade"
+upgrade_ok_test $testdata/new_v1.sqlite3 $backupfile
+rm -f $tempfile $backupfile
+
+
+echo "7.1. Database is V2.0 database - check"
+check_version $testdata/v2_0.sqlite3 "V2.0"
+check_no_backup $tempfile $backupfile
+rm -f $tempfile $backupfile
+
+echo "7.2. Database is a V2.0 database - upgrade"
+upgrade_ok_test $testdata/v2_0.sqlite3 $backupfile
+rm -f $tempfile $backupfile
+
+
+echo "8.1. Database is V2.0 database with empty schema table - check"
+check_version_fail $testdata/empty_version.sqlite3 $backupfile
+rm -f $tempfile $backupfile
+
+echo "8.2. Database is V2.0 database with empty schema table - upgrade"
+upgrade_fail_test $testdata/empty_version.sqlite3 $backupfile
+rm -f $tempfile $backupfile
+
+
+echo "9.1. Database is V2.0 database with over-full schema table - check"
+check_version_fail $testdata/too_many_version.sqlite3 $backupfile
+rm -f $tempfile $backupfile
+
+echo "9.2. Database is V2.0 database with over-full schema table - upgrade"
+upgrade_fail_test $testdata/too_many_version.sqlite3 $backupfile
+rm -f $tempfile $backupfile
+
+
+echo "10.0. Upgrade corrupt database"
+upgrade_fail_test $testdata/corrupt.sqlite3 $backupfile
+rm -f $tempfile $backupfile
+
+
+echo "11. Record count test"
+record_count_test $testdata/new_v1.sqlite3
+rm -f $tempfile $backupfile
+
+
+echo "12. Backup file already exists"
+touch $backupfile
+touch ${backupfile}-1
+upgrade_ok_test $testdata/v2_0.sqlite3 ${backupfile}-2
+rm -f $tempfile $backupfile ${backupfile}-1 ${backupfile}-2
+
+
+echo "13.1 Command-line errors"
+copy_file $testdata/old_v1.sqlite3 $tempfile
+../run_dbutil.sh $tempfile
+failzero $?
+../run_dbutil.sh --upgrade --check $tempfile
+failzero $?
+../run_dbutil.sh --noconfirm --check $tempfile
+failzero $?
+../run_dbutil.sh --check
+failzero $?
+../run_dbutil.sh --upgrade --noconfirm
+failzero $?
+../run_dbutil.sh --check $tempfile $backupfile
+failzero $?
+../run_dbutil.sh --upgrade --noconfirm $tempfile $backupfile
+failzero $?
+rm -f $tempfile $backupfile
+
+echo "13.2 verbose flag"
+copy_file $testdata/old_v1.sqlite3 $tempfile
+../run_dbutil.sh --upgrade --noconfirm --verbose $tempfile
+passzero $?
+rm -f $tempfile $backupfile
+
+echo "13.3 Interactive prompt - yes"
+copy_file $testdata/old_v1.sqlite3 $tempfile
+../run_dbutil.sh --upgrade $tempfile << .
+Yes
+.
+passzero $?
+check_version $tempfile "V2.0"
+rm -f $tempfile $backupfile
+
+echo "13.4 Interactive prompt - no"
+copy_file $testdata/old_v1.sqlite3 $tempfile
+../run_dbutil.sh --upgrade $tempfile << .
+no
+.
+passzero $?
+diff $testdata/old_v1.sqlite3 $tempfile > /dev/null
+passzero $?
+rm -f $tempfile $backupfile
+
+echo "13.5 quiet flag"
+copy_file $testdata/old_v1.sqlite3 $tempfile
+../run_dbutil.sh --check --quiet $tempfile 2>&1 | grep .
+failzero $?
+rm -f $tempfile $backupfile
+
+# Report the result
+if [ $failcount -eq 0 ]; then
+ echo "PASS: $testname"
+elif [ $failcount -eq 1 ]; then
+ echo "FAIL: $testname - 1 test failed"
+else
+ echo "FAIL: $testname - $failcount tests failed"
+fi
+
+# Exit with appropriate error status
+exit $failcount
diff --git a/src/bin/dbutil/tests/testdata/Makefile.am b/src/bin/dbutil/tests/testdata/Makefile.am
new file mode 100644
index 0000000..0d850a7
--- /dev/null
+++ b/src/bin/dbutil/tests/testdata/Makefile.am
@@ -0,0 +1,12 @@
+EXTRA_DIST =
+EXTRA_DIST += corrupt.sqlite3
+EXTRA_DIST += empty_schema.sqlite3
+EXTRA_DIST += empty_v1.sqlite3
+EXTRA_DIST += empty_version.sqlite3
+EXTRA_DIST += invalid_v1.sqlite3
+EXTRA_DIST += new_v1.sqlite3
+EXTRA_DIST += no_schema.sqlite3
+EXTRA_DIST += old_v1.sqlite3
+EXTRA_DIST += README
+EXTRA_DIST += too_many_version.sqlite3
+EXTRA_DIST += v2_0.sqlite3
diff --git a/src/bin/dbutil/tests/testdata/README b/src/bin/dbutil/tests/testdata/README
new file mode 100644
index 0000000..83ce01f
--- /dev/null
+++ b/src/bin/dbutil/tests/testdata/README
@@ -0,0 +1,41 @@
+The versioning of BIND 10 databases to date has not been the best:
+
+The original database is known here as the "old V1" schema. It had a
+schema_version table, with the single "version" value set to 1.
+
+The schema was then updated with a "diffs" table. This is referred to
+here as the "new V1" schema.
+
+The Spring 2012 release of BIND 10 modified the schema. The
+schema_version table was updated to include a "minor" column, holding the
+minor version number. Other changes to the database included redefining
+"STRING" columns as "TEXT" columns. This is referred to as the "V2.0
+schema".
+
+The following test data files are present:
+
+empty_schema.sqlite3: A database conforming to the new V1 schema.
+However, there is nothing in the schema_version table.
+
+empty_v1.sqlite3: A database conforming to the new V1 schema.
+The database is empty, except for the schema_version table, where the
+"version" column is set to 1.
+
+empty_version.sqlite3: A database conforming to the V2.0 schema but without
+anything in the schema_version table.
+
+no_schema.sqlite3: A valid SQLite3 database, but without a schema_version
+table.
+
+old_v1.sqlite3: A valid SQLite3 database conforming to the old V1 schema.
+It does not have a diffs table.
+
+invalid_v1.sqlite3: A valid SQLite3 database that, although the schema
+is marked as V1, does not have the nsec3 table.
+
+new_v1.sqlite3: A valid SQLite3 database with data in all the tables
+(although the single rows in both the nsec3 and diffs table make no
+sense, but are valid).
+
+too_many_version.sqlite3: A database conforming to the V2.0 schema but with
+too many rows of data.
diff --git a/src/bin/dbutil/tests/testdata/corrupt.sqlite3 b/src/bin/dbutil/tests/testdata/corrupt.sqlite3
new file mode 100644
index 0000000..69683b7
Binary files /dev/null and b/src/bin/dbutil/tests/testdata/corrupt.sqlite3 differ
diff --git a/src/bin/dbutil/tests/testdata/empty_schema.sqlite3 b/src/bin/dbutil/tests/testdata/empty_schema.sqlite3
new file mode 100644
index 0000000..b803149
Binary files /dev/null and b/src/bin/dbutil/tests/testdata/empty_schema.sqlite3 differ
diff --git a/src/bin/dbutil/tests/testdata/empty_v1.sqlite3 b/src/bin/dbutil/tests/testdata/empty_v1.sqlite3
new file mode 100644
index 0000000..5ad2136
Binary files /dev/null and b/src/bin/dbutil/tests/testdata/empty_v1.sqlite3 differ
diff --git a/src/bin/dbutil/tests/testdata/empty_version.sqlite3 b/src/bin/dbutil/tests/testdata/empty_version.sqlite3
new file mode 100644
index 0000000..b820fa9
Binary files /dev/null and b/src/bin/dbutil/tests/testdata/empty_version.sqlite3 differ
diff --git a/src/bin/dbutil/tests/testdata/invalid_v1.sqlite3 b/src/bin/dbutil/tests/testdata/invalid_v1.sqlite3
new file mode 100644
index 0000000..e411fd0
Binary files /dev/null and b/src/bin/dbutil/tests/testdata/invalid_v1.sqlite3 differ
diff --git a/src/bin/dbutil/tests/testdata/new_v1.sqlite3 b/src/bin/dbutil/tests/testdata/new_v1.sqlite3
new file mode 100644
index 0000000..9a885a4
Binary files /dev/null and b/src/bin/dbutil/tests/testdata/new_v1.sqlite3 differ
diff --git a/src/bin/dbutil/tests/testdata/no_schema.sqlite3 b/src/bin/dbutil/tests/testdata/no_schema.sqlite3
new file mode 100644
index 0000000..9dd0614
Binary files /dev/null and b/src/bin/dbutil/tests/testdata/no_schema.sqlite3 differ
diff --git a/src/bin/dbutil/tests/testdata/old_v1.sqlite3 b/src/bin/dbutil/tests/testdata/old_v1.sqlite3
new file mode 100644
index 0000000..32dbb9b
Binary files /dev/null and b/src/bin/dbutil/tests/testdata/old_v1.sqlite3 differ
diff --git a/src/bin/dbutil/tests/testdata/too_many_version.sqlite3 b/src/bin/dbutil/tests/testdata/too_many_version.sqlite3
new file mode 100644
index 0000000..5dc8ae3
Binary files /dev/null and b/src/bin/dbutil/tests/testdata/too_many_version.sqlite3 differ
diff --git a/src/bin/dbutil/tests/testdata/v2_0.sqlite3 b/src/bin/dbutil/tests/testdata/v2_0.sqlite3
new file mode 100644
index 0000000..18784fd
Binary files /dev/null and b/src/bin/dbutil/tests/testdata/v2_0.sqlite3 differ
diff --git a/src/bin/ddns/.gitignore b/src/bin/ddns/.gitignore
new file mode 100644
index 0000000..92b86f3
--- /dev/null
+++ b/src/bin/ddns/.gitignore
@@ -0,0 +1,2 @@
+/b10-ddns
+/ddns.py
diff --git a/src/bin/ddns/b10-ddns.8 b/src/bin/ddns/b10-ddns.8
index 3e2e102..131b6cc 100644
--- a/src/bin/ddns/b10-ddns.8
+++ b/src/bin/ddns/b10-ddns.8
@@ -2,21 +2,12 @@
.\" Title: b10-ddns
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: December 9, 2011
+.\" Date: February 28, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-DDNS" "8" "December 9, 2011" "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 "B10\-DDNS" "8" "February 28, 2012" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -43,6 +34,22 @@ boss process\&. When the
DNS server receives a DDNS update,
\fBb10\-ddns\fR
updates the zone in the BIND 10 zone data store\&.
+.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
+.PP
+Currently installed is a dummy component\&. It does not provide any functionality\&. It is a skeleton implementation that will be expanded later\&.
+.sp .5v
+.RE
.PP
This daemon communicates with BIND 10 over a
\fBb10-msgq\fR(8)
@@ -68,22 +75,22 @@ The configurable settings are:
.PP
\fIzones\fR
-The zones option is a named set of zones that can be updated with DDNS\&. Each entry has one element called update_acl, which is is a list of access control rules that define update permissions\&. By default this is empty; DDNS must be explicitely enabled per zone\&.
+The zones option is a named set of zones that can be updated with DDNS\&. Each entry has one element called update_acl, which is a list of access control rules that define update permissions\&. By default this is empty; DDNS must be explicitely enabled per zone\&.
.PP
The module commands are:
.PP
\fBshutdown\fR
-Exits
-\fBb10\-ddns\fR\&. (Note that the BIND 10 boss process will restart this service\&.)
+exits
+\fBb10\-ddns\fR\&. This has an optional
+\fIpid\fR
+argument to select the process ID to stop\&. (Note that the BIND 10 boss process may restart this service if configured\&.)
.SH "SEE ALSO"
.PP
\fBb10-auth\fR(8),
\fBb10-cfgmgr\fR(8),
\fBb10-msgq\fR(8),
-\fBb10-xfrin\fR(8),
-\fBb10-xfrout\fR(8),
\fBbind10\fR(8),
BIND 10 Guide\&.
.SH "HISTORY"
@@ -93,5 +100,5 @@ The
daemon was first implemented in December 2011 for the ISC BIND 10 project\&.
.SH "COPYRIGHT"
.br
-Copyright \(co 2011 Internet Systems Consortium, Inc. ("ISC")
+Copyright \(co 2011-2012 Internet Systems Consortium, Inc. ("ISC")
.br
diff --git a/src/bin/ddns/b10-ddns.xml b/src/bin/ddns/b10-ddns.xml
index e8b3f40..15fcb1a 100644
--- a/src/bin/ddns/b10-ddns.xml
+++ b/src/bin/ddns/b10-ddns.xml
@@ -2,7 +2,7 @@
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[<!ENTITY mdash "—">]>
<!--
- - Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>December 9, 2011</date>
+ <date>February 28, 2012</date>
</refentryinfo>
<refmeta>
@@ -36,7 +36,7 @@
<docinfo>
<copyright>
- <year>2011</year>
+ <year>2011-2012</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
@@ -63,6 +63,13 @@
in the BIND 10 zone data store.
</para>
+ <note><para>
+ Currently installed is a dummy component. It does not provide
+ any functionality. It is a skeleton implementation that
+ will be expanded later.
+<!-- TODO: #1458 -->
+ </para></note>
+
<para>
This daemon communicates with BIND 10 over a
<citerefentry><refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum></citerefentry>
@@ -115,8 +122,11 @@
The module commands are:
</para>
<para>
- <command>shutdown</command> Exits <command>b10-ddns</command>.
- (Note that the BIND 10 boss process will restart this service.)
+ <command>shutdown</command> exits <command>b10-ddns</command>.
+ This has an optional <varname>pid</varname> argument to
+ select the process ID to stop.
+ (Note that the BIND 10 boss process may restart this service
+ if configured.)
</para>
</refsect1>
@@ -135,12 +145,6 @@
<refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum>
</citerefentry>,
<citerefentry>
- <refentrytitle>b10-xfrin</refentrytitle><manvolnum>8</manvolnum>
- </citerefentry>,
- <citerefentry>
- <refentrytitle>b10-xfrout</refentrytitle><manvolnum>8</manvolnum>
- </citerefentry>,
- <citerefentry>
<refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
</citerefentry>,
<citetitle>BIND 10 Guide</citetitle>.
diff --git a/src/bin/ddns/ddns.py.in b/src/bin/ddns/ddns.py.in
index e5ce31d..22e4e9c 100755
--- a/src/bin/ddns/ddns.py.in
+++ b/src/bin/ddns/ddns.py.in
@@ -23,19 +23,32 @@ from isc.dns import *
from isc.config.ccsession import *
from isc.cc import SessionError, SessionTimeout
import isc.util.process
+import isc.util.cio.socketsession
+import select
+import errno
from isc.log_messages.ddns_messages import *
from optparse import OptionParser, OptionValueError
import os
+import os.path
import signal
+import socket
isc.log.init("b10-ddns")
logger = isc.log.Logger("ddns")
+TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
DATA_PATH = bind10_config.DATA_PATH
+SOCKET_FILE = DATA_PATH + '/ddns_socket'
if "B10_FROM_SOURCE" in os.environ:
DATA_PATH = os.environ['B10_FROM_SOURCE'] + "/src/bin/ddns"
+if "B10_FROM_BUILD" in os.environ:
+ if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
+ SOCKET_FILE = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"] + \
+ "/ddns_socket"
+ else:
+ SOCKET_FILE = os.environ["B10_FROM_BUILD"] + "/ddns_socket"
SPECFILE_LOCATION = DATA_PATH + "/ddns.spec"
@@ -65,6 +78,13 @@ class DDNSSession:
'''Initialize a DDNS Session'''
pass
+def clear_socket():
+ '''
+ Removes the socket file, if it exists.
+ '''
+ if os.path.exists(SOCKET_FILE):
+ os.remove(SOCKET_FILE)
+
class DDNSServer:
def __init__(self, cc_session=None):
'''
@@ -85,9 +105,17 @@ class DDNSServer:
self._config_data = self._cc.get_full_config()
self._cc.start()
self._shutdown = False
+ # List of the session receivers where we get the requests
+ self._socksession_receivers = {}
+ clear_socket()
+ self._listen_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self._listen_socket.bind(SOCKET_FILE)
+ self._listen_socket.listen(16)
def config_handler(self, new_config):
'''Update config data.'''
+ # TODO: Handle exceptions and turn them to an error response
+ # (once we have any configuration)
answer = create_answer(0)
return answer
@@ -96,6 +124,7 @@ class DDNSServer:
Handle a CC session command, as sent from bindctl or other
BIND 10 modules.
'''
+ # TODO: Handle exceptions and turn them to an error response
if cmd == "shutdown":
logger.info(DDNS_RECEIVED_SHUTDOWN_COMMAND)
self.trigger_shutdown()
@@ -121,23 +150,95 @@ class DDNSServer:
Perform any cleanup that is necessary when shutting down the server.
Do NOT call this to initialize shutdown, use trigger_shutdown().
- Currently, it does nothing, but cleanup routines are expected.
+ Currently, it only causes the ModuleCCSession to send a message that
+ this module is stopping.
'''
+ self._cc.send_stopping()
+
+ def accept(self):
+ """
+ Accept another connection and create the session receiver.
+ """
+ try:
+ sock = self._listen_socket.accept()
+ fileno = sock.fileno()
+ logger.debug(TRACE_BASIC, DDNS_NEW_CONN, fileno,
+ sock.getpeername())
+ receiver = isc.util.cio.socketsession.SocketSessionReceiver(sock)
+ self._socksession_receivers[fileno] = (sock, receiver)
+ except (socket.error, isc.util.cio.socketsession.SocketSessionError) \
+ as e:
+ # These exceptions mean the connection didn't work, but we can
+ # continue with the rest
+ logger.error(DDNS_ACCEPT_FAILURE, e)
+
+ def handle_request(self, request):
+ """
+ This is the place where the actual DDNS processing is done. Other
+ methods are either subroutines of this method or methods doing the
+ uninteresting "accounting" stuff, like accepting socket,
+ initialization, etc.
+
+ It is called with the request being session as received from
+ SocketSessionReceiver, i.e. tuple
+ (socket, local_address, remote_address, data).
+ """
+ # TODO: Implement the magic
+
+ # TODO: Don't propagate most of the exceptions (like datasrc errors),
+ # just drop the packet.
pass
+ def handle_session(self, fileno):
+ """
+ Handle incoming session on the socket with given fileno.
+ """
+ logger.debug(TRACE_BASIC, DDNS_SESSION, fileno)
+ (socket, receiver) = self._socksession_receivers[fileno]
+ try:
+ self.handle_request(receiver.pop())
+ except isc.util.cio.socketsession.SocketSessionError as se:
+ # No matter why this failed, the connection is in unknown, possibly
+ # broken state. So, we close the socket and remove the receiver.
+ del self._socksession_receivers[fileno]
+ socket.close()
+ logger.warn(DDNS_DROP_CONN, fileno, se)
+
def run(self):
'''
Get and process all commands sent from cfgmgr or other modules.
This loops waiting for events until self.shutdown() has been called.
'''
logger.info(DDNS_RUNNING)
+ cc_fileno = self._cc.get_socket().fileno()
+ listen_fileno = self._listen_socket.fileno()
while not self._shutdown:
- # We do not catch any exceptions here right now, but this would
- # be a good place to catch any exceptions that b10-ddns can
- # recover from. We currently have no exception hierarchy to
- # make such a distinction easily, but once we do, this would
- # be the place to catch.
- self._cc.check_command(False)
+ # In this event loop, we propagate most of exceptions, which will
+ # subsequently kill the process. We expect the handling functions
+ # to catch their own exceptions which they can recover from
+ # (malformed packets, lost connections, etc). The rationale behind
+ # this is they know best which exceptions are recoverable there
+ # and an exception may be recoverable somewhere, but not elsewhere.
+
+ try:
+ (reads, writes, exceptions) = \
+ select.select([cc_fileno, listen_fileno] +
+ list(self._socksession_receivers.keys()), [],
+ [])
+ except select.error as se:
+ # In case it is just interrupted, we continue like nothing
+ # happened
+ if se.args[0] == errno.EINTR:
+ (reads, writes, exceptions) = ([], [], [])
+ else:
+ raise
+ for fileno in reads:
+ if fileno == cc_fileno:
+ self._cc.check_command(True)
+ elif fileno == listen_fileno:
+ self.accept()
+ else:
+ self.handle_session(fileno)
self.shutdown_cleanup()
logger.info(DDNS_STOPPED)
@@ -204,6 +305,7 @@ def main(ddns_server=None):
logger.error(DDNS_CC_SESSION_TIMEOUT_ERROR)
except Exception as e:
logger.error(DDNS_UNCAUGHT_EXCEPTION, type(e).__name__, str(e))
+ clear_socket()
if '__main__' == __name__:
main()
diff --git a/src/bin/ddns/ddns.spec b/src/bin/ddns/ddns.spec
index 07cd2a9..55dab5c 100644
--- a/src/bin/ddns/ddns.spec
+++ b/src/bin/ddns/ddns.spec
@@ -34,7 +34,13 @@
{
"command_name": "shutdown",
"command_description": "Shut down DDNS",
- "command_args": []
+ "command_args": [
+ {
+ "item_name": "pid",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
}
]
}
diff --git a/src/bin/ddns/ddns_messages.mes b/src/bin/ddns/ddns_messages.mes
index 36c6ed1..996e663 100644
--- a/src/bin/ddns/ddns_messages.mes
+++ b/src/bin/ddns/ddns_messages.mes
@@ -19,6 +19,12 @@
# <topsrcdir>/tools/reorder_message_file.py to make sure the
# messages are in the correct order.
+% DDNS_ACCEPT_FAILURE error accepting a connection: %1
+There was a low-level error when we tried to accept an incoming connection
+(probably coming from b10-auth). We continue serving on whatever other
+connections we already have, but this connection is dropped. The reason
+is logged.
+
% DDNS_CC_SESSION_ERROR error reading from cc channel: %1
There was a problem reading from the command and control channel. The
most likely cause is that the msgq process is not running.
@@ -32,6 +38,13 @@ configuration manager b10-cfgmgr is not running.
The ddns process encountered an error when installing the configuration at
startup time. Details of the error are included in the log message.
+% DDNS_DROP_CONN dropping connection on file descriptor %1 because of error %2
+There was an error on a connection with the b10-auth server (or whatever
+connects to the ddns daemon). This might be OK, for example when the
+authoritative server shuts down, the connection would get closed. It also
+can mean the system is busy and can't keep up or that the other side got
+confused and sent bad data.
+
% DDNS_MODULECC_SESSION_ERROR error encountered by configuration/command module: %1
There was a problem in the lower level module handling configuration and
control commands. This could happen for various reasons, but the most likely
@@ -39,6 +52,12 @@ cause is that the configuration database contains a syntax error and ddns
failed to start at initialization. A detailed error message from the module
will also be displayed.
+% DDNS_NEW_CONN new connection on file descriptor %1 from %2
+Debug message. We received a connection and we are going to start handling
+requests from it. The file descriptor number and the address where the request
+comes from is logged. The connection is over a unix domain socket and is likely
+coming from a b10-auth process.
+
% DDNS_RECEIVED_SHUTDOWN_COMMAND shutdown command received
The ddns process received a shutdown command from the command channel
and will now shut down.
@@ -47,6 +66,11 @@ and will now shut down.
The ddns process has successfully started and is now ready to receive commands
and updates.
+% DDNS_SESSION session arrived on file descriptor %1
+A debug message, informing there's some activity on the given file descriptor.
+It will be either a request or the file descriptor will be closed. See
+following log messages to see what of it.
+
% DDNS_SHUTDOWN ddns server shutting down
The ddns process is shutting down. It will no longer listen for new commands
or updates. Any command or update that is being addressed at this moment will
diff --git a/src/bin/ddns/tests/Makefile.am b/src/bin/ddns/tests/Makefile.am
index d8b1c79..cd1082f 100644
--- a/src/bin/ddns/tests/Makefile.am
+++ b/src/bin/ddns/tests/Makefile.am
@@ -21,6 +21,7 @@ endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
B10_FROM_SOURCE=$(abs_top_srcdir) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/ddns:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/util/io/.libs \
TESTDATASRCDIR=$(abs_srcdir)/testdata/ \
diff --git a/src/bin/ddns/tests/ddns_test.py b/src/bin/ddns/tests/ddns_test.py
index 601c281..395aacc 100755
--- a/src/bin/ddns/tests/ddns_test.py
+++ b/src/bin/ddns/tests/ddns_test.py
@@ -19,6 +19,37 @@ import unittest
import isc
import ddns
import isc.config
+import select
+import errno
+import isc.util.cio.socketsession
+import socket
+import os.path
+
+class FakeSocket:
+ """
+ A fake socket. It only provides a file number, peer name and accept method.
+ """
+ def __init__(self, fileno):
+ self.__fileno = fileno
+ def fileno(self):
+ return self.__fileno
+ def getpeername(self):
+ return "fake_unix_socket"
+ def accept(self):
+ return FakeSocket(self.__fileno + 1)
+
+class FakeSessionReceiver:
+ """
+ A fake socket session receiver, for our tests.
+ """
+ def __init__(self, socket):
+ self._socket = socket
+ def socket(self):
+ """
+ This method is not present in the real receiver, but we use it to
+ inspect the socket passed to the constructor.
+ """
+ return self._socket
class MyCCSession(isc.config.ConfigData):
'''Fake session with minimal interface compliance'''
@@ -27,11 +58,22 @@ class MyCCSession(isc.config.ConfigData):
ddns.SPECFILE_LOCATION)
isc.config.ConfigData.__init__(self, module_spec)
self._started = False
+ self._stopped = False
def start(self):
'''Called by DDNSServer initialization, but not used in tests'''
self._started = True
+ def send_stopping(self):
+ '''Called by shutdown code'''
+ self._stopped = True
+
+ def get_socket(self):
+ """
+ Used to get the file number for select.
+ """
+ return FakeSocket(1)
+
class MyDDNSServer():
'''Fake DDNS server used to test the main() function'''
def __init__(self):
@@ -63,7 +105,41 @@ class TestDDNSServer(unittest.TestCase):
cc_session = MyCCSession()
self.assertFalse(cc_session._started)
self.ddns_server = ddns.DDNSServer(cc_session)
+ self.__cc_session = cc_session
self.assertTrue(cc_session._started)
+ self.__select_expected = None
+ self.__select_answer = None
+ self.__select_exception = None
+ self.__hook_called = False
+ self.ddns_server._listen_socket = FakeSocket(2)
+ ddns.select.select = self.__select
+
+ def tearDown(self):
+ ddns.select.select = select.select
+ ddns.isc.util.cio.socketsession.SocketSessionReceiver = \
+ isc.util.cio.socketsession.SocketSessionReceiver
+
+ def test_listen(self):
+ '''
+ Test the old socket file is removed (if any) and a new socket
+ is created when the ddns server is created.
+ '''
+ # Make sure the socket does not exist now
+ ddns.clear_socket()
+ # Hook the call for clearing the socket
+ orig_clear = ddns.clear_socket
+ ddns.clear_socket = self.__hook
+ # Create the server
+ ddnss = ddns.DDNSServer(MyCCSession())
+ ddns.clear_socket = orig_clear
+ # The socket is created
+ self.assertTrue(os.path.exists(ddns.SOCKET_FILE))
+ self.assertTrue(isinstance(ddnss._listen_socket, socket.socket))
+ # And deletion of the socket was requested
+ self.assertIsNone(self.__hook_called)
+ # Now make sure the clear_socket really works
+ ddns.clear_socket()
+ self.assertFalse(os.path.exists(ddns.SOCKET_FILE))
def test_config_handler(self):
# Config handler does not do anything yet, but should at least
@@ -93,14 +169,216 @@ class TestDDNSServer(unittest.TestCase):
signal_handler(None, None)
self.assertTrue(self.ddns_server._shutdown)
+ def __select(self, reads, writes, exceptions, timeout=None):
+ """
+ A fake select. It checks it was called with the correct parameters and
+ returns a preset answer.
+
+ If there's an exception stored in __select_exception, it is raised
+ instead and the exception is cleared.
+ """
+ self.assertEqual(self.__select_expected, (reads, writes, exceptions,
+ timeout))
+ if self.__select_exception is not None:
+ (self.__select_exception, exception) = (None,
+ self.__select_exception)
+ raise exception
+ answer = self.__select_answer
+ self.__select_answer = None
+ self.ddns_server._shutdown = True
+ return answer
+
+ def __hook(self, param=None):
+ """
+ A hook that can be installed to any nullary or unary function and see
+ if it was really called.
+ """
+ self.__hook_called = param
+
+ def test_accept_called(self):
+ """
+ Test we call the accept function when a new connection comes.
+ """
+ self.ddns_server.accept = self.__hook
+ self.__select_expected = ([1, 2], [], [], None)
+ self.__select_answer = ([2], [], [])
+ self.__hook_called = "Not called"
+ self.ddns_server.run()
+ self.assertTrue(self.ddns_server._shutdown)
+ # The answer got used
+ self.assertIsNone(self.__select_answer)
+ # Reset, when called without parameter
+ self.assertIsNone(self.__hook_called)
+
+ def test_check_command_called(self):
+ """
+ Test the check_command is called when there's something on the
+ socket.
+ """
+ self.__cc_session.check_command = self.__hook
+ self.__select_expected = ([1, 2], [], [], None)
+ self.__select_answer = ([1], [], [])
+ self.ddns_server.run()
+ self.assertTrue(self.ddns_server._shutdown)
+ # The answer got used
+ self.assertIsNone(self.__select_answer)
+ # And the check_command was called with true parameter (eg.
+ # non-blocking)
+ self.assertTrue(self.__hook_called)
+
+ def test_accept(self):
+ """
+ Test that we can accept a new connection.
+ """
+ # There's nothing before the accept
+ ddns.isc.util.cio.socketsession.SocketSessionReceiver = \
+ FakeSessionReceiver
+ self.assertEqual({}, self.ddns_server._socksession_receivers)
+ self.ddns_server.accept()
+ # Now the new socket session receiver is stored in the dict
+ # The 3 comes from _listen_socket.accept() - _listen_socket has
+ # fileno 2 and accept returns socket with fileno increased by one.
+ self.assertEqual([3],
+ list(self.ddns_server._socksession_receivers.keys()))
+ (socket, receiver) = self.ddns_server._socksession_receivers[3]
+ self.assertTrue(isinstance(socket, FakeSocket))
+ self.assertEqual(3, socket.fileno())
+ self.assertTrue(isinstance(receiver, FakeSessionReceiver))
+ self.assertEqual(socket, receiver.socket())
+
+ def test_accept_fail(self):
+ """
+ Test we don't crash if an accept fails and that we don't modify the
+ internals.
+ """
+ # Make the accept fail
+ def accept_failure():
+ raise socket.error(errno.ECONNABORTED)
+ orig = self.ddns_server._listen_socket.accept
+ self.ddns_server._listen_socket.accept = accept_failure
+ self.assertEqual({}, self.ddns_server._socksession_receivers)
+ # Doesn't raise the exception
+ self.ddns_server.accept()
+ # And nothing is stored
+ self.assertEqual({}, self.ddns_server._socksession_receivers)
+ # Now make the socket receiver fail
+ self.ddns_server._listen_socket.accept = orig
+ def receiver_failure(sock):
+ raise isc.util.cio.socketsession.SocketSessionError('Test error')
+ ddns.isc.util.cio.socketsession.SocketSessionReceiver = \
+ receiver_failure
+ # Doesn't raise the exception
+ self.ddns_server.accept()
+ # And nothing is stored
+ self.assertEqual({}, self.ddns_server._socksession_receivers)
+ # Check we don't catch everything, so raise just an exception
+ def unexpected_failure(sock):
+ raise Exception('Test error')
+ ddns.isc.util.cio.socketsession.SocketSessionReceiver = \
+ unexpected_failure
+ # This one gets through
+ self.assertRaises(Exception, self.ddns_server.accept)
+ # Nothing is stored as well
+ self.assertEqual({}, self.ddns_server._socksession_receivers)
+
+ def test_session_called(self):
+ """
+ Test the run calls handle_session when there's something on the
+ socket.
+ """
+ socket = FakeSocket(3)
+ self.ddns_server._socksession_receivers = \
+ {3: (socket, FakeSessionReceiver(socket))}
+ self.ddns_server.handle_session = self.__hook
+ self.__select_expected = ([1, 2, 3], [], [], None)
+ self.__select_answer = ([3], [], [])
+ self.ddns_server.run()
+ self.assertTrue(self.ddns_server._shutdown)
+ self.assertTrue(self.__cc_session._stopped)
+ self.assertIsNone(self.__select_answer)
+ self.assertEqual(3, self.__hook_called)
+
+ def test_handle_session_ok(self):
+ """
+ Test the handle_session pops the receiver and calls handle_request
+ when everything is OK.
+ """
+ socket = FakeSocket(3)
+ receiver = FakeSessionReceiver(socket)
+ # It doesn't really matter what data we use here, it is only passed
+ # through the code
+ param = (FakeSocket(4), ('127.0.0.1', 1234), ('127.0.0.1', 1235),
+ 'Some data')
+ def pop():
+ return param
+ # Prepare data into the receiver
+ receiver.pop = pop
+ self.ddns_server._socksession_receivers = {3: (socket, receiver)}
+ self.ddns_server.handle_request = self.__hook
+ # Call it
+ self.ddns_server.handle_session(3)
+ # The popped data are passed into the handle_request
+ self.assertEqual(param, self.__hook_called)
+ # The receivers are kept the same
+ self.assertEqual({3: (socket, receiver)},
+ self.ddns_server._socksession_receivers)
+
+ def test_handle_session_fail(self):
+ """
+ Test the handle_session removes (and closes) the socket and receiver
+ when the receiver complains.
+ """
+ socket = FakeSocket(3)
+ receiver = FakeSessionReceiver(socket)
+ def pop():
+ raise isc.util.cio.socketsession.SocketSessionError('Test error')
+ receiver.pop = pop
+ socket.close = self.__hook
+ self.__hook_called = False
+ self.ddns_server._socksession_receivers = {3: (socket, receiver)}
+ self.ddns_server.handle_session(3)
+ # The "dead" receiver is removed
+ self.assertEqual({}, self.ddns_server._socksession_receivers)
+ # Close is called with no parameter, so the default None
+ self.assertIsNone(self.__hook_called)
+
+ def test_select_exception_ignored(self):
+ """
+ Test that the EINTR is ignored in select.
+ """
+ # Prepare the EINTR exception
+ self.__select_exception = select.error(errno.EINTR)
+ # We reuse the test here, as it should act the same. The exception
+ # should just get ignored.
+ self.test_check_command_called()
+
+ def test_select_exception_fatal(self):
+ """
+ Test that other exceptions are fatal to the run.
+ """
+ # Prepare a different exception
+ self.__select_exception = select.error(errno.EBADF)
+ self.__select_expected = ([1, 2], [], [], None)
+ self.assertRaises(select.error, self.ddns_server.run)
+
class TestMain(unittest.TestCase):
def setUp(self):
self._server = MyDDNSServer()
+ self.__orig_clear = ddns.clear_socket
+ ddns.clear_socket = self.__clear_socket
+ self.__clear_called = False
+
+ def tearDown(self):
+ ddns.clear_socket = self.__orig_clear
def test_main(self):
self.assertFalse(self._server.run_called)
ddns.main(self._server)
self.assertTrue(self._server.run_called)
+ self.assertTrue(self.__clear_called)
+
+ def __clear_socket(self):
+ self.__clear_called = True
def check_exception(self, ex):
'''Common test sequence to see if the given exception is caused.
@@ -135,7 +413,6 @@ class TestMain(unittest.TestCase):
self._server.set_exception(BaseException("error"))
self.assertRaises(BaseException, ddns.main, self._server)
self.assertTrue(self._server.exception_raised)
-
if __name__== "__main__":
isc.log.resetUnitTestRootLogger()
diff --git a/src/bin/dhcp4/.gitignore b/src/bin/dhcp4/.gitignore
new file mode 100644
index 0000000..f7e9973
--- /dev/null
+++ b/src/bin/dhcp4/.gitignore
@@ -0,0 +1,3 @@
+/b10-dhcp4
+/spec_config.h
+/spec_config.h.pre
diff --git a/src/bin/dhcp4/Makefile.am b/src/bin/dhcp4/Makefile.am
index 513ae1c..6c52c05 100644
--- a/src/bin/dhcp4/Makefile.am
+++ b/src/bin/dhcp4/Makefile.am
@@ -15,7 +15,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
CLEANFILES = spec_config.h
man_MANS = b10-dhcp4.8
-EXTRA_DIST = $(man_MANS) dhcp4.spec
+EXTRA_DIST = $(man_MANS) b10-dhcp4.xml dhcp4.spec
if ENABLE_MAN
diff --git a/src/bin/dhcp4/tests/.gitignore b/src/bin/dhcp4/tests/.gitignore
new file mode 100644
index 0000000..5d14dac
--- /dev/null
+++ b/src/bin/dhcp4/tests/.gitignore
@@ -0,0 +1 @@
+/dhcp4_unittests
diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am
index b1b0798..abfffb9 100644
--- a/src/bin/dhcp6/Makefile.am
+++ b/src/bin/dhcp6/Makefile.am
@@ -17,7 +17,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
CLEANFILES = *.gcno *.gcda spec_config.h
man_MANS = b10-dhcp6.8
-EXTRA_DIST = $(man_MANS) dhcp6.spec interfaces.txt
+EXTRA_DIST = $(man_MANS) b10-dhcp6.xml dhcp6.spec interfaces.txt
if ENABLE_MAN
diff --git a/src/bin/host/.gitignore b/src/bin/host/.gitignore
new file mode 100644
index 0000000..0073523
--- /dev/null
+++ b/src/bin/host/.gitignore
@@ -0,0 +1 @@
+/b10-host
diff --git a/src/bin/host/host.cc b/src/bin/host/host.cc
index f0df0c8..a5c6522 100644
--- a/src/bin/host/host.cc
+++ b/src/bin/host/host.cc
@@ -70,8 +70,7 @@ host_lookup(const char* const name, const char* const dns_class,
RRClass(dns_class),
any ? RRType::ANY() : RRType(type))); // if NULL then:
- OutputBuffer obuffer(512);
- MessageRenderer renderer(obuffer);
+ MessageRenderer renderer;
msg.toWire(renderer);
struct addrinfo hints, *res;
@@ -111,7 +110,7 @@ host_lookup(const char* const name, const char* const dns_class,
gettimeofday(&before_time, NULL);
}
- sendto(s, obuffer.getData(), obuffer.getLength(), 0, res->ai_addr,
+ sendto(s, renderer.getData(), renderer.getLength(), 0, res->ai_addr,
res->ai_addrlen);
struct sockaddr_storage ss;
@@ -233,7 +232,7 @@ main(int argc, char* argv[]) {
argv += optind;
if (argc < 1) {
- cout << "Usage: host [-adprv] [-c class] [-t type] hostname [server]\n";
+ cout << "Usage: host [-adrv] [-c class] [-p port] [-t type] hostname [server]\n";
exit(1);
}
diff --git a/src/bin/loadzone/.gitignore b/src/bin/loadzone/.gitignore
new file mode 100644
index 0000000..86761ee
--- /dev/null
+++ b/src/bin/loadzone/.gitignore
@@ -0,0 +1,3 @@
+/b10-loadzone
+/b10-loadzone.py
+/run_loadzone.sh
diff --git a/src/bin/loadzone/b10-loadzone.8 b/src/bin/loadzone/b10-loadzone.8
index d563ff2..cf1c26b 100644
--- a/src/bin/loadzone/b10-loadzone.8
+++ b/src/bin/loadzone/b10-loadzone.8
@@ -2,12 +2,12 @@
.\" Title: b10-loadzone
.\" Author: [see the "AUTHORS" section]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: March 8, 2010
+.\" Date: March 26, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-LOADZONE" "8" "March 8, 2010" "BIND10" "BIND10"
+.TH "B10\-LOADZONE" "8" "March 26, 2012" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -22,7 +22,7 @@
b10-loadzone \- Load DNS Zone File
.SH "SYNOPSIS"
.HP \w'\fBb10\-loadzone\fR\ 'u
-\fBb10\-loadzone\fR [\fB\-d\ \fR\fB\fIdatabase\fR\fR] [\fB\-o\ \fR\fB\fIorigin\fR\fR] [filename]
+\fBb10\-loadzone\fR [\fB\-d\ \fR\fB\fIdatabase\fR\fR] [\fB\-o\ \fR\fB\fIorigin\fR\fR] {filename}
.SH "DESCRIPTION"
.PP
The
diff --git a/src/bin/loadzone/b10-loadzone.xml b/src/bin/loadzone/b10-loadzone.xml
index 25e23a5..8c41e54 100644
--- a/src/bin/loadzone/b10-loadzone.xml
+++ b/src/bin/loadzone/b10-loadzone.xml
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>March 8, 2010</date>
+ <date>March 26, 2012</date>
</refentryinfo>
<refmeta>
@@ -46,7 +46,7 @@
<command>b10-loadzone</command>
<arg><option>-d <replaceable class="parameter">database</replaceable></option></arg>
<arg><option>-o <replaceable class="parameter">origin</replaceable></option></arg>
- <arg chose="req">filename</arg>
+ <arg choice="req">filename</arg>
</cmdsynopsis>
</refsynopsisdiv>
diff --git a/src/bin/loadzone/tests/correct/.gitignore b/src/bin/loadzone/tests/correct/.gitignore
new file mode 100644
index 0000000..2d58698
--- /dev/null
+++ b/src/bin/loadzone/tests/correct/.gitignore
@@ -0,0 +1 @@
+/correct_test.sh
diff --git a/src/bin/loadzone/tests/error/.gitignore b/src/bin/loadzone/tests/error/.gitignore
new file mode 100644
index 0000000..5d20adb
--- /dev/null
+++ b/src/bin/loadzone/tests/error/.gitignore
@@ -0,0 +1 @@
+/error_test.sh
diff --git a/src/bin/msgq/.gitignore b/src/bin/msgq/.gitignore
new file mode 100644
index 0000000..ee1d942
--- /dev/null
+++ b/src/bin/msgq/.gitignore
@@ -0,0 +1,3 @@
+/b10-msgq
+/msgq.py
+/run_msgq.sh
diff --git a/src/bin/msgq/tests/.gitignore b/src/bin/msgq/tests/.gitignore
new file mode 100644
index 0000000..70acfff
--- /dev/null
+++ b/src/bin/msgq/tests/.gitignore
@@ -0,0 +1 @@
+/msgq_test
diff --git a/src/bin/resolver/.gitignore b/src/bin/resolver/.gitignore
new file mode 100644
index 0000000..95abd50
--- /dev/null
+++ b/src/bin/resolver/.gitignore
@@ -0,0 +1,7 @@
+/b10-resolver
+/resolver.spec
+/resolver.spec.pre
+/resolver_messages.cc
+/resolver_messages.h
+/spec_config.h
+/spec_config.h.pre
diff --git a/src/bin/resolver/Makefile.am b/src/bin/resolver/Makefile.am
index 3f5f049..2bb3768 100644
--- a/src/bin/resolver/Makefile.am
+++ b/src/bin/resolver/Makefile.am
@@ -49,9 +49,9 @@ pkglibexec_PROGRAMS = b10-resolver
b10_resolver_SOURCES = resolver.cc resolver.h
b10_resolver_SOURCES += resolver_log.cc resolver_log.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_SOURCES += common.cc common.h
nodist_b10_resolver_SOURCES = resolver_messages.cc resolver_messages.h
@@ -70,7 +70,6 @@ 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
diff --git a/src/bin/resolver/b10-resolver.8 b/src/bin/resolver/b10-resolver.8
index 9161ec2..eed69b8 100644
--- a/src/bin/resolver/b10-resolver.8
+++ b/src/bin/resolver/b10-resolver.8
@@ -2,12 +2,12 @@
.\" 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: August 17, 2011
+.\" Date: February 28, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-RESOLVER" "8" "August 17, 2011" "BIND10" "BIND10"
+.TH "B10\-RESOLVER" "8" "February 28, 2012" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -22,7 +22,7 @@
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]
+\fBb10\-resolver\fR [\fB\-v\fR]
.SH "DESCRIPTION"
.PP
The
@@ -43,15 +43,6 @@ It also receives its configurations from
.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
Enable verbose mode\&. This sets logging to the maximum debugging level\&.
@@ -127,7 +118,9 @@ The configuration command is:
\fBshutdown\fR
exits
-\fBb10\-resolver\fR\&. (Note that the BIND 10 boss process will restart this service\&.)
+\fBb10\-resolver\fR\&. This has an optional
+\fIpid\fR
+argument to select the process ID to stop\&. (Note that the BIND 10 boss process may restart this service if configured\&.)
.SH "SEE ALSO"
.PP
@@ -143,5 +136,5 @@ The
daemon was first coded in September 2010\&. The initial implementation only provided forwarding\&. Iteration was introduced in January 2011\&. Caching was implemented in February 2011\&. Access control was introduced in June 2011\&.
.SH "COPYRIGHT"
.br
-Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")
+Copyright \(co 2010-2012 Internet Systems Consortium, Inc. ("ISC")
.br
diff --git a/src/bin/resolver/b10-resolver.xml b/src/bin/resolver/b10-resolver.xml
index 75cced7..aca8fb2 100644
--- a/src/bin/resolver/b10-resolver.xml
+++ b/src/bin/resolver/b10-resolver.xml
@@ -2,7 +2,7 @@
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[<!ENTITY mdash "—">]>
<!--
- - Copyright (C) 2010-2011 Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2010-2012 Internet Systems Consortium, Inc. ("ISC")
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>August 17, 2011</date>
+ <date>February 28, 2012</date>
</refentryinfo>
<refmeta>
@@ -36,7 +36,7 @@
<docinfo>
<copyright>
- <year>2010</year>
+ <year>2010-2012</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
@@ -44,7 +44,6 @@
<refsynopsisdiv>
<cmdsynopsis>
<command>b10-resolver</command>
- <arg><option>-u <replaceable>username</replaceable></option></arg>
<arg><option>-v</option></arg>
</cmdsynopsis>
</refsynopsisdiv>
@@ -85,20 +84,6 @@
<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>
-
<!-- TODO: this needs to be fixed as -v on command line
should imply stdout or stderr output also -->
<!-- TODO: can this -v be overidden by configuration or bindctl? -->
@@ -216,7 +201,10 @@ once that is merged you can for instance do 'config add Resolver/forward_address
<para>
<command>shutdown</command> exits <command>b10-resolver</command>.
- (Note that the BIND 10 boss process will restart this service.)
+ This has an optional <varname>pid</varname> argument to
+ select the process ID to stop.
+ (Note that the BIND 10 boss process may restart this service
+ if configured.)
</para>
</refsect1>
diff --git a/src/bin/resolver/common.cc b/src/bin/resolver/common.cc
new file mode 100644
index 0000000..ad4fc50
--- /dev/null
+++ b/src/bin/resolver/common.cc
@@ -0,0 +1,17 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "common.h"
+
+const char* const RESOLVER_NAME = "b10-resolver";
diff --git a/src/bin/resolver/common.h b/src/bin/resolver/common.h
new file mode 100644
index 0000000..bcab8ba
--- /dev/null
+++ b/src/bin/resolver/common.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RESOLVER_COMMON_H
+#define RESOLVER_COMMON_H
+
+/// \brief The name used to identify the resolver between modules.
+///
+/// It is currently set to b10-resolver.
+extern const char* const RESOLVER_NAME;
+
+#endif
diff --git a/src/bin/resolver/main.cc b/src/bin/resolver/main.cc
index 090f211..d14fb0b 100644
--- a/src/bin/resolver/main.cc
+++ b/src/bin/resolver/main.cc
@@ -14,18 +14,10 @@
#include <config.h>
-#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 <resolver/spec_config.h>
+#include <resolver/resolver.h>
+#include "resolver_log.h"
+#include "common.h"
#include <asiodns/asiodns.h>
#include <asiolink/asiolink.h>
@@ -41,14 +33,12 @@
#include <cc/data.h>
#include <config/ccsession.h>
+#include <server_common/socket_request.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>
@@ -56,6 +46,20 @@
#include <log/logger_level.h>
#include "resolver_log.h"
+#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 <unistd.h>
+
+#include <string>
+#include <iostream>
+
+#include <boost/foreach.hpp>
+
using namespace std;
using namespace isc::cc;
using namespace isc::config;
@@ -79,21 +83,37 @@ ConstElementPtr
my_command_handler(const string& command, ConstElementPtr args) {
ConstElementPtr answer = createAnswer();
- if (command == "print_message") {
- LOG_INFO(resolver_logger, RESOLVER_PRINT_COMMAND).arg(args);
- /* let's add that message to our answer as well */
- answer = createAnswer(0, args);
- } else if (command == "shutdown") {
- io_service.stop();
- }
+ try {
+ if (command == "print_message") {
+ LOG_INFO(resolver_logger, RESOLVER_PRINT_COMMAND).arg(args);
+ /* let's add that message to our answer as well */
+ answer = createAnswer(0, args);
+ } else if (command == "shutdown") {
+ // Is the pid argument provided?
+ if (args && args->contains("pid")) {
+ // If it is, we check it is the same as our PID
+ const int pid(args->get("pid")->intValue());
+ const pid_t my_pid(getpid());
+ if (my_pid != pid) {
+ // It is not for us (this is expected, see auth/command.cc
+ // and the ShutdownCommand there).
+ return (answer);
+ }
+ }
+ LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT,
+ RESOLVER_SHUTDOWN_RECEIVED);
+ io_service.stop();
+ }
- return (answer);
+ return (answer);
+ } catch (const std::exception& e) {
+ return (createAnswer(1, e.what()));
+ }
}
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);
}
@@ -103,13 +123,9 @@ int
main(int argc, char* argv[]) {
bool verbose = false;
int ch;
- const char* uid = NULL;
while ((ch = getopt(argc, argv, "u:v")) != -1) {
switch (ch) {
- case 'u':
- uid = optarg;
- break;
case 'v':
verbose = true;
break;
@@ -125,7 +141,7 @@ main(int argc, char* argv[]) {
// Until proper logging comes along, initialize the logging with the
// temporary initLogger() code. If verbose, we'll use maximum verbosity.
- isc::log::initLogger("b10-resolver",
+ isc::log::initLogger(RESOLVER_NAME,
(verbose ? isc::log::DEBUG : isc::log::INFO),
isc::log::MAX_DEBUG_LEVEL, NULL);
@@ -206,16 +222,15 @@ main(int argc, char* argv[]) {
LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_SERVICE_CREATED);
cc_session = new Session(io_service.get_io_service());
+ isc::server_common::initSocketRequestor(*cc_session, RESOLVER_NAME);
+
+ // We delay starting listening to new commands/config just before we
+ // go into the main loop. See auth/main.cc for the rationale.
config_session = new ModuleCCSession(specfile, *cc_session,
my_config_handler,
- my_command_handler);
+ my_command_handler, false);
LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_CONFIG_CHANNEL);
- // FIXME: This does not belong here, but inside Boss
- if (uid != NULL) {
- changeUser(uid);
- }
-
resolver->setConfigSession(config_session);
// Install all initial configurations. If loading configuration
// fails, it will be logged, but we start the server anyway, giving
@@ -226,6 +241,9 @@ main(int argc, char* argv[]) {
resolver->updateConfig(config_session->getFullConfig(), true);
LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_CONFIG_LOADED);
+ // Now start asynchronous read.
+ config_session->start();
+
LOG_INFO(resolver_logger, RESOLVER_STARTED);
io_service.run();
} catch (const std::exception& ex) {
diff --git a/src/bin/resolver/resolver.cc b/src/bin/resolver/resolver.cc
index 367c16c..9536608 100644
--- a/src/bin/resolver/resolver.cc
+++ b/src/bin/resolver/resolver.cc
@@ -15,6 +15,7 @@
#include <config.h>
#include <stdint.h>
+#include <sys/types.h>
#include <netinet/in.h>
#include <algorithm>
@@ -91,7 +92,7 @@ public:
queryShutdown();
}
- void querySetup(DNSService& dnss,
+ void querySetup(DNSServiceBase& dnss,
isc::nsas::NameserverAddressStore& nsas,
isc::cache::ResolverCache& cache)
{
@@ -120,10 +121,10 @@ public:
}
void setForwardAddresses(const AddressList& upstream,
- DNSService *dnss)
+ DNSServiceBase* dnss)
{
upstream_ = upstream;
- if (dnss) {
+ if (dnss != NULL) {
if (!upstream_.empty()) {
BOOST_FOREACH(const AddressPair& address, upstream) {
LOG_INFO(resolver_logger, RESOLVER_FORWARD_ADDRESS)
@@ -136,10 +137,10 @@ public:
}
void setRootAddresses(const AddressList& upstream_root,
- DNSService *dnss)
+ DNSServiceBase* dnss)
{
upstream_root_ = upstream_root;
- if (dnss) {
+ if (dnss != NULL) {
if (!upstream_root_.empty()) {
BOOST_FOREACH(const AddressPair& address, upstream_root) {
LOG_INFO(resolver_logger, RESOLVER_SET_ROOT_ADDRESS)
@@ -252,7 +253,8 @@ makeErrorMessage(MessagePtr message, MessagePtr answer_message,
}
for_each(questions.begin(), questions.end(), QuestionInserter(message));
message->setRcode(rcode);
- MessageRenderer renderer(*buffer);
+ MessageRenderer renderer;
+ renderer.setBuffer(buffer.get());
message->toWire(renderer);
}
@@ -303,7 +305,8 @@ public:
// Now we can clear the buffer and render the new message into it
buffer->clear();
- MessageRenderer renderer(*buffer);
+ MessageRenderer renderer;
+ renderer.setBuffer(buffer.get());
ConstEDNSPtr edns(query_message->getEDNS());
const bool dnssec_ok = edns && edns->getDNSSECAwareness();
@@ -327,6 +330,7 @@ public:
}
answer_message->toWire(renderer);
+ renderer.setBuffer(NULL);
LOG_DEBUG(resolver_logger, RESOLVER_DBG_DETAIL,
RESOLVER_DNS_MESSAGE_SENT)
@@ -373,7 +377,7 @@ Resolver::~Resolver() {
}
void
-Resolver::setDNSService(isc::asiodns::DNSService& dnss) {
+Resolver::setDNSService(isc::asiodns::DNSServiceBase& dnss) {
dnss_ = &dnss;
}
diff --git a/src/bin/resolver/resolver.h b/src/bin/resolver/resolver.h
index 096f522..e91192e 100644
--- a/src/bin/resolver/resolver.h
+++ b/src/bin/resolver/resolver.h
@@ -104,7 +104,7 @@ public:
bool startup = false);
/// \brief Assign an ASIO IO Service queue to this Resolver object
- void setDNSService(isc::asiodns::DNSService& dnss);
+ void setDNSService(isc::asiodns::DNSServiceBase& dnss);
/// \brief Assign a NameserverAddressStore to this Resolver object
void setNameserverAddressStore(isc::nsas::NameserverAddressStore &nsas);
@@ -113,7 +113,7 @@ public:
void setCache(isc::cache::ResolverCache& cache);
/// \brief Return this object's ASIO IO Service queue
- isc::asiodns::DNSService& getDNSService() const { return (*dnss_); }
+ isc::asiodns::DNSServiceBase& getDNSService() const { return (*dnss_); }
/// \brief Returns this object's NSAS
isc::nsas::NameserverAddressStore& getNameserverAddressStore() const {
@@ -258,7 +258,7 @@ public:
private:
ResolverImpl* impl_;
- isc::asiodns::DNSService* dnss_;
+ isc::asiodns::DNSServiceBase* dnss_;
isc::asiolink::SimpleCallback* checkin_;
isc::asiodns::DNSLookup* dns_lookup_;
isc::asiodns::DNSAnswer* dns_answer_;
diff --git a/src/bin/resolver/resolver.spec.pre.in b/src/bin/resolver/resolver.spec.pre.in
index 076ef85..d6bb226 100644
--- a/src/bin/resolver/resolver.spec.pre.in
+++ b/src/bin/resolver/resolver.spec.pre.in
@@ -154,7 +154,13 @@
{
"command_name": "shutdown",
"command_description": "Shut down recursive DNS server",
- "command_args": []
+ "command_args": [
+ {
+ "item_name": "pid",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
}
]
}
diff --git a/src/bin/resolver/resolver_messages.mes b/src/bin/resolver/resolver_messages.mes
index 7930c52..4999dbe 100644
--- a/src/bin/resolver/resolver_messages.mes
+++ b/src/bin/resolver/resolver_messages.mes
@@ -246,3 +246,7 @@ RESOLVER_QUERY_REJECTED case, the server does not return any response.
The log message shows the query in the form of <query name>/<query
type>/<query class>, and the client that sends the query in the form of
<Source IP address>#<source port>.
+
+% RESOLVER_SHUTDOWN_RECEIVED received command to shut down
+A debug message noting that the server was asked to terminate and is
+complying to the request.
diff --git a/src/bin/resolver/tests/.gitignore b/src/bin/resolver/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/bin/resolver/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/bin/resolver/tests/resolver_config_unittest.cc b/src/bin/resolver/tests/resolver_config_unittest.cc
index 63d75c2..369ca5c 100644
--- a/src/bin/resolver/tests/resolver_config_unittest.cc
+++ b/src/bin/resolver/tests/resolver_config_unittest.cc
@@ -48,7 +48,9 @@
#include <dns/tests/unittest_util.h>
#include <testutils/srv_test.h>
+#include <testutils/mockups.h>
#include <testutils/portconfig.h>
+#include <testutils/socket_request.h>
using namespace std;
using boost::scoped_ptr;
@@ -63,7 +65,8 @@ using isc::UnitTestUtil;
namespace {
const char* const TEST_ADDRESS = "127.0.0.1";
-const char* const TEST_PORT = "53530";
+const char* const TEST_ADDRESS_FAIL = "192.0.2.2";
+const char* const TEST_PORT = "53210";
// An internal exception class
class TestConfigError : public isc::Exception {
@@ -74,14 +77,18 @@ public:
class ResolverConfig : public ::testing::Test {
protected:
- IOService ios;
- DNSService dnss;
+ MockDNSService dnss;
Resolver server;
scoped_ptr<const IOEndpoint> endpoint;
scoped_ptr<const IOMessage> query_message;
scoped_ptr<const Client> client;
scoped_ptr<const RequestContext> request;
- ResolverConfig() : dnss(ios, NULL, NULL, NULL) {
+ ResolverConfig() :
+ // The empty string is expected value of the parameter of
+ // requestSocket, not the app_name (there's no fallback, it checks
+ // the empty string is passed).
+ sock_requestor_(dnss, address_store_, 53210, "")
+ {
server.setDNSService(dnss);
}
const RequestContext& createRequest(const string& source_addr) {
@@ -96,6 +103,8 @@ protected:
return (*request);
}
void invalidTest(const string &JSON, const string& name);
+ isc::server_common::portconfig::AddressList address_store_;
+ isc::testutils::TestSocketRequestor sock_requestor_;
};
TEST_F(ResolverConfig, forwardAddresses) {
@@ -248,7 +257,7 @@ TEST_F(ResolverConfig, listenOnConfigFail) {
"\"listen_on\": ["
" {"
" \"address\": \"" +
- string(TEST_ADDRESS) + "\","
+ string(TEST_ADDRESS_FAIL) + "\","
" \"port\": " +
string(TEST_PORT) + "}]}"));
configAnswerCheck(server.updateConfig(config), false);
@@ -264,7 +273,7 @@ TEST_F(ResolverConfig, listenOnAndOtherConfig) {
" {\"address\": \"192.0.2.1\","
" \"port\": 53}], "
"\"listen_on\": ["
- " {\"address\": \"" + string(TEST_ADDRESS) + "\","
+ " {\"address\": \"" + string(TEST_ADDRESS_FAIL) + "\","
" \"port\": " + string(TEST_PORT) + "}]}");
// Normally, if listen_on fails the rest of the config parameters will
// be ignored.
@@ -310,6 +319,29 @@ TEST_F(ResolverConfig, invalidForwardAddresses) {
// Try setting the addresses directly
TEST_F(ResolverConfig, listenAddresses) {
isc::testutils::portconfig::listenAddresses(server);
+
+ // listenAddressConfig should have attempted to create 4 DNS server
+ // objects: two IP addresses, TCP and UDP for each. For UDP, the "SYNC_OK"
+ // option (or anything else) should have NOT been specified.
+ EXPECT_EQ(2, dnss.getTCPFdParams().size());
+ EXPECT_EQ(2, dnss.getUDPFdParams().size());
+ EXPECT_EQ(DNSService::SERVER_DEFAULT, dnss.getUDPFdParams().at(0).options);
+ EXPECT_EQ(DNSService::SERVER_DEFAULT, dnss.getUDPFdParams().at(1).options);
+
+ // Check it requests the correct addresses
+ const char* tokens[] = {
+ "TCP:127.0.0.1:53210:1",
+ "UDP:127.0.0.1:53210:2",
+ "TCP:::1:53210:3",
+ "UDP:::1:53210:4",
+ NULL
+ };
+ sock_requestor_.checkTokens(tokens, sock_requestor_.given_tokens_,
+ "Given tokens");
+ // It returns back to empty set of addresses afterwards, so
+ // they should be released
+ sock_requestor_.checkTokens(tokens, sock_requestor_.released_tokens_,
+ "Released tokens");
}
// Try setting some addresses and a rollback
diff --git a/src/bin/sockcreator/.gitignore b/src/bin/sockcreator/.gitignore
new file mode 100644
index 0000000..2985184
--- /dev/null
+++ b/src/bin/sockcreator/.gitignore
@@ -0,0 +1 @@
+/b10-sockcreator
diff --git a/src/bin/sockcreator/Makefile.am b/src/bin/sockcreator/Makefile.am
index 1ac4640..e954c02 100644
--- a/src/bin/sockcreator/Makefile.am
+++ b/src/bin/sockcreator/Makefile.am
@@ -15,4 +15,5 @@ CLEANFILES = *.gcno *.gcda
pkglibexec_PROGRAMS = b10-sockcreator
b10_sockcreator_SOURCES = sockcreator.cc sockcreator.h main.cc
-b10_sockcreator_LDADD = $(top_builddir)/src/lib/util/io/libutil_io.la
+b10_sockcreator_LDADD = $(top_builddir)/src/lib/util/io/libutil_io.la
+b10_sockcreator_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
diff --git a/src/bin/sockcreator/main.cc b/src/bin/sockcreator/main.cc
index 37da303..17efd04 100644
--- a/src/bin/sockcreator/main.cc
+++ b/src/bin/sockcreator/main.cc
@@ -13,6 +13,7 @@
// PERFORMANCE OF THIS SOFTWARE.
#include "sockcreator.h"
+#include <unistd.h>
using namespace isc::socket_creator;
@@ -22,5 +23,10 @@ main() {
* TODO Maybe use some OS-specific caps interface and drop everything
* but ability to bind ports? It would be nice.
*/
- return run(0, 1); // Read commands from stdin, output to stdout
+ try {
+ run(STDIN_FILENO, STDOUT_FILENO, getSock, isc::util::io::send_fd, close);
+ } catch (const SocketCreatorError& ec) {
+ return (ec.getExitCode());
+ }
+ return (0);
}
diff --git a/src/bin/sockcreator/sockcreator.cc b/src/bin/sockcreator/sockcreator.cc
index 6b50813..167e3f0 100644
--- a/src/bin/sockcreator/sockcreator.cc
+++ b/src/bin/sockcreator/sockcreator.cc
@@ -15,134 +15,275 @@
#include "sockcreator.h"
#include <util/io/fd.h>
+#include <util/io/sockaddr_util.h>
-#include <unistd.h>
#include <cerrno>
#include <string.h>
+
+#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
using namespace isc::util::io;
+using namespace isc::util::io::internal;
+using namespace isc::socket_creator;
+
+namespace {
+
+// Simple wrappers for read_data/write_data that throw an exception on error.
+void
+readMessage(const int fd, void* where, const size_t length) {
+ if (read_data(fd, where, length) < length) {
+ isc_throw(ReadError, "Error reading from socket creator client");
+ }
+}
+
+void
+writeMessage(const int fd, const void* what, const size_t length) {
+ if (!write_data(fd, what, length)) {
+ isc_throw(WriteError, "Error writing to socket creator client");
+ }
+}
+
+// Exit on a protocol error after informing the client of the problem.
+void
+protocolError(const int fd, const char reason = 'I') {
+
+ // Tell client we have a problem
+ char message[2];
+ message[0] = 'F';
+ message[1] = reason;
+ writeMessage(fd, message, sizeof(message));
+
+ // ... and exit
+ isc_throw(ProtocolError, "Fatal error, reason: " << reason);
+}
+
+// Return appropriate socket type constant for the socket type requested.
+// The output_fd argument is required to report a protocol error.
+int
+getSocketType(const char type_code, const int output_fd) {
+ int socket_type = 0;
+ switch (type_code) {
+ case 'T':
+ socket_type = SOCK_STREAM;
+ break;
+
+ case 'U':
+ socket_type = SOCK_DGRAM;
+ break;
+
+ default:
+ protocolError(output_fd); // Does not return
+ }
+ return (socket_type);
+}
+
+// Convert return status from getSock() to a character to be sent back to
+// the caller.
+char
+getErrorCode(const int status) {
+ char error_code = ' ';
+ switch (status) {
+ case -1:
+ error_code = 'S';
+ break;
+
+ case -2:
+ error_code = 'B';
+ break;
+
+ default:
+ isc_throw(InternalError, "Error creating socket");
+ }
+ return (error_code);
+}
+
+
+// Handle the request from the client.
+//
+// Reads the type and family of socket required, creates the socket and returns
+// it to the client.
+//
+// The arguments passed (and the exceptions thrown) are the same as those for
+// run().
+void
+handleRequest(const int input_fd, const int output_fd,
+ const get_sock_t get_sock, const send_fd_t send_fd_fun,
+ const close_t close_fun)
+{
+ // Read the message from the client
+ char type[2];
+ readMessage(input_fd, type, sizeof(type));
+
+ // Decide what type of socket is being asked for
+ const int sock_type = getSocketType(type[0], output_fd);
+
+ // Read the address they ask for depending on what address family was
+ // specified.
+ sockaddr* addr = NULL;
+ size_t addr_len = 0;
+ sockaddr_in addr_in;
+ sockaddr_in6 addr_in6;
+ switch (type[1]) { // The address family
+
+ // The casting to apparently incompatible types is required by the
+ // C low-level interface.
+
+ case '4':
+ addr = convertSockAddr(&addr_in);
+ addr_len = sizeof(addr_in);
+ memset(&addr_in, 0, sizeof(addr_in));
+ addr_in.sin_family = AF_INET;
+ readMessage(input_fd, &addr_in.sin_port, sizeof(addr_in.sin_port));
+ readMessage(input_fd, &addr_in.sin_addr.s_addr,
+ sizeof(addr_in.sin_addr.s_addr));
+ break;
+
+ case '6':
+ addr = convertSockAddr(&addr_in6);
+ addr_len = sizeof(addr_in6);
+ memset(&addr_in6, 0, sizeof(addr_in6));
+ addr_in6.sin6_family = AF_INET6;
+ readMessage(input_fd, &addr_in6.sin6_port,
+ sizeof(addr_in6.sin6_port));
+ readMessage(input_fd, &addr_in6.sin6_addr.s6_addr,
+ sizeof(addr_in6.sin6_addr.s6_addr));
+ break;
+
+ default:
+ protocolError(output_fd);
+ }
+
+ // Obtain the socket
+ const int result = get_sock(sock_type, addr, addr_len, close_fun);
+ if (result >= 0) {
+ // Got the socket, send it to the client.
+ writeMessage(output_fd, "S", 1);
+ if (send_fd_fun(output_fd, result) != 0) {
+ // Error. Close the socket (ignore any error from that operation)
+ // and abort.
+ close_fun(result);
+ isc_throw(InternalError, "Error sending descriptor");
+ }
+
+ // Successfully sent the socket, so free up resources we still hold
+ // for it.
+ if (close_fun(result) == -1) {
+ isc_throw(InternalError, "Error closing socket");
+ }
+ } else {
+ // Error. Tell the client.
+ char error_message[2];
+ error_message[0] = 'E';
+ error_message[1] = getErrorCode(result);
+ writeMessage(output_fd, error_message, sizeof(error_message));
+
+ // ...and append the reason code to the error message
+ const int error_number = errno;
+ writeMessage(output_fd, &error_number, sizeof(error_number));
+ }
+}
+
+// Sets the MTU related flags for IPv6 UDP sockets.
+// It is borrowed from bind-9 lib/isc/unix/socket.c and modified
+// to compile here.
+//
+// The function returns -2 if it fails or the socket file descriptor
+// on success (for convenience, so the result can be just returned).
+int
+mtu(int fd) {
+#ifdef IPV6_USE_MIN_MTU /* RFC 3542, not too common yet*/
+ const int on(1);
+ // use minimum MTU
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_USE_MIN_MTU, &on, sizeof(on)) < 0) {
+ return (-2);
+ }
+#else // Try the following as fallback
+#ifdef IPV6_MTU
+ // Use minimum MTU on systems that don't have the IPV6_USE_MIN_MTU
+ const int mtu = 1280;
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU, &mtu, sizeof(mtu)) < 0) {
+ return (-2);
+ }
+#endif
+#if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DONT)
+ // Turn off Path MTU discovery on IPv6/UDP sockets.
+ const int action = IPV6_PMTUDISC_DONT;
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &action,
+ sizeof(action)) < 0) {
+
+ return (-2);
+ }
+#endif
+#endif
+ return (fd);
+}
+
+// This one closes the socket if result is negative. Used not to leak socket
+// on error.
+int maybeClose(const int result, const int socket, const close_t close_fun) {
+ if (result < 0) {
+ if (close_fun(socket) == -1) {
+ isc_throw(InternalError, "Error closing socket");
+ }
+ }
+ return (result);
+}
+
+} // Anonymous namespace
namespace isc {
namespace socket_creator {
+// Get the socket and bind to it.
int
-get_sock(const int type, struct sockaddr *bind_addr, const socklen_t addr_len)
-{
- int sock(socket(bind_addr->sa_family, type, 0));
+getSock(const int type, struct sockaddr* bind_addr, const socklen_t addr_len,
+ const close_t close_fun) {
+ const int sock = socket(bind_addr->sa_family, type, 0);
if (sock == -1) {
- return -1;
+ return (-1);
+ }
+ const int on = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+ // This is part of the binding process, so it's a bind error
+ return (maybeClose(-2, sock, close_fun));
+ }
+ if (bind_addr->sa_family == AF_INET6 &&
+ setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1) {
+ // This is part of the binding process, so it's a bind error
+ return (maybeClose(-2, sock, close_fun));
}
if (bind(sock, bind_addr, addr_len) == -1) {
- return -2;
+ return (maybeClose(-2, sock, close_fun));
+ }
+ if (type == SOCK_DGRAM && bind_addr->sa_family == AF_INET6) {
+ // Set some MTU flags on IPv6 UDP sockets.
+ return (maybeClose(mtu(sock), sock, close_fun));
}
- return sock;
+ return (sock);
}
-// These are macros so they can exit the function
-#define READ(WHERE, HOW_MANY) do { \
- size_t how_many = (HOW_MANY); \
- if (read_data(input_fd, (WHERE), how_many) < how_many) { \
- return 1; \
- } \
- } while (0)
-
-#define WRITE(WHAT, HOW_MANY) do { \
- if (!write_data(output_fd, (WHAT), (HOW_MANY))) { \
- return 2; \
- } \
- } while (0)
-
-#define DEFAULT \
- default: /* Unrecognized part of protocol */ \
- WRITE("FI", 2); \
- return 3;
-
-int
-run(const int input_fd, const int output_fd, const get_sock_t get_sock,
- const send_fd_t send_fd)
+// Main run loop.
+void
+run(const int input_fd, const int output_fd, get_sock_t get_sock,
+ send_fd_t send_fd_fun, close_t close_fun)
{
for (;;) {
- // Read the command
char command;
- READ(&command, 1);
+ readMessage(input_fd, &command, sizeof(command));
switch (command) {
- case 'T': // The "terminate" command
- return 0;
- case 'S': { // Create a socket
- // Read what type of socket they want
- char type[2];
- READ(type, 2);
- // Read the address they ask for
- struct sockaddr *addr(NULL);
- size_t addr_len(0);
- struct sockaddr_in addr_in;
- struct sockaddr_in6 addr_in6;
- switch (type[1]) { // The address family
- /*
- * Here are some casts. They are required by C++ and
- * the low-level interface (they are implicit in C).
- */
- case '4':
- addr = static_cast<struct sockaddr *>(
- static_cast<void *>(&addr_in));
- addr_len = sizeof addr_in;
- memset(&addr_in, 0, sizeof addr_in);
- addr_in.sin_family = AF_INET;
- READ(static_cast<char *>(static_cast<void *>(
- &addr_in.sin_port)), 2);
- READ(static_cast<char *>(static_cast<void *>(
- &addr_in.sin_addr.s_addr)), 4);
- break;
- case '6':
- addr = static_cast<struct sockaddr *>(
- static_cast<void *>(&addr_in6));
- addr_len = sizeof addr_in6;
- memset(&addr_in6, 0, sizeof addr_in6);
- addr_in6.sin6_family = AF_INET6;
- READ(static_cast<char *>(static_cast<void *>(
- &addr_in6.sin6_port)), 2);
- READ(static_cast<char *>(static_cast<void *>(
- &addr_in6.sin6_addr.s6_addr)), 16);
- break;
- DEFAULT
- }
- int sock_type;
- switch (type[0]) { // Translate the type
- case 'T':
- sock_type = SOCK_STREAM;
- break;
- case 'U':
- sock_type = SOCK_DGRAM;
- break;
- DEFAULT
- }
- int result(get_sock(sock_type, addr, addr_len));
- if (result >= 0) { // We got the socket
- WRITE("S", 1);
- // FIXME: Check the output and write a test for it
- send_fd(output_fd, result);
- } else {
- WRITE("E", 1);
- switch (result) {
- case -1:
- WRITE("S", 1);
- break;
- case -2:
- WRITE("B", 1);
- break;
- default:
- return 4;
- }
- int error(errno);
- WRITE(static_cast<char *>(static_cast<void *>(&error)),
- sizeof error);
- }
+ case 'S': // The "get socket" command
+ handleRequest(input_fd, output_fd, get_sock,
+ send_fd_fun, close_fun);
break;
- }
- DEFAULT
+
+ case 'T': // The "terminate" command
+ return;
+
+ default: // Don't recognise anything else
+ protocolError(output_fd);
}
}
}
diff --git a/src/bin/sockcreator/sockcreator.h b/src/bin/sockcreator/sockcreator.h
index ddf9a09..e5d4783 100644
--- a/src/bin/sockcreator/sockcreator.h
+++ b/src/bin/sockcreator/sockcreator.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,89 +12,136 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-/**
- * \file sockcreator.h
- * \short Socket creator functionality.
- *
- * This module holds the functionality of the socket creator. It is
- * a separate module from main to ease up the tests.
- */
+/// \file sockcreator.h
+/// \short Socket creator functionality.
+///
+/// This module holds the functionality of the socket creator. It is a separate
+/// module from main to make testing easier.
#ifndef __SOCKCREATOR_H
#define __SOCKCREATOR_H 1
#include <util/io/fd_share.h>
+#include <exceptions/exceptions.h>
#include <sys/types.h>
#include <sys/socket.h>
+#include <unistd.h>
namespace isc {
namespace socket_creator {
-/**
- * \short Create a socket and bind it.
- *
- * This is just a bundle of socket() and bind() calls. The sa_family of
- * bind_addr is used to determine the domain of the socket.
- *
- * \return The file descriptor of the newly created socket, if everything
- * goes well. A negative number is returned if an error occurs -
- * -1 if the socket() call fails or -2 if bind() fails. In case of error,
- * errno is set (or better, left intact from socket() or bind()).
- * \param type The type of socket to create (SOCK_STREAM, SOCK_DGRAM, etc).
- * \param bind_addr The address to bind.
- * \param addr_len The actual length of bind_addr.
- */
-int
-get_sock(const int type, struct sockaddr *bind_addr, const socklen_t addr_len);
+// Exception classes - the base class exception SocketCreatorError is caught
+// by main() and holds an exit code returned to the environment. The code
+// depends on the exact exception raised.
+class SocketCreatorError : public isc::Exception {
+public:
+ SocketCreatorError(const char* file, size_t line, const char* what,
+ int exit_code) :
+ isc::Exception(file, line, what), exit_code_(exit_code) {}
-/**
- * Type of the get_sock function, to pass it as parameter.
- */
-typedef
-int
-(*get_sock_t)(const int, struct sockaddr *, const socklen_t);
+ int getExitCode() const {
+ return (exit_code_);
+ }
-/**
- * Type of the send_fd() function, so it can be passed as a parameter.
- */
-typedef
-int
-(*send_fd_t)(const int, const int);
-
-/**
- * \short Infinite loop parsing commands and returning the sockets.
- *
- * This reads commands and socket descriptions from the input_fd
- * file descriptor, creates sockets and writes the results (socket or
- * error) to output_fd.
- *
- * Current errors are:
- * - 1: Read error
- * - 2: Write error
- * - 3: Protocol error (unknown command, etc)
- * - 4: Some internal inconsistency detected
- *
- * It terminates either if a command asks it to or when unrecoverable
- * error happens.
- *
- * \return Like a return value of a main - 0 means everything OK, anything
- * else is error.
- * \param input_fd Here is where it reads the commads.
- * \param output_fd Here is where it writes the results.
- * \param get_sock_fun The function that is used to create the sockets.
- * This should be left on the default value, the parameter is here
- * for testing purposes.
- * \param send_fd_fun The function that is used to send the socket over
- * a file descriptor. This should be left on the default value, it is
- * here for testing purposes.
- */
+private:
+ const int exit_code_; // Code returned to exit()
+};
+
+class ReadError : public SocketCreatorError {
+public:
+ ReadError(const char* file, size_t line, const char* what) :
+ SocketCreatorError(file, line, what, 1) {}
+};
+
+class WriteError : public SocketCreatorError {
+public:
+ WriteError(const char* file, size_t line, const char* what) :
+ SocketCreatorError(file, line, what, 2) {}
+};
+
+class ProtocolError : public SocketCreatorError {
+public:
+ ProtocolError(const char* file, size_t line, const char* what) :
+ SocketCreatorError(file, line, what, 3) {}
+};
+
+class InternalError : public SocketCreatorError {
+public:
+ InternalError(const char* file, size_t line, const char* what) :
+ SocketCreatorError(file, line, what, 4) {}
+};
+
+
+// Type of the close() function, so it can be passed as a parameter.
+// Argument is the same as that for close(2).
+typedef int (*close_t)(int);
+
+/// \short Create a socket and bind it.
+///
+/// This is just a bundle of socket() and bind() calls. The sa_family of
+/// bind_addr is used to determine the domain of the socket.
+///
+/// \param type The type of socket to create (SOCK_STREAM, SOCK_DGRAM, etc).
+/// \param bind_addr The address to bind.
+/// \param addr_len The actual length of bind_addr.
+/// \param close_fun The furction used to close a socket if there's an error
+/// after the creation.
+///
+/// \return The file descriptor of the newly created socket, if everything
+/// goes well. A negative number is returned if an error occurs -
+/// -1 if the socket() call fails or -2 if bind() fails. In case of
+/// error, errno is set (or left intact from socket() or bind()).
int
-run(const int input_fd, const int output_fd,
- const get_sock_t get_sock_fun = get_sock,
- const send_fd_t send_fd_fun = isc::util::io::send_fd);
+getSock(const int type, struct sockaddr* bind_addr, const socklen_t addr_len,
+ const close_t close_fun);
+
+// Define some types for functions used to perform socket-related operations.
+// These are typedefed so that alternatives can be passed through to the
+// main functions for testing purposes.
+
+// Type of the function to get a socket and to pass it as parameter.
+// Arguments are those described above for getSock().
+typedef int (*get_sock_t)(const int, struct sockaddr *, const socklen_t,
+ const close_t close_fun);
+
+// Type of the send_fd() function, so it can be passed as a parameter.
+// Arguments are the same as those of the send_fd() function.
+typedef int (*send_fd_t)(const int, const int);
+
+
+/// \brief Infinite loop parsing commands and returning the sockets.
+///
+/// This reads commands and socket descriptions from the input_fd file
+/// descriptor, creates sockets and writes the results (socket or error) to
+/// output_fd.
+///
+/// It terminates either if a command asks it to or when unrecoverable error
+/// happens.
+///
+/// \param input_fd File number of the stream from which the input commands
+/// are read.
+/// \param output_fd File number of the stream to which the response is
+/// written. The socket is sent as part of a control message associated
+/// with that stream.
+/// \param get_sock_fun The function that is used to create the sockets.
+/// This should be left on the default value, the parameter is here
+/// for testing purposes.
+/// \param send_fd_fun The function that is used to send the socket over
+/// a file descriptor. This should be left on the default value, it is
+/// here for testing purposes.
+/// \param close_fun The close function used to close sockets, coming from
+/// unistd.h. It can be overriden in tests.
+///
+/// \exception isc::socket_creator::ReadError Error reading from input
+/// \exception isc::socket_creator::WriteError Error writing to output
+/// \exception isc::socket_creator::ProtocolError Unrecognised command received
+/// \exception isc::socket_creator::InternalError Other error
+void
+run(const int input_fd, const int output_fd, get_sock_t get_sock_fun,
+ send_fd_t send_fd_fun, close_t close_fun);
-} // End of the namespaces
-}
+} // namespace socket_creator
+} // NAMESPACE ISC
#endif // __SOCKCREATOR_H
diff --git a/src/bin/sockcreator/tests/.gitignore b/src/bin/sockcreator/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/bin/sockcreator/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/bin/sockcreator/tests/Makefile.am b/src/bin/sockcreator/tests/Makefile.am
index 223e761..ef518b5 100644
--- a/src/bin/sockcreator/tests/Makefile.am
+++ b/src/bin/sockcreator/tests/Makefile.am
@@ -1,7 +1,8 @@
CLEANFILES = *.gcno *.gcda
-AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
-AM_CXXFLAGS = $(B10_CXXFLAGS)
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
if USE_STATIC_LINK
AM_LDFLAGS = -static
diff --git a/src/bin/sockcreator/tests/sockcreator_tests.cc b/src/bin/sockcreator/tests/sockcreator_tests.cc
index 73cbf48..9604567 100644
--- a/src/bin/sockcreator/tests/sockcreator_tests.cc
+++ b/src/bin/sockcreator/tests/sockcreator_tests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -17,11 +17,15 @@
#include <util/unittests/fork.h>
#include <util/io/fd.h>
+#include <boost/lexical_cast.hpp>
#include <gtest/gtest.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
+#include <arpa/inet.h>
#include <unistd.h>
+
+#include <iostream>
#include <cstring>
#include <cerrno>
@@ -29,245 +33,400 @@ using namespace isc::socket_creator;
using namespace isc::util::unittests;
using namespace isc::util::io;
+// The tests check both TCP and UDP sockets on IPv4 and IPv6.
+//
+// Essentially we need to check all four combinations of TCP/UDP and IPv4/IPv6.
+// The different address families (IPv4/IPv6) require different structures to
+// hold the address information, and so some common code is in the form of
+// templates (or overloads), parameterised on the structure type.
+//
+// The protocol is determined by an integer (SOCK_STREAM or SOCK_DGRAM) so
+// cannot be templated in the same way. Relevant check functions are
+// selected manually.
+
namespace {
-/*
- * Generic version of the creation of socket test. It just tries to
- * create the socket and checks the result is not negative (eg.
- * it is valid descriptor) and that it can listen.
- *
- * This is a macro so ASSERT_* does abort the TEST, not just the
- * function inside.
- */
-#define TEST_ANY_CREATE(SOCK_TYPE, ADDR_TYPE, ADDR_FAMILY, FAMILY_FIELD, \
- ADDR_SET, CHECK_SOCK) \
- do { \
- /*
- * This should create an address that binds on all interfaces
- * and lets the OS choose a free port.
- */ \
- struct ADDR_TYPE addr; \
- memset(&addr, 0, sizeof addr); \
- ADDR_SET(addr); \
- addr.FAMILY_FIELD = ADDR_FAMILY; \
- struct sockaddr *addr_ptr = static_cast<struct sockaddr *>( \
- static_cast<void *>(&addr)); \
- \
- int socket = get_sock(SOCK_TYPE, addr_ptr, sizeof addr); \
- /* Provide even nice error message. */ \
- ASSERT_GE(socket, 0) << "Couldn't create a socket of type " \
- #SOCK_TYPE " and family " #ADDR_FAMILY ", failed with " \
- << socket << " and error " << strerror(errno); \
- CHECK_SOCK(ADDR_TYPE, socket); \
- EXPECT_EQ(0, close(socket)); \
- } while (0)
-
-// Just helper macros
-#define INADDR_SET(WHAT) do { WHAT.sin_addr.s_addr = INADDR_ANY; } while (0)
-#define IN6ADDR_SET(WHAT) do { WHAT.sin6_addr = in6addr_loopback; } while (0)
-// If the get_sock returned something useful, listen must work
-#define TCP_CHECK(UNUSED, SOCKET) do { \
- EXPECT_EQ(0, listen(SOCKET, 1)); \
- } while (0)
-// More complicated with UDP, so we send a packet to ourselfs and se if it
-// arrives
-#define UDP_CHECK(ADDR_TYPE, SOCKET) do { \
- struct ADDR_TYPE addr; \
- memset(&addr, 0, sizeof addr); \
- struct sockaddr *addr_ptr = static_cast<struct sockaddr *>( \
- static_cast<void *>(&addr)); \
- \
- socklen_t len = sizeof addr; \
- ASSERT_EQ(0, getsockname(SOCKET, addr_ptr, &len)); \
- ASSERT_EQ(5, sendto(SOCKET, "test", 5, 0, addr_ptr, sizeof addr)) << \
- "Send failed with error " << strerror(errno) << " on socket " << \
- SOCKET; \
- char buffer[5]; \
- ASSERT_EQ(5, recv(SOCKET, buffer, 5, 0)) << \
- "Recv failed with error " << strerror(errno) << " on socket " << \
- SOCKET; \
- EXPECT_STREQ("test", buffer); \
- } while (0)
-
-/*
- * Several tests to ensure we can create the sockets.
- */
+// Set IP-version-specific fields.
+
+void
+setAddressFamilyFields(sockaddr_in* address) {
+ address->sin_family = AF_INET;
+ address->sin_addr.s_addr = INADDR_ANY;
+}
+
+void
+setAddressFamilyFields(sockaddr_in6* address) {
+ address->sin6_family = AF_INET6;
+ address->sin6_addr = in6addr_loopback;
+}
+
+// Socket has been opened, peform a check on it. The sole argument is the
+// socket descriptor. The TCP check is the same regardless of the address
+// family. The UDP check requires that the socket address be obtained so
+// is parameterised on the type of structure required to hold the address.
+
+void
+tcpCheck(const int socknum) {
+ // Sufficient to be able to listen on the socket.
+ EXPECT_EQ(0, listen(socknum, 1));
+}
+
+template <typename ADDRTYPE>
+void
+udpCheck(const int socknum) {
+ // UDP testing is more complicated than TCP: send a packet to ourselves and
+ // see if it arrives.
+
+ // Get details of the socket so that we can use it as the target of the
+ // sendto().
+ ADDRTYPE addr;
+ memset(&addr, 0, sizeof(addr));
+ sockaddr* addr_ptr = reinterpret_cast<sockaddr*>(&addr);
+ socklen_t len = sizeof(addr);
+ ASSERT_EQ(0, getsockname(socknum, addr_ptr, &len));
+
+ // Send the packet to ourselves and check we receive it.
+ ASSERT_EQ(5, sendto(socknum, "test", 5, 0, addr_ptr, sizeof(addr))) <<
+ "Send failed with error " << strerror(errno) << " on socket " <<
+ socknum;
+ char buffer[5];
+ ASSERT_EQ(5, recv(socknum, buffer, 5, 0)) <<
+ "Recv failed with error " << strerror(errno) << " on socket " <<
+ socknum;
+ EXPECT_STREQ("test", buffer);
+}
+
+// The check function (tcpCheck/udpCheck) is passed as a parameter to the test
+// code, so provide a conveniet typedef.
+typedef void (*socket_check_t)(const int);
+
+// Address-family-specific scoket checks.
+//
+// The first argument is used to select the overloaded check function.
+// The other argument is the socket descriptor number.
+
+// IPv4 check
+void addressFamilySpecificCheck(const sockaddr_in*, const int, const int) {
+}
+
+// IPv6 check
+void addressFamilySpecificCheck(const sockaddr_in6*, const int socknum,
+ const int socket_type)
+{
+ int options;
+ socklen_t len = sizeof(options);
+ EXPECT_EQ(0, getsockopt(socknum, IPPROTO_IPV6, IPV6_V6ONLY, &options,
+ &len));
+ EXPECT_NE(0, options);
+ if (socket_type == SOCK_DGRAM) {
+ // Some more checks for UDP - MTU
+#ifdef IPV6_USE_MIN_MTU /* RFC 3542, not too common yet*/
+ // use minimum MTU
+ EXPECT_EQ(0, getsockopt(socknum, IPPROTO_IPV6, IPV6_USE_MIN_MTU,
+ &options, &len)) << strerror(errno);
+ EXPECT_NE(0, options);
+#else
+ // We do not check for the IPV6_MTU, because while setting works (eg.
+ // the packets are fragmented correctly), the getting does not. If
+ // we try to getsockopt it, an error complaining it can't be accessed
+ // on unconnected socket is returned. If we try to connect it, the
+ // MTU of the interface is returned, not the one we set. So we live
+ // in belief it works because we examined the packet dump.
+#if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DONT)
+ // Turned off Path MTU discovery on IPv6/UDP sockets?
+ EXPECT_EQ(0, getsockopt(socknum, IPPROTO_IPV6, IPV6_MTU_DISCOVER,
+ &options, &len)) << strerror(errno);
+ EXPECT_EQ(IPV6_PMTUDISC_DONT, options);
+#endif
+#endif
+ }
+}
+
+// Just ignore the fd and pretend success. We close invalid fds in the tests.
+int
+closeIgnore(int) {
+ return (0);
+}
+
+// Generic version of the socket test. It creates the socket and checks that
+// it is a valid descriptor. The family-specific check functions are called
+// to check that the socket is valid. The function is parameterised according
+// to the structure used to hold the address.
+//
+// Arguments:
+// socket_type Type of socket to create (SOCK_DGRAM or SOCK_STREAM)
+// socket_check Associated check function - udpCheck() or tcpCheck()
+template <typename ADDRTYPE>
+void testAnyCreate(int socket_type, socket_check_t socket_check) {
+
+ // Create the socket.
+ ADDRTYPE addr;
+ memset(&addr, 0, sizeof(addr));
+ setAddressFamilyFields(&addr);
+ sockaddr* addr_ptr = reinterpret_cast<sockaddr*>(&addr);
+ const int socket = getSock(socket_type, addr_ptr, sizeof(addr),
+ closeIgnore);
+ ASSERT_GE(socket, 0) << "Couldn't create socket: failed with " <<
+ "return code " << socket << " and error " << strerror(errno);
+
+ // Perform socket-type-specific testing.
+ socket_check(socket);
+
+ // Do address-family-independent
+ int options;
+ socklen_t len = sizeof(options);
+ EXPECT_EQ(0, getsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &options, &len));
+ EXPECT_NE(0, options);
+
+ // ...and the address-family specific tests.
+ addressFamilySpecificCheck(&addr, socket, socket_type);
+
+ // Tidy up and exit.
+ EXPECT_EQ(0, close(socket));
+}
+
+
+// Several tests to ensure we can create the sockets.
TEST(get_sock, udp4_create) {
- TEST_ANY_CREATE(SOCK_DGRAM, sockaddr_in, AF_INET, sin_family, INADDR_SET,
- UDP_CHECK);
+ testAnyCreate<sockaddr_in>(SOCK_DGRAM, udpCheck<sockaddr_in>);
}
TEST(get_sock, tcp4_create) {
- TEST_ANY_CREATE(SOCK_STREAM, sockaddr_in, AF_INET, sin_family, INADDR_SET,
- TCP_CHECK);
+ testAnyCreate<sockaddr_in>(SOCK_STREAM, tcpCheck);
}
TEST(get_sock, udp6_create) {
- TEST_ANY_CREATE(SOCK_DGRAM, sockaddr_in6, AF_INET6, sin6_family,
- IN6ADDR_SET, UDP_CHECK);
+ testAnyCreate<sockaddr_in6>(SOCK_DGRAM, udpCheck<sockaddr_in6>);
}
TEST(get_sock, tcp6_create) {
- TEST_ANY_CREATE(SOCK_STREAM, sockaddr_in6, AF_INET6, sin6_family,
- IN6ADDR_SET, TCP_CHECK);
+ testAnyCreate<sockaddr_in6>(SOCK_STREAM, tcpCheck);
+}
+
+bool close_called(false);
+
+// You can use it as a close mockup. If you care about checking if it was really
+// called, you can use the close_called variable. But set it to false before the
+// test.
+int closeCall(int socket) {
+ close(socket);
+ close_called = true;
+ return (0);
}
-/*
- * Try to ask the get_sock function some nonsense and test if it
- * is able to report error.
- */
+// Ask the get_sock function for some nonsense and test if it is able to report
+// an error.
TEST(get_sock, fail_with_nonsense) {
- struct sockaddr addr;
- memset(&addr, 0, sizeof addr);
- ASSERT_LT(get_sock(0, &addr, sizeof addr), 0);
+ sockaddr addr;
+ memset(&addr, 0, sizeof(addr));
+ close_called = false;
+ ASSERT_EQ(-1, getSock(0, &addr, sizeof addr, closeCall));
+ ASSERT_FALSE(close_called); // The "socket" call should have failed already
+}
+
+// Bind should have failed here
+TEST(get_sock, fail_with_bind) {
+ sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = 1;
+ // No host should have this address on the interface, so it should not be
+ // possible to bind it.
+ addr.sin_addr.s_addr = inet_addr("192.0.2.1");
+ close_called = false;
+ ASSERT_EQ(-2, getSock(SOCK_STREAM, reinterpret_cast<sockaddr*>(&addr),
+ sizeof addr, closeCall));
+ ASSERT_TRUE(close_called); // The "socket" call should have failed already
}
-/*
- * Helper functions to pass to run during testing.
- */
+// The main run() function in the socket creator takes three functions to
+// get the socket, send information to it, and close it. These allow for
+// alternatives to the system functions to be used for testing.
+
+// Replacement getSock() function.
+// The return value indicates the result of checks and is encoded. Using LSB
+// bit numbering (least-significant bit is bit 0) then:
+//
+// bit 0: 1 if "type" is known, 0 otherwise
+// bit 1: 1 for UDP, 0 for TCP
+// bit 2: 1 if address family is known, 0 otherwise
+// bit 3: 1 for IPv6, 0 for IPv4
+// bit 4: 1 if port passed was valid
+//
+// Other possible return values are:
+//
+// -1: The simulated bind() call has failed
+// -2: The simulated socket() call has failed
int
-get_sock_dummy(const int type, struct sockaddr *addr, const socklen_t)
-{
- int result(0);
- int port(0);
- /*
- * We encode the type and address family into the int and return it.
- * Lets ignore the port and address for now
- * First bit is 1 if it is known type. Second tells if TCP or UDP.
- * The familly is similar - third bit is known address family,
- * the fourth is the family.
- */
+getSockDummy(const int type, struct sockaddr* addr, const socklen_t,
+ const close_t) {
+ int result = 0;
+ int port = 0;
+
+ // Validate type field
switch (type) {
case SOCK_STREAM:
- result += 1;
+ result |= 0x01;
break;
+
case SOCK_DGRAM:
- result += 3;
+ result |= 0x03;
break;
}
+
+ // Validate address family
switch (addr->sa_family) {
case AF_INET:
- result += 4;
- port = static_cast<struct sockaddr_in *>(
- static_cast<void *>(addr))->sin_port;
+ result |= 0x04;
+ port = reinterpret_cast<sockaddr_in*>(addr)->sin_port;
break;
+
case AF_INET6:
- result += 12;
- port = static_cast<struct sockaddr_in6 *>(
- static_cast<void *>(addr))->sin6_port;
+ result |= 0x0C;
+ port = reinterpret_cast<sockaddr_in6*>(addr)->sin6_port;
break;
}
- /*
- * The port should be 0xffff. If it's not, we change the result.
- * The port of 0xbbbb means bind should fail and 0xcccc means
- * socket should fail.
- */
+
+ // The port should be 0xffff. If it's not, we change the result.
+ // The port of 0xbbbb means bind should fail and 0xcccc means
+ // socket should fail.
if (port != 0xffff) {
errno = 0;
if (port == 0xbbbb) {
- return -2;
+ return (-2);
} else if (port == 0xcccc) {
- return -1;
+ return (-1);
} else {
- result += 16;
+ result |= 0x10;
}
}
- return result;
+ return (result);
}
+// Dummy send function - return data (the result of getSock()) to the destination.
int
-send_fd_dummy(const int destination, const int what)
-{
- /*
- * Make sure it is 1 byte so we know the length. We do not use more during
- * the test anyway.
- */
- char fd_data(what);
- if (!write_data(destination, &fd_data, 1)) {
- return -1;
- } else {
- return 0;
- }
+send_FdDummy(const int destination, const int what) {
+ // Make sure it is 1 byte so we know the length. We do not use more during
+ // the test anyway. And even with the LS bute, we can distinguish between
+ // the different results.
+ const char fd_data = what & 0xff;
+ const bool status = write_data(destination, &fd_data, sizeof(fd_data));
+ return (status ? 0 : -1);
}
-/*
- * Generic test that it works, with various inputs and outputs.
- * It uses different functions to create the socket and send it and pass
- * data to it and check it returns correct data back, to see if the run()
- * parses the commands correctly.
- */
-void run_test(const char *input_data, const size_t input_size,
- const char *output_data, const size_t output_size,
- bool should_succeed = true)
+// Generic test that it works, with various inputs and outputs.
+// It uses different functions to create the socket and send it and pass
+// data to it and check it returns correct data back, to see if the run()
+// parses the commands correctly.
+void runTest(const char* input_data, const size_t input_size,
+ const char* output_data, const size_t output_size,
+ bool should_succeed = true,
+ const close_t test_close = closeIgnore,
+ const send_fd_t send_fd = send_FdDummy)
{
- // Prepare the input feeder and output checker processes
- int input_fd(0), output_fd(0);
- pid_t input(provide_input(&input_fd, input_data, input_size)),
- output(check_output(&output_fd, output_data, output_size));
+ // Prepare the input feeder and output checker processes. The feeder
+ // process sends data from the client to run() and the checker process
+ // reads the response and checks it against the expected response.
+ int input_fd = 0;
+ const pid_t input = provide_input(&input_fd, input_data, input_size);
ASSERT_NE(-1, input) << "Couldn't start input feeder";
+
+ int output_fd = 0;
+ const pid_t output = check_output(&output_fd, output_data, output_size);
ASSERT_NE(-1, output) << "Couldn't start output checker";
+
// Run the body
- int result(run(input_fd, output_fd, get_sock_dummy, send_fd_dummy));
- // Close the pipes
- close(input_fd);
- close(output_fd);
- // Did it run well?
if (should_succeed) {
- EXPECT_EQ(0, result);
+ EXPECT_NO_THROW(run(input_fd, output_fd, getSockDummy, send_fd,
+ test_close));
} else {
- EXPECT_NE(0, result);
+ EXPECT_THROW(run(input_fd, output_fd, getSockDummy, send_fd,
+ test_close), isc::socket_creator::SocketCreatorError);
}
+
+ // Close the pipes
+ close(input_fd);
+ close(output_fd);
+
// Check the subprocesses say everything is OK too
EXPECT_TRUE(process_ok(input));
EXPECT_TRUE(process_ok(output));
}
-/*
- * Check it terminates successfully when asked to.
- */
+
+// Check it terminates successfully when asked to.
TEST(run, terminate) {
- run_test("T", 1, NULL, 0);
+ runTest("T", 1, NULL, 0);
}
-/*
- * Check it rejects incorrect input.
- */
+// Check it rejects incorrect input.
TEST(run, bad_input) {
- run_test("XXX", 3, "FI", 2, false);
+ runTest("XXX", 3, "FI", 2, false);
}
-/*
- * Check it correctly parses queries to create sockets.
- */
+// Check it correctly parses query stream to create sockets.
TEST(run, sockets) {
- run_test(
- "SU4\xff\xff\0\0\0\0" // This has 9 bytes
- "ST4\xff\xff\0\0\0\0" // This has 9 bytes
- "ST6\xff\xff\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // This has 21 bytes
- "SU6\xff\xff\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // This has 21 bytes
- "T", 61,
- "S\x07S\x05S\x0dS\x0f", 8);
+ runTest(
+ // Commands:
+ "SU4\xff\xff\0\0\0\0" // IPv4 UDP socket, port 0xffffff, address 0.0.0.0
+ "ST4\xff\xff\0\0\0\0" // IPv4 TCP socket, port 0xffffff, address 0.0.0.0
+ "ST6\xff\xff\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ // IPv6 UDP socket, port 0xffffff, address ::
+ "SU6\xff\xff\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ // IPv6 TCP socket, port 0xffffff, address ::
+ "T", // ... and terminate
+ 9 + 9 + 21 + 21 + 1, // Length of command string
+ "S\x07S\x05S\x0dS\x0f", // Response ("S" + LS byte of getSock() return)
+ 8); // Length of response
}
-/*
- * Check if failures of get_socket are handled correctly.
- */
+
+// Check if failures of get_socket are handled correctly.
TEST(run, bad_sockets) {
- // We need to construct the answer, but it depends on int length.
- size_t int_len(sizeof(int));
- size_t result_len(4 + 2 * int_len);
- char result[4 + sizeof(int) * 2];
- // Both errno parts should be 0
- memset(result, 0, result_len);
- // Fill the 2 control parts
+ // We need to construct the answer, but it depends on int length. We expect
+ // two failure answers in this test, each answer comprising two characters
+ // followed by the (int) errno value.
+ char result[2 * (2 + sizeof(int))];
+
+ // We expect the errno parts to be zero but the characters to depend on the
+ // exact failure.
+ memset(result, 0, sizeof(result));
strcpy(result, "EB");
- strcpy(result + 2 + int_len, "ES");
+ strcpy(result + 2 + sizeof(int), "ES");
+
// Run the test
- run_test(
- "SU4\xbb\xbb\0\0\0\0"
- "SU4\xcc\xcc\0\0\0\0"
- "T", 19,
- result, result_len);
+ runTest(
+ "SU4\xbb\xbb\0\0\0\0" // Port number will trigger simulated bind() fail
+ "SU4\xcc\xcc\0\0\0\0" // Port number will trigger simulated socket() fail
+ "T", // Terminate
+ 19, // Length of command string
+ result, sizeof(result));
}
+// A close that fails. (This causes an abort.)
+int
+closeFail(int) {
+ return (-1);
+}
+
+TEST(run, cant_close) {
+ runTest("SU4\xff\xff\0\0\0\0", 9,
+ "S\x07", 2,
+ false, closeFail);
}
+
+// A send of the file descriptor that fails. In this case we expect the client
+// to receive the "S" indicating that the descriptor is being sent and nothing
+// else. This causes an abort.
+int
+sendFDFail(const int, const int) {
+ return (FD_SYSTEM_ERROR);
+}
+
+TEST(run, cant_send_fd) {
+ runTest("SU4\xff\xff\0\0\0\0", 9,
+ "S", 1,
+ false, closeIgnore, sendFDFail);
+}
+
+} // Anonymous namespace
diff --git a/src/bin/stats/.gitignore b/src/bin/stats/.gitignore
new file mode 100644
index 0000000..7ff3797
--- /dev/null
+++ b/src/bin/stats/.gitignore
@@ -0,0 +1,4 @@
+/b10-stats
+/b10-stats-httpd
+/stats.py
+/stats_httpd.py
diff --git a/src/bin/stats/b10-stats-httpd.8 b/src/bin/stats/b10-stats-httpd.8
index 1206e1d..6e53543 100644
--- a/src/bin/stats/b10-stats-httpd.8
+++ b/src/bin/stats/b10-stats-httpd.8
@@ -1,22 +1,13 @@
'\" t
.\" Title: b10-stats-httpd
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
-.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
-.\" Date: Mar 8, 2011
+.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
+.\" Date: February 28, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-STATS\-HTTPD" "8" "Mar 8, 2011" "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 "B10\-STATS\-HTTPD" "8" "February 28, 2012" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -110,7 +101,9 @@ with its PID\&.
.RS 4
exits the
\fBb10\-stats\-httpd\fR
-process\&. (Note that the BIND 10 boss process will restart this service\&.)
+process\&. This has an optional
+\fIpid\fR
+argument to select the process ID to stop\&. (Note that the BIND 10 boss process may restart this service if configured\&.)
.RE
.SH "SEE ALSO"
.PP
@@ -125,8 +118,8 @@ BIND 10 Guide\&.
.PP
\fBb10\-stats\-httpd\fR
-was designed and implemented by Naoki Kambe of JPRS in Mar 2011\&.
+was designed and implemented by Naoki Kambe of JPRS in March 2011\&.
.SH "COPYRIGHT"
.br
-Copyright \(co 2011 Internet Systems Consortium, Inc. ("ISC")
+Copyright \(co 2011-2012 Internet Systems Consortium, Inc. ("ISC")
.br
diff --git a/src/bin/stats/b10-stats-httpd.xml b/src/bin/stats/b10-stats-httpd.xml
index c8df9b8..a372244 100644
--- a/src/bin/stats/b10-stats-httpd.xml
+++ b/src/bin/stats/b10-stats-httpd.xml
@@ -2,7 +2,7 @@
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[<!ENTITY mdash "—">]>
<!--
- - Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>Mar 8, 2011</date>
+ <date>February 28, 2012</date>
</refentryinfo>
<refmeta>
@@ -36,7 +36,7 @@
<docinfo>
<copyright>
- <year>2011</year>
+ <year>2011-2012</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
@@ -171,9 +171,12 @@
<term><command>shutdown</command></term>
<listitem>
<para>
- exits the <command>b10-stats-httpd</command> process. (Note that
- the BIND 10 boss process will restart this service.)
- </para>
+ exits the <command>b10-stats-httpd</command> process.
+ This has an optional <varname>pid</varname> argument to
+ select the process ID to stop.
+ (Note that the BIND 10 boss process may restart this service
+ if configured.)
+ </para>
</listitem>
</varlistentry>
</variablelist>
@@ -205,7 +208,7 @@
<title>HISTORY</title>
<para>
<command>b10-stats-httpd</command> was designed and implemented by Naoki
- Kambe of JPRS in Mar 2011.
+ Kambe of JPRS in March 2011.
</para>
</refsect1>
</refentry><!--
diff --git a/src/bin/stats/b10-stats.8 b/src/bin/stats/b10-stats.8
index 0204ca1..e72ec0e 100644
--- a/src/bin/stats/b10-stats.8
+++ b/src/bin/stats/b10-stats.8
@@ -2,12 +2,12 @@
.\" Title: b10-stats
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: August 11, 2011
+.\" Date: March 1, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-STATS" "8" "August 11, 2011" "BIND10" "BIND10"
+.TH "B10\-STATS" "8" "March 1, 2012" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -59,29 +59,25 @@ command does not have any configurable settings\&.
The configuration commands are:
.PP
-
-\fBremove\fR
-removes the named statistics name and data\&.
-.PP
-
-
-\fBreset\fR
-will reset all statistics data to default values except for constant names\&. This may re\-add previously removed statistics names\&.
-.PP
-
\fBset\fR
+will set new statistics data specified in arguments\&. Statistics data to be set and the module name which owns statistics data are required in argument\&. Pid of the module in argument is optional\&.
.PP
\fBshow\fR
will send the statistics data in JSON format\&. By default, it outputs all the statistics data it has collected\&. An optional item name may be specified to receive individual output\&.
.PP
+\fBshowschema\fR
+will send the schema of the statistics data in JSON format\&. The output is equivalent to the statistics part of
+stats\&.spec\&.
+.PP
+
\fBshutdown\fR
will shutdown the
\fBb10\-stats\fR
-process\&. (Note that the
-\fBbind10\fR
-parent may restart it\&.)
+process\&. This has an optional
+\fIpid\fR
+argument to select the process ID to stop\&. (Note that the BIND 10 boss process may restart this service if configured\&.)
.PP
\fBstatus\fR
@@ -90,25 +86,22 @@ simply indicates that the daemon is running\&.
.PP
The
\fBb10\-stats\fR
-daemon contains these statistics:
+daemon contains these
+\(lqStats\(rq
+statistics:
.PP
-report_time
-.RS 4
-The latest report date and time in ISO 8601 format\&.
-.RE
-.PP
-stats\&.boot_time
+boot_time
.RS 4
The date and time when this daemon was started in ISO 8601 format\&. This is a constant which can\'t be reset except by restarting
\fBb10\-stats\fR\&.
.RE
.PP
-stats\&.last_update_time
+last_update_time
.RS 4
The date and time (in ISO 8601 format) when this daemon last received data from another component\&.
.RE
.PP
-stats\&.lname
+lname
.RS 4
This is the name used for the
\fBb10\-msgq\fR
@@ -116,14 +109,19 @@ command\-control channel\&. (This is a constant which can\'t be reset except by
\fBb10\-stats\fR\&.)
.RE
.PP
-stats\&.start_time
+report_time
+.RS 4
+The latest report date and time in ISO 8601 format\&.
+.RE
+.PP
+start_time
.RS 4
This is the date and time (in ISO 8601 format) when this daemon started collecting data\&.
.RE
.PP
-stats\&.timestamp
+timestamp
.RS 4
-The current date and time represented in seconds since UNIX epoch (1970\-01\-01T0 0:00:00Z) with precision (delimited with a period) up to one hundred thousandth of second\&.
+The current date and time represented in seconds since UNIX epoch (1970\-01\-01T00:00:00Z) with precision (delimited with a period) up to one hundred thousandth of second\&.
.RE
.PP
See other manual pages for explanations for their statistics that are kept track by
@@ -150,5 +148,5 @@ The
daemon was initially designed and implemented by Naoki Kambe of JPRS in October 2010\&.
.SH "COPYRIGHT"
.br
-Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")
+Copyright \(co 2010-2012 Internet Systems Consortium, Inc. ("ISC")
.br
diff --git a/src/bin/stats/b10-stats.xml b/src/bin/stats/b10-stats.xml
index 13ada7a..b353f8f 100644
--- a/src/bin/stats/b10-stats.xml
+++ b/src/bin/stats/b10-stats.xml
@@ -2,7 +2,7 @@
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[<!ENTITY mdash "—">]>
<!--
- - Copyright (C) 2010,2011 Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2010-2012 Internet Systems Consortium, Inc. ("ISC")
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>August 11, 2011</date>
+ <date>March 1, 2012</date>
</refentryinfo>
<refmeta>
@@ -36,7 +36,7 @@
<docinfo>
<copyright>
- <year>2010</year>
+ <year>2010-2012</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
@@ -101,20 +101,10 @@
</para>
<para>
-<!-- TODO: remove is removed in trac930 -->
- <command>remove</command> removes the named statistics name and data.
- </para>
-
- <para>
-<!-- TODO: reset is removed in trac930 -->
- <command>reset</command> will reset all statistics data to
- default values except for constant names.
- This may re-add previously removed statistics names.
- </para>
-
- <para>
- <command>set</command>
-<!-- TODO: document this -->
+ <command>set</command> will set new statistics data specified in
+ arguments. Statistics data to be set and the module name which owns
+ statistics data are required in argument. Pid of the module in argument
+ is optional.
</para>
<para>
@@ -124,12 +114,19 @@
An optional item name may be specified to receive individual output.
</para>
-<!-- TODO: document showschema -->
+ <para>
+ <command>showschema</command> will send the schema of the statistics data
+ in JSON format. The output is equivalent to the statistics part
+ of <filename>stats.spec</filename>.
+ </para>
<para>
<command>shutdown</command> will shutdown the
<command>b10-stats</command> process.
- (Note that the <command>bind10</command> parent may restart it.)
+ This has an optional <varname>pid</varname> argument to
+ select the process ID to stop.
+ (Note that the BIND 10 boss process may restart this service
+ if configured.)
</para>
<para>
@@ -143,20 +140,15 @@
<title>STATISTICS DATA</title>
<para>
- The <command>b10-stats</command> daemon contains these statistics:
+ The <command>b10-stats</command> daemon contains these
+ <quote>Stats</quote> statistics:
</para>
<variablelist>
- <varlistentry>
- <term>report_time</term>
-<!-- TODO: why not named stats.report_time? -->
- <listitem><simpara>The latest report date and time in
- ISO 8601 format.</simpara></listitem>
- </varlistentry>
<varlistentry>
- <term>stats.boot_time</term>
+ <term>boot_time</term>
<listitem><simpara>The date and time when this daemon was
started in ISO 8601 format.
This is a constant which can't be reset except by restarting
@@ -165,14 +157,14 @@
</varlistentry>
<varlistentry>
- <term>stats.last_update_time</term>
+ <term>last_update_time</term>
<listitem><simpara>The date and time (in ISO 8601 format)
when this daemon last received data from another component.
</simpara></listitem>
</varlistentry>
<varlistentry>
- <term>stats.lname</term>
+ <term>lname</term>
<listitem><simpara>This is the name used for the
<command>b10-msgq</command> command-control channel.
(This is a constant which can't be reset except by restarting
@@ -181,16 +173,22 @@
</varlistentry>
<varlistentry>
- <term>stats.start_time</term>
+ <term>report_time</term>
+ <listitem><simpara>The latest report date and time in
+ ISO 8601 format.</simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>start_time</term>
<listitem><simpara>This is the date and time (in ISO 8601 format)
when this daemon started collecting data.
</simpara></listitem>
</varlistentry>
<varlistentry>
- <term>stats.timestamp</term>
+ <term>timestamp</term>
<listitem><simpara>The current date and time represented in
- seconds since UNIX epoch (1970-01-01T0 0:00:00Z) with
+ seconds since UNIX epoch (1970-01-01T00:00:00Z) with
precision (delimited with a period) up to
one hundred thousandth of second.</simpara></listitem>
</varlistentry>
diff --git a/src/bin/stats/stats-httpd.spec b/src/bin/stats/stats-httpd.spec
index 6307135..783611d 100644
--- a/src/bin/stats/stats-httpd.spec
+++ b/src/bin/stats/stats-httpd.spec
@@ -47,7 +47,13 @@
{
"command_name": "shutdown",
"command_description": "Shut down the stats httpd",
- "command_args": []
+ "command_args": [
+ {
+ "item_name": "pid",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
}
]
}
diff --git a/src/bin/stats/stats.py.in b/src/bin/stats/stats.py.in
index 51c4e09..fd59c3c 100755
--- a/src/bin/stats/stats.py.in
+++ b/src/bin/stats/stats.py.in
@@ -129,6 +129,8 @@ class Stats:
self.module_name = self.mccs.get_module_spec().get_module_name()
self.modules = {}
self.statistics_data = {}
+ # statistics data by each pid
+ self.statistics_data_bypid = {}
# get commands spec
self.commands_spec = self.mccs.get_module_spec().get_commands_spec()
# add event handler related command_handler of ModuleCCSession
@@ -184,8 +186,11 @@ class Stats:
raise StatsError("stats spec file is incorrect: "
+ ", ".join(errors))
- while self.running:
- self.mccs.check_command(False)
+ try:
+ while self.running:
+ self.mccs.check_command(False)
+ finally:
+ self.mccs.send_stopping()
def config_handler(self, new_config):
"""
@@ -262,36 +267,129 @@ class Stats:
+ "owner: " + str(owner) + ", "
+ "name: " + str(name))
- def update_statistics_data(self, owner=None, **data):
+ def update_statistics_data(self, owner=None, pid=-1, **data):
"""
change statistics date of specified module into specified
data. It updates information of each module first, and it
updates statistics data. If specified data is invalid for
statistics spec of specified owner, it returns a list of error
- messeges. If there is no error or if neither owner nor data is
- specified in args, it returns None.
+ messages. If there is no error or if neither owner nor data is
+ specified in args, it returns None. pid is the process id of
+ the sender module in order for stats to identify which
+ instance sends statistics data in the situation that multiple
+ instances are working.
"""
+ # Note:
+ # The fix of #1751 is for multiple instances working. It is
+ # assumed here that they send different statistics data with
+ # each PID. Stats should save their statistics data by
+ # PID. The statistics data, which is the existing variable, is
+ # preserved by accumlating from statistics data by PID. This
+ # is an ad-hoc fix because administrators can not see
+ # statistics by each instance via bindctl or HTTP/XML. These
+ # interfaces aren't changed in this fix.
+
+ def _accum_bymodule(statistics_data_bypid):
+ # This is an internal function for the superordinate
+ # function. It accumulates statistics data of each PID by
+ # module. It returns the accumulation result.
+ def _accum(a, b):
+ # If the first arg is dict or list type, two values
+ # would be merged and accumlated.
+ if type(a) is dict:
+ return dict([ (k, _accum(v, b[k])) \
+ if k in b else (k, v) \
+ for (k, v) in a.items() ] \
+ + [ (k, v) \
+ for (k, v) in b.items() \
+ if k not in a ])
+ elif type(a) is list:
+ return [ _accum(a[i], b[i]) \
+ if len(b) > i else a[i] \
+ for i in range(len(a)) ] \
+ + [ b[i] \
+ for i in range(len(b)) \
+ if len(a) <= i ]
+ # If the first arg is integer or float type, two
+ # values are just added.
+ elif type(a) is int or type(a) is float:
+ return a + b
+ # If the first arg is str or other types than above,
+ # then it just returns the first arg which is assumed
+ # to be the newer value.
+ return a
+ ret = {}
+ for data in statistics_data_bypid.values():
+ ret.update(_accum(data, ret))
+ return ret
+
+ # Firstly, it gets default statistics data in each spec file.
self.update_modules()
statistics_data = {}
for (name, module) in self.modules.items():
value = get_spec_defaults(module.get_statistics_spec())
if module.validate_statistics(True, value):
statistics_data[name] = value
- for (name, value) in self.statistics_data.items():
- if name in statistics_data:
- statistics_data[name].update(value)
- else:
- statistics_data[name] = value
self.statistics_data = statistics_data
+
+ # If the "owner" and "data" arguments in this function are
+ # specified, then the variable of statistics data of each pid
+ # would be updated.
+ errors = []
if owner and data:
- errors = []
try:
if self.modules[owner].validate_statistics(False, data, errors):
- self.statistics_data[owner].update(data)
- return
+ if owner in self.statistics_data_bypid:
+ if pid in self.statistics_data_bypid[owner]:
+ self.statistics_data_bypid[owner][pid].update(data)
+ else:
+ self.statistics_data_bypid[owner][pid] = data
+ else:
+ self.statistics_data_bypid[owner] = { pid : data }
except KeyError:
errors.append("unknown module name: " + str(owner))
- return errors
+
+ # If there are inactive instances, which was actually running
+ # on the system before, their statistics data would be
+ # removed. To find inactive instances, it invokes the
+ # "show_processes" command to Boss via the cc session. Then it
+ # gets active instance list and compares its PIDs with PIDs in
+ # statistics data which it already has. If inactive instances
+ # are found, it would remove their statistics data.
+ seq = self.cc_session.group_sendmsg(
+ isc.config.ccsession.create_command("show_processes", None),
+ "Boss")
+ (answer, env) = self.cc_session.group_recvmsg(False, seq)
+ if answer:
+ (rcode, value) = isc.config.ccsession.parse_answer(answer)
+ if rcode == 0:
+ if type(value) is list and len(value) > 0 \
+ and type(value[0]) is list and len(value[0]) > 1:
+ mlist = [ k for k in self.statistics_data_bypid.keys() ]
+ for m in mlist:
+ # PID list which it has before except for -1
+ plist1 = [ p for p in self.statistics_data_bypid[m]\
+ .keys() if p != -1]
+ # PID list of active instances which is
+ # received from Boss
+ plist2 = [ v[0] for v in value \
+ if v[1].lower().find(m.lower()) \
+ >= 0 ]
+ # get inactive instance list by the difference
+ # between plist1 and plist2
+ nplist = set(plist1).difference(set(plist2))
+ for p in nplist:
+ self.statistics_data_bypid[m].pop(p)
+ if self.statistics_data_bypid[m]:
+ if m in self.statistics_data:
+ self.statistics_data[m].update(
+ _accum_bymodule(
+ self.statistics_data_bypid[m]))
+ # remove statistics data of the module with no
+ # PID
+ else:
+ self.statistics_data_bypid.pop(m)
+ if errors: return errors
def command_status(self):
"""
@@ -301,9 +399,11 @@ class Stats:
return isc.config.create_answer(
0, "Stats is up. (PID " + str(os.getpid()) + ")")
- def command_shutdown(self):
+ def command_shutdown(self, pid=None):
"""
handle shutdown command
+
+ The pid argument is ignored, it is here to match the signature.
"""
logger.info(STATS_RECEIVED_SHUTDOWN_COMMAND)
self.running = False
@@ -374,11 +474,11 @@ class Stats:
1, "specified arguments are incorrect: " \
+ "owner: " + str(owner) + ", name: " + str(name))
- def command_set(self, owner, data):
+ def command_set(self, owner, pid=-1, data={}):
"""
handle set command
"""
- errors = self.update_statistics_data(owner, **data)
+ errors = self.update_statistics_data(owner, pid, **data)
if errors:
return isc.config.create_answer(
1, "errors while setting statistics data: " \
diff --git a/src/bin/stats/stats.spec b/src/bin/stats/stats.spec
index e716b62..e25dfad 100644
--- a/src/bin/stats/stats.spec
+++ b/src/bin/stats/stats.spec
@@ -12,7 +12,13 @@
{
"command_name": "shutdown",
"command_description": "Shut down the stats module",
- "command_args": []
+ "command_args": [
+ {
+ "item_name": "pid",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
},
{
"command_name": "show",
@@ -66,6 +72,13 @@
"item_description": "module name of the owner of the statistics data"
},
{
+ "item_name": "pid",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": -1,
+ "item_description": "process id of the owner module"
+ },
+ {
"item_name": "data",
"item_type": "map",
"item_optional": false,
diff --git a/src/bin/stats/stats_httpd.py.in b/src/bin/stats/stats_httpd.py.in
index f265abb..7e4da96 100644
--- a/src/bin/stats/stats_httpd.py.in
+++ b/src/bin/stats/stats_httpd.py.in
@@ -189,7 +189,12 @@ class StatsHttpd:
self.load_config()
self.http_addrs = []
self.mccs.start()
- self.open_httpd()
+ try:
+ self.open_httpd()
+ except HttpServerError:
+ # if some exception, e.g. address in use, is raised, then it closes mccs and httpd
+ self.close_mccs()
+ raise
def open_mccs(self):
"""Opens a ModuleCCSession object"""
@@ -203,6 +208,7 @@ class StatsHttpd:
"""Closes a ModuleCCSession object"""
if self.mccs is None:
return
+ self.mccs.send_stopping()
logger.debug(DBG_STATHTTPD_INIT, STATHTTPD_CLOSING_CC_SESSION)
self.mccs.close()
diff --git a/src/bin/stats/tests/b10-stats-httpd_test.py b/src/bin/stats/tests/b10-stats-httpd_test.py
index b6847bd..57abed9 100644
--- a/src/bin/stats/tests/b10-stats-httpd_test.py
+++ b/src/bin/stats/tests/b10-stats-httpd_test.py
@@ -37,7 +37,10 @@ import random
import isc
import stats_httpd
import stats
-from test_utils import BaseModules, ThreadingServerManager, MyStats, MyStatsHttpd, SignalHandler, send_command, send_shutdown
+from test_utils import BaseModules, ThreadingServerManager, MyStats,\
+ MyStatsHttpd, SignalHandler,\
+ send_command, send_shutdown
+from isc.testutils.ccsession_mock import MockModuleCCSession
DUMMY_DATA = {
'Boss' : {
@@ -673,10 +676,34 @@ class TestStatsHttpd(unittest.TestCase):
self.assertTrue('address' in self.stats_httpd.config['listen_on'][0])
self.assertTrue('port' in self.stats_httpd.config['listen_on'][0])
self.assertTrue(server_address in set(self.stats_httpd.http_addrs))
+ ans = send_command(
+ isc.config.ccsession.COMMAND_GET_MODULE_SPEC,
+ "ConfigManager", {"module_name":"StatsHttpd"})
+ # assert StatsHttpd is added to ConfigManager
+ self.assertNotEqual(ans, (0,{}))
+ self.assertTrue(ans[1]['module_name'], 'StatsHttpd')
+
+ def test_init_hterr(self):
+ orig_open_httpd = stats_httpd.StatsHttpd.open_httpd
+ def err_open_httpd(arg): raise stats_httpd.HttpServerError
+ stats_httpd.StatsHttpd.open_httpd = err_open_httpd
+ self.assertRaises(stats_httpd.HttpServerError, stats_httpd.StatsHttpd)
+ ans = send_command(
+ isc.config.ccsession.COMMAND_GET_MODULE_SPEC,
+ "ConfigManager", {"module_name":"StatsHttpd"})
+ # assert StatsHttpd is removed from ConfigManager
+ self.assertEqual(ans, (0,{}))
+ stats_httpd.StatsHttpd.open_httpd = orig_open_httpd
def test_openclose_mccs(self):
self.stats_httpd = MyStatsHttpd(get_availaddr())
+ mccs = MockModuleCCSession()
+ self.stats_httpd.mccs = mccs
+ self.assertFalse(self.stats_httpd.mccs.stopped)
+ self.assertFalse(self.stats_httpd.mccs.closed)
self.stats_httpd.close_mccs()
+ self.assertTrue(mccs.stopped)
+ self.assertTrue(mccs.closed)
self.assertEqual(self.stats_httpd.mccs, None)
self.stats_httpd.open_mccs()
self.assertIsNotNone(self.stats_httpd.mccs)
diff --git a/src/bin/stats/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py
index 3c8599a..5262b8a 100644
--- a/src/bin/stats/tests/b10-stats_test.py
+++ b/src/bin/stats/tests/b10-stats_test.py
@@ -31,6 +31,7 @@ import imp
import stats
import isc.cc.session
from test_utils import BaseModules, ThreadingServerManager, MyStats, SignalHandler, send_command, send_shutdown
+from isc.testutils.ccsession_mock import MockModuleCCSession
class TestUtilties(unittest.TestCase):
items = [
@@ -201,9 +202,20 @@ class TestStats(unittest.TestCase):
self.assertEqual(send_command("status", "Stats"),
(0, "Stats is up. (PID " + str(os.getpid()) + ")"))
self.assertTrue(self.stats.running)
+ # Due to a race-condition related to the threads used in these
+ # tests, use of the mock session and the stopped check (see
+ # below), are temporarily disabled
+ # See ticket #1668
+ # Override moduleCCSession so we can check if send_stopping is called
+ #self.stats.mccs = MockModuleCCSession()
self.assertEqual(send_shutdown("Stats"), (0, None))
self.assertFalse(self.stats.running)
- self.stats_server.shutdown()
+ # Call server.shutdown with argument True so the thread.join() call
+ # blocks and we are sure the main loop has finished (and set
+ # mccs.stopped)
+ self.stats_server.shutdown(True)
+ # Also temporarily disabled for #1668, see above
+ #self.assertTrue(self.stats.mccs.stopped)
# start with err
self.stats = stats.Stats()
@@ -361,6 +373,68 @@ class TestStats(unittest.TestCase):
self.assertEqual(self.stats.update_statistics_data(owner='Dummy', foo='bar'),
['unknown module name: Dummy'])
+ def test_update_statistics_data_withpid(self):
+ # one pid of Auth
+ self.stats.update_statistics_data(owner='Auth',
+ pid=9999,
+ **{'queries.tcp':1001})
+ self.assertTrue('Auth' in self.stats.statistics_data)
+ self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
+ self.assertTrue('Auth' in self.stats.statistics_data_bypid)
+ self.assertTrue(9999 in self.stats.statistics_data_bypid['Auth'])
+ self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9999])
+ self.assertEqual(self.stats.statistics_data_bypid['Auth'][9999]['queries.tcp'], 1001)
+ self.assertEqual(self.stats.statistics_data_bypid,
+ {'Auth': {9999: {'queries.tcp': 1001}}})
+ # non-existent pid of Auth, but no changes in statistics data
+ self.stats.update_statistics_data(owner='Auth',
+ pid=10000,
+ **{'queries.tcp':2001})
+ self.assertTrue('Auth' in self.stats.statistics_data)
+ self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
+ self.assertTrue('Auth' in self.stats.statistics_data_bypid)
+ self.assertTrue(9999 in self.stats.statistics_data_bypid['Auth'])
+ self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9999])
+ self.assertEqual(self.stats.statistics_data_bypid['Auth'][9999]['queries.tcp'], 1001)
+ self.assertEqual(self.stats.statistics_data_bypid,
+ {'Auth': {9999: {'queries.tcp': 1001}}})
+ # kill running Auth, then statistics is reset
+ self.assertEqual(self.base.boss.server.pid_list[0][0], 9999)
+ killed = self.base.boss.server.pid_list.pop(0)
+ self.stats.update_statistics_data()
+ self.assertTrue('Auth' in self.stats.statistics_data)
+ self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+ self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 0)
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'], 0)
+ self.assertFalse('Auth' in self.stats.statistics_data_bypid)
+ # restore statistics data of killed auth
+ self.base.boss.server.pid_list = [ killed ] + self.base.boss.server.pid_list[:]
+ self.stats.update_statistics_data(owner='Auth',
+ pid=9999,
+ **{'queries.tcp':1001})
+ # another pid of Auth
+ self.stats.update_statistics_data(owner='Auth',
+ pid=9998,
+ **{'queries.tcp':1002,
+ 'queries.udp':1003})
+ self.assertTrue('Auth' in self.stats.statistics_data)
+ self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+ self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 2003)
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'], 1003)
+ self.assertTrue('Auth' in self.stats.statistics_data_bypid)
+ self.assertTrue(9999 in self.stats.statistics_data_bypid['Auth'])
+ self.assertTrue(9998 in self.stats.statistics_data_bypid['Auth'])
+ self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9999])
+ self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9998])
+ self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9998])
+ self.assertEqual(self.stats.statistics_data_bypid['Auth'][9999]['queries.tcp'], 1001)
+ self.assertEqual(self.stats.statistics_data_bypid['Auth'][9998]['queries.tcp'], 1002)
+ self.assertEqual(self.stats.statistics_data_bypid['Auth'][9998]['queries.udp'], 1003)
+
def test_commands(self):
# status
self.assertEqual(self.stats.command_status(),
@@ -698,6 +772,123 @@ class TestStats(unittest.TestCase):
self.assertRaises(stats.StatsError,
self.stats.command_set, owner='Stats', data={ 'dummy' : '_xxxx_yyyy_zzz_' })
+ def test_command_set_withpid(self):
+ # one pid of Auth
+ retval = isc.config.ccsession.parse_answer(
+ self.stats.command_set(owner='Auth',
+ pid=9997,
+ data={ 'queries.tcp' : 1001,
+ 'queries.perzone':
+ [{ 'zonename': 'test1.example',
+ 'queries.tcp': 1 },
+ { 'zonename': 'test2.example',
+ 'queries.tcp': 2,
+ 'queries.udp': 3 }]}))
+ self.assertEqual(retval, (0,None))
+ self.assertTrue('Auth' in self.stats.statistics_data)
+ self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.perzone'],
+ [{ 'zonename': 'test1.example',
+ 'queries.tcp': 1 },
+ { 'zonename': 'test2.example',
+ 'queries.tcp': 2,
+ 'queries.udp': 3 }])
+ self.assertTrue('Stats' in self.stats.statistics_data)
+ self.assertTrue('last_update_time' in self.stats.statistics_data['Stats'])
+ self.assertTrue('Auth' in self.stats.statistics_data_bypid)
+ self.assertTrue(9997 in self.stats.statistics_data_bypid['Auth'])
+ self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9997])
+ self.assertTrue('queries.perzone' in self.stats.statistics_data_bypid['Auth'][9997])
+ self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.tcp'], 1001)
+ self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.perzone'],
+ [{ 'zonename': 'test1.example',
+ 'queries.tcp': 1 },
+ { 'zonename': 'test2.example',
+ 'queries.tcp': 2,
+ 'queries.udp': 3 }])
+ # non-existent pid of Auth, but no changes in statistics data
+ retval = isc.config.ccsession.parse_answer(
+ self.stats.command_set(owner='Auth',
+ pid=10000,
+ data={ 'queries.tcp' : 2001,
+ 'queries.perzone':
+ [{ 'zonename': 'test1.example',
+ 'queries.tcp': 101 },
+ { 'zonename': 'test2.example',
+ 'queries.tcp': 102,
+ 'queries.udp': 103 }]}))
+ self.assertEqual(retval, (0,None))
+ self.assertTrue('Auth' in self.stats.statistics_data)
+ self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.perzone'],
+ [{ 'zonename': 'test1.example',
+ 'queries.tcp': 1 },
+ { 'zonename': 'test2.example',
+ 'queries.tcp': 2,
+ 'queries.udp': 3 }])
+ self.assertTrue('Auth' in self.stats.statistics_data_bypid)
+ self.assertTrue(9997 in self.stats.statistics_data_bypid['Auth'])
+ self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9997])
+ self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.tcp'], 1001)
+ self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.perzone'],
+ [{ 'zonename': 'test1.example',
+ 'queries.tcp': 1 },
+ { 'zonename': 'test2.example',
+ 'queries.tcp': 2,
+ 'queries.udp': 3 }])
+ # another pid of Auth
+ retval = isc.config.ccsession.parse_answer(
+ self.stats.command_set(owner='Auth',
+ pid=9996,
+ data={ 'queries.tcp' : 1002,
+ 'queries.udp' : 1003,
+ 'queries.perzone':
+ [{ 'zonename': 'test1.example',
+ 'queries.tcp': 10,
+ 'queries.udp': 11},
+ { 'zonename': 'test2.example',
+ 'queries.tcp': 12,
+ 'queries.udp': 13 }]}))
+ self.assertEqual(retval, (0,None))
+ self.assertTrue('Auth' in self.stats.statistics_data)
+ self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+ self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
+ self.assertTrue('queries.perzone' in self.stats.statistics_data['Auth'])
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 2003)
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'], 1003)
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.perzone'],
+ [{ 'zonename': 'test1.example',
+ 'queries.tcp': 11,
+ 'queries.udp': 11},
+ { 'zonename': 'test2.example',
+ 'queries.tcp': 14,
+ 'queries.udp': 16 }])
+ self.assertTrue('Auth' in self.stats.statistics_data_bypid)
+ self.assertTrue(9997 in self.stats.statistics_data_bypid['Auth'])
+ self.assertTrue(9996 in self.stats.statistics_data_bypid['Auth'])
+ self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9997])
+ self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9996])
+ self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9996])
+ self.assertTrue('queries.perzone' in self.stats.statistics_data_bypid['Auth'][9996])
+ self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.tcp'], 1001)
+ self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.perzone'],
+ [{ 'zonename': 'test1.example',
+ 'queries.tcp': 1 },
+ { 'zonename': 'test2.example',
+ 'queries.tcp': 2,
+ 'queries.udp': 3 }])
+ self.assertEqual(self.stats.statistics_data_bypid['Auth'][9996]['queries.tcp'], 1002)
+ self.assertEqual(self.stats.statistics_data_bypid['Auth'][9996]['queries.udp'], 1003)
+ self.assertEqual(self.stats.statistics_data_bypid['Auth'][9996]['queries.perzone'],
+ [{ 'zonename': 'test1.example',
+ 'queries.tcp': 10,
+ 'queries.udp': 11},
+ { 'zonename': 'test2.example',
+ 'queries.tcp': 12,
+ 'queries.udp': 13 }])
+
class TestOSEnv(unittest.TestCase):
def test_osenv(self):
"""
diff --git a/src/bin/stats/tests/test_utils.py b/src/bin/stats/tests/test_utils.py
index 3f6ff33..d91c1f2 100644
--- a/src/bin/stats/tests/test_utils.py
+++ b/src/bin/stats/tests/test_utils.py
@@ -72,9 +72,19 @@ class ThreadingServerManager:
self.server._started.wait()
self.server._started.clear()
- def shutdown(self):
+ def shutdown(self, blocking=False):
+ """Shut down the server by calling its own shutdown() method.
+ Then wait for its thread to finish. If blocking is True,
+ the thread.join() blocks until the thread finishes. If not,
+ it uses a zero timeout. The latter is necessary in a number
+ of existing tests. We should redo this part (we should not
+ even need threads in most, if not all, of these threads, see
+ ticket #1668)"""
self.server.shutdown()
- self.server._thread.join(0) # timeout is 0
+ if blocking:
+ self.server._thread.join()
+ else:
+ self.server._thread.join(0) # timeout is 0
def do_nothing(*args, **kwargs): pass
@@ -140,6 +150,11 @@ class MockBoss:
"command_name": "sendstats",
"command_description": "Send data to a statistics module at once",
"command_args": []
+ },
+ {
+ "command_name": "show_processes",
+ "command_description": "List the running BIND 10 processes",
+ "command_args": []
}
],
"statistics": [
@@ -170,6 +185,10 @@ class MockBoss:
self.spec_file.close()
self.cc_session = self.mccs._session
self.got_command_name = ''
+ self.pid_list = [[ 9999, "b10-auth" ],
+ [ 9998, "b10-auth-2" ],
+ [ 9997, "b10-auth-3" ],
+ [ 9996, "b10-auth-4" ]]
def run(self):
self.mccs.start()
@@ -200,6 +219,10 @@ class MockBoss:
return isc.config.create_answer(0)
elif command == 'getstats':
return isc.config.create_answer(0, params)
+ elif command == 'show_processes':
+ # Return dummy pids
+ return isc.config.create_answer(
+ 0, self.pid_list)
return isc.config.create_answer(1, "Unknown Command")
class MockAuth:
@@ -330,7 +353,7 @@ class MockAuth:
params = { "owner": "Auth",
"data": { 'queries.tcp': self.queries_tcp,
'queries.udp': self.queries_udp,
- 'queries.per-zone' : self.queries_per_zone } }
+ 'queries.perzone' : self.queries_per_zone } }
return send_command("set", "Stats", params=params, session=self.cc_session)
return isc.config.create_answer(1, "Unknown Command")
diff --git a/src/bin/tests/.gitignore b/src/bin/tests/.gitignore
new file mode 100644
index 0000000..b39aa86
--- /dev/null
+++ b/src/bin/tests/.gitignore
@@ -0,0 +1 @@
+/process_rename_test.py
diff --git a/src/bin/usermgr/.gitignore b/src/bin/usermgr/.gitignore
new file mode 100644
index 0000000..e116052
--- /dev/null
+++ b/src/bin/usermgr/.gitignore
@@ -0,0 +1,3 @@
+/b10-cmdctl-usermgr
+/b10-cmdctl-usermgr.py
+/run_b10-cmdctl-usermgr.sh
diff --git a/src/bin/xfrin/.gitignore b/src/bin/xfrin/.gitignore
new file mode 100644
index 0000000..5ac1942
--- /dev/null
+++ b/src/bin/xfrin/.gitignore
@@ -0,0 +1,3 @@
+/b10-xfrin
+/run_b10-xfrin.sh
+/xfrin.py
diff --git a/src/bin/xfrin/tests/.gitignore b/src/bin/xfrin/tests/.gitignore
new file mode 100644
index 0000000..4de3f47
--- /dev/null
+++ b/src/bin/xfrin/tests/.gitignore
@@ -0,0 +1 @@
+/xfrin_test
diff --git a/src/bin/xfrin/tests/testdata/example.com.sqlite3 b/src/bin/xfrin/tests/testdata/example.com.sqlite3
index 3538e3d..1008249 100644
Binary files a/src/bin/xfrin/tests/testdata/example.com.sqlite3 and b/src/bin/xfrin/tests/testdata/example.com.sqlite3 differ
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index 497ecd1..b88d6a9 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -20,6 +20,7 @@ import socket
import sys
import io
from isc.testutils.tsigctx_mock import MockTSIGContext
+from isc.testutils.ccsession_mock import MockModuleCCSession
from isc.testutils.rrset_utils import *
from xfrin import *
import xfrin
@@ -75,6 +76,27 @@ example_soa_question = Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA())
default_questions = [example_axfr_question]
default_answers = [soa_rrset]
+def get_fake_time_time():
+ '''Returns a temporary replacement function for time.time(), which
+ always returns 0.1 more than the previous call. This is to make
+ sure these tests do not fail on systems where the time.time()
+ function has a high minimal accuracy.
+ This fake time.time() is usually set in place of the real one
+ where we need testing of get_running_time(). It is done is
+ as low a scope as possible, so as not to mess up unit test
+ framework time related tests. It must be set before
+ XfrinTransferState (or any class that initializes that) is
+ initialized.
+ And every time it is set up, in must be reset later (again, so
+ as not to mess up the framework's concept of time).
+ '''
+ fake_time = 0.0
+ def fake_time_time():
+ nonlocal fake_time
+ fake_time += 0.1
+ return fake_time
+ return fake_time_time
+
def check_diffs(assert_fn, expected, actual):
'''A helper function checking the differences made in the XFR session.
@@ -105,7 +127,7 @@ class XfrinTestException(Exception):
class XfrinTestTimeoutException(Exception):
pass
-class MockCC():
+class MockCC(MockModuleCCSession):
def get_default_value(self, identifier):
# The returned values should be identical to the spec file
# XXX: these should be retrieved from the spec file
@@ -117,6 +139,9 @@ class MockCC():
if identifier == "zones/use_ixfr":
return False
+ def remove_remote_config(self, module_name):
+ pass
+
class MockDataSourceClient():
'''A simple mock data source client.
@@ -167,14 +192,14 @@ class MockDataSourceClient():
'''
if name == TEST_ZONE_NAME and rrtype == RRType.SOA():
- return (ZoneFinder.SUCCESS, begin_soa_rrset)
+ return (ZoneFinder.SUCCESS, begin_soa_rrset, 0)
if name == Name('no-soa.example'):
- return (ZoneFinder.NXDOMAIN, None)
+ return (ZoneFinder.NXDOMAIN, None, 0)
if name == Name('dup-soa.example'):
dup_soa_rrset = RRset(name, TEST_RRCLASS, RRType.SOA(), RRTTL(0))
dup_soa_rrset.add_rdata(begin_soa_rdata)
dup_soa_rrset.add_rdata(soa_rdata)
- return (ZoneFinder.SUCCESS, dup_soa_rrset)
+ return (ZoneFinder.SUCCESS, dup_soa_rrset, 0)
raise ValueError('Unexpected input to mock finder: bug in test case?')
def get_updater(self, zone_name, replace, journaling=False):
@@ -560,7 +585,7 @@ class TestXfrinIXFREnd(TestXfrinState):
def test_finish_message(self):
self.assertFalse(self.state.finish_message(self.conn))
-class TestXfrinIXFREnd(TestXfrinState):
+class TestXfrinIXFREndUpToDate(TestXfrinState):
def setUp(self):
super().setUp()
self.state = XfrinIXFRUptodate()
@@ -758,9 +783,15 @@ class TestXfrinConnection(unittest.TestCase):
class TestAXFR(TestXfrinConnection):
def setUp(self):
+ # replace time.time with a steadily increasing fake one
+ self.orig_time_time = time.time
+ time.time = get_fake_time_time()
super().setUp()
XfrinInitialSOA().set_xfrstate(self.conn, XfrinInitialSOA())
+ def tearDown(self):
+ time.time = self.orig_time_time
+
def __create_mock_tsig(self, key, error):
# This helper function creates a MockTSIGContext for a given key
# and TSIG error to be used as a result of verify (normally faked
@@ -1317,6 +1348,14 @@ class TestAXFR(TestXfrinConnection):
self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
self.assertFalse(self.conn._datasrc_client._journaling_enabled)
+ self.assertEqual(2, self.conn._transfer_stats.message_count)
+ self.assertEqual(2, self.conn._transfer_stats.axfr_rr_count)
+ self.assertEqual(0, self.conn._transfer_stats.ixfr_changeset_count)
+ self.assertEqual(0, self.conn._transfer_stats.ixfr_deletion_count)
+ self.assertEqual(0, self.conn._transfer_stats.ixfr_addition_count)
+ self.assertEqual(177, self.conn._transfer_stats.byte_count)
+ self.assertGreater(self.conn._transfer_stats.get_running_time(), 0)
+
def test_do_xfrin_with_tsig(self):
# use TSIG with a mock context. we fake all verify results to
# emulate successful verification.
@@ -1460,6 +1499,10 @@ class TestAXFR(TestXfrinConnection):
class TestIXFRResponse(TestXfrinConnection):
def setUp(self):
+ # replace time.time with a steadily increasing fake one
+ self.orig_time_time = time.time
+ time.time = get_fake_time_time()
+
super().setUp()
self.conn._query_id = self.conn.qid = 1035
self.conn._request_serial = isc.dns.Serial(1230)
@@ -1467,6 +1510,9 @@ class TestIXFRResponse(TestXfrinConnection):
self.conn._datasrc_client = MockDataSourceClient()
XfrinInitialSOA().set_xfrstate(self.conn, XfrinInitialSOA())
+ def tearDown(self):
+ time.time = self.orig_time_time
+
def test_ixfr_response(self):
'''A simplest form of IXFR response.
@@ -1661,8 +1707,14 @@ class TestIXFRSession(TestXfrinConnection):
the general logic flow.
'''
def setUp(self):
+ # replace time.time with a steadily increasing fake one
+ self.orig_time_time = time.time
+ time.time = get_fake_time_time()
super().setUp()
+ def tearDown(self):
+ time.time = self.orig_time_time
+
def test_do_xfrin(self):
def create_ixfr_response():
self.conn.reply_data = self.conn.create_response_data(
@@ -1686,6 +1738,14 @@ class TestIXFRSession(TestXfrinConnection):
self.assertEqual(TEST_ZONE_NAME, qmsg.get_question()[0].get_name())
self.assertEqual(RRType.IXFR(), qmsg.get_question()[0].get_type())
+ self.assertEqual(1, self.conn._transfer_stats.message_count)
+ self.assertEqual(0, self.conn._transfer_stats.axfr_rr_count)
+ self.assertEqual(1, self.conn._transfer_stats.ixfr_changeset_count)
+ self.assertEqual(1, self.conn._transfer_stats.ixfr_deletion_count)
+ self.assertEqual(1, self.conn._transfer_stats.ixfr_addition_count)
+ self.assertEqual(188, self.conn._transfer_stats.byte_count)
+ self.assertGreater(self.conn._transfer_stats.get_running_time(), 0)
+
def test_do_xfrin_fail(self):
'''IXFR fails due to a protocol error.
@@ -1718,6 +1778,14 @@ class TestIXFRSession(TestXfrinConnection):
self.conn.response_generator = create_response
self.assertEqual(XFRIN_OK, self.conn.do_xfrin(False, RRType.IXFR()))
+ self.assertEqual(1, self.conn._transfer_stats.message_count)
+ self.assertEqual(0, self.conn._transfer_stats.axfr_rr_count)
+ self.assertEqual(0, self.conn._transfer_stats.ixfr_changeset_count)
+ self.assertEqual(0, self.conn._transfer_stats.ixfr_deletion_count)
+ self.assertEqual(0, self.conn._transfer_stats.ixfr_addition_count)
+ self.assertEqual(80, self.conn._transfer_stats.byte_count)
+ self.assertGreater(self.conn._transfer_stats.get_running_time(), 0)
+
class TestXFRSessionWithSQLite3(TestXfrinConnection):
'''Tests for XFR sessions using an SQLite3 DB.
@@ -1733,6 +1801,9 @@ class TestXFRSessionWithSQLite3(TestXfrinConnection):
self.empty_sqlite3db_obj = TESTDATA_OBJDIR + '/empty.sqlite3'
self.sqlite3db_cfg = "{ \"database_file\": \"" +\
self.sqlite3db_obj + "\"}"
+ # replace time.time with a steadily increasing fake one
+ self.orig_time_time = time.time
+ time.time = get_fake_time_time()
super().setUp()
if os.path.exists(self.sqlite3db_obj):
os.unlink(self.sqlite3db_obj)
@@ -1747,11 +1818,12 @@ class TestXFRSessionWithSQLite3(TestXfrinConnection):
os.unlink(self.sqlite3db_obj)
if os.path.exists(self.empty_sqlite3db_obj):
os.unlink(self.empty_sqlite3db_obj)
+ time.time = self.orig_time_time
def get_zone_serial(self):
result, finder = self.conn._datasrc_client.find_zone(TEST_ZONE_NAME)
self.assertEqual(DataSourceClient.SUCCESS, result)
- result, soa = finder.find(TEST_ZONE_NAME, RRType.SOA())
+ result, soa, _ = finder.find(TEST_ZONE_NAME, RRType.SOA())
self.assertEqual(ZoneFinder.SUCCESS, result)
self.assertEqual(1, soa.get_rdata_count())
return get_soa_serial(soa.get_rdata()[0])
@@ -1759,7 +1831,7 @@ class TestXFRSessionWithSQLite3(TestXfrinConnection):
def record_exist(self, name, type):
result, finder = self.conn._datasrc_client.find_zone(TEST_ZONE_NAME)
self.assertEqual(DataSourceClient.SUCCESS, result)
- result, soa = finder.find(name, type)
+ result, soa, _ = finder.find(name, type)
return result == ZoneFinder.SUCCESS
def test_do_ixfrin_sqlite3(self):
@@ -2052,7 +2124,9 @@ class TestXfrin(unittest.TestCase):
self.args['tsig_key'] = ''
def tearDown(self):
+ self.assertFalse(self.xfr._module_cc.stopped);
self.xfr.shutdown()
+ self.assertTrue(self.xfr._module_cc.stopped);
sys.stderr= self.stderr_backup
def _do_parse_zone_name_class(self):
@@ -2503,6 +2577,134 @@ class TestXfrin(unittest.TestCase):
self.common_ixfr_setup('refresh', False)
self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
+class TextXfrinMemoryZones(unittest.TestCase):
+ def setUp(self):
+ self.xfr = MockXfrin()
+ # Configuration snippet containing 2 memory datasources,
+ # one for IN and one for CH. Both contain a zone 'example.com'
+ # the IN ds also contains a zone example2.com, and a zone example3.com,
+ # which is of file type 'text' (and hence, should be ignored)
+ self.config = { 'datasources': [
+ { 'type': 'memory',
+ 'class': 'IN',
+ 'zones': [
+ { 'origin': 'example.com',
+ 'filetype': 'sqlite3' },
+ { 'origin': 'EXAMPLE2.com.',
+ 'filetype': 'sqlite3' },
+ { 'origin': 'example3.com',
+ 'filetype': 'text' }
+ ]
+ },
+ { 'type': 'memory',
+ 'class': 'ch',
+ 'zones': [
+ { 'origin': 'example.com',
+ 'filetype': 'sqlite3' }
+ ]
+ }
+ ] }
+
+ def test_updates(self):
+ self.assertFalse(self.xfr._is_memory_zone("example.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example2.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
+
+ # add them all
+ self.xfr._set_memory_zones(self.config, None)
+ self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+ self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+ self.assertTrue(self.xfr._is_memory_zone("example.com", "CH"))
+
+ # Remove the CH data source from the self.config snippet, and update
+ del self.config['datasources'][1]
+ self.xfr._set_memory_zones(self.config, None)
+ self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+ self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
+
+ # Remove example2.com from the datasource, and update
+ del self.config['datasources'][0]['zones'][1]
+ self.xfr._set_memory_zones(self.config, None)
+ self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example2.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
+
+ # If 'datasources' is not in the self.config update list (i.e. its
+ # self.config has not changed), no difference should be found
+ self.xfr._set_memory_zones({}, None)
+ self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example2.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
+
+ # If datasources list becomes empty, everything should be removed
+ self.config['datasources'][0]['zones'] = []
+ self.xfr._set_memory_zones(self.config, None)
+ self.assertFalse(self.xfr._is_memory_zone("example.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example2.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
+
+ def test_normalization(self):
+ self.xfr._set_memory_zones(self.config, None)
+ # make sure it is case insensitive, root-dot-insensitive,
+ # and supports CLASSXXX notation
+ self.assertTrue(self.xfr._is_memory_zone("EXAMPLE.com", "IN"))
+ self.assertTrue(self.xfr._is_memory_zone("example.com", "in"))
+ self.assertTrue(self.xfr._is_memory_zone("example2.com.", "IN"))
+ self.assertTrue(self.xfr._is_memory_zone("example.com", "CLASS3"))
+
+ def test_bad_name(self):
+ # First set it to some config
+ self.xfr._set_memory_zones(self.config, None)
+
+ # Error checking; bad owner name should result in no changes
+ self.config['datasources'][1]['zones'][0]['origin'] = ".."
+ self.xfr._set_memory_zones(self.config, None)
+ self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+ self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+ self.assertTrue(self.xfr._is_memory_zone("example.com", "CH"))
+
+ def test_bad_class(self):
+ # First set it to some config
+ self.xfr._set_memory_zones(self.config, None)
+
+ # Error checking; bad owner name should result in no changes
+ self.config['datasources'][1]['class'] = "Foo"
+ self.xfr._set_memory_zones(self.config, None)
+ self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+ self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+ self.assertTrue(self.xfr._is_memory_zone("example.com", "CH"))
+
+ def test_no_filetype(self):
+ # omitting the filetype should leave that zone out, but not
+ # the rest
+ del self.config['datasources'][1]['zones'][0]['filetype']
+ self.xfr._set_memory_zones(self.config, None)
+ self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+ self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
+
+ def test_class_filetype(self):
+ # omitting the class should have it default to what is in the
+ # specfile for Auth.
+ AuthConfigData = isc.config.config_data.ConfigData(
+ isc.config.module_spec_from_file(xfrin.AUTH_SPECFILE_LOCATION))
+ del self.config['datasources'][0]['class']
+ self.xfr._set_memory_zones(self.config, AuthConfigData)
+ self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+ self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
+ self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+ self.assertTrue(self.xfr._is_memory_zone("example.com", "CH"))
+
def raise_interrupt():
raise KeyboardInterrupt()
@@ -2712,6 +2914,68 @@ class TestFormatting(unittest.TestCase):
self.assertRaises(TypeError, format_addrinfo,
(socket.AF_INET, "asdf", ()))
+class TestXfrinTransferStats(unittest.TestCase):
+ def setUp(self):
+ # replace time.time with a steadily increasing fake one
+ self.orig_time_time = time.time
+ time.time = get_fake_time_time()
+ self.stats = XfrinTransferStats()
+
+ def tearDown(self):
+ time.time = self.orig_time_time
+
+ def zero_check(self):
+ # Checks whether all counters are zero
+ self.assertEqual(0, self.stats.message_count)
+ self.assertEqual(0, self.stats.axfr_rr_count)
+ self.assertEqual(0, self.stats.byte_count)
+ self.assertEqual(0, self.stats.ixfr_changeset_count)
+ self.assertEqual(0, self.stats.ixfr_deletion_count)
+ self.assertEqual(0, self.stats.ixfr_addition_count)
+
+ def test_init(self):
+ self.zero_check()
+ self.assertIsNone(self.stats._end_time)
+
+ def test_get_running_time(self):
+ self.assertIsNone(self.stats._end_time)
+ runtime = self.stats.get_running_time()
+ self.assertIsNotNone(self.stats._end_time)
+ self.assertGreater(runtime, 0)
+ # make sure a second get does not change anything
+ runtime2 = self.stats.get_running_time()
+ self.assertEqual(runtime, runtime2)
+ # And that no counters have been modified
+ self.zero_check()
+
+ def test_bytes_per_second(self):
+ zbps = self.stats.get_bytes_per_second()
+ self.assertEqual(0, zbps)
+
+ self.stats._start_time = 1
+ self.stats._end_time = 2
+ self.stats.byte_count += 4
+ zbps = self.stats.get_bytes_per_second()
+ self.assertEqual(4, zbps)
+
+ self.stats._start_time = float(1)
+ self.stats._end_time = float(11)
+ self.assertEqual(10, self.stats.get_running_time())
+ self.stats.byte_count = 1234
+ zbps = self.stats.get_bytes_per_second()
+ self.assertEqual(123.4, zbps)
+
+ # if for some reason the runtime is 0, depending
+ # on whether bytes have actually been seen, bps is either
+ # 0 or 'infinite'
+ self.stats._end_time = self.stats._start_time
+ zbps = self.stats.get_bytes_per_second()
+ self.assertEqual(float("inf"), zbps)
+
+ self.stats.byte_count = 0
+ zbps = self.stats.get_bytes_per_second()
+ self.assertEqual(0, zbps)
+
if __name__== "__main__":
try:
isc.log.resetUnitTestRootLogger()
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index 7b70b52..27b91a9 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -24,6 +24,7 @@ import struct
import threading
import socket
import random
+import time
from functools import reduce
from optparse import OptionParser, OptionValueError
from isc.config.ccsession import *
@@ -37,6 +38,11 @@ from isc.log_messages.xfrin_messages import *
isc.log.init("b10-xfrin")
logger = isc.log.Logger("xfrin")
+# Pending system-wide debug level definitions, the ones we
+# use here are hardcoded for now
+DBG_PROCESS = logger.DBGLVL_TRACE_BASIC
+DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL
+
try:
from pydnspp import *
except ImportError as e:
@@ -361,7 +367,6 @@ class XfrinFirstData(XfrinState):
conn.zone_str())
# We are now going to add RRs to the new zone. We need create
# a Diff object. It will be used throughtout the XFR session.
- # DISABLE FOR DEBUG
conn._diff = Diff(conn._datasrc_client, conn._zone_name, True)
self.set_xfrstate(conn, XfrinAXFR())
return False
@@ -381,6 +386,7 @@ class XfrinIXFRDeleteSOA(XfrinState):
conn._diff = Diff(conn._datasrc_client, conn._zone_name, False, True)
conn._diff.delete_data(rr)
self.set_xfrstate(conn, XfrinIXFRDelete())
+ conn.get_transfer_stats().ixfr_deletion_count += 1
return True
class XfrinIXFRDelete(XfrinState):
@@ -391,6 +397,7 @@ class XfrinIXFRDelete(XfrinState):
self.set_xfrstate(conn, XfrinIXFRAddSOA())
return False
conn._diff.delete_data(rr)
+ conn.get_transfer_stats().ixfr_deletion_count += 1
return True
class XfrinIXFRAddSOA(XfrinState):
@@ -402,11 +409,14 @@ class XfrinIXFRAddSOA(XfrinState):
' RR is given in IXFRAddSOA state')
conn._diff.add_data(rr)
self.set_xfrstate(conn, XfrinIXFRAdd())
+ conn.get_transfer_stats().ixfr_addition_count += 1
return True
class XfrinIXFRAdd(XfrinState):
def handle_rr(self, conn, rr):
if rr.get_type() == RRType.SOA():
+ # This SOA marks the end of a difference sequence
+ conn.get_transfer_stats().ixfr_changeset_count += 1
soa_serial = get_soa_serial(rr.get_rdata()[0])
if soa_serial == conn._end_serial:
conn._diff.commit()
@@ -422,6 +432,7 @@ class XfrinIXFRAdd(XfrinState):
self.set_xfrstate(conn, XfrinIXFRDeleteSOA())
return False
conn._diff.add_data(rr)
+ conn.get_transfer_stats().ixfr_addition_count += 1
return True
class XfrinIXFREnd(XfrinState):
@@ -462,6 +473,7 @@ class XfrinAXFR(XfrinState):
conn._end_serial, soa_serial)
self.set_xfrstate(conn, XfrinAXFREnd())
+ conn.get_transfer_stats().axfr_rr_count += 1
# Yes, we've eaten this RR.
return True
@@ -484,6 +496,55 @@ class XfrinAXFREnd(XfrinState):
conn._diff.commit()
return False
+class XfrinTransferStats:
+ """
+ This class keeps a record of transfer data for logging purposes.
+ It records number of messages, rrs, and bytes transfered, as well
+ as the start and end time. The start time is set upon instantiation of
+ this class. The end time is set the first time finalize(),
+ get_running_time(), or get_bytes_per_second() is called. The end time is
+ set only once; subsequent calls to any of these methods does not modify
+ it further.
+ All _count instance variables can be directly set as needed by the
+ class collecting these results.
+ """
+ def __init__(self):
+ self.message_count = 0
+ self.axfr_rr_count = 0
+ self.byte_count = 0
+ self.ixfr_changeset_count = 0;
+ self.ixfr_deletion_count = 0;
+ self.ixfr_addition_count = 0;
+ self._start_time = time.time()
+ self._end_time = None
+
+ def finalize(self):
+ """Sets the end time to time.time() if not done already."""
+ if self._end_time is None:
+ self._end_time = time.time()
+
+ def get_running_time(self):
+ """Calls finalize(), then returns the difference between creation
+ and finalization time"""
+ self.finalize()
+ return self._end_time - self._start_time
+
+ def get_bytes_per_second(self):
+ """Returns the number of bytes per second, based on the result of
+ get_running_time() and the value of bytes_count."""
+ runtime = self.get_running_time()
+ if runtime > 0.0:
+ return float(self.byte_count) / runtime
+ else:
+ # This should never happen, but if some clock is so
+ # off or reset in the meantime, we do need to return
+ # *something* (and not raise an error)
+ if self.byte_count == 0:
+ return 0.0
+ else:
+ return float("inf")
+
+
class XfrinConnection(asyncore.dispatcher):
'''Do xfrin in this class. '''
@@ -534,6 +595,10 @@ class XfrinConnection(asyncore.dispatcher):
# easier tests (in normal case we always use the default)
self._tsig_ctx_creator = lambda key : TSIGContext(key)
+ # keep a record of this specific transfer to log on success
+ # (time, rr/s, etc)
+ self._transfer_stats = XfrinTransferStats()
+
def init_socket(self):
'''Initialize the underlyig socket.
@@ -583,7 +648,7 @@ class XfrinConnection(asyncore.dispatcher):
result, finder = self._datasrc_client.find_zone(self._zone_name)
if result != DataSourceClient.SUCCESS:
return None
- result, soa_rrset = finder.find(self._zone_name, RRType.SOA())
+ result, soa_rrset, _ = finder.find(self._zone_name, RRType.SOA())
if result != ZoneFinder.SUCCESS:
logger.info(XFRIN_ZONE_NO_SOA, self.zone_str())
return None
@@ -599,6 +664,11 @@ class XfrinConnection(asyncore.dispatcher):
def get_xfrstate(self):
return self.__state
+ def get_transfer_stats(self):
+ """Returns the transfer stats object, used to measure transfer time,
+ and number of messages/records/bytes transfered."""
+ return self._transfer_stats
+
def zone_str(self):
'''A convenience function for logging to include zone name and class'''
return format_zone_str(self._zone_name, self._rrclass)
@@ -823,7 +893,29 @@ class XfrinConnection(asyncore.dispatcher):
self._send_query(self._request_type)
self.__state = XfrinInitialSOA()
self._handle_xfrin_responses()
- logger.info(XFRIN_XFR_TRANSFER_SUCCESS, req_str, self.zone_str())
+ # Depending what data was found, we log different status reports
+ # (In case of an AXFR-style IXFR, print the 'AXFR' message)
+ if self._transfer_stats.axfr_rr_count == 0:
+ logger.info(XFRIN_IXFR_TRANSFER_SUCCESS,
+ self.zone_str(),
+ self._transfer_stats.message_count,
+ self._transfer_stats.ixfr_changeset_count,
+ self._transfer_stats.ixfr_deletion_count,
+ self._transfer_stats.ixfr_addition_count,
+ self._transfer_stats.byte_count,
+ "%.3f" % self._transfer_stats.get_running_time(),
+ "%.f" % self._transfer_stats.get_bytes_per_second()
+ )
+ else:
+ logger.info(XFRIN_TRANSFER_SUCCESS,
+ req_str,
+ self.zone_str(),
+ self._transfer_stats.message_count,
+ self._transfer_stats.axfr_rr_count,
+ self._transfer_stats.byte_count,
+ "%.3f" % self._transfer_stats.get_running_time(),
+ "%.f" % self._transfer_stats.get_bytes_per_second()
+ )
except XfrinZoneUptodate:
# Eventually we'll probably have to treat this case as a trigger
@@ -895,9 +987,11 @@ class XfrinConnection(asyncore.dispatcher):
while read_next_msg:
data_len = self._get_request_response(2)
msg_len = socket.htons(struct.unpack('H', data_len)[0])
+ self._transfer_stats.byte_count += msg_len + 2
recvdata = self._get_request_response(msg_len)
msg = Message(Message.PARSE)
msg.from_wire(recvdata, Message.PRESERVE_ORDER)
+ self._transfer_stats.message_count += 1
# TSIG related checks, including an unexpected signed response
self._check_response_tsig(msg, recvdata)
@@ -1156,6 +1250,11 @@ class Xfrin:
def __init__(self):
self._max_transfers_in = 10
self._zones = {}
+ # This is a set of (zone/class) tuples (both as strings),
+ # representing the in-memory zones maintaned by Xfrin. It
+ # is used to trigger Auth/in-memory so that it reloads
+ # zones when they have been transfered in
+ self._memory_zones = set()
self._cc_setup()
self.recorder = XfrinRecorder()
self._shutdown_event = threading.Event()
@@ -1174,6 +1273,8 @@ class Xfrin:
self._module_cc.start()
config_data = self._module_cc.get_full_config()
self.config_handler(config_data)
+ self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION,
+ self._auth_config_handler)
def _cc_check_command(self):
'''This is a straightforward wrapper for cc.check_command,
@@ -1220,10 +1321,79 @@ class Xfrin:
return create_answer(0)
+ def _auth_config_handler(self, new_config, config_data):
+ # Config handler for changes in Auth configuration
+ self._set_db_file()
+ self._set_memory_zones(new_config, config_data)
+
+ def _clear_memory_zones(self):
+ """Clears the memory_zones set; called before processing the
+ changed list of memory datasource zones that have file type
+ sqlite3"""
+ self._memory_zones.clear()
+
+ def _is_memory_zone(self, zone_name_str, zone_class_str):
+ """Returns true if the given zone/class combination is configured
+ in the in-memory datasource of the Auth process with file type
+ 'sqlite3'.
+ Note: this method is not thread-safe. We are considering
+ changing the threaded model here, but if we do not, take
+ care in accessing and updating the memory zone set (or add
+ locks)
+ """
+ # Normalize them first, if either conversion fails, return false
+ # (they won't be in the set anyway)
+ try:
+ zone_name_str = Name(zone_name_str).to_text().lower()
+ zone_class_str = RRClass(zone_class_str).to_text()
+ except Exception:
+ return False
+ return (zone_name_str, zone_class_str) in self._memory_zones
+
+ def _set_memory_zones(self, new_config, config_data):
+ """Part of the _auth_config_handler function, keeps an internal set
+ of zones in the datasources config subset that have 'sqlite3' as
+ their file type.
+ Note: this method is not thread-safe. We are considering
+ changing the threaded model here, but if we do not, take
+ care in accessing and updating the memory zone set (or add
+ locks)
+ """
+ # walk through the data and collect the memory zones
+ # If this causes any exception, assume we were passed bad data
+ # and keep the original set
+ new_memory_zones = set()
+ try:
+ if "datasources" in new_config:
+ for datasource in new_config["datasources"]:
+ if "class" in datasource:
+ ds_class = RRClass(datasource["class"])
+ else:
+ # Get the default
+ ds_class = RRClass(config_data.get_default_value(
+ "datasources/class"))
+ if datasource["type"] == "memory":
+ for zone in datasource["zones"]:
+ if "filetype" in zone and \
+ zone["filetype"] == "sqlite3":
+ zone_name = Name(zone["origin"])
+ zone_name_str = zone_name.to_text().lower()
+ new_memory_zones.add((zone_name_str,
+ ds_class.to_text()))
+ # Ok, we can use the data, update our list
+ self._memory_zones = new_memory_zones
+ except Exception:
+ # Something is wrong with the data. If this data even reached us,
+ # we cannot do more than assume the real module has logged and
+ # reported an error. Keep the old set.
+ return
+
def shutdown(self):
''' shutdown the xfrin process. the thread which is doing xfrin should be
terminated.
'''
+ self._module_cc.remove_remote_config(AUTH_SPECFILE_LOCATION)
+ self._module_cc.send_stopping()
self._shutdown_event.set()
main_thread = threading.currentThread()
for th in threading.enumerate():
@@ -1355,20 +1525,19 @@ class Xfrin:
return (addr.family, socket.SOCK_STREAM, (str(addr), port))
def _get_db_file(self):
- #TODO, the db file path should be got in auth server's configuration
- # if we need access to this configuration more often, we
- # should add it on start, and not remove it here
- # (or, if we have writable ds, we might not need this in
- # the first place)
- self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION)
- db_file, is_default = self._module_cc.get_remote_config_value("Auth", "database_file")
+ return self._db_file
+
+ def _set_db_file(self):
+ db_file, is_default =\
+ self._module_cc.get_remote_config_value("Auth", "database_file")
if is_default and "B10_FROM_BUILD" in os.environ:
- # this too should be unnecessary, but currently the
- # 'from build' override isn't stored in the config
- # (and we don't have writable datasources yet)
- db_file = os.environ["B10_FROM_BUILD"] + os.sep + "bind10_zones.sqlite3"
- self._module_cc.remove_remote_config(AUTH_SPECFILE_LOCATION)
- return db_file
+ # override the local database setting if it is default and we
+ # are running from the source tree
+ # This should be hidden inside the data source library and/or
+ # done as a configuration, and this special case should be gone).
+ db_file = os.environ["B10_FROM_BUILD"] + os.sep +\
+ "bind10_zones.sqlite3"
+ self._db_file = db_file
def publish_xfrin_news(self, zone_name, zone_class, xfr_result):
'''Send command to xfrout/zone manager module.
@@ -1411,6 +1580,7 @@ class Xfrin:
logger.error(XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER, ZONE_MANAGER_MODULE_NAME)
def startup(self):
+ logger.debug(DBG_PROCESS, XFRIN_STARTED)
while not self._shutdown_event.is_set():
self._cc_check_command()
diff --git a/src/bin/xfrin/xfrin.spec b/src/bin/xfrin/xfrin.spec
index c1ba61e..7ab1085 100644
--- a/src/bin/xfrin/xfrin.spec
+++ b/src/bin/xfrin/xfrin.spec
@@ -86,7 +86,13 @@
{
"command_name": "shutdown",
"command_description": "Shut down xfrin module",
- "command_args": []
+ "command_args": [
+ {
+ "item_name": "pid",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
}
]
}
diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes
index 5e182d8..25a1fc1 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -15,92 +15,21 @@
# No namespace declaration - these constants go in the global namespace
# of the xfrin messages python module.
-% XFRIN_ZONE_CREATED Zone %1 not found in the given data source, newly created
-On starting an xfrin session, it is identified that the zone to be
-transferred is not found in the data source. This can happen if a
-secondary DNS server first tries to perform AXFR from a primary server
-without creating the zone image beforehand (e.g. by b10-loadzone). As
-of this writing the xfrin process provides backward compatible
-behavior to previous versions: creating a new one in the data source
-not to surprise existing users too much. This is probably not a good
-idea, however, in terms of who should be responsible for managing
-zones at a higher level. In future it is more likely that a separate
-zone management framework is provided, and the situation where the
-given zone isn't found in xfrout will be treated as an error.
-
-% XFRIN_ZONE_NO_SOA Zone %1 does not have SOA
-On starting an xfrin session, it is identified that the zone to be
-transferred does not have an SOA RR in the data source. This is not
-necessarily an error; if a secondary DNS server first tries to perform
-transfer from a primary server, the zone can be empty, and therefore
-doesn't have an SOA. Subsequent AXFR will fill in the zone; if the
-attempt is IXFR it will fail in query creation.
-
-% XFRIN_ZONE_MULTIPLE_SOA Zone %1 has %2 SOA RRs
-On starting an xfrin session, it is identified that the zone to be
-transferred has multiple SOA RRs. Such a zone is broken, but could be
-accidentally configured especially in a data source using "non
-captive" backend database. The implementation ignores entire SOA RRs
-and tries to continue processing as if the zone were empty. This
-means subsequent AXFR can succeed and possibly replace the zone with
-valid content, but an IXFR attempt will fail.
-
-% XFRIN_ZONE_SERIAL_AHEAD Serial number (%1) for %2 received from master %3 < ours (%4)
-The response to an SOA query prior to xfr indicated that the zone's
-SOA serial at the primary server is smaller than that of the xfrin
-client. This is not necessarily an error especially if that
-particular primary server is another secondary server which hasn't got
-the latest version of the zone. But if the primary server is known to
-be the real source of the zone, some unexpected inconsistency may have
-happened, and you may want to take a closer look. In this case xfrin
-doesn't perform subsequent zone transfer.
-
-% XFRIN_XFR_OTHER_FAILURE %1 transfer of zone %2 failed: %3
-The XFR transfer for the given zone has failed due to a problem outside
-of the xfrin module. Possible reasons are a broken DNS message or failure
-in database connection. The error is shown in the log message.
-
-% XFRIN_XFR_TRANSFER_PROTOCOL_ERROR %1 transfer of zone %2 with %3 failed: %4
-The XFR transfer for the given zone has failed due to a protocol
-error, such as an unexpected response from the primary server. The
-error is shown in the log message. It may be because the primary
-server implementation is broken or (although less likely) there was
-some attack attempt, but it can also happen due to configuration
-mismatch such as the remote server does not have authority for the
-zone any more but the local configuration hasn't been updated. So it
-is recommended to check the primary server configuration.
-
-% XFRIN_XFR_TRANSFER_FAILURE %1 transfer of zone %2 with %3 failed: %4
-The XFR transfer for the given zone has failed due to an internal error.
-The error is shown in the log message.
-
-% XFRIN_XFR_TRANSFER_FALLBACK falling back from IXFR to AXFR for %1
-The IXFR transfer of the given zone failed. This might happen in many cases,
-such that the remote server doesn't support IXFR, we don't have the SOA record
-(or the zone at all), we are out of sync, etc. In many of these situations,
-AXFR could still work. Therefore we try that one in case it helps.
-
-% XFRIN_XFR_PROCESS_FAILURE %1 transfer of zone %2/%3 failed: %4
-An XFR session failed outside the main protocol handling. This
-includes an error at the data source level at the initialization
-phase, unexpected failure in the network connection setup to the
-master server, or even more unexpected failure due to unlikely events
-such as memory allocation failure. Details of the error are shown in
-the log message. In general, these errors are not really expected
-ones, and indicate an installation error or a program bug. The
-session handler thread tries to clean up all intermediate resources
-even on these errors, but it may be incomplete. So, if this log
-message continuously appears, system resource consumption should be
-checked, and you may even want to disable the corresponding transfers.
-You may also want to file a bug report if this message appears so
-often.
-
-% XFRIN_XFR_TRANSFER_STARTED %1 transfer of zone %2 started
-A connection to the master server has been made, the serial value in
-the SOA record has been checked, and a zone transfer has been started.
-
-% XFRIN_XFR_TRANSFER_SUCCESS %1 transfer of zone %2 succeeded
-The XFR transfer of the given zone was successfully completed.
+% XFRIN_AXFR_INCONSISTENT_SOA AXFR SOAs are inconsistent for %1: %2 expected, %3 received
+The serial fields of the first and last SOAs of AXFR (including AXFR-style
+IXFR) are not the same. According to RFC 5936 these two SOAs must be the
+"same" (not only for the serial), but it is still not clear what the
+receiver should do if this condition does not hold. There was a discussion
+about this at the IETF dnsext wg:
+http://www.ietf.org/mail-archive/web/dnsext/current/msg07908.html
+and the general feeling seems that it would be better to reject the
+transfer if a mismatch is detected. On the other hand, also as noted
+in that email thread, neither BIND 9 nor NSD performs any comparison
+on the SOAs. For now, we only check the serials (ignoring other fields)
+and only leave a warning log message when a mismatch is found. If it
+turns out to happen with a real world primary server implementation
+and that server actually feeds broken data (e.g. mixed versions of
+zone), we can consider a stricter action.
% XFRIN_BAD_MASTER_ADDR_FORMAT bad format for master address: %1
The given master address is not a valid IP address.
@@ -127,6 +56,58 @@ error is given in the log message.
There was an error opening a connection to the master. The error is
shown in the log message.
+% XFRIN_GOT_INCREMENTAL_RESP got incremental response for %1
+In an attempt of IXFR processing, the begenning SOA of the first difference
+(following the initial SOA that specified the final SOA for all the
+differences) was found. This means a connection for xfrin tried IXFR
+and really aot a response for incremental updates.
+
+% XFRIN_GOT_NONINCREMENTAL_RESP got nonincremental response for %1
+Non incremental transfer was detected at the "first data" of a transfer,
+which is the RR following the initial SOA. Non incremental transfer is
+either AXFR or AXFR-style IXFR. In the latter case, it means that
+in a response to IXFR query the first data is not SOA or its SOA serial
+is not equal to the requested SOA serial.
+
+% XFRIN_IMPORT_DNS error importing python DNS module: %1
+There was an error importing the python DNS module pydnspp. The most
+likely cause is a PYTHONPATH problem.
+
+% XFRIN_IXFR_TRANSFER_SUCCESS incremental IXFR transfer of zone %1 succeeded (messages: %2, changesets: %3, deletions: %4, additions: %5, bytes: %6, run time: %7 seconds, %8 bytes/second)
+The IXFR transfer for the given zone was successful.
+The provided information contains the following values:
+
+messages: Number of overhead DNS messages in the transfer.
+
+changesets: Number of difference sequences.
+
+deletions: Number of Resource Records deleted by all the changesets combined,
+including the SOA records.
+
+additions: Number of Resource Records added by all the changesets combined,
+including the SOA records.
+
+bytes: Full size of the transfer data on the wire.
+
+run time: Time (in seconds) the complete ixfr took.
+
+bytes/second: Transfer speed.
+
+Note that there is no cross-checking of additions and deletions; if the same
+RR gets added and deleted in multiple changesets, it is counted each time;
+therefore, for each changeset, there should at least be 1 deletion and 1
+addition (the updated SOA record).
+
+% XFRIN_IXFR_UPTODATE IXFR requested serial for %1 is %2, master has %3, not updating
+The first SOA record in an IXFR response indicates the zone's serial
+at the primary server is not newer than the client's. This is
+basically unexpected event because normally the client first checks
+the SOA serial by an SOA query, but can still happen if the transfer
+is manually invoked or (although unlikely) there is a rapid change at
+the primary server between the SOA and IXFR queries. The client
+implementation confirms the whole response is this single SOA, and
+aborts the transfer just like a successful case.
+
% XFRIN_MSGQ_SEND_ERROR error while contacting %1 and %2
There was a problem sending a message to the xfrout module or the
zone manager. This most likely means that the msgq daemon has quit or
@@ -142,62 +123,119 @@ from does not match the master address in the Xfrin configuration. The notify
is ignored. This may indicate that the configuration for the master is wrong,
that a wrong machine is sending notifies, or that fake notifies are being sent.
-% XFRIN_IMPORT_DNS error importing python DNS module: %1
-There was an error importing the python DNS module pydnspp. The most
-likely cause is a PYTHONPATH problem.
-
% XFRIN_RETRANSFER_UNKNOWN_ZONE got notification to retransfer unknown zone %1
There was an internal command to retransfer the given zone, but the
zone is not known to the system. This may indicate that the configuration
for xfrin is incomplete, or there was a typographical error in the
zone name in the configuration.
-% XFRIN_STARTING starting resolver with command line '%1'
-An informational message, this is output when the resolver starts up.
+% XFRIN_STARTED xfrin started
+This informational message is output by xfrin when all initialization
+has been completed and it is entering its main loop.
% XFRIN_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down
There was a keyboard interrupt signal to stop the xfrin daemon. The
daemon will now shut down.
+% XFRIN_TRANSFER_SUCCESS full %1 transfer of zone %2 succeeded (messages: %3, records: %4, bytes: %5, run time: %6 seconds, %7 bytes/second)
+The AXFR transfer of the given zone was successful.
+The provided information contains the following values:
+
+messages: Number of overhead DNS messages in the transfer
+
+records: Number of Resource Records in the full transfer, excluding the
+final SOA record that marks the end of the AXFR.
+
+bytes: Full size of the transfer data on the wire.
+
+run time: Time (in seconds) the complete axfr took
+
+bytes/second: Transfer speed
+
% XFRIN_UNKNOWN_ERROR unknown error: %1
An uncaught exception was raised while running the xfrin daemon. The
exception message is printed in the log message.
-% XFRIN_IXFR_UPTODATE IXFR requested serial for %1 is %2, master has %3, not updating
-The first SOA record in an IXFR response indicates the zone's serial
-at the primary server is not newer than the client's. This is
-basically unexpected event because normally the client first checks
-the SOA serial by an SOA query, but can still happen if the transfer
-is manually invoked or (although unlikely) there is a rapid change at
-the primary server between the SOA and IXFR queries. The client
-implementation confirms the whole response is this single SOA, and
-aborts the transfer just like a successful case.
+% XFRIN_XFR_OTHER_FAILURE %1 transfer of zone %2 failed: %3
+The XFR transfer for the given zone has failed due to a problem outside
+of the xfrin module. Possible reasons are a broken DNS message or failure
+in database connection. The error is shown in the log message.
-% XFRIN_GOT_INCREMENTAL_RESP got incremental response for %1
-In an attempt of IXFR processing, the begenning SOA of the first difference
-(following the initial SOA that specified the final SOA for all the
-differences) was found. This means a connection for xfrin tried IXFR
-and really aot a response for incremental updates.
+% XFRIN_XFR_PROCESS_FAILURE %1 transfer of zone %2/%3 failed: %4
+An XFR session failed outside the main protocol handling. This
+includes an error at the data source level at the initialization
+phase, unexpected failure in the network connection setup to the
+master server, or even more unexpected failure due to unlikely events
+such as memory allocation failure. Details of the error are shown in
+the log message. In general, these errors are not really expected
+ones, and indicate an installation error or a program bug. The
+session handler thread tries to clean up all intermediate resources
+even on these errors, but it may be incomplete. So, if this log
+message continuously appears, system resource consumption should be
+checked, and you may even want to disable the corresponding transfers.
+You may also want to file a bug report if this message appears so
+often.
-% XFRIN_GOT_NONINCREMENTAL_RESP got nonincremental response for %1
-Non incremental transfer was detected at the "first data" of a transfer,
-which is the RR following the initial SOA. Non incremental transfer is
-either AXFR or AXFR-style IXFR. In the latter case, it means that
-in a response to IXFR query the first data is not SOA or its SOA serial
-is not equal to the requested SOA serial.
+% XFRIN_XFR_TRANSFER_FAILURE %1 transfer of zone %2 with %3 failed: %4
+The XFR transfer for the given zone has failed due to an internal error.
+The error is shown in the log message.
-% XFRIN_AXFR_INCONSISTENT_SOA AXFR SOAs are inconsistent for %1: %2 expected, %3 received
-The serial fields of the first and last SOAs of AXFR (including AXFR-style
-IXFR) are not the same. According to RFC 5936 these two SOAs must be the
-"same" (not only for the serial), but it is still not clear what the
-receiver should do if this condition does not hold. There was a discussion
-about this at the IETF dnsext wg:
-http://www.ietf.org/mail-archive/web/dnsext/current/msg07908.html
-and the general feeling seems that it would be better to reject the
-transfer if a mismatch is detected. On the other hand, also as noted
-in that email thread, neither BIND 9 nor NSD performs any comparison
-on the SOAs. For now, we only check the serials (ignoring other fields)
-and only leave a warning log message when a mismatch is found. If it
-turns out to happen with a real world primary server implementation
-and that server actually feeds broken data (e.g. mixed versions of
-zone), we can consider a stricter action.
+% XFRIN_XFR_TRANSFER_FALLBACK falling back from IXFR to AXFR for %1
+The IXFR transfer of the given zone failed. This might happen in many cases,
+such that the remote server doesn't support IXFR, we don't have the SOA record
+(or the zone at all), we are out of sync, etc. In many of these situations,
+AXFR could still work. Therefore we try that one in case it helps.
+
+% XFRIN_XFR_TRANSFER_PROTOCOL_ERROR %1 transfer of zone %2 with %3 failed: %4
+The XFR transfer for the given zone has failed due to a protocol
+error, such as an unexpected response from the primary server. The
+error is shown in the log message. It may be because the primary
+server implementation is broken or (although less likely) there was
+some attack attempt, but it can also happen due to configuration
+mismatch such as the remote server does not have authority for the
+zone any more but the local configuration hasn't been updated. So it
+is recommended to check the primary server configuration.
+
+% XFRIN_XFR_TRANSFER_STARTED %1 transfer of zone %2 started
+A connection to the master server has been made, the serial value in
+the SOA record has been checked, and a zone transfer has been started.
+
+% XFRIN_ZONE_CREATED Zone %1 not found in the given data source, newly created
+On starting an xfrin session, it is identified that the zone to be
+transferred is not found in the data source. This can happen if a
+secondary DNS server first tries to perform AXFR from a primary server
+without creating the zone image beforehand (e.g. by b10-loadzone). As
+of this writing the xfrin process provides backward compatible
+behavior to previous versions: creating a new one in the data source
+not to surprise existing users too much. This is probably not a good
+idea, however, in terms of who should be responsible for managing
+zones at a higher level. In future it is more likely that a separate
+zone management framework is provided, and the situation where the
+given zone isn't found in xfrout will be treated as an error.
+
+% XFRIN_ZONE_MULTIPLE_SOA Zone %1 has %2 SOA RRs
+On starting an xfrin session, it is identified that the zone to be
+transferred has multiple SOA RRs. Such a zone is broken, but could be
+accidentally configured especially in a data source using "non
+captive" backend database. The implementation ignores entire SOA RRs
+and tries to continue processing as if the zone were empty. This
+means subsequent AXFR can succeed and possibly replace the zone with
+valid content, but an IXFR attempt will fail.
+
+% XFRIN_ZONE_NO_SOA Zone %1 does not have SOA
+On starting an xfrin session, it is identified that the zone to be
+transferred does not have an SOA RR in the data source. This is not
+necessarily an error; if a secondary DNS server first tries to perform
+transfer from a primary server, the zone can be empty, and therefore
+doesn't have an SOA. Subsequent AXFR will fill in the zone; if the
+attempt is IXFR it will fail in query creation.
+
+% XFRIN_ZONE_SERIAL_AHEAD Serial number (%1) for %2 received from master %3 < ours (%4)
+The response to an SOA query prior to xfr indicated that the zone's
+SOA serial at the primary server is smaller than that of the xfrin
+client. This is not necessarily an error especially if that
+particular primary server is another secondary server which hasn't got
+the latest version of the zone. But if the primary server is known to
+be the real source of the zone, some unexpected inconsistency may have
+happened, and you may want to take a closer look. In this case xfrin
+doesn't perform subsequent zone transfer.
diff --git a/src/bin/xfrout/.gitignore b/src/bin/xfrout/.gitignore
new file mode 100644
index 0000000..2ace679
--- /dev/null
+++ b/src/bin/xfrout/.gitignore
@@ -0,0 +1,5 @@
+/b10-xfrout
+/run_b10-xfrout.sh
+/xfrout.py
+/xfrout.spec
+/xfrout.spec.pre
diff --git a/src/bin/xfrout/b10-xfrout.8 b/src/bin/xfrout/b10-xfrout.8
index c37198c..b3200b5 100644
--- a/src/bin/xfrout/b10-xfrout.8
+++ b/src/bin/xfrout/b10-xfrout.8
@@ -2,12 +2,21 @@
.\" 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: December 15, 2011
+.\" Date: March 16. 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-XFROUT" "8" "December 15, 2011" "BIND10" "BIND10"
+.TH "B10\-XFROUT" "8" "March 16\&. 2012" "BIND10" "BIND10"
+.\" -----------------------------------------------------------------
+.\" * Define some portability stuff
+.\" -----------------------------------------------------------------
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.\" http://bugs.debian.org/507673
+.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -70,11 +79,6 @@ The configurable settings are:
defines the maximum number of outgoing zone transfers that can run concurrently\&. The default is 10\&.
.PP
-\fItsig_key_ring\fR
-A list of TSIG keys (each of which is in the form of
-\fIname:base64\-key[:algorithm]\fR) used for access control on transfer requests\&. The default is an empty list\&.
-.PP
-
\fItransfer_acl\fR
A list of ACL elements that apply to all transfer requests by default (unless overridden in
\fIzone_config\fR)\&. See the
@@ -87,26 +91,6 @@ A list of JSON objects (i\&.e\&. maps) that define per zone configuration concer
\fBb10\-xfrout\fR\&. The supported names of each object are "origin" (the origin name of the zone), "class" (the RR class of the zone, optional, default to "IN"), and "transfer_acl" (ACL only applicable to transfer requests for that zone)\&. See the
BIND 10 Guide
for configuration examples\&. The default is an empty list, that is, no zone specific configuration\&.
-.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
.\}
@@ -127,17 +111,24 @@ This prototype version uses SQLite3 as its data source backend\&. Future version
The configuration commands are:
.PP
-\fBshutdown\fR
-stops all outbound zone transfers and exits
-\fBb10\-xfrout\fR\&. (Note that the BIND 10 boss process will restart this service\&.)
+\fBnotify\fR
+triggers
+\fBb10\-xfrout\fR
+to send NOTIFY message(s)\&. It has the following arguments:
+\fIzone_name\fR
+to define the zone to send notifies for and the optional
+\fIzone_class\fR
+to define the class (defaults to
+\(lqIN\(rq)\&.
+\fBb10-xfrin\fR(8)
+sends this command when a zone transferred in successfully\&.
.PP
-\fBzone_new_data_ready\fR
-is sent from
-\fBb10-xfrin\fR(8)
-to indicate that the zone transferred in successfully\&. This triggers
-\fBb10\-xfrout\fR
-to send NOTIFY message(s)\&. This is an internal command and not exposed to the administrator\&.
+\fBshutdown\fR
+stops all outbound zone transfers and exits
+\fBb10\-xfrout\fR\&. This has an optional
+\fIpid\fR
+argument to select the process ID to stop\&. (Note that the BIND 10 boss process may restart this service if configured\&.)
.SH "SEE ALSO"
.PP
@@ -154,5 +145,5 @@ The
daemon was first implemented in March 2010 by Zhang Likun of CNNIC for the ISC BIND 10 project\&.
.SH "COPYRIGHT"
.br
-Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")
+Copyright \(co 2010-2012 Internet Systems Consortium, Inc. ("ISC")
.br
diff --git a/src/bin/xfrout/b10-xfrout.xml b/src/bin/xfrout/b10-xfrout.xml
index 87d0267..f79a42d 100644
--- a/src/bin/xfrout/b10-xfrout.xml
+++ b/src/bin/xfrout/b10-xfrout.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-2012 Internet Systems Consortium, Inc. ("ISC")
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>December 15, 2011</date>
+ <date>March 16. 2012</date>
</refentryinfo>
<refmeta>
@@ -36,7 +36,7 @@
<docinfo>
<copyright>
- <year>2010</year>
+ <year>2010-2012</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
@@ -98,13 +98,6 @@
that can run concurrently. The default is 10.
</para>
<para>
- <varname>tsig_key_ring</varname>
- A list of TSIG keys (each of which is in the form of
- <replaceable>name:base64-key[:algorithm]</replaceable>)
- used for access control on transfer requests.
- The default is an empty list.
- </para>
- <para>
<varname>transfer_acl</varname>
A list of ACL elements that apply to all transfer requests by
default (unless overridden in <varname>zone_config</varname>).
@@ -122,33 +115,6 @@
See the <citetitle>BIND 10 Guide</citetitle> for configuration examples.
The default is an empty list, that is, no zone specific configuration.
</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 ... -->
@@ -160,31 +126,29 @@
</simpara></note>
-<!--
-
-tsig_key_ring list of
-tsig_key string
-
--->
-
<!-- TODO: formating -->
<para>
The configuration commands are:
</para>
+
<para>
- <command>shutdown</command> stops all outbound zone transfers
- and exits <command>b10-xfrout</command>. (Note that the BIND 10
- boss process will restart this service.)
+ <command>notify</command> triggers <command>b10-xfrout</command>
+ to send NOTIFY message(s).
+ It has the following arguments: <varname>zone_name</varname>
+ to define the zone to send notifies for and the optional
+ <varname>zone_class</varname> to define the class (defaults to
+ <quote>IN</quote>).
+ <citerefentry><refentrytitle>b10-xfrin</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ sends this command when a zone transferred in successfully.
</para>
<para>
- <command>zone_new_data_ready</command> is sent from
- <citerefentry><refentrytitle>b10-xfrin</refentrytitle><manvolnum>8</manvolnum></citerefentry>
- to indicate that the zone transferred in successfully.
- This triggers <command>b10-xfrout</command> to send NOTIFY
- message(s).
- This is an internal command and not exposed to the administrator.
-<!-- not defined in spec -->
+ <command>shutdown</command> stops all outbound zone transfers
+ and exits <command>b10-xfrout</command>.
+ This has an optional <varname>pid</varname> argument to
+ select the process ID to stop.
+ (Note that the BIND 10 boss process may restart this service
+ if configured.)
</para>
</refsect1>
diff --git a/src/bin/xfrout/tests/.gitignore b/src/bin/xfrout/tests/.gitignore
new file mode 100644
index 0000000..92c94aa
--- /dev/null
+++ b/src/bin/xfrout/tests/.gitignore
@@ -0,0 +1,2 @@
+/xfrout_test
+/xfrout_test.py
diff --git a/src/bin/xfrout/tests/testdata/test.sqlite3 b/src/bin/xfrout/tests/testdata/test.sqlite3
index 9eb14f1..a594b44 100644
Binary files a/src/bin/xfrout/tests/testdata/test.sqlite3 and b/src/bin/xfrout/tests/testdata/test.sqlite3 differ
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index 67c6008..b60535c 100644
--- a/src/bin/xfrout/tests/xfrout_test.py.in
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -19,6 +19,7 @@
import unittest
import os
from isc.testutils.tsigctx_mock import MockTSIGContext
+from isc.testutils.ccsession_mock import MockModuleCCSession
from isc.cc.session import *
import isc.config
from isc.dns import *
@@ -27,6 +28,7 @@ from xfrout import *
import xfrout
import isc.log
import isc.acl.dns
+import isc.server_common.tsig_keyring
TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
@@ -105,16 +107,16 @@ class MockDataSrcClient:
'''
if name == Name('nosoa.example.com') and rrtype == RRType.SOA():
- return (ZoneFinder.NXDOMAIN, None)
+ return (ZoneFinder.NXDOMAIN, None, 0)
elif name == Name('multisoa.example.com') and rrtype == RRType.SOA():
soa_rrset = create_soa(SOA_CURRENT_VERSION)
soa_rrset.add_rdata(soa_rrset.get_rdata()[0])
- return (ZoneFinder.SUCCESS, soa_rrset)
+ return (ZoneFinder.SUCCESS, soa_rrset, 0)
elif name == Name('maxserial.example.com'):
soa_rrset = create_soa(0xffffffff)
- return (ZoneFinder.SUCCESS, soa_rrset)
+ return (ZoneFinder.SUCCESS, soa_rrset, 0)
elif rrtype == RRType.SOA():
- return (ZoneFinder.SUCCESS, create_soa(SOA_CURRENT_VERSION))
+ return (ZoneFinder.SUCCESS, create_soa(SOA_CURRENT_VERSION), 0)
raise ValueError('Unexpected input to mock finder: bug in test case?')
def get_iterator(self, zone_name, adjust_ttl=False):
@@ -1154,6 +1156,39 @@ class TestUnixSockServer(unittest.TestCase):
self.write_sock, self.read_sock = socket.socketpair()
self.unix = MyUnixSockServer()
+ def test_tsig_keyring(self):
+ """
+ Check we use the global keyring when starting a request.
+ """
+ try:
+ # These are just so the keyring can be started
+ self.unix._cc.add_remote_config_by_name = \
+ lambda name, callback: None
+ self.unix._cc.get_remote_config_value = \
+ lambda module, name: ([], True)
+ self.unix._cc.remove_remote_config = lambda name: None
+ isc.server_common.tsig_keyring.init_keyring(self.unix._cc)
+ # These are not really interesting for the test. These are just
+ # handled over, so strings are OK.
+ self.unix._guess_remote = lambda sock: "Address"
+ self.unix._zone_config = "Zone config"
+ self.unix._acl = "acl"
+ # This would be the handler class, but we just check it is passed
+ # the right parametes, so function is enough for that.
+ keys = isc.server_common.tsig_keyring.get_keyring()
+ def handler(sock, data, server, keyring, address, acl, config):
+ self.assertEqual("sock", sock)
+ self.assertEqual("data", data)
+ self.assertEqual(self.unix, server)
+ self.assertEqual(keys, keyring)
+ self.assertEqual("Address", address)
+ self.assertEqual("acl", acl)
+ self.assertEqual("Zone config", config)
+ self.unix.RequestHandlerClass = handler
+ self.unix.finish_request("sock", "data")
+ finally:
+ isc.server_common.tsig_keyring.deinit_keyring()
+
def test_guess_remote(self):
"""Test we can guess the remote endpoint when we have only the
file descriptor. This is needed, because we get only that one
@@ -1213,25 +1248,12 @@ class TestUnixSockServer(unittest.TestCase):
def test_update_config_data(self):
self.check_default_ACL()
- tsig_key_str = 'example.com:SFuWd/q99SzF8Yzd1QbB9g=='
- tsig_key_list = [tsig_key_str]
- bad_key_list = ['bad..example.com:SFuWd/q99SzF8Yzd1QbB9g==']
self.unix.update_config_data({'transfers_out':10 })
self.assertEqual(self.unix._max_transfers_out, 10)
- self.assertTrue(self.unix.tsig_key_ring is not None)
self.check_default_ACL()
- self.unix.update_config_data({'transfers_out':9,
- 'tsig_key_ring':tsig_key_list})
+ self.unix.update_config_data({'transfers_out':9})
self.assertEqual(self.unix._max_transfers_out, 9)
- self.assertEqual(self.unix.tsig_key_ring.size(), 1)
- self.unix.tsig_key_ring.remove(Name("example.com."))
- self.assertEqual(self.unix.tsig_key_ring.size(), 0)
-
- # bad tsig key
- config_data = {'transfers_out':9, 'tsig_key_ring': bad_key_list}
- self.assertRaises(None, self.unix.update_config_data(config_data))
- self.assertEqual(self.unix.tsig_key_ring.size(), 0)
# Load the ACL
self.unix.update_config_data({'transfer_acl': [{'from': '127.0.0.1',
@@ -1423,6 +1445,31 @@ class TestInitialization(unittest.TestCase):
xfrout.init_paths()
self.assertEqual(xfrout.UNIX_SOCKET_FILE, "The/Socket/File")
+class MyNotifier():
+ def __init__(self):
+ self.shutdown_called = False
+
+ def shutdown(self):
+ self.shutdown_called = True
+
+class MyXfroutServer(XfroutServer):
+ def __init__(self):
+ self._cc = MockModuleCCSession()
+ self._shutdown_event = threading.Event()
+ self._notifier = MyNotifier()
+ self._unix_socket_server = None
+ # Disable the wait for threads
+ self._wait_for_threads = lambda : None
+
+class TestXfroutServer(unittest.TestCase):
+ def setUp(self):
+ self.xfrout_server = MyXfroutServer()
+
+ def test_shutdown(self):
+ self.xfrout_server.shutdown()
+ self.assertTrue(self.xfrout_server._notifier.shutdown_called)
+ self.assertTrue(self.xfrout_server._cc.stopped)
+
if __name__== "__main__":
isc.log.resetUnitTestRootLogger()
unittest.main()
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index c6b3275..4dd12ce 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -34,11 +34,18 @@ import select
import errno
from optparse import OptionParser, OptionValueError
from isc.util import socketserver_mixin
+import isc.server_common.tsig_keyring
from isc.log_messages.xfrout_messages import *
isc.log.init("b10-xfrout")
logger = isc.log.Logger("xfrout")
+
+# Pending system-wide debug level definitions, the ones we
+# use here are hardcoded for now
+DBG_PROCESS = logger.DBGLVL_TRACE_BASIC
+DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL
+
DBG_XFROUT_TRACE = logger.DBGLVL_TRACE_BASIC
try:
@@ -335,7 +342,7 @@ class XfroutSession():
result, finder = self._datasrc_client.find_zone(zone_name)
if result != DataSourceClient.SUCCESS:
return (Rcode.NOTAUTH(), None)
- result, soa_rrset = finder.find(zone_name, RRType.SOA())
+ result, soa_rrset, _ = finder.find(zone_name, RRType.SOA())
if result != ZoneFinder.SUCCESS:
return (Rcode.SERVFAIL(), None)
# Especially for database-based zones, a working zone may be in
@@ -769,7 +776,7 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
zone_config = self._zone_config
self._lock.release()
self.RequestHandlerClass(sock_fd, request_data, self,
- self.tsig_key_ring,
+ isc.server_common.tsig_keyring.get_keyring(),
self._guess_remote(sock_fd), acl, zone_config)
def _remove_unused_sock_file(self, sock_file):
@@ -833,7 +840,6 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
self._acl = new_acl
self._zone_config = new_zone_config
self._max_transfers_out = new_config.get('transfers_out')
- self.set_tsig_key_ring(new_config.get('tsig_key_ring'))
except Exception as e:
self._lock.release()
raise e
@@ -870,21 +876,6 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
zclass_str + ': ' + str(e))
return new_config
- def set_tsig_key_ring(self, key_list):
- """Set the tsig_key_ring , given a TSIG key string list representation. """
-
- # XXX add values to configure zones/tsig options
- self.tsig_key_ring = TSIGKeyRing()
- # If key string list is empty, create a empty tsig_key_ring
- if not key_list:
- return
-
- for key_item in key_list:
- try:
- self.tsig_key_ring.add(TSIGKey(key_item))
- except InvalidParameter as ipe:
- logger.error(XFROUT_BAD_TSIG_KEY_STRING, str(key_item))
-
def get_db_file(self):
file, is_default = self._cc.get_remote_config_value("Auth", "database_file")
# this too should be unnecessary, but currently the
@@ -920,7 +911,8 @@ class XfroutServer:
self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
self._config_data = self._cc.get_full_config()
self._cc.start()
- self._cc.add_remote_config(AUTH_SPECFILE_LOCATION);
+ self._cc.add_remote_config(AUTH_SPECFILE_LOCATION)
+ isc.server_common.tsig_keyring.init_keyring(self._cc)
self._start_xfr_query_listener()
self._start_notifier()
@@ -940,7 +932,7 @@ class XfroutServer:
self._notifier.dispatcher()
def send_notify(self, zone_name, zone_class):
- self._notifier.send_notify(zone_name, zone_class)
+ return self._notifier.send_notify(zone_name, zone_class)
def config_handler(self, new_config):
'''Update config data. TODO. Do error check'''
@@ -969,12 +961,18 @@ class XfroutServer:
global xfrout_server
xfrout_server = None #Avoid shutdown is called twice
+ self._cc.send_stopping()
self._shutdown_event.set()
self._notifier.shutdown()
if self._unix_socket_server:
self._unix_socket_server.shutdown()
+ self._wait_for_threads()
- # Wait for all threads to terminate
+ def _wait_for_threads(self):
+ # Wait for all threads to terminate. this is a call that is only used
+ # in shutdown(), but it has its own method, so we can test shutdown
+ # without involving thread operations (the test would override this
+ # method)
main_thread = threading.currentThread()
for th in threading.enumerate():
if th is main_thread:
@@ -990,10 +988,16 @@ class XfroutServer:
elif cmd == notify_out.ZONE_NEW_DATA_READY_CMD:
zone_name = args.get('zone_name')
zone_class = args.get('zone_class')
- if zone_name and zone_class:
+ if not zone_class:
+ zone_class = str(RRClass.IN())
+ if zone_name:
logger.info(XFROUT_NOTIFY_COMMAND, zone_name, zone_class)
- self.send_notify(zone_name, zone_class)
- answer = create_answer(0)
+ if self.send_notify(zone_name, zone_class):
+ answer = create_answer(0)
+ else:
+ zonestr = notify_out.format_zone_str(Name(zone_name),
+ zone_class)
+ answer = create_answer(1, "Unknown zone: " + zonestr)
else:
answer = create_answer(1, "Bad command parameter:" + str(args))
@@ -1004,6 +1008,7 @@ class XfroutServer:
def run(self):
'''Get and process all commands sent from cfgmgr or other modules. '''
+ logger.debug(DBG_PROCESS, XFROUT_STARTED)
while not self._shutdown_event.is_set():
self._cc.check_command(False)
diff --git a/src/bin/xfrout/xfrout.spec.pre.in b/src/bin/xfrout/xfrout.spec.pre.in
index 0891a57..30c9d56 100644
--- a/src/bin/xfrout/xfrout.spec.pre.in
+++ b/src/bin/xfrout/xfrout.spec.pre.in
@@ -9,48 +9,6 @@
"item_default": 10
},
{
- "item_name": "log_name",
- "item_type": "string",
- "item_optional": false,
- "item_default": "Xfrout"
- },
- {
- "item_name": "log_file",
- "item_type": "string",
- "item_optional": false,
- "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/log/Xfrout.log"
- },
- {
- "item_name": "log_severity",
- "item_type": "string",
- "item_optional": false,
- "item_default": "debug"
- },
- {
- "item_name": "log_versions",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 5
- },
- {
- "item_name": "log_max_bytes",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 1048576
- },
- {
- "item_name": "tsig_key_ring",
- "item_type": "list",
- "item_optional": true,
- "item_default": [],
- "list_item_spec" :
- {
- "item_name": "tsig_key",
- "item_type": "string",
- "item_optional": true
- }
- },
- {
"item_name": "transfer_acl",
"item_type": "list",
"item_optional": false,
@@ -106,7 +64,26 @@
{
"command_name": "shutdown",
"command_description": "Shut down Xfrout",
- "command_args": []
+ "command_args": [
+ {
+ "item_name": "pid",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
+ },
+ { "command_name": "notify",
+ "command_description": "Send notifies for zone",
+ "command_args": [
+ { "item_name": "zone_name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "" },
+ { "item_name": "zone_class",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "IN"
+ } ]
}
]
}
diff --git a/src/bin/xfrout/xfrout_messages.mes b/src/bin/xfrout/xfrout_messages.mes
index fcc2e59..9996a5a 100644
--- a/src/bin/xfrout/xfrout_messages.mes
+++ b/src/bin/xfrout/xfrout_messages.mes
@@ -133,6 +133,10 @@ be a result of rare local error such as memory allocation failure and
shouldn't happen under normal conditions. The error is included in the
log message.
+% XFROUT_STARTED xfrout started
+This informational message is output by xfrout when all initialization
+has been completed and it is entering its main loop.
+
% XFROUT_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down
There was a keyboard interrupt signal to stop the xfrout daemon. The
daemon will now shut down.
diff --git a/src/bin/zonemgr/.gitignore b/src/bin/zonemgr/.gitignore
new file mode 100644
index 0000000..2d64f2d
--- /dev/null
+++ b/src/bin/zonemgr/.gitignore
@@ -0,0 +1,5 @@
+/b10-zonemgr
+/run_b10-zonemgr.sh
+/zonemgr.py
+/zonemgr.spec
+/zonemgr.spec.pre
diff --git a/src/bin/zonemgr/b10-zonemgr.8 b/src/bin/zonemgr/b10-zonemgr.8
index 1d24bbf..1bce2af 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: December 8, 2011
+.\" Date: February 28, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-ZONEMGR" "8" "December 8, 2011" "BIND10" "BIND10"
+.TH "B10\-ZONEMGR" "8" "February 28, 2012" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -98,7 +98,9 @@ This is an internal command and not exposed to the administrator\&.
\fBshutdown\fR
exits
-\fBb10\-zonemgr\fR\&. (Note that the BIND 10 boss process will restart this service\&.)
+\fBb10\-zonemgr\fR\&. This has an optional
+\fIpid\fR
+argument to select the process ID to stop\&. (Note that the BIND 10 boss process may restart this service if configured\&.)
.PP
\fBzone_new_data_ready\fR
@@ -128,5 +130,5 @@ The
daemon was designed in July 2010 by CNNIC for the ISC BIND 10 project\&.
.SH "COPYRIGHT"
.br
-Copyright \(co 2010-2011 Internet Systems Consortium, Inc. ("ISC")
+Copyright \(co 2010-2012 Internet Systems Consortium, Inc. ("ISC")
.br
diff --git a/src/bin/zonemgr/b10-zonemgr.xml b/src/bin/zonemgr/b10-zonemgr.xml
index 5ea6041..f859d23 100644
--- a/src/bin/zonemgr/b10-zonemgr.xml
+++ b/src/bin/zonemgr/b10-zonemgr.xml
@@ -2,7 +2,7 @@
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[<!ENTITY mdash "—">]>
<!--
- - Copyright (C) 2010-2011 Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2010-2012 Internet Systems Consortium, Inc. ("ISC")
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>December 8, 2011</date>
+ <date>February 28, 2012</date>
</refentryinfo>
<refmeta>
@@ -36,7 +36,7 @@
<docinfo>
<copyright>
- <year>2010-2011</year>
+ <year>2010-2012</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
@@ -186,7 +186,10 @@
<para>
<command>shutdown</command> exits <command>b10-zonemgr</command>.
- (Note that the BIND 10 boss process will restart this service.)
+ This has an optional <varname>pid</varname> argument to
+ select the process ID to stop.
+ (Note that the BIND 10 boss process may restart this service
+ if configured.)
</para>
<para>
diff --git a/src/bin/zonemgr/tests/.gitignore b/src/bin/zonemgr/tests/.gitignore
new file mode 100644
index 0000000..d3a63a3
--- /dev/null
+++ b/src/bin/zonemgr/tests/.gitignore
@@ -0,0 +1,2 @@
+/initdb.file
+/zonemgr_test
diff --git a/src/bin/zonemgr/tests/Makefile.am b/src/bin/zonemgr/tests/Makefile.am
index 8c6b904..b60fae7 100644
--- a/src/bin/zonemgr/tests/Makefile.am
+++ b/src/bin/zonemgr/tests/Makefile.am
@@ -20,6 +20,7 @@ endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
+ TESTDATAOBJDIR=$(abs_top_builddir)/src/bin/zonemgr/tests/ \
B10_FROM_BUILD=$(abs_top_builddir) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/zonemgr:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/xfr/.libs \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
diff --git a/src/bin/zonemgr/tests/zonemgr_test.py b/src/bin/zonemgr/tests/zonemgr_test.py
index 600453d..548d921 100644
--- a/src/bin/zonemgr/tests/zonemgr_test.py
+++ b/src/bin/zonemgr/tests/zonemgr_test.py
@@ -20,6 +20,7 @@ import unittest
import os
import tempfile
from zonemgr import *
+from isc.testutils.ccsession_mock import MockModuleCCSession
ZONE_NAME_CLASS1_IN = ("example.net.", "IN")
ZONE_NAME_CLASS1_CH = ("example.net.", "CH")
@@ -34,6 +35,8 @@ LOWERBOUND_RETRY = 5
REFRESH_JITTER = 0.10
RELOAD_JITTER = 0.75
+TEST_SQLITE3_DBFILE = os.getenv("TESTDATAOBJDIR") + '/initdb.file'
+
class ZonemgrTestException(Exception):
pass
@@ -48,14 +51,15 @@ class MySession():
def group_recvmsg(self, nonblock, seq):
return None, None
-class FakeCCSession(isc.config.ConfigData):
+class FakeCCSession(isc.config.ConfigData, MockModuleCCSession):
def __init__(self):
module_spec = isc.config.module_spec_from_file(SPECFILE_LOCATION)
ConfigData.__init__(self, module_spec)
+ MockModuleCCSession.__init__(self)
def get_remote_config_value(self, module_name, identifier):
if module_name == "Auth" and identifier == "database_file":
- return "initdb.file", False
+ return TEST_SQLITE3_DBFILE, False
else:
return "unknown", False
@@ -79,7 +83,7 @@ class MyZonemgrRefresh(ZonemgrRefresh):
return None
sqlite3_ds.get_zone_soa = get_zone_soa
- ZonemgrRefresh.__init__(self, MySession(), "initdb.file",
+ ZonemgrRefresh.__init__(self, MySession(), TEST_SQLITE3_DBFILE,
self._slave_socket, FakeCCSession())
current_time = time.time()
self._zonemgr_refresh_info = {
@@ -97,11 +101,18 @@ class MyZonemgrRefresh(ZonemgrRefresh):
class TestZonemgrRefresh(unittest.TestCase):
def setUp(self):
+ if os.path.exists(TEST_SQLITE3_DBFILE):
+ os.unlink(TEST_SQLITE3_DBFILE)
self.stderr_backup = sys.stderr
sys.stderr = open(os.devnull, 'w')
self.zone_refresh = MyZonemgrRefresh()
self.cc_session = FakeCCSession()
+ def tearDown(self):
+ if os.path.exists(TEST_SQLITE3_DBFILE):
+ os.unlink(TEST_SQLITE3_DBFILE)
+ sys.stderr = self.stderr_backup
+
def test_random_jitter(self):
max = 100025.120
jitter = 0
@@ -600,13 +611,10 @@ class TestZonemgrRefresh(unittest.TestCase):
self.zone_refresh.update_config_data,
config, self.cc_session)
- def tearDown(self):
- sys.stderr= self.stderr_backup
-
class MyZonemgr(Zonemgr):
def __init__(self):
- self._db_file = "initdb.file"
+ self._db_file = TEST_SQLITE3_DBFILE
self._zone_refresh = None
self._shutdown_event = threading.Event()
self._cc = MySession()
@@ -626,8 +634,14 @@ class MyZonemgr(Zonemgr):
class TestZonemgr(unittest.TestCase):
def setUp(self):
+ if os.path.exists(TEST_SQLITE3_DBFILE):
+ os.unlink(TEST_SQLITE3_DBFILE)
self.zonemgr = MyZonemgr()
+ def tearDown(self):
+ if os.path.exists(TEST_SQLITE3_DBFILE):
+ os.unlink(TEST_SQLITE3_DBFILE)
+
def test_config_handler(self):
config_data1 = {
"lowerbound_refresh" : 60,
@@ -648,8 +662,8 @@ class TestZonemgr(unittest.TestCase):
self.zonemgr.config_handler(config_data3)
self.assertEqual(0.5, self.zonemgr._config_data.get("refresh_jitter"))
# The zone doesn't exist in database, simply skip loading soa for it and log an warning
- self.zonemgr._zone_refresh = ZonemgrRefresh(None, "initdb.file", None,
- FakeCCSession())
+ self.zonemgr._zone_refresh = ZonemgrRefresh(None, TEST_SQLITE3_DBFILE,
+ None, FakeCCSession())
config_data1["secondary_zones"] = [{"name": "nonexistent.example",
"class": "IN"}]
self.assertEqual(self.zonemgr.config_handler(config_data1),
@@ -661,7 +675,7 @@ class TestZonemgr(unittest.TestCase):
self.assertEqual(0.1, self.zonemgr._config_data.get("refresh_jitter"))
def test_get_db_file(self):
- self.assertEqual("initdb.file", self.zonemgr.get_db_file())
+ self.assertEqual(TEST_SQLITE3_DBFILE, self.zonemgr.get_db_file())
def test_parse_cmd_params(self):
params1 = {"zone_name" : "example.com.", "zone_class" : "CH", "master" : "127.0.0.1"}
@@ -683,8 +697,11 @@ class TestZonemgr(unittest.TestCase):
self.zonemgr._config_data_check(config_data3)
self.assertEqual(0.5, config_data3.get("refresh_jitter"))
- def tearDown(self):
- pass
+ def test_shutdown(self):
+ self.assertFalse(self.zonemgr._module_cc.stopped)
+ self.zonemgr._shutdown_event.set()
+ self.zonemgr.run()
+ self.assertTrue(self.zonemgr._module_cc.stopped)
if __name__== "__main__":
isc.log.resetUnitTestRootLogger()
diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
index 4060bb5..87589a8 100755
--- a/src/bin/zonemgr/zonemgr.py.in
+++ b/src/bin/zonemgr/zonemgr.py.in
@@ -44,6 +44,11 @@ from isc.log_messages.zonemgr_messages import *
isc.log.init("b10-zonemgr")
logger = isc.log.Logger("zonemgr")
+# Pending system-wide debug level definitions, the ones we
+# use here are hardcoded for now
+DBG_PROCESS = logger.DBGLVL_TRACE_BASIC
+DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL
+
# Constants for debug levels.
DBG_START_SHUT = logger.DBGLVL_START_SHUT
DBG_ZONEMGR_COMMAND = logger.DBGLVL_COMMAND
@@ -657,9 +662,13 @@ class Zonemgr:
return answer
def run(self):
+ logger.debug(DBG_PROCESS, ZONEMGR_STARTED)
self.running = True
- while not self._shutdown_event.is_set():
- self._module_cc.check_command(False)
+ try:
+ while not self._shutdown_event.is_set():
+ self._module_cc.check_command(False)
+ finally:
+ self._module_cc.send_stopping()
zonemgrd = None
diff --git a/src/bin/zonemgr/zonemgr.spec.pre.in b/src/bin/zonemgr/zonemgr.spec.pre.in
index 36f02df..defa203 100644
--- a/src/bin/zonemgr/zonemgr.spec.pre.in
+++ b/src/bin/zonemgr/zonemgr.spec.pre.in
@@ -63,7 +63,13 @@
{
"command_name": "shutdown",
"command_description": "Shut down Zonemgr",
- "command_args": []
+ "command_args": [
+ {
+ "item_name": "pid",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
}
]
}
diff --git a/src/bin/zonemgr/zonemgr_messages.mes b/src/bin/zonemgr/zonemgr_messages.mes
index 8abec5d..c866b79 100644
--- a/src/bin/zonemgr/zonemgr_messages.mes
+++ b/src/bin/zonemgr/zonemgr_messages.mes
@@ -67,6 +67,10 @@ zone manager to record the master server for the zone and start a timer;
when the timer expires, the master will be polled to see if it contains
new data.
+% ZONEMGR_STARTED zonemgr started
+This informational message is output by zonemgr when all initialization
+has been completed and it is entering its main loop.
+
% ZONEMGR_RECEIVE_SHUTDOWN received SHUTDOWN command
This is a debug message indicating that the zone manager has received
a SHUTDOWN command over the command channel from the Boss process.
@@ -128,9 +132,11 @@ a bug report.
% ZONEMGR_UNKNOWN_ZONE_FAIL zone %1 (class %2) is not known to the zone manager
An XFRIN operation has failed but the zone that was the subject of the
-operation is not being managed by the zone manager. This may indicate
-an error in the program (as the operation should not have been initiated
-if this were the case). Please submit a bug report.
+operation is not being managed by the zone manager. This can be either the
+result of a bindctl command to transfer in a currently unknown (or mistyped)
+zone, or, if this error appears without the administrator giving transfer
+commands, it can indicate an error in the program, as it should not have
+initiated transfers of unknown zones on its own.
% ZONEMGR_UNKNOWN_ZONE_NOTIFIED notified zone %1 (class %2) is not known to the zone manager
A NOTIFY was received but the zone that was the subject of the operation
diff --git a/src/cppcheck-suppress.lst b/src/cppcheck-suppress.lst
index 1020ffe..ff4a79a 100644
--- a/src/cppcheck-suppress.lst
+++ b/src/cppcheck-suppress.lst
@@ -2,10 +2,12 @@
// 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:61
-// Intentional self assignment tests. Suppress warning about them.
-selfAssignment:src/lib/dns/tests/name_unittest.cc:293
-selfAssignment:src/lib/dns/tests/rdata_unittest.cc:228
-selfAssignment:src/lib/dns/tests/tsigkey_unittest.cc:137
-selfAssignment:src/lib/dns/tests/rdata_txt_like_unittest.cc:222
+
+// Please don't add any suppressions here. We now use inline
+// suppressions (in the .cc files) so that we don't have to
+// maintain line numbers in this file.
+//
+// See the cppcheck manual for syntax. It is something like:
+//
+// // cppcheck-suppress duplicateExpression
+// EXPECT_FALSE(small_name < small_name);
diff --git a/src/lib/acl/ip_check.cc b/src/lib/acl/ip_check.cc
index 76aacca..7192064 100644
--- a/src/lib/acl/ip_check.cc
+++ b/src/lib/acl/ip_check.cc
@@ -12,6 +12,7 @@
// 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 <exceptions/exceptions.h>
diff --git a/src/lib/acl/tests/.gitignore b/src/lib/acl/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/lib/acl/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/lib/asiodns/.gitignore b/src/lib/asiodns/.gitignore
new file mode 100644
index 0000000..dedf17e
--- /dev/null
+++ b/src/lib/asiodns/.gitignore
@@ -0,0 +1,2 @@
+/asiodns_messages.cc
+/asiodns_messages.h
diff --git a/src/lib/asiodns/Makefile.am b/src/lib/asiodns/Makefile.am
index 2d246ef..a03f147 100644
--- a/src/lib/asiodns/Makefile.am
+++ b/src/lib/asiodns/Makefile.am
@@ -24,7 +24,9 @@ libasiodns_la_SOURCES += dns_server.h
libasiodns_la_SOURCES += dns_service.cc dns_service.h
libasiodns_la_SOURCES += tcp_server.cc tcp_server.h
libasiodns_la_SOURCES += udp_server.cc udp_server.h
+libasiodns_la_SOURCES += sync_udp_server.cc sync_udp_server.h
libasiodns_la_SOURCES += io_fetch.cc io_fetch.h
+libasiodns_la_SOURCES += logger.h logger.cc
nodist_libasiodns_la_SOURCES = asiodns_messages.cc asiodns_messages.h
diff --git a/src/lib/asiodns/asiodns_messages.mes b/src/lib/asiodns/asiodns_messages.mes
index feb75d4..8fbafdd 100644
--- a/src/lib/asiodns/asiodns_messages.mes
+++ b/src/lib/asiodns/asiodns_messages.mes
@@ -14,6 +14,14 @@
$NAMESPACE isc::asiodns
+% ASIODNS_FD_ADD_TCP adding a new TCP server by opened fd %1
+A debug message informing about installing a file descriptor as a server.
+The file descriptor number is noted.
+
+% ASIODNS_FD_ADD_UDP adding a new UDP server by opened fd %1
+A debug message informing about installing a file descriptor as a server.
+The file descriptor number is noted.
+
% ASIODNS_FETCH_COMPLETED upstream fetch to %1(%2) has now completed
A debug message, this records that the upstream fetch (a query made by the
resolver on behalf of its client) to the specified address has completed.
diff --git a/src/lib/asiodns/dns_server.h b/src/lib/asiodns/dns_server.h
index d3a8528..119aa66 100644
--- a/src/lib/asiodns/dns_server.h
+++ b/src/lib/asiodns/dns_server.h
@@ -88,22 +88,6 @@ public:
/// 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
diff --git a/src/lib/asiodns/dns_service.cc b/src/lib/asiodns/dns_service.cc
index ed7d717..2cfdea5 100644
--- a/src/lib/asiodns/dns_service.cc
+++ b/src/lib/asiodns/dns_service.cc
@@ -14,28 +14,19 @@
#include <config.h>
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <unistd.h> // for some IPC/network system calls
+#include <exceptions/exceptions.h>
-#include <boost/lexical_cast.hpp>
-
-#include <log/dummylog.h>
-
-#include <asio.hpp>
#include <dns_service.h>
+
#include <asiolink/io_service.h>
-#include <asiolink/io_service.h>
+
+#include <asio.hpp> // xxx_server.h requires this to be included first
#include <tcp_server.h>
#include <udp_server.h>
+#include <sync_udp_server.h>
-#include <log/dummylog.h>
-
-#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>
-using isc::log::dlog;
-
using namespace isc::asiolink;
namespace isc {
@@ -44,134 +35,37 @@ namespace asiodns {
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);
+ DNSServiceImpl(IOService& io_service, SimpleCallback* checkin,
+ DNSLookup* lookup, DNSAnswer* answer) :
+ io_service_(io_service), checkin_(checkin), lookup_(lookup),
+ answer_(answer)
+ {}
IOService& io_service_;
typedef boost::shared_ptr<UDPServer> UDPServerPtr;
+ typedef boost::shared_ptr<SyncUDPServer> SyncUDPServerPtr;
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);
+ SimpleCallback* checkin_;
+ DNSLookup* lookup_;
+ DNSAnswer* answer_;
+
+ template<class Ptr, class Server> void addServerFromFD(int fd, int af) {
+ Ptr server(new Server(io_service_.get_io_service(), fd, af, checkin_,
+ lookup_, answer_));
+ (*server)();
+ servers_.push_back(server);
}
};
-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)
+ DNSLookup* lookup, DNSAnswer *answer) :
+ impl_(new DNSServiceImpl(io_service, checkin, lookup, answer)),
+ io_service_(io_service)
{
}
@@ -179,14 +73,22 @@ DNSService::~DNSService() {
delete impl_;
}
-void
-DNSService::addServer(const char& port, const std::string& address) {
- impl_->addServer(port, convertAddr(address));
+void DNSService::addServerTCPFromFD(int fd, int af) {
+ impl_->addServerFromFD<DNSServiceImpl::TCPServerPtr, TCPServer>(fd, af);
}
-void
-DNSService::addServer(uint16_t port, const std::string& address) {
- impl_->addServer(port, convertAddr(address));
+void DNSService::addServerUDPFromFD(int fd, int af, ServerFlag options) {
+ if ((~SERVER_DEFINED_FLAGS & static_cast<unsigned int>(options)) != 0) {
+ isc_throw(isc::InvalidParameter, "Invalid DNS/UDP server option: "
+ << options);
+ }
+ if ((options & SERVER_SYNC_OK) != 0) {
+ impl_->addServerFromFD<DNSServiceImpl::SyncUDPServerPtr,
+ SyncUDPServer>(fd, af);
+ } else {
+ impl_->addServerFromFD<DNSServiceImpl::UDPServerPtr, UDPServer>(
+ fd, af);
+ }
}
void
diff --git a/src/lib/asiodns/dns_service.h b/src/lib/asiodns/dns_service.h
index 6b6a6c0..8f2f6d7 100644
--- a/src/lib/asiodns/dns_service.h
+++ b/src/lib/asiodns/dns_service.h
@@ -27,6 +27,66 @@ class DNSLookup;
class DNSAnswer;
class DNSServiceImpl;
+/// \brief A base class for common \c DNSService interfaces.
+///
+/// This class is defined mainly for test code so it can use a faked/mock
+/// version of a derived class and test scenarios that would involve
+/// \c DNSService without actually instantiating the real service class.
+///
+/// It doesn't intend to be a customization for other purposes - we generally
+/// expect non test code only use \c DNSService directly.
+/// For this reason most of the detailed description are given in the
+/// \c DNSService class. See that for further details of specific methods
+/// and class behaviors.
+class DNSServiceBase {
+protected:
+ /// \brief Default constructor.
+ ///
+ /// This is protected so this class couldn't be accidentally instantiated
+ /// directly, even if there were no pure virtual functions.
+ DNSServiceBase() {}
+
+public:
+ /// \brief Flags for optional server properties.
+ ///
+ /// The values of this enumerable type are intended to be used to specify
+ /// a particular property of the server created via the \c addServer
+ /// variants. As we see need for more such properties, a compound
+ /// form of flags (i.e., a single value generated by bitwise OR'ed
+ /// multiple flag values) will be allowed.
+ ///
+ /// Note: the description is given here because it's used in the method
+ /// signature. It essentially belongs to the derived \c DNSService
+ /// class.
+ enum ServerFlag {
+ SERVER_DEFAULT = 0, ///< The default flag (no particular property)
+ SERVER_SYNC_OK = 1 ///< The server can act in the "synchronous" mode.
+ ///< In this mode, the client ensures that the
+ ///< lookup provider always completes the query
+ ///< process and it immediately releases the
+ ///< ownership of the given buffer. This allows
+ ///< the server implementation to introduce some
+ ///< optimization such as omitting unnecessary
+ ///< operation or reusing internal resources.
+ ///< Note that in functionality the non
+ ///< "synchronous" mode is compatible with the
+ ///< synchronous mode; it's up to the server
+ ///< implementation whether it exploits the
+ ///< information given by the client.
+ };
+
+public:
+ /// \brief The destructor.
+ virtual ~DNSServiceBase() {}
+
+ virtual void addServerTCPFromFD(int fd, int af) = 0;
+ virtual void addServerUDPFromFD(int fd, int af,
+ ServerFlag options = SERVER_DEFAULT) = 0;
+ virtual void clearServers() = 0;
+
+ virtual asiolink::IOService& getIOService() = 0;
+};
+
/// \brief Handle DNS Queries
///
/// DNSService is the service that handles DNS queries and answers with
@@ -34,7 +94,7 @@ class DNSServiceImpl;
/// 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 {
+class DNSService : public DNSServiceBase {
///
/// \name Constructors and Destructor
///
@@ -45,49 +105,72 @@ private:
DNSService(const DNSService& source);
DNSService& operator=(const DNSService& source);
+private:
+ // Bit or'ed all defined \c ServerFlag values. Used internally for
+ // compatibility check. Note that this doesn't have to be used by
+ // applications, and doesn't have to be defined in the "base" class.
+ static const unsigned int SERVER_DEFINED_FLAGS = 1;
+
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(asiolink::IOService& io_service, const char& port,
- const char& address, isc::asiolink::SimpleCallback* checkin,
- DNSLookup* lookup, DNSAnswer* answer);
- /// \brief The constructor with a specific port on which the services
- /// listen on.
+ /// \brief The constructor without any servers.
///
- /// 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.
+ /// Use addServerTCPFromFD() or addServerUDPFromFD() to add some servers.
///
/// \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(asiolink::IOService& io_service, const char& port,
- const bool use_ipv4, const bool use_ipv6,
- isc::asiolink::SimpleCallback* checkin, DNSLookup* lookup,
- DNSAnswer* answer);
- /// \brief The constructor without any servers.
- ///
- /// Use addServer() to add some servers.
- DNSService(asiolink::IOService& io_service, isc::asiolink::SimpleCallback* checkin,
+ DNSService(asiolink::IOService& io_service,
+ isc::asiolink::SimpleCallback* checkin,
DNSLookup* lookup, DNSAnswer* answer);
+
/// \brief The destructor.
- ~DNSService();
+ virtual ~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 Add another TCP server/listener to the service from already
+ /// opened file descriptor
+ ///
+ /// Adds a new TCP server using an already opened file descriptor (eg. it
+ /// only wraps it so the file descriptor is usable within the event loop).
+ /// The file descriptor must be associated with a TCP socket of the given
+ /// address family that is bound to an appropriate port (and possibly a
+ /// specific address) and is ready for listening to new connection
+ /// requests but has not actually started listening.
+ ///
+ /// At the moment, TCP servers don't support any optional properties;
+ /// so unlike the UDP version of the method it doesn't have an \c options
+ /// argument.
+ ///
+ /// \param fd the file descriptor to be used.
+ /// \param af the address family of the file descriptor. Must be either
+ /// AF_INET or AF_INET6.
+ /// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6.
+ /// \throw isc::asiolink::IOError when a low-level error happens, like the
+ /// fd is not a valid descriptor or it can't be listened on.
+ virtual void addServerTCPFromFD(int fd, int af);
+
+ /// \brief Add another UDP server to the service from already opened
+ /// file descriptor
+ ///
+ /// Adds a new UDP server using an already opened file descriptor (eg. it
+ /// only wraps it so the file descriptor is usable within the event loop).
+ /// The file descriptor must be associated with a UDP socket of the given
+ /// address family that is bound to an appropriate port (and possibly a
+ /// specific address).
+ ///
+ /// \param fd the file descriptor to be used.
+ /// \param af the address family of the file descriptor. Must be either
+ /// AF_INET or AF_INET6.
+ /// \param options Optional properties of the server (see ServerFlag).
+ ///
+ /// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6,
+ /// or the given \c options include an unsupported or invalid value.
+ /// \throw isc::asiolink::IOError when a low-level error happens, like the
+ /// fd is not a valid descriptor or it can't be listened on.
+ virtual void addServerUDPFromFD(int fd, int af,
+ ServerFlag options = SERVER_DEFAULT);
+
/// \brief Remove all servers from the service
void clearServers();
@@ -102,7 +185,7 @@ public:
/// \brief Return the IO Service Object
///
/// \return IOService object for this DNS service.
- asiolink::IOService& getIOService() { return (io_service_);}
+ virtual asiolink::IOService& getIOService() { return (io_service_);}
private:
DNSServiceImpl* impl_;
@@ -112,3 +195,7 @@ private:
} // namespace asiodns
} // namespace isc
#endif // __ASIOLINK_DNS_SERVICE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/asiodns/io_fetch.cc b/src/lib/asiodns/io_fetch.cc
index 25ec955..eed5fdf 100644
--- a/src/lib/asiodns/io_fetch.cc
+++ b/src/lib/asiodns/io_fetch.cc
@@ -14,10 +14,10 @@
#include <config.h>
+#include <unistd.h> // for some IPC/network system calls
#include <netinet/in.h>
#include <stdint.h>
#include <sys/socket.h>
-#include <unistd.h> // for some IPC/network system calls
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>
@@ -38,15 +38,13 @@
#include <dns/messagerenderer.h>
#include <dns/opcode.h>
#include <dns/rcode.h>
-#include <log/logger.h>
-#include <log/macros.h>
-#include <asiodns/asiodns_messages.h>
#include <asiodns/io_fetch.h>
#include <util/buffer.h>
#include <util/random/qid_gen.h>
+#include <asiodns/logger.h>
using namespace asio;
using namespace isc::asiolink;
@@ -59,10 +57,6 @@ using namespace std;
namespace isc {
namespace asiodns {
-/// Use the ASIO logger
-
-isc::log::Logger logger("asiolink");
-
// Log debug verbosity
const int DBG_IMPORTANT = DBGLVL_TRACE_BASIC;
@@ -211,7 +205,8 @@ IOFetch::IOFetch(Protocol protocol, IOService& service,
}
void
-IOFetch::initIOFetch(MessagePtr& query_msg, Protocol protocol, IOService& service,
+IOFetch::initIOFetch(MessagePtr& query_msg, Protocol protocol,
+ IOService& service,
const isc::dns::Question& question,
const IOAddress& address, uint16_t port,
OutputBufferPtr& buff, Callback* cb, int wait, bool edns)
@@ -231,8 +226,10 @@ IOFetch::initIOFetch(MessagePtr& query_msg, Protocol protocol, IOService& servic
query_msg->setEDNS(edns_query);
}
- MessageRenderer renderer(*data_->msgbuf);
+ MessageRenderer renderer;
+ renderer.setBuffer(data_->msgbuf.get());
query_msg->toWire(renderer);
+ renderer.setBuffer(NULL);
}
// Return protocol in use.
diff --git a/src/lib/asiodns/logger.cc b/src/lib/asiodns/logger.cc
new file mode 100644
index 0000000..5801a0b
--- /dev/null
+++ b/src/lib/asiodns/logger.cc
@@ -0,0 +1,25 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <asiodns/logger.h>
+
+namespace isc {
+namespace asiodns {
+
+/// Use the ASIO logger
+
+isc::log::Logger logger("asiodns");
+
+}
+}
diff --git a/src/lib/asiodns/logger.h b/src/lib/asiodns/logger.h
new file mode 100644
index 0000000..306d463
--- /dev/null
+++ b/src/lib/asiodns/logger.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log/logger.h>
+#include <log/macros.h>
+#include <log/log_dbglevels.h>
+#include <asiodns/asiodns_messages.h>
+
+namespace isc {
+namespace asiodns {
+
+extern isc::log::Logger logger;
+
+}
+}
diff --git a/src/lib/asiodns/sync_udp_server.cc b/src/lib/asiodns/sync_udp_server.cc
new file mode 100644
index 0000000..a31301d
--- /dev/null
+++ b/src/lib/asiodns/sync_udp_server.cc
@@ -0,0 +1,193 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <asio.hpp>
+#include <asio/error.hpp>
+
+#include "sync_udp_server.h"
+#include "logger.h"
+
+#include <asiolink/dummy_io_cb.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+
+#include <boost/bind.hpp>
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+#include <errno.h>
+
+using namespace std;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace asiodns {
+
+SyncUDPServer::SyncUDPServer(asio::io_service& io_service, const int fd,
+ const int af, asiolink::SimpleCallback* checkin,
+ DNSLookup* lookup, DNSAnswer* answer) :
+ output_buffer_(new isc::util::OutputBuffer(0)),
+ query_(new isc::dns::Message(isc::dns::Message::PARSE)),
+ answer_(new isc::dns::Message(isc::dns::Message::RENDER)),
+ io_(io_service), checkin_callback_(checkin), lookup_callback_(lookup),
+ answer_callback_(answer), stopped_(false)
+{
+ if (af != AF_INET && af != AF_INET6) {
+ isc_throw(InvalidParameter, "Address family must be either AF_INET "
+ "or AF_INET6, not " << af);
+ }
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_FD_ADD_UDP).arg(fd);
+ try {
+ socket_.reset(new asio::ip::udp::socket(io_service));
+ socket_->assign(af == AF_INET6 ? asio::ip::udp::v6() :
+ asio::ip::udp::v4(), fd);
+ } catch (const std::exception& exception) {
+ // Whatever the thing throws, it is something from ASIO and we
+ // convert it
+ isc_throw(IOError, exception.what());
+ }
+}
+
+void
+SyncUDPServer::scheduleRead() {
+ socket_->async_receive_from(asio::buffer(data_, MAX_LENGTH), sender_,
+ boost::bind(&SyncUDPServer::handleRead, this,
+ _1, _2));
+}
+
+void
+SyncUDPServer::handleRead(const asio::error_code& ec, const size_t length) {
+ // Abort on fatal errors
+ if (ec) {
+ using namespace asio::error;
+ if (ec.value() != would_block && ec.value() != try_again &&
+ ec.value() != interrupted) {
+ return;
+ }
+ }
+ // Some kind of interrupt, spurious wakeup, or like that. Just try reading
+ // again.
+ if (ec || length == 0) {
+ scheduleRead();
+ return;
+ }
+ // OK, we have a real packet of data. Let's dig into it!
+
+ // XXX: This is taken (and ported) from UDPSocket class. What the hell does
+ // it really mean?
+
+ // 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.
+ UDPSocket<DummyIOCallback> socket(*socket_);
+ UDPEndpoint endpoint(sender_);
+ IOMessage message(data_, length, socket, endpoint);
+ if (checkin_callback_ != NULL) {
+ (*checkin_callback_)(message);
+ if (stopped_) {
+ return;
+ }
+ }
+
+ // If we don't have a DNS Lookup provider, there's no point in
+ // continuing; we exit the coroutine permanently.
+ if (lookup_callback_ == NULL) {
+ scheduleRead();
+ return;
+ }
+
+ // Make sure the buffers are fresh
+ output_buffer_->clear();
+ query_->clear(isc::dns::Message::PARSE);
+ answer_->clear(isc::dns::Message::RENDER);
+
+ // Mark that we don't have an answer yet.
+ done_ = false;
+ resume_called_ = false;
+
+ // Call the actual lookup
+ (*lookup_callback_)(message, query_, answer_, output_buffer_, this);
+
+ if (!resume_called_) {
+ isc_throw(isc::Unexpected,
+ "No resume called from the lookup callback");
+ }
+
+ if (stopped_) {
+ return;
+ }
+
+ if (done_) {
+ // Good, there's an answer.
+ // Call the answer callback to render it.
+ (*answer_callback_)(message, query_, answer_, output_buffer_);
+
+ if (stopped_) {
+ return;
+ }
+
+ socket_->send_to(asio::buffer(output_buffer_->getData(),
+ output_buffer_->getLength()),
+ sender_);
+ }
+
+ // And schedule handling another socket.
+ scheduleRead();
+}
+
+void
+SyncUDPServer::operator()(asio::error_code, size_t) {
+ // To start the server, we just schedule reading of data when they
+ // arrive.
+ scheduleRead();
+}
+
+/// Stop the UDPServer
+void
+SyncUDPServer::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
+ socket_->close();
+ stopped_ = true;
+}
+
+/// 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
+SyncUDPServer::resume(const bool done) {
+ resume_called_ = true;
+ done_ = done;
+}
+
+bool
+SyncUDPServer::hasAnswer() {
+ return (done_);
+}
+
+} // namespace asiodns
+} // namespace isc
diff --git a/src/lib/asiodns/sync_udp_server.h b/src/lib/asiodns/sync_udp_server.h
new file mode 100644
index 0000000..9718422
--- /dev/null
+++ b/src/lib/asiodns/sync_udp_server.h
@@ -0,0 +1,148 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __SYNC_UDP_SERVER_H
+#define __SYNC_UDP_SERVER_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include "dns_answer.h"
+#include "dns_lookup.h"
+#include "dns_server.h"
+
+#include <dns/message.h>
+#include <asiolink/simple_callback.h>
+#include <util/buffer.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <stdint.h>
+
+namespace isc {
+namespace asiodns {
+
+/// \brief An UDP server that doesn't asynchronous lookup handlers.
+///
+/// That means, the lookup handler must provide the answer right away.
+/// This allows for implementation with less overhead, compared with
+/// the UDPClass.
+class SyncUDPServer : public DNSServer, public boost::noncopyable {
+public:
+ /// \brief Constructor
+ /// \param io_service the asio::io_service to work with
+ /// \param fd the file descriptor of opened UDP socket
+ /// \param af address family, either AF_INET or AF_INET6
+ /// \param checkin the callbackprovider for non-DNS events
+ /// \param lookup the callbackprovider for DNS lookup events
+ /// \param answer the callbackprovider for DNS answer events
+ /// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6
+ /// \throw isc::asiolink::IOError when a low-level error happens, like the
+ /// fd is not a valid descriptor.
+ SyncUDPServer(asio::io_service& io_service, const int fd, const int af,
+ isc::asiolink::SimpleCallback* checkin = NULL,
+ DNSLookup* lookup = NULL, DNSAnswer* answer = NULL);
+
+ /// \brief Start the SyncUDPServer.
+ ///
+ /// This is the function operator to keep interface with other server
+ /// classes. They need that because they're coroutines.
+ virtual void operator()(asio::error_code ec = asio::error_code(),
+ size_t length = 0);
+
+ /// \brief Calls the lookup callback
+ virtual void asyncLookup() {
+ isc_throw(Unexpected,
+ "SyncUDPServer doesn't support asyncLookup by design, use "
+ "UDPServer if you need it.");
+ }
+
+ /// \brief Stop the running server
+ /// \note once the server stopped, it can't restart
+ virtual void stop();
+
+ /// \brief Resume operation
+ ///
+ /// Note that unlike other servers, this one expects it to be called
+ /// directly from the lookup callback. If it isn't, the server will
+ /// throw an Unexpected exception (probably to the event loop, which
+ /// would usually lead to termination of the program, but that's OK,
+ /// as it would be serious programmer error).
+ ///
+ /// \param done Set this to true if the lookup action is done and
+ /// we have an answer
+ virtual void resume(const bool done);
+
+ /// \brief Check if we have an answer
+ ///
+ /// \return true if we have an answer
+ virtual bool hasAnswer();
+
+ /// \brief Clones the object
+ ///
+ /// Since cloning is for the use of coroutines, the synchronous UDP server
+ /// does not need to be cloned. Therefore supporting it would be needless
+ /// work, and trying to clone it would be a programmer error anyway, this
+ /// throws Unexpected.
+ ///
+ /// \return a newly allocated copy of this object
+ virtual DNSServer* clone() {
+ isc_throw(Unexpected, "SyncUDPServer can't be cloned.");
+ }
+private:
+ // Internal state & buffers. We don't use the PIMPL idiom, as this class
+ // isn't usually used directly anyway.
+
+ // Maximum size of incoming UDP packet
+ static const size_t MAX_LENGTH = 4096;
+ // Buffer for incoming data
+ uint8_t data_[MAX_LENGTH];
+ // The buffer to render the output to and send it.
+ // If it was OK to have just a buffer, not the wrapper class,
+ // we could reuse the data_
+ isc::util::OutputBufferPtr output_buffer_;
+ // Objects to hold the query message and the answer
+ isc::dns::MessagePtr query_, answer_;
+ // The socket used for the communication
+ std::auto_ptr<asio::ip::udp::socket> socket_;
+ // The event loop we use
+ asio::io_service& io_;
+ // Place the socket puts the sender of a packet when it is received
+ asio::ip::udp::endpoint sender_;
+ // Callbacks
+ const asiolink::SimpleCallback* checkin_callback_;
+ const DNSLookup* lookup_callback_;
+ const DNSAnswer* answer_callback_;
+ // Answers from the lookup callback (not sent directly, but signalled
+ // through resume()
+ bool resume_called_, done_;
+ // This turns true when the server stops. Allows for not sending the
+ // answer after we closed the socket.
+ bool stopped_;
+
+ // Auxiliary functions
+
+ // Schedule next read on the socket. Just a wrapper around
+ // socket_->async_read_from with the correct parameters.
+ void scheduleRead();
+ // Callback from the socket's read call (called when there's an error or
+ // when a new packet comes).
+ void handleRead(const asio::error_code& ec, const size_t length);
+};
+
+} // namespace asiodns
+} // namespace isc
+#endif // __SYNC_UDP_SERVER_H
diff --git a/src/lib/asiodns/tcp_server.cc b/src/lib/asiodns/tcp_server.cc
index 2606a21..8e4b4d6 100644
--- a/src/lib/asiodns/tcp_server.cc
+++ b/src/lib/asiodns/tcp_server.cc
@@ -14,9 +14,9 @@
#include <config.h>
+#include <unistd.h> // for some IPC/network system calls
#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>
@@ -29,8 +29,8 @@
#include <asiolink/dummy_io_cb.h>
#include <asiolink/tcp_endpoint.h>
#include <asiolink/tcp_socket.h>
-#include <tcp_server.h>
-
+#include <asiodns/tcp_server.h>
+#include <asiodns/logger.h>
using namespace asio;
using asio::ip::udp;
@@ -47,8 +47,7 @@ namespace asiodns {
/// The following functions implement the \c TCPServer class.
///
/// The constructor
-TCPServer::TCPServer(io_service& io_service,
- const ip::address& addr, const uint16_t port,
+TCPServer::TCPServer(io_service& io_service, int fd, int af,
const SimpleCallback* checkin,
const DNSLookup* lookup,
const DNSAnswer* answer) :
@@ -56,17 +55,21 @@ TCPServer::TCPServer(io_service& io_service,
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));
+ if (af != AF_INET && af != AF_INET6) {
+ isc_throw(InvalidParameter, "Address family must be either AF_INET "
+ "or AF_INET6, not " << af);
+ }
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_FD_ADD_TCP).arg(fd);
+
+ try {
+ acceptor_.reset(new tcp::acceptor(io_service));
+ acceptor_->assign(af == AF_INET6 ? tcp::v6() : tcp::v4(), fd);
+ acceptor_->listen();
+ } catch (const std::exception& exception) {
+ // Whatever the thing throws, it is something from ASIO and we convert
+ // it
+ isc_throw(IOError, exception.what());
}
- acceptor_->set_option(tcp::acceptor::reuse_address(true));
- acceptor_->bind(endpoint);
- acceptor_->listen();
}
void
diff --git a/src/lib/asiodns/tcp_server.h b/src/lib/asiodns/tcp_server.h
index 22a2f69..01695e4 100644
--- a/src/lib/asiodns/tcp_server.h
+++ b/src/lib/asiodns/tcp_server.h
@@ -37,20 +37,25 @@ namespace asiodns {
/// 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 isc::asiolink::SimpleCallback* checkin = NULL,
- const DNSLookup* lookup = NULL,
- const DNSAnswer* answer = NULL);
+ /// \brief Constructor
+ /// \param io_service the asio::io_service to work with
+ /// \param fd the file descriptor of opened TCP socket
+ /// \param af address family of the socket, either AF_INET or AF_INET6
+ /// \param checkin the callbackprovider for non-DNS events
+ /// \param lookup the callbackprovider for DNS lookup events
+ /// \param answer the callbackprovider for DNS answer events
+ /// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6
+ /// \throw isc::asiolink::IOError when a low-level error happens, like the
+ /// fd is not a valid descriptor or it can't be listened on.
+ TCPServer(asio::io_service& io_service, int fd, int af,
+ const isc::asiolink::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);
diff --git a/src/lib/asiodns/tests/.gitignore b/src/lib/asiodns/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/lib/asiodns/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/lib/asiodns/tests/Makefile.am b/src/lib/asiodns/tests/Makefile.am
index f49d485..95094f0 100644
--- a/src/lib/asiodns/tests/Makefile.am
+++ b/src/lib/asiodns/tests/Makefile.am
@@ -18,7 +18,7 @@ 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 += io_service_unittest.cc
+run_unittests_SOURCES += dns_service_unittest.cc
run_unittests_SOURCES += dns_server_unittest.cc
run_unittests_SOURCES += io_fetch_unittest.cc
diff --git a/src/lib/asiodns/tests/dns_server_unittest.cc b/src/lib/asiodns/tests/dns_server_unittest.cc
index c79ee7f..a5e83c7 100644
--- a/src/lib/asiodns/tests/dns_server_unittest.cc
+++ b/src/lib/asiodns/tests/dns_server_unittest.cc
@@ -19,10 +19,13 @@
#include <asiolink/io_endpoint.h>
#include <asiolink/io_error.h>
#include <asiodns/udp_server.h>
+#include <asiodns/sync_udp_server.h>
#include <asiodns/tcp_server.h>
#include <asiodns/dns_answer.h>
#include <asiodns/dns_lookup.h>
#include <string>
+#include <cstring>
+#include <cerrno>
#include <csignal>
#include <unistd.h> //for alarm
@@ -30,6 +33,8 @@
#include <boost/bind.hpp>
#include <boost/function.hpp>
+#include <sys/types.h>
+#include <sys/socket.h>
/// The following tests focus on stop interface for udp and
/// tcp server, there are lots of things can be shared to test
@@ -70,11 +75,12 @@ using namespace isc::asiodns;
using namespace asio;
namespace {
-static const std::string server_ip = "127.0.0.1";
+const char* const server_ip = "::1";
const int server_port = 5553;
+const char* const server_port_str = "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");
+const char* const query_message = "BIND10 is awesome";
// \brief provide capacity to derived class the ability
// to stop DNSServer at certern point
@@ -107,15 +113,22 @@ class DummyChecker : public SimpleCallback, public ServerStopper {
// \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::util::OutputBufferPtr buffer,
- DNSServer* server) const {
- stopServer();
+public:
+ DummyLookup() :
+ allow_resume_(true)
+ { }
+ void operator()(const IOMessage& io_message,
+ isc::dns::MessagePtr message,
+ isc::dns::MessagePtr answer_message,
+ isc::util::OutputBufferPtr buffer,
+ DNSServer* server) const {
+ stopServer();
+ if (allow_resume_) {
server->resume(true);
}
+ }
+ // If you want it not to call resume, set this to false
+ bool allow_resume_;
};
// \brief copy the data received from user to the answer part
@@ -200,15 +213,15 @@ class SimpleClient : public ServerStopper {
class UDPClient : public SimpleClient {
public:
- //After 1 seconds without feedback client will stop wait
- static const unsigned int server_time_out = 1;
+ //After 1 second 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)
+ SimpleClient(service, SERVER_TIME_OUT)
{
server_ = server;
socket_.reset(new ip::udp::socket(service));
- socket_->open(ip::udp::v4());
+ socket_->open(ip::udp::v6());
}
@@ -243,13 +256,13 @@ 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;
+ static const unsigned int SERVER_TIME_OUT = 2;
TCPClient(asio::io_service& service, const ip::tcp::endpoint& server)
- : SimpleClient(service, server_time_out)
+ : SimpleClient(service, SERVER_TIME_OUT)
{
server_ = server;
socket_.reset(new ip::tcp::socket(service));
- socket_->open(ip::tcp::v4());
+ socket_->open(ip::tcp::v6());
}
@@ -305,33 +318,41 @@ class TCPClient : public SimpleClient {
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 {
+// \brief provide the context which including two clients and
+// two servers, UDP client will only communicate with UDP server, same for TCP
+// client
+//
+// This is only the active part of the test. We run the test case four times, once
+// for each type of initialization (once when giving it the address and port,
+// once when giving the file descriptor) multiplied by once for each type of UDP
+// server (UDPServer and SyncUDPServer), to ensure it works exactly the same.
+template<class UDPServerClass>
+class DNSServerTestBase : 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));
+ DNSServerTestBase() :
+ server_address_(ip::address::from_string(server_ip)),
+ checker_(new DummyChecker()),
+ lookup_(new DummyLookup()),
+ answer_(new SimpleAnswer()),
+ udp_client_(new UDPClient(service,
+ ip::udp::endpoint(server_address_,
+ server_port))),
+ tcp_client_(new TCPClient(service,
+ ip::tcp::endpoint(server_address_,
+ server_port))),
+ udp_server_(NULL),
+ tcp_server_(NULL)
+ {
+ current_service = &service;
}
-
- void TearDown() {
- udp_server_->stop();
- tcp_server_->stop();
+ ~ DNSServerTestBase() {
+ if (udp_server_ != NULL) {
+ udp_server_->stop();
+ }
+ if (tcp_server_ != NULL) {
+ tcp_server_->stop();
+ }
delete checker_;
delete lookup_;
delete answer_;
@@ -339,22 +360,26 @@ class DNSServerTest : public::testing::Test {
delete udp_client_;
delete tcp_server_;
delete tcp_client_;
+ // No delete here. The service is not allocated by new, but as our
+ // member. This only references it, so just cleaning the pointer.
+ current_service = NULL;
}
-
void testStopServerByStopper(DNSServer* server, SimpleClient* client,
ServerStopper* stopper)
{
- static const unsigned int io_service_time_out = 5;
+ 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);
+ // 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, DNSServerTestBase::stopIOService);
+ current_service = &service;
+ alarm(IO_SERVICE_TIME_OUT);
service.run();
service.reset();
//cancel scheduled alarm
@@ -362,74 +387,157 @@ class DNSServerTest : public::testing::Test {
std::signal(SIGALRM, prev_handler);
}
-
static void stopIOService(int _no_use_parameter) {
io_service_is_time_out = true;
- service.stop();
+ if (current_service != NULL) {
+ current_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_;
+ asio::io_service service;
+ const ip::address server_address_;
+ DummyChecker* const checker_;
+ DummyLookup* const lookup_;
+ SimpleAnswer* const answer_;
+ UDPClient* const udp_client_;
+ TCPClient* const tcp_client_;
+ UDPServerClass* udp_server_;
TCPServer* tcp_server_;
// To access them in signal handle function, the following
// variables have to be static.
- static asio::io_service service;
+ static asio::io_service* current_service;
static bool io_service_is_time_out;
};
-bool DNSServerTest::io_service_is_time_out = false;
-asio::io_service DNSServerTest::service;
+// Initialization (by the file descriptor)
+template<class UDPServerClass>
+class FdInit : public DNSServerTestBase<UDPServerClass> {
+private:
+ // Opens the file descriptor for us
+ // It uses the low-level C api, as it seems to be the easiest way to get
+ // a raw file descriptor. It also is what the socket creator does and this
+ // API is aimed to it.
+ int getFd(int type) {
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = type;
+ hints.ai_protocol = (type == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP;
+ hints.ai_flags = AI_NUMERICSERV | AI_NUMERICHOST;
+
+ struct addrinfo* res;
+ const int error = getaddrinfo(server_ip, server_port_str,
+ &hints, &res);
+ if (error != 0) {
+ isc_throw(IOError, "getaddrinfo failed: " << gai_strerror(error));
+ }
+
+ int sock;
+ const int on(1);
+ // Go as far as you can and stop on failure
+ // Create the socket
+ // set the options
+ // and bind it
+ const bool failed((sock = socket(res->ai_family, res->ai_socktype,
+ res->ai_protocol)) == -1 ||
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on,
+ sizeof(on)) == -1 ||
+ bind(sock, res->ai_addr, res->ai_addrlen) == -1);
+ // No matter if it succeeded or not, free the address info
+ freeaddrinfo(res);
+ if (failed) {
+ if (sock != -1) {
+ close(sock);
+ }
+ return (-1);
+ } else {
+ return (sock);
+ }
+ }
+protected:
+ // Using SetUp here so we can ASSERT_*
+ void SetUp() {
+ const int fdUDP(getFd(SOCK_DGRAM));
+ ASSERT_NE(-1, fdUDP) << strerror(errno);
+ this->udp_server_ = new UDPServerClass(this->service, fdUDP, AF_INET6,
+ this->checker_, this->lookup_,
+ this->answer_);
+ const int fdTCP(getFd(SOCK_STREAM));
+ ASSERT_NE(-1, fdTCP) << strerror(errno);
+ this->tcp_server_ = new TCPServer(this->service, fdTCP, AF_INET6,
+ this->checker_, this->lookup_,
+ this->answer_);
+ }
+};
+
+// This makes it the template as gtest wants it.
+template<class Parent>
+class DNSServerTest : public Parent { };
+
+typedef ::testing::Types<FdInit<UDPServer>, FdInit<SyncUDPServer> >
+ ServerTypes;
+TYPED_TEST_CASE(DNSServerTest, ServerTypes);
+
+typedef ::testing::Types<UDPServer, SyncUDPServer> UDPServerTypes;
+TYPED_TEST_CASE(DNSServerTestBase, UDPServerTypes);
+
+template<class UDPServerClass>
+bool DNSServerTestBase<UDPServerClass>::io_service_is_time_out = false;
+template<class UDPServerClass>
+asio::io_service* DNSServerTestBase<UDPServerClass>::current_service(NULL);
// 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());
+TYPED_TEST(DNSServerTest, stopUDPServerAfterOneQuery) {
+ this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+ this->udp_client_);
+ EXPECT_EQ(query_message, this->udp_client_->getReceivedData());
+ EXPECT_TRUE(this->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());
+TYPED_TEST(DNSServerTest, stopUDPServerBeforeItStartServing) {
+ this->udp_server_->stop();
+ this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+ this->udp_client_);
+ EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
+ EXPECT_TRUE(this->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());
+TYPED_TEST(DNSServerTest, stopUDPServerDuringMessageCheck) {
+ this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+ this->checker_);
+ EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
+ EXPECT_TRUE(this->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());
+TYPED_TEST(DNSServerTest, stopUDPServerDuringQueryLookup) {
+ this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+ this->lookup_);
+ EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
+ EXPECT_TRUE(this->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());
+TYPED_TEST(DNSServerTest, stopUDPServerDuringPrepareAnswer) {
+ this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+ this->answer_);
+ EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
+ EXPECT_TRUE(this->serverStopSucceed());
}
-static void stopServerManyTimes(DNSServer *server, unsigned int times) {
+void
+stopServerManyTimes(DNSServer *server, unsigned int times) {
for (unsigned int i = 0; i < times; ++i) {
server->stop();
}
@@ -437,67 +545,134 @@ static void stopServerManyTimes(DNSServer *server, unsigned int times) {
// Test whether udp server stop interface can be invoked several times without
// throw any exception
-TEST_F(DNSServerTest, stopUDPServeMoreThanOnce) {
+TYPED_TEST(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());
+ = boost::bind(stopServerManyTimes, this->udp_server_, 3);
+ this->udp_client_->setGetFeedbackCallback(stop_server_3_times);
+ this->testStopServerByStopper(this->udp_server_,
+ this->udp_client_, this->udp_client_);
+ EXPECT_EQ(query_message, this->udp_client_->getReceivedData());
});
- EXPECT_TRUE(serverStopSucceed());
+ EXPECT_TRUE(this->serverStopSucceed());
}
-TEST_F(DNSServerTest, stopTCPServerAfterOneQuery) {
- testStopServerByStopper(tcp_server_, tcp_client_, tcp_client_);
- EXPECT_EQ(query_message, tcp_client_->getReceivedData());
- EXPECT_TRUE(serverStopSucceed());
+TYPED_TEST(DNSServerTest, stopTCPServerAfterOneQuery) {
+ this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->tcp_client_);
+ EXPECT_EQ(query_message, this->tcp_client_->getReceivedData());
+ EXPECT_TRUE(this->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());
+TYPED_TEST(DNSServerTest, stopTCPServerBeforeItStartServing) {
+ this->tcp_server_->stop();
+ this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->tcp_client_);
+ EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
+ EXPECT_TRUE(this->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());
+TYPED_TEST(DNSServerTest, stopTCPServerDuringMessageCheck) {
+ this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->checker_);
+ EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
+ EXPECT_TRUE(this->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());
+TYPED_TEST(DNSServerTest, stopTCPServerDuringQueryLookup) {
+ this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->lookup_);
+ EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
+ EXPECT_TRUE(this->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());
+TYPED_TEST(DNSServerTest, stopTCPServerDuringPrepareAnswer) {
+ this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->answer_);
+ EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
+ EXPECT_TRUE(this->serverStopSucceed());
}
// Test whether tcp server stop interface can be invoked several times without
// throw any exception
-TEST_F(DNSServerTest, stopTCPServeMoreThanOnce) {
+TYPED_TEST(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());
+ = boost::bind(stopServerManyTimes, this->tcp_server_, 3);
+ this->tcp_client_->setGetFeedbackCallback(stop_server_3_times);
+ this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->tcp_client_);
+ EXPECT_EQ(query_message, this->tcp_client_->getReceivedData());
});
- EXPECT_TRUE(serverStopSucceed());
+ EXPECT_TRUE(this->serverStopSucceed());
+}
+
+// It raises an exception when invalid address family is passed
+// The parameter here doesn't mean anything
+TYPED_TEST(DNSServerTestBase, invalidFamily) {
+ // We abuse DNSServerTestBase for this test, as we don't need the
+ // initialization.
+ EXPECT_THROW(TypeParam(this->service, 0, AF_UNIX, this->checker_,
+ this->lookup_, this->answer_),
+ isc::InvalidParameter);
+ EXPECT_THROW(TCPServer(this->service, 0, AF_UNIX, this->checker_,
+ this->lookup_, this->answer_),
+ isc::InvalidParameter);
+}
+
+// It raises an exception when invalid address family is passed
+TYPED_TEST(DNSServerTestBase, invalidTCPFD) {
+ // We abuse DNSServerTestBase for this test, as we don't need the
+ // initialization.
+ /*
+ FIXME: The UDP server doesn't fail reliably with an invalid FD.
+ We need to find a way to trigger it reliably (it seems epoll
+ asio backend does fail as it tries to insert it right away, but
+ not the others, maybe we could make it run this at last on epoll-based
+ systems).
+ EXPECT_THROW(UDPServer(service, -1, AF_INET, checker_, lookup_,
+ answer_), isc::asiolink::IOError);
+ */
+ EXPECT_THROW(TCPServer(this->service, -1, AF_INET, this->checker_,
+ this->lookup_, this->answer_),
+ isc::asiolink::IOError);
+}
+
+TYPED_TEST(DNSServerTestBase, DISABLED_invalidUDPFD) {
+ /*
+ FIXME: The UDP server doesn't fail reliably with an invalid FD.
+ We need to find a way to trigger it reliably (it seems epoll
+ asio backend does fail as it tries to insert it right away, but
+ not the others, maybe we could make it run this at least on epoll-based
+ systems).
+ */
+ EXPECT_THROW(TypeParam(this->service, -1, AF_INET, this->checker_,
+ this->lookup_, this->answer_),
+ isc::asiolink::IOError);
+}
+
+// A specialized test type for SyncUDPServer.
+typedef FdInit<SyncUDPServer> SyncServerTest;
+
+// Check it rejects some of the unsupported operations
+TEST_F(SyncServerTest, unsupportedOps) {
+ EXPECT_THROW(udp_server_->clone(), isc::Unexpected);
+ EXPECT_THROW(udp_server_->asyncLookup(), isc::Unexpected);
+}
+
+// Check it rejects forgotten resume (eg. insists that it is synchronous)
+TEST_F(SyncServerTest, mustResume) {
+ lookup_->allow_resume_ = false;
+ ASSERT_THROW(testStopServerByStopper(udp_server_, udp_client_, lookup_),
+ isc::Unexpected);
}
}
diff --git a/src/lib/asiodns/tests/dns_service_unittest.cc b/src/lib/asiodns/tests/dns_service_unittest.cc
new file mode 100644
index 0000000..ce8eee9
--- /dev/null
+++ b/src/lib/asiodns/tests/dns_service_unittest.cc
@@ -0,0 +1,232 @@
+// 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 <exceptions/exceptions.h>
+
+#include <asio.hpp>
+#include <asiolink/asiolink.h>
+#include <asiodns/asiodns.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <csignal>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <unistd.h>
+#include <netdb.h>
+
+using namespace isc::asiolink;
+using namespace isc::asiodns;
+using boost::scoped_ptr;
+using boost::lexical_cast;
+
+namespace {
+const char* const TEST_SERVER_PORT = "53535";
+const char* const TEST_IPV6_ADDR = "::1";
+
+// A simple lookup callback for DNS services. It records the pointer value of
+// to given output buffer each time the callback is called (up to two times)
+// for the main tests. At the end of the second callback it stops the server.
+// The sender of the data doesn't expect to get a response, so it simply
+// discards any received data.
+class TestLookup : public DNSLookup {
+public:
+ TestLookup(isc::util::OutputBuffer** b1, isc::util::OutputBuffer** b2,
+ IOService& io_service) :
+ first_buffer_(b1), second_buffer_(b2), io_service_(io_service)
+ {}
+ void operator()(const IOMessage&, isc::dns::MessagePtr,
+ isc::dns::MessagePtr, isc::util::OutputBufferPtr buffer,
+ DNSServer* server) const
+ {
+ server->resume(false);
+ if (*first_buffer_ == NULL) {
+ *first_buffer_ = buffer.get();
+ } else {
+ assert(*second_buffer_ == NULL);
+ *second_buffer_ = buffer.get();
+ server->stop();
+ io_service_.stop();
+ }
+ }
+ isc::util::OutputBuffer** first_buffer_;
+ isc::util::OutputBuffer** second_buffer_;
+ IOService& io_service_;
+};
+
+// A test fixture to check creation of UDP servers from a socket FD, changing
+// options.
+class UDPDNSServiceTest : public::testing::Test {
+private:
+ static const unsigned int IO_SERVICE_TIME_OUT = 5;
+
+protected:
+ UDPDNSServiceTest() :
+ first_buffer_(NULL), second_buffer_(NULL),
+ lookup(&first_buffer_, &second_buffer_, io_service),
+ dns_service(io_service, NULL, &lookup, NULL),
+ client_socket(io_service.get_io_service(), asio::ip::udp::v6()),
+ server_ep(asio::ip::address::from_string(TEST_IPV6_ADDR),
+ lexical_cast<uint16_t>(TEST_SERVER_PORT)),
+ asio_service(io_service.get_io_service())
+ {
+ current_service = &io_service;
+ // Content shouldn't matter for the tests, but initialize anyway
+ memset(data, 1, sizeof(data));
+ }
+
+ ~UDPDNSServiceTest() {
+ current_service = NULL;
+ }
+
+ void runService() {
+ io_service_is_time_out = false;
+
+ // Send two UDP packets, which will be passed to the TestLookup
+ // callback. They are not expected to be responded, so it simply
+ // closes the socket right after that.
+ client_socket.send_to(asio::buffer(data, sizeof(data)), server_ep);
+ client_socket.send_to(asio::buffer(data, sizeof(data)), server_ep);
+ client_socket.close();
+
+ // set a signal-based alarm to prevent the test from hanging up
+ // due to a bug.
+ void (*prev_handler)(int) =
+ std::signal(SIGALRM, UDPDNSServiceTest::stopIOService);
+ current_service = &io_service;
+ alarm(IO_SERVICE_TIME_OUT);
+ io_service.run();
+ io_service.get_io_service().reset();
+ //cancel scheduled alarm
+ alarm(0);
+ std::signal(SIGALRM, prev_handler);
+ }
+
+ // last resort service stopper by signal
+ static void stopIOService(int) {
+ io_service_is_time_out = true;
+ if (current_service != NULL) {
+ current_service->stop();
+ }
+ }
+
+ bool serverStopSucceed() const {
+ return (!io_service_is_time_out);
+ }
+
+ isc::util::OutputBuffer* first_buffer_;
+ isc::util::OutputBuffer* second_buffer_;
+ IOService io_service;
+ TestLookup lookup;
+ DNSService dns_service;
+private:
+ asio::ip::udp::socket client_socket;
+ const asio::ip::udp::endpoint server_ep;
+ char data[4];
+
+ // To access them in signal handle function, the following
+ // variables have to be static.
+ static IOService* current_service;
+ static bool io_service_is_time_out;
+
+ asio::io_service& asio_service;
+};
+
+// Need to define the non-const static members outside of the class
+// declaration
+IOService* UDPDNSServiceTest::current_service;
+bool UDPDNSServiceTest::io_service_is_time_out;
+
+// A helper socket FD creator for given address and port. It's generally
+// expected to succeed; on failure it simply throws an exception to make
+// the test fail.
+int
+getSocketFD(int family, const char* const address, const char* const port) {
+ struct addrinfo hints, *res = NULL;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
+ int s = -1;
+ int error = getaddrinfo(address, port, &hints, &res);
+ if (error == 0) {
+ // If getaddrinfo returns 0, res should be set to a non NULL valid
+ // pointer, but some variants of cppcheck reportedly complains about
+ // it, so we satisfy them here.
+ if (res != NULL) {
+ s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (s >= 0) {
+ error = bind(s, res->ai_addr, res->ai_addrlen);
+ }
+ freeaddrinfo(res);
+ }
+ }
+ if (error != 0) {
+ if (s >= 0) {
+ close(s);
+ }
+ isc_throw(isc::Unexpected, "failed to open test socket");
+ }
+ return (s);
+}
+
+TEST_F(UDPDNSServiceTest, defaultUDPServerFromFD) {
+ // If no option is explicitly specified, an asynchronous server should be
+ // created. So the two output buffers should be different.
+ dns_service.addServerUDPFromFD(getSocketFD(AF_INET6, TEST_IPV6_ADDR,
+ TEST_SERVER_PORT), AF_INET6);
+ runService();
+ EXPECT_TRUE(serverStopSucceed());
+ EXPECT_NE(first_buffer_, second_buffer_);
+}
+
+TEST_F(UDPDNSServiceTest, explicitDefaultUDPServerFromFD) {
+ // If "default" option is explicitly specified, the effect should be the
+ // same as the previous case.
+ dns_service.addServerUDPFromFD(getSocketFD(AF_INET6, TEST_IPV6_ADDR,
+ TEST_SERVER_PORT),
+ AF_INET6, DNSService::SERVER_DEFAULT);
+ runService();
+ EXPECT_TRUE(serverStopSucceed());
+ EXPECT_NE(first_buffer_, second_buffer_);
+}
+
+TEST_F(UDPDNSServiceTest, syncUDPServerFromFD) {
+ // If "SYNC_OK" option is specified, a synchronous server should be
+ // created. It will reuse the output buffer, so the recorded two pointer
+ // should be identical.
+ dns_service.addServerUDPFromFD(getSocketFD(AF_INET6, TEST_IPV6_ADDR,
+ TEST_SERVER_PORT),
+ AF_INET6, DNSService::SERVER_SYNC_OK);
+ runService();
+ EXPECT_TRUE(serverStopSucceed());
+ EXPECT_EQ(first_buffer_, second_buffer_);
+}
+
+TEST_F(UDPDNSServiceTest, addUDPServerFromFDWithUnknownOption) {
+ // Use of undefined/incompatible options should result in an exception.
+ EXPECT_THROW(dns_service.addServerUDPFromFD(
+ getSocketFD(AF_INET6, TEST_IPV6_ADDR, TEST_SERVER_PORT),
+ AF_INET6, static_cast<DNSService::ServerFlag>(2)),
+ isc::InvalidParameter);
+}
+
+} // unnamed namespace
diff --git a/src/lib/asiodns/tests/io_fetch_unittest.cc b/src/lib/asiodns/tests/io_fetch_unittest.cc
index 936c6c7..acb184c 100644
--- a/src/lib/asiodns/tests/io_fetch_unittest.cc
+++ b/src/lib/asiodns/tests/io_fetch_unittest.cc
@@ -133,10 +133,15 @@ public:
EDNSPtr msg_edns(new EDNS());
msg_edns->setUDPSize(Message::DEFAULT_MAX_EDNS0_UDPSIZE);
msg.setEDNS(msg_edns);
- MessageRenderer renderer(*msgbuf_);
+
+ MessageRenderer renderer;
+ renderer.setBuffer(msgbuf_.get());
+ msg.toWire(renderer);
+ renderer.setBuffer(NULL);
+
+ renderer.setBuffer(expected_buffer_.get());
msg.toWire(renderer);
- MessageRenderer renderer2(*expected_buffer_);
- msg.toWire(renderer2);
+ renderer.setBuffer(NULL);
// 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
@@ -581,20 +586,22 @@ public:
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));
+ 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;
+ cout << "udpSendReceive: async_receive_from posted,"
+ "waiting for callback" << endl;
}
service_.run();
socket.close();
- EXPECT_TRUE(run_);;
+ EXPECT_TRUE(run_);
}
};
diff --git a/src/lib/asiodns/tests/io_service_unittest.cc b/src/lib/asiodns/tests/io_service_unittest.cc
deleted file mode 100644
index cc64022..0000000
--- a/src/lib/asiodns/tests/io_service_unittest.cc
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <config.h>
-#include <gtest/gtest.h>
-
-#include <asio.hpp>
-#include <asiolink/asiolink.h>
-#include <asiodns/asiodns.h>
-
-using namespace isc::asiolink;
-using namespace isc::asiodns;
-
-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/asiodns/udp_server.cc b/src/lib/asiodns/udp_server.cc
index 72db2bf..0f5456b 100644
--- a/src/lib/asiodns/udp_server.cc
+++ b/src/lib/asiodns/udp_server.cc
@@ -12,9 +12,9 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <unistd.h> // for some IPC/network system calls
#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>
@@ -29,6 +29,7 @@
#include <asiolink/udp_endpoint.h>
#include <asiolink/udp_socket.h>
#include "udp_server.h"
+#include "logger.h"
#include <dns/opcode.h>
@@ -53,7 +54,7 @@ namespace asiodns {
*/
struct UDPServer::Data {
/*
- * Constructor from parameters passed to UDPServer constructor.
+ * Constructors 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.
@@ -74,6 +75,26 @@ struct UDPServer::Data {
}
socket_->bind(udp::endpoint(addr, port));
}
+ Data(io_service& io_service, int fd, int af, SimpleCallback* checkin,
+ DNSLookup* lookup, DNSAnswer* answer) :
+ io_(io_service), done_(false),
+ checkin_callback_(checkin),lookup_callback_(lookup),
+ answer_callback_(answer)
+ {
+ if (af != AF_INET && af != AF_INET6) {
+ isc_throw(InvalidParameter, "Address family must be either AF_INET "
+ "or AF_INET6, not " << af);
+ }
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_FD_ADD_UDP).arg(fd);
+ try {
+ socket_.reset(new udp::socket(io_service));
+ socket_->assign(af == AF_INET6 ? udp::v6() : udp::v4(), fd);
+ } catch (const std::exception& exception) {
+ // Whatever the thing throws, it is something from ASIO and we
+ // convert it
+ isc_throw(IOError, exception.what());
+ }
+ }
/*
* Copy constructor. Default one would probably do, but it is unnecessary
@@ -161,10 +182,10 @@ struct UDPServer::Data {
///
/// 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))
+UDPServer::UDPServer(io_service& io_service, int fd, int af,
+ SimpleCallback* checkin, DNSLookup* lookup,
+ DNSAnswer* answer) :
+ data_(new Data(io_service, fd, af, checkin, lookup, answer))
{ }
/// The function operator is implemented with the "stackless coroutine"
@@ -316,10 +337,5 @@ UDPServer::resume(const bool done) {
data_->io_.post(*this);
}
-bool
-UDPServer::hasAnswer() {
- return (data_->done_);
-}
-
} // namespace asiodns
} // namespace isc
diff --git a/src/lib/asiodns/udp_server.h b/src/lib/asiodns/udp_server.h
index 90fcc6f..b32c06c 100644
--- a/src/lib/asiodns/udp_server.h
+++ b/src/lib/asiodns/udp_server.h
@@ -41,16 +41,17 @@ 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 fd the file descriptor of opened UDP socket
+ /// \param af address family, either AF_INET or AF_INET6
/// \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,
- isc::asiolink::SimpleCallback* checkin = NULL,
- DNSLookup* lookup = NULL,
- DNSAnswer* answer = NULL);
+ /// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6
+ /// \throw isc::asiolink::IOError when a low-level error happens, like the
+ /// fd is not a valid descriptor.
+ UDPServer(asio::io_service& io_service, int fd, int af,
+ isc::asiolink::SimpleCallback* checkin = NULL,
+ DNSLookup* lookup = NULL, DNSAnswer* answer = NULL);
/// \brief The function operator
void operator()(asio::error_code ec = asio::error_code(),
@@ -69,16 +70,6 @@ public:
/// 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
diff --git a/src/lib/asiolink/io_address.cc b/src/lib/asiolink/io_address.cc
index 370e8f5..832452c 100644
--- a/src/lib/asiolink/io_address.cc
+++ b/src/lib/asiolink/io_address.cc
@@ -37,7 +37,7 @@ 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) {
+IOAddress::IOAddress(const std::string& address_str) {
asio::error_code err;
asio_address_ = ip::address::from_string(address_str, err);
if (err) {
@@ -46,7 +46,7 @@ IOAddress::IOAddress(const string& address_str) {
}
}
-IOAddress::IOAddress(const ip::address& asio_address) :
+IOAddress::IOAddress(const asio::ip::address& asio_address) :
asio_address_(asio_address)
{}
diff --git a/src/lib/asiolink/io_service.cc b/src/lib/asiolink/io_service.cc
index 76d5ee1..15fad0c 100644
--- a/src/lib/asiolink/io_service.cc
+++ b/src/lib/asiolink/io_service.cc
@@ -14,9 +14,9 @@
#include <config.h>
+#include <unistd.h> // for some IPC/network system calls
#include <netinet/in.h>
#include <sys/socket.h>
-#include <unistd.h> // for some IPC/network system calls
#include <asio.hpp>
#include <asiolink/io_service.h>
diff --git a/src/lib/asiolink/tests/.gitignore b/src/lib/asiolink/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/lib/asiolink/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/lib/bench/benchmark.h b/src/lib/bench/benchmark.h
index 7f77aa1..a5c6fd4 100644
--- a/src/lib/bench/benchmark.h
+++ b/src/lib/bench/benchmark.h
@@ -17,6 +17,7 @@
#include <sys/time.h>
+#include <cassert>
#include <iostream>
#include <ios>
@@ -210,9 +211,9 @@ public:
/// \param target The templated class object that
/// implements the code to be benchmarked.
BenchMark(const int iterations, T target) :
- iterations_(iterations), sub_iterations_(0), target_(target)
+ iterations_(iterations), sub_iterations_(0), target_(NULL)
{
- initialize(true);
+ initialize(target, true);
}
/// \brief Constructor for finer-grained control.
@@ -230,9 +231,9 @@ public:
/// \param immediate If \c true the benchmark will be performed within
/// the constructor; otherwise it only does initialization.
BenchMark(const int iterations, T& target, const bool immediate) :
- iterations_(iterations), sub_iterations_(0), target_(target)
+ iterations_(iterations), sub_iterations_(0), target_(&target)
{
- initialize(immediate);
+ initialize(target, immediate);
}
//@}
@@ -240,15 +241,17 @@ public:
///
/// This method will be called from \c run() before starting the benchmark.
/// By default it's empty, but can be customized via template
- /// specialization.
- void setUp() {}
+ /// specialization. When specialized, a reference to the target object
+ /// given to the constructor will be passed to the implementation.
+ void setUp(T&) {}
/// \brief Hook to be called after benchmark.
///
/// This method will be called from \c run() when the benchmark completes.
/// By default it's empty, but can be customized via template
- /// specialization.
- void tearDown() {}
+ /// specialization. When specialized, a reference to the target object
+ /// given to the constructor will be passed to the implementation.
+ void tearDown(T&) {}
/// \brief Perform benchmark.
///
@@ -257,17 +260,8 @@ public:
/// of times specified on construction, and records the time on completion.
/// Finally, it calls \c tearDown().
void run() {
- setUp();
-
- struct timeval beg, end;
- gettimeofday(&beg, NULL);
- for (unsigned int i = 0; i < iterations_; ++i) {
- sub_iterations_ += target_.run();
- }
- gettimeofday(&end, NULL);
- tv_diff_ = tv_subtract(end, beg);
-
- tearDown();
+ assert(target_ != NULL);
+ run(*target_);
}
/// \brief Print the benchmark result.
@@ -361,9 +355,23 @@ public:
/// performed implicitly.
static const int ITERATION_FAILURE = -1;
private:
- void initialize(const bool immediate) {
+ void run(T& target) {
+ setUp(target);
+
+ struct timeval beg, end;
+ gettimeofday(&beg, NULL);
+ for (unsigned int i = 0; i < iterations_; ++i) {
+ sub_iterations_ += target.run();
+ }
+ gettimeofday(&end, NULL);
+ tv_diff_ = tv_subtract(end, beg);
+
+ tearDown(target);
+ }
+
+ void initialize(T& target, const bool immediate) {
if (immediate) {
- run();
+ run(target);
printResult();
}
}
@@ -388,7 +396,7 @@ private:
static const int ONE_MILLION = 1000000;
const unsigned int iterations_;
unsigned int sub_iterations_;
- T& target_;
+ T* target_;
struct timeval tv_diff_;
};
diff --git a/src/lib/bench/benchmark_util.cc b/src/lib/bench/benchmark_util.cc
index 9cf3b26..34356c8 100644
--- a/src/lib/bench/benchmark_util.cc
+++ b/src/lib/bench/benchmark_util.cc
@@ -61,8 +61,7 @@ loadQueryData(istream& input, BenchQueries& queries, const RRClass& qclass,
string line;
unsigned int linenum = 0;
Message query_message(Message::RENDER);
- OutputBuffer buffer(128); // this should be sufficiently large
- MessageRenderer renderer(buffer);
+ MessageRenderer renderer;
while (getline(input, line), !input.eof()) {
++linenum;
if (input.bad() || input.fail()) {
@@ -99,9 +98,9 @@ loadQueryData(istream& input, BenchQueries& queries, const RRClass& qclass,
renderer.clear();
query_message.toWire(renderer);
vector<unsigned char> query_data(
- static_cast<const unsigned char*>(buffer.getData()),
- static_cast<const unsigned char*>(buffer.getData()) +
- buffer.getLength());
+ static_cast<const unsigned char*>(renderer.getData()),
+ static_cast<const unsigned char*>(renderer.getData()) +
+ renderer.getLength());
queries.push_back(query_data);
} catch (const Exception&) {
if (strict) {
diff --git a/src/lib/bench/example/.gitignore b/src/lib/bench/example/.gitignore
new file mode 100644
index 0000000..eb3877d
--- /dev/null
+++ b/src/lib/bench/example/.gitignore
@@ -0,0 +1 @@
+/search_bench
diff --git a/src/lib/bench/example/search_bench.cc b/src/lib/bench/example/search_bench.cc
index 851d815..84f95d9 100644
--- a/src/lib/bench/example/search_bench.cc
+++ b/src/lib/bench/example/search_bench.cc
@@ -79,9 +79,9 @@ namespace isc {
namespace bench {
template<>
void
-BenchMark<SetSearchBenchMark>::setUp() {
+BenchMark<SetSearchBenchMark>::setUp(SetSearchBenchMark& target) {
cout << "Benchmark for searching std::set (size="
- << target_.data_.size() << ")" << endl;
+ << target.data_.size() << ")" << endl;
}
}
}
diff --git a/src/lib/bench/tests/.gitignore b/src/lib/bench/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/lib/bench/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/lib/bench/tests/benchmark_unittest.cc b/src/lib/bench/tests/benchmark_unittest.cc
index 7bb8a60..dfe7df9 100644
--- a/src/lib/bench/tests/benchmark_unittest.cc
+++ b/src/lib/bench/tests/benchmark_unittest.cc
@@ -46,14 +46,14 @@ namespace isc {
namespace bench {
template <>
void
-BenchMark<TestBenchMark>::setUp() {
- target_.setup_completed_ = true;
+BenchMark<TestBenchMark>::setUp(TestBenchMark& target) {
+ target.setup_completed_ = true;
};
template <>
void
-BenchMark<TestBenchMark>::tearDown() {
- target_.teardown_completed_ = true;
+BenchMark<TestBenchMark>::tearDown(TestBenchMark& target) {
+ target.teardown_completed_ = true;
};
// XXX: some compilers cannot find class static constants used in
@@ -70,9 +70,9 @@ TEST(BenchMarkTest, run) {
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
+ // we'll include some conservative margin (50%) and perform range
// comparison below.
- const int duration_margin = 12500; // 12.5ms
+ const int duration_margin = 25000; // 25ms
const int ONE_MILLION = 1000000;
// Prerequisite check: since the tests in this case may depend on subtle
@@ -80,6 +80,8 @@ TEST(BenchMarkTest, run) {
// 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.
+ // We do this with a tighter margin than the checks themselves
+ const int duration_soft_margin = 12500; // 12.5ms
struct timeval check_begin, check_end;
gettimeofday(&check_begin, NULL);
nanosleep(&sleep_timespec, 0);
@@ -93,8 +95,8 @@ TEST(BenchMarkTest, run) {
--check_end.tv_sec;
}
if (check_end.tv_sec != 0 ||
- sleep_time - duration_margin > check_end.tv_usec ||
- sleep_time + duration_margin < check_end.tv_usec) {
+ sleep_time - duration_soft_margin > check_end.tv_usec ||
+ sleep_time + duration_soft_margin < check_end.tv_usec) {
cerr << "Prerequisite check failed. skipping test" << endl;
return;
}
diff --git a/src/lib/cache/.gitignore b/src/lib/cache/.gitignore
new file mode 100644
index 0000000..a33f3f0
--- /dev/null
+++ b/src/lib/cache/.gitignore
@@ -0,0 +1,2 @@
+/cache_messages.cc
+/cache_messages.h
diff --git a/src/lib/cache/local_zone_data.cc b/src/lib/cache/local_zone_data.cc
index 13d1d75..29ab2bf 100644
--- a/src/lib/cache/local_zone_data.cc
+++ b/src/lib/cache/local_zone_data.cc
@@ -43,7 +43,7 @@ LocalZoneData::lookup(const isc::dns::Name& name,
}
void
-LocalZoneData::update(const isc::dns::RRset& rrset) {
+LocalZoneData::update(const isc::dns::AbstractRRset& rrset) {
//TODO Do we really need to recreate the rrset again?
string key = genCacheEntryName(rrset.getName(), rrset.getType());
LOG_DEBUG(logger, DBG_TRACE_DATA, CACHE_LOCALZONE_UPDATE).arg(key);
diff --git a/src/lib/cache/local_zone_data.h b/src/lib/cache/local_zone_data.h
index 3015847..df77f40 100644
--- a/src/lib/cache/local_zone_data.h
+++ b/src/lib/cache/local_zone_data.h
@@ -47,7 +47,7 @@ public:
/// Otherwise, the existed one will be overwritten.
///
/// \param rrset The rrset to update
- void update(const isc::dns::RRset& rrset);
+ void update(const isc::dns::AbstractRRset& rrset);
private:
std::map<std::string, isc::dns::RRsetPtr> rrsets_map_; // RRsets of the zone
diff --git a/src/lib/cache/rrset_cache.cc b/src/lib/cache/rrset_cache.cc
index 1a5fd48..bb4d339 100644
--- a/src/lib/cache/rrset_cache.cc
+++ b/src/lib/cache/rrset_cache.cc
@@ -70,7 +70,7 @@ RRsetCache::lookup(const isc::dns::Name& qname,
}
RRsetEntryPtr
-RRsetCache::update(const isc::dns::RRset& rrset,
+RRsetCache::update(const isc::dns::AbstractRRset& rrset,
const RRsetTrustLevel& level)
{
LOG_DEBUG(logger, DBG_TRACE_DATA, CACHE_RRSET_UPDATE).arg(rrset.getName()).
diff --git a/src/lib/cache/rrset_cache.h b/src/lib/cache/rrset_cache.h
index 73f9b58..a4ea54e 100644
--- a/src/lib/cache/rrset_cache.h
+++ b/src/lib/cache/rrset_cache.h
@@ -73,7 +73,7 @@ public:
/// \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,
+ RRsetEntryPtr update(const isc::dns::AbstractRRset& rrset,
const RRsetTrustLevel& level);
/// \short Protected memebers, so they can be accessed by tests.
diff --git a/src/lib/cache/rrset_copy.cc b/src/lib/cache/rrset_copy.cc
index 05b139a..b395ce1 100644
--- a/src/lib/cache/rrset_copy.cc
+++ b/src/lib/cache/rrset_copy.cc
@@ -20,7 +20,7 @@ namespace isc {
namespace cache {
void
-rrsetCopy(const isc::dns::RRset& src, isc::dns::RRset& dst) {
+rrsetCopy(const isc::dns::AbstractRRset& src, isc::dns::AbstractRRset& dst) {
RdataIteratorPtr rdata_itor = src.getRdataIterator();
rdata_itor->first();
while(!rdata_itor->isLast()){
diff --git a/src/lib/cache/rrset_copy.h b/src/lib/cache/rrset_copy.h
index b6af8d6..e1dc489 100644
--- a/src/lib/cache/rrset_copy.h
+++ b/src/lib/cache/rrset_copy.h
@@ -33,7 +33,7 @@ namespace cache {
/// we have to do the copy.
void
-rrsetCopy(const isc::dns::RRset& src, isc::dns::RRset& dst);
+rrsetCopy(const isc::dns::AbstractRRset& src, isc::dns::AbstractRRset& dst);
} // namespace cache
} // namespace isc
diff --git a/src/lib/cache/rrset_entry.cc b/src/lib/cache/rrset_entry.cc
index c829956..359fd68 100644
--- a/src/lib/cache/rrset_entry.cc
+++ b/src/lib/cache/rrset_entry.cc
@@ -25,7 +25,8 @@ using namespace isc::dns;
namespace isc {
namespace cache {
-RRsetEntry::RRsetEntry(const isc::dns::RRset& rrset, const RRsetTrustLevel& level):
+RRsetEntry::RRsetEntry(const isc::dns::AbstractRRset& rrset,
+ const RRsetTrustLevel& level):
entry_name_(genCacheEntryName(rrset.getName(), rrset.getType())),
expire_time_(time(NULL) + rrset.getTTL().getValue()),
trust_level_(level),
diff --git a/src/lib/cache/rrset_entry.h b/src/lib/cache/rrset_entry.h
index 09cf79c..129300d 100644
--- a/src/lib/cache/rrset_entry.h
+++ b/src/lib/cache/rrset_entry.h
@@ -75,7 +75,8 @@ 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);
+ RRsetEntry(const isc::dns::AbstractRRset& rrset,
+ const RRsetTrustLevel& level);
/// The destructor.
~RRsetEntry() {}
diff --git a/src/lib/cache/tests/.gitignore b/src/lib/cache/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/lib/cache/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/lib/cache/tests/Makefile.am b/src/lib/cache/tests/Makefile.am
index a215c56..b638f55 100644
--- a/src/lib/cache/tests/Makefile.am
+++ b/src/lib/cache/tests/Makefile.am
@@ -47,11 +47,6 @@ 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/log/liblog.la
run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
diff --git a/src/lib/cache/tests/negative_cache_unittest.cc b/src/lib/cache/tests/negative_cache_unittest.cc
index 56d777d..4935e4a 100644
--- a/src/lib/cache/tests/negative_cache_unittest.cc
+++ b/src/lib/cache/tests/negative_cache_unittest.cc
@@ -52,14 +52,17 @@ TEST_F(NegativeCacheTest, testNXDOMAIN){
msg_nxdomain.makeResponse();
Name non_exist_qname("nonexist.example.com.");
- EXPECT_TRUE(cache->lookup(non_exist_qname, RRType::A(), RRClass::IN(), msg_nxdomain));
+ 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);
+ // May have already crossed seconds boundary
+ EXPECT_GE(nxdomain_ttl1.getValue(), 86399);
+ EXPECT_LE(nxdomain_ttl1.getValue(), 86400);
// SOA response for example.com
Message msg_example_com_soa(Message::PARSE);
@@ -68,16 +71,22 @@ TEST_F(NegativeCacheTest, testNXDOMAIN){
msg_example_com_soa.makeResponse();
Name soa_qname("example.com.");
- EXPECT_TRUE(cache->lookup(soa_qname, RRType::SOA(), RRClass::IN(), msg_example_com_soa));
+ 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);
+ // May have already crossed seconds boundary
+ EXPECT_GE(soa_ttl.getValue(), 172799);
+ EXPECT_LE(soa_ttl.getValue(), 172800);
- sleep(1);
+ // Sleep for 2 seconds. 2 seconds to make sure the final range check
+ // does not overlap with the original ones (in which case this test
+ // would erroneously pass if the ttl value is not changed)
+ sleep(2);
// Query nonexist.example.com again
Message msg_nxdomain2(Message::PARSE);
@@ -90,7 +99,8 @@ TEST_F(NegativeCacheTest, testNXDOMAIN){
// 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);
+ EXPECT_GE(nxdomain_ttl2.getValue(), 86397);
+ EXPECT_LE(nxdomain_ttl2.getValue(), 86398);
// No RRset in ANSWER section
EXPECT_TRUE(msg_nxdomain2.getRRCount(Message::SECTION_ANSWER) == 0);
// Check that only one SOA record exist in AUTHORITY section
@@ -103,13 +113,15 @@ TEST_F(NegativeCacheTest, testNXDOMAIN){
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));
+ 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);
+ EXPECT_GE(soa_ttl2.getValue(), 172797);
+ EXPECT_LE(soa_ttl2.getValue(), 172798);
}
TEST_F(NegativeCacheTest, testNXDOMAINWithoutSOA){
@@ -166,15 +178,16 @@ TEST_F(NegativeCacheTest, testNoerrorNodata){
msg_nodata.makeResponse();
Name example_dot_com("example.com.");
- EXPECT_TRUE(cache->lookup(example_dot_com, RRType::MX(), RRClass::IN(), msg_nodata));
+ 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);
-
+ EXPECT_GE(nodata_ttl1.getValue(), 86399);
+ EXPECT_LE(nodata_ttl1.getValue(), 86400);
// Normal SOA response for example.com
Message msg_example_com_soa(Message::PARSE);
@@ -183,21 +196,26 @@ TEST_F(NegativeCacheTest, testNoerrorNodata){
msg_example_com_soa.makeResponse();
Name soa_qname("example.com.");
- EXPECT_TRUE(cache->lookup(soa_qname, RRType::SOA(), RRClass::IN(), msg_example_com_soa));
+ 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);
+ EXPECT_GE(soa_ttl.getValue(), 172799);
+ EXPECT_LE(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);
+ // Sleep for 2 seconds. 2 seconds to make sure the final range check
+ // does not overlap with the original ones (in which case this test
+ // would erroneously pass if the ttl value is not changed)
+ sleep(2);
EXPECT_TRUE(cache->lookup(example_dot_com, RRType::MX(), RRClass::IN(), msg_nodata2));
@@ -209,9 +227,11 @@ TEST_F(NegativeCacheTest, testNoerrorNodata){
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
+ // 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);
+ EXPECT_GE(nodata_ttl2.getValue(), 86397);
+ EXPECT_LE(nodata_ttl2.getValue(), 86398);
}
TEST_F(NegativeCacheTest, testReferralResponse){
diff --git a/src/lib/cc/.gitignore b/src/lib/cc/.gitignore
new file mode 100644
index 0000000..cb2800f
--- /dev/null
+++ b/src/lib/cc/.gitignore
@@ -0,0 +1,4 @@
+/cc_messages.cc
+/cc_messages.h
+/session_config.h
+/session_config.h.pre
diff --git a/src/lib/cc/cc_messages.mes b/src/lib/cc/cc_messages.mes
index 8370cdd..94b955a 100644
--- a/src/lib/cc/cc_messages.mes
+++ b/src/lib/cc/cc_messages.mes
@@ -14,7 +14,7 @@
$NAMESPACE isc::cc
-% CC_ASYNC_READ_FAILED asynchronous read failed
+% CC_ASYNC_READ_FAILED asynchronous read failed (error code = %1)
This marks a low level error, we tried to read data from the message queue
daemon asynchronously, but the ASIO library returned an error.
diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc
index 77f948a..6ec243a 100644
--- a/src/lib/cc/data.cc
+++ b/src/lib/cc/data.cc
@@ -30,6 +30,10 @@
using namespace std;
+namespace {
+const char* WHITESPACE = " \b\f\n\r\t";
+} // end anonymous namespace
+
namespace isc {
namespace data {
@@ -314,15 +318,49 @@ str_from_stringstream(std::istream &in, const std::string& file, const int line,
} else {
throwJSONError("String expected", file, line, pos);
}
+
while (c != EOF && c != '"') {
- ss << c;
- if (c == '\\' && in.peek() == '"') {
- ss << in.get();
+ if (c == '\\') {
+ // see the spec for allowed escape characters
+ switch (in.peek()) {
+ case '"':
+ c = '"';
+ break;
+ case '/':
+ c = '/';
+ break;
+ case '\\':
+ c = '\\';
+ break;
+ case 'b':
+ c = '\b';
+ break;
+ case 'f':
+ c = '\f';
+ break;
+ case 'n':
+ c = '\n';
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case 't':
+ c = '\t';
+ break;
+ default:
+ throwJSONError("Bad escape", file, line, pos);
+ }
+ // drop the escaped char
+ in.get();
++pos;
}
+ ss << c;
c = in.get();
++pos;
}
+ if (c == EOF) {
+ throwJSONError("Unterminated string", file, line, pos);
+ }
return (ss.str());
}
@@ -427,12 +465,12 @@ from_stringstream_list(std::istream &in, const std::string& file, int& line,
ElementPtr list = Element::createList();
ConstElementPtr cur_list_element;
- skip_chars(in, " \t\n", line, pos);
+ skip_chars(in, WHITESPACE, line, pos);
while (c != EOF && c != ']') {
if (in.peek() != ']') {
cur_list_element = Element::fromJSON(in, file, line, pos);
list->add(cur_list_element);
- skip_to(in, file, line, pos, ",]", " \t\n");
+ skip_to(in, file, line, pos, ",]", WHITESPACE);
}
c = in.get();
pos++;
@@ -445,7 +483,7 @@ from_stringstream_map(std::istream &in, const std::string& file, int& line,
int& pos)
{
ElementPtr map = Element::createMap();
- skip_chars(in, " \t\n", line, pos);
+ skip_chars(in, WHITESPACE, line, pos);
char c = in.peek();
if (c == EOF) {
throwJSONError(std::string("Unterminated map, <string> or } expected"), file, line, pos);
@@ -456,7 +494,7 @@ from_stringstream_map(std::istream &in, const std::string& file, int& line,
while (c != EOF && c != '}') {
std::string key = str_from_stringstream(in, file, line, pos);
- skip_to(in, file, line, pos, ":", " \t\n");
+ skip_to(in, file, line, pos, ":", WHITESPACE);
// skip the :
in.get();
pos++;
@@ -464,7 +502,7 @@ from_stringstream_map(std::istream &in, const std::string& file, int& line,
ConstElementPtr value = Element::fromJSON(in, file, line, pos);
map->set(key, value);
- skip_to(in, file, line, pos, ",}", " \t\n");
+ skip_to(in, file, line, pos, ",}", WHITESPACE);
c = in.get();
pos++;
}
@@ -543,7 +581,7 @@ Element::fromJSON(std::istream &in, const std::string& file, int& line,
char c = 0;
ElementPtr element;
bool el_read = false;
- skip_chars(in, " \n\t", line, pos);
+ skip_chars(in, WHITESPACE, line, pos);
while (c != EOF && !el_read) {
c = in.get();
pos++;
@@ -610,7 +648,14 @@ ElementPtr
Element::fromJSON(const std::string &in) {
std::stringstream ss;
ss << in;
- return (fromJSON(ss, "<string>"));
+ int line = 1, pos = 1;
+ ElementPtr result(fromJSON(ss, "<string>", line, pos));
+ skip_chars(ss, WHITESPACE, line, pos);
+ // ss must now be at end
+ if (ss.peek() != EOF) {
+ throwJSONError("Extra data", "<string>", line, pos);
+ }
+ return result;
}
// to JSON format
@@ -642,7 +687,39 @@ NullElement::toJSON(std::ostream& ss) const {
void
StringElement::toJSON(std::ostream& ss) const {
ss << "\"";
- ss << stringValue();
+ char c;
+ const std::string& str = stringValue();
+ for (size_t i = 0; i < str.size(); ++i) {
+ c = str[i];
+ // Escape characters as defined in JSON spec
+ // Note that we do not escape forward slash; this
+ // is allowed, but not mandatory.
+ switch (c) {
+ case '"':
+ ss << '\\' << c;
+ break;
+ case '\\':
+ ss << '\\' << c;
+ break;
+ case '\b':
+ ss << '\\' << 'b';
+ break;
+ case '\f':
+ ss << '\\' << 'f';
+ break;
+ case '\n':
+ ss << '\\' << 'n';
+ break;
+ case '\r':
+ ss << '\\' << 'r';
+ break;
+ case '\t':
+ ss << '\\' << 't';
+ break;
+ default:
+ ss << c;
+ }
+ }
ss << "\"";
}
diff --git a/src/lib/cc/session.cc b/src/lib/cc/session.cc
index 1b21d21..40ab86d 100644
--- a/src/lib/cc/session.cc
+++ b/src/lib/cc/session.cc
@@ -249,7 +249,7 @@ SessionImpl::internalRead(const asio::error_code& error,
}
user_handler_();
} else {
- LOG_ERROR(logger, CC_ASYNC_READ_FAILED);
+ LOG_ERROR(logger, CC_ASYNC_READ_FAILED).arg(error.value());
isc_throw(SessionError, "asynchronous read failed");
}
}
diff --git a/src/lib/cc/tests/.gitignore b/src/lib/cc/tests/.gitignore
new file mode 100644
index 0000000..f10451c
--- /dev/null
+++ b/src/lib/cc/tests/.gitignore
@@ -0,0 +1,2 @@
+/run_unittests
+/session_unittests_config.h
diff --git a/src/lib/cc/tests/Makefile.am b/src/lib/cc/tests/Makefile.am
index 4760855..08b7f33 100644
--- a/src/lib/cc/tests/Makefile.am
+++ b/src/lib/cc/tests/Makefile.am
@@ -24,11 +24,14 @@ run_unittests_SOURCES = data_unittests.cc session_unittests.cc 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/cc/libcc.la
+# We need to put our libs first, in case gtest (or any dependency, really)
+# is installed in the same location as a different version of bind10
+# Otherwise the linker may not use the source tree libs
+run_unittests_LDADD = $(top_builddir)/src/lib/cc/libcc.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(GTEST_LDADD)
endif
diff --git a/src/lib/cc/tests/data_unittests.cc b/src/lib/cc/tests/data_unittests.cc
index d8624cb..87d92f6 100644
--- a/src/lib/cc/tests/data_unittests.cc
+++ b/src/lib/cc/tests/data_unittests.cc
@@ -20,6 +20,7 @@
using namespace isc::data;
+#include <sstream>
#include <iostream>
using std::oct;
#include <iomanip>
@@ -90,7 +91,7 @@ TEST(Element, from_and_to_json) {
sv.push_back("-1");
sv.push_back("-1.234");
sv.push_back("-123.456");
-
+
BOOST_FOREACH(const std::string& s, sv) {
// test << operator, which uses Element::str()
std::ostringstream stream;
@@ -122,8 +123,16 @@ TEST(Element, from_and_to_json) {
sv.push_back("{ \"a\": None}");
sv.push_back("");
sv.push_back("nul");
+ sv.push_back("hello\"foobar\"");
+ sv.push_back("\"foobar\"hello");
+ sv.push_back("[]hello");
+ sv.push_back("{}hello");
+ // String not delimited correctly
+ sv.push_back("\"hello");
+ sv.push_back("hello\"");
+
+
BOOST_FOREACH(std::string s, sv) {
-
EXPECT_THROW(el = Element::fromJSON(s), isc::data::JSONError);
}
@@ -150,6 +159,9 @@ TEST(Element, from_and_to_json) {
EXPECT_EQ("false", Element::fromJSON("FALSE")->str());
EXPECT_EQ("true", Element::fromJSON("True")->str());
EXPECT_EQ("true", Element::fromJSON("TRUE")->str());
+ EXPECT_EQ("\"\"", Element::fromJSON(" \n \t \r \f \b \"\" \n \f \t \r \b")->str());
+ EXPECT_EQ("{ }", Element::fromJSON("{ \n \r \t \b \f }")->str());
+ EXPECT_EQ("[ ]", Element::fromJSON("[ \n \r \f \t \b ]")->str());
// number overflows
EXPECT_THROW(Element::fromJSON("12345678901234567890")->str(), JSONError);
@@ -299,6 +311,43 @@ TEST(Element, create_and_value_throws) {
}
+// Helper for escape check; it puts the given string in a StringElement,
+// then checks for the following conditions:
+// stringValue() must be same as input
+// toJSON() output must be escaped
+// fromJSON() on the previous output must result in original input
+void
+escapeHelper(const std::string& input, const std::string& expected) {
+ StringElement str_element = StringElement(input);
+ EXPECT_EQ(input, str_element.stringValue());
+ std::stringstream os;
+ str_element.toJSON(os);
+ EXPECT_EQ(expected, os.str());
+ ElementPtr str_element2 = Element::fromJSON(os.str());
+ EXPECT_EQ(str_element.stringValue(), str_element2->stringValue());
+}
+
+TEST(Element, escape) {
+ // Test whether quotes are escaped correctly when creating direct
+ // String elements.
+ escapeHelper("foo\"bar", "\"foo\\\"bar\"");
+ escapeHelper("foo\\bar", "\"foo\\\\bar\"");
+ escapeHelper("foo\bbar", "\"foo\\bbar\"");
+ escapeHelper("foo\fbar", "\"foo\\fbar\"");
+ escapeHelper("foo\nbar", "\"foo\\nbar\"");
+ escapeHelper("foo\rbar", "\"foo\\rbar\"");
+ escapeHelper("foo\tbar", "\"foo\\tbar\"");
+ // Bad escapes
+ EXPECT_THROW(Element::fromJSON("\\a"), JSONError);
+ EXPECT_THROW(Element::fromJSON("\\"), JSONError);
+ // Can't have escaped quotes outside strings
+ EXPECT_THROW(Element::fromJSON("\\\"\\\""), JSONError);
+ // Inside strings is OK
+ EXPECT_NO_THROW(Element::fromJSON("\"\\\"\\\"\""));
+ // A whitespace test
+ EXPECT_NO_THROW(Element::fromJSON("\" \n \r \t \f \n \n \t\""));
+}
+
TEST(Element, ListElement) {
// this function checks the specific functions for ListElements
ElementPtr el = Element::fromJSON("[ 1, \"bar\", 3 ]");
diff --git a/src/lib/config/.gitignore b/src/lib/config/.gitignore
new file mode 100644
index 0000000..c7ec9d3
--- /dev/null
+++ b/src/lib/config/.gitignore
@@ -0,0 +1,2 @@
+/config_messages.cc
+/config_messages.h
diff --git a/src/lib/config/ccsession.cc b/src/lib/config/ccsession.cc
index ac85077..63fa4cd 100644
--- a/src/lib/config/ccsession.cc
+++ b/src/lib/config/ccsession.cc
@@ -489,6 +489,18 @@ ModuleCCSession::ModuleCCSession(
}
+ModuleCCSession::~ModuleCCSession() {
+ try {
+ sendStopping();
+ } catch (const std::exception& exc) {
+ LOG_ERROR(config_logger,
+ CONFIG_CCSESSION_STOPPING).arg(exc.what());
+ } catch (...) {
+ LOG_ERROR(config_logger,
+ CONFIG_CCSESSION_STOPPING_UNKNOWN);
+ }
+};
+
void
ModuleCCSession::start() {
if (started_) {
@@ -741,5 +753,16 @@ ModuleCCSession::updateRemoteConfig(const std::string& module_name,
}
}
+void
+ModuleCCSession::sendStopping() {
+ // Inform the configuration manager that this module is stopping
+ ConstElementPtr cmd(createCommand("stopping",
+ Element::fromJSON(
+ "{\"module_name\": \"" +
+ module_name_ + "\"}")));
+ // It's just an FYI, configmanager is not expected to respond.
+ session_.group_sendmsg(cmd, "ConfigManager");
+}
+
}
}
diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h
index 50bb65c..059968c 100644
--- a/src/lib/config/ccsession.h
+++ b/src/lib/config/ccsession.h
@@ -192,6 +192,14 @@ public:
bool handle_logging = true
);
+ ///
+ /// Destructor
+ ///
+ /// The destructor automatically calls sendStopping(), which sends
+ /// a message to the ConfigManager that this module is stopping
+ ///
+ virtual ~ModuleCCSession();
+
/// Start receiving new commands and configuration changes asynchronously.
///
/// This method must be called only once, and only when the ModuleCCSession
@@ -315,10 +323,45 @@ public:
isc::data::ConstElementPtr getRemoteConfigValue(
const std::string& module_name,
const std::string& identifier) const;
-
+
+ /**
+ * Send a message to the underlying CC session.
+ * This has the same interface as isc::cc::Session::group_sendmsg()
+ *
+ * \param msg see isc::cc::Session::group_sendmsg()
+ * \param group see isc::cc::Session::group_sendmsg()
+ * \param instance see isc::cc::Session::group_sendmsg()
+ * \param to see isc::cc::Session::group_sendmsg()
+ * \return see isc::cc::Session::group_sendmsg()
+ */
+ int groupSendMsg(isc::data::ConstElementPtr msg,
+ std::string group,
+ std::string instance = "*",
+ std::string to = "*") {
+ return (session_.group_sendmsg(msg, group, instance, to));
+ };
+
+ /**
+ * Receive a message from the underlying CC session.
+ * This has the same interface as isc::cc::Session::group_recvmsg()
+ *
+ * \param envelope see isc::cc::Session::group_recvmsg()
+ * \param msg see isc::cc::Session::group_recvmsg()
+ * \param nonblock see isc::cc::Session::group_recvmsg()
+ * \param seq see isc::cc::Session::group_recvmsg()
+ * \return see isc::cc::Session::group_recvmsg()
+ */
+ bool groupRecvMsg(isc::data::ConstElementPtr& envelope,
+ isc::data::ConstElementPtr& msg,
+ bool nonblock = true,
+ int seq = -1) {
+ return (session_.group_recvmsg(envelope, msg, nonblock, seq));
+ };
+
private:
ModuleSpec readModuleSpecification(const std::string& filename);
void startCheck();
+ void sendStopping();
bool started_;
std::string module_name_;
diff --git a/src/lib/config/config_data.h b/src/lib/config/config_data.h
index 197d319..3fdbc25 100644
--- a/src/lib/config/config_data.h
+++ b/src/lib/config/config_data.h
@@ -32,7 +32,7 @@ public:
DataNotFoundError(const char* file, size_t line, const std::string& what) :
isc::Exception(file, line, what) {}
};
-
+
class ConfigData {
public:
/// Constructs a ConfigData option with no specification and an
diff --git a/src/lib/config/config_messages.mes b/src/lib/config/config_messages.mes
index c439edd..552256c 100644
--- a/src/lib/config/config_messages.mes
+++ b/src/lib/config/config_messages.mes
@@ -30,6 +30,18 @@ but will not send back an answer.
The most likely cause of this error is a programming error. Please raise
a bug report.
+% CONFIG_CCSESSION_STOPPING error sending stopping message: %1
+There was a problem when sending a message signaling that the module using
+this CCSession is stopping. This message is sent so that the rest of the
+system is aware that the module is no longer running. Apart from logging
+this message, the error itself is ignored, and the ModuleCCSession is
+still stopped. The specific exception message is printed.
+
+% CONFIG_CCSESSION_STOPPING_UNKNOWN unknown error sending stopping message
+Similar to CONFIG_CCSESSION_STOPPING, but in this case the exception that
+is seen is not a standard exception, and further information is unknown.
+This is a bug.
+
% CONFIG_GET_FAIL error getting configuration from cfgmgr: %1
The configuration manager returned an error when this module requested
the configuration. The full error message answer from the configuration
@@ -37,6 +49,11 @@ manager is appended to the log error. The most likely cause is that
the module is of a different (command specification) version than the
running configuration manager.
+% CONFIG_JSON_PARSE JSON parse error in %1: %2
+There was an error parsing the JSON file. The given file does not appear
+to be in valid JSON format. Please verify that the filename is correct
+and that the contents are valid JSON.
+
% CONFIG_LOG_EXPLICIT will use logging configuration for explicitly-named logger %1
This is a debug message. When processing the "loggers" part of the
configuration file, the configuration library found an entry for the named
@@ -62,11 +79,6 @@ wildcard entry (one containing the "*" character) that matches a logger
specification in the program. The logging configuration for the program
will be updated with the information.
-% CONFIG_JSON_PARSE JSON parse error in %1: %2
-There was an error parsing the JSON file. The given file does not appear
-to be in valid JSON format. Please verify that the filename is correct
-and that the contents are valid JSON.
-
% CONFIG_MOD_SPEC_FORMAT module specification error in %1: %2
The given file does not appear to be a valid specification file: details
are included in the message. Please verify that the filename is correct
diff --git a/src/lib/config/module_spec.cc b/src/lib/config/module_spec.cc
index bebe695..a931070 100644
--- a/src/lib/config/module_spec.cc
+++ b/src/lib/config/module_spec.cc
@@ -40,13 +40,14 @@ check_leaf_item(ConstElementPtr spec, const std::string& name,
if (spec->get(name)->getType() == type) {
return;
} else {
- throw ModuleSpecError(name + " not of type " + Element::typeToName(type));
+ isc_throw(ModuleSpecError,
+ name + " not of type " + Element::typeToName(type));
}
} else if (mandatory) {
// todo: want parent item name, and perhaps some info about location
// in list? or just catch and throw new...
// or make this part non-throwing and check return value...
- throw ModuleSpecError(name + " missing in " + spec->str());
+ isc_throw(ModuleSpecError, name + " missing in " + spec->str());
}
}
@@ -80,7 +81,7 @@ check_config_item(ConstElementPtr spec) {
void
check_config_item_list(ConstElementPtr spec) {
if (spec->getType() != Element::list) {
- throw ModuleSpecError("config_data is not a list of elements");
+ isc_throw(ModuleSpecError, "config_data is not a list of elements");
}
BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
check_config_item(item);
@@ -122,7 +123,7 @@ void check_statistics_item_list(ConstElementPtr spec);
void
check_statistics_item_list(ConstElementPtr spec) {
if (spec->getType() != Element::list) {
- throw ModuleSpecError("statistics is not a list of elements");
+ isc_throw(ModuleSpecError, "statistics is not a list of elements");
}
BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
check_config_item(item);
@@ -135,7 +136,7 @@ check_statistics_item_list(ConstElementPtr spec) {
&& item->contains("item_default")) {
if(!check_format(item->get("item_default"),
item->get("item_format"))) {
- throw ModuleSpecError(
+ isc_throw(ModuleSpecError,
"item_default not valid type of item_format");
}
}
@@ -152,7 +153,7 @@ check_command(ConstElementPtr spec) {
void
check_command_list(ConstElementPtr spec) {
if (spec->getType() != Element::list) {
- throw ModuleSpecError("commands is not a list of elements");
+ isc_throw(ModuleSpecError, "commands is not a list of elements");
}
BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
check_command(item);
@@ -183,7 +184,7 @@ check_module_specification(ConstElementPtr def) {
try {
check_data_specification(def);
} catch (const TypeError& te) {
- throw ModuleSpecError(te.what());
+ isc_throw(ModuleSpecError, te.what());
}
}
}
@@ -314,14 +315,14 @@ moduleSpecFromFile(const std::string& file_name, const bool check)
if (!file) {
std::stringstream errs;
errs << "Error opening " << file_name << ": " << strerror(errno);
- throw ModuleSpecError(errs.str());
+ isc_throw(ModuleSpecError, errs.str());
}
ConstElementPtr module_spec_element = Element::fromJSON(file, file_name);
if (module_spec_element->contains("module_spec")) {
return (ModuleSpec(module_spec_element->get("module_spec"), check));
} else {
- throw ModuleSpecError("No module_spec in specification");
+ isc_throw(ModuleSpecError, "No module_spec in specification");
}
}
@@ -333,7 +334,7 @@ moduleSpecFromFile(std::ifstream& in, const bool check)
if (module_spec_element->contains("module_spec")) {
return (ModuleSpec(module_spec_element->get("module_spec"), check));
} else {
- throw ModuleSpecError("No module_spec in specification");
+ isc_throw(ModuleSpecError, "No module_spec in specification");
}
}
@@ -466,7 +467,6 @@ 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 (!validateSpec(cur_spec_el, data, full, errors)) {
validated = false;
diff --git a/src/lib/config/module_spec.h b/src/lib/config/module_spec.h
index ce3762f..27dcfe3 100644
--- a/src/lib/config/module_spec.h
+++ b/src/lib/config/module_spec.h
@@ -26,15 +26,11 @@ namespace isc { namespace config {
/// A standard ModuleSpec exception that is thrown when a
/// specification is not in the correct form.
///
- /// TODO: use jinmei's exception class as a base and not c_str in
- /// what() there
- class ModuleSpecError : public std::exception {
+ class ModuleSpecError : public isc::Exception {
public:
- ModuleSpecError(std::string m = "Module specification is invalid") : msg(m) {}
- ~ModuleSpecError() throw() {}
- const char* what() const throw() { return (msg.c_str()); }
- private:
- std::string msg;
+ ModuleSpecError(const char* file, size_t line,
+ const char* what = "Module specification is invalid") :
+ isc::Exception(file, line, what) {}
};
///
diff --git a/src/lib/config/tests/.gitignore b/src/lib/config/tests/.gitignore
new file mode 100644
index 0000000..abdfa8a
--- /dev/null
+++ b/src/lib/config/tests/.gitignore
@@ -0,0 +1,2 @@
+/data_def_unittests_config.h
+/run_unittests
diff --git a/src/lib/config/tests/ccsession_unittests.cc b/src/lib/config/tests/ccsession_unittests.cc
index 793fa30..abaff8e 100644
--- a/src/lib/config/tests/ccsession_unittests.cc
+++ b/src/lib/config/tests/ccsession_unittests.cc
@@ -26,6 +26,8 @@
#include <log/logger_name.h>
+#include <boost/scoped_ptr.hpp>
+
using namespace isc::data;
using namespace isc::config;
using namespace isc::cc;
@@ -190,6 +192,67 @@ TEST_F(CCSessionTest, session2) {
EXPECT_EQ(0, session.getMsgQueue()->size());
}
+TEST_F(CCSessionTest, session_close) {
+ // Test whether ModuleCCSession automatically sends a 'stopping'
+ // message when it is destroyed
+ ConstElementPtr msg;
+ std::string group, to;
+
+ EXPECT_FALSE(session.haveSubscription("Spec2", "*"));
+
+ boost::scoped_ptr<ModuleCCSession> mccs(new ModuleCCSession(
+ ccspecfile("spec2.spec"),
+ session, NULL, NULL,
+ true, false));
+ EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
+ // The initial message is irrelevant for this test
+ // (see session2 test), drop it
+ session.getFirstMessage(group, to);
+ // Queue should now be empty
+ ASSERT_EQ(0, session.getMsgQueue()->size());
+ // Invoke the destructor
+ mccs.reset();
+ // Destructor should have caused a new message
+ ASSERT_EQ(1, session.getMsgQueue()->size());
+ msg = session.getFirstMessage(group, to);
+ EXPECT_EQ("{ \"command\": [ \"stopping\", "
+ "{ \"module_name\": \"Spec2\" } ] }", msg->str());
+ EXPECT_EQ("ConfigManager", group);
+ EXPECT_EQ("*", to);
+ EXPECT_EQ(0, session.getMsgQueue()->size());
+}
+
+TEST_F(CCSessionTest, session_close_exception) {
+ // Test whether an exception encountered during the destructor is
+ // handled correctly
+ ConstElementPtr msg;
+ std::string group, to;
+
+ EXPECT_FALSE(session.haveSubscription("Spec2", "*"));
+
+ boost::scoped_ptr<ModuleCCSession> mccs(new ModuleCCSession(
+ ccspecfile("spec2.spec"),
+ session, NULL, NULL,
+ true, false));
+ EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
+ // The initial message is irrelevant for this test
+ // (see session2 test), drop it
+ session.getFirstMessage(group, to);
+ // Queue should now be empty
+ ASSERT_EQ(0, session.getMsgQueue()->size());
+
+ // Set fake session to throw an exception
+ session.setThrowOnSend(true);
+
+ // Invoke the destructor
+ mccs.reset();
+ // Destructor should not have caused a new message (since fakesession
+ // should have thrown an exception)
+ ASSERT_EQ(0, session.getMsgQueue()->size());
+ //EXPECT_EQ(0, session.getMsgQueue()->size());
+}
+
+
ConstElementPtr my_config_handler(ConstElementPtr new_config) {
if (new_config && new_config->contains("item1") &&
new_config->get("item1")->intValue() == 5) {
diff --git a/src/lib/config/tests/fake_session.cc b/src/lib/config/tests/fake_session.cc
index 2b216e7..177e629 100644
--- a/src/lib/config/tests/fake_session.cc
+++ b/src/lib/config/tests/fake_session.cc
@@ -72,7 +72,8 @@ FakeSession::FakeSession(isc::data::ElementPtr initial_messages,
messages_(initial_messages),
subscriptions_(subscriptions),
msg_queue_(msg_queue),
- started_(false)
+ started_(false),
+ throw_on_send_(false)
{
}
@@ -181,8 +182,9 @@ int
FakeSession::group_sendmsg(ConstElementPtr msg, std::string group,
std::string to, std::string)
{
- //cout << "[XX] client sends message: " << msg << endl;
- //cout << "[XX] to: " << group << " . " << instance << "." << to << endl;
+ if (throw_on_send_) {
+ isc_throw(Exception, "Throw on send is set in FakeSession");
+ }
addMessage(msg, group, to);
return (1);
}
@@ -261,6 +263,5 @@ FakeSession::haveSubscription(ConstElementPtr group, ConstElementPtr instance)
{
return (haveSubscription(group->stringValue(), instance->stringValue()));
}
-
}
}
diff --git a/src/lib/config/tests/fake_session.h b/src/lib/config/tests/fake_session.h
index 85e47d5..79ff174 100644
--- a/src/lib/config/tests/fake_session.h
+++ b/src/lib/config/tests/fake_session.h
@@ -87,6 +87,14 @@ public:
isc::data::ElementPtr getMessages() { return (messages_); }
isc::data::ElementPtr getMsgQueue() { return (msg_queue_); }
+ /// Throw exception on sendmsg()
+ ///
+ /// When set to true, and sendmsg() is later called, this
+ /// will throw isc::Exception
+ ///
+ /// \param value If true, enable throw. If false, disable it
+ void setThrowOnSend(bool value) { throw_on_send_ = value; }
+
private:
bool recvmsg(isc::data::ConstElementPtr& msg,
bool nonblock = true, int seq = -1);
@@ -98,6 +106,7 @@ private:
isc::data::ElementPtr subscriptions_;
isc::data::ElementPtr msg_queue_;
bool started_;
+ bool throw_on_send_;
};
} // namespace cc
} // namespace isc
diff --git a/src/lib/config/tests/testdata/.gitignore b/src/lib/config/tests/testdata/.gitignore
new file mode 100644
index 0000000..1c67281
--- /dev/null
+++ b/src/lib/config/tests/testdata/.gitignore
@@ -0,0 +1 @@
+/b10-config.db
diff --git a/src/lib/config/tests/testdata/Makefile.am b/src/lib/config/tests/testdata/Makefile.am
index 0d8b92e..1bf9496 100644
--- a/src/lib/config/tests/testdata/Makefile.am
+++ b/src/lib/config/tests/testdata/Makefile.am
@@ -65,3 +65,4 @@ EXTRA_DIST += spec35.spec
EXTRA_DIST += spec36.spec
EXTRA_DIST += spec37.spec
EXTRA_DIST += spec38.spec
+EXTRA_DIST += spec39.spec
diff --git a/src/lib/config/tests/testdata/spec39.spec b/src/lib/config/tests/testdata/spec39.spec
new file mode 100644
index 0000000..1f72319
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec39.spec
@@ -0,0 +1,21 @@
+{
+ "module_spec": {
+ "module_name": "Spec39",
+ "config_data": [
+ { "item_name": "list",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec": {
+ "item_name": "list_item",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": false
+ }
+ }
+ ],
+ "commands": [],
+ "statistics": []
+ }
+}
+
diff --git a/src/lib/cryptolink/tests/.gitignore b/src/lib/cryptolink/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/lib/cryptolink/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/lib/datasrc/.gitignore b/src/lib/datasrc/.gitignore
new file mode 100644
index 0000000..05c761e
--- /dev/null
+++ b/src/lib/datasrc/.gitignore
@@ -0,0 +1,4 @@
+/datasrc_messages.cc
+/datasrc_messages.h
+/datasrc_config.h
+/datasrc_config.h.pre
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index b6c314c..2cdb8ea 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -7,10 +7,10 @@ AM_CPPFLAGS += $(SQLITE_CFLAGS)
AM_CXXFLAGS = $(B10_CXXFLAGS)
-pkglibexecdir = $(libexecdir)/@PACKAGE@/backends
+pkglibdir = $(libexecdir)/@PACKAGE@/backends
datasrc_config.h: datasrc_config.h.pre
- $(SED) -e "s|@@PKGLIBEXECDIR@@|$(pkglibexecdir)|" datasrc_config.h.pre >$@
+ $(SED) -e "s|@@PKGLIBDIR@@|$(pkglibdir)|" datasrc_config.h.pre >$@
CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
CLEANFILES += datasrc_config.h
@@ -21,27 +21,31 @@ 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 += rbnode_rrset.h
libdatasrc_la_SOURCES += rbtree.h
libdatasrc_la_SOURCES += zonetable.h zonetable.cc
-libdatasrc_la_SOURCES += zone.h
+libdatasrc_la_SOURCES += zone.h zone_finder_context.cc
libdatasrc_la_SOURCES += result.h
libdatasrc_la_SOURCES += logger.h logger.cc
libdatasrc_la_SOURCES += client.h iterator.h
libdatasrc_la_SOURCES += database.h database.cc
libdatasrc_la_SOURCES += factory.h factory.cc
nodist_libdatasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
+libdatasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
-pkglibexec_LTLIBRARIES = sqlite3_ds.la memory_ds.la
+pkglib_LTLIBRARIES = sqlite3_ds.la memory_ds.la
sqlite3_ds_la_SOURCES = sqlite3_accessor.h sqlite3_accessor.cc
-sqlite3_ds_la_LDFLAGS = -module
+sqlite3_ds_la_SOURCES += sqlite3_accessor_link.cc
+sqlite3_ds_la_LDFLAGS = -module -avoid-version
sqlite3_ds_la_LDFLAGS += -no-undefined -version-info 1:0:0
sqlite3_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
sqlite3_ds_la_LIBADD += libdatasrc.la
sqlite3_ds_la_LIBADD += $(SQLITE_LIBS)
memory_ds_la_SOURCES = memory_datasrc.h memory_datasrc.cc
-memory_ds_la_LDFLAGS = -module
+memory_ds_la_SOURCES += memory_datasrc_link.cc
+memory_ds_la_LDFLAGS = -module -avoid-version
memory_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
memory_ds_la_LIBADD += libdatasrc.la
diff --git a/src/lib/datasrc/data_source.cc b/src/lib/datasrc/data_source.cc
index 94dec89..a713b82 100644
--- a/src/lib/datasrc/data_source.cc
+++ b/src/lib/datasrc/data_source.cc
@@ -384,7 +384,8 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
const Name* const zonename = zoneinfo.getEnclosingZone();
if (ds == NULL) {
task.flags |= DataSrc::NO_SUCH_ZONE;
- logger.info(DATASRC_QUERY_NO_ZONE).arg(task.qname).arg(task.qclass);
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_QUERY_NO_ZONE).
+ arg(task.qname).arg(task.qclass);
return (DataSrc::SUCCESS);
}
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index 2b324fc..7b271f1 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -27,15 +27,18 @@
#include <dns/rrset.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/nsec3hash.h>
#include <datasrc/data_source.h>
#include <datasrc/logger.h>
#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
using namespace isc::dns;
using namespace std;
using namespace isc::dns::rdata;
+using namespace boost;
namespace isc {
namespace datasrc {
@@ -177,15 +180,17 @@ private:
DatabaseClient::Finder::FoundRRsets
DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
bool check_ns, const string* construct_name,
- bool any)
+ bool any,
+ DatabaseAccessor::IteratorContextPtr context)
{
RRsigStore sig_store;
bool records_found = false;
std::map<RRType, RRsetPtr> result;
- // Request the context
- DatabaseAccessor::IteratorContextPtr
- context(accessor_->getRecords(name, zone_id_));
+ // Request the context in case we didn't get one
+ if (!context) {
+ context = accessor_->getRecords(name, zone_id_);
+ }
// It must not return NULL, that's a bug of the implementation
if (!context) {
isc_throw(isc::Unexpected, "Iterator context null at " + name);
@@ -286,13 +291,11 @@ DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
i != result.end(); ++ i) {
sig_store.appendSignatures(i->second);
}
-
if (records_found && any) {
result[RRType::ANY()] = RRsetPtr();
// These will be sitting on the other RRsets.
result.erase(RRType::RRSIG());
}
-
return (FoundRRsets(records_found, result));
}
@@ -318,6 +321,30 @@ namespace {
typedef std::set<RRType> WantedTypes;
const WantedTypes&
+NSEC3_TYPES() {
+ static bool initialized(false);
+ static WantedTypes result;
+
+ if (!initialized) {
+ result.insert(RRType::NSEC3());
+ initialized = true;
+ }
+ return (result);
+}
+
+const WantedTypes&
+NSEC3PARAM_TYPES() {
+ static bool initialized(false);
+ static WantedTypes result;
+
+ if (!initialized) {
+ result.insert(RRType::NSEC3PARAM());
+ initialized = true;
+ }
+ return (result);
+}
+
+const WantedTypes&
NSEC_TYPES() {
static bool initialized(false);
static WantedTypes result;
@@ -355,56 +382,20 @@ FINAL_TYPES() {
}
return (result);
}
-
-}
-
-ConstRRsetPtr
-DatabaseClient::Finder::findNSECCover(const Name& name) {
- try {
- // Which one should contain the NSEC record?
- const Name coverName(findPreviousName(name));
- // Get the record and copy it out
- const FoundRRsets found = getRRsets(coverName.toText(), NSEC_TYPES(),
- coverName != getOrigin());
- const FoundIterator
- nci(found.second.find(RRType::NSEC()));
- if (nci != found.second.end()) {
- return (nci->second);
- } else {
- // The previous doesn't contain NSEC.
- // Badly signed zone or a bug?
-
- // FIXME: Currently, if the zone is not signed, we could get
- // here. In that case we can't really throw, but for now, we can't
- // recognize it. So we don't throw at all, enable it once
- // we have a is_signed flag or something.
-#if 0
- isc_throw(DataSourceError, "No NSEC in " +
- coverName.toText() + ", but it was "
- "returned as previous - "
- "accessor error? Badly signed zone?");
-#endif
- }
- }
- catch (const isc::NotImplemented&) {
- // Well, they want DNSSEC, but there is no available.
- // So we don't provide anything.
- LOG_INFO(logger, DATASRC_DATABASE_COVER_NSEC_UNSUPPORTED).
- arg(accessor_->getDBName()).arg(name);
- }
- // We didn't find it, return nothing
- return (ConstRRsetPtr());
}
-ZoneFinder::FindResult
+ZoneFinderContextPtr
DatabaseClient::Finder::findAll(const isc::dns::Name& name,
std::vector<isc::dns::ConstRRsetPtr>& target,
const FindOptions options)
{
- return (findInternal(name, RRType::ANY(), &target, options));
+ return (ZoneFinderContextPtr(new Context(*this, options,
+ findInternal(name, RRType::ANY(),
+ &target, options),
+ target)));
}
-ZoneFinder::FindResult
+ZoneFinderContextPtr
DatabaseClient::Finder::find(const isc::dns::Name& name,
const isc::dns::RRType& type,
const FindOptions options)
@@ -412,7 +403,9 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
if (type == RRType::ANY()) {
isc_throw(isc::Unexpected, "Use findAll to answer ANY");
}
- return (findInternal(name, type, NULL, options));
+ return (ZoneFinderContextPtr(new Context(*this, options,
+ findInternal(name, type, NULL,
+ options))));
}
DatabaseClient::Finder::DelegationSearchResult
@@ -573,11 +566,11 @@ DatabaseClient::Finder::findDelegationPoint(const isc::dns::Name& name,
// covering NSEC record.
//
// If none of the above applies in any level, the search fails with NXDOMAIN.
-ZoneFinder::FindResult
+ZoneFinder::ResultContext
DatabaseClient::Finder::findWildcardMatch(
- const isc::dns::Name& name, const isc::dns::RRType& type,
- const FindOptions options, const DelegationSearchResult& dresult,
- std::vector<isc::dns::ConstRRsetPtr>* target)
+ const Name& name, const RRType& type, const FindOptions options,
+ const DelegationSearchResult& dresult, vector<ConstRRsetPtr>* target,
+ FindDNSSECContext& dnssec_ctx)
{
// Note that during the search we are going to search not only for the
// requested type, but also for types that indicate a delegation -
@@ -616,13 +609,12 @@ DatabaseClient::Finder::findWildcardMatch(
DATASRC_DATABASE_WILDCARD_CANCEL_NS).
arg(accessor_->getDBName()).arg(wildcard).
arg(dresult.first_ns->getName());
- return (ZoneFinder::FindResult(DELEGATION, dresult.first_ns));
-
+ return (ResultContext(DELEGATION, dresult.first_ns));
} else if (!hasSubdomains(name.split(i - 1).toText())) {
// The wildcard match is the best one, find the final result
// at it. Note that wildcard should never be the zone origin.
- return (findOnNameResult(name, type, options, false,
- found, &wildcard, target));
+ return (findOnNameResult(name, type, options, false, found,
+ &wildcard, target, dnssec_ctx));
} else {
// more specified match found, cancel wildcard match
@@ -630,7 +622,7 @@ DatabaseClient::Finder::findWildcardMatch(
DATASRC_DATABASE_WILDCARD_CANCEL_SUB).
arg(accessor_->getDBName()).arg(wildcard).
arg(name).arg(superdomain);
- return (ZoneFinder::FindResult(NXDOMAIN, ConstRRsetPtr()));
+ return (ResultContext(NXDOMAIN, ConstRRsetPtr()));
}
} else if (hasSubdomains(wildcard)) {
@@ -638,25 +630,23 @@ DatabaseClient::Finder::findWildcardMatch(
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_WILDCARD_EMPTY).
arg(accessor_->getDBName()).arg(wildcard).arg(name);
- if ((options & FIND_DNSSEC) != 0) {
- ConstRRsetPtr nsec = findNSECCover(Name(wildcard));
- if (nsec) {
- return (ZoneFinder::FindResult(WILDCARD_NXRRSET, nsec));
- }
- }
- return (ZoneFinder::FindResult(NXRRSET, ConstRRsetPtr()));
+ const FindResultFlags flags = (RESULT_WILDCARD |
+ dnssec_ctx.getResultFlags());
+ return (ResultContext(NXRRSET,
+ dnssec_ctx.getDNSSECRRset(Name(wildcard),
+ true), flags));
}
}
// Nothing found at any level.
- return (ZoneFinder::FindResult(NXDOMAIN, ConstRRsetPtr()));
+ return (ResultContext(NXDOMAIN, ConstRRsetPtr()));
}
-ZoneFinder::FindResult
+ZoneFinder::ResultContext
DatabaseClient::Finder::logAndCreateResult(
const Name& name, const string* wildname, const RRType& type,
ZoneFinder::Result code, ConstRRsetPtr rrset,
- const isc::log::MessageID& log_id) const
+ const isc::log::MessageID& log_id, FindResultFlags flags) const
{
if (rrset) {
if (wildname == NULL) {
@@ -679,10 +669,125 @@ DatabaseClient::Finder::logAndCreateResult(
arg(getClass()).arg(*wildname);
}
}
- return (ZoneFinder::FindResult(code, rrset));
+ return (ResultContext(code, rrset, flags));
}
-ZoneFinder::FindResult
+DatabaseClient::Finder::FindDNSSECContext::FindDNSSECContext(
+ DatabaseClient::Finder& finder,
+ const FindOptions options) :
+ finder_(finder),
+ need_dnssec_((options & FIND_DNSSEC) != 0),
+ is_nsec3_(false),
+ is_nsec_(false),
+ probed_(false)
+{}
+
+void
+DatabaseClient::Finder::FindDNSSECContext::probe() {
+ if (!probed_) {
+ probed_ = true;
+ if (need_dnssec_) {
+ // If an NSEC3PARAM RR exists at the zone apex, it's quite likely
+ // that the zone is signed with NSEC3. (If not the zone is more
+ // or less broken, but it's caller's responsibility how to handle
+ // such cases).
+ const string origin = finder_.getOrigin().toText();
+ const FoundRRsets nsec3_found =
+ finder_.getRRsets(origin, NSEC3PARAM_TYPES(), false);
+ const FoundIterator nfi=
+ nsec3_found.second.find(RRType::NSEC3PARAM());
+ is_nsec3_ = (nfi != nsec3_found.second.end());
+
+ // Likewise for NSEC, depending on the apex has an NSEC RR.
+ // If we know the zone is NSEC3-signed, however, we don't bother
+ // to check that. This is aligned with the transition guideline
+ // described in Section 10.4 of RFC 5155.
+ if (!is_nsec3_) {
+ const FoundRRsets nsec_found =
+ finder_.getRRsets(origin, NSEC_TYPES(), false);
+ const FoundIterator nfi =
+ nsec_found.second.find(RRType::NSEC());
+ is_nsec_ = (nfi != nsec_found.second.end());
+ }
+ }
+ }
+}
+
+bool
+DatabaseClient::Finder::FindDNSSECContext::isNSEC3() {
+ if (!probed_) {
+ probe();
+ }
+ return (is_nsec3_);
+}
+
+bool
+DatabaseClient::Finder::FindDNSSECContext::isNSEC() {
+ if (!probed_) {
+ probe();
+ }
+ return (is_nsec_);
+}
+
+isc::dns::ConstRRsetPtr
+DatabaseClient::Finder::FindDNSSECContext::getDNSSECRRset(
+ const FoundRRsets& found_set)
+{
+ if (!isNSEC()) {
+ return (ConstRRsetPtr());
+ }
+
+ const FoundIterator nci = found_set.second.find(RRType::NSEC());
+ if (nci != found_set.second.end()) {
+ return (nci->second);
+ } else {
+ return (ConstRRsetPtr());
+ }
+}
+
+isc::dns::ConstRRsetPtr
+DatabaseClient::Finder::FindDNSSECContext::getDNSSECRRset(const Name &name,
+ bool covering)
+{
+ if (!isNSEC()) {
+ return (ConstRRsetPtr());
+ }
+
+ try {
+ const Name& nsec_name =
+ covering ? finder_.findPreviousName(name) : name;
+ const bool need_nscheck = (nsec_name != finder_.getOrigin());
+ const FoundRRsets found = finder_.getRRsets(nsec_name.toText(),
+ NSEC_TYPES(),
+ need_nscheck);
+ const FoundIterator nci = found.second.find(RRType::NSEC());
+ if (nci != found.second.end()) {
+ return (nci->second);
+ }
+ } catch (const isc::NotImplemented&) {
+ // This happens when the underlying database accessor doesn't support
+ // findPreviousName() (it probably doesn't support DNSSEC at all) but
+ // there is somehow an NSEC RR at the zone apex. We log the fact but
+ // otherwise let the caller decide what to do (so, for example, a
+ // higher level query processing won't completely fail but can return
+ // anything it can get).
+ LOG_INFO(logger, DATASRC_DATABASE_COVER_NSEC_UNSUPPORTED).
+ arg(finder_.accessor_->getDBName()).arg(name);
+ }
+ return (ConstRRsetPtr());
+}
+
+ZoneFinder::FindResultFlags
+DatabaseClient::Finder::FindDNSSECContext::getResultFlags() {
+ if (isNSEC3()) {
+ return (RESULT_NSEC3_SIGNED);
+ } else if (isNSEC()) {
+ return (RESULT_NSEC_SIGNED);
+ }
+ return (RESULT_DEFAULT);
+}
+
+ZoneFinder::ResultContext
DatabaseClient::Finder::findOnNameResult(const Name& name,
const RRType& type,
const FindOptions options,
@@ -690,9 +795,14 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
const FoundRRsets& found,
const string* wildname,
std::vector<isc::dns::ConstRRsetPtr>*
- target)
+ target, FindDNSSECContext& dnssec_ctx)
{
const bool wild = (wildname != NULL);
+ // For wildcard case with DNSSEC required, the caller would need to
+ // know whether it's NSEC or NSEC3 signed. getResultFlags returns
+ // appropriate flag based on the query context and zone status.
+ const FindResultFlags flags =
+ wild ? (RESULT_WILDCARD | dnssec_ctx.getResultFlags()) : RESULT_DEFAULT;
// Get iterators for the different types of records we are interested in -
// CNAME, NS and Wanted types.
@@ -700,7 +810,7 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
const FoundIterator cni(found.second.find(RRType::CNAME()));
const FoundIterator wti(found.second.find(type));
- if (!is_origin && ((options & FIND_GLUE_OK) == 0) &&
+ if (!is_origin && (options & FIND_GLUE_OK) == 0 &&
nsi != found.second.end()) {
// A NS RRset was found at the domain we were searching for. As it is
// not at the origin of the zone, it is a delegation and indicates that
@@ -709,7 +819,8 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
return (logAndCreateResult(name, wildname, type, DELEGATION,
nsi->second,
wild ? DATASRC_DATABASE_WILDCARD_NS :
- DATASRC_DATABASE_FOUND_DELEGATION_EXACT));
+ DATASRC_DATABASE_FOUND_DELEGATION_EXACT,
+ flags));
} else if (type != RRType::CNAME() && cni != found.second.end()) {
// We are not searching for a CNAME but nevertheless we have found one
@@ -722,11 +833,10 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
cni->second->getRdataCount() << " rdata at " << name <<
", expected 1");
}
- return (logAndCreateResult(name, wildname, type,
- wild ? WILDCARD_CNAME : CNAME, cni->second,
+ return (logAndCreateResult(name, wildname, type, CNAME, cni->second,
wild ? DATASRC_DATABASE_WILDCARD_CNAME :
- DATASRC_DATABASE_FOUND_CNAME));
-
+ DATASRC_DATABASE_FOUND_CNAME,
+ flags));
} else if (wti != found.second.end()) {
bool any(type == RRType::ANY());
isc::log::MessageID lid(wild ? DATASRC_DATABASE_WILDCARD_MATCH :
@@ -748,9 +858,8 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
// includes the case where we were explicitly querying for a CNAME and
// found it. It also includes the case where we were querying for an
// NS RRset and found it at the apex of the zone.)
- return (logAndCreateResult(name, wildname, type,
- wild ? WILDCARD : SUCCESS, wti->second,
- lid));
+ return (logAndCreateResult(name, wildname, type, SUCCESS,
+ wti->second, lid, flags));
}
// If we get here, we have found something at the requested name but not
@@ -759,45 +868,29 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
// provide the NSEC records. If it's for wildcard, we need to get the
// NSEC records in the name of the wildcard, not the substituted one,
// so we need to search the tree again.
- ConstRRsetPtr nsec_rrset; // possibly used with DNSSEC, otherwise NULL
- if ((options & FIND_DNSSEC) != 0) {
- if (wild) {
- const FoundRRsets wfound = getRRsets(*wildname, NSEC_TYPES(),
- true);
- const FoundIterator nci = wfound.second.find(RRType::NSEC());
- if (nci != wfound.second.end()) {
- nsec_rrset = nci->second;
- }
- } else {
- const FoundIterator nci = found.second.find(RRType::NSEC());
- if (nci != found.second.end()) {
- nsec_rrset = nci->second;
- }
- }
- }
- if (nsec_rrset) {
+ const ConstRRsetPtr dnssec_rrset =
+ wild ? dnssec_ctx.getDNSSECRRset(Name(*wildname), false) :
+ dnssec_ctx.getDNSSECRRset(found);
+ if (dnssec_rrset) {
// This log message covers both normal and wildcard cases, so we pass
// NULL for 'wildname'.
- return (logAndCreateResult(name, NULL, type,
- wild ? WILDCARD_NXRRSET : NXRRSET,
- nsec_rrset,
- DATASRC_DATABASE_FOUND_NXRRSET_NSEC));
+ return (logAndCreateResult(name, NULL, type, NXRRSET, dnssec_rrset,
+ DATASRC_DATABASE_FOUND_NXRRSET_NSEC,
+ flags | RESULT_NSEC_SIGNED));
}
- return (logAndCreateResult(name, wildname, type,
- wild ? WILDCARD_NXRRSET : NXRRSET, nsec_rrset,
+ return (logAndCreateResult(name, wildname, type, NXRRSET, dnssec_rrset,
wild ? DATASRC_DATABASE_WILDCARD_NXRRSET :
- DATASRC_DATABASE_FOUND_NXRRSET));
+ DATASRC_DATABASE_FOUND_NXRRSET,
+ flags | dnssec_ctx.getResultFlags()));
}
-ZoneFinder::FindResult
+ZoneFinder::ResultContext
DatabaseClient::Finder::findNoNameResult(const Name& name, const RRType& type,
FindOptions options,
const DelegationSearchResult& dresult,
std::vector<isc::dns::ConstRRsetPtr>*
- target)
+ target, FindDNSSECContext& dnssec_ctx)
{
- const bool dnssec_data = ((options & FIND_DNSSEC) != 0);
-
// On entry to this method, we know that the database doesn't have any
// entry for this name. Before returning NXDOMAIN, we need to check
// for special cases.
@@ -809,18 +902,18 @@ DatabaseClient::Finder::findNoNameResult(const Name& name, const RRType& type,
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL).
arg(accessor_->getDBName()).arg(name);
- return (FindResult(NXRRSET, dnssec_data ? findNSECCover(name) :
- ConstRRsetPtr()));
-
+ return (ResultContext(NXRRSET, dnssec_ctx.getDNSSECRRset(name, true),
+ dnssec_ctx.getResultFlags()));
} else if ((options & NO_WILDCARD) == 0) {
// It's not an empty non-terminal and wildcard matching is not
// disabled, so check for wildcards. If there is a wildcard match
// (i.e. all results except NXDOMAIN) return it; otherwise fall
// through to the NXDOMAIN case below.
- const ZoneFinder::FindResult wresult =
- findWildcardMatch(name, type, options, dresult, target);
- if (wresult.code != NXDOMAIN) {
- return (FindResult(wresult.code, wresult.rrset));
+ const ResultContext wcontext =
+ findWildcardMatch(name, type, options, dresult, target,
+ dnssec_ctx);
+ if (wcontext.code != NXDOMAIN) {
+ return (wcontext);
}
}
@@ -828,15 +921,14 @@ DatabaseClient::Finder::findNoNameResult(const Name& name, const RRType& type,
// NSEC records if requested).
LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_NO_MATCH).
arg(accessor_->getDBName()).arg(name).arg(type).arg(getClass());
- return (FindResult(NXDOMAIN, dnssec_data ? findNSECCover(name) :
- ConstRRsetPtr()));
+ return (ResultContext(NXDOMAIN, dnssec_ctx.getDNSSECRRset(name, true),
+ dnssec_ctx.getResultFlags()));
}
-ZoneFinder::FindResult
-DatabaseClient::Finder::findInternal(const isc::dns::Name& name,
- const isc::dns::RRType& type,
- std::vector<isc::dns::ConstRRsetPtr>* target,
- const FindOptions options)
+ZoneFinder::ResultContext
+DatabaseClient::Finder::findInternal(const Name& name, const RRType& type,
+ std::vector<ConstRRsetPtr>* target,
+ const FindOptions options)
{
LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
.arg(accessor_->getDBName()).arg(name).arg(type).arg(getClass());
@@ -848,7 +940,7 @@ DatabaseClient::Finder::findInternal(const isc::dns::Name& name,
name.compare(getOrigin()).getRelation();
if (reln != NameComparisonResult::SUBDOMAIN &&
reln != NameComparisonResult::EQUAL) {
- return (FindResult(NXDOMAIN, ConstRRsetPtr()));
+ isc_throw(OutOfZone, name.toText() << " not in " << getOrigin());
}
// First, go through all superdomains from the origin down, searching for
@@ -864,7 +956,8 @@ DatabaseClient::Finder::findInternal(const isc::dns::Name& name,
// presence of the delegation.)
const DelegationSearchResult dresult = findDelegationPoint(name, options);
if (dresult.rrset) {
- return (FindResult(dresult.code, dresult.rrset));
+ // In this case no special flags are needed.
+ return (ResultContext(dresult.code, dresult.rrset));
}
// If there is no delegation, look for the exact match to the request
@@ -879,19 +972,136 @@ DatabaseClient::Finder::findInternal(const isc::dns::Name& name,
const FoundRRsets found = getRRsets(name.toText(), final_types,
!is_origin, NULL,
type == RRType::ANY());
-
+ FindDNSSECContext dnssec_ctx(*this, options);
if (found.first) {
// Something found at the domain name. Look into it further to get
// the final result.
return (findOnNameResult(name, type, options, is_origin, found, NULL,
- target));
+ target, dnssec_ctx));
} else {
// Did not find anything at all at the domain name, so check for
// subdomains or wildcards.
- return (findNoNameResult(name, type, options, dresult, target));
+ return (findNoNameResult(name, type, options, dresult, target,
+ dnssec_ctx));
}
}
+// The behaviour is inspired by the one in the in-memory implementation.
+ZoneFinder::FindNSEC3Result
+DatabaseClient::Finder::findNSEC3(const Name& name, bool recursive) {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_DATABASE_FINDNSEC3).arg(name).
+ arg(recursive ? "recursive" : "non-recursive");
+
+ // First, validate the input
+ const NameComparisonResult cmp_result(name.compare(getOrigin()));
+ if (cmp_result.getRelation() != NameComparisonResult::EQUAL &&
+ cmp_result.getRelation() != NameComparisonResult::SUBDOMAIN) {
+ isc_throw(OutOfZone, "findNSEC3 attempt for out-of-zone name: " <<
+ name << ", zone: " << getOrigin() << "/" << getClass());
+ }
+
+ // Now, we need to get the NSEC3 params from the apex and create the hash
+ // creator for it.
+ const FoundRRsets nsec3param(getRRsets(getOrigin().toText(),
+ NSEC3PARAM_TYPES(), false));
+ const FoundIterator param(nsec3param.second.find(RRType::NSEC3PARAM()));
+ if (!nsec3param.first || param == nsec3param.second.end()) {
+ // No NSEC3 params? :-(
+ isc_throw(DataSourceError, "findNSEC3 attempt for non NSEC3 signed " <<
+ "zone: " << getOrigin() << "/" << getClass());
+ }
+ // This takes the RRset received from the find method, takes the first RR
+ // in it, casts it to NSEC3PARAM (as it should be that one) and then creates
+ // the hash calculator class from it.
+ const scoped_ptr<NSEC3Hash> calculator(NSEC3Hash::create(
+ dynamic_cast<const generic::NSEC3PARAM&>(
+ param->second->getRdataIterator()->getCurrent())));
+
+ // Few shortcut variables
+ const unsigned olabels(getOrigin().getLabelCount());
+ const unsigned qlabels(name.getLabelCount());
+ const string otext(getOrigin().toText());
+
+ // This will be set to the one covering the query name
+ ConstRRsetPtr covering_proof;
+
+ // We keep stripping the leftmost label until we find something.
+ // In case it is recursive, we'll exit the loop at the first iteration.
+ for (unsigned labels(qlabels); labels >= olabels; -- labels) {
+ const string hash(calculator->calculate(labels == qlabels ? name :
+ name.split(qlabels - labels,
+ labels)));
+ // Get the exact match for the name.
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_DATABASE_FINDNSEC3_TRYHASH).
+ arg(name).arg(labels).arg(hash);
+
+ DatabaseAccessor::IteratorContextPtr
+ context(accessor_->getNSEC3Records(hash, zone_id_));
+
+ if (!context) {
+ isc_throw(Unexpected, "Iterator context null for hash " + hash);
+ }
+
+ const FoundRRsets nsec3(getRRsets(hash + "." + otext, NSEC3_TYPES(),
+ false, NULL, false, context));
+
+ if (nsec3.first) {
+ // We found an exact match against the current label.
+ const FoundIterator it(nsec3.second.find(RRType::NSEC3()));
+ if (it == nsec3.second.end()) {
+ isc_throw(DataSourceError, "Hash " + hash +
+ "exists, but no NSEC3 there");
+ }
+
+ LOG_DEBUG(logger, DBG_TRACE_BASIC,
+ DATASRC_DATABASE_FINDNSEC3_MATCH).arg(name).arg(labels).
+ arg(*it->second);
+ // Yes, we win
+ return (FindNSEC3Result(true, labels, it->second, covering_proof));
+ } else {
+ // There's no exact match. We try a previous one. We must find it
+ // (if the zone is properly signed).
+ const string prevHash(accessor_->findPreviousNSEC3Hash(zone_id_,
+ hash));
+ LOG_DEBUG(logger, DBG_TRACE_BASIC,
+ DATASRC_DATABASE_FINDNSEC3_TRYHASH_PREV).arg(name).
+ arg(labels).arg(prevHash);
+ context = accessor_->getNSEC3Records(prevHash, zone_id_);
+ const FoundRRsets prev_nsec3(getRRsets(prevHash + "." + otext,
+ NSEC3_TYPES(), false, NULL,
+ false, context));
+
+ if (!prev_nsec3.first) {
+ isc_throw(DataSourceError, "Hash " + prevHash + " returned "
+ "from findPreviousNSEC3Hash, but it is empty");
+ }
+ const FoundIterator
+ prev_it(prev_nsec3.second.find(RRType::NSEC3()));
+ if (prev_it == prev_nsec3.second.end()) {
+ isc_throw(DataSourceError, "The previous hash " + prevHash +
+ "exists, but does not contain the NSEC3");
+ }
+
+ covering_proof = prev_it->second;
+ // In case it is recursive, we try to get an exact match a level
+ // up. If it is not recursive, the caller is ok with a covering
+ // one, so we just return it.
+ if (!recursive) {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC,
+ DATASRC_DATABASE_FINDNSEC3_COVER).arg(name).
+ arg(labels).arg(*covering_proof);
+ return (FindNSEC3Result(false, labels, covering_proof,
+ ConstRRsetPtr()));
+ }
+ }
+ }
+
+ // The zone must contain at least the apex and that one should match
+ // exactly. If that doesn't happen, we have a problem.
+ isc_throw(DataSourceError, "recursive findNSEC3 mode didn't stop, likely a "
+ "broken NSEC3 zone: " << otext << "/" << getClass());
+}
+
Name
DatabaseClient::Finder::findPreviousName(const Name& name) const {
const string str(accessor_->findPreviousName(zone_id_,
@@ -899,16 +1109,9 @@ DatabaseClient::Finder::findPreviousName(const Name& name) const {
try {
return (Name(str));
}
-
- // To avoid having the same code many times, we just catch all the
- // exceptions and handle them in a common code below
- catch (const isc::dns::EmptyLabel&) {}
- catch (const isc::dns::TooLongLabel&) {}
- catch (const isc::dns::BadLabelType&) {}
- catch (const isc::dns::BadEscape&) {}
- catch (const isc::dns::TooLongName&) {}
- catch (const isc::dns::IncompleteName&) {}
- isc_throw(DataSourceError, "Bad name " + str + " from findPreviousName");
+ catch (const isc::dns::NameParserException&) {
+ isc_throw(DataSourceError, "Bad name " + str + " from findPreviousName");
+ }
}
Name
@@ -956,7 +1159,7 @@ public:
// Find the SOA of the zone (may or may not succeed). Note that
// this must be done before starting the iteration context.
soa_ = DatabaseClient::Finder(accessor_, zone.second, zone_name).
- find(zone_name, RRType::SOA()).rrset;
+ find(zone_name, RRType::SOA())->rrset;
// Request the context
context_ = accessor_->getAllRecords(zone.second);
@@ -988,28 +1191,44 @@ public:
// At the end of zone
accessor_->commit();
ready_ = false;
- LOG_DEBUG(logger, DBG_TRACE_DETAILED,
- DATASRC_DATABASE_ITERATE_END);
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ITERATE_END);
return (ConstRRsetPtr());
}
- const string name_str(name_), rtype_str(rtype_), ttl(ttl_);
- const Name name(name_str);
- const RRType rtype(rtype_str);
- RRsetPtr rrset(new RRset(name, class_, rtype, RRTTL(ttl)));
- while (data_ready_ && name_ == name_str && rtype_str == rtype_) {
- if (ttl_ != ttl) {
- if (ttl < ttl_) {
- ttl_ = ttl;
- rrset->setTTL(RRTTL(ttl));
- }
- LOG_WARN(logger, DATASRC_DATABASE_ITERATE_TTL_MISMATCH).
- arg(name_).arg(class_).arg(rtype_).arg(rrset->getTTL());
- }
- rrset->addRdata(rdata::createRdata(rtype, class_, rdata_));
+ const RRType rtype(rtype_txt_);
+ RRsetPtr rrset(new RRset(Name(name_txt_), class_, rtype,
+ RRTTL(ttl_txt_)));
+ // Remember the first RDATA of the RRset for comparison:
+ const ConstRdataPtr rdata_base = rdata_;
+ while (true) {
+ // Extend the RRset with the new RDATA.
+ rrset->addRdata(rdata_);
+
+ // Retrieve the next record from the database. If we reach the
+ // end of the zone, done; if we were requested to separate all RRs,
+ // just remember this record and return the single RR.
getData();
- if (separate_rrs_) {
+ if (separate_rrs_ || !data_ready_) {
+ break;
+ }
+
+ // Check if the next record belongs to the same RRset. If not,
+ // we are done. The next RDATA has been stored in rdata_, which
+ // is used within this loop (if it belongs to the same RRset) or
+ // in the next call.
+ if (Name(name_txt_) != rrset->getName() ||
+ !isSameType(rtype, rdata_base, RRType(rtype_txt_), rdata_)) {
break;
}
+
+ // Adjust TTL if necessary
+ const RRTTL next_ttl(ttl_txt_);
+ if (next_ttl != rrset->getTTL()) {
+ if (next_ttl < rrset->getTTL()) {
+ rrset->setTTL(next_ttl);
+ }
+ LOG_WARN(logger, DATASRC_DATABASE_ITERATE_TTL_MISMATCH).
+ arg(name_txt_).arg(class_).arg(rtype).arg(rrset->getTTL());
+ }
}
LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ITERATE_NEXT).
arg(rrset->getName()).arg(rrset->getType());
@@ -1017,14 +1236,34 @@ public:
}
private:
+ // Check two RDATA types are equivalent. Basically it's a trivial
+ // comparison, but if both are of RRSIG, we should also compare the types
+ // covered.
+ static bool isSameType(RRType type1, ConstRdataPtr rdata1,
+ RRType type2, ConstRdataPtr rdata2)
+ {
+ if (type1 != type2) {
+ return (false);
+ }
+ if (type1 == RRType::RRSIG()) {
+ return (dynamic_cast<const generic::RRSIG&>(*rdata1).typeCovered()
+ == dynamic_cast<const generic::RRSIG&>(*rdata2).
+ typeCovered());
+ }
+ return (true);
+ }
+
// Load next row of data
void getData() {
string data[DatabaseAccessor::COLUMN_COUNT];
data_ready_ = context_->getNext(data);
- name_ = data[DatabaseAccessor::NAME_COLUMN];
- rtype_ = data[DatabaseAccessor::TYPE_COLUMN];
- ttl_ = data[DatabaseAccessor::TTL_COLUMN];
- rdata_ = data[DatabaseAccessor::RDATA_COLUMN];
+ if (data_ready_) {
+ name_txt_ = data[DatabaseAccessor::NAME_COLUMN];
+ rtype_txt_ = data[DatabaseAccessor::TYPE_COLUMN];
+ ttl_txt_ = data[DatabaseAccessor::TTL_COLUMN];
+ rdata_ = rdata::createRdata(RRType(rtype_txt_), class_,
+ data[DatabaseAccessor::RDATA_COLUMN]);
+ }
}
// The dedicated accessor
@@ -1038,10 +1277,12 @@ private:
// Status
bool ready_, data_ready_;
// Data of the next row
- string name_, rtype_, rdata_, ttl_;
+ string name_txt_, rtype_txt_, ttl_txt_;
+ // RDATA of the next row
+ ConstRdataPtr rdata_;
// Whether to modify differing TTL values, or treat a different TTL as
// a different RRset
- bool separate_rrs_;
+ const bool separate_rrs_;
};
}
@@ -1101,8 +1342,8 @@ public:
virtual ZoneFinder& getFinder() { return (*finder_); }
- virtual void addRRset(const RRset& rrset);
- virtual void deleteRRset(const RRset& rrset);
+ virtual void addRRset(const AbstractRRset& rrset);
+ virtual void deleteRRset(const AbstractRRset& rrset);
virtual void commit();
private:
@@ -1129,14 +1370,15 @@ private:
// This is a set of validation checks commonly used for addRRset() and
// deleteRRset to minimize duplicate code logic and to make the main
// code concise.
- void validateAddOrDelete(const char* const op_str, const RRset& rrset,
+ void validateAddOrDelete(const char* const op_str,
+ const AbstractRRset& rrset,
DiffPhase prev_phase,
DiffPhase current_phase) const;
};
void
DatabaseUpdater::validateAddOrDelete(const char* const op_str,
- const RRset& rrset,
+ const AbstractRRset& rrset,
DiffPhase prev_phase,
DiffPhase current_phase) const
{
@@ -1173,54 +1415,137 @@ DatabaseUpdater::validateAddOrDelete(const char* const op_str,
}
}
+// This is a helper class used in adding/deleting RRsets to/from a database.
+// The purpose of this class is to provide conversion interface from various
+// parameters of the RRset to corresponding textual representations that the
+// underlying database interface expects. The necessary parameters and how
+// to convert them depend on several things, such as whether it's NSEC3 related
+// or not, or whether journaling is requested. In order to avoid unnecessary
+// conversion, this class also performs the conversion in a lazy manner.
+// Also, in order to avoid redundant conversion when the conversion is
+// requested for the same parameter multiple times, it remembers the
+// conversion result first time, and reuses it for subsequent requests
+// (this implicitly assumes copying std::string objects is not very expensive;
+// this is often the case in some common implementations that have
+// copy-on-write semantics for the string class).
+class RRParameterConverter {
+public:
+ RRParameterConverter(const AbstractRRset& rrset) : rrset_(rrset)
+ {}
+ const string& getName() {
+ if (name_.empty()) {
+ name_ = rrset_.getName().toText();
+ }
+ return (name_);
+ }
+ const string& getNSEC3Name() {
+ if (nsec3_name_.empty()) {
+ nsec3_name_ = rrset_.getName().split(0, 1).toText(true);
+ }
+ return (nsec3_name_);
+ }
+ const string& getRevName() {
+ if (revname_.empty()) {
+ revname_ = rrset_.getName().reverse().toText();
+ }
+ return (revname_);
+ }
+ const string& getTTL() {
+ if (ttl_.empty()) {
+ ttl_ = rrset_.getTTL().toText();
+ }
+ return (ttl_);
+ }
+ const string& getType() {
+ if (type_.empty()) {
+ type_ = rrset_.getType().toText();
+ }
+ return (type_);
+ }
+
+private:
+ string name_;
+ string nsec3_name_;
+ string revname_;
+ string ttl_;
+ string type_;
+ const AbstractRRset& rrset_;
+};
+
+namespace {
+// A shared shortcut to detect if the given type of RDATA is NSEC3 or
+// RRSIG covering NSEC3. RRSIG for NSEC3 should go to the (conceptual)
+// separate namespace, so we need to check the covered type.
+// Note: in principle the type covered should be the same for
+// all RDATA, but the RRset interface doesn't ensure that condition.
+// So we explicitly check that for every RDATA below.
+bool
+isNSEC3KindType(RRType rrtype, const Rdata& rdata) {
+ if (rrtype == RRType::NSEC3()) {
+ return (true);
+ }
+ if (rrtype == RRType::RRSIG() &&
+ dynamic_cast<const generic::RRSIG&>(rdata).typeCovered() ==
+ RRType::NSEC3())
+ {
+ return (true);
+ }
+ return (false);
+}
+}
+
void
-DatabaseUpdater::addRRset(const RRset& rrset) {
+DatabaseUpdater::addRRset(const AbstractRRset& rrset) {
validateAddOrDelete("add", rrset, DELETE, ADD);
// It's guaranteed rrset has at least one RDATA at this point.
RdataIteratorPtr it = rrset.getRdataIterator();
-
- string columns[Accessor::ADD_COLUMN_COUNT]; // initialized with ""
- columns[Accessor::ADD_NAME] = rrset.getName().toText();
- columns[Accessor::ADD_REV_NAME] = rrset.getName().reverse().toText();
- columns[Accessor::ADD_TTL] = rrset.getTTL().toText();
- columns[Accessor::ADD_TYPE] = rrset.getType().toText();
- string journal[Accessor::DIFF_PARAM_COUNT];
if (journaling_) {
- journal[Accessor::DIFF_NAME] = columns[Accessor::ADD_NAME];
- journal[Accessor::DIFF_TYPE] = columns[Accessor::ADD_TYPE];
- journal[Accessor::DIFF_TTL] = columns[Accessor::ADD_TTL];
diff_phase_ = ADD;
if (rrset.getType() == RRType::SOA()) {
- serial_ =
- dynamic_cast<const generic::SOA&>(it->getCurrent()).
+ serial_ = dynamic_cast<const generic::SOA&>(it->getCurrent()).
getSerial();
}
}
+
+ RRParameterConverter cvtr(rrset);
for (; !it->isLast(); it->next()) {
+ const Rdata& rdata = it->getCurrent();
+ const bool nsec3_type = isNSEC3KindType(rrset.getType(), rdata);
+
+ string sigtype;
if (rrset.getType() == RRType::RRSIG()) {
// XXX: the current interface (based on the current sqlite3
// data source schema) requires a separate "sigtype" column,
// even though it won't be used in a newer implementation.
// We should eventually clean up the schema design and simplify
// the interface, but until then we have to conform to the schema.
- const generic::RRSIG& rrsig_rdata =
- dynamic_cast<const generic::RRSIG&>(it->getCurrent());
- columns[Accessor::ADD_SIGTYPE] =
- rrsig_rdata.typeCovered().toText();
+ sigtype = dynamic_cast<const generic::RRSIG&>(rdata).
+ typeCovered().toText();
}
- columns[Accessor::ADD_RDATA] = it->getCurrent().toText();
+ const string& rdata_txt = rdata.toText();
if (journaling_) {
- journal[Accessor::DIFF_RDATA] = columns[Accessor::ADD_RDATA];
+ const string journal[Accessor::DIFF_PARAM_COUNT] =
+ { cvtr.getName(), cvtr.getType(), cvtr.getTTL(), rdata_txt };
accessor_->addRecordDiff(zone_id_, serial_.getValue(),
Accessor::DIFF_ADD, journal);
}
- accessor_->addRecordToZone(columns);
+ if (nsec3_type) {
+ const string nsec3_columns[Accessor::ADD_NSEC3_COLUMN_COUNT] =
+ { cvtr.getNSEC3Name(), cvtr.getTTL(), cvtr.getType(),
+ rdata_txt };
+ accessor_->addNSEC3RecordToZone(nsec3_columns);
+ } else {
+ const string columns[Accessor::ADD_COLUMN_COUNT] =
+ { cvtr.getName(), cvtr.getRevName(), cvtr.getTTL(),
+ cvtr.getType(), sigtype, rdata_txt };
+ accessor_->addRecordToZone(columns);
+ }
}
}
void
-DatabaseUpdater::deleteRRset(const RRset& rrset) {
+DatabaseUpdater::deleteRRset(const AbstractRRset& rrset) {
// If this is the first operation, pretend we are starting a new delete
// sequence after adds. This will simplify the validation below.
if (diff_phase_ == NOT_STARTED) {
@@ -1230,15 +1555,7 @@ DatabaseUpdater::deleteRRset(const RRset& rrset) {
validateAddOrDelete("delete", rrset, ADD, DELETE);
RdataIteratorPtr it = rrset.getRdataIterator();
-
- string params[Accessor::DEL_PARAM_COUNT]; // initialized with ""
- params[Accessor::DEL_NAME] = rrset.getName().toText();
- params[Accessor::DEL_TYPE] = rrset.getType().toText();
- string journal[Accessor::DIFF_PARAM_COUNT];
if (journaling_) {
- journal[Accessor::DIFF_NAME] = params[Accessor::DEL_NAME];
- journal[Accessor::DIFF_TYPE] = params[Accessor::DEL_TYPE];
- journal[Accessor::DIFF_TTL] = rrset.getTTL().toText();
diff_phase_ = DELETE;
if (rrset.getType() == RRType::SOA()) {
serial_ =
@@ -1246,14 +1563,27 @@ DatabaseUpdater::deleteRRset(const RRset& rrset) {
getSerial();
}
}
+
+ RRParameterConverter cvtr(rrset);
for (; !it->isLast(); it->next()) {
- params[Accessor::DEL_RDATA] = it->getCurrent().toText();
+ const Rdata& rdata = it->getCurrent();
+ const bool nsec3_type = isNSEC3KindType(rrset.getType(), rdata);
+ const string& rdata_txt = it->getCurrent().toText();
+
if (journaling_) {
- journal[Accessor::DIFF_RDATA] = params[Accessor::DEL_RDATA];
+ const string journal[Accessor::DIFF_PARAM_COUNT] =
+ { cvtr.getName(), cvtr.getType(), cvtr.getTTL(), rdata_txt };
accessor_->addRecordDiff(zone_id_, serial_.getValue(),
Accessor::DIFF_DELETE, journal);
}
- accessor_->deleteRecordInZone(params);
+ const string params[Accessor::DEL_PARAM_COUNT] =
+ { nsec3_type ? cvtr.getNSEC3Name() : cvtr.getName(),
+ cvtr.getType(), rdata_txt };
+ if (nsec3_type) {
+ accessor_->deleteNSEC3RecordInZone(params);
+ } else {
+ accessor_->deleteRecordInZone(params);
+ }
}
}
diff --git a/src/lib/datasrc/database.h b/src/lib/datasrc/database.h
index fdbaa0a..8083322 100644
--- a/src/lib/datasrc/database.h
+++ b/src/lib/datasrc/database.h
@@ -26,7 +26,7 @@
#include <datasrc/data_source.h>
#include <datasrc/client.h>
-#include <datasrc/client.h>
+#include <datasrc/zone.h>
#include <datasrc/logger.h>
#include <dns/name.h>
@@ -95,13 +95,36 @@ public:
ADD_COLUMN_COUNT = 6 ///< Number of columns
};
+ /// \brief Definitions of the fields to be passed to addNSEC3RecordToZone()
+ ///
+ /// Each derived implementation of addNSEC3RecordToZone() should expect
+ /// the "columns" array to be filled with the values as described in this
+ /// enumeration, in this order.
+ ///
+ /// Note that there is no "reversed name" column. Since the conceptual
+ /// separate namespace for NSEC3 is very simplified and essentially only
+ /// consists of a single-label names, there is no need for using reversed
+ /// names to identify the "previous hash".
+ enum AddNSEC3RecordColumns {
+ ADD_NSEC3_HASH = 0, ///< The hash (1st) label of the owner name,
+ ///< excluding the dot character
+ ADD_NSEC3_TTL = 1, ///< The TTL of the record (in numeric form)
+ ADD_NSEC3_TYPE = 2, ///< The RRType of the record (either NSEC3 or
+ ///< RRSIG for NSEC3)
+ ADD_NSEC3_RDATA = 3, ///< Full text representation of the record's
+ ///< RDATA
+ ADD_NSEC3_COLUMN_COUNT = 4 ///< Number of columns
+ };
+
/// \brief Definitions of the fields to be passed to deleteRecordInZone()
+ /// and deleteNSEC3RecordInZone()
///
/// Each derived implementation of deleteRecordInZone() should expect
/// the "params" array to be filled with the values as described in this
/// enumeration, in this order.
enum DeleteRecordParams {
DEL_NAME = 0, ///< The owner name of the record (a domain name)
+ ///< or the hash label for deleteNSEC3RecordInZone()
DEL_TYPE = 1, ///< The RRType of the record (A/NS/TXT etc.)
DEL_RDATA = 2, ///< Full text representation of the record's RDATA
DEL_PARAM_COUNT = 3 ///< Number of parameters
@@ -234,6 +257,41 @@ public:
int id,
bool subdomains = false) const = 0;
+ /// \brief Creates an iterator context for the records of NSEC3 namespace
+ /// for the given hash
+ ///
+ /// Returns an Iteratorcontextptr that contains all the records of the given
+ /// hash in the NSEC3 namespace of the given zone.
+ ///
+ /// The implementation of the iterator that is returned may leave the
+ /// NAME_COLUMN column of the array passed to getNext() untouched,
+ /// as that name is easy to construct on the caller side (both the
+ /// hash and the name of the zone is known). The SIGTYPE_COLUMN can
+ /// be omitted as well, as it would be always empty for NSEC3 RRs or
+ /// contained "NSEC3" in case of RRSIG RRs.
+ ///
+ /// The iterator will contain both the NSEC3 records and the corresponding
+ /// RRSIGs, in arbitrary order.
+ ///
+ /// The iterator might be empty (containing no RRs) in case the zone is not
+ /// signed by NSEC3.
+ ///
+ /// \note In case there are multiple NSEC3 chains and they collide
+ /// (unlikely, but it can happen), this can return multiple NSEC3
+ /// records.
+ /// \exception any Since any implementaion can be used, the caller should
+ /// expect any exception to be thrown.
+ /// \exception isc::NotImplemented in case the database does not support
+ /// NSEC3
+ ///
+ /// \param hash The hash part of the NSEC3 name (eg. for a name of NSEC3
+ /// RKBUCQT8T78GV6QBCGBHCHC019LG73SJ.example.com., we the hash would be
+ /// RKBUCQT8T78GV6QBCGBHCHC019LG73SJ).
+ /// \param id The id of te zone, as returned from getZone().
+ /// \return Newly created iterator context. Must not be NULL.
+ virtual IteratorContextPtr getNSEC3Records(const std::string& hash,
+ int id) const = 0;
+
/// \brief Creates an iterator context for the whole zone.
///
/// Returns an IteratorContextPtr that contains all records of the
@@ -397,6 +455,46 @@ public:
virtual void addRecordToZone(
const std::string (&columns)[ADD_COLUMN_COUNT]) = 0;
+ /// \brief Add a single NSEC3-related record to the zone to be updated.
+ ///
+ /// This method is similar to \c addRecordToZone(), but is expected to
+ /// be only used for NSEC3 RRs or RRSIG RRs that cover NSEC3. In terms
+ /// of the DNS protocol, these types of RRs reside in a separate space
+ /// of the zone. While this interface does not mandate a specific way
+ /// of implementing the separate namespaces in the underlying database,
+ /// it would be more convenient for the underlying implementation if the
+ /// interfaces are separated; for example, the implementation does not
+ /// have to examine the given data to identify the appropriate namespace.
+ ///
+ /// An implementation may choose to skip providing this interface if the
+ /// zones managed by that data source are known to not support NSEC3.
+ /// In that case the implementation should throw the
+ /// \c isc::NotImplemented exception.
+ ///
+ /// Note that the \c ADD_NSEC3_HASH column of \c columns is expected to
+ /// store only the hash label, not the entire owner name. This is similar
+ /// to the \c hash parameter of \c getNSEC3Records().
+ ///
+ /// The RRs to be added using this method are expected to be limited to
+ /// NSEC3 or RRSIG RRs that cover NSEC3, but it's generally assumed to
+ /// be the caller's responsibility to ensure that; the implementation
+ /// is not required to check that condition. The result of adding
+ /// unexpected type of RRs (and the result of subsequent lookups) is
+ /// undefined.
+ ///
+ /// Other general notes for \c addRecordToZone() also apply to this
+ /// method.
+ ///
+ /// \exception DataSourceError Invalid call without starting a transaction,
+ /// or other internal database error.
+ /// \exception isc::NotImplemented in case the database does not support
+ /// NSEC3
+ ///
+ /// \param columns An array of strings that defines a record to be added
+ /// to the NSEC3 namespace of the zone.
+ virtual void addNSEC3RecordToZone(
+ const std::string (&columns)[ADD_NSEC3_COLUMN_COUNT]) = 0;
+
/// \brief Delete a single record from the zone to be updated.
///
/// This method provides a simple interface to delete a record
@@ -434,6 +532,31 @@ public:
virtual void deleteRecordInZone(
const std::string (¶ms)[DEL_PARAM_COUNT]) = 0;
+ /// \brief Delete a single NSEC3-related record from the zone to be
+ /// updated.
+ ///
+ /// This method is similar to \c deleteRecordInZone(), but is expected to
+ /// be only used for NSEC3 RRs or RRSIG RRs that cover NSEC3. The
+ /// relationship between these two methods is similar to that between
+ /// \c addRecordToZone() and \c addNSEC3RecordToZone(), and the same
+ /// notes apply to this method.
+ ///
+ /// This method uses the same set of parameters to specify the record
+ /// to be deleted as \c deleteRecordInZone(), but the \c DEL_NAME column
+ /// is expected to only store the hash label of the owner name.
+ /// This is the same as \c ADD_NSEC3_HASH column for
+ /// \c addNSEC3RecordToZone().
+ ///
+ /// \exception DataSourceError Invalid call without starting a transaction,
+ /// or other internal database error.
+ /// \exception isc::NotImplemented in case the database does not support
+ /// NSEC3
+ ///
+ /// \param params An array of strings that defines a record to be deleted
+ /// from the NSEC3 namespace of the zone.
+ virtual void deleteNSEC3RecordInZone(
+ const std::string (¶ms)[DEL_PARAM_COUNT]) = 0;
+
/// \brief Start a general transaction.
///
/// Each derived class version of this method starts a database
@@ -643,6 +766,34 @@ public:
/// apex of the zone).
virtual std::string findPreviousName(int zone_id,
const std::string& rname) const = 0;
+
+ /// \brief It returns the previous hash in the NSEC3 chain.
+ ///
+ /// This is used to find previous NSEC3 hashes, to find covering NSEC3 in
+ /// case none match exactly.
+ ///
+ /// In case a hash before the lowest or the lowest is provided,
+ /// this should return the largest one in the zone (NSEC3 needs a
+ /// wrap-around semantics).
+ ///
+ /// \param zone_id Specifies the zone to look into, as returned by getZone.
+ /// \param hash The hash to look before.
+ /// \return The nearest smaller hash than the provided one, or the largest
+ /// hash in the zone if something smaller or equal to the lowest one
+ /// is provided.
+ /// \note If the zone contains multiple NSEC3 chains, you should check that
+ /// the returned result contains the NSEC3 for correct parameters. If
+ /// not, query again and get something smaller - this will eventually
+ /// get to the correct one. This interface and semantics might change
+ /// in future.
+ ///
+ /// \throw DataSourceError if there's a problem with the database or if
+ /// this zone is not signed with NSEC3.
+ /// \throw NotImplemented if this database doesn't support NSEC3.
+ /// \throw anything else, as this might be any implementation.
+ virtual std::string findPreviousNSEC3Hash(int zone_id,
+ const std::string& hash)
+ const = 0;
};
/// \brief Concrete data source client oriented at database backends.
@@ -673,6 +824,7 @@ public:
DatabaseClient(isc::dns::RRClass rrclass,
boost::shared_ptr<DatabaseAccessor> accessor);
+
/// \brief Corresponding ZoneFinder implementation
///
/// The zone finder implementation for database data sources. Similarly
@@ -738,22 +890,30 @@ public:
/// \param type The RRType to find
/// \param options Options about how to search.
/// See ZoneFinder::FindOptions.
- virtual FindResult find(const isc::dns::Name& name,
- const isc::dns::RRType& type,
- const FindOptions options = FIND_DEFAULT);
+ virtual ZoneFinderContextPtr find(const isc::dns::Name& name,
+ const isc::dns::RRType& type,
+ const FindOptions options =
+ FIND_DEFAULT);
/// \brief Implementation of the ZoneFinder::findAll method.
///
/// In short, it is mostly the same thing as find, but it returns all
/// RRsets in the named node through the target parameter in successful
/// case. It acts the same in the unsuccessful one.
- virtual FindResult findAll(const isc::dns::Name& name,
- std::vector<isc::dns::ConstRRsetPtr>& target,
- const FindOptions options = FIND_DEFAULT);
+ virtual ZoneFinderContextPtr findAll(
+ const isc::dns::Name& name,
+ std::vector<isc::dns::ConstRRsetPtr>& target,
+ const FindOptions options = FIND_DEFAULT);
/// \brief Implementation of ZoneFinder::findPreviousName method.
virtual isc::dns::Name findPreviousName(const isc::dns::Name& query)
const;
+ /// Look for NSEC3 for proving (non)existence of given name.
+ ///
+ /// See documentation in \c Zone.
+ virtual FindNSEC3Result
+ findNSEC3(const isc::dns::Name& name, bool recursive);
+
/// \brief The zone ID
///
/// This function provides the stored zone ID as passed to the
@@ -774,11 +934,13 @@ public:
boost::shared_ptr<DatabaseAccessor> accessor_;
const int zone_id_;
const isc::dns::Name origin_;
+
/// \brief Shortcut name for the result of getRRsets
typedef std::pair<bool, std::map<dns::RRType, dns::RRsetPtr> >
FoundRRsets;
/// \brief Just shortcut for set of types
typedef std::set<dns::RRType> WantedTypes;
+
/// \brief Internal logit of find and findAll methods.
///
/// Most of their handling is in the "error" cases and delegations
@@ -788,10 +950,12 @@ public:
/// Parameters and behaviour is like of those combined together.
/// Unexpected parameters, like type != ANY and having the target, are
/// just that - unexpected and not checked.
- FindResult findInternal(const isc::dns::Name& name,
- const isc::dns::RRType& type,
- std::vector<isc::dns::ConstRRsetPtr>* target,
- const FindOptions options = FIND_DEFAULT);
+ ResultContext findInternal(const isc::dns::Name& name,
+ const isc::dns::RRType& type,
+ std::vector<isc::dns::ConstRRsetPtr>*
+ target,
+ const FindOptions options = FIND_DEFAULT);
+
/// \brief Searches database for RRsets of one domain.
///
/// This method scans RRs of single domain specified by name and
@@ -813,6 +977,9 @@ public:
/// ones requested by types. It also puts a NULL pointer under the
/// ANY type into the result, if it finds any RRs at all, to easy the
/// identification of success.
+ /// \param srcContext This can be set to non-NULL value to override the
+ /// iterator context used for obtaining the data. This can be used,
+ /// for example, to get data from the NSEC3 namespace.
/// \return A pair, where the first element indicates if the domain
/// contains any RRs at all (not only the requested, it may happen
/// this is set to true, but the second part is empty). The second
@@ -824,7 +991,122 @@ public:
FoundRRsets getRRsets(const std::string& name,
const WantedTypes& types, bool check_ns,
const std::string* construct_name = NULL,
- bool any = false);
+ bool any = false,
+ DatabaseAccessor::IteratorContextPtr srcContext =
+ DatabaseAccessor::IteratorContextPtr());
+
+ /// \brief DNSSEC related context for ZoneFinder::findInternal.
+ ///
+ /// This class is a helper for the ZoneFinder::findInternal method,
+ /// encapsulating DNSSEC related information and processing logic.
+ /// Specifically, it tells the finder whether the zone under search
+ /// is DNSSEC signed or not, and if it is, whether it's with NSEC or
+ /// with NSEC3. It also provides a RRset DNSSEC proof RRset for some
+ /// specific situations (in practice, this means an NSEC RRs for
+ /// negative proof when they are needed and expected).
+ ///
+ /// The purpose of this class is to keep the main finder implementation
+ /// unaware of DNSSEC related details. It's also intended to help
+ /// avoid unnecessary lookup for DNSSEC proof RRsets; this class
+ /// doesn't look into the DB for these RRsets unless it's known to
+ /// be needed. The same optimization could be implemented in the
+ /// main code, but it will result in duplicate similar code logic
+ /// and make the code more complicated. By encapsulating and unifying
+ /// the logic in a single separate class, we can keep the main
+ /// search logic readable.
+ class FindDNSSECContext {
+ public:
+ /// \brief Constructor for FindDNSSECContext class.
+ ///
+ /// This constructor doesn't involve any expensive operation such
+ /// as database lookups. It only initializes some internal
+ /// states (in a cheap way) and remembers if DNSSEC proof
+ /// is requested.
+ ///
+ /// \param finder The Finder for the findInternal that uses this
+ /// context.
+ /// \param options Find options given to the finder.
+ FindDNSSECContext(Finder& finder, const FindOptions options);
+
+ /// \brief Return DNSSEC related result flags for the context.
+ ///
+ /// This method returns a FindResultFlags value related to
+ /// DNSSEC, based on the context. If DNSSEC proof is requested
+ /// and the zone is signed with NSEC/NSEC3, it returns
+ /// RESULT_NSEC_SIGNED/RESULT_NSEC3_SIGNED, respectively;
+ /// otherwise it returns RESULT_DEFAULT. So the caller can simply
+ /// take a logical OR for the returned value of this method and
+ /// whatever other flags it's going to set, without knowing
+ /// DNSSEC specific information.
+ ///
+ /// If it's not yet identified whether and how the zone is DNSSEC
+ /// signed at the time of the call, it now detects that via
+ /// database lookups (if necessary). (And this is because why
+ /// this method cannot be a const member function).
+ ZoneFinder::FindResultFlags getResultFlags();
+
+ /// \brief Get DNSSEC negative proof for a given name.
+ ///
+ /// If the zone is considered NSEC-signed and the context
+ /// requested DNSSEC proofs, this method tries to find NSEC RRs
+ /// for the give name. If \c covering is true, it means a
+ /// "no name" proof is requested, so it calls findPreviousName on
+ /// the given name and extracts an NSEC record on the result;
+ /// otherwise it tries to get NSEC RRs for the given name. If
+ /// the NSEC is found, this method returns it; otherwise it returns
+ /// NULL.
+ ///
+ /// In all other cases this method simply returns NULL.
+ ///
+ /// \param name The name which the NSEC RRset belong to.
+ /// \param covering true if a covering NSEC is required; false if
+ /// a matching NSEC is required.
+ /// \return Any found DNSSEC proof RRset or NULL
+ isc::dns::ConstRRsetPtr getDNSSECRRset(
+ const isc::dns::Name& name, bool covering);
+
+ /// \brief Get DNSSEC negative proof for a given name.
+ ///
+ /// If the zone is considered NSEC-signed and the context
+ /// requested DNSSEC proofs, this method tries to find NSEC RRset
+ /// from the given set (\c found_set) and returns it if found;
+ /// in other cases this method simply returns NULL.
+ ///
+ /// \param found_set The RRset which may contain an NSEC RRset.
+ /// \return Any found DNSSEC proof RRset or NULL
+ isc::dns::ConstRRsetPtr getDNSSECRRset(const FoundRRsets&
+ found_set);
+
+ private:
+ /// \brief Returns whether the zone is signed with NSEC3.
+ ///
+ /// This method returns true if the zone for the finder that
+ /// uses this context is considered DNSSEC signed with NSEC3;
+ /// otherwise it returns false. If it's not yet detected,
+ /// this method now detects that via database lookups (if
+ /// necessary).
+ bool isNSEC3();
+
+ /// \brief Returns whether the zone is signed with NSEC.
+ ///
+ /// This is similar to isNSEC3(), but works for NSEC.
+ bool isNSEC();
+
+ /// \brief Probe into the database to see if/how the zone is
+ /// signed.
+ ///
+ /// This is a subroutine of isNSEC3() and isNSEC(), and performs
+ /// delayed database probe to detect whether the zone used by
+ /// the finder is DNSSEC signed, and if it is, with NSEC or NSEC3.
+ void probe();
+
+ DatabaseClient::Finder& finder_;
+ const bool need_dnssec_;
+
+ bool is_nsec3_;
+ bool is_nsec_;
+ bool probed_;
+ };
/// \brief Search result of \c findDelegationPoint().
///
@@ -928,7 +1210,8 @@ public:
/// \param target If the type happens to be ANY, it will insert all
/// the RRsets of the found name (if any is found) here instead
/// of being returned by the result.
- ///
+ /// \param dnssec_ctx The dnssec context, it is a DNSSEC wrapper for
+ /// find function.
/// \return Tuple holding the result of the search - the RRset of the
/// wildcard records matching the name, together with a status
/// indicating the match type (e.g. CNAME at the wildcard
@@ -936,11 +1219,12 @@ public:
/// success due to an exact match). Also returned if there
/// is no match is an indication as to whether there was an
/// NXDOMAIN or an NXRRSET.
- FindResult findWildcardMatch(
- const isc::dns::Name& name,
- const isc::dns::RRType& type, const FindOptions options,
- const DelegationSearchResult& dresult,
- std::vector<isc::dns::ConstRRsetPtr>* target);
+ ResultContext findWildcardMatch(const isc::dns::Name& name,
+ const isc::dns::RRType& type,
+ const FindOptions options,
+ const DelegationSearchResult& dresult,
+ std::vector<isc::dns::ConstRRsetPtr>*
+ target, FindDNSSECContext& dnssec_ctx);
/// \brief Handle matching results for name
///
@@ -973,20 +1257,23 @@ public:
/// it's NULL in the case of non wildcard match.
/// \param target When the query is any, this must be set to a vector
/// where the result will be stored.
- ///
+ /// \param dnssec_ctx The dnssec context, it is a DNSSEC wrapper for
+ /// find function.
+
/// \return Tuple holding the result of the search - the RRset of the
/// wildcard records matching the name, together with a status
/// indicating the match type (corresponding to the each of
/// the above 4 cases). The return value is intended to be
/// usable as a return value of the caller of this helper
/// method.
- FindResult findOnNameResult(const isc::dns::Name& name,
- const isc::dns::RRType& type,
- const FindOptions options,
- const bool is_origin,
- const FoundRRsets& found,
- const std::string* wildname,
- std::vector<isc::dns::ConstRRsetPtr>* target);
+ ResultContext findOnNameResult(const isc::dns::Name& name,
+ const isc::dns::RRType& type,
+ const FindOptions options,
+ const bool is_origin,
+ const FoundRRsets& found,
+ const std::string* wildname,
+ std::vector<isc::dns::ConstRRsetPtr>*
+ target, FindDNSSECContext& dnssec_ctx);
/// \brief Handle no match for name
///
@@ -1011,18 +1298,19 @@ public:
/// \param target If the query is for type ANY, the successfull result,
/// if there happens to be one, will be returned through the
/// parameter, as it doesn't fit into the result.
- ///
+ /// \param dnssec_ctx The dnssec context, it is a DNSSEC wrapper for
+ /// find function.
/// \return Tuple holding the result of the search - the RRset of the
/// wildcard records matching the name, together with a status
/// indicating the match type (e.g. CNAME at the wildcard
/// match, no RRs of the requested type at the wildcard,
/// success due to an exact match).
- FindResult findNoNameResult(const isc::dns::Name& name,
- const isc::dns::RRType& type,
- FindOptions options,
- const DelegationSearchResult& dresult,
- std::vector<isc::dns::ConstRRsetPtr>*
- target);
+ ResultContext findNoNameResult(const isc::dns::Name& name,
+ const isc::dns::RRType& type,
+ FindOptions options,
+ const DelegationSearchResult& dresult,
+ std::vector<isc::dns::ConstRRsetPtr>*
+ target, FindDNSSECContext& dnssec_ctx);
/// Logs condition and creates result
///
@@ -1045,12 +1333,13 @@ public:
///
/// \return FindResult object constructed from the code and rrset
/// arguments.
- FindResult logAndCreateResult(const isc::dns::Name& name,
- const std::string* wildname,
- const isc::dns::RRType& type,
- ZoneFinder::Result code,
- isc::dns::ConstRRsetPtr rrset,
- const isc::log::MessageID& log_id) const;
+ ResultContext logAndCreateResult(const isc::dns::Name& name,
+ const std::string* wildname,
+ const isc::dns::RRType& type,
+ ZoneFinder::Result code,
+ isc::dns::ConstRRsetPtr rrset,
+ const isc::log::MessageID& log_id,
+ FindResultFlags flags) const;
/// \brief Checks if something lives below this domain.
///
@@ -1062,13 +1351,6 @@ public:
/// \return true if the name has subdomains, false if not.
bool hasSubdomains(const std::string& name);
- /// \brief Get the NSEC covering a name.
- ///
- /// This one calls findPreviousName on the given name and extracts an
- /// NSEC record on the result. It handles various error cases. The
- /// method exists to share code present at more than one location.
- dns::ConstRRsetPtr findNSECCover(const dns::Name& name);
-
/// \brief Convenience type shortcut.
///
/// To find stuff in the result of getRRsets.
@@ -1143,3 +1425,7 @@ private:
}
#endif // __DATABASE_DATASRC_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/datasrc_config.h.pre.in b/src/lib/datasrc/datasrc_config.h.pre.in
index ff99601..9074df6 100644
--- a/src/lib/datasrc/datasrc_config.h.pre.in
+++ b/src/lib/datasrc/datasrc_config.h.pre.in
@@ -23,7 +23,7 @@ namespace datasrc {
/// such as memory_ds.so and sqlite3_ds.so are found. It is used by the
/// DataSourceClient loader if no absolute path is used and
/// B10_FROM_BUILD is not set in the environment.
-const char* const BACKEND_LIBRARY_PATH = "@@PKGLIBEXECDIR@@/";
+const char* const BACKEND_LIBRARY_PATH = "@@PKGLIBDIR@@/";
} // end namespace datasrc
} // end namespace isc
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index fd46896..a9870d6 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -16,6 +16,13 @@ $NAMESPACE isc::datasrc
# \brief Messages for the data source library
+% DATASRC_BAD_NSEC3_NAME NSEC3 record has a bad owner name '%1'
+The software refuses to load NSEC3 records into a wildcard domain or
+the owner name has two or more labels below the zone origin.
+It isn't explicitly forbidden, but no sane zone wouldn have such names
+for NSEC3. BIND 9 also refuses NSEC3 at wildcard, so this behavior is
+compatible with BIND 9.
+
% DATASRC_CACHE_CREATE creating the hotspot cache
This is a debug message issued during startup when the hotspot cache
is created.
@@ -68,6 +75,35 @@ The datasource tried to provide an NSEC proof that the named domain does not
exist, but the database backend doesn't support DNSSEC. No proof is included
in the answer as a result.
+% DATASRC_DATABASE_FINDNSEC3 Looking for NSEC3 for %1 in %2 mode
+Debug information. A search in an database data source for NSEC3 that
+matches or covers the given name is being started.
+
+% DATASRC_DATABASE_FINDNSEC3_COVER found a covering NSEC3 for %1: %2
+Debug information. An NSEC3 that covers the given name is found and
+being returned. The found NSEC3 RRset is also displayed.
+
+% DATASRC_DATABASE_FINDNSEC3_MATCH found a matching NSEC3 for %1 at label count %2: %3
+Debug information. An NSEC3 that matches (a possibly superdomain of)
+the given name is found and being returned. When the shown label
+count is smaller than that of the given name, the matching NSEC3 is
+for a superdomain of the given name (see DATASRC_DATABSE_FINDNSEC3_TRYHASH).
+The found NSEC3 RRset is also displayed.
+
+% DATASRC_DATABASE_FINDNSEC3_TRYHASH looking for NSEC3 for %1 at label count %2 (hash %3)
+Debug information. In an attempt of finding an NSEC3 for the give name,
+(a possibly superdomain of) the name is hashed and searched for in the
+NSEC3 name space. When the shown label count is smaller than that of the
+shown name, the search tries the superdomain name that share the shown
+(higher) label count of the shown name (e.g., for
+www.example.com. with shown label count of 3, example.com. is being
+tried, as "." is 1 label long).
+
+% DATASRC_DATABASE_FINDNSEC3_TRYHASH_PREV looking for previous NSEC3 for %1 at label count %2 (hash %3)
+Debug information. An exact match on hash (see
+DATASRC_DATABASE_FINDNSEC3_TRYHASH) was unsuccessful. We get the previous hash
+to that one instead.
+
% DATASRC_DATABASE_FIND_RECORDS looking in datasource %1 for record %2/%3/%4
Debug information. The database data source is looking up records with the given
name and type in the database.
@@ -138,10 +174,40 @@ While iterating through the zone, the program extracted next RRset from it.
The name and RRtype of the RRset is indicated in the message.
% DATASRC_DATABASE_ITERATE_TTL_MISMATCH TTL values differ for RRs of %1/%2/%3, setting to %4
-While iterating through the zone, the time to live for RRs of the given RRset
-were found to be different. This isn't allowed on the wire and is considered
-an error, so we set it to the lowest value we found (but we don't modify the
-database). The data in database should be checked and fixed.
+While iterating through the zone, the time to live for RRs of the
+given RRset were found to be different. Since an RRset cannot have
+multiple TTLs, we set it to the lowest value we found (but we don't
+modify the database). This is what the client would do when such RRs
+were given in a DNS response according to RFC2181. The data in
+database should be checked and fixed.
+
+% DATASRC_DATABASE_JOURNALREADER_END %1/%2 on %3 from %4 to %5
+This is a debug message indicating that the program (successfully)
+reaches the end of sequences of a zone's differences. The zone's name
+and class, database name, and the start and end serials are shown in
+the message.
+
+% DATASRC_DATABASE_JOURNALREADER_NEXT %1/%2 in %3/%4 on %5
+This is a debug message indicating that the program retrieves one
+difference in difference sequences of a zone and successfully converts
+it to an RRset. The zone's name and class, database name, and the
+name and RR type of the retrieved diff are shown in the message.
+
+% DATASRC_DATABASE_JOURNALREADER_START %1/%2 on %3 from %4 to %5
+This is a debug message indicating that the program starts reading
+a zone's difference sequences from a database-based data source. The
+zone's name and class, database name, and the start and end serials
+are shown in the message.
+
+% DATASRC_DATABASE_JOURNALREADR_BADDATA failed to convert a diff to RRset in %1/%2 on %3 between %4 and %5: %6
+This is an error message indicating that a zone's diff is broken and
+the data source library failed to convert it to a valid RRset. The
+most likely cause of this is that someone has manually modified the
+zone's diff in the database and inserted invalid data as a result.
+The zone's name and class, database name, and the start and end
+serials, and an additional detail of the error are shown in the
+message. The administrator should examine the diff in the database
+to find any invalid data and fix it.
% DATASRC_DATABASE_NO_MATCH not match for %2/%3/%4 in %1
No match (not even a wildcard) was found in the named data source for the given
@@ -297,6 +363,30 @@ should be followed. The requested domain is an apex of some zone.
% DATASRC_MEM_FIND find '%1/%2'
Debug information. A search for the requested RRset is being started.
+% DATASRC_MEM_FINDNSEC3 finding NSEC3 for %1, mode %2
+Debug information. A search in an in-memory data source for NSEC3 that
+matches or covers the given name is being started.
+
+% DATASRC_MEM_FINDNSEC3_COVER found a covering NSEC3 for %1: %2
+Debug information. An NSEC3 that covers the given name is found and
+being returned. The found NSEC3 RRset is also displayed.
+
+% DATASRC_MEM_FINDNSEC3_MATCH found a matching NSEC3 for %1 at label count %2: %3
+Debug information. An NSEC3 that matches (a possibly superdomain of)
+the given name is found and being returned. When the shown label
+count is smaller than that of the given name, the matching NSEC3 is
+for a superdomain of the given name (see DATASRC_MEM_FINDNSEC3_TRYHASH).
+The found NSEC3 RRset is also displayed.
+
+% DATASRC_MEM_FINDNSEC3_TRYHASH looking for NSEC3 for %1 at label count %2 (hash %3)
+Debug information. In an attempt of finding an NSEC3 for the give name,
+(a possibly superdomain of) the name is hashed and searched for in the
+NSEC3 name space. When the shown label count is smaller than that of the
+shown name, the search tries the superdomain name that share the shown
+(higher) label count of the shown name (e.g., for
+www.example.com. with shown label count of 3, example.com. is being
+tried).
+
% DATASRC_MEM_FIND_ZONE looking for zone '%1'
Debug information. A zone object for this zone is being searched for in the
in-memory data source.
@@ -307,6 +397,14 @@ Debug information. The content of master file is being loaded into the memory.
% DATASRC_MEM_NOT_FOUND requested domain '%1' not found
Debug information. The requested domain does not exist.
+% DATASRC_MEM_NO_NSEC3PARAM NSEC3PARAM is missing for NSEC3-signed zone %1/%2
+The in-memory data source has loaded a zone signed with NSEC3 RRs,
+but it doesn't have a NSEC3PARAM RR at the zone origin. It's likely that
+the zone is somehow broken, but this RR is not necessarily needed for
+handling lookups with NSEC3 in this data source, so it accepts the given
+content of the zone. Nevertheless the administrator should look into
+the integrity of the zone data.
+
% DATASRC_MEM_NS_ENCOUNTERED encountered a NS
Debug information. While searching for the requested domain, a NS was
encountered on the way (a delegation). This may lead to stop of the search.
@@ -333,10 +431,12 @@ Some resource types are singletons -- only one is allowed in a domain
% DATASRC_MEM_SUCCESS query for '%1/%2' successful
Debug information. The requested record was found.
-% DATASRC_MEM_SUPER_STOP stopped at superdomain '%1', domain '%2' is empty
-Debug information. The search stopped at a superdomain of the requested
-domain. The domain is an empty nonterminal, therefore it is treated as NXRRSET
-case (eg. the domain exists, but it doesn't have the requested record type).
+% DATASRC_MEM_SUPER_STOP stopped as '%1' is superdomain of a zone node, meaning it's empty
+Debug information. The search stopped because the requested domain was
+detected to be a superdomain of some existing node of zone (while there
+was no exact match). This means that the domain is an empty nonterminal,
+therefore it is treated as NXRRSET case (eg. the domain exists, but it
+doesn't have the requested record type).
% DATASRC_MEM_SWAP swapping contents of two zone representations ('%1' and '%2')
Debug information. The contents of two in-memory zones are being exchanged.
@@ -500,8 +600,10 @@ An attempt to add a NSEC3 record into the message failed, because the zone does
not have any DS record. This indicates problem with the provided data.
% DATASRC_QUERY_NO_ZONE no zone containing '%1' in class '%2'
-Lookup of domain failed because the data have no zone that contain the
-domain. Maybe someone sent a query to the wrong server for some reason.
+Debug information. Lookup of domain failed because the datasource
+has no zone that contains the domain. Maybe someone sent a query
+to the wrong server for some reason. This may also happen when
+looking in the datasource for addresses for NS records.
% DATASRC_QUERY_PROCESS processing query '%1/%2' in the '%3' class
Debug information. A sure query is being processed now.
@@ -516,7 +618,7 @@ The underlying data source failed to answer the query for referral information.
1 means some error, 2 is not implemented. The data source should have logged
the specific error already.
-% DATASRC_QUERY_RRSIG unable to answer RRSIG query
+% DATASRC_QUERY_RRSIG unable to answer RRSIG query for %1
The server is unable to answer a direct query for RRSIG type, but was asked
to do so.
@@ -563,6 +665,17 @@ enough information for it. The code is 1 for error, 2 for not implemented.
% DATASRC_SQLITE_CLOSE closing SQLite database
Debug information. The SQLite data source is closing the database file.
+% DATASRC_SQLITE_COMPATIBLE_VERSION database schema V%1.%2 not up to date (expecting V%3.%4) but is compatible
+The version of the SQLite3 database schema used to hold the zone data
+is not the latest one - the current version of BIND 10 was written
+with a later schema version in mind. However, the database is
+compatible with the current version of BIND 10, and BIND 10 will run
+without any problems.
+
+Consult the release notes for your version of BIND 10. Depending on
+the changes made to the database schema, it is possible that improved
+performance could result if the database were upgraded.
+
% DATASRC_SQLITE_CONNCLOSE Closing sqlite database
The database file is no longer needed and is being closed.
@@ -630,6 +743,14 @@ source.
The SQLite data source was asked to provide a NSEC3 record for given zone.
But it doesn't contain that zone.
+% DATASRC_SQLITE_INCOMPATIBLE_VERSION database schema V%1.%2 incompatible with version (V%3.%4) expected
+The version of the SQLite3 database schema used to hold the zone data
+is incompatible with the version expected by BIND 10. As a result,
+BIND 10 is unable to run using the database file as the data source.
+
+The database should be updated using the means described in the BIND
+10 documentation.
+
% DATASRC_SQLITE_NEWCONN SQLite3Database is being initialized
A wrapper object to hold database connection is being initialized.
@@ -671,66 +792,3 @@ data source.
% DATASRC_UNEXPECTED_QUERY_STATE unexpected query state
This indicates a programming error. An internal task of unknown type was
generated.
-
-% DATASRC_DATABASE_UPDATER_CREATED zone updater created for '%1/%2' on %3
-Debug information. A zone updater object is created to make updates to
-the shown zone on the shown backend database.
-
-% DATASRC_DATABASE_UPDATER_DESTROYED zone updater destroyed for '%1/%2' on %3
-Debug information. A zone updater object is destroyed, either successfully
-or after failure of, making updates to the shown zone on the shown backend
-database.
-
-%DATASRC_DATABASE_UPDATER_ROLLBACK zone updates roll-backed for '%1/%2' on %3
-A zone updater is being destroyed without committing the changes.
-This would typically mean the update attempt was aborted due to some
-error, but may also be a bug of the application that forgets committing
-the changes. The intermediate changes made through the updater won't
-be applied to the underlying database. The zone name, its class, and
-the underlying database name are shown in the log message.
-
-%DATASRC_DATABASE_UPDATER_ROLLBACKFAIL failed to roll back zone updates for '%1/%2' on %3: %4
-A zone updater is being destroyed without committing the changes to
-the database, and attempts to rollback incomplete updates, but it
-unexpectedly fails. The higher level implementation does not expect
-it to fail, so this means either a serious operational error in the
-underlying data source (such as a system failure of a database) or
-software bug in the underlying data source implementation. In either
-case if this message is logged the administrator should carefully
-examine the underlying data source to see what exactly happens and
-whether the data is still valid. The zone name, its class, and the
-underlying database name as well as the error message thrown from the
-database module are shown in the log message.
-
-% DATASRC_DATABASE_UPDATER_COMMIT updates committed for '%1/%2' on %3
-Debug information. A set of updates to a zone has been successfully
-committed to the corresponding database backend. The zone name,
-its class and the database name are printed.
-
-% DATASRC_DATABASE_JOURNALREADER_START %1/%2 on %3 from %4 to %5
-This is a debug message indicating that the program starts reading
-a zone's difference sequences from a database-based data source. The
-zone's name and class, database name, and the start and end serials
-are shown in the message.
-
-% DATASRC_DATABASE_JOURNALREADER_NEXT %1/%2 in %3/%4 on %5
-This is a debug message indicating that the program retrieves one
-difference in difference sequences of a zone and successfully converts
-it to an RRset. The zone's name and class, database name, and the
-name and RR type of the retrieved diff are shown in the message.
-
-% DATASRC_DATABASE_JOURNALREADER_END %1/%2 on %3 from %4 to %5
-This is a debug message indicating that the program (successfully)
-reaches the end of sequences of a zone's differences. The zone's name
-and class, database name, and the start and end serials are shown in
-the message.
-
-% DATASRC_DATABASE_JOURNALREADR_BADDATA failed to convert a diff to RRset in %1/%2 on %3 between %4 and %5: %6
-This is an error message indicating that a zone's diff is broken and
-the data source library failed to convert it to a valid RRset. The
-most likely cause of this is that someone has manually modified the
-zone's diff in the database and inserted invalid data as a result.
-The zone's name and class, database name, and the start and end
-serials, and an additional detail of the error are shown in the
-message. The administrator should examine the diff in the database
-to find any invalid data and fix it.
diff --git a/src/lib/datasrc/factory.h b/src/lib/datasrc/factory.h
index 9d0a762..f3ca397 100644
--- a/src/lib/datasrc/factory.h
+++ b/src/lib/datasrc/factory.h
@@ -163,7 +163,7 @@ public:
///
/// \return Reference to the DataSourceClient instance contained in this
/// container.
- DataSourceClient& getInstance() { return *instance_; }
+ DataSourceClient& getInstance() { return (*instance_); }
private:
DataSourceClient* instance_;
diff --git a/src/lib/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index d09eb6d..ea35cfa 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -12,37 +12,55 @@
// 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 <boost/foreach.hpp>
-
#include <exceptions/exceptions.h>
#include <dns/name.h>
+#include <dns/nsec3hash.h>
+#include <dns/rdataclass.h>
#include <dns/rrclass.h>
#include <dns/rrsetlist.h>
#include <dns/masterload.h>
#include <datasrc/memory_datasrc.h>
#include <datasrc/rbtree.h>
+#include <datasrc/rbnode_rrset.h>
#include <datasrc/logger.h>
#include <datasrc/iterator.h>
#include <datasrc/data_source.h>
#include <datasrc/factory.h>
-#include <cc/data.h>
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+
+#include <algorithm>
+#include <map>
+#include <utility>
+#include <cctype>
+#include <cassert>
using namespace std;
using namespace isc::dns;
-using namespace isc::data;
+using namespace isc::dns::rdata;
+using boost::scoped_ptr;
namespace isc {
namespace datasrc {
+using namespace internal;
+
namespace {
// Some type aliases
+
+// A functor type used for loading.
+typedef boost::function<void(ConstRRsetPtr)> LoadCallback;
+
+// RRset specified for this implementation
+typedef boost::shared_ptr<internal::RBNodeRRset> RBNodeRRsetPtr;
+typedef boost::shared_ptr<const internal::RBNodeRRset> ConstRBNodeRRsetPtr;
+
/*
* Each domain consists of some RRsets. They will be looked up by the
* RRType.
@@ -54,36 +72,714 @@ namespace {
* critical place and map has better interface for the lookups, so we use
* that.
*/
-typedef map<RRType, ConstRRsetPtr> Domain;
+typedef map<RRType, ConstRBNodeRRsetPtr> Domain;
typedef Domain::value_type DomainPair;
typedef boost::shared_ptr<Domain> DomainPtr;
// The tree stores domains
typedef RBTree<Domain> DomainTree;
typedef RBNode<Domain> DomainNode;
-}
-// Private data and hidden methods of InMemoryZoneFinder
-struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
- // Constructor
- InMemoryZoneFinderImpl(const RRClass& zone_class, const Name& origin) :
- zone_class_(zone_class), origin_(origin), origin_data_(NULL),
- domains_(true)
+// In the following dedicated namespace we define a few application-specific
+// RBNode flags. We use a separate namespace so we can consolidate the
+// definition in a single place, which would hopefully reduce the risk of
+// collisions.
+// (Note: it's within an unnamed namespace, so effectively private.)
+namespace domain_flag {
+// This flag indicates the node is at a "wildcard level" (in short, it means
+// one of the node's immediate child is a wildcard). See addWildcards()
+// for more details.
+const DomainNode::Flags WILD = DomainNode::FLAG_USER1;
+
+// This flag is used for additional record shortcut. If a node has this
+// flag, it's under a zone cut for a delegation to a child zone.
+// Note: for a statically built zone this information is stable, but if we
+// change the implementation to be dynamically modifiable, it may not be
+// realistic to keep this flag update for all affected nodes, and we may
+// have to reconsider the mechanism.
+const DomainNode::Flags GLUE = DomainNode::FLAG_USER2;
+
+// This flag indicates the node is generated as a result of wildcard
+// expansion. In this implementation, this flag can be set only in
+// the separate auxiliary tree of ZoneData (see the structure description).
+const DomainNode::Flags WILD_EXPANDED = DomainNode::FLAG_USER3;
+};
+
+// Separate storage for NSEC3 RRs (and their RRSIGs). It's an STL map
+// from string to the NSEC3 RRset. The map key is the first label
+// (upper cased) of the owner name of the corresponding NSEC3 (i.e., map
+// value). We can use the standard string comparison (if the comparison
+// target is also upper cased) due to the nature of NSEC3 owner names.
+//
+// Note: We maintain the RRsets in the form of RBNodeRRset even if they are
+// not stored in the RB tree. The reason is because comparison can be
+// more efficient if we make sure all RRsets returned from this module are
+// of the same type.
+typedef map<string, ConstRBNodeRRsetPtr> NSEC3Map;
+typedef NSEC3Map::value_type NSEC3Pair;
+
+// Actual zone data: Essentially a set of zone's RRs. This is defined as
+// a separate structure so that it'll be replaceable on reload.
+struct ZoneData {
+ ZoneData(const Name& origin) :
+ domains_(true),
+ origin_data_(NULL),
+ nsec_signed_(false)
{
// 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);
}
- static const DomainNode::Flags DOMAINFLAG_WILD = DomainNode::FLAG_USER1;
+
+ // The main data (name + RRsets)
+ DomainTree domains_;
+
+ // An auxiliary tree for wildcard expanded data used in additional data
+ // processing. It contains names like "ns.wild.example" in the following
+ // example:
+ // child.wild.example. NS ns.wild.example.
+ // *.wild.example IN AAAA 2001:db8::1234
+ // (and there's no exact ns.wild.example. in the zone). This tree contains
+ // such names with a copy of the RRsets of the matching wildcard name
+ // with its owner name expanded, e.g.:
+ // ns.wild.example. IN AAAA 2001:db8::1234
+ // In theory, this tree could have many such wildcard-expandable names,
+ // each of which has a copy of the original list of RRsets. In practice,
+ // however, it should be very rare that names for additional section
+ // processing are subject to wildcard expansion, so in most cases this tree
+ // should be even empty, and even if it has content it should be very
+ // small.
+private:
+ scoped_ptr<DomainTree> aux_wild_domains_;
+public:
+ DomainTree& getAuxWildDomains() {
+ if (!aux_wild_domains_) {
+ aux_wild_domains_.reset(new DomainTree);
+ }
+ return (*aux_wild_domains_);
+ }
+
+ // Shortcut to the origin node, which should always exist
+ DomainNode* origin_data_;
+
+ // The optional NSEC3 related data
+ struct NSEC3Data {
+ NSEC3Data(const generic::NSEC3PARAM& nsec3param) :
+ hash_(NSEC3Hash::create(nsec3param))
+ {}
+ NSEC3Data(const generic::NSEC3& nsec3) :
+ hash_(NSEC3Hash::create(nsec3))
+ {}
+ NSEC3Map map_; // Actual NSEC3 RRs
+ const scoped_ptr<NSEC3Hash> hash_; // hash parameter/calculator
+ };
+ scoped_ptr<NSEC3Data> nsec3_data_; // non NULL only when it's NSEC3 signed
+ bool nsec_signed_; // True if there's at least one NSEC record
+
+ // This templated structure encapsulates the find result of findNode()
+ // method (also templated) below.
+ // The template parameter is expected to be either 'const DomainNode' or
+ // 'DomainNode' (to avoid misuse the template definition itself is kept
+ // private - we only expose expected typedefs). The former is expected
+ // to be used for lookups, and the latter is expected to be used for
+ // constructing the zone.
+private:
+ template <typename NodeType>
+ struct FindNodeResultBase {
+ // Bitwise flags to represent supplemental information of the
+ // search result:
+ // Search resulted in a wildcard match.
+ static const unsigned int FIND_WILDCARD = 1;
+ // Search encountered a zone cut due to NS but continued to look for
+ // a glue.
+ static const unsigned int FIND_ZONECUT = 2;
+
+ FindNodeResultBase(ZoneFinder::Result code_param,
+ NodeType* node_param,
+ ConstRBNodeRRsetPtr rrset_param,
+ unsigned int flags_param = 0) :
+ code(code_param), node(node_param), rrset(rrset_param),
+ flags(flags_param)
+ {}
+ const ZoneFinder::Result code;
+ NodeType* const node;
+ ConstRBNodeRRsetPtr const rrset;
+ const unsigned int flags;
+ };
+public:
+ typedef FindNodeResultBase<const DomainNode> FindNodeResult;
+ typedef FindNodeResultBase<DomainNode> FindMutableNodeResult;
+
+ // Identify the RBTree node that best matches the given name.
+ // See implementation notes below.
+ template <typename ResultType>
+ ResultType findNode(const Name& name,
+ ZoneFinder::FindOptions options) const;
+};
+
+/// Maintain intermediate data specific to the search context used in
+/// \c find().
+///
+/// It will be passed to \c cutCallback() (see below) and record a possible
+/// zone cut node and related RRset (normally NS or DNAME).
+struct FindState {
+ FindState(bool glue_ok) :
+ zonecut_node_(NULL),
+ dname_node_(NULL),
+ glue_ok_(glue_ok)
+ {}
+
+ // These will be set to a domain node of the highest delegation point,
+ // if any. In fact, we could use a single variable instead of both.
+ // But then we would need to distinquish these two cases by something
+ // else and it seemed little more confusing when this was written.
+ const DomainNode* zonecut_node_;
+ const DomainNode* dname_node_;
+
+ // Delegation RRset (NS or DNAME), if found.
+ ConstRBNodeRRsetPtr rrset_;
+
+ // Whether to continue search below a delegation point.
+ // Set at construction time.
+ const bool glue_ok_;
+};
+
+// A callback called from possible zone cut nodes and nodes with DNAME.
+// This will be passed from findNode() to \c RBTree::find().
+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 found_dname(node.getData()->find(
+ RRType::DNAME()));
+ if (found_dname != node.getData()->end()) {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_DNAME_ENCOUNTERED);
+ state->dname_node_ = &node;
+ state->rrset_ = found_dname->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 found_ns(node.getData()->find(RRType::NS()));
+ if (found_ns != 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);
+ }
+
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_NS_ENCOUNTERED);
+
+ // 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_ = found_ns->second;
+
+ // Unless glue is allowed the search stops here, so we return
+ // false; otherwise return true to continue the search.
+ return (!state->glue_ok_);
+ }
+
+ // 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);
+}
+
+// Implementation notes: this method identifies an RBT node that best matches
+// the give name in terms of DNS query handling. In many cases,
+// DomainTree::find() will result in EXACTMATCH or PARTIALMATCH (note that
+// the given name is generally expected to be contained in the zone, so
+// even if it doesn't exist, it should at least match the zone origin).
+// If it finds an exact match, that's obviously the best one. The partial
+// match case is more complicated.
+//
+// We first need to consider the case where search hits a delegation point,
+// either due to NS or DNAME. They are indicated as either dname_node_ or
+// zonecut_node_ being non NULL. 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.
+//
+// Next, we need to check if the RBTree search stopped at a node for a
+// subdomain of the search name (so the comparison result that stopped the
+// search is "SUPERDOMAIN"), it means the stopping node is an empty
+// non-terminal node. In this case the search name is considered to exist
+// but no data should be found there.
+//
+// If none of above is the case, we then consider whether there's a matching
+// wildcard. DomainTree::find() records the node if it encounters a
+// "wildcarding" node, i.e., the immediate ancestor of a wildcard name
+// (e.g., wild.example.com for *.wild.example.com), and returns it if it
+// doesn't find any node that better matches the query name. In this case
+// we'll check if there's indeed a wildcard below the wildcarding node.
+//
+// Note, first, that 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).
+//
+// Before we try to find a wildcard, we should check whether there's
+// an existing node that would cancel the wildcard match. If
+// DomainTree::find() stopped at a node which has a common ancestor
+// with the query name, 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).
+//
+// If there's no node below the wildcarding node that shares a common ancestor
+// of the query name, we can conclude the wildcard is the best match.
+// We'll then identify the wildcard node via an incremental search. Note that
+// there's no possibility that the query name is at an empty non terminal
+// node below the wildcarding node at this stage; that case should have been
+// caught above.
+//
+// If none of the above succeeds, we conclude the name doesn't exist in
+// the zone.
+template <typename ResultType>
+ResultType
+ZoneData::findNode(const Name& name, ZoneFinder::FindOptions options) const {
+ DomainNode* node = NULL;
+ RBTreeNodeChain<Domain> node_path;
+ FindState state((options & ZoneFinder::FIND_GLUE_OK) != 0);
+
+ const DomainTree::Result result =
+ domains_.find(name, &node, node_path, cutCallback, &state);
+ const unsigned int zonecut_flag =
+ (state.zonecut_node_ != NULL) ? FindNodeResult::FIND_ZONECUT : 0;
+ if (result == DomainTree::EXACTMATCH) {
+ return (ResultType(ZoneFinder::SUCCESS, node, state.rrset_,
+ zonecut_flag));
+ } else if (result == DomainTree::PARTIALMATCH) {
+ assert(node != NULL);
+ if (state.dname_node_ != NULL) { // DNAME
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DNAME_FOUND).
+ arg(state.rrset_->getName());
+ return (ResultType(ZoneFinder::DNAME, NULL, state.rrset_));
+ }
+ if (state.zonecut_node_ != NULL) { // DELEGATION due to NS
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DELEG_FOUND).
+ arg(state.rrset_->getName());
+ return (ResultType(ZoneFinder::DELEGATION, NULL, state.rrset_));
+ }
+ if (node_path.getLastComparisonResult().getRelation() ==
+ NameComparisonResult::SUPERDOMAIN) { // empty node, so NXRRSET
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUPER_STOP).arg(name);
+ return (ResultType(ZoneFinder::NXRRSET, node,
+ ConstRBNodeRRsetPtr()));
+ }
+ if (node->getFlag(domain_flag::WILD)) { // maybe a wildcard
+ if (node_path.getLastComparisonResult().getRelation() ==
+ NameComparisonResult::COMMONANCESTOR &&
+ node_path.getLastComparisonResult().getCommonLabels() > 1) {
+ // Wildcard canceled. Treat it as NXDOMAIN.
+ // Note: 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).
+ LOG_DEBUG(logger, DBG_TRACE_DATA,
+ DATASRC_MEM_WILDCARD_CANCEL).arg(name);
+ return (ResultType(ZoneFinder::NXDOMAIN, NULL,
+ ConstRBNodeRRsetPtr()));
+ }
+ // Now the wildcard should be the best match.
+ const Name wildcard(Name("*").concatenate(
+ node_path.getAbsoluteName()));
+ DomainTree::Result result = domains_.find(wildcard, &node);
+ // Otherwise, why would the domain_flag::WILD be there if
+ // there was no wildcard under it?
+ assert(result == DomainTree::EXACTMATCH);
+ return (ResultType(ZoneFinder::SUCCESS, node, state.rrset_,
+ FindNodeResult::FIND_WILDCARD |
+ zonecut_flag));
+ }
+ // Nothing really matched.
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NOT_FOUND).arg(name);
+ return (ResultType(ZoneFinder::NXDOMAIN, node, state.rrset_));
+ } else {
+ // If the name is neither an exact or partial match, it is
+ // out of bailiwick, which is considered an error.
+ isc_throw(OutOfZone, name.toText() << " not in " <<
+ origin_data_->getName());
+ }
+}
+} // unnamed namespace
+
+namespace internal {
+
+/// \brief An encapsulation type for a pointer of an additional node
+/// associated with an \c RBNodeRRset object.
+///
+/// Currently this is defined as a structure only so that it can declared
+/// in rbnode_rrset.h; this is essentially a pointer to \c DomainNode.
+/// In future, however, this structure may have other attributes.
+struct AdditionalNodeInfo {
+ explicit AdditionalNodeInfo(DomainNode* node) : node_(node) {}
+ DomainNode* node_;
+};
+
+//
+// RBNodeRRset details
+//
+struct RBNodeRRsetImpl {
+public:
+ RBNodeRRsetImpl(const ConstRRsetPtr& rrset) : rrset_(rrset)
+ {}
+
+ ConstRRsetPtr rrset_; ///< Underlying RRset
+ scoped_ptr<vector<AdditionalNodeInfo> > additionals_;
+};
+
+RBNodeRRset::RBNodeRRset(const ConstRRsetPtr& rrset) :
+ impl_(new RBNodeRRsetImpl(rrset))
+{
+}
+
+RBNodeRRset::~RBNodeRRset() {
+ delete impl_;
+}
+
+unsigned int
+RBNodeRRset::getRdataCount() const {
+ return (impl_->rrset_->getRdataCount());
+}
+
+const Name&
+RBNodeRRset::getName() const {
+ return (impl_->rrset_->getName());
+}
+
+const RRClass&
+RBNodeRRset::getClass() const {
+ return (impl_->rrset_->getClass());
+}
+
+const RRType&
+RBNodeRRset::getType() const {
+ return (impl_->rrset_->getType());
+}
+
+const RRTTL&
+RBNodeRRset::getTTL() const {
+ return (impl_->rrset_->getTTL());
+}
+
+void
+RBNodeRRset::setName(const Name&) {
+ isc_throw(isc::NotImplemented, "RBNodeRRset::setName() not supported");
+}
+
+void
+RBNodeRRset::setTTL(const RRTTL&) {
+ isc_throw(isc::NotImplemented, "RBNodeRRset::setTTL() not supported");
+}
+
+string
+RBNodeRRset::toText() const {
+ return (impl_->rrset_->toText());
+}
+
+unsigned int
+RBNodeRRset::toWire(AbstractMessageRenderer& renderer) const {
+ return (impl_->rrset_->toWire(renderer));
+}
+
+unsigned int
+RBNodeRRset::toWire(isc::util::OutputBuffer& buffer) const {
+ return (impl_->rrset_->toWire(buffer));
+}
+
+void
+RBNodeRRset::addRdata(ConstRdataPtr) {
+ isc_throw(isc::NotImplemented, "RBNodeRRset::addRdata() not supported");
+}
+
+void
+RBNodeRRset::addRdata(const Rdata&) {
+ isc_throw(isc::NotImplemented, "RBNodeRRset::addRdata() not supported");
+}
+
+RdataIteratorPtr
+RBNodeRRset::getRdataIterator() const {
+ return (impl_->rrset_->getRdataIterator());
+}
+
+RRsetPtr
+RBNodeRRset::getRRsig() const {
+ return (impl_->rrset_->getRRsig());
+}
+
+void
+RBNodeRRset::addRRsig(const ConstRdataPtr& rdata) {
+ AbstractRRset* p = const_cast<AbstractRRset*>(impl_->rrset_.get());
+ p->addRRsig(rdata);
+}
+
+void
+RBNodeRRset::addRRsig(const RdataPtr& rdata) {
+ AbstractRRset* p = const_cast<AbstractRRset*>(impl_->rrset_.get());
+ p->addRRsig(rdata);
+}
+
+void
+RBNodeRRset::addRRsig(const AbstractRRset& sigs) {
+ AbstractRRset* p = const_cast<AbstractRRset*>(impl_->rrset_.get());
+ p->addRRsig(sigs);
+}
+
+void
+RBNodeRRset::addRRsig(const ConstRRsetPtr& sigs) {
+ AbstractRRset* p = const_cast<AbstractRRset*>(impl_->rrset_.get());
+ p->addRRsig(sigs);
+}
+
+void
+RBNodeRRset::addRRsig(const RRsetPtr& sigs) {
+ AbstractRRset* p = const_cast<AbstractRRset*>(impl_->rrset_.get());
+ p->addRRsig(sigs);
+}
+
+void
+RBNodeRRset::removeRRsig() {
+ AbstractRRset* p = const_cast<AbstractRRset*>(impl_->rrset_.get());
+ p->removeRRsig();
+}
+
+ConstRRsetPtr
+RBNodeRRset::getUnderlyingRRset() const {
+ return (impl_->rrset_);
+}
+
+void
+RBNodeRRset::addAdditionalNode(const AdditionalNodeInfo& additional) {
+ // Lazy initialization
+ if (!impl_->additionals_) {
+ impl_->additionals_.reset(new vector<AdditionalNodeInfo>);
+ }
+ impl_->additionals_->push_back(additional);
+}
+
+const vector<AdditionalNodeInfo>*
+RBNodeRRset::getAdditionalNodes() const {
+ return (impl_->additionals_.get());
+}
+
+void
+RBNodeRRset::copyAdditionalNodes(RBNodeRRset& dst) const {
+ if (impl_->additionals_) {
+ dst.impl_->additionals_.reset(
+ new vector<AdditionalNodeInfo>(impl_->additionals_->begin(),
+ impl_->additionals_->end()));
+ }
+}
+
+} // end of internal
+
+namespace {
+/*
+ * 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.
+ * In addition, if DNSSEC records are required by the original caller of
+ * find(), it also creates expanded RRSIG based on the RRSIG of the
+ * wildcard RRset.
+ * It is designed for wildcard case, where we create the rrsets
+ * dynamically.
+ */
+ConstRBNodeRRsetPtr
+prepareRRset(const Name& name, const ConstRBNodeRRsetPtr& rrset, bool rename,
+ ZoneFinder::FindOptions options)
+{
+ if (rename) {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_RENAME).
+ arg(rrset->getName()).arg(name);
+ RRsetPtr result_base(new RRset(name, rrset->getClass(),
+ rrset->getType(), rrset->getTTL()));
+ for (RdataIteratorPtr i(rrset->getRdataIterator()); !i->isLast();
+ i->next()) {
+ result_base->addRdata(i->getCurrent());
+ }
+ if ((options & ZoneFinder::FIND_DNSSEC) != 0) {
+ ConstRRsetPtr sig_rrset = rrset->getRRsig();
+ if (sig_rrset) {
+ RRsetPtr result_sig(new RRset(name, sig_rrset->getClass(),
+ RRType::RRSIG(),
+ sig_rrset->getTTL()));
+ for (RdataIteratorPtr i(sig_rrset->getRdataIterator());
+ !i->isLast();
+ i->next())
+ {
+ result_sig->addRdata(i->getCurrent());
+ }
+ result_base->addRRsig(result_sig);
+ }
+ }
+ RBNodeRRsetPtr result(new RBNodeRRset(result_base));
+ rrset->copyAdditionalNodes(*result);
+ return (result);
+ } else {
+ return (rrset);
+ }
+}
+
+// Specialized version of ZoneFinder::ResultContext, which specifically
+// holds rrset in the form of RBNodeRRset.
+struct RBNodeResultContext {
+ /// \brief Constructor
+ ///
+ /// The first three parameters correspond to those of
+ /// ZoneFinder::ResultContext. If node is non NULL, it specifies the
+ /// found RBNode in the search.
+ RBNodeResultContext(ZoneFinder::Result code_param,
+ ConstRBNodeRRsetPtr rrset_param,
+ ZoneFinder::FindResultFlags flags_param,
+ const DomainNode* node) :
+ code(code_param), rrset(rrset_param), flags(flags_param),
+ found_node(node)
+ {}
+
+ const ZoneFinder::Result code;
+ const ConstRBNodeRRsetPtr rrset;
+ const ZoneFinder::FindResultFlags flags;
+ const DomainNode* const found_node;
+};
+}
+
+class InMemoryZoneFinder::Context : public ZoneFinder::Context {
+public:
+ /// \brief Constructor.
+ ///
+ /// Note that we don't have a specific constructor for the findAll() case.
+ /// For (successful) type ANY query, found_node points to the
+ /// corresponding RB node, which is recorded within this specialized
+ /// context.
+ Context(ZoneFinder& finder, ZoneFinder::FindOptions options,
+ const RBNodeResultContext& result) :
+ ZoneFinder::Context(finder, options,
+ ResultContext(result.code, result.rrset,
+ result.flags)),
+ rrset_(result.rrset), found_node_(result.found_node)
+ {}
+
+protected:
+ virtual void getAdditionalImpl(const vector<RRType>& requested_types,
+ vector<ConstRRsetPtr>& result)
+ {
+ if (!rrset_) {
+ // In this case this context should encapsulate the result of
+ // findAll() and found_node_ should point to a valid answer node.
+ if (found_node_ == NULL || found_node_->isEmpty()) {
+ isc_throw(isc::Unexpected,
+ "Invalid call to in-memory getAdditional: caller's "
+ "bug or broken zone");
+ }
+ BOOST_FOREACH(const DomainPair& dom_it, *found_node_->getData()) {
+ getAdditionalForRRset(*dom_it.second, requested_types,
+ result);
+ }
+ } else {
+ getAdditionalForRRset(*rrset_, requested_types, result);
+ }
+ }
+
+private:
+ // Retrieve additional RRsets for a given RRset associated in the context.
+ // The process is straightforward: it examines the link to
+ // AdditionalNodeInfo vector (if set), and find RRsets of the requested
+ // type for each node.
+ static void getAdditionalForRRset(const RBNodeRRset& rrset,
+ const vector<RRType>& requested_types,
+ vector<ConstRRsetPtr>& result)
+ {
+ const vector<AdditionalNodeInfo>* additionals_ =
+ rrset.getAdditionalNodes();
+ if (additionals_ == NULL) {
+ return;
+ }
+ const bool glue_ok = (rrset.getType() == RRType::NS());
+ BOOST_FOREACH(const AdditionalNodeInfo& additional, *additionals_) {
+ assert(additional.node_ != NULL);
+ if (additional.node_->isEmpty()) {
+ continue;
+ }
+ if (!glue_ok && additional.node_->getFlag(domain_flag::GLUE)) {
+ continue;
+ }
+ const bool wild_expanded =
+ additional.node_->getFlag(domain_flag::WILD_EXPANDED);
+ BOOST_FOREACH(const RRType& rrtype, requested_types) {
+ Domain::const_iterator found =
+ additional.node_->getData()->find(rrtype);
+ if (found != additional.node_->getData()->end()) {
+ // If the additional node was generated as a result of
+ // wildcard expansion, we return the underlying RRset,
+ // in case the caller has the same RRset but as a result
+ // of normal find() and needs to know they are of the same
+ // kind; otherwise we simply use the stored RBNodeRRset.
+ if (wild_expanded) {
+ result.push_back(found->second->getUnderlyingRRset());
+ } else {
+ result.push_back(found->second);
+ }
+ }
+ }
+ }
+ }
+
+ const ConstRBNodeRRsetPtr rrset_;
+ const DomainNode* const found_node_;
+};
+
+// Private data and hidden methods of InMemoryZoneFinder
+struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
+ // Constructor
+ InMemoryZoneFinderImpl(const RRClass& zone_class, const Name& origin) :
+ zone_class_(zone_class), origin_(origin),
+ zone_data_(new ZoneData(origin_))
+ {}
// Information about the zone
RRClass zone_class_;
Name origin_;
- DomainNode* origin_data_;
string file_name_;
// The actual zone data
- DomainTree domains_;
+ scoped_ptr<ZoneData> zone_data_;
+
+ // Common process for zone load.
+ // rrset_installer is a functor that takes another functor as an argument,
+ // and expected to call the latter for each RRset of the zone. How the
+ // sequence of the RRsets is generated depends on the internal
+ // details of the loader: either from a textual master file or from
+ // another data source.
+ // filename is the file name of the master file or empty if the zone is
+ // loaded from another data source.
+ void load(const string& filename,
+ boost::function<void(LoadCallback)> rrset_installer);
// Add the necessary magic for any wildcard contained in 'name'
// (including itself) to be found in the zone.
@@ -115,7 +811,7 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
&node));
assert(result == DomainTree::SUCCESS ||
result == DomainTree::ALREADYEXISTS);
- node->setFlag(DOMAINFLAG_WILD);
+ node->setFlag(domain_flag::WILD);
// Ensure a separate level exists for the wildcard name.
// Note: for 'name' itself we do this later anyway, but the
@@ -128,6 +824,12 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
}
}
+ // A helper predicate used in contextCheck() to check if a given domain
+ // name has a RRset of type different than NSEC.
+ static bool isNotNSEC(const DomainPair& element) {
+ return (element.second->getType() != RRType::NSEC());
+ }
+
/*
* Does some checks in context of the data that are already in the zone.
* Currently checks for forbidden combinations of RRsets in the same
@@ -135,24 +837,23 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
*
* If such condition is found, it throws AddError.
*/
- void contextCheck(const ConstRRsetPtr& rrset,
- const DomainPtr& domain) const {
+ void contextCheck(const AbstractRRset& rrset, const Domain& domain) const {
// Ensure CNAME and other type of RR don't coexist for the same
- // owner name.
- if (rrset->getType() == RRType::CNAME()) {
- // TODO: 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()) {
+ // owner name except with NSEC, which is the only RR that can coexist
+ // with CNAME (and also RRSIG, which is handled separately)
+ if (rrset.getType() == RRType::CNAME()) {
+ if (find_if(domain.begin(), domain.end(), isNotNSEC)
+ != domain.end()) {
LOG_ERROR(logger, DATASRC_MEM_CNAME_TO_NONEMPTY).
- arg(rrset->getName());
+ arg(rrset.getName());
isc_throw(AddError, "CNAME can't be added with other data for "
- << rrset->getName());
+ << rrset.getName());
}
- } else if (domain->find(RRType::CNAME()) != domain->end()) {
- LOG_ERROR(logger, DATASRC_MEM_CNAME_COEXIST).arg(rrset->getName());
- isc_throw(AddError, "CNAME and " << rrset->getType() <<
- " can't coexist for " << rrset->getName());
+ } else if (rrset.getType() != RRType::NSEC() &&
+ domain.find(RRType::CNAME()) != domain.end()) {
+ LOG_ERROR(logger, DATASRC_MEM_CNAME_COEXIST).arg(rrset.getName());
+ isc_throw(AddError, "CNAME and " << rrset.getType() <<
+ " can't coexist for " << rrset.getName());
}
/*
@@ -160,17 +861,17 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
* non-apex domains.
* RFC 2672 section 3 mentions that it is implied from it and RFC 2181
*/
- if (rrset->getName() != origin_ &&
+ if (rrset.getName() != origin_ &&
// Adding DNAME, NS already there
- ((rrset->getType() == RRType::DNAME() &&
- domain->find(RRType::NS()) != domain->end()) ||
+ ((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())))
+ (rrset.getType() == RRType::NS() &&
+ domain.find(RRType::DNAME()) != domain.end())))
{
- LOG_ERROR(logger, DATASRC_MEM_DNAME_NS).arg(rrset->getName());
+ LOG_ERROR(logger, DATASRC_MEM_DNAME_NS).arg(rrset.getName());
isc_throw(AddError, "DNAME can't coexist with NS in non-apex "
- "domain " << rrset->getName());
+ "domain " << rrset.getName());
}
}
@@ -181,8 +882,12 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
if (!rrset) {
isc_throw(NullRRset, "The rrset provided is NULL");
}
+ if (rrset->getRdataCount() == 0) {
+ isc_throw(AddError, "The rrset provided is empty: " <<
+ rrset->getName() << "/" << rrset->getType());
+ }
// Check for singleton RRs. It should probably handled at a different
- // in future.
+ // layer in future.
if ((rrset->getType() == RRType::CNAME() ||
rrset->getType() == RRType::DNAME()) &&
rrset->getRdataCount() > 1)
@@ -195,6 +900,14 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
isc_throw(AddError, "multiple RRs of singleton type for "
<< rrset->getName());
}
+ // NSEC3/NSEC3PARAM is not a "singleton" per protocol, but this
+ // implementation requests it be so at the moment.
+ if ((rrset->getType() == RRType::NSEC3() ||
+ rrset->getType() == RRType::NSEC3PARAM()) &&
+ rrset->getRdataCount() > 1) {
+ isc_throw(AddError, "Multiple NSEC3/NSEC3PARAM RDATA is given for "
+ << rrset->getName() << " which isn't supported");
+ }
NameComparisonResult compare(origin_.compare(rrset->getName()));
if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN &&
@@ -228,6 +941,132 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
rrset->getName());
}
}
+
+ // Owner names of NSEC3 have special format as defined in RFC5155,
+ // and cannot be a wildcard name or must be one label longer than
+ // the zone origin. While the RFC doesn't prohibit other forms of
+ // names, no sane zone would have such names for NSEC3.
+ // BIND 9 also refuses NSEC3 at wildcard.
+ if (rrset->getType() == RRType::NSEC3() &&
+ (rrset->getName().isWildcard() ||
+ rrset->getName().getLabelCount() !=
+ origin_.getLabelCount() + 1)) {
+ LOG_ERROR(logger, DATASRC_BAD_NSEC3_NAME).
+ arg(rrset->getName());
+ isc_throw(AddError, "Invalid NSEC3 owner name: " <<
+ rrset->getName());
+ }
+ }
+
+ result::Result addRRsig(const ConstRRsetPtr sig_rrset, ZoneData& zone_data)
+ {
+ // Check consistency of the type covered.
+ // We know the RRset isn't empty, so the following check is safe.
+ RdataIteratorPtr rit = sig_rrset->getRdataIterator();
+ const RRType covered = dynamic_cast<const generic::RRSIG&>(
+ rit->getCurrent()).typeCovered();
+ for (rit->next(); !rit->isLast(); rit->next()) {
+ if (dynamic_cast<const generic::RRSIG&>(
+ rit->getCurrent()).typeCovered() != covered) {
+ isc_throw(AddError, "RRSIG contains mixed covered types: "
+ << sig_rrset->toText());
+ }
+ }
+
+ // Find the RRset to be covered; if not found, treat it as an error
+ // for now.
+ ConstRRsetPtr covered_rrset;
+ if (covered != RRType::NSEC3()) {
+ DomainNode* node = NULL;
+ if (zone_data.domains_.find(sig_rrset->getName(), &node) !=
+ DomainTree::EXACTMATCH || node == NULL || !node->getData()) {
+ isc_throw(AddError,
+ "RRSIG is being added, but no RR to be covered: "
+ << sig_rrset->getName());
+ }
+ const Domain::const_iterator it = node->getData()->find(covered);
+ if (it != node->getData()->end()) {
+ covered_rrset = it->second;
+ }
+ } else {
+ // In case of NSEC3 if something is found it must be NSEC3 RRset
+ // under the assumption of our current implementation.
+ if (zone_data.nsec3_data_) {
+ // Convert the first label to upper-cased text. Note that
+ // for a valid NSEC3 RR the label should only consist of
+ // positive 8-bit char values, so using toupper(int) should be
+ // safe (if it's a bogus label for NSEC3 the zone won't work
+ // anyway). Also note the '::' below: g++'s STL implementation
+ // seems to require it to toupper to make this compile.
+ string fst_label =
+ sig_rrset->getName().split(0, 1).toText(true);
+ transform(fst_label.begin(), fst_label.end(),
+ fst_label.begin(), ::toupper);
+
+ NSEC3Map::const_iterator found =
+ zone_data.nsec3_data_->map_.find(fst_label);
+ if (found != zone_data.nsec3_data_->map_.end()) {
+ covered_rrset = found->second;
+ assert(covered_rrset->getType() == covered);
+ }
+ }
+ }
+ if (!covered_rrset) {
+ isc_throw(AddError, "RRSIG is being added, but no RR of "
+ "covered type found: " << sig_rrset->toText());
+ }
+
+ // The current implementation doesn't allow an existing RRSIG to be
+ // overridden (or updated with additional ones).
+ if (covered_rrset->getRRsig()) {
+ isc_throw(AddError,
+ "RRSIG is being added to override an existing one: "
+ << sig_rrset->toText());
+ }
+
+ // All okay, setting the RRSIG.
+ // XXX: we break const-ness of the covered RRsets. In practice the
+ // ownership of these RRsets would have been given to us so it should
+ // be safe, but it's still a very bad practice.
+ // We'll fix this problem anyway when we update the underlying
+ // representation so that it's more space efficient.
+ // Note: there's a slight chance of getting an exception.
+ // As noted in add(), we give up strong exception guarantee in such
+ // cases.
+ boost::const_pointer_cast<AbstractRRset>(covered_rrset)->addRRsig(sig_rrset);
+
+ return (result::SUCCESS);
+ }
+
+ result::Result addNSEC3(const ConstRRsetPtr rrset, ZoneData& zone_data) {
+ // We know rrset has exactly one RDATA
+ const generic::NSEC3& nsec3_rdata =
+ dynamic_cast<const generic::NSEC3&>(
+ rrset->getRdataIterator()->getCurrent());
+
+ // If we've not done any NSEC3 setup for the zone, do it now;
+ // otherwise check parameter consistency.
+ if (!zone_data.nsec3_data_) {
+ zone_data.nsec3_data_.reset(new ZoneData::NSEC3Data(nsec3_rdata));
+ } else if (!zone_data.nsec3_data_->hash_->match(nsec3_rdata)) {
+ isc_throw(AddError, "NSEC3 with inconsistent parameters: " <<
+ rrset->toText());
+ }
+
+ string fst_label = rrset->getName().split(0, 1).toText(true);
+ transform(fst_label.begin(), fst_label.end(), fst_label.begin(),
+ ::toupper);
+
+ // Our current implementation doesn't allow an existing NSEC3 to be
+ // updated/overridden.
+ if (zone_data.nsec3_data_->map_.find(fst_label) !=
+ zone_data.nsec3_data_->map_.end()) {
+ return (result::EXIST);
+ }
+
+ zone_data.nsec3_data_->map_.insert(
+ NSEC3Pair(fst_label, ConstRBNodeRRsetPtr(new RBNodeRRset(rrset))));
+ return (result::SUCCESS);
}
/*
@@ -235,24 +1074,42 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
* access is without the impl_-> and it will get inlined anyway.
*/
// Implementation of InMemoryZoneFinder::add
- result::Result add(const ConstRRsetPtr& rrset, DomainTree* domains) {
+ result::Result add(const ConstRRsetPtr& rawrrset, ZoneData& zone_data,
+ vector<RBNodeRRset*>* need_additionals)
+ {
// Sanitize input. This will cause an exception to be thrown
// if the input RRset is empty.
- addValidation(rrset);
+ addValidation(rawrrset);
// OK, can add the RRset.
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ADD_RRSET).
- arg(rrset->getName()).arg(rrset->getType()).arg(origin_);
+ arg(rawrrset->getName()).arg(rawrrset->getType()).arg(origin_);
+
+ // ... although instead of loading the RRset directly, we encapsulate
+ // it within an RBNodeRRset. This contains additional information that
+ // speeds up queries.
+ RBNodeRRsetPtr rrset(new RBNodeRRset(rawrrset));
+
+ if (rrset->getType() == RRType::NSEC3()) {
+ return (addNSEC3(rrset, zone_data));
+ }
+
+ // RRSIGs are special in various points, so we handle it in a
+ // separate dedicated method.
+ if (rrset->getType() == RRType::RRSIG()) {
+ return (addRRsig(rrset, zone_data));
+ }
// 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());
+ addWildcards(zone_data.domains_, rrset->getName());
// Get the node
DomainNode* node;
- DomainTree::Result result = domains->insert(rrset->getName(), &node);
+ DomainTree::Result result = zone_data.domains_.insert(rrset->getName(),
+ &node);
// Just check it returns reasonable results
assert((result == DomainTree::SUCCESS ||
result == DomainTree::ALREADYEXISTS) && node!= NULL);
@@ -272,7 +1129,7 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
// break strong exception guarantee. At the moment we prefer
// code simplicity and don't bother to introduce complicated
// recovery code.
- contextCheck(rrset, domain);
+ contextCheck(*rrset, *domain);
// Try inserting the rrset there
if (domain->insert(DomainPair(rrset->getType(), rrset)).second) {
@@ -283,11 +1140,38 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
if (rrset->getType() == RRType::NS() &&
rrset->getName() != origin_) {
node->setFlag(DomainNode::FLAG_CALLBACK);
- // If it is DNAME, we have a callback as well here
+ // If it is DNAME, we have a callback as well here
} else if (rrset->getType() == RRType::DNAME()) {
node->setFlag(DomainNode::FLAG_CALLBACK);
}
+ if (need_additionals != NULL &&
+ (rrset->getType() == RRType::NS() ||
+ rrset->getType() == RRType::MX())) {
+ need_additionals->push_back(rrset.get());
+ }
+
+ // If we've added NSEC3PARAM at zone origin, set up NSEC3 specific
+ // data or check consistency with already set up parameters.
+ if (rrset->getType() == RRType::NSEC3PARAM() &&
+ rrset->getName() == origin_) {
+ // We know rrset has exactly one RDATA
+ const generic::NSEC3PARAM& param =
+ dynamic_cast<const generic::NSEC3PARAM&>(
+ rrset->getRdataIterator()->getCurrent());
+
+ if (!zone_data.nsec3_data_) {
+ zone_data.nsec3_data_.reset(
+ new ZoneData::NSEC3Data(param));
+ } else if (!zone_data.nsec3_data_->hash_->match(param)) {
+ isc_throw(AddError, "NSEC3PARAM with inconsistent "
+ "parameters: " << rrset->toText());
+ }
+ } else if (rrset->getType() == RRType::NSEC()) {
+ // If it is NSEC signed zone, so we put a flag there
+ // (flag is enough)
+ zone_data.nsec_signed_ = true;
+ }
return (result::SUCCESS);
} else {
// The RRSet of given type was already there
@@ -299,271 +1183,94 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
* 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:
- LOG_ERROR(logger, DATASRC_MEM_DUP_RRSET).
- arg(set->getName()).arg(set->getType());
- 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()) {
- LOG_DEBUG(logger, DBG_TRACE_DETAILED,
- DATASRC_MEM_DNAME_ENCOUNTERED);
- 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);
- }
-
- LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_NS_ENCOUNTERED);
-
- // 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);
+ void addFromLoad(const ConstRRsetPtr& set, ZoneData* zone_data,
+ vector<RBNodeRRset*>* need_additionals)
+ {
+ switch (add(set, *zone_data, need_additionals)) {
+ case result::EXIST:
+ LOG_ERROR(logger, DATASRC_MEM_DUP_RRSET).
+ arg(set->getName()).arg(set->getType());
+ isc_throw(dns::MasterLoadError, "Duplicate rrset: " <<
+ set->toText());
+ case result::SUCCESS:
+ return;
+ default:
+ assert(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)
+ // Set up FindContext object as a return value of find(), taking into
+ // account wildcard matches and DNSSEC information. We set the NSEC/NSEC3
+ // flag when applicable regardless of the find option; the caller would
+ // simply ignore these when they didn't request DNSSEC related results.
+ // When the optional parameter 'node' is given (in which case it should be
+ // non NULL), it means it's a result of ANY query and the context should
+ // remember the matched node.
+ RBNodeResultContext createFindResult(Result code,
+ ConstRBNodeRRsetPtr rrset,
+ bool wild = false,
+ const DomainNode* node = NULL) const
{
- if (rename) {
- LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_RENAME).
- arg(rrset->getName()).arg(name);
- /*
- * 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());
+ FindResultFlags flags = RESULT_DEFAULT;
+ if (wild) {
+ flags = flags | RESULT_WILDCARD;
+ }
+ if (code == NXRRSET || code == NXDOMAIN || wild) {
+ if (zone_data_->nsec3_data_) {
+ flags = flags | RESULT_NSEC3_SIGNED;
+ }
+ if (zone_data_->nsec_signed_) {
+ flags = flags | RESULT_NSEC_SIGNED;
}
- return (result);
- } else {
- return (rrset);
}
+ return (RBNodeResultContext(code, rrset, flags, node));
}
// Implementation of InMemoryZoneFinder::find
- FindResult find(const Name& name, RRType type,
- std::vector<ConstRRsetPtr> *target,
- const FindOptions options) const
+ RBNodeResultContext find(const Name& name, RRType type,
+ std::vector<ConstRRsetPtr>* target,
+ const FindOptions options) const
{
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_FIND).arg(name).
arg(type);
- // 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) {
- LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DNAME_FOUND).
- arg(state.rrset_->getName());
- // 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) {
- LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DELEG_FOUND).
- arg(state.rrset_->getName());
- 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) {
- LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUPER_STOP).
- arg(node_path.getAbsoluteName()).arg(name);
- 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) {
- LOG_DEBUG(logger, DBG_TRACE_DATA,
- DATASRC_MEM_WILDCARD_CANCEL).arg(name);
- 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:
- LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NOT_FOUND).
- arg(name);
- return (FindResult(NXDOMAIN, ConstRRsetPtr()));
- case DomainTree::EXACTMATCH: // This one is OK, handle it
- break;
- default:
- assert(0);
+ // Get the node. All other cases than an exact match are handled
+ // in findNode(). We simply construct a result structure and return.
+ const ZoneData::FindNodeResult node_result =
+ zone_data_->findNode<ZoneData::FindNodeResult>(name, options);
+ if (node_result.code != SUCCESS) {
+ return (createFindResult(node_result.code, node_result.rrset));
}
+
+ // We've found an exact match, may or may not be a result of wildcard.
+ const DomainNode* node = node_result.node;
assert(node != NULL);
+ const bool rename = ((node_result.flags &
+ ZoneData::FindNodeResult::FIND_WILDCARD) != 0);
// If there is an exact match but the node is empty, it's equivalent
// to NXRRSET.
if (node->isEmpty()) {
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DOMAIN_EMPTY).
arg(name);
- return (FindResult(NXRRSET, ConstRRsetPtr()));
+ return (createFindResult(NXRRSET, ConstRBNodeRRsetPtr(), rename));
}
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_) {
+ // There is one exception: the case for DS query, which should always
+ // be considered in-zone lookup.
+ if (node->getFlag(DomainNode::FLAG_CALLBACK) &&
+ node != zone_data_->origin_data_ && type != RRType::DS()) {
found = node->getData()->find(RRType::NS());
if (found != node->getData()->end()) {
LOG_DEBUG(logger, DBG_TRACE_DATA,
DATASRC_MEM_EXACT_DELEGATION).arg(name);
- return (FindResult(DELEGATION, prepareRRset(name,
- found->second, rename)));
+ return (createFindResult(DELEGATION,
+ prepareRRset(name, found->second,
+ rename, options)));
}
}
@@ -573,11 +1280,13 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
for (found = node->getData()->begin();
found != node->getData()->end(); ++found)
{
- target->push_back(prepareRRset(name, found->second, rename));
+ target->push_back(prepareRRset(name, found->second, rename,
+ options));
}
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ANY_SUCCESS).
arg(name);
- return (FindResult(SUCCESS, ConstRRsetPtr()));
+ return (createFindResult(SUCCESS, ConstRBNodeRRsetPtr(), rename,
+ node));
}
found = node->getData()->find(type);
@@ -585,25 +1294,30 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
// Good, it is here
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUCCESS).arg(name).
arg(type);
- return (FindResult(SUCCESS, prepareRRset(name, found->second,
- rename)));
+ return (createFindResult(SUCCESS, prepareRRset(name,
+ found->second,
+ rename, options),
+ rename));
} else {
// Next, try CNAME.
found = node->getData()->find(RRType::CNAME());
if (found != node->getData()->end()) {
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_CNAME).arg(name);
- return (FindResult(CNAME, prepareRRset(name, found->second,
- rename)));
+ return (createFindResult(CNAME,
+ prepareRRset(name, found->second,
+ rename, options),
+ rename));
}
}
// No exact match or CNAME. Return NXRRSET.
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NXRRSET).arg(type).
arg(name);
- return (FindResult(NXRRSET, ConstRRsetPtr()));
+ return (createFindResult(NXRRSET, ConstRBNodeRRsetPtr(), rename));
}
};
-InMemoryZoneFinder::InMemoryZoneFinder(const RRClass& zone_class, const Name& origin) :
+InMemoryZoneFinder::InMemoryZoneFinder(const RRClass& zone_class,
+ const Name& origin) :
impl_(new InMemoryZoneFinderImpl(zone_class, origin))
{
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_CREATE).arg(origin).
@@ -626,39 +1340,339 @@ InMemoryZoneFinder::getClass() const {
return (impl_->zone_class_);
}
-ZoneFinder::FindResult
+ZoneFinderContextPtr
InMemoryZoneFinder::find(const Name& name, const RRType& type,
- const FindOptions options)
+ const FindOptions options)
{
- return (impl_->find(name, type, NULL, options));
+ return (ZoneFinderContextPtr(
+ new Context(*this, options, impl_->find(name, type, NULL,
+ options))));
}
-ZoneFinder::FindResult
+ZoneFinderContextPtr
InMemoryZoneFinder::findAll(const Name& name,
std::vector<ConstRRsetPtr>& target,
const FindOptions options)
{
- return (impl_->find(name, RRType::ANY(), &target, options));
+ return (ZoneFinderContextPtr(
+ new Context(*this, options, impl_->find(name, RRType::ANY(),
+ &target, options))));
+}
+
+ZoneFinder::FindNSEC3Result
+InMemoryZoneFinder::findNSEC3(const Name& name, bool recursive) {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_FINDNSEC3).arg(name).
+ arg(recursive ? "recursive" : "non-recursive");
+
+ if (!impl_->zone_data_->nsec3_data_) {
+ isc_throw(DataSourceError,
+ "findNSEC3 attempt for non NSEC3 signed zone: " <<
+ impl_->origin_ << "/" << impl_->zone_class_);
+ }
+ const NSEC3Map& map = impl_->zone_data_->nsec3_data_->map_;
+ if (map.empty()) {
+ isc_throw(DataSourceError,
+ "findNSEC3 attempt but zone has no NSEC3 RR: " <<
+ impl_->origin_ << "/" << impl_->zone_class_);
+ }
+ const NameComparisonResult cmp_result = name.compare(impl_->origin_);
+ if (cmp_result.getRelation() != NameComparisonResult::EQUAL &&
+ cmp_result.getRelation() != NameComparisonResult::SUBDOMAIN) {
+ isc_throw(OutOfZone, "findNSEC3 attempt for out-of-zone name: "
+ << name << ", zone: " << impl_->origin_ << "/"
+ << impl_->zone_class_);
+ }
+
+ // Convenient shortcuts
+ const NSEC3Hash& nsec3hash = *impl_->zone_data_->nsec3_data_->hash_;
+ const unsigned int olabels = impl_->origin_.getLabelCount();
+ const unsigned int qlabels = name.getLabelCount();
+
+ ConstRBNodeRRsetPtr covering_proof; // placeholder of the next closer proof
+ // Examine all names from the query name to the origin name, stripping
+ // the deepest label one by one, until we find a name that has a matching
+ // NSEC3 hash.
+ for (unsigned int labels = qlabels; labels >= olabels; --labels) {
+ const string hlabel = nsec3hash.calculate(
+ labels == qlabels ? name : name.split(qlabels - labels, labels));
+ NSEC3Map::const_iterator found = map.lower_bound(hlabel);
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_FINDNSEC3_TRYHASH).
+ arg(name).arg(labels).arg(hlabel);
+
+ // If the given hash is larger than the largest stored hash or
+ // the first label doesn't match the target, identify the "previous"
+ // hash value and remember it as the candidate next closer proof.
+ if (found == map.end() || found->first != hlabel) {
+ // If the given hash is larger or smaller than everything,
+ // the covering proof is the NSEC3 that has the largest hash.
+ // Note that we know the map isn't empty, so rbegin() is
+ // safe.
+ if (found == map.end() || found == map.begin()) {
+ covering_proof = map.rbegin()->second;
+ } else {
+ // Otherwise, H(found_entry-1) < given_hash < H(found_entry).
+ // The covering proof is the first one (and it's valid
+ // because found is neither begin nor end)
+ covering_proof = (--found)->second;
+ }
+ if (!recursive) { // in non recursive mode, we are done.
+ LOG_DEBUG(logger, DBG_TRACE_BASIC,
+ DATASRC_MEM_FINDNSEC3_COVER).
+ arg(name).arg(*covering_proof);
+ return (FindNSEC3Result(false, labels, covering_proof,
+ ConstRRsetPtr()));
+ }
+ } else { // found an exact match.
+ LOG_DEBUG(logger, DBG_TRACE_BASIC,
+ DATASRC_MEM_FINDNSEC3_MATCH).arg(name).arg(labels).
+ arg(*found->second);
+ return (FindNSEC3Result(true, labels, found->second,
+ covering_proof));
+ }
+ }
+
+ isc_throw(DataSourceError, "recursive findNSEC3 mode didn't stop, likely "
+ "a broken NSEC3 zone: " << impl_->origin_ << "/"
+ << impl_->zone_class_);
}
result::Result
InMemoryZoneFinder::add(const ConstRRsetPtr& rrset) {
- return (impl_->add(rrset, &impl_->domains_));
+ return (impl_->add(rrset, *impl_->zone_data_, NULL));
+}
+
+namespace {
+// This should eventually be more generalized.
+const Name
+getAdditionalName(RRType rrtype, const rdata::Rdata& rdata) {
+ if (rrtype == RRType::NS()) {
+ const generic::NS& ns = dynamic_cast<const generic::NS&>(rdata);
+ return (ns.getNSName());
+ } else {
+ // In our usage the only other possible case is MX.
+ assert(rrtype == RRType::MX());
+ const generic::MX& mx = dynamic_cast<const generic::MX&>(rdata);
+ return (mx.getMXName());
+ }
+}
+
+void
+convertAndInsert(const DomainPair& rrset_item, DomainPtr dst_domain,
+ const Name* dstname)
+{
+ // We copy RRSIGs, too, if they are attached in case we need it in
+ // getAdditional().
+ dst_domain->insert(DomainPair(rrset_item.first,
+ prepareRRset(*dstname, rrset_item.second,
+ true,
+ ZoneFinder::FIND_DNSSEC)));
+}
+
+void
+addAdditional(RBNodeRRset* rrset, ZoneData* zone_data,
+ vector<RBNodeRRset*>* wild_rrsets)
+{
+ RdataIteratorPtr rdata_iterator = rrset->getRdataIterator();
+ bool match_wild = false; // will be true if wildcard match is found
+ for (; !rdata_iterator->isLast(); rdata_iterator->next()) {
+ // For each domain name that requires additional section processing
+ // in each RDATA, search the tree for the name and remember it if
+ // found. If the name is under a zone cut (for a delegation to a
+ // child zone), mark the node as "GLUE", so we can selectively
+ // include/exclude them when we use it.
+
+ const Name& name = getAdditionalName(rrset->getType(),
+ rdata_iterator->getCurrent());
+ // if the name is not in or below this zone, skip it
+ const NameComparisonResult::NameRelation reln =
+ name.compare(zone_data->origin_data_->getName()).getRelation();
+ if (reln != NameComparisonResult::SUBDOMAIN &&
+ reln != NameComparisonResult::EQUAL) {
+ continue;
+ }
+ const ZoneData::FindMutableNodeResult result =
+ zone_data->findNode<ZoneData::FindMutableNodeResult>(
+ name, ZoneFinder::FIND_GLUE_OK);
+ if (result.code != ZoneFinder::SUCCESS) {
+ // We are not interested in anything but a successful match.
+ continue;
+ }
+ DomainNode* node = result.node;
+ assert(node != NULL);
+ if ((result.flags & ZoneData::FindNodeResult::FIND_ZONECUT) != 0 ||
+ (node->getFlag(DomainNode::FLAG_CALLBACK) &&
+ node->getData()->find(RRType::NS()) != node->getData()->end())) {
+ // The node is under or at a zone cut; mark it as a glue.
+ node->setFlag(domain_flag::GLUE);
+ }
+
+ // A rare case: the additional name may have to be expanded with a
+ // wildcard. We'll store the name in a separate auxiliary tree,
+ // copying all RRsets of the original wildcard node with expanding
+ // the owner name. This is costly in terms of memory, but this case
+ // should be pretty rare. On the other hand we won't have to worry
+ // about wildcard expansion in getAdditional, which is quite
+ // performance sensitive.
+ DomainNode* wildnode = NULL;
+ if ((result.flags & ZoneData::FindNodeResult::FIND_WILDCARD) != 0) {
+ // Wildcard and glue shouldn't coexist. Make it sure here.
+ assert(!node->getFlag(domain_flag::GLUE));
+
+ if (zone_data->getAuxWildDomains().insert(name, &wildnode)
+ == DomainTree::SUCCESS) {
+ // If we first insert the node, copy the RRsets. If the
+ // original node was empty, we add empty data so
+ // addWildAdditional() can get an exactmatch for this name.
+ DomainPtr dst_domain(new Domain);
+ if (!node->isEmpty()) {
+ for_each(node->getData()->begin(), node->getData()->end(),
+ boost::bind(convertAndInsert, _1, dst_domain,
+ &name));
+ }
+ wildnode->setData(dst_domain);
+ // Mark the node as "wildcard expanded" so it can be
+ // distinguished at lookup time.
+ wildnode->setFlag(domain_flag::WILD_EXPANDED);
+ }
+ match_wild = true;
+ node = wildnode;
+ }
+
+ // If this name wasn't subject to wildcard substitution, we can add
+ // the additional information to the RRset now; otherwise I'll defer
+ // it until the entire auxiliary tree is built (pointers may be
+ // invalidated as we build it).
+ if (wildnode == NULL) {
+ // Note that node may be empty. We should keep it in the list
+ // in case we dynamically update the tree and it becomes non empty
+ // (which is not supported yet)
+ rrset->addAdditionalNode(AdditionalNodeInfo(node));
+ }
+ }
+
+ if (match_wild) {
+ wild_rrsets->push_back(rrset);
+ }
+}
+
+void
+addWildAdditional(RBNodeRRset* rrset, ZoneData* zone_data) {
+ // Similar to addAdditional(), but due to the first stage we know that
+ // the rrset should contain a name stored in the auxiliary trees, and
+ // that it should be found as an exact match. The RRset may have other
+ // names that didn't require wildcard expansion, but we can simply ignore
+ // them in this context. (Note that if we find an exact match in the
+ // auxiliary tree, it shouldn't be in the original zone; otherwise it
+ // shouldn't have resulted in wildcard in the first place).
+
+ RdataIteratorPtr rdata_iterator = rrset->getRdataIterator();
+ for (; !rdata_iterator->isLast(); rdata_iterator->next()) {
+ const Name& name = getAdditionalName(rrset->getType(),
+ rdata_iterator->getCurrent());
+ DomainNode* wildnode = NULL;
+ if (zone_data->getAuxWildDomains().find(name, &wildnode) ==
+ DomainTree::EXACTMATCH) {
+ rrset->addAdditionalNode(AdditionalNodeInfo(wildnode));
+ }
+ }
+}
+}
+
+void
+InMemoryZoneFinder::InMemoryZoneFinderImpl::load(
+ const string& filename,
+ boost::function<void(LoadCallback)> rrset_installer)
+{
+ vector<RBNodeRRset*> need_additionals;
+ scoped_ptr<ZoneData> tmp(new ZoneData(origin_));
+
+ rrset_installer(boost::bind(&InMemoryZoneFinderImpl::addFromLoad, this,
+ _1, tmp.get(), &need_additionals));
+
+ vector<RBNodeRRset*> wild_additionals;
+ for_each(need_additionals.begin(), need_additionals.end(),
+ boost::bind(addAdditional, _1, tmp.get(), &wild_additionals));
+ for_each(wild_additionals.begin(), wild_additionals.end(),
+ boost::bind(addWildAdditional, _1, tmp.get()));
+
+ // If the zone is NSEC3-signed, check if it has NSEC3PARAM
+ if (tmp->nsec3_data_) {
+ // Note: origin_data_ is set on creation of ZoneData, and the load
+ // process only adds new nodes (and their data), so this assertion
+ // should hold.
+ assert(tmp->origin_data_ != NULL && !tmp->origin_data_->isEmpty());
+ if (tmp->origin_data_->getData()->find(RRType::NSEC3PARAM()) ==
+ tmp->origin_data_->getData()->end()) {
+ LOG_WARN(logger, DATASRC_MEM_NO_NSEC3PARAM).
+ arg(origin_).arg(zone_class_);
+ }
+ }
+
+ // If it went well, put it inside
+ file_name_ = filename;
+ tmp.swap(zone_data_);
+ // And let the old data die with tmp
+}
+
+namespace {
+// A wrapper for dns::masterLoad used by load() below. Essentially it
+// converts the two callback types. Note the mostly redundant wrapper of
+// boost::bind. It converts function<void(ConstRRsetPtr)> to
+// function<void(RRsetPtr)> (masterLoad() expects the latter). SunStudio
+// doesn't seem to do this conversion if we just pass 'callback'.
+void
+masterLoadWrapper(const char* const filename, const Name& origin,
+ const RRClass& zone_class, LoadCallback callback)
+{
+ masterLoad(filename, origin, zone_class, boost::bind(callback, _1));
}
+// The installer called from Impl::load() for the iterator version of load().
+void
+generateRRsetFromIterator(ZoneIterator* iterator, LoadCallback callback) {
+ ConstRRsetPtr rrset;
+ vector<ConstRRsetPtr> rrsigs; // placeholder for RRSIGs until "commitable".
+
+ // The current internal implementation assumes an RRSIG is always added
+ // after the RRset they cover. So we store any RRSIGs in 'rrsigs' until
+ // it's safe to add them; based on our assumption if the owner name
+ // changes, all covered RRsets of the previous name should have been
+ // installed and any pending RRSIGs can be added at that point. RRSIGs
+ // of the last name from the iterator must be added separately.
+ while ((rrset = iterator->getNextRRset()) != NULL) {
+ if (!rrsigs.empty() && rrset->getName() != rrsigs[0]->getName()) {
+ BOOST_FOREACH(ConstRRsetPtr sig_rrset, rrsigs) {
+ callback(sig_rrset);
+ }
+ rrsigs.clear();
+ }
+ if (rrset->getType() == RRType::RRSIG()) {
+ rrsigs.push_back(rrset);
+ } else {
+ callback(rrset);
+ }
+ }
+
+ BOOST_FOREACH(ConstRRsetPtr sig_rrset, rrsigs) {
+ callback(sig_rrset);
+ }
+}
+}
void
InMemoryZoneFinder::load(const string& filename) {
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_LOAD).arg(getOrigin()).
arg(filename);
- // Load it into a temporary tree
- DomainTree tmp;
- masterLoad(filename.c_str(), getOrigin(), getClass(),
- boost::bind(&InMemoryZoneFinderImpl::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
+
+ impl_->load(filename,
+ boost::bind(masterLoadWrapper, filename.c_str(), getOrigin(),
+ getClass(), _1));
+}
+
+void
+InMemoryZoneFinder::load(ZoneIterator& iterator) {
+ impl_->load(string(),
+ boost::bind(generateRRsetFromIterator, &iterator, _1));
}
void
@@ -848,8 +1862,9 @@ InMemoryClient::getIterator(const Name& name, bool separate_rrs) const {
isc_throw(Unexpected, "The zone at " + name.toText() +
" is not InMemoryZoneFinder");
}
- return (ZoneIteratorPtr(new MemoryIterator(zone->impl_->domains_, name,
- separate_rrs)));
+ return (ZoneIteratorPtr(new MemoryIterator(
+ zone->impl_->zone_data_->domains_, name,
+ separate_rrs)));
}
ZoneUpdaterPtr
@@ -865,147 +1880,5 @@ InMemoryClient::getJournalReader(const isc::dns::Name&, uint32_t,
"in memory data source");
}
-namespace {
-// convencience function to add an error message to a list of those
-// (TODO: move functions like these to some util lib?)
-void
-addError(ElementPtr errors, const std::string& error) {
- if (errors != ElementPtr() && errors->getType() == Element::list) {
- errors->add(Element::create(error));
- }
-}
-
-/// Check if the given element exists in the map, and if it is a string
-bool
-checkConfigElementString(ConstElementPtr config, const std::string& name,
- ElementPtr errors)
-{
- if (!config->contains(name)) {
- addError(errors,
- "Config for memory backend does not contain a '"
- +name+
- "' value");
- return false;
- } else if (!config->get(name) ||
- config->get(name)->getType() != Element::string) {
- addError(errors, "value of " + name +
- " in memory backend config is not a string");
- return false;
- } else {
- return true;
- }
-}
-
-bool
-checkZoneConfig(ConstElementPtr config, ElementPtr errors) {
- bool result = true;
- if (!config || config->getType() != Element::map) {
- addError(errors, "Elements in memory backend's zone list must be maps");
- result = false;
- } else {
- if (!checkConfigElementString(config, "origin", errors)) {
- result = false;
- }
- if (!checkConfigElementString(config, "file", errors)) {
- result = false;
- }
- // we could add some existence/readabilty/parsability checks here
- // if we want
- }
- return result;
-}
-
-bool
-checkConfig(ConstElementPtr config, ElementPtr errors) {
- /* Specific configuration is under discussion, right now this accepts
- * the 'old' configuration, see [TODO]
- * So for memory datasource, we get a structure like this:
- * { "type": string ("memory"),
- * "class": string ("IN"/"CH"/etc),
- * "zones": list
- * }
- * Zones list is a list of maps:
- * { "origin": string,
- * "file": string
- * }
- *
- * At this moment we cannot be completely sure of the contents of the
- * structure, so we have to do some more extensive tests than should
- * strictly be necessary (e.g. existence and type of elements)
- */
- bool result = true;
-
- if (!config || config->getType() != Element::map) {
- addError(errors, "Base config for memory backend must be a map");
- result = false;
- } else {
- if (!checkConfigElementString(config, "type", errors)) {
- result = false;
- } else {
- if (config->get("type")->stringValue() != "memory") {
- addError(errors,
- "Config for memory backend is not of type \"memory\"");
- result = false;
- }
- }
- if (!checkConfigElementString(config, "class", errors)) {
- result = false;
- } else {
- try {
- RRClass rrc(config->get("class")->stringValue());
- } catch (const isc::Exception& rrce) {
- addError(errors,
- "Error parsing class config for memory backend: " +
- std::string(rrce.what()));
- result = false;
- }
- }
- if (!config->contains("zones")) {
- addError(errors, "No 'zones' element in memory backend config");
- result = false;
- } else if (!config->get("zones") ||
- config->get("zones")->getType() != Element::list) {
- addError(errors, "'zones' element in memory backend config is not a list");
- result = false;
- } else {
- BOOST_FOREACH(ConstElementPtr zone_config,
- config->get("zones")->listValue()) {
- if (!checkZoneConfig(zone_config, errors)) {
- result = false;
- }
- }
- }
- }
-
- return (result);
- return true;
-}
-
-} // end anonymous namespace
-
-DataSourceClient *
-createInstance(isc::data::ConstElementPtr config, std::string& error) {
- ElementPtr errors(Element::createList());
- if (!checkConfig(config, errors)) {
- error = "Configuration error: " + errors->str();
- return (NULL);
- }
- try {
- return (new InMemoryClient());
- } catch (const std::exception& exc) {
- error = std::string("Error creating memory datasource: ") + exc.what();
- return (NULL);
- } catch (...) {
- error = std::string("Error creating memory datasource, "
- "unknown exception");
- return (NULL);
- }
-}
-
-void destroyInstance(DataSourceClient* instance) {
- delete instance;
-}
-
-
} // end of namespace datasrc
} // end of namespace isc
diff --git a/src/lib/datasrc/memory_datasrc.h b/src/lib/datasrc/memory_datasrc.h
index 32cf518..c687d1b 100644
--- a/src/lib/datasrc/memory_datasrc.h
+++ b/src/lib/datasrc/memory_datasrc.h
@@ -70,18 +70,26 @@ public:
/// See documentation in \c Zone.
///
/// It returns NULL pointer in case of NXDOMAIN and NXRRSET.
- virtual FindResult find(const isc::dns::Name& name,
- const isc::dns::RRType& type,
- const FindOptions options = FIND_DEFAULT);
+ virtual ZoneFinderContextPtr find(const isc::dns::Name& name,
+ const isc::dns::RRType& type,
+ const FindOptions options =
+ FIND_DEFAULT);
/// \brief Version of find that returns all types at once
///
/// It acts the same as find, just that when the correct node is found,
/// all the RRsets are filled into the target parameter instead of being
/// returned by the result.
- virtual FindResult findAll(const isc::dns::Name& name,
- std::vector<isc::dns::ConstRRsetPtr>& target,
- const FindOptions options = FIND_DEFAULT);
+ virtual ZoneFinderContextPtr findAll(
+ const isc::dns::Name& name,
+ std::vector<isc::dns::ConstRRsetPtr>& target,
+ const FindOptions options = FIND_DEFAULT);
+
+ /// Look for NSEC3 for proving (non)existence of given name.
+ ///
+ /// See documentation in \c Zone.
+ virtual FindNSEC3Result
+ findNSEC3(const isc::dns::Name& name, bool recursive);
/// \brief Imelementation of the ZoneFinder::findPreviousName method
///
@@ -93,6 +101,15 @@ public:
///
/// It puts another RRset into the zone.
///
+ /// In the current implementation, this method doesn't allow an existing
+ /// RRset to be updated or overridden. So the caller must make sure that
+ /// all RRs of the same type and name must be given in the form of a
+ /// single RRset. The current implementation will also require that
+ /// when an RRSIG is added the RRset to be covered has already been
+ /// added. These restrictions are probably too strict when this data
+ /// source accepts various forms of input, so they should be revisited
+ /// later.
+ ///
/// 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).
@@ -108,16 +125,6 @@ public:
/// 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.
@@ -181,6 +188,26 @@ public:
/// configuration reloading is written.
void load(const std::string& filename);
+ /// \brief Load zone from another data source.
+ ///
+ /// This is similar to the other version, but zone's RRsets are provided
+ /// by an iterator of another data source. On successful load, the
+ /// internal filename will be cleared.
+ ///
+ /// This implementation assumes the iterator produces combined RRsets,
+ /// that is, there should exactly one RRset for the same owner name and
+ /// RR type. This means the caller is expected to create the iterator
+ /// with \c separate_rrs being \c false. This implementation also assumes
+ /// RRsets of different names are not mixed; so if the iterator produces
+ /// an RRset of a different name than that of the previous RRset, that
+ /// previous name must never appear in the subsequent sequence of RRsets.
+ /// Note that the iterator API does not ensure this. If the underlying
+ /// implementation does not follow it, load() will fail. Note, however,
+ /// that this whole interface is tentative. in-memory zone loading will
+ /// have to be revisited fundamentally, and at that point this restriction
+ /// probably won't matter.
+ void load(ZoneIterator& iterator);
+
/// Exchanges the content of \c this zone finder with that of the given
/// \c zone_finder.
///
@@ -201,6 +228,12 @@ private:
// extracts the pointer to data and puts it into the iterator.
// The access is read only.
friend class InMemoryClient;
+
+ /// \brief In-memory version of finder context.
+ ///
+ /// The implementation (and any specialized interface) is completely local
+ /// to the InMemoryZoneFinder class, so it's defined as private
+ class Context;
};
/// \brief A data source client that holds all necessary data in memory.
diff --git a/src/lib/datasrc/memory_datasrc_link.cc b/src/lib/datasrc/memory_datasrc_link.cc
new file mode 100644
index 0000000..a0b4bf6
--- /dev/null
+++ b/src/lib/datasrc/memory_datasrc_link.cc
@@ -0,0 +1,173 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <cc/data.h>
+
+#include <dns/rrclass.h>
+
+#include <datasrc/client.h>
+#include <datasrc/memory_datasrc.h>
+
+#include <boost/foreach.hpp>
+
+#include <string>
+
+using namespace isc::dns;
+using namespace isc::data;
+
+namespace isc {
+namespace datasrc {
+
+namespace {
+// convencience function to add an error message to a list of those
+// (TODO: move functions like these to some util lib?)
+void
+addError(ElementPtr errors, const std::string& error) {
+ if (errors != ElementPtr() && errors->getType() == Element::list) {
+ errors->add(Element::create(error));
+ }
+}
+
+/// Check if the given element exists in the map, and if it is a string
+bool
+checkConfigElementString(ConstElementPtr config, const std::string& name,
+ ElementPtr errors)
+{
+ if (!config->contains(name)) {
+ addError(errors,
+ "Config for memory backend does not contain a '"
+ +name+
+ "' value");
+ return false;
+ } else if (!config->get(name) ||
+ config->get(name)->getType() != Element::string) {
+ addError(errors, "value of " + name +
+ " in memory backend config is not a string");
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool
+checkZoneConfig(ConstElementPtr config, ElementPtr errors) {
+ bool result = true;
+ if (!config || config->getType() != Element::map) {
+ addError(errors, "Elements in memory backend's zone list must be maps");
+ result = false;
+ } else {
+ if (!checkConfigElementString(config, "origin", errors)) {
+ result = false;
+ }
+ if (!checkConfigElementString(config, "file", errors)) {
+ result = false;
+ }
+ // we could add some existence/readabilty/parsability checks here
+ // if we want
+ }
+ return result;
+}
+
+bool
+checkConfig(ConstElementPtr config, ElementPtr errors) {
+ /* Specific configuration is under discussion, right now this accepts
+ * the 'old' configuration, see [TODO]
+ * So for memory datasource, we get a structure like this:
+ * { "type": string ("memory"),
+ * "class": string ("IN"/"CH"/etc),
+ * "zones": list
+ * }
+ * Zones list is a list of maps:
+ * { "origin": string,
+ * "file": string
+ * }
+ *
+ * At this moment we cannot be completely sure of the contents of the
+ * structure, so we have to do some more extensive tests than should
+ * strictly be necessary (e.g. existence and type of elements)
+ */
+ bool result = true;
+
+ if (!config || config->getType() != Element::map) {
+ addError(errors, "Base config for memory backend must be a map");
+ result = false;
+ } else {
+ if (!checkConfigElementString(config, "type", errors)) {
+ result = false;
+ } else {
+ if (config->get("type")->stringValue() != "memory") {
+ addError(errors,
+ "Config for memory backend is not of type \"memory\"");
+ result = false;
+ }
+ }
+ if (!checkConfigElementString(config, "class", errors)) {
+ result = false;
+ } else {
+ try {
+ RRClass rrc(config->get("class")->stringValue());
+ } catch (const isc::Exception& rrce) {
+ addError(errors,
+ "Error parsing class config for memory backend: " +
+ std::string(rrce.what()));
+ result = false;
+ }
+ }
+ if (!config->contains("zones")) {
+ addError(errors, "No 'zones' element in memory backend config");
+ result = false;
+ } else if (!config->get("zones") ||
+ config->get("zones")->getType() != Element::list) {
+ addError(errors, "'zones' element in memory backend config is not a list");
+ result = false;
+ } else {
+ BOOST_FOREACH(ConstElementPtr zone_config,
+ config->get("zones")->listValue()) {
+ if (!checkZoneConfig(zone_config, errors)) {
+ result = false;
+ }
+ }
+ }
+ }
+
+ return (result);
+}
+
+} // end unnamed namespace
+
+DataSourceClient *
+createInstance(isc::data::ConstElementPtr config, std::string& error) {
+ ElementPtr errors(Element::createList());
+ if (!checkConfig(config, errors)) {
+ error = "Configuration error: " + errors->str();
+ return (NULL);
+ }
+ try {
+ return (new isc::datasrc::InMemoryClient());
+ } catch (const std::exception& exc) {
+ error = std::string("Error creating memory datasource: ") + exc.what();
+ return (NULL);
+ } catch (...) {
+ error = std::string("Error creating memory datasource, "
+ "unknown exception");
+ return (NULL);
+ }
+}
+
+void destroyInstance(DataSourceClient* instance) {
+ delete instance;
+}
+
+} // end of namespace datasrc
+} // end of namespace isc
diff --git a/src/lib/datasrc/rbnode_rrset.h b/src/lib/datasrc/rbnode_rrset.h
new file mode 100644
index 0000000..3e5d20a
--- /dev/null
+++ b/src/lib/datasrc/rbnode_rrset.h
@@ -0,0 +1,228 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __RBNODE_RRSET_H
+#define __RBNODE_RRSET_H
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/rrtype.h>
+#include <util/buffer.h>
+
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace datasrc {
+namespace internal {
+
+/// \brief The actual content of \c RBNodeRRset
+///
+/// This is defined in the namespace-scope (not hidden in the main class)
+/// so that the In-memory data source implementation can refer to it.
+struct RBNodeRRsetImpl;
+
+// Forward declaration of an opaque data type defined and used within the
+// implementation. This is public only because it needs to be used within
+// the in-memory data source implementation, but conceptually this is a
+// private type for the in-memory data source implementation.
+// Note that the definition of the structure is still hidden within the
+// implementation, so, basically, a normal application should never be able
+// to use it directly even if it peeks into the "internal" namespace.
+struct AdditionalNodeInfo;
+
+/// \brief Special RRset for optimizing memory datasource requirement
+///
+/// To speed up the performance of the in-memory data source, at load time
+/// associate relevant "additional section" data with each RRset in the
+/// data source.
+///
+/// This class, derived from AbstractRRset, holds a "const" pointer to the
+/// underlying RRset object. All calls to methods on the class are passed to
+/// the underlying object. However, there are some restrictions:
+///
+/// - Calls to methods that change attributes of the underlying RRset (such as
+/// TTL or Name) cause an exception to be thrown. The in-memory data source
+/// does not allow modification of these attributes. In theory, it is a bad
+/// practice in that it doesn't preserve the assumed behavior of the base
+/// class. In practice, however, it should be acceptable because this
+/// class is effectively hidden from applications and will only be given
+/// to them as a const pointer to the base class via find() variants.
+/// So the application cannot call non const methods anyway unless it
+/// intentionally breaks the constness.
+///
+/// - Calls that add the pointer to the associated RRSIG to the RRset are
+/// allowed (even though the pointer is to a "const" RRset). The reason here
+/// is that RRSIGs are added to the in-memory data source after the
+/// RBNodeRRset objects have been created. Thus there has to be the
+/// capability of modifying this information.
+///
+/// The class is not derived from RRset itself to simplify coding: part of the
+/// loading of the memory data source is handled in the BIND 10 "libdns++"
+/// code, which creates RRsets and passes them to the data source code. This
+/// does not have to be altered if encapsulation, rather than inheritance, is
+/// used.
+///
+/// \note This class is exposed in this separate header file so that test code
+/// can refer to its definition, and only for that purpose. Otherwise this is
+/// essentially a private class of the in-memory data source implementation,
+/// and an application shouldn't directly refer to this class.
+///
+// Note: non-Doxygen-documented methods are documented in the base class.
+
+class RBNodeRRset : public isc::dns::AbstractRRset {
+
+private:
+ // Note: The copy constructor and the assignment operator are intentionally
+ // defined as private as we would normally not duplicate a RBNodeRRset.
+ // (We use the "private" method instead of inheriting from
+ // boost::noncopyable so as to avoid multiple inheritance.)
+ RBNodeRRset(const RBNodeRRset& source);
+ RBNodeRRset& operator=(const RBNodeRRset& source);
+
+public:
+ /// \brief Usual Constructor
+ ///
+ /// Creates an RBNodeRRset from the pointer to the RRset passed to it.
+ ///
+ /// \param rrset Pointer to underlying RRset encapsulated by this object.
+ explicit RBNodeRRset(const isc::dns::ConstRRsetPtr& rrset);
+
+ /// \brief Destructor
+ virtual ~RBNodeRRset();
+
+ // Getter and Setter Methods
+ //
+ // The getter methods pass the call through to the underlying RRset. The
+ // setter methods thrown an exception - this specialisation of the RRset
+ // object does not expect the underlying RRset to be modified.
+
+ virtual unsigned int getRdataCount() const;
+
+ virtual const isc::dns::Name& getName() const;
+
+ virtual const isc::dns::RRClass& getClass() const;
+
+ virtual const isc::dns::RRType& getType() const;
+
+ virtual const isc::dns::RRTTL& getTTL() const;
+
+ virtual void setName(const isc::dns::Name&);
+
+ virtual void setTTL(const isc::dns::RRTTL&);
+
+ virtual std::string toText() const;
+
+ virtual bool isSameKind(const AbstractRRset& other) const {
+ // This code is an optimisation for comparing
+ // RBNodeRRsets. However, in doing this optimisation,
+ // semantically the code is not "is same kind" but is instead
+ // "is identical object" in the case where RBNodeRRsets are compared.
+
+ const RBNodeRRset* rb = dynamic_cast<const RBNodeRRset*>(&other);
+ if (rb != NULL) {
+ return (this == rb);
+ } else {
+ return (AbstractRRset::isSameKind(other));
+ }
+ }
+
+ virtual unsigned int toWire(
+ isc::dns::AbstractMessageRenderer& renderer) const;
+
+ virtual unsigned int toWire(isc::util::OutputBuffer& buffer) const;
+
+ virtual void addRdata(isc::dns::rdata::ConstRdataPtr);
+
+ virtual void addRdata(const isc::dns::rdata::Rdata&);
+
+ virtual isc::dns::RdataIteratorPtr getRdataIterator() const;
+
+ virtual isc::dns::RRsetPtr getRRsig() const;
+
+ // With all the RRsig methods, we have the problem that we store the
+ // underlying RRset using a ConstRRsetPtr - a pointer to a "const" RRset -
+ // but we need to modify it by adding or removing an RRSIG. We overcome
+ // this by temporarily violating the "const" nature of the RRset to add the
+ // data.
+
+ virtual void addRRsig(const isc::dns::rdata::ConstRdataPtr& rdata);
+
+ virtual void addRRsig(const isc::dns::rdata::RdataPtr& rdata);
+
+ virtual void addRRsig(const AbstractRRset& sigs);
+
+ virtual void addRRsig(const isc::dns::ConstRRsetPtr& sigs);
+
+ virtual void addRRsig(const isc::dns::RRsetPtr& sigs);
+
+ virtual void removeRRsig();
+
+ /// \brief Associate a link to an RB node of the additional record.
+ ///
+ /// This method adds a given opaque object that holds a link to an RB node
+ /// of the underlying in-memory data source that is corresponding to an
+ /// RDATA of this RRset.
+ ///
+ /// This method is exposed as public so it can be used within the in-memory
+ /// data source implementation, and only for that purpose.
+ ///
+ /// \param additional An opaque \c AdditionalNodeInfo object to be
+ /// associated with this RRset.
+ void addAdditionalNode(const AdditionalNodeInfo& additional);
+
+ /// \brief Return a pointer to the list (vector) of additional RB nodes.
+ ///
+ /// This method returns a pointer to a vector storing the opaque
+ /// \c AdditionalNodeInfo object that may be possibly set in this RRset.
+ /// Not all RRsets are associated with additional nodes; if no
+ /// such node is stored, this method returns NULL.
+ ///
+ /// Like \c addAdditionalNode(), this method is exposed as public only for
+ /// the in-memory data source implementation.
+ ///
+ /// \return A pointer to the associated vector of \c AdditionalNodeInfo;
+ /// NULL if no additional nodes are associated to this RRset.
+ const std::vector<AdditionalNodeInfo>* getAdditionalNodes() const;
+
+ /// \brief Copy the list of additional RB nodes to another RRset.
+ ///
+ /// This method copies the internal list (an STL vector in the actual
+ /// implementation) of additional RB nodes for this RRset to another
+ /// \c RBNodeRRset object. The copy destination is generally expected to
+ /// be newly created and have an empty list, but this method does not
+ /// check the condition. If the destination already has a non empty list,
+ /// the existing entries will be lost.
+ ///
+ /// \param dst The \c RBNodeRRset object to which the additional
+ /// RB node list is to be copied.
+ void copyAdditionalNodes(RBNodeRRset& dst) const;
+
+ /// \brief Return underlying RRset pointer
+ ///
+ /// ... mainly for testing.
+ isc::dns::ConstRRsetPtr getUnderlyingRRset() const;
+
+private:
+ RBNodeRRsetImpl* impl_;
+};
+
+} // namespace internal
+} // namespace datasrc
+} // namespace isc
+
+#endif // __RBNODE_RRSET_H
diff --git a/src/lib/datasrc/rbtree.h b/src/lib/datasrc/rbtree.h
index 4757a45..dbf0591 100644
--- a/src/lib/datasrc/rbtree.h
+++ b/src/lib/datasrc/rbtree.h
@@ -123,7 +123,9 @@ public:
/// set to on by the \c setFlag() method.
enum Flags {
FLAG_CALLBACK = 1, ///< Callback enabled. See \ref callback
- FLAG_USER1 = 0x80000000U ///< Application specific flag
+ FLAG_USER1 = 0x80000000U, ///< Application specific flag
+ FLAG_USER2 = 0x40000000U, ///< Application specific flag
+ FLAG_USER3 = 0x20000000U ///< Application specific flag
};
private:
// Some flag values are expected to be used for internal purposes
@@ -131,7 +133,8 @@ private:
// 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);
+ static const uint32_t SETTABLE_FLAGS = (FLAG_CALLBACK | FLAG_USER1 |
+ FLAG_USER2 | FLAG_USER3);
public:
@@ -1159,6 +1162,10 @@ RBTree<T>::insert(const isc::dns::Name& target_name, RBNode<T>** new_node) {
}
+// Note: when we redesign this (still keeping the basic concept), we should
+// change this part so the newly created node will be used for the inserted
+// name (and therefore the name for the existing node doesn't change).
+// Otherwise, things like shortcut links between nodes won't work.
template <typename T>
void
RBTree<T>::nodeFission(RBNode<T>& node, const isc::dns::Name& base_name) {
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index fb2ffef..ba21de8 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -15,9 +15,10 @@
#include <sqlite3.h>
#include <string>
+#include <utility>
#include <vector>
-#include <boost/foreach.hpp>
+#include <exceptions/exceptions.h>
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/logger.h>
@@ -29,9 +30,20 @@
using namespace std;
using namespace isc::data;
-#define SQLITE_SCHEMA_VERSION 1
-
-#define CONFIG_ITEM_DATABASE_FILE "database_file"
+namespace {
+// Expected schema. The major version must match else there is an error. If
+// the minor version of the database is less than this, a warning is output.
+//
+// It is assumed that a program written to run on m.n of the database will run
+// with a database version m.p, where p is any number. However, if p < n,
+// we assume that the database structure was upgraded for some reason, and that
+// some advantage may result if the database is upgraded. Conversely, if p > n,
+// The database is at a later version than the program was written for and the
+// program may not be taking advantage of features (possibly performance
+// improvements) added to the database.
+const int SQLITE_SCHEMA_MAJOR_VERSION = 2;
+const int SQLITE_SCHEMA_MINOR_VERSION = 0;
+}
namespace isc {
namespace datasrc {
@@ -54,11 +66,16 @@ enum StatementID {
ITERATE = 9,
FIND_PREVIOUS = 10,
ADD_RECORD_DIFF = 11,
- GET_RECORD_DIFF = 12, // This is temporary for testing "add diff"
- LOW_DIFF_ID = 13,
- HIGH_DIFF_ID = 14,
- DIFF_RECS = 15,
- NUM_STATEMENTS = 16
+ LOW_DIFF_ID = 12,
+ HIGH_DIFF_ID = 13,
+ DIFF_RECS = 14,
+ NSEC3 = 15,
+ NSEC3_PREVIOUS = 16,
+ NSEC3_LAST = 17,
+ ADD_NSEC3_RECORD = 18,
+ DEL_ZONE_NSEC3_RECORDS = 19,
+ DEL_NSEC3_RECORD = 20,
+ NUM_STATEMENTS = 21
};
const char* const text_statements[NUM_STATEMENTS] = {
@@ -78,8 +95,19 @@ const char* const text_statements[NUM_STATEMENTS] = {
"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
"DELETE FROM records WHERE zone_id=?1 AND name=?2 " // DEL_RECORD
"AND rdtype=?3 AND rdata=?4",
- "SELECT rdtype, ttl, sigtype, rdata, name FROM records " // ITERATE
- "WHERE zone_id = ?1 ORDER BY rname, rdtype",
+ // The following iterates the whole zone. As the NSEC3 records
+ // (and corresponding RRSIGs) live in separate table, we need to
+ // take both of them. As the RRSIGs are for NSEC3s in the other
+ // table, we can easily hardcode the sigtype.
+ //
+ // The extra column is so we can order it by rname. This is to
+ // preserve the previous order, mostly for tests.
+ // TODO: Is it possible to get rid of the ordering?
+ "SELECT rdtype, ttl, sigtype, rdata, name, rname FROM records " // ITERATE
+ "WHERE zone_id = ?1 "
+ "UNION "
+ "SELECT rdtype, ttl, \"NSEC3\", rdata, owner, owner FROM nsec3 "
+ "WHERE zone_id = ?1 ORDER by rname, rdtype",
/*
* This one looks for previous name with NSEC record. It is done by
* using the reversed name. The NSEC is checked because we need to
@@ -87,12 +115,10 @@ const char* const text_statements[NUM_STATEMENTS] = {
*/
"SELECT name FROM records " // FIND_PREVIOUS
"WHERE zone_id=?1 AND rdtype = 'NSEC' AND "
- "rname < $2 ORDER BY rname DESC LIMIT 1",
+ "rname < ?2 ORDER BY rname DESC LIMIT 1",
"INSERT INTO diffs " // ADD_RECORD_DIFF
"(zone_id, version, operation, name, rrtype, ttl, rdata) "
"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
- "SELECT name, rrtype, ttl, rdata, version, operation " // GET_RECORD_DIFF
- "FROM diffs WHERE zone_id = ?1 ORDER BY id, operation",
// Two statements to select the lowest ID and highest ID in a set of
// differences.
@@ -107,13 +133,35 @@ const char* const text_statements[NUM_STATEMENTS] = {
// that the columns match the column IDs passed to the iterator
"SELECT rrtype, ttl, id, rdata, name FROM diffs " // DIFF_RECS
"WHERE zone_id=?1 AND id>=?2 and id<=?3 "
- "ORDER BY id ASC"
+ "ORDER BY id ASC",
+
+ // NSEC3: Query to get the NSEC3 records
+ //
+ // The "1" in SELECT is for positioning the rdata column to the
+ // expected position, so we can reuse the same code as for other
+ // lookups.
+ "SELECT rdtype, ttl, 1, rdata FROM nsec3 WHERE zone_id=?1 AND "
+ "hash=?2",
+ // NSEC3_PREVIOUS: For getting the previous NSEC3 hash
+ "SELECT DISTINCT hash FROM nsec3 WHERE zone_id=?1 AND hash < ?2 "
+ "ORDER BY hash DESC LIMIT 1",
+ // NSEC3_LAST: And for wrap-around
+ "SELECT DISTINCT hash FROM nsec3 WHERE zone_id=?1 "
+ "ORDER BY hash DESC LIMIT 1",
+ // ADD_NSEC3_RECORD: Add NSEC3-related (NSEC3 or NSEC3-covering RRSIG) RR
+ "INSERT INTO nsec3 (zone_id, hash, owner, ttl, rdtype, rdata) "
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
+ // DEL_ZONE_NSEC3_RECORDS: delete all NSEC3-related records from the zone
+ "DELETE FROM nsec3 WHERE zone_id=?1",
+ // DEL_NSEC3_RECORD: delete specified NSEC3-related records
+ "DELETE FROM nsec3 WHERE zone_id=?1 AND hash=?2 "
+ "AND rdtype=?3 AND rdata=?4"
};
struct SQLite3Parameters {
SQLite3Parameters() :
- db_(NULL), version_(-1), in_transaction(false), updating_zone(false),
- updated_zone_id(-1)
+ db_(NULL), major_version_(-1), minor_version_(-1),
+ in_transaction(false), updating_zone(false), updated_zone_id(-1)
{
for (int i = 0; i < NUM_STATEMENTS; ++i) {
statements_[i] = NULL;
@@ -151,10 +199,12 @@ struct SQLite3Parameters {
}
sqlite3* db_;
- int version_;
+ int major_version_;
+ int minor_version_;
bool in_transaction; // whether or not a transaction has been started
bool updating_zone; // whether or not updating the zone
int updated_zone_id; // valid only when in_transaction is true
+ string updated_zone_origin_; // ditto, and only needed to handle NSEC3s
private:
// statements_ are private and must be accessed via getStatement() outside
// of this structure.
@@ -169,6 +219,10 @@ private:
// statement, which is completed with a single "step" (normally within a
// single call to an SQLite3Database method). In particular, it cannot be
// used for "SELECT" variants, which generally expect multiple matching rows.
+//
+// The bindXXX methods are straightforward wrappers for the corresponding
+// sqlite3_bind_xxx functions that make bindings with the given parameters
+// on the statement maintained in this class.
class StatementProcessor {
public:
// desc will be used on failure in the what() message of the resulting
@@ -185,6 +239,33 @@ public:
sqlite3_reset(stmt_);
}
+ void bindInt(int index, int val) {
+ if (sqlite3_bind_int(stmt_, index, val) != SQLITE_OK) {
+ isc_throw(DataSourceError,
+ "failed to bind SQLite3 parameter: " <<
+ sqlite3_errmsg(dbparameters_.db_));
+ }
+ }
+
+ void bindInt64(int index, sqlite3_int64 val) {
+ if (sqlite3_bind_int64(stmt_, index, val) != SQLITE_OK) {
+ isc_throw(DataSourceError,
+ "failed to bind SQLite3 parameter: " <<
+ sqlite3_errmsg(dbparameters_.db_));
+ }
+ }
+
+ // For simplicity, we assume val is a NULL-terminated string, and the
+ // entire non NUL characters are to be bound. The destructor parameter
+ // is normally either SQLITE_TRANSIENT or SQLITE_STATIC.
+ void bindText(int index, const char* val, void(*destructor)(void*)) {
+ if (sqlite3_bind_text(stmt_, index, val, -1, destructor)
+ != SQLITE_OK) {
+ isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
+ sqlite3_errmsg(dbparameters_.db_));
+ }
+ }
+
void exec() {
if (sqlite3_step(stmt_) != SQLITE_DONE) {
sqlite3_reset(stmt_);
@@ -242,34 +323,42 @@ public:
};
const char* const SCHEMA_LIST[] = {
- "CREATE TABLE schema_version (version INTEGER NOT NULL)",
- "INSERT INTO schema_version VALUES (1)",
+ "CREATE TABLE schema_version (version INTEGER NOT NULL, "
+ "minor INTEGER NOT NULL DEFAULT 0)",
+ "INSERT INTO schema_version VALUES (2, 0)",
"CREATE TABLE zones (id INTEGER PRIMARY KEY, "
- "name STRING NOT NULL COLLATE NOCASE, "
- "rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN', "
+ "name TEXT NOT NULL COLLATE NOCASE, "
+ "rdclass TEXT NOT NULL COLLATE NOCASE DEFAULT 'IN', "
"dnssec BOOLEAN NOT NULL DEFAULT 0)",
"CREATE INDEX zones_byname ON zones (name)",
"CREATE TABLE records (id INTEGER PRIMARY KEY, "
- "zone_id INTEGER NOT NULL, name STRING NOT NULL COLLATE NOCASE, "
- "rname STRING NOT NULL COLLATE NOCASE, ttl INTEGER NOT NULL, "
- "rdtype STRING NOT NULL COLLATE NOCASE, sigtype STRING COLLATE NOCASE, "
- "rdata STRING NOT NULL)",
+ "zone_id INTEGER NOT NULL, name TEXT NOT NULL COLLATE NOCASE, "
+ "rname TEXT NOT NULL COLLATE NOCASE, ttl INTEGER NOT NULL, "
+ "rdtype TEXT NOT NULL COLLATE NOCASE, sigtype TEXT COLLATE NOCASE, "
+ "rdata TEXT NOT NULL)",
"CREATE INDEX records_byname ON records (name)",
"CREATE INDEX records_byrname ON records (rname)",
+ // The next index is a tricky one. It's necessary for
+ // FIND_PREVIOUS to use the index efficiently; since there's an
+ // "inequality", the rname column must be placed later. records_byrname
+ // may not be sufficient especially when the zone is not signed (and
+ // defining a separate index for rdtype only doesn't work either; SQLite3
+ // would then create a temporary B-tree for "ORDER BY").
+ "CREATE INDEX records_bytype_and_rname ON records (rdtype, rname)",
"CREATE TABLE nsec3 (id INTEGER PRIMARY KEY, zone_id INTEGER NOT NULL, "
- "hash STRING NOT NULL COLLATE NOCASE, "
- "owner STRING NOT NULL COLLATE NOCASE, "
- "ttl INTEGER NOT NULL, rdtype STRING NOT NULL COLLATE NOCASE, "
- "rdata STRING NOT NULL)",
+ "hash TEXT NOT NULL COLLATE NOCASE, "
+ "owner TEXT NOT NULL COLLATE NOCASE, "
+ "ttl INTEGER NOT NULL, rdtype TEXT NOT NULL COLLATE NOCASE, "
+ "rdata TEXT NOT NULL)",
"CREATE INDEX nsec3_byhash ON nsec3 (hash)",
"CREATE TABLE diffs (id INTEGER PRIMARY KEY, "
"zone_id INTEGER NOT NULL, "
"version INTEGER NOT NULL, "
"operation INTEGER NOT NULL, "
- "name STRING NOT NULL COLLATE NOCASE, "
- "rrtype STRING NOT NULL COLLATE NOCASE, "
+ "name TEXT NOT NULL COLLATE NOCASE, "
+ "rrtype TEXT NOT NULL COLLATE NOCASE, "
"ttl INTEGER NOT NULL, "
- "rdata STRING NOT NULL)",
+ "rdata TEXT NOT NULL)",
NULL
};
@@ -295,14 +384,13 @@ void doSleep() {
// returns the schema version if the schema version table exists
// returns -1 if it does not
-int checkSchemaVersion(sqlite3* db) {
+int checkSchemaVersionElement(sqlite3* db, const char* const query) {
sqlite3_stmt* prepared = NULL;
// At this point in time, the database might be exclusively locked, in
// which case even prepare() will return BUSY, so we may need to try a
// few times
for (size_t i = 0; i < 50; ++i) {
- int rc = sqlite3_prepare_v2(db, "SELECT version FROM schema_version",
- -1, &prepared, NULL);
+ int rc = sqlite3_prepare_v2(db, query, -1, &prepared, NULL);
if (rc == SQLITE_ERROR) {
// this is the error that is returned when the table does not
// exist
@@ -324,50 +412,116 @@ int checkSchemaVersion(sqlite3* db) {
return (version);
}
+// Returns the schema major and minor version numbers in a pair.
+// Returns (-1, -1) if the table does not exist, (1, 0) for a V1
+// database, and (n, m) for any other.
+pair<int, int> checkSchemaVersion(sqlite3* db) {
+ int major = checkSchemaVersionElement(db,
+ "SELECT version FROM schema_version");
+ if (major == -1) {
+ return (make_pair(-1, -1));
+ } else if (major == 1) {
+ return (make_pair(1, 0));
+ } else {
+ int minor = checkSchemaVersionElement(db,
+ "SELECT minor FROM schema_version");
+ return (make_pair(major, minor));
+ }
+}
+
+// A helper class used in createDatabase() below so we manage the one shot
+// transaction safely.
+class ScopedTransaction {
+public:
+ ScopedTransaction(sqlite3* db) : db_(NULL) {
+ // try for 5 secs (50*0.1)
+ for (size_t i = 0; i < 50; ++i) {
+ const int rc = sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION",
+ NULL, NULL, NULL);
+ if (rc == SQLITE_OK) {
+ break;
+ } else if (rc != SQLITE_BUSY || i == 50) {
+ isc_throw(SQLite3Error, "Unable to acquire exclusive lock "
+ "for database creation: " << sqlite3_errmsg(db));
+ }
+ doSleep();
+ }
+ // Hold the DB pointer once we have successfully acquired the lock.
+ db_ = db;
+ }
+ ~ScopedTransaction() {
+ if (db_ != NULL) {
+ // Note: even rollback could fail in theory, but in that case
+ // we cannot do much for safe recovery anyway. We could at least
+ // log the event, but for now don't even bother to do that, with
+ // the expectation that we'll soon stop creating the schema in this
+ // module.
+ sqlite3_exec(db_, "ROLLBACK", NULL, NULL, NULL);
+ }
+ }
+ void commit() {
+ if (sqlite3_exec(db_, "COMMIT TRANSACTION", NULL, NULL, NULL) !=
+ SQLITE_OK) {
+ isc_throw(SQLite3Error, "Unable to commit newly created database "
+ "schema: " << sqlite3_errmsg(db_));
+ }
+ db_ = NULL;
+ }
+
+private:
+ sqlite3* db_;
+};
+
// return db version
-int create_database(sqlite3* db) {
+pair<int, int>
+createDatabase(sqlite3* db) {
+ logger.info(DATASRC_SQLITE_SETUP);
+
// try to get an exclusive lock. Once that is obtained, do the version
// check *again*, just in case this process was racing another
- //
- // try for 5 secs (50*0.1)
- int rc;
- logger.info(DATASRC_SQLITE_SETUP);
- for (size_t i = 0; i < 50; ++i) {
- rc = sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL,
- NULL);
- if (rc == SQLITE_OK) {
- break;
- } else if (rc != SQLITE_BUSY || i == 50) {
- isc_throw(SQLite3Error, "Unable to acquire exclusive lock "
- "for database creation: " << sqlite3_errmsg(db));
- }
- doSleep();
- }
- int schema_version = checkSchemaVersion(db);
- if (schema_version == -1) {
+ ScopedTransaction trasaction(db);
+ pair<int, int> schema_version = checkSchemaVersion(db);
+ if (schema_version.first == -1) {
for (int i = 0; SCHEMA_LIST[i] != NULL; ++i) {
if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
SQLITE_OK) {
isc_throw(SQLite3Error,
- "Failed to set up schema " << SCHEMA_LIST[i]);
+ "Failed to set up schema " << SCHEMA_LIST[i]);
}
}
- sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, NULL);
- return (SQLITE_SCHEMA_VERSION);
- } else {
- return (schema_version);
+ trasaction.commit();
+
+ // Return the version. We query again to ensure that the only point
+ // in which the current schema version is defined is in the create
+ // statements.
+ schema_version = checkSchemaVersion(db);
}
+
+ return (schema_version);
}
void
checkAndSetupSchema(Initializer* initializer) {
sqlite3* const db = initializer->params_.db_;
- int schema_version = checkSchemaVersion(db);
- if (schema_version != SQLITE_SCHEMA_VERSION) {
- schema_version = create_database(db);
- }
- initializer->params_.version_ = schema_version;
+ pair<int, int> schema_version = checkSchemaVersion(db);
+ if (schema_version.first == -1) {
+ schema_version = createDatabase(db);
+ } else if (schema_version.first != SQLITE_SCHEMA_MAJOR_VERSION) {
+ LOG_ERROR(logger, DATASRC_SQLITE_INCOMPATIBLE_VERSION)
+ .arg(schema_version.first).arg(schema_version.second)
+ .arg(SQLITE_SCHEMA_MAJOR_VERSION).arg(SQLITE_SCHEMA_MINOR_VERSION);
+ isc_throw(IncompatibleDbVersion,
+ "incompatible SQLite3 database version: " <<
+ schema_version.first << "." << schema_version.second);
+ } else if (schema_version.second < SQLITE_SCHEMA_MINOR_VERSION) {
+ LOG_WARN(logger, DATASRC_SQLITE_COMPATIBLE_VERSION)
+ .arg(schema_version.first).arg(schema_version.second)
+ .arg(SQLITE_SCHEMA_MAJOR_VERSION).arg(SQLITE_SCHEMA_MINOR_VERSION);
+ }
+
+ initializer->params_.major_version_ = schema_version.first;
+ initializer->params_.minor_version_ = schema_version.second;
}
}
@@ -486,19 +640,46 @@ public:
bindZoneId(id);
}
+ // What kind of query it is - selection of the statement for DB
+ enum QueryType {
+ QT_ANY, // Directly for a domain
+ QT_SUBDOMAINS, // Subdomains of a given domain
+ QT_NSEC3 // Domain in the NSEC3 namespace (the name is is the hash,
+ // not the whole name)
+ };
+
// Construct an iterator for records with a specific name. When constructed
// this way, the getNext() call will copy all fields except name
Context(const boost::shared_ptr<const SQLite3Accessor>& accessor, int id,
- const std::string& name, bool subdomains) :
- iterator_type_(ITT_NAME),
+ const std::string& name, QueryType qtype) :
+ iterator_type_(qtype == QT_NSEC3 ? ITT_NSEC3 : ITT_NAME),
accessor_(accessor),
statement_(NULL),
name_(name)
{
+ // Choose the statement text depending on the query type
+ const char* statement(NULL);
+ switch (qtype) {
+ case QT_ANY:
+ statement = text_statements[ANY];
+ break;
+ case QT_SUBDOMAINS:
+ statement = text_statements[ANY_SUB];
+ break;
+ case QT_NSEC3:
+ statement = text_statements[NSEC3];
+ break;
+ default:
+ // Can Not Happen - there isn't any other type of query
+ // and all the calls to the constructor are from this
+ // file. Therefore no way to test it throws :-(.
+ isc_throw(Unexpected,
+ "Invalid qtype passed - unreachable code branch "
+ "reached");
+ }
+
// We create the statement now and then just keep getting data from it
- statement_ = prepare(accessor->dbparameters_->db_,
- subdomains ? text_statements[ANY_SUB] :
- text_statements[ANY]);
+ statement_ = prepare(accessor->dbparameters_->db_, statement);
bindZoneId(id);
bindName(name_);
}
@@ -515,7 +696,11 @@ public:
// For both types, we copy the first four columns
copyColumn(data, TYPE_COLUMN);
copyColumn(data, TTL_COLUMN);
- copyColumn(data, SIGTYPE_COLUMN);
+ // The NSEC3 lookup does not provide the SIGTYPE, it is not
+ // necessary and not contained in the table.
+ if (iterator_type_ != ITT_NSEC3) {
+ copyColumn(data, SIGTYPE_COLUMN);
+ }
copyColumn(data, RDATA_COLUMN);
// Only copy Name if we are iterating over every record
if (iterator_type_ == ITT_ALL) {
@@ -541,7 +726,8 @@ private:
// See description of getNext() and the constructors
enum IteratorType {
ITT_ALL,
- ITT_NAME
+ ITT_NAME,
+ ITT_NSEC3
};
void copyColumn(std::string (&data)[COLUMN_COUNT], int column) {
@@ -588,7 +774,15 @@ SQLite3Accessor::getRecords(const std::string& name, int id,
bool subdomains) const
{
return (IteratorContextPtr(new Context(shared_from_this(), id, name,
- subdomains)));
+ subdomains ?
+ Context::QT_SUBDOMAINS :
+ Context::QT_ANY)));
+}
+
+DatabaseAccessor::IteratorContextPtr
+SQLite3Accessor::getNSEC3Records(const std::string& hash, int id) const {
+ return (IteratorContextPtr(new Context(shared_from_this(), id, hash,
+ Context::QT_NSEC3)));
}
DatabaseAccessor::IteratorContextPtr
@@ -652,7 +846,7 @@ public:
///
/// \return bool true if data is returned, false if not.
///
- /// \exceptions any Varied
+ /// \exception any Varied
bool getNext(std::string (&data)[COLUMN_COUNT]) {
if (last_status_ != SQLITE_DONE) {
@@ -805,7 +999,7 @@ private:
// No data returned but the SQL query succeeded. Only possibility
// is that there is no entry in the differences table for the given
// zone and version.
- isc_throw(NoSuchSerial, "No entry in differences table for " <<
+ isc_throw(NoSuchSerial, "No entry in differences table for" <<
" zone ID " << zone_id << ", serial number " << serial);
}
@@ -867,19 +1061,22 @@ SQLite3Accessor::startUpdateZone(const string& zone_name, const bool replace) {
"start an SQLite3 update transaction").exec();
if (replace) {
+ // First, clear all current data from tables.
+ typedef pair<StatementID, const char* const> StatementSpec;
+ const StatementSpec delzone_stmts[] =
+ { StatementSpec(DEL_ZONE_RECORDS, "delete zone records"),
+ StatementSpec(DEL_ZONE_NSEC3_RECORDS,
+ "delete zone NSEC3 records") };
try {
- StatementProcessor delzone_exec(*dbparameters_, DEL_ZONE_RECORDS,
- "delete zone records");
-
- sqlite3_stmt* stmt = dbparameters_->getStatement(DEL_ZONE_RECORDS);
- sqlite3_clear_bindings(stmt);
- if (sqlite3_bind_int(stmt, 1, zone_info.second) != SQLITE_OK) {
- isc_throw(DataSourceError,
- "failed to bind SQLite3 parameter: " <<
- sqlite3_errmsg(dbparameters_->db_));
+ for (size_t i = 0;
+ i < sizeof(delzone_stmts) / sizeof(delzone_stmts[0]);
+ ++i) {
+ StatementProcessor delzone_proc(*dbparameters_,
+ delzone_stmts[i].first,
+ delzone_stmts[i].second);
+ delzone_proc.bindInt(1, zone_info.second);
+ delzone_proc.exec();
}
-
- delzone_exec.exec();
} catch (const DataSourceError&) {
// Once we start a transaction, if something unexpected happens
// we need to rollback the transaction so that a subsequent update
@@ -893,6 +1090,7 @@ SQLite3Accessor::startUpdateZone(const string& zone_name, const bool replace) {
dbparameters_->in_transaction = true;
dbparameters_->updating_zone = true;
dbparameters_->updated_zone_id = zone_info.second;
+ dbparameters_->updated_zone_origin_ = zone_name;
return (zone_info);
}
@@ -919,7 +1117,9 @@ SQLite3Accessor::commit() {
StatementProcessor(*dbparameters_, COMMIT,
"commit an SQLite3 transaction").exec();
dbparameters_->in_transaction = false;
+ dbparameters_->updating_zone = false;
dbparameters_->updated_zone_id = -1;
+ dbparameters_->updated_zone_origin_.clear();
}
void
@@ -932,7 +1132,9 @@ SQLite3Accessor::rollback() {
StatementProcessor(*dbparameters_, ROLLBACK,
"rollback an SQLite3 transaction").exec();
dbparameters_->in_transaction = false;
+ dbparameters_->updating_zone = false;
dbparameters_->updated_zone_id = -1;
+ dbparameters_->updated_zone_origin_.clear();
}
namespace {
@@ -942,29 +1144,19 @@ void
doUpdate(SQLite3Parameters& dbparams, StatementID stmt_id,
COLUMNS_TYPE update_params, const char* exec_desc)
{
- sqlite3_stmt* const stmt = dbparams.getStatement(stmt_id);
- StatementProcessor executer(dbparams, stmt_id, exec_desc);
+ StatementProcessor proc(dbparams, stmt_id, exec_desc);
int param_id = 0;
- if (sqlite3_bind_int(stmt, ++param_id, dbparams.updated_zone_id)
- != SQLITE_OK) {
- isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
- sqlite3_errmsg(dbparams.db_));
- }
+ proc.bindInt(++param_id, dbparams.updated_zone_id);
const size_t column_count =
sizeof(update_params) / sizeof(update_params[0]);
for (int i = 0; i < column_count; ++i) {
// The old sqlite3 data source API assumes NULL for an empty column.
// We need to provide compatibility at least for now.
- if (sqlite3_bind_text(stmt, ++param_id,
- update_params[i].empty() ? NULL :
- update_params[i].c_str(),
- -1, SQLITE_TRANSIENT) != SQLITE_OK) {
- isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
- sqlite3_errmsg(dbparams.db_));
- }
+ proc.bindText(++param_id, update_params[i].empty() ? NULL :
+ update_params[i].c_str(), SQLITE_TRANSIENT);
}
- executer.exec();
+ proc.exec();
}
}
@@ -974,21 +1166,58 @@ SQLite3Accessor::addRecordToZone(const string (&columns)[ADD_COLUMN_COUNT]) {
isc_throw(DataSourceError, "adding record to SQLite3 "
"data source without transaction");
}
- doUpdate<const string (&)[DatabaseAccessor::ADD_COLUMN_COUNT]>(
+ doUpdate<const string (&)[ADD_COLUMN_COUNT]>(
*dbparameters_, ADD_RECORD, columns, "add record to zone");
}
void
+SQLite3Accessor::addNSEC3RecordToZone(
+ const string (&columns)[ADD_NSEC3_COLUMN_COUNT])
+{
+ if (!dbparameters_->updating_zone) {
+ isc_throw(DataSourceError, "adding NSEC3-related record to SQLite3 "
+ "data source without transaction");
+ }
+
+ // XXX: the current implementation of SQLite3 schema requires the 'owner'
+ // column, and the current implementation of getAllRecords() relies on it,
+ // while the addNSEC3RecordToZone interface doesn't provide it explicitly.
+ // We should revisit it at the design level, but for now we internally
+ // convert the given parameter to satisfy the internal requirements.
+ const string sqlite3_columns[ADD_NSEC3_COLUMN_COUNT + 1] =
+ { columns[ADD_NSEC3_HASH],
+ columns[ADD_NSEC3_HASH] + "." + dbparameters_->updated_zone_origin_,
+ columns[ADD_NSEC3_TTL],
+ columns[ADD_NSEC3_TYPE], columns[ADD_NSEC3_RDATA] };
+ doUpdate<const string (&)[ADD_NSEC3_COLUMN_COUNT + 1]>(
+ *dbparameters_, ADD_NSEC3_RECORD, sqlite3_columns,
+ "add NSEC3 record to zone");
+}
+
+void
SQLite3Accessor::deleteRecordInZone(const string (¶ms)[DEL_PARAM_COUNT]) {
if (!dbparameters_->updating_zone) {
isc_throw(DataSourceError, "deleting record in SQLite3 "
"data source without transaction");
}
- doUpdate<const string (&)[DatabaseAccessor::DEL_PARAM_COUNT]>(
+ doUpdate<const string (&)[DEL_PARAM_COUNT]>(
*dbparameters_, DEL_RECORD, params, "delete record from zone");
}
void
+SQLite3Accessor::deleteNSEC3RecordInZone(
+ const string (¶ms)[DEL_PARAM_COUNT])
+{
+ if (!dbparameters_->updating_zone) {
+ isc_throw(DataSourceError, "deleting NSEC3-related record in SQLite3 "
+ "data source without transaction");
+ }
+ doUpdate<const string (&)[DEL_PARAM_COUNT]>(
+ *dbparameters_, DEL_NSEC3_RECORD, params,
+ "delete NSEC3 record from zone");
+}
+
+void
SQLite3Accessor::addRecordDiff(int zone_id, uint32_t serial,
DiffOperation operation,
const std::string (¶ms)[DIFF_PARAM_COUNT])
@@ -1003,53 +1232,16 @@ SQLite3Accessor::addRecordDiff(int zone_id, uint32_t serial,
<< dbparameters_->updated_zone_id);
}
- sqlite3_stmt* const stmt = dbparameters_->getStatement(ADD_RECORD_DIFF);
- StatementProcessor executer(*dbparameters_, ADD_RECORD_DIFF,
- "add record diff");
+ StatementProcessor proc(*dbparameters_, ADD_RECORD_DIFF,
+ "add record diff");
int param_id = 0;
- if (sqlite3_bind_int(stmt, ++param_id, zone_id)
- != SQLITE_OK) {
- isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
- sqlite3_errmsg(dbparameters_->db_));
- }
- if (sqlite3_bind_int64(stmt, ++param_id, serial)
- != SQLITE_OK) {
- isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
- sqlite3_errmsg(dbparameters_->db_));
- }
- if (sqlite3_bind_int(stmt, ++param_id, operation)
- != SQLITE_OK) {
- isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
- sqlite3_errmsg(dbparameters_->db_));
- }
+ proc.bindInt(++param_id, zone_id);
+ proc.bindInt64(++param_id, serial);
+ proc.bindInt(++param_id, operation);
for (int i = 0; i < DIFF_PARAM_COUNT; ++i) {
- if (sqlite3_bind_text(stmt, ++param_id, params[i].c_str(),
- -1, SQLITE_TRANSIENT) != SQLITE_OK) {
- isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
- sqlite3_errmsg(dbparameters_->db_));
- }
+ proc.bindText(++param_id, params[i].c_str(), SQLITE_TRANSIENT);
}
- executer.exec();
-}
-
-vector<vector<string> >
-SQLite3Accessor::getRecordDiff(int zone_id) {
- sqlite3_stmt* const stmt = dbparameters_->getStatement(GET_RECORD_DIFF);
- sqlite3_bind_int(stmt, 1, zone_id);
-
- vector<vector<string> > result;
- while (sqlite3_step(stmt) == SQLITE_ROW) {
- vector<string> row_result;
- for (int i = 0; i < 6; ++i) {
- row_result.push_back(convertToPlainChar(sqlite3_column_text(stmt,
- i),
- dbparameters_->db_));
- }
- result.push_back(row_result);
- }
- sqlite3_reset(stmt);
-
- return (result);
+ proc.exec();
}
std::string
@@ -1096,74 +1288,74 @@ SQLite3Accessor::findPreviousName(int zone_id, const std::string& rname)
return (result);
}
-namespace {
-void
-addError(ElementPtr errors, const std::string& error) {
- if (errors != ElementPtr() && errors->getType() == Element::list) {
- errors->add(Element::create(error));
+std::string
+SQLite3Accessor::findPreviousNSEC3Hash(int zone_id, const std::string& hash)
+ const
+{
+ sqlite3_stmt* const stmt = dbparameters_->getStatement(NSEC3_PREVIOUS);
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ if (sqlite3_bind_int(stmt, 1, zone_id) != SQLITE_OK) {
+ isc_throw(SQLite3Error, "Could not bind zone ID " << zone_id <<
+ " to SQL statement (find previous NSEC3): " <<
+ sqlite3_errmsg(dbparameters_->db_));
+ }
+ if (sqlite3_bind_text(stmt, 2, hash.c_str(), -1, SQLITE_STATIC) !=
+ SQLITE_OK) {
+ isc_throw(SQLite3Error, "Could not bind hash " << hash <<
+ " to SQL statement (find previous NSEC3): " <<
+ sqlite3_errmsg(dbparameters_->db_));
}
-}
-bool
-checkConfig(ConstElementPtr config, ElementPtr errors) {
- /* Specific configuration is under discussion, right now this accepts
- * the 'old' configuration, see header file
- */
- bool result = true;
+ std::string result;
+ const int rc = sqlite3_step(stmt);
+ if (rc == SQLITE_ROW) {
+ // We found it
+ result = convertToPlainChar(sqlite3_column_text(stmt, 0),
+ dbparameters_->db_);
+ }
+ sqlite3_reset(stmt);
- if (!config || config->getType() != Element::map) {
- addError(errors, "Base config for SQlite3 backend must be a map");
- result = false;
- } else {
- if (!config->contains(CONFIG_ITEM_DATABASE_FILE)) {
- addError(errors,
- "Config for SQlite3 backend does not contain a '"
- CONFIG_ITEM_DATABASE_FILE
- "' value");
- result = false;
- } else if (!config->get(CONFIG_ITEM_DATABASE_FILE) ||
- config->get(CONFIG_ITEM_DATABASE_FILE)->getType() !=
- Element::string) {
- addError(errors, "value of " CONFIG_ITEM_DATABASE_FILE
- " in SQLite3 backend is not a string");
- result = false;
- } else if (config->get(CONFIG_ITEM_DATABASE_FILE)->stringValue() ==
- "") {
- addError(errors, "value of " CONFIG_ITEM_DATABASE_FILE
- " in SQLite3 backend is empty");
- result = false;
- }
+ if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
+ // Some kind of error
+ isc_throw(SQLite3Error, "Could not get data for previous hash");
}
- return (result);
-}
+ if (rc == SQLITE_DONE) {
+ // No NSEC3 records before this hash. This means we should wrap
+ // around and take the last one.
+ sqlite3_stmt* const stmt = dbparameters_->getStatement(NSEC3_LAST);
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ if (sqlite3_bind_int(stmt, 1, zone_id) != SQLITE_OK) {
+ isc_throw(SQLite3Error, "Could not bind zone ID " << zone_id <<
+ " to SQL statement (find last NSEC3): " <<
+ sqlite3_errmsg(dbparameters_->db_));
+ }
-} // end anonymous namespace
-
-DataSourceClient *
-createInstance(isc::data::ConstElementPtr config, std::string& error) {
- ElementPtr errors(Element::createList());
- if (!checkConfig(config, errors)) {
- error = "Configuration error: " + errors->str();
- return (NULL);
- }
- std::string dbfile = config->get(CONFIG_ITEM_DATABASE_FILE)->stringValue();
- try {
- boost::shared_ptr<DatabaseAccessor> sqlite3_accessor(
- new SQLite3Accessor(dbfile, "IN")); // XXX: avoid hardcode RR class
- return (new DatabaseClient(isc::dns::RRClass::IN(), sqlite3_accessor));
- } catch (const std::exception& exc) {
- error = std::string("Error creating sqlite3 datasource: ") + exc.what();
- return (NULL);
- } catch (...) {
- error = std::string("Error creating sqlite3 datasource, "
- "unknown exception");
- return (NULL);
+ const int rc = sqlite3_step(stmt);
+ if (rc == SQLITE_ROW) {
+ // We found it
+ result = convertToPlainChar(sqlite3_column_text(stmt, 0),
+ dbparameters_->db_);
+ }
+ sqlite3_reset(stmt);
+
+ if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
+ // Some kind of error
+ isc_throw(SQLite3Error, "Could not get data for last hash");
+ }
+
+ if (rc == SQLITE_DONE) {
+ // No NSEC3 at all in the zone. Well, bad luck, but you should not
+ // have asked in the first place.
+ isc_throw(DataSourceError, "No NSEC3 in this zone");
+ }
}
-}
-void destroyInstance(DataSourceClient* instance) {
- delete instance;
+ return (result);
}
} // end of namespace datasrc
diff --git a/src/lib/datasrc/sqlite3_accessor.h b/src/lib/datasrc/sqlite3_accessor.h
index 08be824..3e44d5b 100644
--- a/src/lib/datasrc/sqlite3_accessor.h
+++ b/src/lib/datasrc/sqlite3_accessor.h
@@ -47,6 +47,12 @@ public:
DataSourceError(file, line, what) {}
};
+class IncompatibleDbVersion : public Exception {
+public:
+ IncompatibleDbVersion(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
/**
* \brief Too Much Data
*
@@ -141,6 +147,14 @@ public:
int id,
bool subdomains = false) const;
+ /// \brief Look up NSEC3 records for the given hash
+ ///
+ /// This implements the getNSEC3Records of DatabaseAccessor.
+ ///
+ /// \todo Actually implement, currently throws NotImplemented.
+ virtual IteratorContextPtr getNSEC3Records(const std::string& hash,
+ int id) const;
+
/** \brief Look up all resource records for a zone
*
* This implements the getRecords() method from DatabaseAccessor
@@ -200,9 +214,15 @@ public:
virtual void addRecordToZone(
const std::string (&columns)[ADD_COLUMN_COUNT]);
+ virtual void addNSEC3RecordToZone(
+ const std::string (&columns)[ADD_NSEC3_COLUMN_COUNT]);
+
virtual void deleteRecordInZone(
const std::string (¶ms)[DEL_PARAM_COUNT]);
+ virtual void deleteNSEC3RecordInZone(
+ const std::string (¶ms)[DEL_PARAM_COUNT]);
+
/// This derived version of the method prepares an SQLite3 statement
/// for adding the diff first time it's called, and if it fails throws
// an \c SQLite3Error exception.
@@ -210,16 +230,6 @@ public:
int zone_id, uint32_t serial, DiffOperation operation,
const std::string (¶ms)[DIFF_PARAM_COUNT]);
- // A short term method for tests until we implement more complete
- // API to retrieve diffs (#1330). It returns all records of the diffs
- // table whose zone_id column is identical to the given value.
- // Since this is a short term workaround, it ignores some corner cases
- // (such as an SQLite3 execution failure) and is not very efficient,
- // in favor of brevity. Once #1330 is completed, this method must be
- // removed, and the tests using this method must be rewritten using the
- // official API.
- std::vector<std::vector<std::string> > getRecordDiff(int zone_id);
-
/// The SQLite3 implementation of this method returns a string starting
/// with a fixed prefix of "sqlite3_" followed by the DB file name
/// removing any path name. For example, for the DB file
@@ -231,6 +241,11 @@ public:
virtual std::string findPreviousName(int zone_id, const std::string& rname)
const;
+ /// \brief Conrete implemantion of the pure virtual method of
+ /// DatabaseAccessor
+ virtual std::string findPreviousNSEC3Hash(int zone_id,
+ const std::string& hash) const;
+
private:
/// \brief Private database data
boost::scoped_ptr<SQLite3Parameters> dbparameters_;
diff --git a/src/lib/datasrc/sqlite3_accessor_link.cc b/src/lib/datasrc/sqlite3_accessor_link.cc
new file mode 100644
index 0000000..c064e0f
--- /dev/null
+++ b/src/lib/datasrc/sqlite3_accessor_link.cc
@@ -0,0 +1,107 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <cc/data.h>
+
+#include <dns/rrclass.h>
+
+#include <datasrc/sqlite3_accessor.h>
+#include <datasrc/database.h>
+
+#include <string>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::data;
+
+namespace isc {
+namespace datasrc {
+
+namespace {
+
+const char* const CONFIG_ITEM_DATABASE_FILE = "database_file";
+
+void
+addError(ElementPtr errors, const std::string& error) {
+ if (errors != ElementPtr() && errors->getType() == Element::list) {
+ errors->add(Element::create(error));
+ }
+}
+
+bool
+checkConfig(ConstElementPtr config, ElementPtr errors) {
+ /* Specific configuration is under discussion, right now this accepts
+ * the 'old' configuration, see header file
+ */
+ bool result = true;
+
+ if (!config || config->getType() != Element::map) {
+ addError(errors, "Base config for SQlite3 backend must be a map");
+ result = false;
+ } else {
+ if (!config->contains(CONFIG_ITEM_DATABASE_FILE)) {
+ addError(errors,
+ "Config for SQlite3 backend does not contain a '" +
+ string(CONFIG_ITEM_DATABASE_FILE) +
+ "' value");
+ result = false;
+ } else if (!config->get(CONFIG_ITEM_DATABASE_FILE) ||
+ config->get(CONFIG_ITEM_DATABASE_FILE)->getType() !=
+ Element::string) {
+ addError(errors, "value of " + string(CONFIG_ITEM_DATABASE_FILE) +
+ " in SQLite3 backend is not a string");
+ result = false;
+ } else if (config->get(CONFIG_ITEM_DATABASE_FILE)->stringValue() ==
+ "") {
+ addError(errors, "value of " + string(CONFIG_ITEM_DATABASE_FILE) +
+ " in SQLite3 backend is empty");
+ result = false;
+ }
+ }
+
+ return (result);
+}
+
+} // end unnamed namespace
+
+DataSourceClient *
+createInstance(isc::data::ConstElementPtr config, std::string& error) {
+ ElementPtr errors(Element::createList());
+ if (!checkConfig(config, errors)) {
+ error = "Configuration error: " + errors->str();
+ return (NULL);
+ }
+ const std::string dbfile =
+ config->get(CONFIG_ITEM_DATABASE_FILE)->stringValue();
+ try {
+ boost::shared_ptr<DatabaseAccessor> sqlite3_accessor(
+ new SQLite3Accessor(dbfile, "IN")); // XXX: avoid hardcode RR class
+ return (new DatabaseClient(isc::dns::RRClass::IN(), sqlite3_accessor));
+ } catch (const std::exception& exc) {
+ error = std::string("Error creating sqlite3 datasource: ") +
+ exc.what();
+ return (NULL);
+ } catch (...) {
+ error = std::string("Error creating sqlite3 datasource, "
+ "unknown exception");
+ return (NULL);
+ }
+}
+
+void destroyInstance(DataSourceClient* instance) {
+ delete instance;
+}
+
+} // end of namespace datasrc
+} // end of namespace isc
diff --git a/src/lib/datasrc/sqlite3_datasrc.cc b/src/lib/datasrc/sqlite3_datasrc.cc
index 03b057c..b450cd5 100644
--- a/src/lib/datasrc/sqlite3_datasrc.cc
+++ b/src/lib/datasrc/sqlite3_datasrc.cc
@@ -14,19 +14,33 @@
#include <string>
#include <sstream>
+#include <utility>
#include <sqlite3.h>
#include <datasrc/sqlite3_datasrc.h>
#include <datasrc/logger.h>
-
+#include <exceptions/exceptions.h>
#include <dns/rrttl.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/rrset.h>
#include <dns/rrsetlist.h>
-#define SQLITE_SCHEMA_VERSION 1
+namespace {
+// Expected schema. The major version must match else there is an error. If
+// the minor version of the database is less than this, a warning is output.
+//
+// It is assumed that a program written to run on m.n of the database will run
+// with a database version m.p, where p is any number. However, if p < n,
+// we assume that the database structure was upgraded for some reason, and that
+// some advantage may result if the database is upgraded. Conversely, if p > n,
+// The database is at a later version than the program was written for and the
+// program may not be taking advantage of features (possibly performance
+// improvements) added to the database.
+const int SQLITE_SCHEMA_MAJOR_VERSION = 2;
+const int SQLITE_SCHEMA_MINOR_VERSION = 0;
+}
using namespace std;
using namespace isc::dns;
@@ -36,13 +50,14 @@ namespace isc {
namespace datasrc {
struct Sqlite3Parameters {
- Sqlite3Parameters() : db_(NULL), version_(-1),
+ Sqlite3Parameters() : db_(NULL), major_version_(-1), minor_version_(-1),
q_zone_(NULL), q_record_(NULL), q_addrs_(NULL), q_referral_(NULL),
q_any_(NULL), q_count_(NULL), q_previous_(NULL), q_nsec3_(NULL),
q_prevnsec3_(NULL)
{}
sqlite3* db_;
- int version_;
+ int major_version_;
+ int minor_version_;
sqlite3_stmt* q_zone_;
sqlite3_stmt* q_record_;
sqlite3_stmt* q_addrs_;
@@ -56,30 +71,41 @@ struct Sqlite3Parameters {
namespace {
const char* const SCHEMA_LIST[] = {
- "CREATE TABLE schema_version (version INTEGER NOT NULL)",
- "INSERT INTO schema_version VALUES (1)",
+ "CREATE TABLE schema_version (version INTEGER NOT NULL, "
+ "minor INTEGER NOT NULL DEFAULT 0)",
+ "INSERT INTO schema_version VALUES (2, 0)",
"CREATE TABLE zones (id INTEGER PRIMARY KEY, "
- "name STRING NOT NULL COLLATE NOCASE, "
- "rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN', "
+ "name TEXT NOT NULL COLLATE NOCASE, "
+ "rdclass TEXT NOT NULL COLLATE NOCASE DEFAULT 'IN', "
"dnssec BOOLEAN NOT NULL DEFAULT 0)",
"CREATE INDEX zones_byname ON zones (name)",
"CREATE TABLE records (id INTEGER PRIMARY KEY, "
- "zone_id INTEGER NOT NULL, name STRING NOT NULL COLLATE NOCASE, "
- "rname STRING NOT NULL COLLATE NOCASE, ttl INTEGER NOT NULL, "
- "rdtype STRING NOT NULL COLLATE NOCASE, sigtype STRING COLLATE NOCASE, "
- "rdata STRING NOT NULL)",
+ "zone_id INTEGER NOT NULL, name TEXT NOT NULL COLLATE NOCASE, "
+ "rname TEXT NOT NULL COLLATE NOCASE, ttl INTEGER NOT NULL, "
+ "rdtype TEXT NOT NULL COLLATE NOCASE, sigtype TEXT COLLATE NOCASE, "
+ "rdata TEXT NOT NULL)",
"CREATE INDEX records_byname ON records (name)",
"CREATE INDEX records_byrname ON records (rname)",
+ "CREATE INDEX records_bytype_and_rname ON records (rdtype, rname)",
"CREATE TABLE nsec3 (id INTEGER PRIMARY KEY, zone_id INTEGER NOT NULL, "
- "hash STRING NOT NULL COLLATE NOCASE, "
- "owner STRING NOT NULL COLLATE NOCASE, "
- "ttl INTEGER NOT NULL, rdtype STRING NOT NULL COLLATE NOCASE, "
- "rdata STRING NOT NULL)",
+ "hash TEXT NOT NULL COLLATE NOCASE, "
+ "owner TEXT NOT NULL COLLATE NOCASE, "
+ "ttl INTEGER NOT NULL, rdtype TEXT NOT NULL COLLATE NOCASE, "
+ "rdata TEXT NOT NULL)",
"CREATE INDEX nsec3_byhash ON nsec3 (hash)",
+ "CREATE TABLE diffs (id INTEGER PRIMARY KEY, "
+ "zone_id INTEGER NOT NULL, "
+ "version INTEGER NOT NULL, "
+ "operation INTEGER NOT NULL, "
+ "name TEXT NOT NULL COLLATE NOCASE, "
+ "rrtype TEXT NOT NULL COLLATE NOCASE, "
+ "ttl INTEGER NOT NULL, "
+ "rdata TEXT NOT NULL)",
NULL
};
const char* const q_version_str = "SELECT version FROM schema_version";
+const char* const q_minor_str = "SELECT minor FROM schema_version";
const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1";
@@ -101,12 +127,16 @@ const char* const q_referral_str = "SELECT rdtype, ttl, sigtype, rdata FROM "
const char* const q_any_str = "SELECT rdtype, ttl, sigtype, rdata "
"FROM records WHERE zone_id=?1 AND name=?2";
+// Note: the wildcard symbol '%' is expected to be added to the text
+// for the placeholder for LIKE given via sqlite3_bind_text(). We don't
+// use the expression such as (?2 || '%') because it would disable the use
+// of indices and could result in terrible performance.
const char* const q_count_str = "SELECT COUNT(*) FROM records "
- "WHERE zone_id=?1 AND rname LIKE (?2 || '%');";
+ "WHERE zone_id=?1 AND rname LIKE ?2;";
const char* const q_previous_str = "SELECT name FROM records "
- "WHERE zone_id=?1 AND rdtype = 'NSEC' AND "
- "rname < $2 ORDER BY rname DESC LIMIT 1";
+ "WHERE rname < ?2 AND zone_id=?1 AND rdtype = 'NSEC' "
+ "ORDER BY rname DESC LIMIT 1";
const char* const q_nsec3_str = "SELECT rdtype, ttl, rdata FROM nsec3 "
"WHERE zone_id = ?1 AND hash = $2";
@@ -306,8 +336,9 @@ Sqlite3DataSrc::findRecords(const Name& name, const RRType& rdtype,
" to SQL statement (qcount)");
}
- const string revname_text = name.reverse().toText();
- rc = sqlite3_bind_text(dbparameters->q_count_, 2, revname_text.c_str(),
+ const string revname_text = name.reverse().toText() + "%";
+ rc = sqlite3_bind_text(dbparameters->q_count_, 2,
+ revname_text.c_str(),
-1, SQLITE_STATIC);
if (rc != SQLITE_OK) {
isc_throw(Sqlite3Error, "Could not bind name " << name.reverse() <<
@@ -667,15 +698,15 @@ void do_sleep() {
nanosleep(&req, NULL);
}
-// returns the schema version if the schema version table exists
+// returns the schema version element if the schema version table exists
// returns -1 if it does not
-int check_schema_version(sqlite3* db) {
+int check_schema_version_element(sqlite3* db, const char* const version_query) {
sqlite3_stmt* prepared = NULL;
// At this point in time, the database might be exclusively locked, in
// which case even prepare() will return BUSY, so we may need to try a
// few times
for (size_t i = 0; i < 50; ++i) {
- int rc = sqlite3_prepare_v2(db, q_version_str, -1, &prepared, NULL);
+ int rc = sqlite3_prepare_v2(db, version_query, -1, &prepared, NULL);
if (rc == SQLITE_ERROR) {
// this is the error that is returned when the table does not
// exist
@@ -697,27 +728,73 @@ int check_schema_version(sqlite3* db) {
return (version);
}
+// Returns the schema major and minor version numbers in a pair.
+// Returns (-1, -1) if the table does not exist, (1, 0) for a V1
+// database, and (n, m) for any other.
+pair<int, int> check_schema_version(sqlite3* db) {
+ int major = check_schema_version_element(db, q_version_str);
+ if (major == -1) {
+ return (make_pair(-1, -1));
+ } else if (major == 1) {
+ return (make_pair(1, 0));
+ } else {
+ int minor = check_schema_version_element(db, q_minor_str);
+ return (make_pair(major, minor));
+ }
+}
+
+// A helper class used in create_database() below so we manage the one shot
+// transaction safely.
+class ScopedTransaction {
+public:
+ ScopedTransaction(sqlite3* db) : db_(NULL) {
+ // try for 5 secs (50*0.1)
+ for (size_t i = 0; i < 50; ++i) {
+ const int rc = sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION",
+ NULL, NULL, NULL);
+ if (rc == SQLITE_OK) {
+ break;
+ } else if (rc != SQLITE_BUSY || i == 50) {
+ isc_throw(Sqlite3Error, "Unable to acquire exclusive lock "
+ "for database creation: " << sqlite3_errmsg(db));
+ }
+ do_sleep();
+ }
+ // Hold the DB pointer once we have successfully acquired the lock.
+ db_ = db;
+ }
+ ~ScopedTransaction() {
+ if (db_ != NULL) {
+ // Note: even rollback could fail in theory, but in that case
+ // we cannot do much for safe recovery anyway. We could at least
+ // log the event, but for now don't even bother to do that, with
+ // the expectation that we'll soon stop creating the schema in this
+ // module.
+ sqlite3_exec(db_, "ROLLBACK", NULL, NULL, NULL);
+ }
+ }
+ void commit() {
+ if (sqlite3_exec(db_, "COMMIT TRANSACTION", NULL, NULL, NULL) !=
+ SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Unable to commit newly created database "
+ "schema: " << sqlite3_errmsg(db_));
+ }
+ db_ = NULL;
+ }
+
+private:
+ sqlite3* db_;
+};
+
// return db version
-int create_database(sqlite3* db) {
+pair<int, int> create_database(sqlite3* db) {
+ logger.info(DATASRC_SQLITE_SETUP);
+
// try to get an exclusive lock. Once that is obtained, do the version
// check *again*, just in case this process was racing another
- //
- // try for 5 secs (50*0.1)
- int rc;
- logger.info(DATASRC_SQLITE_SETUP);
- for (size_t i = 0; i < 50; ++i) {
- rc = sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL,
- NULL);
- if (rc == SQLITE_OK) {
- break;
- } else if (rc != SQLITE_BUSY || i == 50) {
- isc_throw(Sqlite3Error, "Unable to acquire exclusive lock "
- "for database creation: " << sqlite3_errmsg(db));
- }
- do_sleep();
- }
- int schema_version = check_schema_version(db);
- if (schema_version == -1) {
+ ScopedTransaction transaction(db);
+ pair<int, int> schema_version = check_schema_version(db);
+ if (schema_version.first == -1) {
for (int i = 0; SCHEMA_LIST[i] != NULL; ++i) {
if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
SQLITE_OK) {
@@ -725,23 +802,40 @@ int create_database(sqlite3* db) {
"Failed to set up schema " << SCHEMA_LIST[i]);
}
}
- sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, NULL);
- return (SQLITE_SCHEMA_VERSION);
- } else {
- return (schema_version);
+ transaction.commit();
+
+ // Return the version. We query again to ensure that the only point
+ // in which the current schema version is defined is in the
+ // CREATE statements.
+ schema_version = check_schema_version(db);
}
+ return (schema_version);
}
void
checkAndSetupSchema(Sqlite3Initializer* initializer) {
sqlite3* const db = initializer->params_.db_;
- int schema_version = check_schema_version(db);
- if (schema_version != SQLITE_SCHEMA_VERSION) {
+ // Note: we use the same SCHEMA_xxx_VERSION log IDs here and in
+ // sqlite3_accessor.cc, which is against our policy of ID uniqueness.
+ // The assumption is that this file will soon be deprecated, and we don't
+ // bother to define separate IDs for the short period.
+ pair<int, int> schema_version = check_schema_version(db);
+ if (schema_version.first == -1) {
schema_version = create_database(db);
- }
- initializer->params_.version_ = schema_version;
-
+ } else if (schema_version.first != SQLITE_SCHEMA_MAJOR_VERSION) {
+ LOG_ERROR(logger, DATASRC_SQLITE_INCOMPATIBLE_VERSION)
+ .arg(schema_version.first).arg(schema_version.second)
+ .arg(SQLITE_SCHEMA_MAJOR_VERSION).arg(SQLITE_SCHEMA_MINOR_VERSION);
+ isc_throw(IncompatibleDbVersion, "Incompatible database version");
+ } else if (schema_version.second < SQLITE_SCHEMA_MINOR_VERSION) {
+ LOG_WARN(logger, DATASRC_SQLITE_COMPATIBLE_VERSION)
+ .arg(schema_version.first).arg(schema_version.second)
+ .arg(SQLITE_SCHEMA_MAJOR_VERSION).arg(SQLITE_SCHEMA_MINOR_VERSION);
+ }
+
+ initializer->params_.major_version_ = schema_version.first;
+ initializer->params_.minor_version_ = schema_version.second;
initializer->params_.q_zone_ = prepare(db, q_zone_str);
initializer->params_.q_record_ = prepare(db, q_record_str);
initializer->params_.q_addrs_ = prepare(db, q_addrs_str);
diff --git a/src/lib/datasrc/sqlite3_datasrc.h b/src/lib/datasrc/sqlite3_datasrc.h
index d4abef7..8ee042f 100644
--- a/src/lib/datasrc/sqlite3_datasrc.h
+++ b/src/lib/datasrc/sqlite3_datasrc.h
@@ -41,6 +41,12 @@ public:
isc::Exception(file, line, what) {}
};
+class IncompatibleDbVersion : public Exception {
+public:
+ IncompatibleDbVersion(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
class Sqlite3DataSrc : public DataSrc {
///
/// \name Constructors, Assignment Operator and Destructor.
diff --git a/src/lib/datasrc/static_datasrc.cc b/src/lib/datasrc/static_datasrc.cc
index fd43e1c..77d7a1d 100644
--- a/src/lib/datasrc/static_datasrc.cc
+++ b/src/lib/datasrc/static_datasrc.cc
@@ -73,14 +73,17 @@ StaticDataSrcImpl::StaticDataSrcImpl() :
authors->addRdata(generic::TXT("Dmitriy Volodin"));
authors->addRdata(generic::TXT("Evan Hunt"));
authors->addRdata(generic::TXT("Haidong Wang")); // Ocean
+ authors->addRdata(generic::TXT("Haikuo Zhang"));
authors->addRdata(generic::TXT("Han Feng"));
authors->addRdata(generic::TXT("Jelte Jansen"));
authors->addRdata(generic::TXT("Jeremy C. Reed"));
+ authors->addRdata(generic::TXT("Xie Jiagui")); // Kevin Tes
authors->addRdata(generic::TXT("Jin Jian"));
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("Mukund Sivaraman"));
authors->addRdata(generic::TXT("Naoki Kambe"));
authors->addRdata(generic::TXT("Shane Kerr"));
authors->addRdata(generic::TXT("Shen Tingting"));
diff --git a/src/lib/datasrc/tests/.gitignore b/src/lib/datasrc/tests/.gitignore
new file mode 100644
index 0000000..bae5d90
--- /dev/null
+++ b/src/lib/datasrc/tests/.gitignore
@@ -0,0 +1,4 @@
+/run_unittests
+/run_unittests_factory
+/run_unittests_memory
+/run_unittests_sqlite3
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 113d03c..90fb3e4 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -5,6 +5,7 @@ AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/lib/dns
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += $(SQLITE_CFLAGS)
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_COMMONDIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_builddir)/testdata\"
AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
@@ -19,17 +20,13 @@ CLEANFILES = *.gcno *.gcda
TESTS =
noinst_PROGRAMS =
if HAVE_GTEST
-TESTS += run_unittests run_unittests_sqlite3 run_unittests_memory
+TESTS += run_unittests
-#
-# For each specific datasource, there is a separate binary that includes
-# the code itself (we can't unittest through the public API). These need
-# to be separate because the included code, by design, contains conflicting
-# symbols.
-# We also have a 'general' run_unittests with non-datasource-specific tests
-#
+# We have two sets of tests: the general tests and factory tests (see below
+# for the latter). They are separate binary files sharing some program files
+# and libraries.
-# First define the parts shared by all
+# First define the parts shared by both
common_sources = run_unittests.cc
common_sources += $(top_srcdir)/src/lib/dns/tests/unittest_util.h
common_sources += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
@@ -45,46 +42,35 @@ common_ldadd += $(top_builddir)/src/lib/cc/libcc.la
common_ldadd += $(top_builddir)/src/lib/testutils/libtestutils.la
common_ldadd += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
-
# The general tests
run_unittests_SOURCES = $(common_sources)
run_unittests_SOURCES += datasrc_unittest.cc
run_unittests_SOURCES += static_unittest.cc
run_unittests_SOURCES += query_unittest.cc
run_unittests_SOURCES += cache_unittest.cc
+run_unittests_SOURCES += test_client.h test_client.cc
run_unittests_SOURCES += test_datasrc.h test_datasrc.cc
run_unittests_SOURCES += rbtree_unittest.cc
run_unittests_SOURCES += logger_unittest.cc
run_unittests_SOURCES += client_unittest.cc
+run_unittests_SOURCES += database_unittest.cc
+run_unittests_SOURCES += sqlite3_unittest.cc
+run_unittests_SOURCES += sqlite3_accessor_unittest.cc
+run_unittests_SOURCES += memory_datasrc_unittest.cc
+run_unittests_SOURCES += rbnode_rrset_unittest.cc
+run_unittests_SOURCES += zone_finder_context_unittest.cc
+run_unittests_SOURCES += faked_nsec3.h faked_nsec3.cc
+
+# We need the actual module implementation in the tests (they are not part
+# of libdatasrc)
+run_unittests_SOURCES += $(top_srcdir)/src/lib/datasrc/sqlite3_accessor.cc
+run_unittests_SOURCES += $(top_srcdir)/src/lib/datasrc/memory_datasrc.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
run_unittests_LDADD = $(common_ldadd)
-
-# SQlite3 datasource tests
-run_unittests_sqlite3_SOURCES = $(common_sources)
-run_unittests_sqlite3_SOURCES += database_unittest.cc
-run_unittests_sqlite3_SOURCES += sqlite3_unittest.cc
-run_unittests_sqlite3_SOURCES += sqlite3_accessor_unittest.cc
-run_unittests_sqlite3_SOURCES += $(top_srcdir)/src/lib/datasrc/sqlite3_accessor.cc
-
-run_unittests_sqlite3_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_sqlite3_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
-
-run_unittests_sqlite3_LDADD = $(common_ldadd)
-
-# In-memory datasource tests
-run_unittests_memory_SOURCES = $(common_sources)
-run_unittests_memory_SOURCES += memory_datasrc_unittest.cc
-run_unittests_memory_SOURCES += $(top_srcdir)/src/lib/datasrc/memory_datasrc.cc
-
-run_unittests_memory_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_memory_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
-
-run_unittests_memory_LDADD = $(common_ldadd)
-
noinst_PROGRAMS+= $(TESTS)
# For the factory unit tests, we need to specify that we want
@@ -108,17 +94,23 @@ endif
endif
EXTRA_DIST = testdata/brokendb.sqlite3
+EXTRA_DIST += testdata/contexttest.zone
+EXTRA_DIST += testdata/diffs.sqlite3
+EXTRA_DIST += testdata/example2.com
+EXTRA_DIST += testdata/example2.com.sqlite3
EXTRA_DIST += testdata/example.com.signed
EXTRA_DIST += testdata/example.org
+EXTRA_DIST += testdata/example.org.nsec3-signed
+EXTRA_DIST += testdata/example.org.nsec3-signed-noparam
EXTRA_DIST += testdata/example.org.sqlite3
-EXTRA_DIST += testdata/example2.com
-EXTRA_DIST += testdata/example2.com.sqlite3
EXTRA_DIST += testdata/mkbrokendb.c
EXTRA_DIST += testdata/root.zone
+EXTRA_DIST += testdata/rrset_toWire1
+EXTRA_DIST += testdata/rrset_toWire2
EXTRA_DIST += testdata/sql1.example.com.signed
EXTRA_DIST += testdata/sql2.example.com.signed
EXTRA_DIST += testdata/test-root.sqlite3
EXTRA_DIST += testdata/test.sqlite3
-EXTRA_DIST += testdata/test.sqlite3.nodiffs
-EXTRA_DIST += testdata/rwtest.sqlite3
-EXTRA_DIST += testdata/diffs.sqlite3
+EXTRA_DIST += testdata/new_minor_schema.sqlite3
+EXTRA_DIST += testdata/newschema.sqlite3
+EXTRA_DIST += testdata/oldschema.sqlite3
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index c56ba40..110b3f0 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -12,18 +12,15 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <stdlib.h>
-
-#include <boost/shared_ptr.hpp>
-#include <boost/lexical_cast.hpp>
-
-#include <gtest/gtest.h>
+#include "faked_nsec3.h"
#include <exceptions/exceptions.h>
+#include <dns/masterload.h>
#include <dns/name.h>
#include <dns/rrttl.h>
#include <dns/rrset.h>
+#include <dns/nsec3hash.h>
#include <exceptions/exceptions.h>
#include <datasrc/database.h>
@@ -34,6 +31,13 @@
#include <testutils/dnsmessage_test.h>
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <cstdlib>
#include <map>
#include <vector>
@@ -44,6 +48,8 @@ using namespace std;
using boost::dynamic_pointer_cast;
using boost::lexical_cast;
using namespace isc::dns;
+using namespace isc::testutils;
+using namespace isc::datasrc::test;
namespace {
@@ -167,7 +173,10 @@ const char* const TEST_RECORDS[][5] = {
"1234 3600 1800 2419200 7200" },
{"example.org.", "NS", "3600", "", "ns.example.com."},
{"example.org.", "A", "3600", "", "192.0.2.1"},
- {"example.org.", "NSEC", "3600", "", "acnamesig1.example.org. NS A NSEC RRSIG"},
+ // Note that the RDATA text is "normalized", i.e., identical to what
+ // Rdata::toText() would produce. some tests rely on that behavior.
+ {"example.org.", "NSEC", "3600", "",
+ "acnamesig1.example.org. A NS RRSIG NSEC"},
{"example.org.", "RRSIG", "3600", "", "SOA 5 3 3600 20000101000000 "
"20000201000000 12345 example.org. FAKEFAKEFAKE"},
{"example.org.", "RRSIG", "3600", "", "NSEC 5 3 3600 20000101000000 "
@@ -205,6 +214,19 @@ const char* const TEST_RECORDS[][5] = {
{NULL, NULL, NULL, NULL, NULL},
};
+// FIXME: Taken from a different test. Fill with proper data when creating a test.
+const char* TEST_NSEC3_RECORDS[][5] = {
+ {apex_hash, "NSEC3", "300", "", "1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG"},
+ {apex_hash, "RRSIG", "300", "", "NSEC3 5 4 7200 20100410172647 20100311172647 63192 example.org. gNIVj4T8t51fEU6kOPpvK7HOGBFZGbalN5ZK mInyrww6UWZsUNdw07ge6/U6HfG+/s61RZ/L is2M6yUWHyXbNbj/QqwqgadG5dhxTArfuR02 xP600x0fWX8LXzW4yLMdKVxGbzYT+vvGz71o 8gHSY5vYTtothcZQa4BMKhmGQEk="},
+ {ns1_hash, "NSEC3", "300", "", "1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG"},
+ {ns1_hash, "RRSIG", "300", "", "NSEC3 5 4 7200 20100410172647 20100311172647 63192 example.org. gNIVj4T8t51fEU6kOPpvK7HOGBFZGbalN5ZK mInyrww6UWZsUNdw07ge6/U6HfG+/s61RZ/L is2M6yUWHyXbNbj/QqwqgadG5dhxTArfuR02 xP600x0fWX8LXzW4yLMdKVxGbzYT+vvGz71o 8gHSY5vYTtothcZQa4BMKhmGQEk="},
+ {w_hash, "NSEC3", "300", "", "1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG"},
+ {w_hash, "RRSIG", "300", "", "NSEC3 5 4 7200 20100410172647 20100311172647 63192 example.org. gNIVj4T8t51fEU6kOPpvK7HOGBFZGbalN5ZK mInyrww6UWZsUNdw07ge6/U6HfG+/s61RZ/L is2M6yUWHyXbNbj/QqwqgadG5dhxTArfuR02 xP600x0fWX8LXzW4yLMdKVxGbzYT+vvGz71o 8gHSY5vYTtothcZQa4BMKhmGQEk="},
+ {zzz_hash, "NSEC3", "300", "", "1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG"},
+ {zzz_hash, "RRSIG", "300", "", "NSEC3 5 4 7200 20100410172647 20100311172647 63192 example.org. gNIVj4T8t51fEU6kOPpvK7HOGBFZGbalN5ZK mInyrww6UWZsUNdw07ge6/U6HfG+/s61RZ/L is2M6yUWHyXbNbj/QqwqgadG5dhxTArfuR02 xP600x0fWX8LXzW4yLMdKVxGbzYT+vvGz71o 8gHSY5vYTtothcZQa4BMKhmGQEk="},
+ {NULL, NULL, NULL, NULL, NULL}
+};
+
/*
* An accessor with minimum implementation, keeping the original
* "NotImplemented" methods.
@@ -241,7 +263,10 @@ public:
virtual void commit() {}
virtual void rollback() {}
virtual void addRecordToZone(const string (&)[ADD_COLUMN_COUNT]) {}
+ virtual void addNSEC3RecordToZone(const string (&)[ADD_NSEC3_COLUMN_COUNT])
+ {}
virtual void deleteRecordInZone(const string (&)[DEL_PARAM_COUNT]) {}
+ virtual void deleteNSEC3RecordInZone(const string (&)[DEL_PARAM_COUNT]) {}
virtual void addRecordDiff(int, uint32_t, DiffOperation,
const std::string (&)[DIFF_PARAM_COUNT]) {}
@@ -251,11 +276,16 @@ public:
virtual IteratorContextPtr getRecords(const std::string&, int, bool)
const
- {
+ {
isc_throw(isc::NotImplemented,
"This database datasource can't be iterated");
}
+ virtual IteratorContextPtr getNSEC3Records(const std::string&, int) const {
+ isc_throw(isc::NotImplemented, "This test database datasource won't "
+ "give you any NSEC3. Ever. Ask someone else.");
+ }
+
virtual IteratorContextPtr getAllRecords(int) const {
isc_throw(isc::NotImplemented,
"This database datasource can't be iterated");
@@ -270,6 +300,11 @@ public:
isc_throw(isc::NotImplemented,
"This data source doesn't support DNSSEC");
}
+
+ virtual std::string findPreviousNSEC3Hash(int, const std::string&) const {
+ isc_throw(isc::NotImplemented,
+ "This test database knows nothing about NSEC3 nor order");
+ }
private:
const std::string database_name_;
@@ -344,6 +379,8 @@ public:
MockAccessor() : rollbacked_(false), did_transaction_(false) {
readonly_records_ = &readonly_records_master_;
update_records_ = &update_records_master_;
+ nsec3_namespace_ = &nsec3_namespace_master_;
+ update_nsec3_namespace_ = &update_nsec3_namespace_master_;
empty_records_ = &empty_records_master_;
journal_entries_ = &journal_entries_master_;
fillData();
@@ -353,6 +390,9 @@ public:
boost::shared_ptr<MockAccessor> cloned_accessor(new MockAccessor());
cloned_accessor->readonly_records_ = &readonly_records_master_;
cloned_accessor->update_records_ = &update_records_master_;
+ cloned_accessor->nsec3_namespace_ = &nsec3_namespace_master_;
+ cloned_accessor->update_nsec3_namespace_ =
+ &update_nsec3_namespace_master_;
cloned_accessor->empty_records_ = &empty_records_master_;
cloned_accessor->journal_entries_ = &journal_entries_master_;
latest_clone_ = cloned_accessor;
@@ -378,6 +418,28 @@ public:
}
private:
+ class DomainIterator : public IteratorContext {
+ public:
+ DomainIterator(const std::vector<std::vector<std::string> >& domain) :
+ domain_(domain),
+ position_(domain_.begin())
+ {}
+ virtual bool getNext(std::string (&columns)[COLUMN_COUNT]) {
+ if (position_ == domain_.end()) {
+ return (false);
+ } else {
+ for (size_t i(0); i < COLUMN_COUNT; ++ i) {
+ columns[i] = (*position_)[i];
+ }
+ ++ position_;
+ return (true);
+ }
+ }
+ private:
+ const std::vector<std::vector<std::string> > domain_;
+ std::vector<std::vector<std::string> >::const_iterator position_;
+ };
+
class MockNameIteratorContext : public IteratorContext {
public:
MockNameIteratorContext(const MockAccessor& mock_accessor, int zone_id,
@@ -462,62 +524,46 @@ private:
}
// Return faked data for tests
- switch (step ++) {
- case 0:
- data[DatabaseAccessor::NAME_COLUMN] = "example.org";
- data[DatabaseAccessor::TYPE_COLUMN] = "A";
- data[DatabaseAccessor::TTL_COLUMN] = "3600";
- data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.1";
- return (true);
- case 1:
- data[DatabaseAccessor::NAME_COLUMN] = "example.org";
- data[DatabaseAccessor::TYPE_COLUMN] = "SOA";
- data[DatabaseAccessor::TTL_COLUMN] = "3600";
- data[DatabaseAccessor::RDATA_COLUMN] = "ns1.example.org. admin.example.org. "
- "1234 3600 1800 2419200 7200";
- return (true);
- case 2:
- data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
- data[DatabaseAccessor::TYPE_COLUMN] = "A";
- data[DatabaseAccessor::TTL_COLUMN] = "300";
- data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.1";
- return (true);
- case 3:
- data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
- data[DatabaseAccessor::TYPE_COLUMN] = "A";
- data[DatabaseAccessor::TTL_COLUMN] = "300";
- data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.2";
- return (true);
- case 4:
- data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
- data[DatabaseAccessor::TYPE_COLUMN] = "AAAA";
- data[DatabaseAccessor::TTL_COLUMN] = "300";
- data[DatabaseAccessor::RDATA_COLUMN] = "2001:db8::1";
- return (true);
- case 5:
- data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
- data[DatabaseAccessor::TYPE_COLUMN] = "AAAA";
- data[DatabaseAccessor::TTL_COLUMN] = "300";
- data[DatabaseAccessor::RDATA_COLUMN] = "2001:db8::2";
- return (true);
- case 6:
- data[DatabaseAccessor::NAME_COLUMN] = "ttldiff.example.org";
- data[DatabaseAccessor::TYPE_COLUMN] = "A";
- data[DatabaseAccessor::TTL_COLUMN] = "300";
- data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.1";
- return (true);
- case 7:
- data[DatabaseAccessor::NAME_COLUMN] = "ttldiff.example.org";
- data[DatabaseAccessor::TYPE_COLUMN] = "A";
- data[DatabaseAccessor::TTL_COLUMN] = "600";
- data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.2";
- return (true);
- default:
- ADD_FAILURE() <<
- "Request past the end of iterator context";
- case 8:
- return (false);
+ // This is the sequence of zone data in the order of appearance
+ // in the returned sequence from this iterator.
+ typedef const char* ColumnText[4];
+ const ColumnText zone_data[] = {
+ // A couple of basic RRs at the zone origin.
+ {"example.org", "A", "3600", "192.0.2.1"},
+ {"example.org", "SOA", "3600", "ns1.example.org. "
+ "admin.example.org. 1234 3600 1800 2419200 7200"},
+ // RRsets sharing the same owner name with multiple RRs.
+ {"x.example.org", "A", "300", "192.0.2.1"},
+ {"x.example.org", "A", "300", "192.0.2.2"},
+ {"x.example.org", "AAAA", "300", "2001:db8::1"},
+ {"x.example.org", "AAAA", "300", "2001:db8::2"},
+ // RRSIGs. Covered types are different and these two should
+ // be distinguished.
+ {"x.example.org", "RRSIG", "300",
+ "A 5 3 3600 20000101000000 20000201000000 12345 "
+ "example.org. FAKEFAKEFAKE"},
+ {"x.example.org", "RRSIG", "300",
+ "AAAA 5 3 3600 20000101000000 20000201000000 12345 "
+ "example.org. FAKEFAKEFAKEFAKE"},
+ // Mixture of different TTLs. Covering both cases of small
+ // then large and large then small. In either case the smaller
+ // TTL should win.
+ {"ttldiff.example.org", "A", "300", "192.0.2.1"},
+ {"ttldiff.example.org", "A", "600", "192.0.2.2"},
+ {"ttldiff2.example.org", "AAAA", "600", "2001:db8::1"},
+ {"ttldiff2.example.org", "AAAA", "300", "2001:db8::2"}};
+ const size_t num_rrs = sizeof(zone_data) / sizeof(zone_data[0]);
+ if (step > num_rrs) {
+ ADD_FAILURE() << "Request past the end of iterator context";
+ } else if (step < num_rrs) {
+ data[DatabaseAccessor::NAME_COLUMN] = zone_data[step][0];
+ data[DatabaseAccessor::TYPE_COLUMN] = zone_data[step][1];
+ data[DatabaseAccessor::TTL_COLUMN] = zone_data[step][2];
+ data[DatabaseAccessor::RDATA_COLUMN] = zone_data[step][3];
+ ++step;
+ return (true);
}
+ return (false);
}
};
class EmptyIteratorContext : public IteratorContext {
@@ -610,6 +656,17 @@ public:
}
}
+ virtual IteratorContextPtr getNSEC3Records(const std::string& hash,
+ int) const
+ {
+ Domains::const_iterator it(nsec3_namespace_->find(hash));
+ if (it == nsec3_namespace_->end()) {
+ return (IteratorContextPtr(new EmptyIteratorContext()));
+ } else {
+ return (IteratorContextPtr(new DomainIterator(it->second)));
+ }
+ }
+
virtual pair<bool, int> startUpdateZone(const std::string& zone_name,
bool replace)
{
@@ -623,8 +680,10 @@ public:
// original.
if (replace) {
update_records_->clear();
+ update_nsec3_namespace_->clear();
} else {
*update_records_ = *readonly_records_;
+ *update_nsec3_namespace_ = nsec3_namespace_master_;
}
if (zone_name == "bad.example.org.") {
@@ -637,7 +696,9 @@ public:
}
virtual void commit() {
*readonly_records_ = *update_records_;
+ *nsec3_namespace_ = *update_nsec3_namespace_;
}
+
virtual void rollback() {
// Special hook: if something with a name of "throw.example.org"
// has been added, trigger an imaginary unexpected event with an
@@ -648,27 +709,54 @@ public:
rollbacked_ = true;
}
- virtual void addRecordToZone(const string (&columns)[ADD_COLUMN_COUNT]) {
+
+private:
+ // Common subroutine for addRecordToZone and addNSEC3RecordToZone.
+ void addRecord(Domains& domains,
+ const string (&columns)[ADD_COLUMN_COUNT])
+ {
// Copy the current value to cur_name. If it doesn't exist,
// operator[] will create a new one.
- cur_name_ = (*update_records_)[columns[DatabaseAccessor::ADD_NAME]];
+ cur_name_ = domains[columns[ADD_NAME]];
vector<string> record_columns;
- record_columns.push_back(columns[DatabaseAccessor::ADD_TYPE]);
- record_columns.push_back(columns[DatabaseAccessor::ADD_TTL]);
- record_columns.push_back(columns[DatabaseAccessor::ADD_SIGTYPE]);
- record_columns.push_back(columns[DatabaseAccessor::ADD_RDATA]);
- record_columns.push_back(columns[DatabaseAccessor::ADD_NAME]);
+ record_columns.push_back(columns[ADD_TYPE]);
+ record_columns.push_back(columns[ADD_TTL]);
+ record_columns.push_back(columns[ADD_SIGTYPE]);
+ record_columns.push_back(columns[ADD_RDATA]);
+ record_columns.push_back(columns[ADD_NAME]);
// copy back the added entry
cur_name_.push_back(record_columns);
- (*update_records_)[columns[DatabaseAccessor::ADD_NAME]] = cur_name_;
+ domains[columns[DatabaseAccessor::ADD_NAME]] = cur_name_;
// remember this one so that test cases can check it.
copy(columns, columns + DatabaseAccessor::ADD_COLUMN_COUNT,
columns_lastadded_);
}
+public:
+ virtual void addRecordToZone(const string (&columns)[ADD_COLUMN_COUNT]) {
+ addRecord(*update_records_, columns);
+ }
+
+ virtual void addNSEC3RecordToZone(
+ const string (&columns)[ADD_NSEC3_COLUMN_COUNT])
+ {
+ // Convert the NSEC3 parameters in the normal (non NSEC3) style so
+ // we can share the merge code, and then update using addRecord().
+ string normal_columns[ADD_COLUMN_COUNT];
+
+ normal_columns[ADD_TYPE] = columns[ADD_NSEC3_TYPE];
+ normal_columns[ADD_TTL] = columns[ADD_NSEC3_TTL];
+ normal_columns[ADD_SIGTYPE] = "";
+ normal_columns[ADD_RDATA] = columns[ADD_NSEC3_RDATA];
+ normal_columns[ADD_NAME] = columns[ADD_NSEC3_HASH];
+
+ addRecord(*update_nsec3_namespace_, normal_columns);
+ }
+
+private:
// Helper predicate class used in deleteRecordInZone().
struct deleteMatch {
deleteMatch(const string& type, const string& rdata) :
@@ -681,19 +769,33 @@ public:
const string& rdata_;
};
- virtual void deleteRecordInZone(const string (¶ms)[DEL_PARAM_COUNT]) {
+ // Common subroutine for deleteRecordinZone and deleteNSEC3RecordInZone.
+ void deleteRecord(Domains& domains,
+ const string (¶ms)[DEL_PARAM_COUNT])
+ {
vector<vector<string> >& records =
- (*update_records_)[params[DatabaseAccessor::DEL_NAME]];
+ domains[params[DatabaseAccessor::DEL_NAME]];
records.erase(remove_if(records.begin(), records.end(),
deleteMatch(
params[DatabaseAccessor::DEL_TYPE],
params[DatabaseAccessor::DEL_RDATA])),
records.end());
if (records.empty()) {
- (*update_records_).erase(params[DatabaseAccessor::DEL_NAME]);
+ domains.erase(params[DatabaseAccessor::DEL_NAME]);
}
}
+public:
+ virtual void deleteRecordInZone(const string (¶ms)[DEL_PARAM_COUNT]) {
+ deleteRecord(*update_records_, params);
+ }
+
+ virtual void deleteNSEC3RecordInZone(
+ const string (¶ms)[DEL_PARAM_COUNT])
+ {
+ deleteRecord(*update_nsec3_namespace_, params);
+ }
+
//
// Helper methods to keep track of some update related activities
//
@@ -747,6 +849,21 @@ public:
isc_throw(isc::Unexpected, "Unknown zone ID");
}
}
+ virtual std::string findPreviousNSEC3Hash(int,
+ const std::string& hash) const
+ {
+ // TODO: Provide some broken data, but it is not known yet how broken
+ // they'll have to be.
+ Domains::const_iterator it(nsec3_namespace_->lower_bound(hash));
+ // We got just after the one we want
+ if (it == nsec3_namespace_->begin()) {
+ // Hmm, we got something really small. So we wrap around.
+ // This is one after the last, so after decreasing it we'll get
+ // the biggest.
+ it = nsec3_namespace_->end();
+ }
+ return ((--it)->first);
+ }
virtual void addRecordDiff(int id, uint32_t serial,
DiffOperation operation,
const std::string (&data)[DIFF_PARAM_COUNT])
@@ -827,6 +944,10 @@ private:
Domains* readonly_records_;
Domains update_records_master_;
Domains* update_records_;
+ Domains nsec3_namespace_master_;
+ Domains* nsec3_namespace_;
+ Domains update_nsec3_namespace_master_;
+ Domains* update_nsec3_namespace_;
const Domains empty_records_master_;
const Domains* empty_records_;
@@ -890,6 +1011,20 @@ private:
cur_name_.clear();
}
+ // Works in a similar way to addCurName, but it is added to
+ // the NSEC3 namespace. You don't provide the full name, only
+ // the hash part.
+ void addCurHash(const std::string& hash) {
+ ASSERT_EQ(0, nsec3_namespace_->count(hash));
+ // Append the name to all of them
+ for (std::vector<std::vector<std::string> >::iterator
+ i = cur_name_.begin(); i != cur_name_.end(); ++ i) {
+ i->push_back(hash);
+ }
+ (*nsec3_namespace_)[hash] = cur_name_;
+ cur_name_.clear();
+ }
+
// Fills the database with zone data.
// This method constructs a number of resource records (with addRecord),
// which will all be added for one domain name to the fake database
@@ -912,6 +1047,42 @@ private:
TEST_RECORDS[i][3], TEST_RECORDS[i][4]);
}
addCurName(prev_name);
+ prev_name = NULL;
+ for (int i = 0; TEST_NSEC3_RECORDS[i][0] != NULL; ++i) {
+ if (prev_name != NULL &&
+ strcmp(prev_name, TEST_NSEC3_RECORDS[i][0]) != 0) {
+ addCurHash(prev_name);
+ }
+ prev_name = TEST_NSEC3_RECORDS[i][0];
+ addRecord(TEST_NSEC3_RECORDS[i][1], TEST_NSEC3_RECORDS[i][2],
+ TEST_NSEC3_RECORDS[i][3], TEST_NSEC3_RECORDS[i][4]);
+ }
+ addCurHash(prev_name);
+ }
+
+public:
+ // This adds the NSEC3PARAM into the apex, so we can perform some NSEC3
+ // tests. Note that the NSEC3 namespace is available in other tests, but
+ // it should not be accessed at that time.
+ void enableNSEC3() {
+ // We place the signature first, so it's in the block with the other
+ // signatures
+ vector<string> signature;
+ signature.push_back("RRSIG");
+ signature.push_back("3600");
+ signature.push_back("");
+ signature.push_back("NSEC3PARAM 5 3 3600 20000101000000 20000201000000 "
+ "12345 example.org. FAKEFAKEFAKE");
+ signature.push_back("exmaple.org.");
+ (*readonly_records_)["example.org."].push_back(signature);
+ // Now the NSEC3 param itself
+ vector<string> param;
+ param.push_back("NSEC3PARAM");
+ param.push_back("3600");
+ param.push_back("");
+ param.push_back("1 0 12 aabbccdd");
+ param.push_back("example.org.");
+ (*readonly_records_)["example.org."].push_back(param);
}
};
@@ -966,6 +1137,11 @@ public:
"FAKEFAKEFAKE"));
}
+ ~ DatabaseClientTest() {
+ // Make sure we return the default creator no matter if we set it or not
+ setNSEC3HashCreator(NULL);
+ }
+
/*
* We initialize the client from a function, so we can call it multiple
* times per test.
@@ -979,7 +1155,7 @@ public:
// probably move this to some specialized templated method specific
// to SQLite3 (or for even a longer term we should add an API to
// purge the diffs table).
- const char* const install_cmd = INSTALL_PROG " " TEST_DATA_DIR
+ const char* const install_cmd = INSTALL_PROG " -c " TEST_DATA_COMMONDIR
"/rwtest.sqlite3 " TEST_DATA_BUILDDIR
"/rwtest.sqlite3.copied";
if (system(install_cmd) != 0) {
@@ -1087,7 +1263,7 @@ public:
rdata::createRdata(expected_rrset->getType(),
expected_rrset->getClass(),
(*it).data_[Accessor::DIFF_RDATA]));
- isc::testutils::rrsetCheck(expected_rrset, rrset);
+ rrsetCheck(expected_rrset, rrset);
}
// We should have examined all entries of both expected and
// actual data.
@@ -1126,6 +1302,9 @@ public:
const std::vector<std::string> empty_rdatas_; // for NXRRSET/NXDOMAIN
std::vector<std::string> expected_rdatas_;
std::vector<std::string> expected_sig_rdatas_;
+
+ // A creator for use in several NSEC3 related tests.
+ TestNSEC3HashCreator test_nsec3_hash_creator_;
};
class TestSQLite3Accessor : public SQLite3Accessor {
@@ -1253,10 +1432,10 @@ checkRRset(isc::dns::ConstRRsetPtr rrset,
isc::dns::rdata::createRdata(rrtype, rrclass,
rdatas[i]));
}
- isc::testutils::rrsetCheck(expected_rrset, rrset);
+ rrsetCheck(expected_rrset, rrset);
}
-// Iterate through a zone
+// Iterate through a zone, common case
TYPED_TEST(DatabaseClientTest, iterator) {
ZoneIteratorPtr it(this->client_->getIterator(Name("example.org")));
ConstRRsetPtr rrset(it->getNextRRset());
@@ -1264,47 +1443,100 @@ TYPED_TEST(DatabaseClientTest, iterator) {
// The first name should be the zone origin.
EXPECT_EQ(this->zname_, rrset->getName());
+}
- // The rest of the checks work only for the mock accessor.
- if (!this->is_mock_) {
- return;
- }
-
- this->expected_rdatas_.clear();
- this->expected_rdatas_.push_back("192.0.2.1");
- checkRRset(rrset, Name("example.org"), this->qclass_, RRType::A(),
- this->rrttl_, this->expected_rdatas_);
-
- rrset = it->getNextRRset();
- this->expected_rdatas_.clear();
- this->expected_rdatas_.push_back("ns1.example.org. admin.example.org. "
- "1234 3600 1800 2419200 7200");
- checkRRset(rrset, Name("example.org"), this->qclass_, RRType::SOA(),
- this->rrttl_, this->expected_rdatas_);
-
- rrset = it->getNextRRset();
- this->expected_rdatas_.clear();
- this->expected_rdatas_.push_back("192.0.2.1");
- this->expected_rdatas_.push_back("192.0.2.2");
- checkRRset(rrset, Name("x.example.org"), this->qclass_, RRType::A(),
- RRTTL(300), this->expected_rdatas_);
-
- rrset = it->getNextRRset();
- this->expected_rdatas_.clear();
- this->expected_rdatas_.push_back("2001:db8::1");
- this->expected_rdatas_.push_back("2001:db8::2");
- checkRRset(rrset, Name("x.example.org"), this->qclass_, RRType::AAAA(),
- RRTTL(300), this->expected_rdatas_);
-
- rrset = it->getNextRRset();
- ASSERT_NE(ConstRRsetPtr(), rrset);
- this->expected_rdatas_.clear();
- this->expected_rdatas_.push_back("192.0.2.1");
- this->expected_rdatas_.push_back("192.0.2.2");
- checkRRset(rrset, Name("ttldiff.example.org"), this->qclass_, RRType::A(),
- RRTTL(300), this->expected_rdatas_);
+// Supplemental structure used in the couple of tests below. It represents
+// parameters of an expected RRset containing up to two RDATAs. If it contains
+// only one RDATA, rdata2 is NULL.
+struct ExpectedRRset {
+ const char* const name;
+ const RRType rrtype;
+ const RRTTL rrttl;
+ const char* const rdata1;
+ const char* const rdata2;
+};
- EXPECT_EQ(ConstRRsetPtr(), it->getNextRRset());
+// Common checker for the iterator tests below. It extracts RRsets from the
+// give iterator and compare them to the expected sequence.
+void
+checkIteratorSequence(ZoneIterator& it, ExpectedRRset expected_sequence[],
+ size_t num_rrsets)
+{
+ vector<string> expected_rdatas;
+ for (size_t i = 0; i < num_rrsets; ++i) {
+ const ConstRRsetPtr rrset = it.getNextRRset();
+ ASSERT_TRUE(rrset);
+
+ expected_rdatas.clear();
+ expected_rdatas.push_back(expected_sequence[i].rdata1);
+ if (expected_sequence[i].rdata2 != NULL) {
+ expected_rdatas.push_back(expected_sequence[i].rdata2);
+ }
+ checkRRset(rrset, Name(expected_sequence[i].name), RRClass::IN(),
+ expected_sequence[i].rrtype, expected_sequence[i].rrttl,
+ expected_rdatas);
+ }
+ EXPECT_FALSE(it.getNextRRset());
+}
+
+TEST_F(MockDatabaseClientTest, iterator) {
+ // This version of test creates an iterator that combines same types of
+ // RRs into single RRsets.
+ ExpectedRRset expected_sequence[] = {
+ {"example.org", RRType::A(), rrttl_, "192.0.2.1", NULL},
+ {"example.org", RRType::SOA(), rrttl_,
+ "ns1.example.org. admin.example.org. 1234 3600 1800 2419200 7200",
+ NULL},
+ {"x.example.org", RRType::A(), RRTTL(300), "192.0.2.1", "192.0.2.2"},
+ {"x.example.org", RRType::AAAA(), RRTTL(300),
+ "2001:db8::1", "2001:db8::2"},
+ {"x.example.org", RRType::RRSIG(), RRTTL(300),
+ "A 5 3 3600 20000101000000 20000201000000 12345 example.org. "
+ "FAKEFAKEFAKE", NULL},
+ {"x.example.org", RRType::RRSIG(), RRTTL(300),
+ "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. "
+ "FAKEFAKEFAKEFAKE", NULL},
+ {"ttldiff.example.org", RRType::A(), RRTTL(300),
+ "192.0.2.1", "192.0.2.2"},
+ {"ttldiff2.example.org", RRType::AAAA(), RRTTL(300),
+ "2001:db8::1", "2001:db8::2"}
+ };
+ checkIteratorSequence(*client_->getIterator(Name("example.org")),
+ expected_sequence,
+ sizeof(expected_sequence) /
+ sizeof(expected_sequence[0]));
+}
+
+TEST_F(MockDatabaseClientTest, iteratorSeparateRRs) {
+ // This version of test creates an iterator that separates all RRs as
+ // individual RRsets. In particular, it preserves the TTLs of an RRset
+ // even if they are different.
+ ExpectedRRset expected_sequence[] = {
+ {"example.org", RRType::A(), rrttl_, "192.0.2.1", NULL},
+ {"example.org", RRType::SOA(), rrttl_,
+ "ns1.example.org. admin.example.org. 1234 3600 1800 2419200 7200",
+ NULL},
+ {"x.example.org", RRType::A(), RRTTL(300), "192.0.2.1", NULL},
+ {"x.example.org", RRType::A(), RRTTL(300), "192.0.2.2", NULL},
+ {"x.example.org", RRType::AAAA(), RRTTL(300), "2001:db8::1", NULL},
+ {"x.example.org", RRType::AAAA(), RRTTL(300), "2001:db8::2", NULL},
+ {"x.example.org", RRType::RRSIG(), RRTTL(300),
+ "A 5 3 3600 20000101000000 20000201000000 12345 example.org. "
+ "FAKEFAKEFAKE", NULL},
+ {"x.example.org", RRType::RRSIG(), RRTTL(300),
+ "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. "
+ "FAKEFAKEFAKEFAKE", NULL},
+ {"ttldiff.example.org", RRType::A(), RRTTL(300), "192.0.2.1", NULL},
+ {"ttldiff.example.org", RRType::A(), RRTTL(600), "192.0.2.2", NULL},
+ {"ttldiff2.example.org", RRType::AAAA(), RRTTL(600), "2001:db8::1",
+ NULL},
+ {"ttldiff2.example.org", RRType::AAAA(), RRTTL(300), "2001:db8::2",
+ NULL}
+ };
+ checkIteratorSequence(*client_->getIterator(Name("example.org"), true),
+ expected_sequence,
+ sizeof(expected_sequence) /
+ sizeof(expected_sequence[0]));
}
// This has inconsistent TTL in the set (the rest, like nonsense in
@@ -1336,7 +1568,7 @@ TYPED_TEST(DatabaseClientTest, getSOAFromIterator) {
}
ASSERT_TRUE(rrset);
// It should be identical to the result of getSOA().
- isc::testutils::rrsetCheck(it->getSOA(), rrset);
+ rrsetCheck(it->getSOA(), rrset);
}
TYPED_TEST(DatabaseClientTest, noSOAFromIterator) {
@@ -1364,7 +1596,7 @@ TYPED_TEST(DatabaseClientTest, iterateThenUpdate) {
// Confirm at least it doesn't contain any SOA
EXPECT_EQ(ZoneFinder::NXDOMAIN,
- this->getFinder()->find(this->zname_, RRType::SOA()).code);
+ this->getFinder()->find(this->zname_, RRType::SOA())->code);
} catch (const DataSourceError&) {}
ConstRRsetPtr rrset;
@@ -1374,7 +1606,7 @@ TYPED_TEST(DatabaseClientTest, iterateThenUpdate) {
}
ASSERT_TRUE(rrset);
// It should be identical to the result of getSOA().
- isc::testutils::rrsetCheck(it->getSOA(), rrset);
+ rrsetCheck(it->getSOA(), rrset);
}
TYPED_TEST(DatabaseClientTest, updateThenIterateThenUpdate) {
@@ -1416,29 +1648,39 @@ doFindTest(ZoneFinder& finder,
ZoneFinder::Result expected_result,
const std::vector<std::string>& expected_rdatas,
const std::vector<std::string>& expected_sig_rdatas,
+ ZoneFinder::FindResultFlags expected_flags =
+ ZoneFinder::RESULT_DEFAULT,
const isc::dns::Name& expected_name = isc::dns::Name::ROOT_NAME(),
const ZoneFinder::FindOptions options = ZoneFinder::FIND_DEFAULT)
{
SCOPED_TRACE("doFindTest " + name.toText() + " " + type.toText());
- const ZoneFinder::FindResult result = finder.find(name, type, options);
- ASSERT_EQ(expected_result, result.code) << name << " " << type;
- if (!expected_rdatas.empty() && result.rrset) {
- checkRRset(result.rrset, expected_name != Name(".") ? expected_name :
+ ConstZoneFinderContextPtr result = finder.find(name, type, options);
+ ASSERT_EQ(expected_result, result->code) << name << " " << type;
+ EXPECT_EQ((expected_flags & ZoneFinder::RESULT_WILDCARD) != 0,
+ result->isWildcard());
+ EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0,
+ result->isNSECSigned());
+ EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0,
+ result->isNSEC3Signed());
+ if (!expected_rdatas.empty() && result->rrset) {
+ checkRRset(result->rrset, expected_name != Name(".") ? expected_name :
name, finder.getClass(), expected_type, expected_ttl,
expected_rdatas);
- if (!expected_sig_rdatas.empty() && result.rrset->getRRsig()) {
- checkRRset(result.rrset->getRRsig(), expected_name != Name(".") ?
+ if (!expected_sig_rdatas.empty() && result->rrset->getRRsig()) {
+ checkRRset(result->rrset->getRRsig(), expected_name != Name(".") ?
expected_name : name, finder.getClass(),
isc::dns::RRType::RRSIG(), expected_ttl,
expected_sig_rdatas);
} else if (expected_sig_rdatas.empty()) {
- EXPECT_EQ(isc::dns::RRsetPtr(), result.rrset->getRRsig());
+ EXPECT_EQ(isc::dns::RRsetPtr(), result->rrset->getRRsig()) <<
+ "Unexpected RRSIG: " << result->rrset->getRRsig()->toText();
} else {
ADD_FAILURE() << "Missing RRSIG";
}
} else if (expected_rdatas.empty()) {
- EXPECT_EQ(isc::dns::RRsetPtr(), result.rrset);
+ EXPECT_EQ(isc::dns::RRsetPtr(), result->rrset) <<
+ "Unexpected RRset: " << result->rrset->toText();
} else {
ADD_FAILURE() << "Missing result";
}
@@ -1452,15 +1694,26 @@ doFindAllTestResult(ZoneFinder& finder, const isc::dns::Name& name,
const isc::dns::Name& expected_name =
isc::dns::Name::ROOT_NAME(),
const ZoneFinder::FindOptions options =
- ZoneFinder::FIND_DEFAULT)
+ ZoneFinder::FIND_DEFAULT,
+ ZoneFinder::FindResultFlags expected_flags =
+ ZoneFinder::RESULT_DEFAULT)
{
SCOPED_TRACE("All test for " + name.toText());
std::vector<ConstRRsetPtr> target;
- ZoneFinder::FindResult result(finder.findAll(name, target, options));
+ ConstZoneFinderContextPtr result(finder.findAll(name, target, options));
EXPECT_TRUE(target.empty());
- EXPECT_EQ(expected_result, result.code);
- EXPECT_EQ(expected_type, result.rrset->getType());
- RdataIteratorPtr it(result.rrset->getRdataIterator());
+ EXPECT_EQ(expected_result, result->code);
+ EXPECT_EQ(expected_type, result->rrset->getType());
+ if (expected_flags != ZoneFinder::RESULT_DEFAULT){
+ EXPECT_EQ((expected_flags & ZoneFinder::RESULT_WILDCARD) != 0,
+ result->isWildcard());
+ EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0,
+ result->isNSECSigned());
+ EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0,
+ result->isNSEC3Signed());
+
+ }
+ RdataIteratorPtr it(result->rrset->getRdataIterator());
std::vector<std::string> rdata;
while (!it->isLast()) {
rdata.push_back(it->getCurrent().toText());
@@ -1474,7 +1727,7 @@ doFindAllTestResult(ZoneFinder& finder, const isc::dns::Name& name,
}
EXPECT_TRUE(expected_rdata == rdata);
EXPECT_EQ(expected_name == isc::dns::Name::ROOT_NAME() ? name :
- expected_name, result.rrset->getName());
+ expected_name, result->rrset->getName());
}
// When asking for an RRset where RRs somehow have different TTLs, it should
@@ -1771,38 +2024,30 @@ TYPED_TEST(DatabaseClientTest, find) {
}
TYPED_TEST(DatabaseClientTest, findOutOfZone) {
- // If the query name is out-of-zone it should result in NXDOMAIN
+ // If the query name is out-of-zone it should result in an exception
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
vector<ConstRRsetPtr> target;
// Superdomain
- doFindTest(*finder, Name("org"), this->qtype_, this->qtype_,
- this->rrttl_, ZoneFinder::NXDOMAIN,
- this->empty_rdatas_, this->empty_rdatas_);
- EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->findAll(Name("org"), target).code);
+ EXPECT_THROW(finder->find(Name("org"), this->qtype_), OutOfZone);
+ EXPECT_THROW(finder->findAll(Name("org"), target), OutOfZone);
+
// sharing a common ancestor
- doFindTest(*finder, Name("noexample.org"), this->qtype_, this->qtype_,
- this->rrttl_, ZoneFinder::NXDOMAIN,
- this->empty_rdatas_, this->empty_rdatas_);
- EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->findAll(Name("noexample.org"),
- target).code);
+ EXPECT_THROW(finder->find(Name("noexample.org"), this->qtype_), OutOfZone);
+ EXPECT_THROW(finder->findAll(Name("noexample.org"), target), OutOfZone);
+
// totally unrelated domain, smaller number of labels
- doFindTest(*finder, Name("com"), this->qtype_, this->qtype_,
- this->rrttl_, ZoneFinder::NXDOMAIN,
- this->empty_rdatas_, this->empty_rdatas_);
- EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->findAll(Name("com"), target).code);
+ EXPECT_THROW(finder->find(Name("com"), this->qtype_), OutOfZone);
+ EXPECT_THROW(finder->findAll(Name("com"), target), OutOfZone);
+
// totally unrelated domain, same number of labels
- doFindTest(*finder, Name("example.com"), this->qtype_, this->qtype_,
- this->rrttl_, ZoneFinder::NXDOMAIN,
- this->empty_rdatas_, this->empty_rdatas_);
- EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->findAll(Name("example.com"),
- target).code);
+ EXPECT_THROW(finder->find(Name("example.com"), this->qtype_), OutOfZone);
+ EXPECT_THROW(finder->findAll(Name("example.com"), target), OutOfZone);
+
// totally unrelated domain, larger number of labels
- doFindTest(*finder, Name("more.example.com"), this->qtype_, this->qtype_,
- this->rrttl_, ZoneFinder::NXDOMAIN,
- this->empty_rdatas_, this->empty_rdatas_);
- EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->findAll(Name("more.example.com"),
- target).code);
+ EXPECT_THROW(finder->find(Name("more.example.com"), this->qtype_),
+ OutOfZone);
+ EXPECT_THROW(finder->findAll(Name("more.example.com"), target), OutOfZone);
}
TYPED_TEST(DatabaseClientTest, findDelegation) {
@@ -1838,17 +2083,17 @@ TYPED_TEST(DatabaseClientTest, findDelegation) {
doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
this->qtype_, isc::dns::RRType::NS(),
this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
- this->expected_sig_rdatas_,
+ this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
isc::dns::Name("delegation.example.org."));
doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
- this->expected_sig_rdatas_,
+ this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
isc::dns::Name("delegation.example.org."));
doFindTest(*finder, isc::dns::Name("deep.below.delegation.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
- this->expected_sig_rdatas_,
+ this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
isc::dns::Name("delegation.example.org."));
// Even when we check directly at the delegation point, we should get
@@ -1876,15 +2121,18 @@ TYPED_TEST(DatabaseClientTest, findDelegation) {
doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
this->qtype_, isc::dns::RRType::DNAME(),
this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
- this->expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
+ this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
+ isc::dns::Name("dname.example.org."));
doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
- this->expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
+ this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
+ isc::dns::Name("dname.example.org."));
doFindTest(*finder, isc::dns::Name("really.deep.below.dname.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
- this->expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
+ this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
+ isc::dns::Name("dname.example.org."));
// Asking direcly for DNAME should give SUCCESS
doFindTest(*finder, isc::dns::Name("dname.example.org."),
@@ -1944,12 +2192,14 @@ TYPED_TEST(DatabaseClientTest, glueOK) {
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
this->rrttl_, ZoneFinder::NXRRSET,
this->expected_rdatas_, this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_DEFAULT,
isc::dns::Name("ns.delegation.example.org."),
ZoneFinder::FIND_GLUE_OK);
doFindTest(*finder, isc::dns::Name("nothere.delegation.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
this->rrttl_, ZoneFinder::NXDOMAIN,
this->expected_rdatas_, this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_DEFAULT,
isc::dns::Name("nothere.delegation.example.org."),
ZoneFinder::FIND_GLUE_OK);
this->expected_rdatas_.push_back("192.0.2.1");
@@ -1957,6 +2207,7 @@ TYPED_TEST(DatabaseClientTest, glueOK) {
this->qtype_, this->qtype_,
this->rrttl_, ZoneFinder::SUCCESS,
this->expected_rdatas_, this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_DEFAULT,
isc::dns::Name("ns.delegation.example.org."),
ZoneFinder::FIND_GLUE_OK);
this->expected_rdatas_.clear();
@@ -1972,6 +2223,7 @@ TYPED_TEST(DatabaseClientTest, glueOK) {
isc::dns::RRType::NS(), isc::dns::RRType::NS(),
this->rrttl_, ZoneFinder::SUCCESS,
this->expected_rdatas_, this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_DEFAULT,
isc::dns::Name("delegation.example.org."),
ZoneFinder::FIND_GLUE_OK);
this->expected_rdatas_.clear();
@@ -1983,12 +2235,12 @@ TYPED_TEST(DatabaseClientTest, glueOK) {
doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
this->qtype_, isc::dns::RRType::DNAME(),
this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
- this->expected_sig_rdatas_,
+ this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
isc::dns::Name("dname.example.org."), ZoneFinder::FIND_GLUE_OK);
doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
- this->expected_sig_rdatas_,
+ this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
isc::dns::Name("dname.example.org."), ZoneFinder::FIND_GLUE_OK);
}
@@ -2003,21 +2255,24 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
"FAKEFAKEFAKE");
doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
this->qtype_, this->qtype_, this->rrttl_,
- ZoneFinder::WILDCARD, this->expected_rdatas_,
- this->expected_sig_rdatas_);
+ ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_, ZoneFinder::RESULT_WILDCARD);
doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
- this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::WILDCARD,
- this->expected_rdatas_, this->expected_sig_rdatas_);
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_WILDCARD);
this->expected_rdatas_.clear();
this->expected_sig_rdatas_.clear();
doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- this->rrttl_, ZoneFinder::WILDCARD_NXRRSET,
- this->expected_rdatas_, this->expected_sig_rdatas_);
+ this->rrttl_, ZoneFinder::NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_WILDCARD);
doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- this->rrttl_, ZoneFinder::WILDCARD_NXRRSET,
- this->expected_rdatas_, this->expected_sig_rdatas_);
+ this->rrttl_, ZoneFinder::NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_WILDCARD);
// Direct request for this wildcard
this->expected_rdatas_.push_back("192.0.2.5");
@@ -2067,14 +2322,14 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
doFindTest(*finder, isc::dns::Name("below.delegatedwild.example.org"),
this->qtype_, isc::dns::RRType::NS(), this->rrttl_,
ZoneFinder::DELEGATION, this->expected_rdatas_,
- this->expected_sig_rdatas_,
+ this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
isc::dns::Name("delegatedwild.example.org"));
// FIXME: This doesn't look logically OK, GLUE_OK should make it transparent,
// so the match should either work or be canceled, but return NXDOMAIN
doFindTest(*finder, isc::dns::Name("below.delegatedwild.example.org"),
this->qtype_, isc::dns::RRType::NS(), this->rrttl_,
ZoneFinder::DELEGATION, this->expected_rdatas_,
- this->expected_sig_rdatas_,
+ this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
isc::dns::Name("delegatedwild.example.org"),
ZoneFinder::FIND_GLUE_OK);
@@ -2097,16 +2352,10 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
this->expected_rdatas_.clear();
const char* negative_names[] = {
"a.foo.example.org.",
- "*.foo.example.org.",
- "foo.example.org.",
"wild.bar.foo.example.org.",
"baz.foo.*.bar.example.org",
"baz.foo.baz.bar.example.org",
"*.foo.baz.bar.example.org",
- "*.foo.*.bar.example.org",
- "foo.*.bar.example.org",
- "*.bar.example.org",
- "bar.example.org",
NULL
};
// Unless FIND_DNSSEC is specified, this is no different from other
@@ -2114,10 +2363,11 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
for (const char** name = negative_names; *name != NULL; ++ name) {
doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
- this->expected_rdatas_, this->expected_sig_rdatas_);
+ this->expected_rdatas_, this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_WILDCARD);
}
- // With FIND_DNSSEC, it should result in WILDCARD_NXRRSET.
+ // With FIND_DNSSEC, it should have NSEC_SIGNED flag.
const char* negative_dnssec_names[] = {
"a.bar.example.org.",
"foo.baz.bar.example.org.",
@@ -2129,8 +2379,9 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
this->expected_sig_rdatas_.clear();
for (const char** name = negative_dnssec_names; *name != NULL; ++ name) {
doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
- RRType::NSEC(), this->rrttl_, ZoneFinder::WILDCARD_NXRRSET,
+ RRType::NSEC(), this->rrttl_, ZoneFinder::NXRRSET,
this->expected_rdatas_, this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_WILDCARD | ZoneFinder::RESULT_NSEC_SIGNED,
Name("bao.example.org."), ZoneFinder::FIND_DNSSEC);
}
@@ -2140,19 +2391,18 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
this->expected_sig_rdatas_.clear();
doFindTest(*finder, isc::dns::Name("a.cnamewild.example.org."),
isc::dns::RRType::TXT(), isc::dns::RRType::CNAME(),
- this->rrttl_, ZoneFinder::WILDCARD_CNAME,
- this->expected_rdatas_, this->expected_sig_rdatas_);
+ this->rrttl_, ZoneFinder::CNAME,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_WILDCARD);
// DNAME on a wildcard. In our implementation we ignore DNAMEs on a
// wildcard, but at a higher level we say the behavior is "unspecified".
// rfc2672bis strongly discourages the mixture of DNAME and wildcard
// (with SHOULD NOT).
- this->expected_rdatas_.clear();
- this->expected_sig_rdatas_.clear();
doFindTest(*finder, Name("a.dnamewild.example.org."),
this->qtype_, this->qtype_, this->rrttl_,
- ZoneFinder::WILDCARD_NXRRSET, this->expected_rdatas_,
- this->expected_sig_rdatas_);
+ ZoneFinder::NXRRSET, this->empty_rdatas_,
+ this->empty_rdatas_, ZoneFinder::RESULT_WILDCARD);
// Some strange things in the wild node
this->expected_rdatas_.clear();
@@ -2160,7 +2410,8 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
doFindTest(*finder, isc::dns::Name("a.nswild.example.org."),
isc::dns::RRType::TXT(), isc::dns::RRType::NS(),
this->rrttl_, ZoneFinder::DELEGATION,
- this->expected_rdatas_, this->expected_sig_rdatas_);
+ this->expected_rdatas_, this->empty_rdatas_,
+ ZoneFinder::RESULT_WILDCARD);
}
TYPED_TEST(DatabaseClientTest, noWildcard) {
@@ -2178,7 +2429,8 @@ TYPED_TEST(DatabaseClientTest, noWildcard) {
doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
RRType::NSEC(), RRType::NSEC(), this->rrttl_,
ZoneFinder::NXDOMAIN, this->expected_rdatas_,
- this->expected_sig_rdatas_, Name("*.wild.example.org."),
+ this->expected_sig_rdatas_, ZoneFinder::RESULT_NSEC_SIGNED,
+ Name("*.wild.example.org."),
ZoneFinder::FIND_DNSSEC | ZoneFinder::NO_WILDCARD);
// Should be the same without FIND_DNSSEC (but in this case no RRsets
@@ -2186,7 +2438,8 @@ TYPED_TEST(DatabaseClientTest, noWildcard) {
doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
RRType::NSEC(), RRType::NSEC(), this->rrttl_,
ZoneFinder::NXDOMAIN, this->empty_rdatas_,
- this->empty_rdatas_, Name::ROOT_NAME(), // name is dummy
+ this->empty_rdatas_, ZoneFinder::RESULT_DEFAULT,
+ Name::ROOT_NAME(), // name is dummy
ZoneFinder::NO_WILDCARD);
// Same for wildcard empty non terminal.
@@ -2195,7 +2448,8 @@ TYPED_TEST(DatabaseClientTest, noWildcard) {
doFindTest(*finder, isc::dns::Name("a.bar.example.org"),
RRType::NSEC(), RRType::NSEC(), this->rrttl_,
ZoneFinder::NXDOMAIN, this->expected_rdatas_,
- this->empty_rdatas_, Name("wild.*.foo.*.bar.example.org"),
+ this->empty_rdatas_, ZoneFinder::RESULT_NSEC_SIGNED,
+ Name("wild.*.foo.*.bar.example.org"),
ZoneFinder::FIND_DNSSEC | ZoneFinder::NO_WILDCARD);
// Search for a wildcard name with NO_WILDCARD. There should be no
@@ -2206,7 +2460,8 @@ TYPED_TEST(DatabaseClientTest, noWildcard) {
doFindTest(*finder, isc::dns::Name("*.nonterminal.example.org"),
RRType::NSEC(), RRType::NSEC(), this->rrttl_,
ZoneFinder::NXDOMAIN, this->expected_rdatas_,
- this->empty_rdatas_, Name("l.example.org"),
+ this->empty_rdatas_, ZoneFinder::RESULT_NSEC_SIGNED,
+ Name("l.example.org"),
ZoneFinder::FIND_DNSSEC | ZoneFinder::NO_WILDCARD);
// On the other hand, if there's exact match for the wildcard name
@@ -2220,8 +2475,8 @@ TYPED_TEST(DatabaseClientTest, noWildcard) {
doFindTest(*finder, isc::dns::Name("*.wild.example.org"),
this->qtype_, this->qtype_, this->rrttl_,
ZoneFinder::SUCCESS, this->expected_rdatas_,
- this->expected_sig_rdatas_, Name("*.wild.example.org"),
- ZoneFinder::NO_WILDCARD);
+ this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
+ Name("*.wild.example.org"), ZoneFinder::NO_WILDCARD);
}
TYPED_TEST(DatabaseClientTest, NXRRSET_NSEC) {
@@ -2237,7 +2492,8 @@ TYPED_TEST(DatabaseClientTest, NXRRSET_NSEC) {
isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(),
this->rrttl_, ZoneFinder::NXRRSET,
this->expected_rdatas_, this->expected_sig_rdatas_,
- Name::ROOT_NAME(), ZoneFinder::FIND_DNSSEC);
+ ZoneFinder::RESULT_NSEC_SIGNED, Name::ROOT_NAME(),
+ ZoneFinder::FIND_DNSSEC);
}
TYPED_TEST(DatabaseClientTest, wildcardNXRRSET_NSEC) {
@@ -2256,15 +2512,175 @@ TYPED_TEST(DatabaseClientTest, wildcardNXRRSET_NSEC) {
// Note that the NSEC name should NOT be synthesized.
doFindTest(*finder, isc::dns::Name("a.wild.example.org."),
isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(),
- this->rrttl_, ZoneFinder::WILDCARD_NXRRSET,
+ this->rrttl_, ZoneFinder::NXRRSET,
this->expected_rdatas_, this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_WILDCARD | ZoneFinder::RESULT_NSEC_SIGNED,
+ Name("*.wild.example.org"), ZoneFinder::FIND_DNSSEC);
+}
+
+// Subroutine for dnssecFlagCheck defined below. It performs some simple
+// checks regarding DNSSEC related result flags for findAll().
+void
+dnssecFlagCheckForAny(ZoneFinder& finder, const Name& name,
+ ZoneFinder::FindResultFlags sec_flag)
+{
+ std::vector<ConstRRsetPtr> target; // just for placeholder
+ ConstZoneFinderContextPtr all_result =
+ finder.findAll(name, target, ZoneFinder::FIND_DNSSEC);
+ EXPECT_EQ((sec_flag & ZoneFinder::RESULT_NSEC_SIGNED) != 0,
+ all_result->isNSECSigned());
+ EXPECT_EQ((sec_flag & ZoneFinder::RESULT_NSEC3_SIGNED) != 0,
+ all_result->isNSEC3Signed());
+}
+
+// Common tests about DNSSEC related result flags. Shared for the NSEC
+// and NSEC3 cases.
+void
+dnssecFlagCheck(ZoneFinder& finder, ZoneFinder::FindResultFlags sec_flag) {
+ std::vector<std::string> expected_rdatas;
+ std::vector<std::string> expected_sig_rdatas;
+
+ // Check NXDOMAIN case in NSEC signed zone, and RESULT_NSEC_SIGNED flag
+ // should be returned to upper layer caller.
+ if ((sec_flag & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+ expected_rdatas.push_back("www2.example.org. A AAAA NSEC RRSIG");
+ expected_sig_rdatas.push_back("NSEC 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. "
+ "FAKEFAKEFAKE");
+ }
+ doFindTest(finder, Name("www1.example.org"), RRType::A(), RRType::NSEC(),
+ RRTTL(3600), ZoneFinder::NXDOMAIN, expected_rdatas,
+ expected_sig_rdatas, sec_flag, Name("www.example.org."),
+ ZoneFinder::FIND_DNSSEC);
+ dnssecFlagCheckForAny(finder, Name("www1.example.org"), sec_flag);
+
+ // Check NXRRSET case in NSEC signed zone, and RESULT_NSEC_SIGNED flag
+ // should be return.
+ // No "findAll" test case for this because NXRRSET shouldn't happen for it.
+ expected_rdatas.clear();
+ expected_sig_rdatas.clear();
+ if ((sec_flag & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+ expected_rdatas.push_back("www2.example.org. A AAAA NSEC RRSIG");
+ expected_sig_rdatas.push_back("NSEC 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. "
+ "FAKEFAKEFAKE");
+ }
+ doFindTest(finder, Name("www.example.org."), RRType::TXT(), RRType::NSEC(),
+ RRTTL(3600), ZoneFinder::NXRRSET, expected_rdatas,
+ expected_sig_rdatas, sec_flag, Name::ROOT_NAME(),
+ ZoneFinder::FIND_DNSSEC);
+
+ // Empty name, should result in NXRRSET (in this test setup the NSEC
+ // doesn't have RRSIG).
+ expected_rdatas.clear();
+ expected_sig_rdatas.clear();
+ if ((sec_flag & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+ expected_rdatas.push_back("empty.nonterminal.example.org. NSEC");
+ }
+ doFindTest(finder, Name("nonterminal.example.org."), RRType::A(),
+ RRType::NSEC(), RRTTL(3600), ZoneFinder::NXRRSET,
+ expected_rdatas,expected_sig_rdatas, sec_flag,
+ Name("l.example.org."), ZoneFinder::FIND_DNSSEC);
+ dnssecFlagCheckForAny(finder, Name("nonterminal.example.org"), sec_flag);
+
+ // Wildcard match
+ expected_rdatas.clear();
+ expected_sig_rdatas.clear();
+ expected_rdatas.push_back("192.0.2.5");
+ expected_sig_rdatas.push_back("A 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. "
+ "FAKEFAKEFAKE");
+ doFindTest(finder, Name("b.a.wild.example.org"), RRType::A(),
+ RRType::A(), RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas,
+ expected_sig_rdatas, (ZoneFinder::RESULT_WILDCARD | sec_flag),
+ Name("b.a.wild.example.org"), ZoneFinder::FIND_DNSSEC);
+ dnssecFlagCheckForAny(finder, Name("b.a.wild.example.org"), sec_flag);
+
+ // Wildcard + NXRRSET (no "findAll" test for this case)
+ expected_rdatas.clear();
+ expected_sig_rdatas.clear();
+ if ((sec_flag & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+ expected_rdatas.push_back("cancel.here.wild.example.org. "
+ "A NSEC RRSIG");
+ expected_sig_rdatas.push_back("NSEC 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. "
+ "FAKEFAKEFAKE");
+ }
+ doFindTest(finder, Name("b.a.wild.example.org"),
+ RRType::TXT(), RRType::NSEC(), RRTTL(3600), ZoneFinder::NXRRSET,
+ expected_rdatas, expected_sig_rdatas,
+ (ZoneFinder::RESULT_WILDCARD | sec_flag),
Name("*.wild.example.org"), ZoneFinder::FIND_DNSSEC);
+
+ // Empty wildcard (this NSEC doesn't have RRSIG in our test data)
+ expected_rdatas.clear();
+ expected_sig_rdatas.clear();
+ if ((sec_flag & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+ expected_rdatas.push_back("wild.*.foo.*.bar.example.org. NSEC");
+ }
+ doFindTest(finder, Name("foo.wild.bar.example.org"),
+ RRType::TXT(), RRType::NSEC(), RRTTL(3600), ZoneFinder::NXRRSET,
+ expected_rdatas, expected_sig_rdatas,
+ (ZoneFinder::RESULT_WILDCARD | sec_flag),
+ Name("bao.example.org"), ZoneFinder::FIND_DNSSEC);
+ dnssecFlagCheckForAny(finder, Name("foo.wild.bar.example.org"), sec_flag);
+}
+
+TYPED_TEST(DatabaseClientTest, dnssecResultFlags) {
+ // ZoneFinder::find() for negative cases and wildcard cases should check
+ // whether the zone is signed with NSEC or NSEC3.
+
+ // In the default test setup, the zone should be considered NSEC-signed
+ // (the apex node has an NSEC RR).
+ {
+ SCOPED_TRACE("NSEC only");
+ dnssecFlagCheck(*this->getFinder(), ZoneFinder::RESULT_NSEC_SIGNED);
+ }
+
+ // Then add an NSEC3PARAM RRset at the apex (it may look weird if the
+ // zone only has NSEC3PARM RRset (but no NSEC3s), but it is okay for the
+ // purpose of this test). The zone should now be considered NSEC3-signed.
+ // Note that the apex NSEC still exists; NSEC3 should override NSEC.
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_.reset(new RRset(this->zname_, this->qclass_,
+ RRType::NSEC3PARAM(), this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "1 0 12 aabbccdd"));
+ this->updater_->addRRset(*this->rrset_);
+ {
+ SCOPED_TRACE("NSEC and NSEC3");
+ dnssecFlagCheck(this->updater_->getFinder(),
+ ZoneFinder::RESULT_NSEC3_SIGNED);
+ }
+
+ // Next, delete the apex NSEC. Since NSEC3PARAM remains, the zone should
+ // still be considered NSEC3-signed.
+ RRsetPtr nsec_rrset(new RRset(this->zname_, this->qclass_, RRType::NSEC(),
+ this->rrttl_));
+ nsec_rrset->addRdata(rdata::createRdata(RRType::NSEC(), this->qclass_,
+ "acnamesig1.example.org. NS A "
+ "NSEC RRSIG"));
+ this->updater_->deleteRRset(*nsec_rrset);
+ {
+ SCOPED_TRACE("NSEC3 only");
+ dnssecFlagCheck(this->updater_->getFinder(),
+ ZoneFinder::RESULT_NSEC3_SIGNED);
+ }
+
+ // Finally, delete the NSEC3PARAM we just added above. The zone should
+ // then be considered unsigned.
+ this->updater_->deleteRRset(*this->rrset_);
+ {
+ SCOPED_TRACE("unsigned");
+ dnssecFlagCheck(this->updater_->getFinder(),
+ ZoneFinder::RESULT_DEFAULT);
+ }
}
TYPED_TEST(DatabaseClientTest, NXDOMAIN_NSEC) {
// The domain doesn't exist, so we must get the right NSEC
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
-
this->expected_rdatas_.push_back("www2.example.org. A AAAA NSEC RRSIG");
this->expected_sig_rdatas_.push_back("NSEC 5 3 3600 20000101000000 "
"20000201000000 12345 example.org. "
@@ -2273,7 +2689,8 @@ TYPED_TEST(DatabaseClientTest, NXDOMAIN_NSEC) {
isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(),
this->rrttl_, ZoneFinder::NXDOMAIN,
this->expected_rdatas_, this->expected_sig_rdatas_,
- Name("www.example.org."), ZoneFinder::FIND_DNSSEC);
+ ZoneFinder::RESULT_NSEC_SIGNED, Name("www.example.org."),
+ ZoneFinder::FIND_DNSSEC);
this->expected_rdatas_.clear();
this->expected_rdatas_.push_back("acnamesig1.example.org. NS A NSEC RRSIG");
// This tests it works correctly in apex (there was a bug, where a check
@@ -2282,20 +2699,21 @@ TYPED_TEST(DatabaseClientTest, NXDOMAIN_NSEC) {
isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(),
this->rrttl_, ZoneFinder::NXDOMAIN,
this->expected_rdatas_, this->expected_sig_rdatas_,
- Name("example.org."), ZoneFinder::FIND_DNSSEC);
+ ZoneFinder::RESULT_NSEC_SIGNED, Name("example.org."),
+ ZoneFinder::FIND_DNSSEC);
// Check that if the DB doesn't support it, the exception from there
// is not propagated and it only does not include the NSEC
if (!this->is_mock_) {
return; // We don't make the real DB to throw
}
- EXPECT_NO_THROW(doFindTest(*finder,
- isc::dns::Name("notimplnsec.example.org."),
- isc::dns::RRType::TXT(),
- isc::dns::RRType::NSEC(), this->rrttl_,
- ZoneFinder::NXDOMAIN, this->empty_rdatas_,
- this->empty_rdatas_, Name::ROOT_NAME(),
- ZoneFinder::FIND_DNSSEC));
+ // In this case the accessor doesn't support findPreviousName(), but the
+ // zone apex has NSEC, and the zone itself is considered NSEC-signed.
+ doFindTest(*finder, Name("notimplnsec.example.org."),
+ RRType::TXT(), RRType::NSEC(), this->rrttl_,
+ ZoneFinder::NXDOMAIN, this->empty_rdatas_,
+ this->empty_rdatas_, ZoneFinder::RESULT_NSEC_SIGNED,
+ Name::ROOT_NAME(), ZoneFinder::FIND_DNSSEC);
}
TYPED_TEST(DatabaseClientTest, emptyNonterminalNSEC) {
@@ -2305,22 +2723,22 @@ TYPED_TEST(DatabaseClientTest, emptyNonterminalNSEC) {
this->expected_rdatas_.push_back("empty.nonterminal.example.org. NSEC");
doFindTest(*finder, isc::dns::Name("nonterminal.example.org."),
isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(), this->rrttl_,
- ZoneFinder::NXRRSET,
- this->expected_rdatas_, this->expected_sig_rdatas_,
- Name("l.example.org."), ZoneFinder::FIND_DNSSEC);
+ ZoneFinder::NXRRSET, this->expected_rdatas_,
+ this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_NSEC_SIGNED, Name("l.example.org."),
+ ZoneFinder::FIND_DNSSEC);
// Check that if the DB doesn't support it, the exception from there
// is not propagated and it only does not include the NSEC
if (!this->is_mock_) {
return; // We don't make the real DB to throw
}
- EXPECT_NO_THROW(doFindTest(*finder,
- isc::dns::Name("here.wild.example.org."),
- isc::dns::RRType::TXT(),
- isc::dns::RRType::NSEC(),
- this->rrttl_, ZoneFinder::NXRRSET,
- this->empty_rdatas_, this->empty_rdatas_,
- Name::ROOT_NAME(), ZoneFinder::FIND_DNSSEC));
+ // See the corresponding case of NXDOMAIN_NSEC.
+ doFindTest(*finder, Name("here.wild.example.org."),
+ RRType::TXT(), RRType::NSEC(), this->rrttl_,
+ ZoneFinder::NXRRSET, this->empty_rdatas_,
+ this->empty_rdatas_, ZoneFinder::RESULT_NSEC_SIGNED,
+ Name::ROOT_NAME(), ZoneFinder::FIND_DNSSEC);
}
TYPED_TEST(DatabaseClientTest, anyFromFind) {
@@ -2338,11 +2756,11 @@ TYPED_TEST(DatabaseClientTest, getAll) {
std::vector<ConstRRsetPtr> target;
EXPECT_EQ(ZoneFinder::NXDOMAIN,
finder->findAll(isc::dns::Name("nothere.example.org."),
- target).code);
+ target)->code);
EXPECT_TRUE(target.empty());
EXPECT_EQ(ZoneFinder::NXRRSET,
finder->findAll(isc::dns::Name("here.wild.example.org."),
- target).code);
+ target)->code);
this->expected_rdatas_.push_back("ns.delegation.example.org.");
this->expected_rdatas_.push_back("ns.example.com.");
doFindAllTestResult(*finder, isc::dns::Name("xx.delegation.example.org."),
@@ -2363,7 +2781,7 @@ TYPED_TEST(DatabaseClientTest, getAll) {
// It should get the data on success
EXPECT_EQ(ZoneFinder::SUCCESS,
finder->findAll(isc::dns::Name("www2.example.org."),
- target).code);
+ target)->code);
ASSERT_EQ(2, target.size());
size_t a_idx(target[1]->getType() == RRType::A());
EXPECT_EQ(RRType::A(), target[a_idx]->getType());
@@ -2387,9 +2805,13 @@ TYPED_TEST(DatabaseClientTest, getAll) {
// And on wildcard. Check the signatures as well.
target.clear();
- EXPECT_EQ(ZoneFinder::WILDCARD,
- finder->findAll(isc::dns::Name("a.wild.example.org"),
- target, ZoneFinder::FIND_DNSSEC).code);
+ ConstZoneFinderContextPtr result =
+ finder->findAll(Name("a.wild.example.org"), target,
+ ZoneFinder::FIND_DNSSEC);
+ EXPECT_EQ(ZoneFinder::SUCCESS, result->code);
+ EXPECT_TRUE(result->isWildcard());
+ EXPECT_TRUE(result->isNSECSigned());
+ EXPECT_FALSE(result->isNSEC3Signed());
ASSERT_EQ(2, target.size());
a_idx = target[1]->getType() == RRType::A();
EXPECT_EQ(RRType::A(), target[a_idx]->getType());
@@ -2467,22 +2889,22 @@ TYPED_TEST(DatabaseClientTest, flushZone) {
// Before update, the name exists.
EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(this->qname_,
- this->qtype_).code);
+ this->qtype_)->code);
// start update in the replace mode. the normal finder should still
// be able to see the record, but the updater's finder shouldn't.
this->updater_ = this->client_->getUpdater(this->zname_, true);
this->setUpdateAccessor();
EXPECT_EQ(ZoneFinder::SUCCESS,
- finder->find(this->qname_, this->qtype_).code);
+ finder->find(this->qname_, this->qtype_)->code);
EXPECT_EQ(ZoneFinder::NXDOMAIN,
this->updater_->getFinder().find(this->qname_,
- this->qtype_).code);
+ this->qtype_)->code);
// commit the update. now the normal finder shouldn't see it.
this->updater_->commit();
EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->find(this->qname_,
- this->qtype_).code);
+ this->qtype_)->code);
// Check rollback wasn't accidentally performed.
EXPECT_FALSE(this->isRollbacked());
@@ -2493,13 +2915,13 @@ TYPED_TEST(DatabaseClientTest, updateCancel) {
ZoneFinderPtr finder = this->client_->findZone(this->zname_).zone_finder;
EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(this->qname_,
- this->qtype_).code);
+ this->qtype_)->code);
this->updater_ = this->client_->getUpdater(this->zname_, true);
this->setUpdateAccessor();
EXPECT_EQ(ZoneFinder::NXDOMAIN,
this->updater_->getFinder().find(this->qname_,
- this->qtype_).code);
+ this->qtype_)->code);
// DB should not have been rolled back yet.
EXPECT_FALSE(this->isRollbacked());
this->updater_.reset(); // destruct without commit
@@ -2509,7 +2931,7 @@ TYPED_TEST(DatabaseClientTest, updateCancel) {
// isRollbacked())
EXPECT_TRUE(this->isRollbacked(true));
EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(this->qname_,
- this->qtype_).code);
+ this->qtype_)->code);
}
TYPED_TEST(DatabaseClientTest, exceptionFromRollback) {
@@ -2580,6 +3002,96 @@ TYPED_TEST(DatabaseClientTest, addRRsetToNewZone) {
this->checkLastAdded(rrset_added);
}
+//
+// Below we define a set of NSEC3 update tests.
+//
+// Commonly used data for NSEC3 update tests below.
+const char* const nsec3_hash = "1BB7SO0452U1QHL98UISNDD9218GELR5";
+const char* const nsec3_rdata = "1 1 12 AABBCCDD "
+ "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR NS SOA RRSIG NSEC3PARAM";
+const char* const nsec3_rdata2 = "1 1 12 AABBCCDD "
+ "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR NS SOA RRSIG"; // differ in bitmaps
+const char* const nsec3_sig_rdata = "NSEC3 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. FAKEFAKEFAKE";
+const char* const nsec3_sig_rdata2 = "NSEC3 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. FAKEFAKE"; // differ in the signature
+
+// Commonly used subroutine that checks if we can get the expected record.
+// According to the API, implementations can skip filling in columns other
+// than those explicitly checked below, so we don't check them.
+void
+nsec3Check(const vector<ConstRRsetPtr>& expected_rrsets,
+ const Name& zone_name, const string& expected_hash,
+ DatabaseAccessor& accessor)
+{
+ const int zone_id = accessor.getZone(zone_name.toText()).second;
+ DatabaseAccessor::IteratorContextPtr itctx =
+ accessor.getNSEC3Records(expected_hash, zone_id);
+ ASSERT_TRUE(itctx);
+
+ // Build a list of matched RRsets and compare the both expected and built
+ // ones as sets.
+ string columns[DatabaseAccessor::COLUMN_COUNT];
+ vector<ConstRRsetPtr> actual_rrsets;
+ while (itctx->getNext(columns)) {
+ actual_rrsets.push_back(
+ textToRRset(expected_hash + "." + zone_name.toText() + " " +
+ columns[DatabaseAccessor::TTL_COLUMN] + " IN " +
+ columns[DatabaseAccessor::TYPE_COLUMN] + " " +
+ columns[DatabaseAccessor::RDATA_COLUMN]));
+ }
+ rrsetsCheck(expected_rrsets.begin(), expected_rrsets.end(),
+ actual_rrsets.begin(), actual_rrsets.end());
+}
+
+TYPED_TEST(DatabaseClientTest, addDeleteNSEC3InZone) {
+ // Add one NSEC3 RR to the zone, delete it, and add another one.
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+ const ConstRRsetPtr nsec3_rrset =
+ textToRRset(string(nsec3_hash) + ".example.org. 3600 IN NSEC3 " +
+ string(nsec3_rdata));
+ const ConstRRsetPtr nsec3_rrset2 =
+ textToRRset(string(nsec3_hash) + ".example.org. 3600 IN NSEC3 " +
+ string(nsec3_rdata2));
+ this->updater_->addRRset(*nsec3_rrset);
+ this->updater_->deleteRRset(*nsec3_rrset);
+ this->updater_->addRRset(*nsec3_rrset2);
+ this->updater_->commit();
+
+ // Check if we can get the expected record.
+ vector<ConstRRsetPtr> expected_rrsets;
+ expected_rrsets.push_back(nsec3_rrset2);
+ nsec3Check(expected_rrsets, this->zname_, nsec3_hash,
+ *this->current_accessor_);
+}
+
+TYPED_TEST(DatabaseClientTest, addDeleteNSEC3AndRRSIGToZone) {
+ // Add one NSEC3 RR and its RRSIG to the zone, delete the RRSIG and add
+ // a new one.
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+ const ConstRRsetPtr nsec3_rrset =
+ textToRRset(string(nsec3_hash) + ".example.org. 3600 IN NSEC3 " +
+ string(nsec3_rdata));
+ const ConstRRsetPtr nsec3_sig_rrset =
+ textToRRset(string(nsec3_hash) + ".example.org. 3600 IN RRSIG " +
+ string(nsec3_sig_rdata));
+ const ConstRRsetPtr nsec3_sig_rrset2 =
+ textToRRset(string(nsec3_hash) + ".example.org. 3600 IN RRSIG " +
+ string(nsec3_sig_rdata2));
+ this->updater_->addRRset(*nsec3_rrset);
+ this->updater_->addRRset(*nsec3_sig_rrset);
+ this->updater_->deleteRRset(*nsec3_sig_rrset);
+ this->updater_->addRRset(*nsec3_sig_rrset2);
+ this->updater_->commit();
+
+ // Check if we can get the expected record.
+ vector<ConstRRsetPtr> expected_rrsets;
+ expected_rrsets.push_back(nsec3_rrset);
+ expected_rrsets.push_back(nsec3_sig_rrset2);
+ nsec3Check(expected_rrsets, this->zname_, nsec3_hash,
+ *this->current_accessor_);
+}
+
TYPED_TEST(DatabaseClientTest, addRRsetToCurrentZone) {
// Similar to the previous test, but not replacing the existing data.
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
@@ -2709,14 +3221,13 @@ TYPED_TEST(DatabaseClientTest, addDeviantRR) {
this->expected_rdatas_.clear();
this->expected_rdatas_.push_back("192.0.2.100");
{
- // Note: find() rejects out-of-zone query name with NXDOMAIN
+ // Note: find() rejects out-of-zone query name with an exception
// regardless of whether adding the RR succeeded, so this check
// actually doesn't confirm it.
SCOPED_TRACE("add out-of-zone RR");
- doFindTest(this->updater_->getFinder(), Name("example.com"),
- this->qtype_, this->qtype_, this->rrttl_,
- ZoneFinder::NXDOMAIN, this->empty_rdatas_,
- this->empty_rdatas_);
+ EXPECT_THROW(this->updater_->getFinder().find(Name("example.com"),
+ this->qtype_),
+ OutOfZone);
}
}
@@ -3127,6 +3638,49 @@ TYPED_TEST(DatabaseClientTest, journal) {
this->checkJournal(expected);
}
+TYPED_TEST(DatabaseClientTest, journalForNSEC3) {
+ // Similar to the previous test, but adding/deleting NSEC3 RRs, just to
+ // confirm that NSEC3 is not special for managing diffs.
+ const ConstRRsetPtr nsec3_rrset =
+ textToRRset(string(nsec3_hash) + ".example.org. 3600 IN NSEC3 " +
+ string(nsec3_rdata));
+
+ this->updater_ = this->client_->getUpdater(this->zname_, false, true);
+ this->updater_->deleteRRset(*this->soa_);
+ this->updater_->deleteRRset(*nsec3_rrset);
+
+ this->soa_.reset(new RRset(this->zname_, this->qclass_, RRType::SOA(),
+ this->rrttl_));
+ this->soa_->addRdata(rdata::createRdata(this->soa_->getType(),
+ this->soa_->getClass(),
+ "ns1.example.org. "
+ "admin.example.org. "
+ "1235 3600 1800 2419200 7200"));
+ this->updater_->addRRset(*this->soa_);
+ this->updater_->addRRset(*nsec3_rrset);
+ this->updater_->commit();
+ std::vector<JournalEntry> expected;
+ expected.push_back(JournalEntry(WRITABLE_ZONE_ID, 1234,
+ DatabaseAccessor::DIFF_DELETE,
+ "example.org.", "SOA", "3600",
+ "ns1.example.org. admin.example.org. "
+ "1234 3600 1800 2419200 7200"));
+ expected.push_back(JournalEntry(WRITABLE_ZONE_ID, 1234,
+ DatabaseAccessor::DIFF_DELETE,
+ string(nsec3_hash) + ".example.org.",
+ "NSEC3", "3600", nsec3_rdata));
+ expected.push_back(JournalEntry(WRITABLE_ZONE_ID, 1235,
+ DatabaseAccessor::DIFF_ADD,
+ "example.org.", "SOA", "3600",
+ "ns1.example.org. admin.example.org. "
+ "1235 3600 1800 2419200 7200"));
+ expected.push_back(JournalEntry(WRITABLE_ZONE_ID, 1235,
+ DatabaseAccessor::DIFF_ADD,
+ string(nsec3_hash) + ".example.org.",
+ "NSEC3", "3600", nsec3_rdata));
+ this->checkJournal(expected);
+}
+
/*
* Push multiple delete-add sequences. Checks it is allowed and all is
* saved.
@@ -3308,10 +3862,10 @@ TYPED_TEST(DatabaseClientTest, journalReader) {
ASSERT_TRUE(jnl_reader);
ConstRRsetPtr rrset = jnl_reader->getNextDiff();
ASSERT_TRUE(rrset);
- isc::testutils::rrsetCheck(this->soa_, rrset);
+ rrsetCheck(this->soa_, rrset);
rrset = jnl_reader->getNextDiff();
ASSERT_TRUE(rrset);
- isc::testutils::rrsetCheck(soa_end, rrset);
+ rrsetCheck(soa_end, rrset);
rrset = jnl_reader->getNextDiff();
ASSERT_FALSE(rrset);
@@ -3355,7 +3909,7 @@ TYPED_TEST(DatabaseClientTest, readLargeJournal) {
ConstRRsetPtr actual;
int i = 0;
while ((actual = jnl_reader->getNextDiff()) != NULL) {
- isc::testutils::rrsetCheck(expected.at(i++), actual);
+ rrsetCheck(expected.at(i++), actual);
}
EXPECT_EQ(expected.size(), i); // we should have eaten all expected data
}
@@ -3420,4 +3974,25 @@ TEST_F(MockDatabaseClientTest, journalWithBadData) {
second->getNextDiff(), DataSourceError);
}
+/// Let us test a little bit of NSEC3.
+TEST_F(MockDatabaseClientTest, findNSEC3) {
+ // Set up the faked hash calculator.
+ setNSEC3HashCreator(&test_nsec3_hash_creator_);
+
+ DataSourceClient::FindResult
+ zone(this->client_->findZone(Name("example.org")));
+ ASSERT_EQ(result::SUCCESS, zone.code);
+ boost::shared_ptr<DatabaseClient::Finder> finder(
+ dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
+
+ // It'll complain if there is no NSEC3PARAM yet
+ EXPECT_THROW(finder->findNSEC3(Name("example.org"), false),
+ DataSourceError);
+ // And enable NSEC3 in the zone.
+ this->current_accessor_->enableNSEC3();
+
+ // The rest is in the function, it is shared with in-memory tests
+ performNSEC3Test(*finder);
+}
+
}
diff --git a/src/lib/datasrc/tests/datasrc_unittest.cc b/src/lib/datasrc/tests/datasrc_unittest.cc
index cd1a40c..36bed1d 100644
--- a/src/lib/datasrc/tests/datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/datasrc_unittest.cc
@@ -58,7 +58,7 @@ ConstElementPtr SQLITE_DBFILE_EXAMPLE = Element::fromJSON(
class DataSrcTest : public ::testing::Test {
protected:
- DataSrcTest() : obuffer(0), renderer(obuffer), msg(Message::PARSE),
+ DataSrcTest() : msg(Message::PARSE),
opcodeval(Opcode::QUERY().getCode()), qid(0)
{
DataSrcPtr sql3_source = DataSrcPtr(new Sqlite3DataSrc);
@@ -76,7 +76,6 @@ protected:
HotCache cache;
MetaDataSrc meta_source;
- OutputBuffer obuffer;
MessageRenderer renderer;
Message msg;
const uint16_t opcodeval;
diff --git a/src/lib/datasrc/tests/faked_nsec3.cc b/src/lib/datasrc/tests/faked_nsec3.cc
new file mode 100644
index 0000000..0a1823b
--- /dev/null
+++ b/src/lib/datasrc/tests/faked_nsec3.cc
@@ -0,0 +1,209 @@
+// 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 "faked_nsec3.h"
+
+#include <dns/name.h>
+#include <testutils/dnsmessage_test.h>
+
+#include <map>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::testutils;
+
+namespace isc {
+namespace datasrc {
+namespace test {
+
+// Constant data definitions
+
+const char* const nsec3_common = " 300 IN NSEC3 1 1 12 aabbccdd "
+ "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG";
+const char* const nsec3_rrsig_common = " 300 IN RRSIG NSEC3 5 3 3600 "
+ "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE";
+const char* const apex_hash = "0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+const char* const apex_hash_lower = "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom";
+const char* const ns1_hash = "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR";
+const char* const w_hash = "01UDEMVP1J2F7EG6JEBPS17VP3N8I58H";
+const char* const xyw_hash = "2vptu5timamqttgl4luu9kg21e0aor3s";
+const char* const zzz_hash = "R53BQ7CC2UVMUBFU5OCMM6PERS9TK9EN";
+
+class TestNSEC3HashCreator::TestNSEC3Hash : public NSEC3Hash {
+private:
+ typedef map<Name, string> NSEC3HashMap;
+ typedef NSEC3HashMap::value_type NSEC3HashPair;
+ NSEC3HashMap map_;
+public:
+ TestNSEC3Hash() {
+ // Build pre-defined hash
+ map_[Name("example.org")] = apex_hash;
+ map_[Name("www.example.org")] = "2S9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+ map_[Name("xxx.example.org")] = "Q09MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+ map_[Name("yyy.example.org")] = "0A9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+ map_[Name("x.y.w.example.org")] =
+ "2VPTU5TIMAMQTTGL4LUU9KG21E0AOR3S";
+ map_[Name("y.w.example.org")] = "K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H";
+ map_[Name("w.example.org")] = w_hash;
+ map_[Name("zzz.example.org")] = zzz_hash;
+ map_[Name("smallest.example.org")] =
+ "00000000000000000000000000000000";
+ map_[Name("largest.example.org")] =
+ "UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU";
+ }
+ virtual string calculate(const Name& name) const {
+ const NSEC3HashMap::const_iterator found = map_.find(name);
+ if (found != map_.end()) {
+ return (found->second);
+ }
+ isc_throw(isc::Unexpected, "unexpected name for NSEC3 test: "
+ << name);
+ }
+ virtual bool match(const rdata::generic::NSEC3PARAM&) const {
+ return (true);
+ }
+ virtual bool match(const rdata::generic::NSEC3&) const {
+ return (true);
+ }
+};
+
+NSEC3Hash* TestNSEC3HashCreator::create(const rdata::generic::NSEC3PARAM&)
+ const
+{
+ return (new TestNSEC3Hash);
+}
+
+NSEC3Hash* TestNSEC3HashCreator::create(const rdata::generic::NSEC3&) const {
+ return (new TestNSEC3Hash);
+}
+
+void
+findNSEC3Check(bool expected_matched, uint8_t expected_labels,
+ const string& expected_closest,
+ const string& expected_next,
+ const ZoneFinder::FindNSEC3Result& result,
+ bool expected_sig)
+{
+ EXPECT_EQ(expected_matched, result.matched);
+ // Convert to int so the error messages would be more readable:
+ EXPECT_EQ(static_cast<int>(expected_labels),
+ static_cast<int>(result.closest_labels));
+
+ vector<ConstRRsetPtr> actual_rrsets;
+ ASSERT_TRUE(result.closest_proof);
+ actual_rrsets.push_back(result.closest_proof);
+ if (expected_sig) {
+ actual_rrsets.push_back(result.closest_proof->getRRsig());
+ }
+ rrsetsCheck(expected_closest, actual_rrsets.begin(),
+ actual_rrsets.end());
+
+ actual_rrsets.clear();
+ if (expected_next.empty()) {
+ EXPECT_FALSE(result.next_proof);
+ } else {
+ ASSERT_TRUE(result.next_proof);
+ actual_rrsets.push_back(result.next_proof);
+ if (expected_sig) {
+ actual_rrsets.push_back(result.next_proof->getRRsig());
+ }
+ rrsetsCheck(expected_next, actual_rrsets.begin(),
+ actual_rrsets.end());
+ }
+}
+
+void
+performNSEC3Test(ZoneFinder &finder) {
+ // Parameter validation: the query name must be in or below the zone
+ EXPECT_THROW(finder.findNSEC3(Name("example.com"), false), OutOfZone);
+ EXPECT_THROW(finder.findNSEC3(Name("org"), true), OutOfZone);
+
+ Name origin("example.org");
+ const string apex_nsec3_text = string(apex_hash) + ".example.org." +
+ string(nsec3_common);
+ const string ns1_nsec3_text = string(ns1_hash) + ".example.org." +
+ string(nsec3_common);
+ const string w_nsec3_text = string(w_hash) + ".example.org." +
+ string(nsec3_common);
+ const string zzz_nsec3_text = string(zzz_hash) + ".example.org." +
+ string(nsec3_common);
+
+ // Apex name. It should have a matching NSEC3.
+ {
+ SCOPED_TRACE("apex, non recursive mode");
+ findNSEC3Check(true, origin.getLabelCount(), apex_nsec3_text, "",
+ finder.findNSEC3(origin, false));
+ }
+
+ // Recursive mode doesn't change the result in this case.
+ {
+ SCOPED_TRACE("apex, recursive mode");
+ findNSEC3Check(true, origin.getLabelCount(), apex_nsec3_text, "",
+ finder.findNSEC3(origin, true));
+ }
+
+ // Non existent name (in the NSEC3 namespace -- the findNSEC3 does
+ // not look into the normal data). Disabling recursion, a covering
+ // NSEC3 should be returned.
+ const Name www_name("www.example.org");
+ {
+ SCOPED_TRACE("non existent name, non recursive mode");
+ findNSEC3Check(false, www_name.getLabelCount(), apex_nsec3_text, "",
+ finder.findNSEC3(www_name, false));
+ }
+
+ // Non existent name. The closest provable encloser is the apex,
+ // and next closer is the query name itself (which NSEC3 for ns1
+ // covers)
+ // H(ns1) = 2T... < H(xxx) = Q0... < H(zzz) = R5...
+ {
+ SCOPED_TRACE("non existent name, recursive mode");
+ findNSEC3Check(true, origin.getLabelCount(), apex_nsec3_text,
+ ns1_nsec3_text,
+ finder.findNSEC3(Name("xxx.example.org"), true));
+ }
+
+ // Similar to the previous case, but next closer name is different
+ // from the query name. The closet encloser is w.example.org, and
+ // next closer is y.w.example.org.
+ // H(ns1) = 2T.. < H(y.w) = K8.. < H(zzz) = R5
+ {
+ SCOPED_TRACE("non existent name, non qname next closer");
+ findNSEC3Check(true, Name("w.example.org").getLabelCount(),
+ w_nsec3_text, ns1_nsec3_text,
+ finder.findNSEC3(Name("x.y.w.example.org"),
+ true));
+ }
+
+ // In the rest of test we check hash comparison for wrap around cases.
+ {
+ SCOPED_TRACE("very small hash");
+ const Name smallest_name("smallest.example.org");
+ findNSEC3Check(false, smallest_name.getLabelCount(),
+ zzz_nsec3_text, "",
+ finder.findNSEC3(smallest_name, false));
+ }
+ {
+ SCOPED_TRACE("very large hash");
+ const Name largest_name("largest.example.org");
+ findNSEC3Check(false, largest_name.getLabelCount(),
+ zzz_nsec3_text, "",
+ finder.findNSEC3(largest_name, false));
+ }
+}
+
+}
+}
+}
diff --git a/src/lib/datasrc/tests/faked_nsec3.h b/src/lib/datasrc/tests/faked_nsec3.h
new file mode 100644
index 0000000..10d9444
--- /dev/null
+++ b/src/lib/datasrc/tests/faked_nsec3.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 FAKED_NSEC3_H
+#define FAKED_NSEC3_H
+
+#include <datasrc/zone.h>
+
+#include <dns/nsec3hash.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace datasrc {
+namespace test {
+
+//
+// (Faked) NSEC3 hash data. Arbitrarily borrowed from RFC515 examples.
+//
+// Commonly used NSEC3 suffix. It's incorrect to use it for all NSEC3s, but
+// doesn't matter for the purpose of our tests.
+extern const char* const nsec3_common;
+// Likewise, common RRSIG suffix for NSEC3s.
+extern const char* const nsec3_rrsig_common;
+
+// Some faked NSEC3 hash values commonly used in tests and the faked NSEC3Hash
+// object.
+//
+// For apex (example.org)
+extern const char* const apex_hash;
+extern const char* const apex_hash_lower;
+// For ns1.example.org
+extern const char* const ns1_hash;
+// For w.example.org
+extern const char* const w_hash;
+// For x.y.w.example.org (lower-cased)
+extern const char* const xyw_hash;
+// For zzz.example.org.
+extern const char* const zzz_hash;
+
+// A simple faked NSEC3 hash calculator with a dedicated creator for it.
+//
+// This is used in some NSEC3-related tests below.
+class TestNSEC3HashCreator : public isc::dns::NSEC3HashCreator {
+private:
+ class TestNSEC3Hash;
+public:
+ virtual isc::dns::NSEC3Hash* create(const
+ isc::dns::rdata::generic::NSEC3PARAM&)
+ const;
+ virtual isc::dns::NSEC3Hash* create(const isc::dns::rdata::generic::NSEC3&)
+ const;
+};
+
+// Check the result against expected values. It directly calls EXPECT_ macros
+void
+findNSEC3Check(bool expected_matched, uint8_t expected_labels,
+ const std::string& expected_closest,
+ const std::string& expected_next,
+ const isc::datasrc::ZoneFinder::FindNSEC3Result& result,
+ bool expected_sig = false);
+
+// Perform the shared part of NSEC3 test (shared between in-memory and database
+// tests).
+void
+performNSEC3Test(ZoneFinder &finder);
+
+}
+}
+}
+
+#endif // FAKED_NSEC3_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/tests/memory_datasrc_unittest.cc b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
index 8a5a8db..07d1fb9 100644
--- a/src/lib/datasrc/tests/memory_datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
@@ -12,16 +12,13 @@
// 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 <boost/foreach.hpp>
+#include "faked_nsec3.h"
#include <exceptions/exceptions.h>
#include <dns/masterload.h>
#include <dns/name.h>
+#include <dns/nsec3hash.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/rrclass.h>
@@ -29,16 +26,31 @@
#include <dns/rrttl.h>
#include <dns/masterload.h>
+#include <datasrc/client.h>
#include <datasrc/memory_datasrc.h>
#include <datasrc/data_source.h>
#include <datasrc/iterator.h>
+#include "test_client.h"
+
+#include <testutils/dnsmessage_test.h>
+
#include <gtest/gtest.h>
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <sstream>
+#include <vector>
+
using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::datasrc;
+using namespace isc::testutils;
+using boost::shared_ptr;
+using namespace isc::datasrc::test;
namespace {
// Commonly used result codes (Who should write the prefix all the time)
@@ -168,14 +180,23 @@ TEST_F(InMemoryClientTest, iterator) {
EXPECT_EQ(result::SUCCESS, zone->add(aRRsetA));
EXPECT_EQ(result::SUCCESS, zone->add(aRRsetAAAA));
EXPECT_EQ(result::SUCCESS, zone->add(subRRsetA));
- // Check it with full zone, one by one.
- // It should be in ascending order in case of InMemory data source
- // (isn't guaranteed in general)
+
+ // Check it with full zone.
+ vector<ConstRRsetPtr> expected_rrsets;
+ expected_rrsets.push_back(aRRsetA);
+ expected_rrsets.push_back(aRRsetAAAA);
+ expected_rrsets.push_back(subRRsetA);
+
iterator = memory_client.getIterator(Name("a"));
- EXPECT_EQ(aRRsetA, iterator->getNextRRset());
- EXPECT_EQ(aRRsetAAAA, iterator->getNextRRset());
- EXPECT_EQ(subRRsetA, iterator->getNextRRset());
- EXPECT_EQ(ConstRRsetPtr(), iterator->getNextRRset());
+ vector<ConstRRsetPtr> actual_rrsets;
+ ConstRRsetPtr actual;
+ while ((actual = iterator->getNextRRset()) != NULL) {
+ actual_rrsets.push_back(actual);
+ }
+
+ rrsetsCheck(expected_rrsets.begin(), expected_rrsets.end(),
+ actual_rrsets.begin(), actual_rrsets.end());
+
}
TEST_F(InMemoryClientTest, iterator_separate_rrs) {
@@ -251,6 +272,20 @@ TEST_F(InMemoryClientTest, startUpdateZone) {
isc::NotImplemented);
}
+// Commonly used RRSIG data
+const char* const rrsig_a_txt =
+ "example.org. 300 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 "
+ "example.org. FAKEFAKEFAKE\n";
+const char* const rrsig_ns_txt =
+ "example.org. 300 IN RRSIG NS 5 3 3600 20000101000000 20000201000000 "
+ "54321 example.org. FAKEFAKEFAKEFAKE\n";
+// This RRset has two RRSIGs
+const char* const rrsig_aaaa_txt =
+ "ns.example.org. 300 IN RRSIG AAAA 5 3 3600 20000101000000 20000201000000 "
+ "12345 example.org. FAKEFAKEFAKE\n"
+ "ns.example.org. 300 IN RRSIG AAAA 5 3 3600 20000101000000 20000201000000 "
+ "54321 example.org. FAKEFAKEFAKEFAKE\n";
+
// A helper callback of masterLoad() used in InMemoryZoneFinderTest.
void
setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
@@ -258,6 +293,72 @@ setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
++it;
}
+// Some faked NSEC3 hash values commonly used in tests and the faked NSEC3Hash
+// object.
+//
+// For apex (example.org)
+const char* const apex_hash = "0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+const char* const apex_hash_lower = "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom";
+// For ns1.example.org
+const char* const ns1_hash = "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR";
+// For w.example.org
+const char* const w_hash = "01UDEMVP1J2F7EG6JEBPS17VP3N8I58H";
+// For x.y.w.example.org (lower-cased)
+const char* const xyw_hash = "2vptu5timamqttgl4luu9kg21e0aor3s";
+// For zzz.example.org.
+const char* const zzz_hash = "R53BQ7CC2UVMUBFU5OCMM6PERS9TK9EN";
+
+// A simple faked NSEC3 hash calculator with a dedicated creator for it.
+//
+// This is used in some NSEC3-related tests below.
+class TestNSEC3HashCreator : public NSEC3HashCreator {
+ class TestNSEC3Hash : public NSEC3Hash {
+ private:
+ typedef map<Name, string> NSEC3HashMap;
+ typedef NSEC3HashMap::value_type NSEC3HashPair;
+ NSEC3HashMap map_;
+ public:
+ TestNSEC3Hash() {
+ // Build pre-defined hash
+ map_[Name("example.org")] = apex_hash;
+ map_[Name("www.example.org")] = "2S9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+ map_[Name("xxx.example.org")] = "Q09MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+ map_[Name("yyy.example.org")] = "0A9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+ map_[Name("x.y.w.example.org")] =
+ "2VPTU5TIMAMQTTGL4LUU9KG21E0AOR3S";
+ map_[Name("y.w.example.org")] = "K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H";
+ map_[Name("w.example.org")] = w_hash;
+ map_[Name("zzz.example.org")] = zzz_hash;
+ map_[Name("smallest.example.org")] =
+ "00000000000000000000000000000000";
+ map_[Name("largest.example.org")] =
+ "UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU";
+ }
+ virtual string calculate(const Name& name) const {
+ const NSEC3HashMap::const_iterator found = map_.find(name);
+ if (found != map_.end()) {
+ return (found->second);
+ }
+ isc_throw(isc::Unexpected, "unexpected name for NSEC3 test: "
+ << name);
+ }
+ virtual bool match(const generic::NSEC3PARAM&) const {
+ return (true);
+ }
+ virtual bool match(const generic::NSEC3&) const {
+ return (true);
+ }
+ };
+
+public:
+ virtual NSEC3Hash* create(const generic::NSEC3PARAM&) const {
+ return (new TestNSEC3Hash);
+ }
+ virtual NSEC3Hash* create(const generic::NSEC3&) const {
+ return (new TestNSEC3Hash);
+ }
+};
+
/// \brief Test fixture for the InMemoryZoneFinder class
class InMemoryZoneFinderTest : public ::testing::Test {
// A straightforward pair of textual RR(set) and a RRsetPtr variable
@@ -266,6 +367,25 @@ class InMemoryZoneFinderTest : public ::testing::Test {
const char* const text; // textual representation of an RRset
RRsetPtr* rrset;
};
+protected:
+ // The following sub tests are shared by multiple test cases, changing
+ // the zone's DNSSEC status (unsigned, NSEC-signed or NSEC3-signed).
+ // expected_flags is set to either RESULT_NSEC_SIGNED or
+ // RESULT_NSEC3_SIGNED when it's NSEC/NSEC3 signed respectively and
+ // find() is expected to set the corresponding flags.
+ void findCheck(ZoneFinder::FindResultFlags expected_flags =
+ ZoneFinder::RESULT_DEFAULT);
+ void emptyNodeCheck(ZoneFinder::FindResultFlags expected_flags =
+ ZoneFinder::RESULT_DEFAULT);
+ void wildcardCheck(ZoneFinder::FindResultFlags expected_flags =
+ ZoneFinder::RESULT_DEFAULT);
+ void doCancelWildcardCheck(ZoneFinder::FindResultFlags expected_flags =
+ ZoneFinder::RESULT_DEFAULT);
+ void anyWildcardCheck(ZoneFinder::FindResultFlags expected_flags =
+ ZoneFinder::RESULT_DEFAULT);
+ void emptyWildcardCheck(ZoneFinder::FindResultFlags expected_flags =
+ ZoneFinder::RESULT_DEFAULT);
+
public:
InMemoryZoneFinderTest() :
class_(RRClass::IN()),
@@ -275,6 +395,8 @@ public:
// Build test RRsets. Below, we construct an RRset for
// each textual RR(s) of zone_data, and assign it to the corresponding
// rr_xxx.
+ // Note that this contains an out-of-zone RR, and due to the
+ // validation check of masterLoad() used below, we cannot add SOA.
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_},
@@ -291,6 +413,8 @@ public:
{"example.org. 300 IN DNAME example.com.", &rr_dname_apex_},
{"child.example.org. 300 IN NS ns.child.example.org.",
&rr_child_ns_},
+ {"child.example.org. 300 IN DS 12345 5 1 DEADBEEF",
+ &rr_child_ds_},
{"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.",
@@ -301,6 +425,8 @@ public:
&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_},
+ {"*.cnamewild.example.org. 300 IN CNAME canonial.example.org.",
+ &rr_cnamewild_},
{"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",
@@ -312,6 +438,11 @@ public:
{"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_},
+ {"0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM.example.org. 300 IN "
+ "NSEC3 1 1 12 aabbccdd 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG",
+ &rr_nsec3_},
+ {"example.org. 300 IN NSEC cname.example.org. A NS NSEC",
+ &rr_nsec_},
{NULL, NULL}
};
@@ -322,16 +453,24 @@ public:
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));
+ boost::bind(setRRset, _1, rrsets.begin()));
}
+
+ ~InMemoryZoneFinderTest() {
+ // Make sure we reset the hash creator to the default
+ setNSEC3HashCreator(NULL);
+ }
+
// Some data to test with
const RRClass class_;
const Name origin_;
// The zone finder to torture by tests
InMemoryZoneFinder zone_finder_;
+ // Placeholder for storing RRsets to be checked with rrsetsCheck()
+ vector<ConstRRsetPtr> actual_rrsets_;
+
/*
* Some RRsets to put inside the zone.
*/
@@ -353,11 +492,13 @@ public:
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_ds_; // DS of a child domain (for delegation, auth data)
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_wild_; // Wildcard record
+ RRsetPtr rr_cnamewild_; // CNAME at a wildcard
RRsetPtr rr_emptywild_;
RRsetPtr rr_nested_emptywild_;
RRsetPtr rr_nswild_, rr_dnamewild_;
@@ -365,6 +506,14 @@ public:
RRsetPtr rr_under_wild_;
RRsetPtr rr_not_wild_;
RRsetPtr rr_not_wild_another_;
+ RRsetPtr rr_nsec3_;
+ RRsetPtr rr_nsec_;
+
+ // A faked NSEC3 hash calculator for convenience.
+ // Tests that need to use the faked hashed values should call
+ // setNSEC3HashCreator() with a pointer to this variable at the beginning
+ // of the test (at least before adding any NSEC3/NSEC3PARAM RR).
+ TestNSEC3HashCreator nsec3_hash_creator_;
/**
* \brief Test one find query to the zone finder.
@@ -379,6 +528,8 @@ public:
* \param check_answer Should a check against equality of the answer be
* done?
* \param answer The expected rrset, if any should be returned.
+ * \param expected_flags The expected result flags returned via find().
+ * These can be tested using isWildcard() etc.
* \param zone_finder Check different InMemoryZoneFinder object than
* zone_finder_ (if NULL, uses zone_finder_)
* \param check_wild_answer Checks that the answer has the same RRs, type
@@ -390,82 +541,113 @@ public:
ZoneFinder::Result result,
bool check_answer = true,
const ConstRRsetPtr& answer = ConstRRsetPtr(),
+ ZoneFinder::FindResultFlags expected_flags =
+ ZoneFinder::RESULT_DEFAULT,
InMemoryZoneFinder* zone_finder = NULL,
ZoneFinder::FindOptions options = ZoneFinder::FIND_DEFAULT,
bool check_wild_answer = false)
{
+ SCOPED_TRACE("findTest for " + name.toText() + "/" + rrtype.toText());
+
if (zone_finder == NULL) {
zone_finder = &zone_finder_;
}
+ const ConstRRsetPtr answer_sig = answer ? answer->getRRsig() :
+ RRsetPtr(); // note we use the same type as of retval of getRRsig()
// The whole block is inside, because we need to check the result and
// we can't assign to FindResult
EXPECT_NO_THROW({
- ZoneFinder::FindResult find_result(zone_finder->find(
- name, rrtype,
- options));
+ ZoneFinderContextPtr find_result(zone_finder->find(
+ name, rrtype, options));
// Check it returns correct answers
- EXPECT_EQ(result, find_result.code);
+ EXPECT_EQ(result, find_result->code);
+ EXPECT_EQ((expected_flags & ZoneFinder::RESULT_WILDCARD) != 0,
+ find_result->isWildcard());
+ EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED)
+ != 0, find_result->isNSECSigned());
+ EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED)
+ != 0, find_result->isNSEC3Signed());
if (check_answer) {
- EXPECT_EQ(answer, find_result.rrset);
+ if (!answer) {
+ ASSERT_FALSE(find_result->rrset);
+ } else {
+ ASSERT_TRUE(find_result->rrset);
+ rrsetCheck(answer, find_result->rrset);
+ if (answer_sig) {
+ ASSERT_TRUE(find_result->rrset->getRRsig());
+ rrsetCheck(answer_sig,
+ find_result->rrset->getRRsig());
+ }
+ }
} 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) <<
+ ASSERT_NE(ConstRRsetPtr(), find_result->rrset) <<
"No answer found";
+ // Build the expected answer using the given name and
+ // other parameter of the base wildcard RRset.
+ RRsetPtr wildanswer(new RRset(name, answer->getClass(),
+ answer->getType(),
+ answer->getTTL()));
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();
+ for (; !expectedIt->isLast(); expectedIt->next()) {
+ wildanswer->addRdata(expectedIt->getCurrent());
+ }
+ rrsetCheck(wildanswer, find_result->rrset);
+
+ // Same for the RRSIG, if any.
+ if (answer_sig) {
+ ASSERT_TRUE(find_result->rrset->getRRsig());
+
+ RRsetPtr wildsig(new RRset(name,
+ answer_sig->getClass(),
+ RRType::RRSIG(),
+ answer_sig->getTTL()));
+ RdataIteratorPtr expectedIt(
+ answer_sig->getRdataIterator());
+ for (; !expectedIt->isLast(); expectedIt->next()) {
+ wildsig->addRdata(expectedIt->getCurrent());
+ }
+ rrsetCheck(wildsig, find_result->rrset->getRRsig());
}
- 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());
}
});
}
/**
* \brief Calls the findAll on the finder and checks the result.
*/
- std::vector<ConstRRsetPtr> findAllTest(const Name& name,
- ZoneFinder::Result result,
- size_t expected_size,
- InMemoryZoneFinder* finder = NULL,
- const ConstRRsetPtr &rrset_result =
- ConstRRsetPtr(),
- ZoneFinder::FindOptions options =
- ZoneFinder::FIND_DEFAULT)
+ void findAllTest(const Name& name, ZoneFinder::Result result,
+ const vector<ConstRRsetPtr>& expected_rrsets,
+ ZoneFinder::FindResultFlags expected_flags =
+ ZoneFinder::RESULT_DEFAULT,
+ InMemoryZoneFinder* finder = NULL,
+ const ConstRRsetPtr &rrset_result = ConstRRsetPtr(),
+ ZoneFinder::FindOptions options =
+ ZoneFinder::FIND_DEFAULT)
{
if (finder == NULL) {
finder = &zone_finder_;
}
std::vector<ConstRRsetPtr> target;
- ZoneFinder::FindResult findResult(finder->findAll(name, target,
- options));
- EXPECT_EQ(result, findResult.code);
- EXPECT_EQ(rrset_result, findResult.rrset);
- BOOST_FOREACH(const ConstRRsetPtr& rrset, target) {
- EXPECT_EQ(name, rrset->getName());
+ ZoneFinderContextPtr find_result(finder->findAll(name, target,
+ options));
+ EXPECT_EQ(result, find_result->code);
+ if (!rrset_result) {
+ EXPECT_FALSE(find_result->rrset);
+ } else {
+ ASSERT_TRUE(find_result->rrset);
+ rrsetCheck(rrset_result, find_result->rrset);
}
- EXPECT_EQ(expected_size, target.size());
- return (target);
+ EXPECT_EQ((expected_flags & ZoneFinder::RESULT_WILDCARD) != 0,
+ find_result->isWildcard());
+ EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED)
+ != 0, find_result->isNSECSigned());
+ EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED)
+ != 0, find_result->isNSEC3Signed());
+ rrsetsCheck(expected_rrsets.begin(), expected_rrsets.end(),
+ target.begin(), target.end());
}
- // Internal part of the cancelWildcard test that is multiple times
- void doCancelWildcardTest();
};
/**
@@ -494,7 +676,7 @@ TEST_F(InMemoryZoneFinderTest, constructor) {
*/
TEST_F(InMemoryZoneFinderTest, add) {
// This one does not belong to this zone
- EXPECT_THROW(zone_finder_.add(rr_out_), InMemoryZoneFinder::OutOfZone);
+ EXPECT_THROW(zone_finder_.add(rr_out_), OutOfZone);
// Test null pointer
EXPECT_THROW(zone_finder_.add(ConstRRsetPtr()),
InMemoryZoneFinder::NullRRset);
@@ -525,6 +707,37 @@ TEST_F(InMemoryZoneFinderTest, addOtherThenCNAME) {
EXPECT_THROW(zone_finder_.add(rr_cname_), InMemoryZoneFinder::AddError);
}
+TEST_F(InMemoryZoneFinderTest, addCNAMEAndDNSSECRecords) {
+ // CNAME and RRSIG can coexist
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_cname_));
+ EXPECT_EQ(SUCCESS, zone_finder_.add(
+ textToRRset("cname.example.org. 300 IN RRSIG CNAME 5 3 "
+ "3600 20000101000000 20000201000000 12345 "
+ "example.org. FAKEFAKEFAKE")));
+
+ // Same for NSEC
+ EXPECT_EQ(SUCCESS, zone_finder_.add(
+ textToRRset("cname.example.org. 300 IN NSEC "
+ "dname.example.org. CNAME RRSIG NSEC")));
+
+ // Same as above, but adding NSEC first.
+ EXPECT_EQ(SUCCESS, zone_finder_.add(
+ textToRRset("cname2.example.org. 300 IN NSEC "
+ "dname.example.org. CNAME RRSIG NSEC")));
+ EXPECT_EQ(SUCCESS, zone_finder_.add(
+ textToRRset("cname2.example.org. 300 IN CNAME c.example.")));
+
+ // If there's another type of RRset with NSEC, it should still fail.
+ EXPECT_EQ(SUCCESS, zone_finder_.add(
+ textToRRset("cname3.example.org. 300 IN A 192.0.2.1")));
+ EXPECT_EQ(SUCCESS, zone_finder_.add(
+ textToRRset("cname3.example.org. 300 IN NSEC "
+ "dname.example.org. CNAME RRSIG NSEC")));
+ EXPECT_THROW(zone_finder_.add(textToRRset("cname3.example.org. 300 "
+ "IN CNAME c.example.")),
+ InMemoryZoneFinder::AddError);
+}
+
TEST_F(InMemoryZoneFinderTest, findCNAME) {
// install CNAME RR
EXPECT_EQ(SUCCESS, zone_finder_.add(rr_cname_));
@@ -543,13 +756,12 @@ TEST_F(InMemoryZoneFinderTest, findCNAMEUnderZoneCut) {
// (with FIND_GLUE_OK). The behavior is different from BIND 9,
// so we test this case explicitly.
EXPECT_EQ(SUCCESS, zone_finder_.add(rr_child_ns_));
- RRsetPtr rr_cname_under_cut_(new RRset(Name("cname.child.example.org"),
- class_, RRType::CNAME(),
- RRTTL(300)));
+ ConstRRsetPtr rr_cname_under_cut_ = textToRRset(
+ "cname.child.example.org. 300 IN CNAME target.child.example.org.");
EXPECT_EQ(SUCCESS, zone_finder_.add(rr_cname_under_cut_));
findTest(Name("cname.child.example.org"), RRType::AAAA(),
- ZoneFinder::CNAME, true, rr_cname_under_cut_, NULL,
- ZoneFinder::FIND_GLUE_OK);
+ ZoneFinder::CNAME, true, rr_cname_under_cut_,
+ ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK);
}
// Two DNAMEs at single domain are disallowed by RFC 2672, section 3)
@@ -624,7 +836,7 @@ TEST_F(InMemoryZoneFinderTest, DNAMEUnderNS) {
findTest(lowName, RRType::A(), ZoneFinder::DELEGATION, true, rr_child_ns_);
findTest(lowName, RRType::A(), ZoneFinder::DNAME, true, rr_child_dname_,
- NULL, ZoneFinder::FIND_GLUE_OK);
+ ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK);
}
// Test adding child zones and zone cut handling
@@ -656,39 +868,61 @@ TEST_F(InMemoryZoneFinderTest, delegationNS) {
ZoneFinder::DELEGATION, true, rr_child_ns_);
}
+TEST_F(InMemoryZoneFinderTest, delegationWithDS) {
+ // Similar setup to the previous one, but with DS RR at the delegation
+ // point.
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_ns_));
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_child_ns_));
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_child_ds_));
+
+ // Normal types of query should result in delegation, but DS query
+ // should be considered in-zone (but only exactly at the delegation point).
+ findTest(Name("child.example.org"), RRType::A(), ZoneFinder::DELEGATION,
+ true, rr_child_ns_);
+ findTest(Name("child.example.org"), RRType::DS(), ZoneFinder::SUCCESS,
+ true, rr_child_ds_);
+ findTest(Name("grand.child.example.org"), RRType::DS(),
+ ZoneFinder::DELEGATION, true, rr_child_ns_);
+
+ // There's nothing special for DS query at the zone apex. It would
+ // normally result in NXRRSET.
+ findTest(Name("example.org"), RRType::DS(), ZoneFinder::NXRRSET,
+ true, ConstRRsetPtr());
+}
+
TEST_F(InMemoryZoneFinderTest, findAny) {
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_a_)));
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_ns_)));
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_child_glue_)));
+ vector<ConstRRsetPtr> expected_sets;
+
// origin
- std::vector<ConstRRsetPtr> rrsets(findAllTest(origin_, ZoneFinder::SUCCESS,
- 2));
- EXPECT_FALSE(rrsets.end() == std::find(rrsets.begin(), rrsets.end(),
- rr_a_));
- EXPECT_FALSE(rrsets.end() == std::find(rrsets.begin(), rrsets.end(),
- rr_ns_));
+ expected_sets.push_back(rr_a_);
+ expected_sets.push_back(rr_ns_);
+ findAllTest(origin_, ZoneFinder::SUCCESS, expected_sets);
// out zone name
- findAllTest(Name("example.com"), ZoneFinder::NXDOMAIN, 0);
-
- rrsets = findAllTest(rr_child_glue_->getName(), ZoneFinder::SUCCESS, 1);
- EXPECT_FALSE(rrsets.end() == std::find(rrsets.begin(), rrsets.end(),
- rr_child_glue_));
+ EXPECT_THROW(findAllTest(Name("example.com"), ZoneFinder::NXDOMAIN,
+ vector<ConstRRsetPtr>()),
+ OutOfZone);
- // TODO: test NXRRSET case after rbtree non-terminal logic has
- // been implemented
+ expected_sets.clear();
+ expected_sets.push_back(rr_child_glue_);
+ findAllTest(rr_child_glue_->getName(), ZoneFinder::SUCCESS, expected_sets);
// add zone cut
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_child_ns_)));
// zone cut
- findAllTest(rr_child_ns_->getName(), ZoneFinder::DELEGATION, 0, NULL,
- rr_child_ns_);
+ findAllTest(rr_child_ns_->getName(), ZoneFinder::DELEGATION,
+ vector<ConstRRsetPtr>(), ZoneFinder::RESULT_DEFAULT,
+ NULL, rr_child_ns_);
// glue for this zone cut
- findAllTest(rr_child_glue_->getName(),ZoneFinder::DELEGATION, 0, NULL,
- rr_child_ns_);
+ findAllTest(rr_child_glue_->getName(),ZoneFinder::DELEGATION,
+ vector<ConstRRsetPtr>(), ZoneFinder::RESULT_DEFAULT,
+ NULL, rr_child_ns_);
}
TEST_F(InMemoryZoneFinderTest, glue) {
@@ -709,27 +943,30 @@ TEST_F(InMemoryZoneFinderTest, glue) {
// If we do it in the "glue OK" mode, we should find the exact match.
findTest(rr_child_glue_->getName(), RRType::A(), ZoneFinder::SUCCESS, true,
- rr_child_glue_, NULL, ZoneFinder::FIND_GLUE_OK);
+ rr_child_glue_, ZoneFinder::RESULT_DEFAULT, NULL,
+ ZoneFinder::FIND_GLUE_OK);
// glue OK + NXRRSET case
findTest(rr_child_glue_->getName(), RRType::AAAA(), ZoneFinder::NXRRSET,
- true, ConstRRsetPtr(), NULL, ZoneFinder::FIND_GLUE_OK);
+ true, ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, NULL,
+ ZoneFinder::FIND_GLUE_OK);
// glue OK + NXDOMAIN case
findTest(Name("www.child.example.org"), RRType::A(),
- ZoneFinder::DELEGATION, true, rr_child_ns_, NULL,
- ZoneFinder::FIND_GLUE_OK);
+ ZoneFinder::DELEGATION, true, rr_child_ns_,
+ ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK);
// nested cut case. The glue should be found.
findTest(rr_grandchild_glue_->getName(), RRType::AAAA(),
ZoneFinder::SUCCESS,
- true, rr_grandchild_glue_, NULL, ZoneFinder::FIND_GLUE_OK);
+ true, rr_grandchild_glue_, ZoneFinder::RESULT_DEFAULT, NULL,
+ ZoneFinder::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(),
- ZoneFinder::DELEGATION, true, rr_child_ns_, NULL,
- ZoneFinder::FIND_GLUE_OK);
+ ZoneFinder::DELEGATION, true, rr_child_ns_,
+ ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK);
}
/**
@@ -739,13 +976,20 @@ TEST_F(InMemoryZoneFinderTest, glue) {
* \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(InMemoryZoneFinderTest, find) {
+void
+InMemoryZoneFinderTest::findCheck(ZoneFinder::FindResultFlags expected_flags) {
// Fill some data inside
// Now put all the data we have there. It should throw nothing
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_ns_)));
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_ns_a_)));
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_ns_aaaa_)));
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_a_)));
+ if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec3_));
+ }
+ if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec_));
+ }
// These two should be successful
findTest(origin_, RRType::NS(), ZoneFinder::SUCCESS, true, rr_ns_);
@@ -753,15 +997,34 @@ TEST_F(InMemoryZoneFinderTest, find) {
rr_ns_a_);
// These domain exist but don't have the provided RRType
- findTest(origin_, RRType::AAAA(), ZoneFinder::NXRRSET);
- findTest(rr_ns_a_->getName(), RRType::NS(), ZoneFinder::NXRRSET);
+ findTest(origin_, RRType::AAAA(), ZoneFinder::NXRRSET, true,
+ ConstRRsetPtr(), expected_flags);
+ findTest(rr_ns_a_->getName(), RRType::NS(), ZoneFinder::NXRRSET, true,
+ ConstRRsetPtr(), expected_flags);
// These domains don't exist (and one is out of the zone)
- findTest(Name("nothere.example.org"), RRType::A(), ZoneFinder::NXDOMAIN);
- findTest(Name("example.net"), RRType::A(), ZoneFinder::NXDOMAIN);
+ findTest(Name("nothere.example.org"), RRType::A(), ZoneFinder::NXDOMAIN,
+ true, ConstRRsetPtr(), expected_flags);
+ EXPECT_THROW(zone_finder_.find(Name("example.net"), RRType::A()),
+ OutOfZone);
}
-TEST_F(InMemoryZoneFinderTest, emptyNode) {
+TEST_F(InMemoryZoneFinderTest, find) {
+ findCheck();
+}
+
+TEST_F(InMemoryZoneFinderTest, findNSEC3Signed) {
+ findCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+}
+
+TEST_F(InMemoryZoneFinderTest, findNSECSigned) {
+ findCheck(ZoneFinder::RESULT_NSEC_SIGNED);
+}
+
+void
+InMemoryZoneFinderTest::emptyNodeCheck(
+ ZoneFinder::FindResultFlags expected_flags)
+{
/*
* The backend RBTree for this test should look like as follows:
* example.org
@@ -776,28 +1039,48 @@ TEST_F(InMemoryZoneFinderTest, emptyNode) {
// Construct the test zone
const char* const names[] = {
- "bar.example.org", "x.foo.example.org", "aaa.baz.example.org",
+ "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)));
+ ConstRRsetPtr rrset = textToRRset(string(names[i]) +
+ " 300 IN A 192.0.2.1");
EXPECT_EQ(SUCCESS, zone_finder_.add(rrset));
}
+ if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec3_));
+ }
+ if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec_));
+ }
// empty node matching, easy case: the node for 'baz' exists with
// no data.
- findTest(Name("baz.example.org"), RRType::A(), ZoneFinder::NXRRSET);
+ findTest(Name("baz.example.org"), RRType::A(), ZoneFinder::NXRRSET, true,
+ ConstRRsetPtr(), expected_flags);
// 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(), ZoneFinder::NXRRSET);
+ findTest(Name("foo.example.org"), RRType::A(), ZoneFinder::NXRRSET, true,
+ ConstRRsetPtr(), expected_flags);
// "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(), ZoneFinder::NXDOMAIN);
+ EXPECT_THROW(zone_finder_.find(Name("org"), RRType::A()), OutOfZone);
+}
+
+TEST_F(InMemoryZoneFinderTest, emptyNode) {
+ emptyNodeCheck();
+}
+
+TEST_F(InMemoryZoneFinderTest, emptyNodeNSEC3) {
+ emptyNodeCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+}
+
+TEST_F(InMemoryZoneFinderTest, emptyNodeNSEC) {
+ emptyNodeCheck(ZoneFinder::RESULT_NSEC_SIGNED);
}
TEST_F(InMemoryZoneFinderTest, load) {
@@ -817,70 +1100,198 @@ TEST_F(InMemoryZoneFinderTest, load) {
// Now see there are some rrsets (we don't look inside, though)
findTest(Name("."), RRType::SOA(), ZoneFinder::SUCCESS, false,
- ConstRRsetPtr(), &rootzone);
+ ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, &rootzone);
findTest(Name("."), RRType::NS(), ZoneFinder::SUCCESS, false,
- ConstRRsetPtr(), &rootzone);
+ ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, &rootzone);
findTest(Name("a.root-servers.net."), RRType::A(), ZoneFinder::SUCCESS,
- false, ConstRRsetPtr(), &rootzone);
+ false, ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, &rootzone);
// But this should no longer be here
findTest(rr_ns_a_->getName(), RRType::AAAA(), ZoneFinder::NXDOMAIN, true,
- ConstRRsetPtr(), &rootzone);
+ ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, &rootzone);
// Try loading zone that is wrong in a different way
EXPECT_THROW(zone_finder_.load(TEST_DATA_DIR "/duplicate_rrset.zone"),
- MasterLoadError);
+ MasterLoadError);
+}
+
+TEST_F(InMemoryZoneFinderTest, loadFromIterator) {
+ // The initial test set doesn't have SOA at the apex.
+ findTest(origin_, RRType::SOA(), ZoneFinder::NXRRSET, false,
+ ConstRRsetPtr());
+
+ // The content of the new version of zone to be first installed to
+ // the SQLite3 data source, then to in-memory via SQLite3. The data are
+ // chosen to cover major check points of the implementation:
+ // - the previously non-existent record is added (SOA)
+ // - An RRSIG is given from the iterator before the RRset it covers
+ // (RRSIG for SOA, because they are sorted by name then rrtype as text)
+ // - An RRset containing multiple RRs (ns1/A)
+ // - RRSIGs for different owner names
+ stringstream ss;
+ const char* const soa_txt = "example.org. 300 IN SOA . . 0 0 0 0 0\n";
+ const char* const soa_sig_txt = "example.org. 300 IN RRSIG SOA 5 3 300 "
+ "20000101000000 20000201000000 12345 example.org. FAKEFAKE\n";
+ const char* const a_txt =
+ "ns1.example.org. 300 IN A 192.0.2.1\n"
+ "ns1.example.org. 300 IN A 192.0.2.2\n";
+ const char* const a_sig_txt = "ns1.example.org. 300 IN RRSIG A 5 3 300 "
+ "20000101000000 20000201000000 12345 example.org. FAKEFAKE\n";
+ ss << soa_txt << soa_sig_txt << a_txt << a_sig_txt;
+ shared_ptr<DataSourceClient> db_client = unittest::createSQLite3Client(
+ class_, origin_, TEST_DATA_BUILDDIR "/contexttest.sqlite3.copied", ss);
+ zone_finder_.load(*db_client->getIterator(origin_));
+
+ // The new content should be visible, including the previously-nonexistent
+ // SOA.
+ RRsetPtr expected_answer = textToRRset(soa_txt, RRClass::IN(), origin_);
+ expected_answer->addRRsig(textToRRset(soa_sig_txt));
+ findTest(origin_, RRType::SOA(), ZoneFinder::SUCCESS, true,
+ expected_answer);
+
+ expected_answer = textToRRset(a_txt);
+ expected_answer->addRRsig(textToRRset(a_sig_txt));
+ findTest(Name("ns1.example.org"), RRType::A(), ZoneFinder::SUCCESS, true,
+ expected_answer);
+
+ // File name should be (re)set to empty.
+ EXPECT_TRUE(zone_finder_.getFileName().empty());
+
+ // Loading the zone with an iterator separating RRs of the same RRset
+ // will fail because the resulting sequence doesn't meet assumptions of
+ // the (current) in-memory implementation.
+ EXPECT_THROW(zone_finder_.load(*db_client->getIterator(origin_, true)),
+ MasterLoadError);
+
+ // Load the zone from a file that contains more realistic data (borrowed
+ // from a different test). There's nothing special in this case for the
+ // purpose of this test, so it should just succeed.
+ db_client = unittest::createSQLite3Client(
+ class_, origin_, TEST_DATA_BUILDDIR "/contexttest.sqlite3.copied",
+ TEST_DATA_DIR "/contexttest.zone");
+ zone_finder_.load(*db_client->getIterator(origin_));
+
+ // just checking a couple of RRs in the new version of zone.
+ findTest(Name("mx1.example.org"), RRType::A(), ZoneFinder::SUCCESS, true,
+ textToRRset("mx1.example.org. 3600 IN A 192.0.2.10"));
+ findTest(Name("ns1.example.org"), RRType::AAAA(), ZoneFinder::SUCCESS,
+ true, textToRRset("ns1.example.org. 3600 IN AAAA 2001:db8::1"));
}
/*
* Test that puts a (simple) wildcard into the zone and checks we can
* correctly find the data.
*/
-TEST_F(InMemoryZoneFinderTest, wildcard) {
+void
+InMemoryZoneFinderTest::wildcardCheck(
+ ZoneFinder::FindResultFlags expected_flags)
+{
/*
* example.org.
* |
- * wild (not *.wild, should have wild mark)
+ * [cname]wild (not *.wild, should have wild mark)
* |
* *
*/
+
+ // If the zone is "signed" (detecting it by the NSEC/NSEC3 signed flags),
+ // add RRSIGs to the records.
+ ZoneFinder::FindOptions find_options = ZoneFinder::FIND_DEFAULT;
+ if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0 ||
+ (expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+ // Convenience shortcut. The RDATA is not really validatable, but
+ // it doesn't matter for our tests.
+ const char* const rrsig_common = "5 3 3600 "
+ "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE";
+
+ find_options = find_options | ZoneFinder::FIND_DNSSEC;
+ rr_wild_->addRRsig(textToRRset("*.wild.example.org. 300 IN RRSIG A " +
+ string(rrsig_common)));
+ rr_cnamewild_->addRRsig(textToRRset("*.cnamewild.example.org. 300 IN "
+ "RRSIG CNAME " +
+ string(rrsig_common)));
+ }
EXPECT_EQ(SUCCESS, zone_finder_.add(rr_wild_));
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_cnamewild_));
+ // If the zone is expected to be "signed" with NSEC3, add an NSEC3.
+ // (the content of the NSEC3 shouldn't matter)
+ if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec3_));
+ }
+ if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec_));
+ }
// 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(), ZoneFinder::NXRRSET);
+ SCOPED_TRACE("Search at parent");
+ findTest(Name("wild.example.org"), RRType::A(), ZoneFinder::NXRRSET,
+ true, ConstRRsetPtr(), expected_flags, NULL, find_options);
}
// Search the original name of wildcard
{
SCOPED_TRACE("Search directly at *");
findTest(Name("*.wild.example.org"), RRType::A(), ZoneFinder::SUCCESS,
- true, rr_wild_);
+ true, rr_wild_, ZoneFinder::RESULT_DEFAULT, NULL,
+ find_options);
}
// Search "created" name.
{
SCOPED_TRACE("Search at created child");
findTest(Name("a.wild.example.org"), RRType::A(), ZoneFinder::SUCCESS,
- false, rr_wild_, NULL, ZoneFinder::FIND_DEFAULT, true);
+ false, rr_wild_,
+ ZoneFinder::RESULT_WILDCARD | expected_flags, NULL,
+ find_options, true);
+ // Wildcard match, but no data
+ findTest(Name("a.wild.example.org"), RRType::AAAA(),
+ ZoneFinder::NXRRSET, true, ConstRRsetPtr(),
+ ZoneFinder::RESULT_WILDCARD | expected_flags, NULL,
+ find_options);
+ }
+
+ // Search name that has CNAME.
+ {
+ SCOPED_TRACE("Matching CNAME");
+ findTest(Name("a.cnamewild.example.org"), RRType::A(),
+ ZoneFinder::CNAME, false, rr_cnamewild_,
+ ZoneFinder::RESULT_WILDCARD | expected_flags, NULL,
+ find_options, 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(),
- ZoneFinder::SUCCESS, false, rr_wild_, NULL,
- ZoneFinder::FIND_DEFAULT, true);
+ ZoneFinder::SUCCESS, false, rr_wild_,
+ ZoneFinder::RESULT_WILDCARD | expected_flags, NULL,
+ find_options, true);
}
EXPECT_EQ(SUCCESS, zone_finder_.add(rr_under_wild_));
{
SCOPED_TRACE("Search under non-wildcard");
findTest(Name("bar.foo.wild.example.org"), RRType::A(),
- ZoneFinder::NXDOMAIN);
+ ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags,
+ NULL, find_options);
}
}
+TEST_F(InMemoryZoneFinderTest, wildcard) {
+ // Normal case
+ wildcardCheck();
+}
+
+TEST_F(InMemoryZoneFinderTest, wildcardNSEC3) {
+ // Similar to the previous one, but the zone signed with NSEC3
+ wildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+}
+
+TEST_F(InMemoryZoneFinderTest, wildcardNSEC) {
+ // Similar to the previous one, but the zone signed with NSEC
+ wildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED);
+}
+
/*
* Test that we don't match a wildcard if we get under delegation.
* By 4.3.3 of RFC1034:
@@ -901,40 +1312,67 @@ TEST_F(InMemoryZoneFinderTest, delegatedWildcard) {
{
SCOPED_TRACE("Looking under delegation point in GLUE_OK mode");
findTest(Name("a.child.example.org"), RRType::A(),
- ZoneFinder::DELEGATION, true, rr_child_ns_, NULL,
- ZoneFinder::FIND_GLUE_OK);
+ ZoneFinder::DELEGATION, true, rr_child_ns_,
+ ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK);
}
}
// Tests combination of wildcard and ANY.
-TEST_F(InMemoryZoneFinderTest, anyWildcard) {
+void
+InMemoryZoneFinderTest::anyWildcardCheck(
+ ZoneFinder::FindResultFlags expected_flags)
+{
EXPECT_EQ(SUCCESS, zone_finder_.add(rr_wild_));
+ if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec3_));
+ }
+ if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec_));
+ }
+
+ vector<ConstRRsetPtr> expected_sets;
// First try directly the name (normal match)
{
SCOPED_TRACE("Asking direcly for *");
- const std::vector<ConstRRsetPtr>
- target(findAllTest(Name("*.wild.example.org"), ZoneFinder::SUCCESS,
- 1));
- ASSERT_EQ(1, target.size());
- EXPECT_EQ(RRType::A(), (*target.begin())->getType());
- EXPECT_EQ(Name("*.wild.example.org"), (*target.begin())->getName());
+ expected_sets.push_back(rr_wild_);
+ findAllTest(Name("*.wild.example.org"), ZoneFinder::SUCCESS,
+ expected_sets);
}
// Then a wildcard match
{
SCOPED_TRACE("Asking in the wild way");
- const std::vector<ConstRRsetPtr>
- target(findAllTest(Name("a.wild.example.org"), ZoneFinder::SUCCESS,
- 1));
- EXPECT_EQ(RRType::A(), (*target.begin())->getType());
- EXPECT_EQ(Name("a.wild.example.org"), (*target.begin())->getName());
+ expected_sets.clear();
+ RRsetPtr expected(new RRset(Name("a.wild.example.org"),
+ rr_wild_->getClass(), rr_wild_->getType(),
+ rr_wild_->getTTL()));
+ expected->addRdata(rr_wild_->getRdataIterator()->getCurrent());
+ expected_sets.push_back(expected);
+ findAllTest(Name("a.wild.example.org"), ZoneFinder::SUCCESS,
+ expected_sets,
+ ZoneFinder::RESULT_WILDCARD | expected_flags);
}
}
+TEST_F(InMemoryZoneFinderTest, anyWildcard) {
+ anyWildcardCheck();
+}
+
+TEST_F(InMemoryZoneFinderTest, anyWildcardNSEC3) {
+ anyWildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+}
+
+TEST_F(InMemoryZoneFinderTest, anyWildcardNSEC) {
+ anyWildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED);
+}
+
// Test there's nothing in the wildcard in the middle if we load
// wild.*.foo.example.org.
-TEST_F(InMemoryZoneFinderTest, emptyWildcard) {
+void
+InMemoryZoneFinderTest::emptyWildcardCheck(
+ ZoneFinder::FindResultFlags expected_flags)
+{
/*
* example.org.
* foo
@@ -942,6 +1380,12 @@ TEST_F(InMemoryZoneFinderTest, emptyWildcard) {
* wild
*/
EXPECT_EQ(SUCCESS, zone_finder_.add(rr_emptywild_));
+ if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec3_));
+ }
+ if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec_));
+ }
{
SCOPED_TRACE("Asking for the original record under wildcard");
@@ -951,25 +1395,45 @@ TEST_F(InMemoryZoneFinderTest, emptyWildcard) {
{
SCOPED_TRACE("Asking for A record");
- findTest(Name("a.foo.example.org"), RRType::A(), ZoneFinder::NXRRSET);
- findTest(Name("*.foo.example.org"), RRType::A(), ZoneFinder::NXRRSET);
- findTest(Name("foo.example.org"), RRType::A(), ZoneFinder::NXRRSET);
+ findTest(Name("a.foo.example.org"), RRType::A(), ZoneFinder::NXRRSET,
+ true, ConstRRsetPtr(),
+ ZoneFinder::RESULT_WILDCARD | expected_flags);
+ findTest(Name("*.foo.example.org"), RRType::A(), ZoneFinder::NXRRSET,
+ true, ConstRRsetPtr(), expected_flags);
+ findTest(Name("foo.example.org"), RRType::A(), ZoneFinder::NXRRSET,
+ true, ConstRRsetPtr(), expected_flags);
}
{
SCOPED_TRACE("Asking for ANY record");
- findAllTest(Name("*.foo.example.org"), ZoneFinder::NXRRSET, 0);
+ findAllTest(Name("*.foo.example.org"), ZoneFinder::NXRRSET,
+ vector<ConstRRsetPtr>(), expected_flags);
- findAllTest(Name("a.foo.example.org"), ZoneFinder::NXRRSET, 0);
+ findAllTest(Name("a.foo.example.org"), ZoneFinder::NXRRSET,
+ vector<ConstRRsetPtr>(),
+ ZoneFinder::RESULT_WILDCARD | expected_flags);
}
{
SCOPED_TRACE("Asking on the non-terminal");
findTest(Name("wild.bar.foo.example.org"), RRType::A(),
- ZoneFinder::NXRRSET);
+ ZoneFinder::NXRRSET, true, ConstRRsetPtr(),
+ ZoneFinder::RESULT_WILDCARD | expected_flags);
}
}
+TEST_F(InMemoryZoneFinderTest, emptyWildcard) {
+ emptyWildcardCheck();
+}
+
+TEST_F(InMemoryZoneFinderTest, emptyWildcardNSEC3) {
+ emptyWildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+}
+
+TEST_F(InMemoryZoneFinderTest, emptyWildcardNSEC) {
+ emptyWildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED);
+}
+
// Same as emptyWildcard, but with multiple * in the path.
TEST_F(InMemoryZoneFinderTest, nestedEmptyWildcard) {
EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nested_emptywild_));
@@ -992,7 +1456,8 @@ TEST_F(InMemoryZoneFinderTest, nestedEmptyWildcard) {
for (const char** name = names; *name != NULL; ++ name) {
SCOPED_TRACE(string("Node ") + *name);
- findTest(Name(*name), RRType::A(), ZoneFinder::NXRRSET);
+ findTest(Name(*name), RRType::A(), ZoneFinder::NXRRSET, true,
+ ConstRRsetPtr(), ZoneFinder::RESULT_WILDCARD);
}
}
@@ -1020,7 +1485,8 @@ TEST_F(InMemoryZoneFinderTest, nestedEmptyWildcard) {
for (const char** name = names; *name != NULL; ++ name) {
SCOPED_TRACE(string("Node ") + *name);
- findAllTest(Name(*name), ZoneFinder::NXRRSET, 0);
+ findAllTest(Name(*name), ZoneFinder::NXRRSET,
+ vector<ConstRRsetPtr>());
}
}
}
@@ -1028,14 +1494,16 @@ TEST_F(InMemoryZoneFinderTest, nestedEmptyWildcard) {
// We run this part twice from the below test, in two slightly different
// situations
void
-InMemoryZoneFinderTest::doCancelWildcardTest() {
+InMemoryZoneFinderTest::doCancelWildcardCheck(
+ ZoneFinder::FindResultFlags expected_flags)
+{
// These should be canceled
{
SCOPED_TRACE("Canceled under foo.wild.example.org");
findTest(Name("aaa.foo.wild.example.org"), RRType::A(),
- ZoneFinder::NXDOMAIN);
+ ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
findTest(Name("zzz.foo.wild.example.org"), RRType::A(),
- ZoneFinder::NXDOMAIN);
+ ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
}
// This is existing, non-wildcard domain, shouldn't wildcard at all
@@ -1060,7 +1528,9 @@ InMemoryZoneFinderTest::doCancelWildcardTest() {
SCOPED_TRACE(string("Node ") + *name);
findTest(Name(*name), RRType::A(), ZoneFinder::SUCCESS, false,
- rr_wild_, NULL, ZoneFinder::FIND_DEFAULT, true);
+ rr_wild_,
+ ZoneFinder::RESULT_WILDCARD | expected_flags, NULL,
+ ZoneFinder::FIND_DEFAULT, true);
}
}
@@ -1068,7 +1538,7 @@ InMemoryZoneFinderTest::doCancelWildcardTest() {
{
SCOPED_TRACE("The foo.wild.example.org itself");
findTest(Name("foo.wild.example.org"), RRType::A(),
- ZoneFinder::NXRRSET);
+ ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
}
}
@@ -1088,7 +1558,7 @@ TEST_F(InMemoryZoneFinderTest, cancelWildcard) {
{
SCOPED_TRACE("Runnig with single entry under foo.wild.example.org");
- doCancelWildcardTest();
+ doCancelWildcardCheck();
}
// Try putting another one under foo.wild....
@@ -1097,7 +1567,23 @@ TEST_F(InMemoryZoneFinderTest, cancelWildcard) {
EXPECT_EQ(SUCCESS, zone_finder_.add(rr_not_wild_another_));
{
SCOPED_TRACE("Runnig with two entries under foo.wild.example.org");
- doCancelWildcardTest();
+ doCancelWildcardCheck();
+ }
+}
+
+TEST_F(InMemoryZoneFinderTest, cancelWildcardNSEC3) {
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_wild_));
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_not_wild_));
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec3_));
+
+ {
+ SCOPED_TRACE("Runnig with single entry under foo.wild.example.org");
+ doCancelWildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+ }
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_not_wild_another_));
+ {
+ SCOPED_TRACE("Runnig with two entries under foo.wild.example.org");
+ doCancelWildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
}
}
@@ -1120,9 +1606,8 @@ TEST_F(InMemoryZoneFinderTest, swap) {
ASSERT_NE(origin_, other_origin); // make sure these two are different
InMemoryZoneFinder finder2(RRClass::CH(), other_origin);
EXPECT_EQ(result::SUCCESS,
- finder2.add(RRsetPtr(new RRset(Name("version.bind"),
- RRClass::CH(), RRType::TXT(),
- RRTTL(0)))));
+ finder2.add(textToRRset("version.bind. 0 CH TXT \"test\"",
+ RRClass::CH())));
finder1.swap(finder2);
EXPECT_EQ(other_origin, finder1.getOrigin());
@@ -1130,14 +1615,12 @@ TEST_F(InMemoryZoneFinderTest, swap) {
EXPECT_EQ(RRClass::CH(), finder1.getClass());
EXPECT_EQ(RRClass::IN(), finder2.getClass());
// make sure the zone data is swapped, too
- findTest(origin_, RRType::NS(), ZoneFinder::NXDOMAIN, false,
- ConstRRsetPtr(), &finder1);
+ EXPECT_THROW(finder1.find(origin_, RRType::NS()), OutOfZone);
findTest(other_origin, RRType::TXT(), ZoneFinder::SUCCESS, false,
- ConstRRsetPtr(), &finder1);
+ ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, &finder1);
findTest(origin_, RRType::NS(), ZoneFinder::SUCCESS, false,
- ConstRRsetPtr(), &finder2);
- findTest(other_origin, RRType::TXT(), ZoneFinder::NXDOMAIN, false,
- ConstRRsetPtr(), &finder2);
+ ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, &finder2);
+ EXPECT_THROW(finder2.find(other_origin, RRType::TXT()), OutOfZone);
}
TEST_F(InMemoryZoneFinderTest, getFileName) {
@@ -1164,4 +1647,452 @@ TEST_F(InMemoryZoneFinderTest, getFileName) {
EXPECT_EQ(TEST_DATA_DIR "/root.zone", zone_finder_.getFileName());
EXPECT_TRUE(rootzone.getFileName().empty());
}
+
+TEST_F(InMemoryZoneFinderTest, addRRsig) {
+ // A simple valid case: adding an RRset to be signed followed by an RRSIG
+ // that covers the first RRset
+ zone_finder_.add(rr_a_);
+ zone_finder_.add(textToRRset(rrsig_a_txt));
+ ZoneFinderContextPtr result = zone_finder_.find(origin_, RRType::A(),
+ ZoneFinder::FIND_DNSSEC);
+ EXPECT_EQ(ZoneFinder::SUCCESS, result->code);
+ ASSERT_TRUE(result->rrset);
+ ASSERT_TRUE(result->rrset->getRRsig());
+ actual_rrsets_.push_back(result->rrset->getRRsig());
+ rrsetsCheck(rrsig_a_txt, actual_rrsets_.begin(), actual_rrsets_.end());
+
+ // Confirm a separate RRISG for a different type can be added
+ actual_rrsets_.clear();
+ zone_finder_.add(rr_ns_);
+ zone_finder_.add(textToRRset(rrsig_ns_txt));
+ result = zone_finder_.find(origin_, RRType::NS(), ZoneFinder::FIND_DNSSEC);
+ EXPECT_EQ(ZoneFinder::SUCCESS, result->code);
+ ASSERT_TRUE(result->rrset);
+ ASSERT_TRUE(result->rrset->getRRsig());
+ actual_rrsets_.push_back(result->rrset->getRRsig());
+ rrsetsCheck(rrsig_ns_txt, actual_rrsets_.begin(), actual_rrsets_.end());
+
+ // Check a case with multiple RRSIGs
+ actual_rrsets_.clear();
+ zone_finder_.add(rr_ns_aaaa_);
+ zone_finder_.add(textToRRset(rrsig_aaaa_txt));
+ result = zone_finder_.find(Name("ns.example.org"), RRType::AAAA(),
+ ZoneFinder::FIND_DNSSEC);
+ EXPECT_EQ(ZoneFinder::SUCCESS, result->code);
+ ASSERT_TRUE(result->rrset);
+ ASSERT_TRUE(result->rrset->getRRsig());
+ actual_rrsets_.push_back(result->rrset->getRRsig());
+ rrsetsCheck(rrsig_aaaa_txt, actual_rrsets_.begin(), actual_rrsets_.end());
+}
+
+TEST_F(InMemoryZoneFinderTest, addRRsigWithoutCovered) {
+ // The current implementation rejects attempts of adding RRSIG without
+ // covered RRsets already in the zone.
+
+ // Name doesn't exist
+ EXPECT_THROW(zone_finder_.add(
+ textToRRset("notexist.example.org. 300 IN RRSIG A 5 3 "
+ "3600 20000101000000 20000201000000 12345 "
+ "example.org. FAKEFAKEFAKE\n")),
+ InMemoryZoneFinder::AddError);
+
+ // Name exists, but is empty.
+ zone_finder_.add(rr_emptywild_);
+ EXPECT_THROW(zone_finder_.add(
+ textToRRset("foo.example.org. 300 IN RRSIG A 5 3 "
+ "3600 20000101000000 20000201000000 12345 "
+ "example.org. FAKEFAKEFAKE\n")),
+ InMemoryZoneFinder::AddError);
+
+ // Add RRSIG RRset without covered RR
+ zone_finder_.add(rr_a_);
+ EXPECT_THROW(zone_finder_.add(textToRRset(rrsig_ns_txt)),
+ InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, addbadRRsig) {
+ // Tests with other types of bogus input
+
+ // Empty RRSIG RRset.
+ EXPECT_THROW(zone_finder_.add(RRsetPtr(new RRset(origin_, class_,
+ RRType::RRSIG(),
+ RRTTL(300)))),
+ InMemoryZoneFinder::AddError);
+
+ // RRSIG with mixed covered types
+ zone_finder_.add(rr_a_); // make sure the covered name exists
+ // textToRRset() doesn't work as intended for this pathological case,
+ // so we need to construct the RRset by hand.
+ RRsetPtr rrset(new RRset(origin_, class_, RRType::RRSIG(), RRTTL(300)));
+ rrset->addRdata(generic::RRSIG("A 5 3 3600 20000101000000 20000201000000 "
+ "12345 example.org. FAKEFAKEFAKE"));
+ rrset->addRdata(generic::RRSIG("NS 5 3 3600 20000101000000 20000201000000 "
+ "54321 example.org. FAKEFAKEFAKEFAKE"));
+ EXPECT_THROW(zone_finder_.add(rrset), InMemoryZoneFinder::AddError);
+
+ // An attempt of overriding an existing RRSIG. The current implementation
+ // prohibits that.
+ zone_finder_.add(textToRRset(rrsig_a_txt));
+ EXPECT_THROW(zone_finder_.add(textToRRset(rrsig_a_txt)),
+ InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, addNSEC3) {
+ // Set up the faked hash calculator.
+ setNSEC3HashCreator(&nsec3_hash_creator_);
+
+ const string nsec3_text = string(apex_hash) + ".example.org." +
+ string(nsec3_common);
+ // This name shouldn't be found in the normal domain tree.
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(nsec3_text)));
+ EXPECT_EQ(ZoneFinder::NXDOMAIN,
+ zone_finder_.find(Name(string(apex_hash) + ".example.org"),
+ RRType::NSEC3())->code);
+ // Dedicated NSEC3 find should be able to find it.
+ findNSEC3Check(true, origin_.getLabelCount(), nsec3_text, "",
+ zone_finder_.findNSEC3(Name("example.org"), false));
+
+ // This implementation rejects duplicate/update add of the same hash name
+ EXPECT_EQ(result::EXIST,
+ zone_finder_.add(textToRRset(
+ string(apex_hash) + ".example.org." +
+ string(nsec3_common) + " AAAA")));
+ // The original NSEC3 should be intact
+ findNSEC3Check(true, origin_.getLabelCount(), nsec3_text, "",
+ zone_finder_.findNSEC3(Name("example.org"), false));
+
+ // NSEC3-like name but of ordinary RR type should go to normal tree.
+ const string nonsec3_text = string(apex_hash) + ".example.org. " +
+ "300 IN A 192.0.2.1";
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(nonsec3_text)));
+ EXPECT_EQ(ZoneFinder::SUCCESS,
+ zone_finder_.find(Name(string(apex_hash) + ".example.org"),
+ RRType::A())->code);
+}
+
+TEST_F(InMemoryZoneFinderTest, addNSEC3Lower) {
+ // Set up the faked hash calculator.
+ setNSEC3HashCreator(&nsec3_hash_creator_);
+
+ // Similar to the previous case, but NSEC3 owner name is lower-cased.
+ const string nsec3_text = string(apex_hash_lower) + ".example.org." +
+ string(nsec3_common);
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(nsec3_text)));
+ findNSEC3Check(true, origin_.getLabelCount(), nsec3_text, "",
+ zone_finder_.findNSEC3(Name("example.org"), false));
+}
+
+TEST_F(InMemoryZoneFinderTest, addNSEC3Ordering) {
+ // Set up the faked hash calculator.
+ setNSEC3HashCreator(&nsec3_hash_creator_);
+
+ // Check that the internal storage ensures comparison based on the NSEC3
+ // semantics, regardless of the add order or the letter-case of hash.
+
+ // Adding "0P..", "2v..", then "2T..".
+ const string smallest = string(apex_hash) + ".example.org." +
+ string(nsec3_common);
+ const string middle = string(ns1_hash) + ".example.org." +
+ string(nsec3_common);
+ const string largest = string(xyw_hash) + ".example.org." +
+ string(nsec3_common);
+ zone_finder_.add(textToRRset(smallest));
+ zone_finder_.add(textToRRset(largest));
+ zone_finder_.add(textToRRset(middle));
+
+ // Then look for NSEC3 that covers a name whose hash is "2S.."
+ // The covering NSEC3 should be "0P.."
+ findNSEC3Check(false, 4, smallest, "",
+ zone_finder_.findNSEC3(Name("www.example.org"), false));
+
+ // Look for NSEC3 that covers names whose hash are "Q0.." and "0A.."
+ // The covering NSEC3 should be "2v.." in both cases
+ findNSEC3Check(false, 4, largest, "",
+ zone_finder_.findNSEC3(Name("xxx.example.org"), false));
+ findNSEC3Check(false, 4, largest, "",
+ zone_finder_.findNSEC3(Name("yyy.example.org"), false));
+}
+
+TEST_F(InMemoryZoneFinderTest, badNSEC3Name) {
+ // Our implementation refuses to load NSEC3 at a wildcard name
+ EXPECT_THROW(zone_finder_.add(textToRRset("*.example.org." +
+ string(nsec3_common))),
+ InMemoryZoneFinder::AddError);
+
+ // Likewise, if the owner name of NSEC3 has too many labels, it's refused.
+ EXPECT_THROW(zone_finder_.add(textToRRset("a." + string(apex_hash) +
+ ".example.org." +
+ string(nsec3_common))),
+ InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, addMultiNSEC3) {
+ // In this current implementation multiple NSEC3 RDATA isn't supported.
+ RRsetPtr nsec3(new RRset(Name(string(apex_hash) + ".example.org"),
+ RRClass::IN(), RRType::NSEC3(), RRTTL(300)));
+ nsec3->addRdata(
+ generic::NSEC3("1 0 12 aabbccdd 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A"));
+ nsec3->addRdata(
+ generic::NSEC3("1 1 1 ddccbbaa 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A"));
+ EXPECT_THROW(zone_finder_.add(nsec3), InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, addNSEC3WithRRSIG) {
+ // Set up the faked hash calculator.
+ setNSEC3HashCreator(&nsec3_hash_creator_);
+
+ // Adding NSEC3 and its RRSIG
+ const string nsec3_text = string(apex_hash) + ".example.org." +
+ string(nsec3_common);
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(nsec3_text)));
+ const string nsec3_rrsig_text = string(apex_hash) + ".example.org." +
+ string(nsec3_rrsig_common);
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(nsec3_rrsig_text)));
+
+ // Then look for it. The NSEC3 should have the RRSIG that was just added.
+ findNSEC3Check(true, origin_.getLabelCount(),
+ nsec3_text + "\n" + nsec3_rrsig_text, "",
+ zone_finder_.findNSEC3(Name("example.org"), false), true);
+
+ // Duplicate add of RRSIG for the same NSEC3 is prohibited.
+ EXPECT_THROW(zone_finder_.add(textToRRset(nsec3_rrsig_text)),
+ InMemoryZoneFinder::AddError);
+
+ // Same check using the lower-cased name. This also confirms matching
+ // is case-insensitive.
+ EXPECT_THROW(zone_finder_.add(textToRRset(string(apex_hash_lower) +
+ ".example.org."
+ + string(nsec3_rrsig_common))),
+ InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, badRRsigForNSEC3) {
+ // adding RRSIG for NSEC3 even before adding any NSEC3 (internally,
+ // a space for NSEC3 namespace isn't yet allocated)
+ EXPECT_THROW(zone_finder_.add(textToRRset(string(apex_hash) +
+ ".example.org." +
+ string(nsec3_rrsig_common))),
+ InMemoryZoneFinder::AddError);
+
+ // Add an NSEC3
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(
+ textToRRset(string(apex_hash) + ".example.org." +
+ string(nsec3_common))));
+
+ // Then add an NSEC3 for a non existent NSEC3. It should fail in the
+ // current implementation.
+ EXPECT_THROW(zone_finder_.add(textToRRset(string(ns1_hash) +
+ ".example.org." +
+ string(nsec3_rrsig_common))),
+ InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, paramConsistencyWithNSEC3PARAM) {
+ // First, add an NSEC3PARAM RR
+ EXPECT_EQ(result::SUCCESS,
+ zone_finder_.add(textToRRset("example.org. 300 IN NSEC3PARAM "
+ "1 0 12 aabbccdd")));
+ // Adding an NSEC3 that has matching parameters is okay.
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(
+ textToRRset(string(apex_hash) + ".example.org." +
+ string(nsec3_common))));
+ // NSEC3 with inconsistent parameter will be rejected
+ EXPECT_THROW(zone_finder_.add(
+ textToRRset("a.example.org. 300 IN NSEC3 1 0 1 aabbccdd "
+ "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG")),
+ InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, paramConsistencyWithNSEC3) {
+ // Add an NSEC3 without adding NSEC3PARAM
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(
+ textToRRset(string(apex_hash) + ".example.org." +
+ string(nsec3_common))));
+ // Adding an NSEC3 with inconsistent parameter will be rejected at this pt.
+ EXPECT_THROW(zone_finder_.add(
+ textToRRset("a.example.org. 300 IN NSEC3 1 0 1 aabbccdd "
+ "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG")),
+ InMemoryZoneFinder::AddError);
+
+ // Likewise, NSEC3PARAM with inconsistent parameter will be rejected.
+ EXPECT_THROW(zone_finder_.add(textToRRset("example.org. 300 IN NSEC3PARAM "
+ "1 0 1 aabbccdd")),
+ InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, multiNSEC3PARAM) {
+ // In this current implementation multiple NSEC3PARAM isn't supported.
+ RRsetPtr nsec3param(new RRset(Name("example.org"), RRClass::IN(),
+ RRType::NSEC3PARAM(), RRTTL(300)));
+ nsec3param->addRdata(generic::NSEC3PARAM("1 0 12 aabbccdd"));
+ nsec3param->addRdata(generic::NSEC3PARAM("1 1 1 ddccbbaa"));
+ EXPECT_THROW(zone_finder_.add(nsec3param), InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, nonOriginNSEC3PARAM) {
+ // This is a normal NSEC3PARAM at the zone origin
+ EXPECT_EQ(result::SUCCESS,
+ zone_finder_.add(textToRRset("example.org. 300 IN NSEC3PARAM "
+ "1 0 12 aabbccdd")));
+ // Add another (with different param) at a non origin node. This is
+ // awkward, but the implementation accepts it as an ordinary RR.
+ EXPECT_EQ(result::SUCCESS,
+ zone_finder_.add(textToRRset("a.example.org. 300 IN NSEC3PARAM "
+ "1 1 1 aabbccdd")));
+}
+
+TEST_F(InMemoryZoneFinderTest, loadNSEC3Zone) {
+ // Check if it can load validly NSEC3-signed zone. At this moment
+ // it's sufficient to see it doesn't crash
+ zone_finder_.load(TEST_DATA_DIR "/example.org.nsec3-signed");
+
+ // Reload the zone with a version that doesn't have NSEC3PARAM.
+ // This is an abnormal case, but the implementation accepts it.
+ zone_finder_.load(TEST_DATA_DIR "/example.org.nsec3-signed-noparam");
+}
+
+// This test checks that the NSEC3 names don't really exist in the real
+// namespace.
+TEST_F(InMemoryZoneFinderTest, queryToNSEC3Name) {
+ // Add the NSEC3 and NSEC3PARAM there.
+ EXPECT_EQ(result::SUCCESS,
+ zone_finder_.add(textToRRset("example.org. 300 IN NSEC3PARAM "
+ "1 0 12 aabbccdd")));
+ const Name nsec3domain(string(apex_hash) + ".example.org.");
+ // Adding an NSEC3 that has matching parameters is okay.
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(
+ textToRRset(string(apex_hash) + ".example.org." +
+ string(nsec3_common))));
+ // Now, the domain should not exist
+ findTest(nsec3domain, RRType::AAAA(), ZoneFinder::NXDOMAIN, false,
+ ConstRRsetPtr(), ZoneFinder::RESULT_NSEC3_SIGNED, &zone_finder_,
+ ZoneFinder::FIND_DNSSEC);
+ // If we add an A record, the domain should exist
+ ConstRRsetPtr rrset(textToRRset(string(apex_hash) +
+ ".example.org. 300 IN A 192.0.2.1"));
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(rrset));
+ // Searching for a different RRType will tell us this RRset doesn't exist
+ findTest(nsec3domain, RRType::AAAA(), ZoneFinder::NXRRSET, false,
+ ConstRRsetPtr(), ZoneFinder::RESULT_NSEC3_SIGNED, &zone_finder_,
+ ZoneFinder::FIND_DNSSEC);
+ // Searching for the A record would find it
+ findTest(nsec3domain, RRType::A(), ZoneFinder::SUCCESS, true,
+ rrset, ZoneFinder::RESULT_DEFAULT, &zone_finder_,
+ ZoneFinder::FIND_DNSSEC);
+}
+
+// Continuation of the previous test (queryToNSEC3Name), we check we don't break
+// the empty nonterminal case by existence of NSEC3 record with that name.
+TEST_F(InMemoryZoneFinderTest, queryToNSEC3NameNonterminal) {
+ // Add the NSEC3 and NSEC3PARAM there.
+ EXPECT_EQ(result::SUCCESS,
+ zone_finder_.add(textToRRset("example.org. 300 IN NSEC3PARAM "
+ "1 0 12 aabbccdd")));
+ const Name nsec3domain(string(apex_hash) + ".example.org.");
+ // Adding an NSEC3 that has matching parameters is okay.
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(
+ textToRRset(string(apex_hash) + ".example.org." +
+ string(nsec3_common))));
+ // Something below the name
+ ConstRRsetPtr rrset(textToRRset("below." + string(apex_hash) +
+ ".example.org. 300 IN A 192.0.2.1"));
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(rrset));
+ // Now, the node is empty non-terminal.
+ findTest(nsec3domain, RRType::AAAA(), ZoneFinder::NXRRSET, false,
+ ConstRRsetPtr(), ZoneFinder::RESULT_NSEC3_SIGNED, &zone_finder_,
+ ZoneFinder::FIND_DNSSEC);
+}
+
+TEST_F(InMemoryZoneFinderTest, findNSEC3) {
+ // Set up the faked hash calculator.
+ setNSEC3HashCreator(&nsec3_hash_creator_);
+
+ // Add a few NSEC3 records:
+ // apex (example.org.): hash=0P..
+ // ns1.example.org: hash=2T..
+ // w.example.org: hash=01..
+ // zzz.example.org: hash=R5..
+ const string apex_nsec3_text = string(apex_hash) + ".example.org." +
+ string(nsec3_common);
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(apex_nsec3_text)));
+ const string ns1_nsec3_text = string(ns1_hash) + ".example.org." +
+ string(nsec3_common);
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(ns1_nsec3_text)));
+ const string w_nsec3_text = string(w_hash) + ".example.org." +
+ string(nsec3_common);
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(w_nsec3_text)));
+ const string zzz_nsec3_text = string(zzz_hash) + ".example.org." +
+ string(nsec3_common);
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(zzz_nsec3_text)));
+
+ performNSEC3Test(zone_finder_);
+}
+
+TEST_F(InMemoryZoneFinderTest, findNSEC3ForBadZone) {
+ // Set up the faked hash calculator.
+ setNSEC3HashCreator(&nsec3_hash_creator_);
+
+ // If the zone has nothing about NSEC3 (neither NSEC3 or NSEC3PARAM),
+ // findNSEC3() should be rejected.
+ EXPECT_THROW(zone_finder_.findNSEC3(Name("www.example.org"), true),
+ DataSourceError);
+
+ // Only having NSEC3PARAM isn't enough
+ EXPECT_EQ(result::SUCCESS,
+ zone_finder_.add(textToRRset("example.org. 300 IN NSEC3PARAM "
+ "1 0 12 aabbccdd")));
+ EXPECT_THROW(zone_finder_.findNSEC3(Name("www.example.org"), true),
+ DataSourceError);
+
+ // Unless NSEC3 for apex is added the result in the recursive mode
+ // is guaranteed.
+ const string ns1_nsec3_text = string(ns1_hash) + ".example.org." +
+ string(nsec3_common);
+ EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(ns1_nsec3_text)));
+ EXPECT_THROW(zone_finder_.findNSEC3(Name("www.example.org"), true),
+ DataSourceError);
+}
+
+TEST_F(InMemoryZoneFinderTest, loadAndFindNSEC3) {
+ // Using more realistic example, borrowed from RFC5155, with compliant
+ // hash calculator. We only confirm the data source can load it
+ // successfully and find correct NSEC3 RRs for some selected cases
+ // (detailed tests have been done above).
+
+ InMemoryZoneFinder finder(class_, Name("example"));
+ finder.load(TEST_DATA_COMMONDIR "/rfc5155-example.zone.signed");
+
+ // See RFC5155 B.1
+ ZoneFinder::FindNSEC3Result result1 =
+ finder.findNSEC3(Name("c.x.w.example"), true);
+ ASSERT_TRUE(result1.closest_proof);
+ // We compare closest_labels as int so the error report will be more
+ // readable in case it fails.
+ EXPECT_EQ(4, static_cast<int>(result1.closest_labels));
+ EXPECT_EQ(Name("b4um86eghhds6nea196smvmlo4ors995.example"),
+ result1.closest_proof->getName());
+ ASSERT_TRUE(result1.next_proof);
+ EXPECT_EQ(Name("0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example"),
+ result1.next_proof->getName());
+
+ // See RFC5155 B.2.
+ ZoneFinder::FindNSEC3Result result2 =
+ finder.findNSEC3(Name("ns1.example"), true);
+ ASSERT_TRUE(result2.closest_proof);
+ EXPECT_EQ(3, static_cast<int>(result2.closest_labels));
+ EXPECT_EQ(Name("2t7b4g4vsa5smi47k61mv5bv1a22bojr.example"),
+ result2.closest_proof->getName());
+ ASSERT_FALSE(result2.next_proof);
+
+ // See RFC5155 B.5.
+ ZoneFinder::FindNSEC3Result result3 =
+ finder.findNSEC3(Name("a.z.w.example"), true);
+ ASSERT_TRUE(result3.closest_proof);
+ EXPECT_EQ(3, static_cast<int>(result3.closest_labels));
+ EXPECT_EQ(Name("k8udemvp1j2f7eg6jebps17vp3n8i58h.example"),
+ result3.closest_proof->getName());
+ ASSERT_TRUE(result3.next_proof);
+ EXPECT_EQ(Name("q04jkcevqvmu85r014c7dkba38o0ji5r.example"),
+ result3.next_proof->getName());
+}
}
diff --git a/src/lib/datasrc/tests/rbnode_rrset_unittest.cc b/src/lib/datasrc/tests/rbnode_rrset_unittest.cc
new file mode 100644
index 0000000..57e8dbd
--- /dev/null
+++ b/src/lib/datasrc/tests/rbnode_rrset_unittest.cc
@@ -0,0 +1,276 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+#include <dns/rdataclass.h>
+#include <datasrc/rbnode_rrset.h>
+#include <testutils/dnsmessage_test.h>
+
+#include <dns/tests/unittest_util.h>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <stdexcept>
+
+using isc::UnitTestUtil;
+
+using namespace isc;
+using namespace isc::datasrc;
+using namespace isc::datasrc::internal;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::testutils;
+using namespace isc::util;
+using namespace std;
+
+// These tests are very similar to those for RRset - indeed, this file was
+// created from those tests. However, the significant difference in behaviour
+// between RRset and RBNodeRRset - that the "set" methods in the latter mostly
+// result in exceptions being thrown - preclude use of full type
+// parameterisation of the tests.
+
+namespace {
+const char* const RRSIG_TXT =
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=";
+
+class RBNodeRRsetTest : public ::testing::Test {
+protected:
+ RBNodeRRsetTest() :
+ test_name("test.example.com"),
+ test_domain("example.com"),
+ test_nsname("ns.example.com"),
+ rrset_a(ConstRRsetPtr(new RRset(
+ test_name, RRClass::IN(), RRType::A(), RRTTL(3600)))),
+ rrset_a_empty(ConstRRsetPtr(new RRset(
+ test_name, RRClass::IN(), RRType::A(), RRTTL(3600)))),
+ rrset_ns(ConstRRsetPtr(new RRset(
+ test_domain, RRClass::IN(), RRType::NS(), RRTTL(86400)))),
+ rrset_ch_txt(ConstRRsetPtr(new RRset(
+ test_domain, RRClass::CH(), RRType::TXT(), RRTTL(0)))),
+ rrset_siga(new RRset(test_name, RRClass::IN(), RRType::RRSIG(),
+ RRTTL(3600)))
+
+ {
+ // Add a couple of Rdata elements to the A RRset. The easiest way to
+ // do this is to override the "const" restrictions. As this is a test,
+ // we don't feel too bad about doing so.
+ AbstractRRset* a_rrset =
+ const_cast<AbstractRRset*>(rrset_a.getUnderlyingRRset().get());
+ a_rrset->addRdata(in::A("192.0.2.1"));
+ a_rrset->addRdata(in::A("192.0.2.2"));
+
+ // Create the RRSIG corresponding to the rrset_a record. The RDATA
+ // won't match the A record it covers, although it is internally
+ // self-consistent.
+ AbstractRRset* sig_rrset =
+ const_cast<AbstractRRset*>(rrset_siga.get());
+ sig_rrset->addRdata(generic::RRSIG(RRSIG_TXT));
+ }
+
+ const Name test_name;
+ const Name test_domain;
+ const Name test_nsname;
+
+ RBNodeRRset rrset_a;
+ RBNodeRRset rrset_a_empty;
+ const RBNodeRRset rrset_ns;
+ const RBNodeRRset rrset_ch_txt;
+
+ ConstRRsetPtr rrset_siga;
+};
+
+TEST_F(RBNodeRRsetTest, getRdataCount) {
+ EXPECT_EQ(0, rrset_a_empty.getRdataCount());
+ EXPECT_EQ(2, rrset_a.getRdataCount());
+}
+
+TEST_F(RBNodeRRsetTest, getName) {
+ EXPECT_EQ(test_name, rrset_a.getName());
+ EXPECT_EQ(test_domain, rrset_ns.getName());
+}
+
+TEST_F(RBNodeRRsetTest, getClass) {
+ EXPECT_EQ(RRClass("IN"), rrset_a.getClass());
+ EXPECT_EQ(RRClass("CH"), rrset_ch_txt.getClass());
+}
+
+TEST_F(RBNodeRRsetTest, getType) {
+ EXPECT_EQ(RRType("A"), rrset_a.getType());
+ EXPECT_EQ(RRType("NS"), rrset_ns.getType());
+ EXPECT_EQ(RRType("TXT"), rrset_ch_txt.getType());
+}
+
+TEST_F(RBNodeRRsetTest, getTTL) {
+ EXPECT_EQ(RRTTL(3600), rrset_a.getTTL());
+ EXPECT_EQ(RRTTL(86400), rrset_ns.getTTL());
+ EXPECT_EQ(RRTTL(0), rrset_ch_txt.getTTL());
+}
+
+TEST_F(RBNodeRRsetTest, setName) {
+ EXPECT_THROW(rrset_a.setName(test_nsname), NotImplemented);
+}
+
+TEST_F(RBNodeRRsetTest, setTTL) {
+ EXPECT_THROW(rrset_a.setTTL(RRTTL(86400)), NotImplemented);
+}
+
+TEST_F(RBNodeRRsetTest, toText) {
+ EXPECT_EQ("test.example.com. 3600 IN A 192.0.2.1\n"
+ "test.example.com. 3600 IN A 192.0.2.2\n",
+ rrset_a.toText());
+
+ // toText() cannot be performed for an empty RRset.
+ EXPECT_THROW(rrset_a_empty.toText(), EmptyRRset);
+}
+
+TEST_F(RBNodeRRsetTest, isSameKind) {
+ RBNodeRRset rrset_p(ConstRRsetPtr(new RRset(test_name, RRClass::IN(), RRType::A(), RRTTL(3600))));
+ RBNodeRRset rrset_q(ConstRRsetPtr(new RRset(test_name, RRClass::IN(), RRType::A(), RRTTL(3600))));
+ RRset rrset_w(test_name, RRClass::IN(), RRType::A(), RRTTL(3600));
+ RRset rrset_x(test_nsname, RRClass::IN(), RRType::A(), RRTTL(3600));
+ RRset rrset_y(test_name, RRClass::IN(), RRType::NS(), RRTTL(3600));
+ RRset rrset_z(test_name, RRClass::CH(), RRType::A(), RRTTL(3600));
+
+ EXPECT_TRUE(rrset_p.isSameKind(rrset_p));
+ EXPECT_FALSE(rrset_p.isSameKind(rrset_q));
+
+ EXPECT_TRUE(rrset_p.isSameKind(rrset_w));
+ EXPECT_FALSE(rrset_p.isSameKind(rrset_x));
+ EXPECT_FALSE(rrset_p.isSameKind(rrset_y));
+ EXPECT_FALSE(rrset_p.isSameKind(rrset_z));
+}
+
+// Note: although the next two tests are essentially the same and used common
+// test code, they use different test data: the MessageRenderer produces
+// compressed wire data whereas the OutputBuffer does not.
+
+template <typename T>
+void
+performToWireTest(T& dataHolder, const RBNodeRRset& rrset,
+ const RBNodeRRset& rrset_empty, const char* testdata)
+{
+ rrset.toWire(dataHolder);
+
+ std::vector<unsigned char> wiredata;
+ UnitTestUtil::readWireData(testdata, wiredata);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, dataHolder.getData(),
+ dataHolder.getLength(), &wiredata[0], wiredata.size());
+
+ // toWire() cannot be performed for an empty RRset.
+ dataHolder.clear();
+ EXPECT_THROW(rrset_empty.toWire(dataHolder), EmptyRRset);
+}
+
+TEST_F(RBNodeRRsetTest, toWireRenderer) {
+ MessageRenderer renderer;
+ performToWireTest(renderer, rrset_a, rrset_a_empty, "rrset_toWire2");
+}
+
+TEST_F(RBNodeRRsetTest, toWireBuffer) {
+ OutputBuffer buffer(0);
+ performToWireTest(buffer, rrset_a, rrset_a_empty, "rrset_toWire1");
+}
+
+TEST_F(RBNodeRRsetTest, addRdata) {
+ EXPECT_THROW(rrset_a.addRdata(in::A("192.0.2.3")), NotImplemented);
+
+ // Check the same goes for trying to add the wrong type of data
+ EXPECT_THROW(rrset_a.addRdata(generic::NS(test_nsname)), NotImplemented);
+}
+
+TEST_F(RBNodeRRsetTest, addRdataPtr) {
+ EXPECT_THROW(rrset_a_empty.addRdata(createRdata(rrset_a_empty.getType(),
+ rrset_a_empty.getClass(),
+ "192.0.2.1")),
+ NotImplemented);
+}
+
+TEST_F(RBNodeRRsetTest, getRDataIterator) {
+ RdataIteratorPtr it = rrset_a.getRdataIterator();
+ for (int i = 0; i < 2; ++i) {
+ ASSERT_FALSE(it->isLast());
+ ASSERT_EQ(0, it->getCurrent().compare(in::A("192.0.2.1")));
+
+ it->next();
+ ASSERT_FALSE(it->isLast());
+ ASSERT_EQ(0, it->getCurrent().compare(in::A("192.0.2.2")));
+
+ it->next();
+ ASSERT_TRUE(it->isLast());
+
+ // Should be able repeat the iteration by calling first().
+ it->first();
+ }
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST_F(RBNodeRRsetTest, LeftShiftOperator) {
+ ostringstream oss;
+ oss << rrset_a;
+ EXPECT_EQ("test.example.com. 3600 IN A 192.0.2.1\n"
+ "test.example.com. 3600 IN A 192.0.2.2\n", oss.str());
+}
+
+// addRRSIG tests.
+TEST_F(RBNodeRRsetTest, addRRsigConstRdataPointer) {
+ EXPECT_FALSE(rrset_a.getRRsig());
+ ConstRdataPtr data = createRdata(rrset_siga->getType(),
+ rrset_siga->getClass(), RRSIG_TXT);
+ rrset_a.addRRsig(data);
+ rrsetCheck(rrset_siga, rrset_a.getRRsig());
+}
+
+TEST_F(RBNodeRRsetTest, addRRsigRdataPointer) {
+ EXPECT_FALSE(rrset_a.getRRsig());
+ RdataPtr data = createRdata(rrset_siga->getType(), rrset_siga->getClass(),
+ RRSIG_TXT);
+ rrset_a.addRRsig(data);
+ rrsetCheck(rrset_siga, rrset_a.getRRsig());
+}
+
+TEST_F(RBNodeRRsetTest, addRRsigAbstractRRset) {
+ EXPECT_FALSE(rrset_a.getRRsig());
+ rrset_a.addRRsig(*(rrset_siga.get()));
+ rrsetCheck(rrset_siga, rrset_a.getRRsig());
+}
+
+TEST_F(RBNodeRRsetTest, addRRsigConstantRRsetPointer) {
+ EXPECT_FALSE(rrset_a.getRRsig());
+ rrset_a.addRRsig(rrset_siga);
+ rrsetCheck(rrset_siga, rrset_a.getRRsig());
+}
+
+TEST_F(RBNodeRRsetTest, addRRsigRRsetPointer) {
+ EXPECT_FALSE(rrset_a.getRRsig());
+ RRsetPtr rrsig(new RRset(test_name, RRClass::IN(), RRType::RRSIG(),
+ RRTTL(3600)));
+ rrsig->addRdata(generic::RRSIG(RRSIG_TXT));
+ rrset_a.addRRsig(rrsig);
+ rrsetCheck(rrset_siga, rrset_a.getRRsig());
+}
+
+TEST_F(RBNodeRRsetTest, removeRRsig) {
+ EXPECT_FALSE(rrset_a.getRRsig());
+ rrset_a.addRRsig(*(rrset_siga.get()));
+ EXPECT_TRUE(rrset_a.getRRsig());
+ rrset_a.removeRRsig();
+ EXPECT_FALSE(rrset_a.getRRsig());
+}
+
+} // Anonymous namespace
diff --git a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
index 5122136..718d29b 100644
--- a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
@@ -12,8 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <algorithm>
-#include <vector>
+#include "faked_nsec3.h"
#include <datasrc/sqlite3_accessor.h>
@@ -21,14 +20,20 @@
#include <dns/rrclass.h>
+#include <sqlite3.h>
+
#include <gtest/gtest.h>
+
#include <boost/lexical_cast.hpp>
#include <boost/scoped_ptr.hpp>
+
+#include <algorithm>
+#include <vector>
#include <fstream>
-#include <sqlite3.h>
using namespace std;
using namespace isc::datasrc;
+using namespace isc::datasrc::test;
using boost::lexical_cast;
using isc::data::ConstElementPtr;
using isc::data::Element;
@@ -37,15 +42,22 @@ using isc::dns::Name;
namespace {
// Some test data
-std::string SQLITE_DBFILE_EXAMPLE = TEST_DATA_DIR "/test.sqlite3";
-std::string SQLITE_DBFILE_EXAMPLE2 = TEST_DATA_DIR "/example2.com.sqlite3";
-std::string SQLITE_DBNAME_EXAMPLE2 = "sqlite3_example2.com.sqlite3";
-std::string SQLITE_DBFILE_EXAMPLE_ROOT = TEST_DATA_DIR "/test-root.sqlite3";
-std::string SQLITE_DBNAME_EXAMPLE_ROOT = "sqlite3_test-root.sqlite3";
-std::string SQLITE_DBFILE_BROKENDB = TEST_DATA_DIR "/brokendb.sqlite3";
-std::string SQLITE_DBFILE_MEMORY = ":memory:";
-std::string SQLITE_DBFILE_EXAMPLE_ORG = TEST_DATA_DIR "/example.org.sqlite3";
-std::string SQLITE_DBFILE_DIFFS = TEST_DATA_DIR "/diffs.sqlite3";
+const char* const SQLITE_DBFILE_EXAMPLE = TEST_DATA_DIR "/test.sqlite3";
+const char* const SQLITE_DBFILE_EXAMPLE2 =
+ TEST_DATA_DIR "/example2.com.sqlite3";
+const char* const SQLITE_DBNAME_EXAMPLE2 = "sqlite3_example2.com.sqlite3";
+const char* const SQLITE_DBFILE_EXAMPLE_ROOT =
+ TEST_DATA_DIR "/test-root.sqlite3";
+const char* const SQLITE_DBNAME_EXAMPLE_ROOT = "sqlite3_test-root.sqlite3";
+const char* const SQLITE_DBFILE_BROKENDB = TEST_DATA_DIR "/brokendb.sqlite3";
+const char* const SQLITE_DBFILE_MEMORY = ":memory:";
+const char* const SQLITE_DBFILE_EXAMPLE_ORG =
+ TEST_DATA_DIR "/example.org.sqlite3";
+const char* const SQLITE_DBFILE_DIFFS = TEST_DATA_DIR "/diffs.sqlite3";
+const char* const SQLITE_DBFILE_NEWSCHEMA = TEST_DATA_DIR "/newschema.sqlite3";
+const char* const SQLITE_DBFILE_OLDSCHEMA = TEST_DATA_DIR "/oldschema.sqlite3";
+const char* const SQLITE_DBFILE_NEW_MINOR_SCHEMA =
+ TEST_DATA_DIR "/new_minor_schema.sqlite3";
// 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,
@@ -74,6 +86,20 @@ TEST(SQLite3Open, brokenDB) {
SQLite3Error);
}
+// Different schema versions
+TEST(SQLite3Open, differentSchemaVersions) {
+ // If the major version is different from the current one, it should fail.
+ EXPECT_THROW(SQLite3Accessor(SQLITE_DBFILE_NEWSCHEMA, "IN"),
+ IncompatibleDbVersion);
+ EXPECT_THROW(SQLite3Accessor(SQLITE_DBFILE_OLDSCHEMA, "IN"),
+ IncompatibleDbVersion);
+
+ // Difference in the minor version is okay (as of this test written
+ // the current minor version is 0, so we can only test the case with a
+ // higher minor version).
+ EXPECT_NO_THROW(SQLite3Accessor(SQLITE_DBFILE_NEW_MINOR_SCHEMA, "IN"));
+}
+
// Test we can create the schema on the fly
TEST(SQLite3Open, memoryDB) {
EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_MEMORY, "IN"));
@@ -173,6 +199,151 @@ TEST_F(SQLite3AccessorTest, iterator) {
EXPECT_FALSE(context->getNext(data));
}
+// This tests the iterator through the whole zone returns NSEC3 records as
+// well. We test this specifically, as it lives in separate table and needs
+// extra handling.
+TEST_F(SQLite3AccessorTest, nsec3Iterator) {
+ // Get the zone
+ const std::pair<bool, int>
+ zone_info(accessor->getZone("sql2.example.com."));
+ ASSERT_TRUE(zone_info.first);
+
+ // Iterate through it
+ DatabaseAccessor::IteratorContextPtr
+ context(accessor->getAllRecords(zone_info.second));
+
+ // We just pick a random NSEC3 to check, the check of complete iterator
+ // is in the above test. In addition, we count the number of NSEC3, RRSIG
+ // and all records, as some kind of check it returns all the data.
+ std::string data[DatabaseAccessor::COLUMN_COUNT];
+
+ size_t nsec3count(0), rrsigcount(0), recordcount(0);
+ bool nsec3match(false);
+ while (context->getNext(data)) {
+ if (data[DatabaseAccessor::TYPE_COLUMN] == "NSEC3") {
+ nsec3count ++;
+ if (data[DatabaseAccessor::NAME_COLUMN] ==
+ "1BB7SO0452U1QHL98UISNDD9218GELR5.sql2.example.com.") {
+ nsec3match = true;
+ EXPECT_EQ("7200", data[DatabaseAccessor::TTL_COLUMN]);
+ EXPECT_EQ("1 0 10 FEEDABEE 4KLSVDE8KH8G95VU68R7AHBE1CPQN38J",
+ data[DatabaseAccessor::RDATA_COLUMN]);
+ }
+ } else if (data[DatabaseAccessor::TYPE_COLUMN] == "RRSIG") {
+ rrsigcount ++;
+ }
+ recordcount ++;
+ }
+
+ // We counted everything now, so check there's nothing else to count
+ EXPECT_EQ(11, nsec3count);
+ EXPECT_EQ(22, rrsigcount);
+ EXPECT_EQ(46, recordcount);
+ EXPECT_TRUE(nsec3match) << "No NSEC3 found when iterating the zone";
+}
+
+// This tests getting NSEC3 records
+TEST_F(SQLite3AccessorTest, nsec3) {
+ const std::pair<bool, int>
+ zone_info(accessor->getZone("sql2.example.com."));
+ ASSERT_TRUE(zone_info.first);
+
+ DatabaseAccessor::IteratorContextPtr
+ context(accessor->getNSEC3Records("1BB7SO0452U1QHL98UISNDD9218GELR5",
+ zone_info.second));
+ // This relies on specific ordering in the DB. Is it OK?
+ // The name field is empty, as well as the sigtype one. This is OK, as
+ // both are not needed and the interface allows it.
+ checkRR(context, "", "7200", "NSEC3",
+ "1 0 10 FEEDABEE 4KLSVDE8KH8G95VU68R7AHBE1CPQN38J");
+ checkRR(context, "", "7200", "RRSIG",
+ "NSEC3 5 4 7200 20100410172647 20100311172647 63192 "
+ "sql2.example.com. gNIVj4T8t51fEU6kOPpvK7HOGBFZGbalN5ZK "
+ "mInyrww6UWZsUNdw07ge6/U6HfG+/s61RZ/L is2M6yUWHyXbNbj/"
+ "QqwqgadG5dhxTArfuR02 xP600x0fWX8LXzW4yLMdKVxGbzYT+vvGz71o "
+ "8gHSY5vYTtothcZQa4BMKhmGQEk=");
+
+ // And that's all
+ std::string data[DatabaseAccessor::COLUMN_COUNT];
+ EXPECT_FALSE(context->getNext(data));
+
+ // Calling again won't hurt
+ EXPECT_FALSE(context->getNext(data));
+
+ // This one should be empty â no data here
+ context = accessor->getNSEC3Records("NO_SUCH_HASH", zone_info.second);
+ EXPECT_FALSE(context->getNext(data));
+ // Still nothing? ;-)
+ EXPECT_FALSE(context->getNext(data));
+}
+
+// This tests getting a previoeus hash in the NSEC3 namespace of a zone,
+// including a wrap-around and asking for a hash that does not exist in the.
+// zone at all.
+TEST_F(SQLite3AccessorTest, nsec3Previous) {
+ // Get the zone
+ const std::pair<bool, int>
+ zone_info(accessor->getZone("sql2.example.com."));
+ ASSERT_TRUE(zone_info.first);
+
+ std::string data[DatabaseAccessor::COLUMN_COUNT];
+
+ // Test a previous hash for something that is in the zone
+ // (ensuring it is really there)
+ DatabaseAccessor::IteratorContextPtr
+ context(accessor->getNSEC3Records("703OOGCKF8VEV1N7U64D1JG19URETN8N",
+ zone_info.second));
+ EXPECT_TRUE(context->getNext(data));
+ EXPECT_EQ("56IEQ664LHDAKVPE2FL179MSM3QAOFVC", accessor->
+ findPreviousNSEC3Hash(zone_info.second,
+ "703OOGCKF8VEV1N7U64D1JG19URETN8N"));
+
+ // Test a previous hash for something that is not in the
+ // zone
+ context = accessor->getNSEC3Records("702OOGCKF8VEV1N7U64D1JG19URETN8N",
+ zone_info.second);
+ EXPECT_FALSE(context->getNext(data));
+ EXPECT_EQ("56IEQ664LHDAKVPE2FL179MSM3QAOFVC", accessor->
+ findPreviousNSEC3Hash(zone_info.second,
+ "702OOGCKF8VEV1N7U64D1JG19URETN8N"));
+
+ // Search at the first item, should wrap around
+ context = accessor->getNSEC3Records("1BB7SO0452U1QHL98UISNDD9218GELR5",
+ zone_info.second);
+ EXPECT_TRUE(context->getNext(data));
+ EXPECT_EQ("RKBUCQT8T78GV6QBCGBHCHC019LG73SJ", accessor->
+ findPreviousNSEC3Hash(zone_info.second,
+ "1BB7SO0452U1QHL98UISNDD9218GELR5"));
+
+ // Search before the first item, should wrap around
+ context = accessor->getNSEC3Records("0BB7SO0452U1QHL98UISNDD9218GELR5",
+ zone_info.second);
+ EXPECT_FALSE(context->getNext(data));
+ EXPECT_EQ("RKBUCQT8T78GV6QBCGBHCHC019LG73SJ", accessor->
+ findPreviousNSEC3Hash(zone_info.second,
+ "0BB7SO0452U1QHL98UISNDD9218GELR5"));
+
+ // Search after the last item (should return the last one)
+ context = accessor->getNSEC3Records("RRBUCQT8T78GV6QBCGBHCHC019LG73SJ",
+ zone_info.second);
+ EXPECT_FALSE(context->getNext(data));
+ EXPECT_EQ("RKBUCQT8T78GV6QBCGBHCHC019LG73SJ", accessor->
+ findPreviousNSEC3Hash(zone_info.second,
+ "RRBUCQT8T78GV6QBCGBHCHC019LG73SJ"));
+}
+
+// Check it throws when we want a previous NSEC3 hash in an unsigned zone
+TEST_F(SQLite3AccessorTest, nsec3PreviousUnsigned) {
+ // This zone did not look signed in the test file.
+ const std::pair<bool, int>
+ unsigned_zone_info(accessor->getZone("example.com."));
+
+ EXPECT_THROW(accessor->
+ findPreviousNSEC3Hash(unsigned_zone_info.second,
+ "0BB7SO0452U1QHL98UISNDD9218GELR5"),
+ DataSourceError);
+}
+
// This tests the difference iterator context
// Test that at attempt to create a difference iterator for a serial number
@@ -297,11 +468,11 @@ TEST(SQLite3Open, getDBNameExampleROOT) {
// Simple function to match records
void
checkRecordRow(const std::string columns[],
- const std::string& field0,
- const std::string& field1,
- const std::string& field2,
- const std::string& field3,
- const std::string& field4)
+ const std::string& field0, // for type
+ const std::string& field1, // for TTL
+ const std::string& field2, // for "sigtype"
+ const std::string& field3, // for rdata
+ const std::string& field4) // for name
{
EXPECT_EQ(field0, columns[DatabaseAccessor::TYPE_COLUMN]);
EXPECT_EQ(field1, columns[DatabaseAccessor::TTL_COLUMN]);
@@ -570,13 +741,30 @@ const char* const deleted_data[] = {
// Existing data to be removed commonly used by some of the tests below
"foo.bar.example.com.", "A", "192.0.2.1"
};
+const char* const nsec3_data[DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT] = {
+ // example NSEC3 parameters. Using "apex_hash" just as a convenient
+ // shortcut; otherwise it has nothing to do with the zone apex for the
+ // purpose of this test.
+ apex_hash, "3600", "NSEC3",
+ "1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR NS SOA"
+};
+const char* const nsec3_sig_data[DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT] = {
+ ns1_hash, "3600", "RRSIG",
+ "NSEC3 5 3 3600 20000101000000 20000201000000 12345 "
+ "example.com. FAKEFAKEFAKE"
+};
+const char* const nsec3_deleted_data[] = {
+ // Delete parameters for nsec3_data
+ apex_hash, nsec3_data[DatabaseAccessor::ADD_NSEC3_TYPE],
+ nsec3_data[DatabaseAccessor::ADD_NSEC3_RDATA]
+};
class SQLite3Update : public SQLite3AccessorTest {
protected:
SQLite3Update() {
// Note: if "installing" the test file fails some of the subsequent
// tests would fail.
- const char *install_cmd = INSTALL_PROG " " TEST_DATA_DIR
+ const char *install_cmd = INSTALL_PROG " -c " TEST_DATA_DIR
"/test.sqlite3 " TEST_DATA_BUILDDIR
"/test.sqlite3.copied";
if (system(install_cmd) != 0) {
@@ -596,6 +784,7 @@ protected:
int zone_id;
std::string get_columns[DatabaseAccessor::COLUMN_COUNT];
std::string add_columns[DatabaseAccessor::ADD_COLUMN_COUNT];
+ std::string add_nsec3_columns[DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT];
std::string del_params[DatabaseAccessor::DEL_PARAM_COUNT];
std::string diff_params[DatabaseAccessor::DIFF_PARAM_COUNT];
@@ -623,6 +812,28 @@ checkRecords(SQLite3Accessor& accessor, int zone_id, const std::string& name,
EXPECT_TRUE(it == expected_rows.end());
}
+// Similar to the previous one, but checking transactions on the nsec3 table.
+void
+checkNSEC3Records(SQLite3Accessor& accessor, int zone_id,
+ const std::string& hash,
+ vector<const char* const*> expected_rows)
+{
+ DatabaseAccessor::IteratorContextPtr iterator =
+ accessor.getNSEC3Records(hash, zone_id);
+ std::string columns[DatabaseAccessor::COLUMN_COUNT];
+ vector<const char* const*>::const_iterator it = expected_rows.begin();
+ while (iterator->getNext(columns)) {
+ ASSERT_TRUE(it != expected_rows.end());
+ checkRecordRow(columns, (*it)[DatabaseAccessor::ADD_NSEC3_TYPE],
+ (*it)[DatabaseAccessor::ADD_NSEC3_TTL],
+ "", // sigtype, should always be empty
+ (*it)[DatabaseAccessor::ADD_NSEC3_RDATA],
+ ""); // name, always empty
+ ++it;
+ }
+ EXPECT_TRUE(it == expected_rows.end());
+}
+
TEST_F(SQLite3Update, emptyUpdate) {
// If we do nothing between start and commit, the zone content
// should be intact.
@@ -645,6 +856,26 @@ TEST_F(SQLite3Update, flushZone) {
checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
}
+TEST_F(SQLite3Update, flushZoneWithNSEC3) {
+ // Similar to the previous case, but make sure the separate nsec3 table
+ // is also cleared. We first need to add something to the table.
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+ copy(nsec3_data, nsec3_data + DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT,
+ add_nsec3_columns);
+ accessor->addNSEC3RecordToZone(add_nsec3_columns);
+ accessor->commit();
+
+ // Confirm it surely exists.
+ expected_stored.clear();
+ expected_stored.push_back(nsec3_data);
+ checkNSEC3Records(*accessor, zone_id, apex_hash, expected_stored);
+
+ // Then starting zone replacement. the NSEC3 record should have been
+ // removed.
+ zone_id = accessor->startUpdateZone("example.com.", true).second;
+ checkNSEC3Records(*accessor, zone_id, apex_hash, empty_stored);
+}
+
TEST_F(SQLite3Update, readWhileUpdate) {
zone_id = accessor->startUpdateZone("example.com.", true).second;
checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
@@ -669,17 +900,27 @@ TEST_F(SQLite3Update, rollback) {
checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
}
-TEST_F(SQLite3Update, rollbackFailure) {
+TEST_F(SQLite3Update, rollbackFailure) {
// This test emulates a rare scenario of making rollback attempt fail.
// The iterator is paused in the middle of getting records, which prevents
// the rollback operation at the end of the test.
+ // Since SQLite3 version 3.7.11, rollbacks do not fail on pending
+ // transactions anymore, making this test fail (and moot), but the
+ // transactions will fail after it, so, depending on version,
+ // we test whether that happens and is caught
string columns[DatabaseAccessor::COLUMN_COUNT];
iterator = accessor->getRecords("example.com.", zone_id);
EXPECT_TRUE(iterator->getNext(columns));
accessor->startUpdateZone("example.com.", true);
+#if SQLITE_VERSION_NUMBER < 3007011
EXPECT_THROW(accessor->rollback(), DataSourceError);
+ EXPECT_NO_THROW(iterator->getNext(columns));
+#else
+ EXPECT_NO_THROW(accessor->rollback());
+ EXPECT_THROW(iterator->getNext(columns), DataSourceError);
+#endif
}
TEST_F(SQLite3Update, commitConflict) {
@@ -748,6 +989,62 @@ TEST_F(SQLite3Update, addRecord) {
checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
}
+TEST_F(SQLite3Update, addNSEC3Record) {
+ // Similar to the previous test, but for NSEC3-related records
+ checkRecords(*accessor, zone_id, apex_hash, empty_stored);
+ checkRecords(*accessor, zone_id, ns1_hash, empty_stored);
+
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+ // Add an NSEC3
+ copy(nsec3_data, nsec3_data + DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT,
+ add_nsec3_columns);
+ accessor->addNSEC3RecordToZone(add_nsec3_columns);
+
+ // Add an RRSIG for NSEC3
+ copy(nsec3_sig_data,
+ nsec3_sig_data + DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT,
+ add_nsec3_columns);
+ accessor->addNSEC3RecordToZone(add_nsec3_columns);
+
+ // Check the stored data, before and after commit().
+ for (size_t i = 0; i < 2; ++i) {
+ expected_stored.clear();
+ expected_stored.push_back(nsec3_data);
+ checkNSEC3Records(*accessor, zone_id, apex_hash, expected_stored);
+
+ expected_stored.clear();
+ expected_stored.push_back(nsec3_sig_data);
+ checkNSEC3Records(*accessor, zone_id, ns1_hash, expected_stored);
+
+ if (i == 0) { // make sure commit() happens only once
+ accessor->commit();
+ }
+ }
+}
+
+TEST_F(SQLite3Update, nsec3IteratorOnAdd) {
+ // This test checks if an added NSEC3 record will appear in the iterator
+ // result, meeting the expectation of addNSEC3RecordToZone.
+ // Specifically, it checks if the name column is filled with the complete
+ // owner name.
+
+ // We'll replace the zone, and add one NSEC3 record, and only that one.
+ zone_id = accessor->startUpdateZone("example.com.", true).second;
+ copy(nsec3_data, nsec3_data + DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT,
+ add_nsec3_columns);
+ accessor->addNSEC3RecordToZone(add_nsec3_columns);
+ accessor->commit();
+
+ // the zone should contain only one record we just added.
+ DatabaseAccessor::IteratorContextPtr context =
+ accessor->getAllRecords(zone_id);
+ string data[DatabaseAccessor::COLUMN_COUNT];
+ EXPECT_TRUE(context->getNext(data));
+ EXPECT_EQ(string(apex_hash) + ".example.com.",
+ data[DatabaseAccessor::NAME_COLUMN]);
+ EXPECT_FALSE(context->getNext(data));
+}
+
TEST_F(SQLite3Update, addThenRollback) {
zone_id = accessor->startUpdateZone("example.com.", false).second;
copy(new_data, new_data + DatabaseAccessor::ADD_COLUMN_COUNT,
@@ -758,7 +1055,11 @@ TEST_F(SQLite3Update, addThenRollback) {
expected_stored.push_back(new_data);
checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
+ // Rollback the transaction, and confirm the zone reverts to the previous
+ // state. We also start another update to check if the accessor can be
+ // reused for a new update after rollback.
accessor->rollback();
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
checkRecords(*accessor, zone_id, "newdata.example.com.", empty_stored);
}
@@ -784,6 +1085,12 @@ TEST_F(SQLite3Update, duplicateAdd) {
TEST_F(SQLite3Update, invalidAdd) {
// An attempt of add before an explicit start of transaction
EXPECT_THROW(accessor->addRecordToZone(add_columns), DataSourceError);
+
+ // Same for addNSEC3.
+ copy(nsec3_data, nsec3_data + DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT,
+ add_nsec3_columns);
+ EXPECT_THROW(accessor->addNSEC3RecordToZone(add_nsec3_columns),
+ DataSourceError);
}
TEST_F(SQLite3Update, deleteRecord) {
@@ -801,6 +1108,32 @@ TEST_F(SQLite3Update, deleteRecord) {
checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
}
+TEST_F(SQLite3Update, deleteNSEC3Record) {
+ // Similar to the previous test, but for NSEC3.
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+ checkNSEC3Records(*accessor, zone_id, apex_hash, empty_stored);
+
+ // We first need to add some record.
+ copy(nsec3_data, nsec3_data + DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT,
+ add_nsec3_columns);
+ accessor->addNSEC3RecordToZone(add_nsec3_columns);
+
+ // Now it should exist.
+ expected_stored.clear();
+ expected_stored.push_back(nsec3_data);
+ checkNSEC3Records(*accessor, zone_id, apex_hash, expected_stored);
+
+ // Delete it, and confirm that.
+ copy(nsec3_deleted_data,
+ nsec3_deleted_data + DatabaseAccessor::DEL_PARAM_COUNT, del_params);
+ accessor->deleteNSEC3RecordInZone(del_params);
+ checkNSEC3Records(*accessor, zone_id, apex_hash, empty_stored);
+
+ // Commit the change, and confirm the deleted data still isn't there.
+ accessor->commit();
+ checkNSEC3Records(*accessor, zone_id, apex_hash, empty_stored);
+}
+
TEST_F(SQLite3Update, deleteThenRollback) {
zone_id = accessor->startUpdateZone("example.com.", false).second;
@@ -847,6 +1180,10 @@ TEST_F(SQLite3Update, deleteNonexistent) {
TEST_F(SQLite3Update, invalidDelete) {
// An attempt of delete before an explicit start of transaction
EXPECT_THROW(accessor->deleteRecordInZone(del_params), DataSourceError);
+
+ // Same for NSEC3.
+ EXPECT_THROW(accessor->deleteNSEC3RecordInZone(del_params),
+ DataSourceError);
}
TEST_F(SQLite3Update, emptyTransaction) {
@@ -947,7 +1284,7 @@ const char* const diff_end_data[] = {
"1300", DIFF_ADD_TEXT
};
const char* const diff_add_a_data[] = {
- "dns01.example.com.", "A", "3600", "192.0.2.10", "1234", DIFF_ADD_TEXT
+ "dns01.example.com.", "A", "3600", "192.0.2.10", "1300", DIFF_ADD_TEXT
};
// The following two are helper functions to convert textual test data
@@ -968,8 +1305,19 @@ getOperation(const char* const diff_data[]) {
// diffs.
void
checkDiffs(const vector<const char* const*>& expected,
- const vector<vector<string> >& actual)
+ DatabaseAccessor::IteratorContextPtr rr_iterator)
{
+ vector<vector<string> > actual;
+ string columns_holder[DatabaseAccessor::COLUMN_COUNT];
+ while (rr_iterator->getNext(columns_holder)) {
+ // Reorder the 'actual' vector to be compatible with the expected one.
+ vector<string> columns;
+ columns.push_back(columns_holder[DatabaseAccessor::NAME_COLUMN]);
+ columns.push_back(columns_holder[DatabaseAccessor::TYPE_COLUMN]);
+ columns.push_back(columns_holder[DatabaseAccessor::TTL_COLUMN]);
+ columns.push_back(columns_holder[DatabaseAccessor::RDATA_COLUMN]);
+ actual.push_back(columns);
+ }
EXPECT_EQ(expected.size(), actual.size());
const size_t n_diffs = std::min(expected.size(), actual.size());
for (size_t i = 0; i < n_diffs; ++i) {
@@ -995,16 +1343,18 @@ TEST_F(SQLite3Update, addRecordDiff) {
getOperation(diff_end_data), diff_params);
// Until the diffs are committed, they are not visible to other accessors.
- EXPECT_TRUE(another_accessor->getRecordDiff(zone_id).empty());
+ EXPECT_THROW(another_accessor->getDiffs(zone_id, 1234, 1300),
+ NoSuchSerial);
accessor->commit();
expected_stored.clear();
expected_stored.push_back(diff_begin_data);
expected_stored.push_back(diff_end_data);
- checkDiffs(expected_stored, accessor->getRecordDiff(zone_id));
+ checkDiffs(expected_stored, accessor->getDiffs(zone_id, 1234, 1300));
// Now it should be visible to others, too.
- checkDiffs(expected_stored, another_accessor->getRecordDiff(zone_id));
+ checkDiffs(expected_stored, another_accessor->getDiffs(zone_id, 1234,
+ 1300));
}
TEST_F(SQLite3Update, addRecordOfLargeSerial) {
@@ -1036,7 +1386,7 @@ TEST_F(SQLite3Update, addRecordOfLargeSerial) {
expected_stored.clear();
expected_stored.push_back(begin_data);
expected_stored.push_back(diff_end_data);
- checkDiffs(expected_stored, accessor->getRecordDiff(zone_id));
+ checkDiffs(expected_stored, accessor->getDiffs(zone_id, 4294967295U, 1300));
}
TEST_F(SQLite3Update, addDiffWithoutUpdate) {
@@ -1081,7 +1431,7 @@ TEST_F(SQLite3Update, addDiffRollback) {
getOperation(diff_begin_data), diff_params);
accessor->rollback();
- EXPECT_TRUE(accessor->getRecordDiff(zone_id).empty());
+ EXPECT_THROW(accessor->getDiffs(zone_id, 1234, 1234), NoSuchSerial);
}
TEST_F(SQLite3Update, addDiffInBadOrder) {
@@ -1093,19 +1443,23 @@ TEST_F(SQLite3Update, addDiffInBadOrder) {
copy(diff_end_data, diff_end_data + DatabaseAccessor::DIFF_PARAM_COUNT,
diff_params);
accessor->addRecordDiff(zone_id, getVersion(diff_end_data),
- getOperation(diff_end_data), diff_params);
+ static_cast<DatabaseAccessor::DiffOperation>(
+ lexical_cast<int>(DIFF_DELETE_TEXT)),
+ diff_params);
copy(diff_begin_data, diff_begin_data + DatabaseAccessor::DIFF_PARAM_COUNT,
diff_params);
accessor->addRecordDiff(zone_id, getVersion(diff_begin_data),
- getOperation(diff_begin_data), diff_params);
+ static_cast<DatabaseAccessor::DiffOperation>(
+ lexical_cast<int>(DIFF_ADD_TEXT)),
+ diff_params);
accessor->commit();
expected_stored.clear();
expected_stored.push_back(diff_end_data);
expected_stored.push_back(diff_begin_data);
- checkDiffs(expected_stored, accessor->getRecordDiff(zone_id));
+ checkDiffs(expected_stored, accessor->getDiffs(zone_id, 1300, 1234));
}
TEST_F(SQLite3Update, addDiffWithUpdate) {
@@ -1175,19 +1529,6 @@ TEST_F(SQLite3Update, addDiffWithUpdate) {
expected_stored.push_back(diff_end_data);
expected_stored.push_back(diff_add_a_data);
- checkDiffs(expected_stored, accessor->getRecordDiff(zone_id));
-}
-
-TEST_F(SQLite3Update, addDiffWithNoTable) {
- // An attempt of adding diffs to an old version of database that doesn't
- // have a diffs table. This will fail in preparing the statement.
- initAccessor(SQLITE_DBFILE_EXAMPLE + ".nodiffs", "IN");
- zone_id = accessor->startUpdateZone("example.com.", false).second;
- copy(diff_begin_data, diff_begin_data + DatabaseAccessor::DIFF_PARAM_COUNT,
- diff_params);
- EXPECT_THROW(accessor->addRecordDiff(zone_id, getVersion(diff_begin_data),
- getOperation(diff_begin_data),
- diff_params),
- SQLite3Error);
+ checkDiffs(expected_stored, accessor->getDiffs(zone_id, 1234, 1300));
}
} // end anonymous namespace
diff --git a/src/lib/datasrc/tests/sqlite3_unittest.cc b/src/lib/datasrc/tests/sqlite3_unittest.cc
index ce9413d..ac1211b 100644
--- a/src/lib/datasrc/tests/sqlite3_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_unittest.cc
@@ -51,6 +51,10 @@ ConstElementPtr SQLITE_DBFILE_BROKENDB = Element::fromJSON(
"{ \"database_file\": \"" TEST_DATA_DIR "/brokendb.sqlite3\"}");
ConstElementPtr SQLITE_DBFILE_MEMORY = Element::fromJSON(
"{ \"database_file\": \":memory:\"}");
+ConstElementPtr SQLITE_DBFILE_NEWSCHEMA = Element::fromJSON(
+ "{ \"database_file\": \"" TEST_DATA_DIR "/newschema.sqlite3\"}");
+ConstElementPtr SQLITE_DBFILE_OLDSCHEMA = Element::fromJSON(
+ "{ \"database_file\": \"" TEST_DATA_DIR "/oldschema.sqlite3\"}");
// 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,
@@ -403,6 +407,17 @@ TEST_F(Sqlite3DataSourceTest, openBrokenDB) {
EXPECT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_EXAMPLE));
}
+// Different schema versions, see sqlite3_accessor_unittest.
+TEST_F(Sqlite3DataSourceTest, differentSchemaVersions) {
+ EXPECT_EQ(DataSrc::SUCCESS, data_source.close());
+ EXPECT_THROW(data_source.init(SQLITE_DBFILE_NEWSCHEMA),
+ IncompatibleDbVersion);
+ EXPECT_THROW(data_source.init(SQLITE_DBFILE_OLDSCHEMA),
+ IncompatibleDbVersion);
+ // Don't bother to test the new_minor case; we should retire this stuff
+ // before it really happens.
+}
+
// This test only confirms that on-the-fly schema creation works.
TEST_F(Sqlite3DataSourceTest, memoryDB) {
EXPECT_EQ(DataSrc::SUCCESS, data_source.close());
diff --git a/src/lib/datasrc/tests/static_unittest.cc b/src/lib/datasrc/tests/static_unittest.cc
index 4c9fe42..2a19ecb 100644
--- a/src/lib/datasrc/tests/static_unittest.cc
+++ b/src/lib/datasrc/tests/static_unittest.cc
@@ -56,14 +56,17 @@ protected:
authors_data.push_back("Dmitriy Volodin");
authors_data.push_back("Evan Hunt");
authors_data.push_back("Haidong Wang");
+ authors_data.push_back("Haikuo Zhang");
authors_data.push_back("Han Feng");
authors_data.push_back("Jelte Jansen");
- authors_data.push_back("Jeremy C. Reed");
+ authors_data.push_back("Jeremy C. Reed");
+ authors_data.push_back("Xie Jiagui");
authors_data.push_back("Jin Jian");
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("Mukund Sivaraman");
authors_data.push_back("Naoki Kambe");
authors_data.push_back("Shane Kerr");
authors_data.push_back("Shen Tingting");
diff --git a/src/lib/datasrc/tests/test_client.cc b/src/lib/datasrc/tests/test_client.cc
new file mode 100644
index 0000000..c7854ed
--- /dev/null
+++ b/src/lib/datasrc/tests/test_client.cc
@@ -0,0 +1,92 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+
+#include <dns/masterload.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <datasrc/client.h>
+#include <datasrc/zone.h>
+#include <datasrc/sqlite3_accessor.h>
+
+#include "test_client.h"
+
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <cstdlib>
+#include <istream>
+#include <fstream>
+
+using namespace std;
+using boost::shared_ptr;
+
+using namespace isc::dns;
+
+namespace isc {
+namespace datasrc {
+namespace unittest {
+
+namespace {
+// A helper subroutine for the SQLite3Client creator.
+void
+addRRset(ZoneUpdaterPtr updater, ConstRRsetPtr rrset) {
+ updater->addRRset(*rrset);
+}
+}
+
+shared_ptr<DataSourceClient>
+createSQLite3Client(RRClass zclass, const Name& zname,
+ const char* const db_file, const char* const zone_file)
+{
+ ifstream ifs(zone_file, ios_base::in);
+ if (ifs.fail()) {
+ isc_throw(isc::Unexpected, "Failed to open test zone file: "
+ << zone_file);
+ }
+ return (createSQLite3Client(zclass, zname, db_file, ifs));
+}
+
+shared_ptr<DataSourceClient>
+createSQLite3Client(RRClass zclass, const Name& zname,
+ const char* const db_file, istream& rr_stream)
+{
+ // We always begin with an empty template SQLite3 DB file and install
+ // the zone data from the zone file to ensure both cases have the
+ // same test data.
+ const char* const install_cmd_prefix = INSTALL_PROG " -c " TEST_DATA_COMMONDIR
+ "/rwtest.sqlite3 ";
+ const string install_cmd = string(install_cmd_prefix) + db_file;
+ if (system(install_cmd.c_str()) != 0) {
+ isc_throw(isc::Unexpected,
+ "Error setting up; command failed: " << install_cmd);
+ }
+
+ shared_ptr<SQLite3Accessor> accessor(
+ new SQLite3Accessor(db_file, zclass.toText()));
+ shared_ptr<DatabaseClient> client(new DatabaseClient(zclass, accessor));
+
+ ZoneUpdaterPtr updater = client->getUpdater(zname, true);
+ masterLoad(rr_stream, zname, zclass, boost::bind(addRRset, updater, _1));
+
+ updater->commit();
+
+ return (client);
+}
+
+}
+}
+}
diff --git a/src/lib/datasrc/tests/test_client.h b/src/lib/datasrc/tests/test_client.h
new file mode 100644
index 0000000..2c692d3
--- /dev/null
+++ b/src/lib/datasrc/tests/test_client.h
@@ -0,0 +1,71 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __TEST_DATA_SOURCE_CLIENT_H
+#define __TEST_DATA_SOURCE_CLIENT_H 1
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <istream>
+
+namespace isc {
+namespace datasrc {
+namespace unittest {
+
+// Here we define utility modules for the convenience of tests that create
+// a data source client according to the specified conditions.
+
+/// \brief Create an SQLite3 data source client from a zone file.
+///
+/// This function creates an SQLite3 client for the specified zone containing
+/// RRs in the specified zone file. The zone will be created in the given
+/// SQLite3 database file. The database file does not have to exist; this
+/// function will automatically create a new file for the test; if the given
+/// file already exists this function overrides the content (so basically the
+/// file must be an ephemeral one only for that test case).
+///
+/// The zone file must be formatted so it's accepted by the dns::masterLoad()
+/// function.
+///
+/// \param zclass The RR class of the zone
+/// \param zname The origin name of the zone
+/// \param db_file The SQLite3 data base file in which the zone data should be
+/// installed.
+/// \param zone_file The filename of the zone data in the textual format.
+/// \return Newly created \c DataSourceClient using the SQLite3 data source
+boost::shared_ptr<DataSourceClient>
+createSQLite3Client(dns::RRClass zclass, const dns::Name& zname,
+ const char* const db_file, const char* const zone_file);
+
+/// \brief Create an SQLite3 data source client from a stream.
+///
+/// This is similar to the other version of the function, but takes an input
+/// stream for the zone data. The stream produces strings as the corresponding
+/// dns::masterLoad() function expects.
+boost::shared_ptr<DataSourceClient>
+createSQLite3Client(dns::RRClass zclass, const dns::Name& zname,
+ const char* const db_file, std::istream& rr_stream);
+
+} // end of unittest
+} // end of datasrc
+} // end of isc
+
+#endif // __TEST_DATA_SOURCE_CLIENT_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/tests/testdata/.gitignore b/src/lib/datasrc/tests/testdata/.gitignore
new file mode 100644
index 0000000..58ea8cd
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/.gitignore
@@ -0,0 +1 @@
+/*.sqlite3.copied
diff --git a/src/lib/datasrc/tests/testdata/contexttest.zone b/src/lib/datasrc/tests/testdata/contexttest.zone
new file mode 100644
index 0000000..e499e0d
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/contexttest.zone
@@ -0,0 +1,81 @@
+;; test zone file used for ZoneFinderContext tests.
+;; RRSIGs are (obviouslly) faked ones for testing.
+
+example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 67 3600 300 3600000 3600
+example.org. 3600 IN NS ns1.example.org.
+example.org. 3600 IN NS ns2.example.org.
+example.org. 3600 IN MX 1 mx1.example.org.
+example.org. 3600 IN MX 2 mx2.example.org.
+example.org. 3600 IN MX 3 mx.a.example.org.
+
+ns1.example.org. 3600 IN A 192.0.2.1
+ns1.example.org. 3600 IN RRSIG A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
+ns1.example.org. 3600 IN AAAA 2001:db8::1
+ns1.example.org. 3600 IN RRSIG AAAA 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKEFAKE
+ns2.example.org. 3600 IN A 192.0.2.2
+ns2.example.org. 3600 IN TXT "text data"
+
+mx1.example.org. 3600 IN A 192.0.2.10
+mx2.example.org. 3600 IN AAAA 2001:db8::10
+
+;; delegation
+a.example.org. 3600 IN NS ns1.a.example.org.
+a.example.org. 3600 IN NS ns2.a.example.org.
+a.example.org. 3600 IN NS ns.example.com.
+
+ns1.a.example.org. 3600 IN A 192.0.2.5
+ns2.a.example.org. 3600 IN A 192.0.2.6
+ns2.a.example.org. 3600 IN AAAA 2001:db8::6
+mx.a.example.org. 3600 IN A 192.0.2.7
+
+;; delegation, one of its NS names is at zone cut.
+b.example.org. 3600 IN NS ns.b.example.org.
+b.example.org. 3600 IN NS b.example.org.
+b.example.org. 3600 IN AAAA 2001:db8::8
+
+ns.b.example.org. 3600 IN A 192.0.2.9
+
+;; The MX name is at a zone cut. shouldn't be included in the
+;; additional section.
+mxatcut.example.org. 3600 IN MX 1 b.example.org.
+
+;; delegation, one of its NS names is under a DNAME delegation point;
+;; another is at that point; and yet another is under DNAME below a
+;; zone cut.
+c.example.org. 3600 IN NS ns.dname.example.org.
+c.example.org. 3600 IN NS dname.example.org.
+c.example.org. 3600 IN NS ns.deepdname.example.org.
+ns.dname.example.org. 3600 IN A 192.0.2.11
+dname.example.org. 3600 IN A 192.0.2.12
+ns.deepdname.example.org. 3600 IN AAAA 2001:db8::9
+
+;; delegation, one of its NS name is at an empty non terminal.
+d.example.org. 3600 IN NS ns.empty.example.org.
+d.example.org. 3600 IN NS ns1.example.org.
+;; by adding these two we can create an empty RB node for
+;; ns.empty.example.org in the in-memory zone
+foo.ns.empty.example.org. 3600 IN A 192.0.2.13
+bar.ns.empty.example.org. 3600 IN A 192.0.2.14
+
+;; delegation; the NS name matches a wildcard (and there's no exact
+;; match). One of the NS names matches an empty wildcard node, for
+;; which no additional record should be provided (or any other
+;; disruption should happen).
+e.example.org. 3600 IN NS ns.wild.example.org.
+e.example.org. 3600 IN NS ns.emptywild.example.org.
+e.example.org. 3600 IN NS ns2.example.org.
+*.wild.example.org. 3600 IN A 192.0.2.15
+a.*.emptywild.example.org. 3600 IN AAAA 2001:db8::2
+
+;; additional for an answer RRset (MX) as a result of wildcard
+;; expansion
+*.wildmx.example.org. 3600 IN MX 1 mx1.example.org.
+
+;; CNAME
+alias.example.org. 3600 IN CNAME cname.example.org.
+
+;; DNAME
+dname.example.org. 3600 IN DNAME dname.example.com.
+
+;; DNAME under a NS (strange one)
+deepdname.c.example.org. 3600 IN DNAME deepdname.example.com.
diff --git a/src/lib/datasrc/tests/testdata/diffs.sqlite3 b/src/lib/datasrc/tests/testdata/diffs.sqlite3
index 3820563..4cf8fb7 100644
Binary files a/src/lib/datasrc/tests/testdata/diffs.sqlite3 and b/src/lib/datasrc/tests/testdata/diffs.sqlite3 differ
diff --git a/src/lib/datasrc/tests/testdata/example.org.nsec3-signed b/src/lib/datasrc/tests/testdata/example.org.nsec3-signed
new file mode 100644
index 0000000..9c1195f
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/example.org.nsec3-signed
@@ -0,0 +1,14 @@
+example.org. 86400 IN SOA ns.example.org. ns.example.org. 2012013000 7200 3600 2592000 1200
+example.org. 86400 IN RRSIG SOA 7 2 86400 20120301040838 20120131040838 19562 example.org. Jt9wCRLS5TQxZH0IBqrM9uMGD453rIoxYopfM9AjjRZfEx+HGlBpOZeR pGN7yLcN+URnicOD0ydLHiakaBODiZyNoYCKYG5d2ZOhL+026REnDKNM 0m5T3X3sczP+l55An/GITheTdrKt3Y1Ouc2yKI8ro8JjOxV/a4nGDWjK x9A=
+example.org. 86400 IN NS ns.example.org.
+example.org. 86400 IN RRSIG NS 7 2 86400 20120301040838 20120131040838 19562 example.org. gYXL3xK4IFdJU6TtiVuzqDBb2MeA8xB3AKtHlJGFTfTRNHyuej0ZGovx TeUYsLYmoiGYaJG66iD1tYYFq0qdj0xWq+LEa53ACtKvYf9IIwK4ijJs k0g6xCNavc6/qPppymDhN7MvoFVkW59uJa0HPWlsIIuRlEAr7xyt85vq yoA=
+example.org. 86400 IN DNSKEY 256 3 7 AwEAAbrBkKf2gmGtG4aogZY4svIZCrOLLZlQzVHwz7WxJdTR8iEnvz/x Q/jReDroS5CBZWvzwLlhPIpsJAojx0oj0RvfJNsz3+6LN8q7x9u6+86B 85CYjTk3dcFOebgkF4fXr7/kkOX+ZY94Zk0Z1+pUC3eY4gkKcyME/Uxm O18PBTeB
+example.org. 86400 IN RRSIG DNSKEY 7 2 86400 20120301040838 20120131040838 19562 example.org. d0eLF8JqNHaGuBSX0ashU5c1O/wyWU43UUsKGrMQIoBDiJ588MWQOnas rwvW6vdkLNqRqCsP/B4epV/EtLL0tBsk5SHkTYbNo80gGrBufQ6YrWRr Ile8Z+h+MR4y9DybbjmuNKqaO4uQMg/X6+4HqRAKx1lmZMTcrcVeOwDM ZA4=
+example.org. 0 IN NSEC3PARAM 1 0 10 AABBCCDD
+example.org. 0 IN RRSIG NSEC3PARAM 7 2 0 20120301040838 20120131040838 19562 example.org. Ggs5MiQDlXXt22Fz9DNg3Ujc0T6MBfumlRkd8/enBbJwLmqw2QXAzDEk pjUeGstCEHKzxJDJstavGoCpTDJgoV4Fd9szooMx69rzPrq9tdoM46jG xZHqw+Pv2fkRGC6aP7ZX1r3Qnpwpk47AQgATftbO4G6KcMcO8JoKE47x XLM=
+ns.example.org. 86400 IN A 192.0.2.1
+ns.example.org. 86400 IN RRSIG A 7 3 86400 20120301040838 20120131040838 19562 example.org. dOH+Dxib8VcGnjLrKILsqDhS1wki6BWk1dZwpOGUGHyLWcLNW8ygWY2o r29jPhHtaFCNWpn46JebgnXDPRiQjaY3dQqL8rcf2QX1e3+Cicw1OSrs S0sUDE5DmMNEac+ZCUQ0quCStZTCldl05hlspV2RS92TpApnoOK0nXMp Uak=
+09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG
+09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKO yfZc8wKRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVv cD3dFksPyiKHf/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3 CTM=
+RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN NSEC3 1 0 10 AABBCCDD 09GM5T42SMIMT7R8DF6RTG80SFMS1NLU NS SOA RRSIG DNSKEY NSEC3PARAM
+RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN RRSIG NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. j7d8GL4YqX035FBcPPsEcSWHjWcKdlQMHLL4TB67xVNFnl4SEFQCp4OO AtPap5tkKakwgWxoQVN9XjnqrBz+oQhofDkB3aTatAjIIkcwcnrm3AYQ rTI3E03ySiRwuCPKVmHOLUV2cG6O4xzcmP+MYZcvPTS8V3F5LlaU22i7 A3E=
diff --git a/src/lib/datasrc/tests/testdata/example.org.nsec3-signed-noparam b/src/lib/datasrc/tests/testdata/example.org.nsec3-signed-noparam
new file mode 100644
index 0000000..5caa308
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/example.org.nsec3-signed-noparam
@@ -0,0 +1,15 @@
+;; This file intentionally removes NSEC3PARAM from example.org.nsec3-signed
+example.org. 86400 IN SOA ns.example.org. ns.example.org. 2012013000 7200 3600 2592000 1200
+example.org. 86400 IN RRSIG SOA 7 2 86400 20120301040838 20120131040838 19562 example.org. Jt9wCRLS5TQxZH0IBqrM9uMGD453rIoxYopfM9AjjRZfEx+HGlBpOZeR pGN7yLcN+URnicOD0ydLHiakaBODiZyNoYCKYG5d2ZOhL+026REnDKNM 0m5T3X3sczP+l55An/GITheTdrKt3Y1Ouc2yKI8ro8JjOxV/a4nGDWjK x9A=
+example.org. 86400 IN NS ns.example.org.
+example.org. 86400 IN RRSIG NS 7 2 86400 20120301040838 20120131040838 19562 example.org. gYXL3xK4IFdJU6TtiVuzqDBb2MeA8xB3AKtHlJGFTfTRNHyuej0ZGovx TeUYsLYmoiGYaJG66iD1tYYFq0qdj0xWq+LEa53ACtKvYf9IIwK4ijJs k0g6xCNavc6/qPppymDhN7MvoFVkW59uJa0HPWlsIIuRlEAr7xyt85vq yoA=
+example.org. 86400 IN DNSKEY 256 3 7 AwEAAbrBkKf2gmGtG4aogZY4svIZCrOLLZlQzVHwz7WxJdTR8iEnvz/x Q/jReDroS5CBZWvzwLlhPIpsJAojx0oj0RvfJNsz3+6LN8q7x9u6+86B 85CYjTk3dcFOebgkF4fXr7/kkOX+ZY94Zk0Z1+pUC3eY4gkKcyME/Uxm O18PBTeB
+example.org. 86400 IN RRSIG DNSKEY 7 2 86400 20120301040838 20120131040838 19562 example.org. d0eLF8JqNHaGuBSX0ashU5c1O/wyWU43UUsKGrMQIoBDiJ588MWQOnas rwvW6vdkLNqRqCsP/B4epV/EtLL0tBsk5SHkTYbNo80gGrBufQ6YrWRr Ile8Z+h+MR4y9DybbjmuNKqaO4uQMg/X6+4HqRAKx1lmZMTcrcVeOwDM ZA4=
+;; example.org. 0 IN NSEC3PARAM 1 0 10 AABBCCDD
+;; example.org. 0 IN RRSIG NSEC3PARAM 7 2 0 20120301040838 20120131040838 19562 example.org. Ggs5MiQDlXXt22Fz9DNg3Ujc0T6MBfumlRkd8/enBbJwLmqw2QXAzDEk pjUeGstCEHKzxJDJstavGoCpTDJgoV4Fd9szooMx69rzPrq9tdoM46jG xZHqw+Pv2fkRGC6aP7ZX1r3Qnpwpk47AQgATftbO4G6KcMcO8JoKE47x XLM=
+ns.example.org. 86400 IN A 192.0.2.1
+ns.example.org. 86400 IN RRSIG A 7 3 86400 20120301040838 20120131040838 19562 example.org. dOH+Dxib8VcGnjLrKILsqDhS1wki6BWk1dZwpOGUGHyLWcLNW8ygWY2o r29jPhHtaFCNWpn46JebgnXDPRiQjaY3dQqL8rcf2QX1e3+Cicw1OSrs S0sUDE5DmMNEac+ZCUQ0quCStZTCldl05hlspV2RS92TpApnoOK0nXMp Uak=
+09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG
+09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKO yfZc8wKRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVv cD3dFksPyiKHf/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3 CTM=
+RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN NSEC3 1 0 10 AABBCCDD 09GM5T42SMIMT7R8DF6RTG80SFMS1NLU NS SOA RRSIG DNSKEY NSEC3PARAM
+RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN RRSIG NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. j7d8GL4YqX035FBcPPsEcSWHjWcKdlQMHLL4TB67xVNFnl4SEFQCp4OO AtPap5tkKakwgWxoQVN9XjnqrBz+oQhofDkB3aTatAjIIkcwcnrm3AYQ rTI3E03ySiRwuCPKVmHOLUV2cG6O4xzcmP+MYZcvPTS8V3F5LlaU22i7 A3E=
diff --git a/src/lib/datasrc/tests/testdata/example.org.sqlite3 b/src/lib/datasrc/tests/testdata/example.org.sqlite3
index 60e6e05..c7388ff 100644
Binary files a/src/lib/datasrc/tests/testdata/example.org.sqlite3 and b/src/lib/datasrc/tests/testdata/example.org.sqlite3 differ
diff --git a/src/lib/datasrc/tests/testdata/example2.com.sqlite3 b/src/lib/datasrc/tests/testdata/example2.com.sqlite3
index 9da7d0e..a0a576c 100644
Binary files a/src/lib/datasrc/tests/testdata/example2.com.sqlite3 and b/src/lib/datasrc/tests/testdata/example2.com.sqlite3 differ
diff --git a/src/lib/datasrc/tests/testdata/new_minor_schema.sqlite3 b/src/lib/datasrc/tests/testdata/new_minor_schema.sqlite3
new file mode 100644
index 0000000..1542c20
Binary files /dev/null and b/src/lib/datasrc/tests/testdata/new_minor_schema.sqlite3 differ
diff --git a/src/lib/datasrc/tests/testdata/newschema.sqlite3 b/src/lib/datasrc/tests/testdata/newschema.sqlite3
new file mode 100644
index 0000000..460cfa8
Binary files /dev/null and b/src/lib/datasrc/tests/testdata/newschema.sqlite3 differ
diff --git a/src/lib/datasrc/tests/testdata/oldschema.sqlite3 b/src/lib/datasrc/tests/testdata/oldschema.sqlite3
new file mode 100644
index 0000000..b44c5eb
Binary files /dev/null and b/src/lib/datasrc/tests/testdata/oldschema.sqlite3 differ
diff --git a/src/lib/datasrc/tests/testdata/rrset_toWire1 b/src/lib/datasrc/tests/testdata/rrset_toWire1
new file mode 100644
index 0000000..8f81a0e
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/rrset_toWire1
@@ -0,0 +1,23 @@
+#
+# Rendering an IN/A RRset containing 2 RRs:
+# test.example.com. 3600 IN A 192.0.2.1
+# test.example.com. 3600 IN A 192.0.2.2
+#
+#(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
+# type/class: A = 1, IN = 1
+00 01 00 01
+# TTL: 3600
+00 00 0e 10
+#6 7
+# RDLENGTH: 4
+00 04
+# RDATA: 192.0.2.1
+c0 00 02 01
+#
+# 2nd RR: mostly the same except the RDATA
+04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+00 01 00 01
+00 00 0e 10
+00 04
+c0 00 02 02
diff --git a/src/lib/datasrc/tests/testdata/rrset_toWire2 b/src/lib/datasrc/tests/testdata/rrset_toWire2
new file mode 100644
index 0000000..b9a6a15
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/rrset_toWire2
@@ -0,0 +1,26 @@
+#
+# Rendering an IN/A RRset and NS RRset as follows:
+# test.example.com. 3600 IN A 192.0.2.1
+# test.example.com. 3600 IN A 192.0.2.2
+# example.com. 1D IN NS ns.example.com.
+# Names will be compressed when possible.
+#
+# 0 1 2 3 4 5
+#(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
+# type/class: A = 1, IN = 1
+00 01 00 01
+# TTL: 3600
+00 00 0e 10
+#6 7
+# RDLENGTH: 4
+00 04
+# RDATA: 192.0.2.1
+c0 00 02 01
+#
+# 2nd RR: the owner name is compresed
+c0 00
+00 01 00 01
+00 00 0e 10
+00 04
+c0 00 02 02
diff --git a/src/lib/datasrc/tests/testdata/rwtest.sqlite3 b/src/lib/datasrc/tests/testdata/rwtest.sqlite3
deleted file mode 100644
index ccbb884..0000000
Binary files a/src/lib/datasrc/tests/testdata/rwtest.sqlite3 and /dev/null differ
diff --git a/src/lib/datasrc/tests/testdata/test-root.sqlite3 b/src/lib/datasrc/tests/testdata/test-root.sqlite3
index c1dae47..1bef761 100644
Binary files a/src/lib/datasrc/tests/testdata/test-root.sqlite3 and b/src/lib/datasrc/tests/testdata/test-root.sqlite3 differ
diff --git a/src/lib/datasrc/tests/testdata/test.sqlite3 b/src/lib/datasrc/tests/testdata/test.sqlite3
index 521cf31..9c71cb5 100644
Binary files a/src/lib/datasrc/tests/testdata/test.sqlite3 and b/src/lib/datasrc/tests/testdata/test.sqlite3 differ
diff --git a/src/lib/datasrc/tests/testdata/test.sqlite3.nodiffs b/src/lib/datasrc/tests/testdata/test.sqlite3.nodiffs
deleted file mode 100644
index cc8cfc3..0000000
Binary files a/src/lib/datasrc/tests/testdata/test.sqlite3.nodiffs and /dev/null differ
diff --git a/src/lib/datasrc/tests/zone_finder_context_unittest.cc b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
new file mode 100644
index 0000000..50d409e
--- /dev/null
+++ b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
@@ -0,0 +1,413 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+
+#include <dns/masterload.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <datasrc/zone.h>
+#include <datasrc/memory_datasrc.h>
+#include <datasrc/database.h>
+#include <datasrc/sqlite3_accessor.h>
+
+#include "test_client.h"
+#include <testutils/dnsmessage_test.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <fstream>
+#include <sstream>
+#include <vector>
+
+using namespace std;
+using boost::shared_ptr;
+
+using namespace isc::dns;
+using namespace isc::datasrc;
+using namespace isc::testutils;
+
+namespace {
+
+// Commonly used test zone file.
+const char* const TEST_ZONE_FILE = TEST_DATA_DIR "/contexttest.zone";
+
+// Convenient shortcut
+typedef shared_ptr<DataSourceClient> DataSourceClientPtr;
+
+// This is the type used as the test parameter. Note that this is
+// intentionally a plain old type (i.e. a function pointer), not a class;
+// otherwise it could cause initialization fiasco at the instantiation time.
+typedef DataSourceClientPtr (*ClientCreator)(RRClass, const Name&);
+
+// Creator for the in-memory client to be tested
+DataSourceClientPtr
+createInMemoryClient(RRClass zclass, const Name& zname) {
+ shared_ptr<InMemoryClient> client(new InMemoryClient);
+
+ shared_ptr<InMemoryZoneFinder> finder(
+ new InMemoryZoneFinder(zclass, zname));
+ finder->load(TEST_ZONE_FILE);
+
+ client->addZone(finder);
+
+ return (client);
+}
+
+void
+addRRset(ZoneUpdaterPtr updater, ConstRRsetPtr rrset) {
+ updater->addRRset(*rrset);
+}
+
+DataSourceClientPtr
+createSQLite3Client(RRClass zclass, const Name& zname) {
+ // We always begin with an empty template SQLite3 DB file and install
+ // the zone data from the zone file to ensure both cases have the
+ // same test data.
+ DataSourceClientPtr client = unittest::createSQLite3Client(
+ zclass, zname, TEST_DATA_BUILDDIR "/contexttest.sqlite3.copied",
+ TEST_ZONE_FILE);
+
+ // Insert an out-of-zone name to test if it's incorrectly returned.
+ // Note that neither updater nor SQLite3 accessor checks this condition,
+ // so this should succeed.
+ ZoneUpdaterPtr updater = client->getUpdater(zname, false);
+ stringstream ss("ns.example.com. 3600 IN A 192.0.2.7");
+ masterLoad(ss, Name::ROOT_NAME(), zclass,
+ boost::bind(addRRset, updater, _1));
+ updater->commit();
+
+ return (client);
+}
+
+// The test class. Its parameterized so we can share the test scnearios
+// for any concrete data source implementaitons.
+class ZoneFinderContextTest :
+ public ::testing::TestWithParam<ClientCreator>
+{
+protected:
+ ZoneFinderContextTest() : qclass_(RRClass::IN()), qzone_("example.org") {
+ client_ = (*GetParam())(qclass_, qzone_);
+ REQUESTED_A.push_back(RRType::A());
+ REQUESTED_AAAA.push_back(RRType::AAAA());
+ REQUESTED_BOTH.push_back(RRType::A());
+ REQUESTED_BOTH.push_back(RRType::AAAA());
+ }
+ void SetUp() {
+ finder_ = client_->findZone(qzone_).zone_finder;
+ ASSERT_TRUE(finder_);
+ }
+
+ const RRClass qclass_;
+ const Name qzone_;
+ DataSourceClientPtr client_;
+ ZoneFinderPtr finder_;
+
+ vector<RRType> requested_types_;
+ vector<RRType> REQUESTED_A;
+ vector<RRType> REQUESTED_AAAA;
+ vector<RRType> REQUESTED_BOTH;
+ vector<ConstRRsetPtr> result_sets_;
+};
+
+// We test the in-memory and SQLite3 data source implementations.
+INSTANTIATE_TEST_CASE_P(, ZoneFinderContextTest,
+ ::testing::Values(createInMemoryClient,
+ createSQLite3Client));
+
+TEST_P(ZoneFinderContextTest, getAdditionalAuthNS) {
+ ZoneFinderContextPtr ctx = finder_->find(qzone_, RRType::NS());
+ EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+
+ // Getting both A and AAAA NS addresses
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ rrsetsCheck("ns1.example.org. 3600 IN A 192.0.2.1\n"
+ "ns1.example.org. 3600 IN AAAA 2001:db8::1\n"
+ "ns2.example.org. 3600 IN A 192.0.2.2\n",
+ result_sets_.begin(), result_sets_.end());
+
+ // Getting only A
+ result_sets_.clear();
+ ctx->getAdditional(REQUESTED_A, result_sets_);
+ rrsetsCheck("ns1.example.org. 3600 IN A 192.0.2.1\n"
+ "ns2.example.org. 3600 IN A 192.0.2.2\n",
+ result_sets_.begin(), result_sets_.end());
+
+ // Getting only AAAA
+ result_sets_.clear();
+ ctx->getAdditional(REQUESTED_AAAA, result_sets_);
+ rrsetsCheck("ns1.example.org. 3600 IN AAAA 2001:db8::1\n",
+ result_sets_.begin(), result_sets_.end());
+
+ // Getting A again, without clearing the result sets. This confirms
+ // getAdditional() doesn't change the existing vector content.
+ ctx->getAdditional(REQUESTED_A, result_sets_);
+ // The first element should be the existing AAAA RR, followed by the A's.
+ EXPECT_EQ(RRType::AAAA(), result_sets_[0]->getType());
+ rrsetsCheck("ns1.example.org. 3600 IN AAAA 2001:db8::1\n"
+ "ns1.example.org. 3600 IN A 192.0.2.1\n"
+ "ns2.example.org. 3600 IN A 192.0.2.2\n",
+ result_sets_.begin(), result_sets_.end());
+
+ // Normally expected type set contain only A and/or AAAA, but others aren't
+ // excluded.
+ result_sets_.clear();
+ requested_types_.push_back(RRType::TXT());
+ ctx->getAdditional(requested_types_, result_sets_);
+ rrsetsCheck("ns2.example.org. 3600 IN TXT \"text data\"",
+ result_sets_.begin(), result_sets_.end());
+
+ // Even empty set is okay. The result should also be empty.
+ result_sets_.clear();
+ ctx->getAdditional(vector<RRType>(), result_sets_);
+ EXPECT_TRUE(result_sets_.empty());
+}
+
+TEST_P(ZoneFinderContextTest, getAdditionalDelegation) {
+ // Basically similar to the AuthNS case, but NS names are glues.
+ // It contains an out-of-zone NS name. Its address (even if it's somehow
+ // inserted to the zone data) shouldn't be returned.
+ const Name qname("www.a.example.org");
+ ZoneFinderContextPtr ctx = finder_->find(qname, RRType::AAAA());
+ EXPECT_EQ(ZoneFinder::DELEGATION, ctx->code);
+
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ rrsetsCheck("ns1.a.example.org. 3600 IN A 192.0.2.5\n"
+ "ns2.a.example.org. 3600 IN A 192.0.2.6\n"
+ "ns2.a.example.org. 3600 IN AAAA 2001:db8::6\n",
+ result_sets_.begin(), result_sets_.end());
+
+ result_sets_.clear();
+ ctx->getAdditional(REQUESTED_A, result_sets_);
+ rrsetsCheck("ns1.a.example.org. 3600 IN A 192.0.2.5\n"
+ "ns2.a.example.org. 3600 IN A 192.0.2.6\n",
+ result_sets_.begin(), result_sets_.end());
+
+ result_sets_.clear();
+ ctx->getAdditional(REQUESTED_AAAA, result_sets_);
+ rrsetsCheck("ns2.a.example.org. 3600 IN AAAA 2001:db8::6\n",
+ result_sets_.begin(), result_sets_.end());
+}
+
+TEST_P(ZoneFinderContextTest, getAdditionalDelegationAtZoneCut) {
+ // Similar to the previous case, but one of the NS addresses is at the
+ // zone cut.
+
+ // XXX: the current database-based data source incorrectly rejects this
+ // setup (see #1771)
+ if (GetParam() == createSQLite3Client) {
+ return;
+ }
+
+ ZoneFinderContextPtr ctx = finder_->find(Name("www.b.example.org"),
+ RRType::SOA());
+ EXPECT_EQ(ZoneFinder::DELEGATION, ctx->code);
+
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ rrsetsCheck("b.example.org. 3600 IN AAAA 2001:db8::8\n"
+ "ns.b.example.org. 3600 IN A 192.0.2.9\n",
+ result_sets_.begin(), result_sets_.end());
+}
+
+TEST_P(ZoneFinderContextTest, getAdditionalDelegationWithDname) {
+ // Delegation: One of the NS names under a DNAME delegation; another
+ // is at the delegation point; yet another is under DNAME below a zone cut.
+ // The first should be hidden.
+ ZoneFinderContextPtr ctx = finder_->find(Name("www.c.example.org"),
+ RRType::TXT());
+ EXPECT_EQ(ZoneFinder::DELEGATION, ctx->code);
+
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ rrsetsCheck("dname.example.org. 3600 IN A 192.0.2.12\n"
+ "ns.deepdname.example.org. 3600 IN AAAA 2001:db8::9\n",
+ result_sets_.begin(), result_sets_.end());
+}
+
+TEST_P(ZoneFinderContextTest, getAdditionalDelegationWithEmptyName) {
+ // One of NS names is at an empty non terminal node. It shouldn't cause
+ // any disruption.
+ ZoneFinderContextPtr ctx = finder_->find(Name("www.d.example.org"),
+ RRType::A());
+ EXPECT_EQ(ZoneFinder::DELEGATION, ctx->code);
+
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ rrsetsCheck("ns1.example.org. 3600 IN A 192.0.2.1\n"
+ "ns1.example.org. 3600 IN AAAA 2001:db8::1\n",
+ result_sets_.begin(), result_sets_.end());
+}
+
+TEST_P(ZoneFinderContextTest, getAdditionalDelegationWithWild) {
+ // An NS name needs to be expanded by a wildcard. Another NS name
+ // also matches a wildcard, but it's an empty node, so there's no
+ // corresponding additional RR. The other NS name isn't subject to
+ // wildcard expansion, which shouldn't cause any disruption.
+ ZoneFinderContextPtr ctx = finder_->find(Name("www.e.example.org"),
+ RRType::AAAA());
+ EXPECT_EQ(ZoneFinder::DELEGATION, ctx->code);
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ rrsetsCheck("ns.wild.example.org. 3600 IN A 192.0.2.15\n"
+ "ns2.example.org. 3600 IN A 192.0.2.2\n",
+ result_sets_.begin(), result_sets_.end());
+
+ // ns.wild.example.org/A (expanded from a wildcard) should be considered
+ // the same kind, whether it's a direct result of find() or a result of
+ // getAdditional().
+ ctx = finder_->find(Name("ns.wild.example.org"), RRType::A());
+ EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+ for (vector<ConstRRsetPtr>::const_iterator it = result_sets_.begin();
+ it != result_sets_.end(); ++it) {
+ const bool same_kind = (*it)->isSameKind(*ctx->rrset);
+ EXPECT_EQ((*it)->getName() == ctx->rrset->getName(), same_kind);
+ }
+}
+
+TEST_P(ZoneFinderContextTest, getAdditionalDelegationForWild) {
+ // additional for an answer RRset (MX) as a result of wildcard expansion.
+ // note the difference from the previous test. in this case wildcard
+ // applies to the owner name of the answer, not the owner name of the
+ // additional.
+ ZoneFinderContextPtr ctx = finder_->find(Name("mx.wildmx.example.org"),
+ RRType::MX());
+ EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ rrsetsCheck("mx1.example.org. 3600 IN A 192.0.2.10\n",
+ result_sets_.begin(), result_sets_.end());
+}
+
+TEST_P(ZoneFinderContextTest, getAdditionalMX) {
+ // Similar to the previous cases, but for MX addresses. The test zone
+ // contains MX name under a zone cut. Its address shouldn't be returned.
+ ZoneFinderContextPtr ctx = finder_->find(qzone_, RRType::MX());
+ EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+
+ // Getting both A and AAAA NS addresses
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ rrsetsCheck("mx1.example.org. 3600 IN A 192.0.2.10\n"
+ "mx2.example.org. 3600 IN AAAA 2001:db8::10\n",
+ result_sets_.begin(), result_sets_.end());
+
+ // Getting only A
+ result_sets_.clear();
+ ctx->getAdditional(REQUESTED_A, result_sets_);
+ rrsetsCheck("mx1.example.org. 3600 IN A 192.0.2.10\n",
+ result_sets_.begin(), result_sets_.end());
+
+ // Getting only AAAA
+ result_sets_.clear();
+ ctx->getAdditional(REQUESTED_AAAA, result_sets_);
+ rrsetsCheck("mx2.example.org. 3600 IN AAAA 2001:db8::10\n",
+ result_sets_.begin(), result_sets_.end());
+}
+
+TEST_P(ZoneFinderContextTest, getAdditionalMXAtZoneCut) {
+ // XXX: the current database-based data source incorrectly rejects this
+ // setup (see #1771)
+ if (GetParam() == createSQLite3Client) {
+ return;
+ }
+
+ ZoneFinderContextPtr ctx = finder_->find(Name("mxatcut.example.org."),
+ RRType::MX());
+ EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ EXPECT_TRUE(result_sets_.empty());
+}
+
+TEST_P(ZoneFinderContextTest, getAdditionalWithSIG) {
+ // Similar to the AuthNS test, but the original find() requested DNSSEC
+ // RRSIGs. Then additional records will also have RRSIGs.
+ ZoneFinderContextPtr ctx = finder_->find(qzone_, RRType::NS(),
+ ZoneFinder::FIND_DNSSEC);
+ EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ rrsetsCheck("ns1.example.org. 3600 IN A 192.0.2.1\n"
+ "ns1.example.org. 3600 IN AAAA 2001:db8::1\n"
+ "ns2.example.org. 3600 IN A 192.0.2.2\n",
+ result_sets_.begin(), result_sets_.end());
+
+ vector<ConstRRsetPtr> sigresult_sets;
+ BOOST_FOREACH(ConstRRsetPtr rrset, result_sets_) {
+ ConstRRsetPtr sig_rrset = rrset->getRRsig();
+ if (sig_rrset) {
+ sigresult_sets.push_back(sig_rrset);
+ }
+ }
+ rrsetsCheck("ns1.example.org. 3600 IN RRSIG A 7 3 3600 20150420235959 "
+ "20051021000000 40430 example.org. FAKEFAKE\n"
+ "ns1.example.org. 3600 IN RRSIG AAAA 7 3 3600 20150420235959 "
+ "20051021000000 40430 example.org. FAKEFAKEFAKE\n",
+ sigresult_sets.begin(), sigresult_sets.end());
+}
+
+TEST_P(ZoneFinderContextTest, getAdditionalNoOP) {
+ // getAdditional() is only meaningful after SUCCESS or DELEGATION.
+
+ ZoneFinderContextPtr ctx = finder_->find(Name("nxdomain.example.org"),
+ RRType::NS());
+ EXPECT_EQ(ZoneFinder::NXDOMAIN, ctx->code);
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ EXPECT_TRUE(result_sets_.empty());
+
+ ctx = finder_->find(qzone_, RRType::TXT());
+ EXPECT_EQ(ZoneFinder::NXRRSET, ctx->code);
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ EXPECT_TRUE(result_sets_.empty());
+
+ ctx = finder_->find(Name("alias.example.org."), RRType::A());
+ EXPECT_EQ(ZoneFinder::CNAME, ctx->code);
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ EXPECT_TRUE(result_sets_.empty());
+
+ ctx = finder_->find(Name("www.dname.example.org."), RRType::A());
+ EXPECT_EQ(ZoneFinder::DNAME, ctx->code);
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ EXPECT_TRUE(result_sets_.empty());
+}
+
+TEST_P(ZoneFinderContextTest, getAdditionalForAny) {
+ // getAdditional() after successful type ANY query should return
+ // the additional records of all returned RRsets.
+ vector<ConstRRsetPtr> all_rrsets;
+ ZoneFinderContextPtr ctx = finder_->findAll(qzone_, all_rrsets);
+ EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ rrsetsCheck("ns1.example.org. 3600 IN A 192.0.2.1\n"
+ "ns1.example.org. 3600 IN AAAA 2001:db8::1\n"
+ "ns2.example.org. 3600 IN A 192.0.2.2\n"
+ "mx1.example.org. 3600 IN A 192.0.2.10\n"
+ "mx2.example.org. 3600 IN AAAA 2001:db8::10\n",
+ result_sets_.begin(), result_sets_.end());
+
+ // If the type ANY query results in DELEGATION, the result should be the
+ // same as normal query.
+ all_rrsets.clear();
+ result_sets_.clear();
+ ctx = finder_->findAll(Name("www.a.example.org"), all_rrsets);
+ EXPECT_EQ(ZoneFinder::DELEGATION, ctx->code);
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ rrsetsCheck("ns1.a.example.org. 3600 IN A 192.0.2.5\n"
+ "ns2.a.example.org. 3600 IN A 192.0.2.6\n"
+ "ns2.a.example.org. 3600 IN AAAA 2001:db8::6\n",
+ result_sets_.begin(), result_sets_.end());
+}
+
+}
diff --git a/src/lib/datasrc/zone.h b/src/lib/datasrc/zone.h
index b922662..c68a01c 100644
--- a/src/lib/datasrc/zone.h
+++ b/src/lib/datasrc/zone.h
@@ -15,14 +15,28 @@
#ifndef __ZONE_H
#define __ZONE_H 1
+#include <dns/name.h>
#include <dns/rrset.h>
-#include <dns/rrsetlist.h>
+#include <dns/rrtype.h>
#include <datasrc/result.h>
+#include <utility>
+#include <vector>
+
namespace isc {
namespace datasrc {
+/// \brief Out of zone exception
+///
+/// This is thrown when a method is called for a name or RRset which
+/// is not in or below the zone.
+class OutOfZone : public Exception {
+public:
+ OutOfZone(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
/// \brief The base class to search a zone for RRsets
///
/// The \c ZoneFinder class is an abstract base class for representing
@@ -55,118 +69,36 @@ public:
/// Note: the codes are tentative. We may need more, or we may find
/// some of them unnecessary as we implement more details.
///
- /// Some are synonyms of others in terms of RCODE returned to user.
- /// But they help the logic to decide if it should ask for a NSEC
- /// that covers something or not (for example, in case of NXRRSET,
- /// the directly returned NSEC is sufficient, but with wildcard one,
- /// we need to add one proving there's no exact match and this is
- /// actually the best wildcard we have). Data sources that don't
- /// support DNSSEC don't need to distinguish them.
- ///
- /// In case of CNAME, if the CNAME is a wildcard (i.e., its owner name
- /// starts with the label "*"), WILDCARD_CNAME will be returned instead
- /// of CNAME.
- ///
- /// In case of NXDOMAIN, the returned NSEC covers the queried domain
- /// that proves that the query name does not exist in the zone. Note that
- /// this does not necessarily prove it doesn't even match a wildcard
- /// (even if the result of NXDOMAIN can only happen when there's no
- /// matching wildcard either). It is caller's responsibility to provide
- /// a proof that there is no matching wildcard if that proof is necessary.
- ///
- /// Various variants of "no data" cases are complicated, when involves
- /// DNSSEC and wildcard processing. Referring to Section 3.1.3 of
- /// RFC4035, we need to consider the following cases:
- /// -# (Normal) no data: there is a matching non-wildcard name with a
- /// different RR type. This is the "No Data" case of the RFC.
- /// -# (Normal) empty non terminal: there is no matching (exact or
- /// wildcard) name, but there is a subdomain with an RR of the query
- /// name. This is one case of "Name Error" of the RFC.
- /// -# Wildcard empty non terminal: similar to 2a, but the empty name
- /// is a wildcard, and matches the query name by wildcard expansion.
- /// This is a special case of "Name Error" of the RFC.
- /// -# Wildcard no data: there is no exact match name, but there is a
- /// wildcard name that matches the query name with a different type
- /// of RR. This is the "Wildcard No Data" case of the RFC.
- ///
- /// In any case, \c find() will result in \c NXRRSET with no RRset
- /// unless the \c FIND_DNSSEC option is specified. The rest of the
- /// discussion only applies to the case where this option is specified.
- ///
- /// In case 1, \c find() will result in NXRRSET, and return NSEC of the
- /// matching name.
- ///
- /// In case 2, \c find() will result in NXRRSET, and return NSEC for the
- /// interval where the empty nonterminal lives. The end of the interval
- /// is the subdomain causing existence of the empty nonterminal (if
- /// there's sub.x.example.com, and no record in x.example.com, then
- /// x.example.com exists implicitly - is the empty nonterminal and
- /// sub.x.example.com is the subdomain causing it). Note that this NSEC
- /// proves not only the existence of empty non terminal name but also
- /// the non existence of possibly matching wildcard name, because
- /// there can be no better wildcard match than the exact matching empty
- /// name.
- ///
- /// In case 3, \c find() will result in WILDCARD_NXRRSET, and return NSEC
- /// for the interval where the wildcard empty nonterminal lives.
- /// Cases 2 and 3 are especially complicated and confusing. See the
- /// examples below.
- ///
- /// In case 4, \c find() will result in WILDCARD_NXRRSET, and return
- /// NSEC of the matching wildcard name.
- ///
- /// Examples: if zone "example.com" has the following record:
- /// \code
- /// a.example.com. NSEC a.b.example.com.
- /// \endcode
- /// a call to \c find() for "b.example.com." with the FIND_DNSSEC option
- /// will result in NXRRSET, and this NSEC will be returned.
- /// Likewise, if zone "example.org" has the following record,
- /// \code
- /// a.example.org. NSEC x.*.b.example.org.
- /// \endcode
- /// a call to \c find() for "y.b.example.org" with FIND_DNSSEC will
- /// result in NXRRSET_NXRRSET, and this NSEC will be returned.
+ /// See the description of \c find() for further details of how
+ /// these results should be interpreted.
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
- WILDCARD, ///< Succes by wildcard match, for DNSSEC
- WILDCARD_CNAME, ///< CNAME on wildcard, search returns CNAME, for DNSSEC
- WILDCARD_NXRRSET ///< NXRRSET on wildcard, for DNSSEC
+ 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;
+ /// Special attribute flags on the result of the \c find() method
+ ///
+ /// The flag values defined here are intended to signal to the caller
+ /// that it may need special handling on the result. This is particularly
+ /// of concern when DNSSEC is requested. For example, for negative
+ /// responses the caller would want to know whether the zone is signed
+ /// with NSEC or NSEC3 so that it can subsequently provide necessary
+ /// proof of the result.
+ ///
+ /// The caller is generally expected to get access to the information
+ /// via read-only getter methods of \c FindContext so that it won't rely
+ /// on specific details of the representation of the flags. So these
+ /// definitions are basically only meaningful for data source
+ /// implementations.
+ enum FindResultFlags {
+ RESULT_DEFAULT = 0, ///< The default flags
+ RESULT_WILDCARD = 1, ///< find() resulted in a wildcard match
+ RESULT_NSEC_SIGNED = 2, ///< The zone is signed with NSEC RRs
+ RESULT_NSEC3_SIGNED = 4 ///< The zone is signed with NSEC3 RRs
};
/// Find options.
@@ -184,6 +116,193 @@ public:
NO_WILDCARD = 4 ///< Do not try wildcard matching.
};
+protected:
+ /// \brief A convenient tuple representing a set of find() results.
+ ///
+ /// This helper structure is specifically expected to be used as an input
+ /// for the construct of the \c Context class object used by derived
+ /// ZoneFinder implementations. This is therefore defined as protected.
+ struct ResultContext {
+ ResultContext(Result code_param,
+ isc::dns::ConstRRsetPtr rrset_param,
+ FindResultFlags flags_param = RESULT_DEFAULT) :
+ code(code_param), rrset(rrset_param), flags(flags_param)
+ {}
+ const Result code;
+ const isc::dns::ConstRRsetPtr rrset;
+ const FindResultFlags flags;
+ };
+
+public:
+ /// \brief Context of the result of a find() call.
+ ///
+ /// This class encapsulates results and (possibly) associated context
+ /// of a call to the \c find() method. The public member variables of
+ /// this class reprsent the result of the call. They are a
+ /// straightforward tuple of the result code and a pointer (and
+ /// optionally special flags) to the found RRset.
+ ///
+ /// These member variables will be initialized on construction and never
+ /// change, so for convenience we allow the applications to refer to some
+ /// of the members directly. For some others we provide read-only accessor
+ /// methods to hide specific representation.
+ ///
+ /// Another role of this class is to provide the interface to some common
+ /// processing logic that may be necessary using the result of \c find().
+ /// Specifically, it's expected to be used in the context of DNS query
+ /// handling, where the caller would need to look into the data source
+ /// again based on the \c find() result. For example, it would need to
+ /// get A and/or AAAA records for some of the answer or authority RRs.
+ ///
+ /// This class defines (a set of) method(s) that can be commonly used
+ /// for such purposes for any type of data source (as long as it conforms
+ /// to the public \c find() interface). In some cases, a specific data
+ /// source implementation may want to (and can) optimize the processing
+ /// exploiting its internal data structure and the knowledge of the context
+ /// of the precedent \c find() call. Such a data source implementation
+ /// can define a derived class of the base Context and override the
+ /// specific virtual method.
+ ///
+ /// This class object is generally expected to be associated with the
+ /// ZoneFinder that originally performed the \c find() call, and expects
+ /// the finder is valid throughout the lifetime of this object. It's
+ /// caller's responsibility to ensure that assumption.
+ class Context {
+ public:
+ /// \brief The constructor for the normal find call.
+ ///
+ /// This constructor is expected to be called from the \c find()
+ /// method when it constructs the return value.
+ ///
+ /// \param finder The ZoneFinder on which find() is called.
+ /// \param options The find options specified for the find() call.
+ /// \param result The result of the find() call.
+ Context(ZoneFinder& finder, FindOptions options,
+ const ResultContext& result) :
+ code(result.code), rrset(result.rrset),
+ finder_(finder), flags_(result.flags), options_(options)
+ {}
+
+ /// \brief The constructor for the normal findAll call.
+ ///
+ /// This constructor is expected to be called from the \c findAll()
+ /// method when it constructs the return value.
+ ///
+ /// It copies the vector that is to be returned to the caller of
+ /// \c findAll() for possible subsequent use. Note that it cannot
+ /// simply hold a reference to the vector because the caller may
+ /// alter it after the \c findAll() call.
+ ///
+ /// \param finder The ZoneFinder on which findAll() is called.
+ /// \param options The find options specified for the findAll() call.
+ /// \param result The result of the findAll() call (whose rrset is
+ /// expected to be NULL).
+ /// \param all_set Reference to the vector given by the caller of
+ /// \c findAll(), storing the RRsets to be returned.
+ Context(ZoneFinder& finder, FindOptions options,
+ const ResultContext& result,
+ const std::vector<isc::dns::ConstRRsetPtr> &all_set) :
+ code(result.code), rrset(result.rrset),
+ finder_(finder), flags_(result.flags), options_(options),
+ all_set_(all_set)
+ {}
+
+ /// \brief The destructor.
+ virtual ~Context() {}
+
+ const Result code;
+ const isc::dns::ConstRRsetPtr rrset;
+
+ /// Return true iff find() results in a wildcard match.
+ bool isWildcard() const { return ((flags_ & RESULT_WILDCARD) != 0); }
+
+ /// Return true when the underlying zone is signed with NSEC.
+ ///
+ /// The \c find() implementation allows this to return false if
+ /// \c FIND_DNSSEC isn't specified regardless of whether the zone
+ /// is signed or which of NSEC/NSEC3 is used.
+ ///
+ /// When this is returned, the implementation of find() must ensure
+ /// that \c rrset be a valid NSEC RRset as described in \c find()
+ /// documentation.
+ bool isNSECSigned() const {
+ return ((flags_ & RESULT_NSEC_SIGNED) != 0);
+ }
+
+ /// Return true when the underlying zone is signed with NSEC3.
+ ///
+ /// The \c find() implementation allows this to return false if
+ /// \c FIND_DNSSEC isn't specified regardless of whether the zone
+ /// is signed or which of NSEC/NSEC3 is used.
+ bool isNSEC3Signed() const {
+ return ((flags_ & RESULT_NSEC3_SIGNED) != 0);
+ }
+
+ /// \brief Find and return additional RRsets corresponding to the
+ /// result of \c find().
+ ///
+ /// If this context is based on a normal find() call that resulted
+ /// in SUCCESS or DELEGATION, it examines the returned RRset (in many
+ /// cases NS, sometimes MX or others), searches the data source for
+ /// specified type of additional RRs for each RDATA of the RRset
+ /// (e.g., A or AAAA for the name server addresses), and stores the
+ /// result in the given vector. The vector may not be empty; this
+ /// method appends any found RRsets to it, without touching existing
+ /// elements.
+ ///
+ /// If this context is based on a findAll() call that resulted in
+ /// SUCCESS, it performs the same process for each RRset returned in
+ /// the \c findAll() call.
+ ///
+ /// The caller specifies desired RR types of the additional RRsets
+ /// in \c requested_types. Normally it consists of A and/or AAAA
+ /// types, but other types can be specified.
+ ///
+ /// This method is meaningful only when the precedent find()/findAll()
+ /// call resulted in SUCCESS or DELEGATION. Otherwise this method
+ /// does nothing.
+ ///
+ /// \note The additional RRsets returned via method are limited to
+ /// ones contained in the zone which the corresponding find/findAll
+ /// call searched (possibly including glues under a zone cut where
+ /// they are applicable). If the caller needs to get out-of-zone
+ /// additional RRsets, it needs to explicitly finds them by
+ /// identifying the corresponding zone and calls \c find() for it.
+ ///
+ /// \param requested_types A vector of RR types for desired additional
+ /// RRsets.
+ /// \param result A vector to which any found additional RRsets are
+ /// to be inserted.
+ void getAdditional(
+ const std::vector<isc::dns::RRType>& requested_types,
+ std::vector<isc::dns::ConstRRsetPtr>& result)
+ {
+ // Perform common checks, and delegate the process to the default
+ // or specialized implementation.
+ if (code != SUCCESS && code != DELEGATION) {
+ return;
+ }
+
+ getAdditionalImpl(requested_types, result);
+ }
+
+ protected:
+ /// \brief Actual implementation of getAdditional().
+ ///
+ /// This base class defines a default implementation that can be
+ /// used for any type of data sources. A data source implementation
+ /// can override it.
+ virtual void getAdditionalImpl(
+ const std::vector<isc::dns::RRType>& requested_types,
+ std::vector<isc::dns::ConstRRsetPtr>& result);
+
+ private:
+ ZoneFinder& finder_;
+ const FindResultFlags flags_;
+ const FindOptions options_;
+ std::vector<isc::dns::ConstRRsetPtr> all_set_;
+ };
+
///
/// \name Constructors and Destructor.
///
@@ -224,12 +343,10 @@ public:
///
/// - 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 no matching name, it returns the code of \c NXDOMAIN.
/// - 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.
+ /// returns the code of \c NXRRSET. This case includes the search name
+ /// matches an empty node of the zone.
/// - 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.
@@ -238,6 +355,16 @@ public:
/// - If the search name matches a delegation point of DNAME, it returns
/// the code of \c DNAME and that DNAME RR.
///
+ /// No RRset will be returned in the \c NXDOMAIN and \c NXRRSET cases
+ /// (\c rrset member of \c FindContext will be NULL), unless DNSSEC data
+ /// are required. See below for the cases with DNSSEC.
+ ///
+ /// The returned \c FindContext object can also provide supplemental
+ /// information about the search result via its methods returning a
+ /// boolean value. Such information may be useful for the caller if
+ /// the caller wants to collect additional DNSSEC proofs based on the
+ /// search result.
+ ///
/// The \c options parameter specifies customized behavior of the search.
/// Their semantics is as follows (they are or bit-field):
///
@@ -267,8 +394,90 @@ public:
/// future version. In any case applications shouldn't call this method
/// for an out-of-zone name.
///
+ /// <b>DNSSEC considerations:</b>
+ /// The result when DNSSEC data are required can be very complicated,
+ /// especially if it involves negative result or wildcard match.
+ /// Specifically, if an application calls this method for DNS query
+ /// processing with DNSSEC data, and if the search result code is
+ /// either \c NXDOMAIN or \c NXRRRSET, and/or \c isWildcard() returns
+ /// true, then the application will need to find additional NSEC or
+ /// NSEC3 records for supplemental proofs. This method helps the
+ /// application for such post search processing.
+ ///
+ /// First, it tells the application whether the zone is signed with
+ /// NSEC or NSEC3 via the \c isNSEC(3)Signed() method. Any sanely signed
+ /// zone should be signed with either (and only one) of these two types
+ /// of RRs; however, the application should expect that the zone could
+ /// be broken and these methods could both return false. But this method
+ /// should ensure that not both of these methods return true.
+ ///
+ /// In case it's signed with NSEC3, there is no further information
+ /// returned from this method.
+ ///
+ /// In case it's signed with NSEC, this method will possibly return
+ /// a related NSEC RRset in the \c rrset member of \c FindContext.
+ /// What kind of NSEC is returned depends on the result code
+ /// (\c NXDOMAIN or \c NXRRSET) and on whether it's a wildcard match:
+ ///
+ /// - In case of NXDOMAIN, the returned NSEC covers the queried domain
+ /// that proves that the query name does not exist in the zone. Note
+ /// that this does not necessarily prove it doesn't even match a
+ /// wildcard (even if the result of NXDOMAIN can only happen when
+ /// there's no matching wildcard either). It is caller's
+ /// responsibility to provide a proof that there is no matching
+ /// wildcard if that proof is necessary.
+ /// - In case of NXRRSET, we need to consider the following cases
+ /// referring to Section 3.1.3 of RFC4035:
+ ///
+ /// -# (Normal) no data: there is a matching non-wildcard name with a
+ /// different RR type. This is the "No Data" case of the RFC.
+ /// -# (Normal) empty non terminal: there is no matching (exact or
+ /// wildcard) name, but there is a subdomain with an RR of the query
+ /// name. This is one case of "Name Error" of the RFC.
+ /// -# Wildcard empty non terminal: similar to 2a, but the empty name
+ /// is a wildcard, and matches the query name by wildcard expansion.
+ /// This is a special case of "Name Error" of the RFC.
+ /// -# Wildcard no data: there is no exact match name, but there is a
+ /// wildcard name that matches the query name with a different type
+ /// of RR. This is the "Wildcard No Data" case of the RFC.
+ ///
+ /// In case 1, \c find() returns NSEC of the matching name.
+ ///
+ /// In case 2, \c find() will return NSEC for the interval where the
+ /// empty nonterminal lives. The end of the interval is the subdomain
+ /// causing existence of the empty nonterminal (if there's
+ /// sub.x.example.com, and no record in x.example.com, then
+ /// x.example.com exists implicitly - is the empty nonterminal and
+ /// sub.x.example.com is the subdomain causing it). Note that this NSEC
+ /// proves not only the existence of empty non terminal name but also
+ /// the non existence of possibly matching wildcard name, because
+ /// there can be no better wildcard match than the exact matching empty
+ /// name.
+ ///
+ /// In case 3, \c find() will return NSEC for the interval where the
+ /// wildcard empty nonterminal lives. Cases 2 and 3 are especially
+ /// complicated and confusing. See the examples below.
+ ///
+ /// In case 4, \c find() will return NSEC of the matching wildcard name.
+ ///
+ /// Examples: if zone "example.com" has the following record:
+ /// \code
+ /// a.example.com. NSEC a.b.example.com.
+ /// \endcode
+ /// a call to \c find() for "b.example.com." with the FIND_DNSSEC option
+ /// will result in NXRRSET, and this NSEC will be returned.
+ /// Likewise, if zone "example.org" has the following record,
+ /// \code
+ /// a.example.org. NSEC x.*.b.example.org.
+ /// \endcode
+ /// a call to \c find() for "y.b.example.org" with FIND_DNSSEC will
+ /// result in NXRRSET and this NSEC; \c isWildcard() on the returned
+ /// \c FindContext object will return true.
+ ///
/// \exception std::bad_alloc Memory allocation such as for constructing
/// the resulting RRset fails
+ /// \throw OutOfZone The Name \c name is outside of the origin of the
+ /// zone of this ZoneFinder.
/// \exception DataSourceError Derived class specific exception, e.g.
/// when encountering a bad zone configuration or database connection
/// failure. Although these are considered rare, exceptional events,
@@ -280,31 +489,134 @@ public:
/// \param name The domain name to be searched for.
/// \param type The RR type to be searched for.
/// \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,
- const FindOptions options
- = FIND_DEFAULT) = 0;
+ /// \return A \c FindContext object enclosing the search result
+ /// (see above).
+ virtual boost::shared_ptr<Context> find(const isc::dns::Name& name,
+ const isc::dns::RRType& type,
+ const FindOptions options
+ = FIND_DEFAULT) = 0;
///
/// \brief Finds all RRsets in the given name.
///
/// This function works almost exactly in the same way as the find one. The
/// only difference is, when the lookup is successful (eg. the code is
- /// SUCCESS or WILDCARD), all the RRsets residing in the named node are
+ /// SUCCESS), all the RRsets residing in the named node are
/// copied into the \c target parameter and the rrset member of the result
/// is NULL. All the other (unsuccessful) cases are handled the same,
- /// including returning delegations, NSEC/NSEC3 proofs, etc. The options
- /// parameter works the same way and it should conform to the same exception
- /// restrictions.
+ /// including returning delegations, NSEC/NSEC3 availability and NSEC
+ /// proofs, wildcard information etc. The options parameter works the
+ /// same way and it should conform to the same exception restrictions.
///
/// \param name \see find, parameter name
/// \param target the successfull result is returned through this
/// \param options \see find, parameter options
/// \return \see find and it's result
- virtual FindResult findAll(const isc::dns::Name& name,
- std::vector<isc::dns::ConstRRsetPtr> &target,
- const FindOptions options = FIND_DEFAULT) = 0;
+ virtual boost::shared_ptr<Context> findAll(
+ const isc::dns::Name& name,
+ std::vector<isc::dns::ConstRRsetPtr> &target,
+ const FindOptions options = FIND_DEFAULT) = 0;
+
+ /// A helper structure to represent the search result of \c findNSEC3().
+ ///
+ /// The idea is similar to that of \c FindContext, but \c findNSEC3() has
+ /// special interface and semantics, we use a different structure to
+ /// represent the result.
+ struct FindNSEC3Result {
+ FindNSEC3Result(bool param_matched, uint8_t param_closest_labels,
+ isc::dns::ConstRRsetPtr param_closest_proof,
+ isc::dns::ConstRRsetPtr param_next_proof) :
+ matched(param_matched), closest_labels(param_closest_labels),
+ closest_proof(param_closest_proof),
+ next_proof(param_next_proof)
+ {}
+
+ /// true iff closest_proof is a matching NSEC3
+ const bool matched;
+
+ /// The number of labels of the identified closest encloser.
+ const uint8_t closest_labels;
+
+ /// Either the NSEC3 for the closest provable encloser of the given
+ /// name or NSEC3 that covers the name
+ const isc::dns::ConstRRsetPtr closest_proof;
+
+ /// When non NULL, NSEC3 for the next closer name.
+ const isc::dns::ConstRRsetPtr next_proof;
+ };
+
+ /// Search the zone for the NSEC3 RR(s) that prove existence or non
+ /// existence of a give name.
+ ///
+ /// It searches the NSEC3 namespace of the zone (how that namespace is
+ /// implemented can vary in specific data source implementation) for NSEC3
+ /// RRs that match or cover the NSEC3 hash value for the given name.
+ ///
+ /// If \c recursive is false, it will first look for the NSEC3 that has
+ /// a matching hash. If it doesn't exist, it identifies the covering NSEC3
+ /// for the hash. In either case the search stops at that point and the
+ /// found NSEC3 RR(set) will be returned in the closest_proof member of
+ /// \c FindNSEC3Result. \c matched is true or false depending on
+ /// the found NSEC3 is a matched one or covering one. \c next_proof
+ /// is always NULL. closest_labels must be equal to the number of
+ /// labels of \c name (and therefore meaningless).
+ ///
+ /// If \c recursive is true, it will continue the search toward the zone
+ /// apex (origin name) until it finds a provable encloser, that is,
+ /// an ancestor of \c name that has a matching NSEC3. This is the closest
+ /// provable encloser of \c name as defined in RFC5155. In this case,
+ /// if the found encloser is not equal to \c name, the search should
+ /// have seen a covering NSEC3 for the immediate child of the found
+ /// encloser. That child name is the next closer name as defined in
+ /// RFC5155. In this case, this method returns the NSEC3 for the
+ /// closest encloser in \c closest_proof, and the NSEC3 for the next
+ /// closer name in \c next_proof of \c FindNSEC3Result. This set of
+ /// NSEC3 RRs provide the closest encloser proof as defined in RFC5155.
+ /// closest_labels will be set to the number of labels of the identified
+ /// closest encloser. This will be useful when the caller needs to
+ /// construct the closest encloser name from the original \c name.
+ /// If, on the other hand, the found closest name is equal to \c name,
+ /// this method simply returns it in \c closest_proof. \c next_proof
+ /// is set to NULL. In all cases \c matched is set to true.
+ /// closest_labels will be set to the number of labels of \c name.
+ ///
+ /// When looking for NSEC3, this method retrieves NSEC3 parameters from
+ /// the corresponding zone to calculate hash values. Actual implementation
+ /// of how to do this will differ in different data sources. If the
+ /// NSEC3 parameters are not available \c DataSourceError exception
+ /// will be thrown.
+ ///
+ /// \note This implicitly means this method assumes the zone does not
+ /// have more than one set of parameters. This assumption should be
+ /// reasonable in actual deployment and will help simplify the interface
+ /// and implementation. But if there's a real need for supporting
+ /// multiple sets of parameters in a single zone, we will have to
+ /// extend this method so that, e.g., the caller can specify the parameter
+ /// set.
+ ///
+ /// In general, this method expects the zone is properly signed with NSEC3
+ /// RRs. Specifically, it assumes at least the apex node has a matching
+ /// NSEC3 RR (so the search in the recursive mode must always succeed);
+ /// it also assumes that it can retrieve NSEC parameters (iterations,
+ /// algorithm, and salt) from the zone as noted above. If these
+ /// assumptions aren't met, \c DataSourceError exception will be thrown.
+ ///
+ /// \exception OutOfZone name is not a subdomain of the zone origin
+ /// \exception DataSourceError Low-level or internal datasource errors
+ /// happened, or the zone isn't properly signed with NSEC3
+ /// (NSEC3 parameters cannot be found, no NSEC3s are available, etc).
+ /// \exception std::bad_alloc The underlying implementation involves
+ /// memory allocation and it fails
+ ///
+ /// \param name The name for which NSEC3 RRs are to be found. It must
+ /// be a subdomain of the zone.
+ /// \param recursive Whether or not search should continue until it finds
+ /// a provable encloser (see above).
+ ///
+ /// \return The search result and whether or not the closest_proof is
+ /// a matching NSEC3, in the form of \c FindNSEC3Result object.
+ virtual FindNSEC3Result
+ findNSEC3(const isc::dns::Name& name, bool recursive) = 0;
/// \brief Get previous name in the zone
///
@@ -346,12 +658,31 @@ inline ZoneFinder::FindOptions operator |(ZoneFinder::FindOptions a,
static_cast<unsigned>(b)));
}
+/// \brief Operator to combine FindResultFlags
+///
+/// Similar to the same operator for \c FindOptions. Refer to the description
+/// of that function.
+inline ZoneFinder::FindResultFlags operator |(
+ ZoneFinder::FindResultFlags a,
+ ZoneFinder::FindResultFlags b)
+{
+ return (static_cast<ZoneFinder::FindResultFlags>(
+ static_cast<unsigned>(a) | static_cast<unsigned>(b)));
+}
+
/// \brief A pointer-like type pointing to a \c ZoneFinder object.
typedef boost::shared_ptr<ZoneFinder> ZoneFinderPtr;
-/// \brief A pointer-like type pointing to a \c ZoneFinder object.
+/// \brief A pointer-like type pointing to an immutable \c ZoneFinder object.
typedef boost::shared_ptr<const ZoneFinder> ConstZoneFinderPtr;
+/// \brief A pointer-like type pointing to a \c ZoneFinder::Context object.
+typedef boost::shared_ptr<ZoneFinder::Context> ZoneFinderContextPtr;
+
+/// \brief A pointer-like type pointing to an immutable
+/// \c ZoneFinder::Context object.
+typedef boost::shared_ptr<ZoneFinder::Context> ConstZoneFinderContextPtr;
+
/// The base class to make updates to a single zone.
///
/// On construction, each derived class object will start a "transaction"
@@ -486,7 +817,7 @@ public:
/// \exception std::bad_alloc Resource allocation failure
///
/// \param rrset The RRset to be added
- virtual void addRRset(const isc::dns::RRset& rrset) = 0;
+ virtual void addRRset(const isc::dns::AbstractRRset& rrset) = 0;
/// Delete an RRset from a zone via the updater
///
@@ -558,7 +889,7 @@ public:
/// \exception std::bad_alloc Resource allocation failure
///
/// \param rrset The RRset to be deleted
- virtual void deleteRRset(const isc::dns::RRset& rrset) = 0;
+ virtual void deleteRRset(const isc::dns::AbstractRRset& rrset) = 0;
/// Commit the updates made in the updater to the zone
///
diff --git a/src/lib/datasrc/zone_finder_context.cc b/src/lib/datasrc/zone_finder_context.cc
new file mode 100644
index 0000000..7913d71
--- /dev/null
+++ b/src/lib/datasrc/zone_finder_context.cc
@@ -0,0 +1,102 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/rdata.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+#include <dns/rdataclass.h>
+
+#include <datasrc/zone.h>
+
+#include <boost/foreach.hpp>
+
+#include <vector>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace isc {
+namespace datasrc {
+
+namespace {
+void
+getAdditionalAddrs(ZoneFinder& finder, const Name& name,
+ const vector<RRType>& requested_types,
+ vector<ConstRRsetPtr>& result_rrsets,
+ ZoneFinder::FindOptions options)
+{
+ // Ignore out-of-zone names
+ const NameComparisonResult cmp = finder.getOrigin().compare(name);
+ if ((cmp.getRelation() != NameComparisonResult::SUPERDOMAIN) &&
+ (cmp.getRelation() != NameComparisonResult::EQUAL)) {
+ return;
+ }
+
+ BOOST_FOREACH(RRType rrtype, requested_types) {
+ ConstZoneFinderContextPtr ctx = finder.find(name, rrtype, options);
+ if (ctx->code == ZoneFinder::SUCCESS) {
+ result_rrsets.push_back(ctx->rrset);
+ }
+ }
+}
+
+void
+getAdditionalForRRset(ZoneFinder& finder, const AbstractRRset& rrset,
+ const vector<RRType>& requested_types,
+ vector<ConstRRsetPtr>& result,
+ ZoneFinder::FindOptions orig_options)
+{
+ RdataIteratorPtr rdata_iterator(rrset.getRdataIterator());
+ ZoneFinder::FindOptions options = ZoneFinder::FIND_DEFAULT;
+ if ((orig_options & ZoneFinder::FIND_DNSSEC) != 0) {
+ options = options | ZoneFinder::FIND_DNSSEC;
+ }
+
+ 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);
+ getAdditionalAddrs(finder, ns.getNSName(), requested_types,
+ result, options | ZoneFinder::FIND_GLUE_OK);
+ } else if (rrset.getType() == RRType::MX()) {
+ const generic::MX& mx = dynamic_cast<const generic::MX&>(rdata);
+ getAdditionalAddrs(finder, mx.getMXName(), requested_types,
+ result, options);
+ }
+ }
+}
+}
+
+void
+ZoneFinder::Context::getAdditionalImpl(const vector<RRType>& requested_types,
+ vector<ConstRRsetPtr>& result)
+{
+ // If rrset is non NULL, it should have been SUCCESS/DELEGATION; otherwise
+ // we should have responded to type ANY query.
+ if (rrset) {
+ getAdditionalForRRset(finder_, *rrset, requested_types, result,
+ options_);
+ return;
+ }
+ BOOST_FOREACH(ConstRRsetPtr rrset_in_set, all_set_) {
+ getAdditionalForRRset(finder_, *rrset_in_set, requested_types, result,
+ options_);
+ }
+}
+
+} // namespace datasrc
+} // datasrc isc
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
index 210e149..c394da5 100644
--- a/src/lib/dhcp/iface_mgr.cc
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -131,7 +131,7 @@ IfaceMgr::IfaceMgr()
// interface detection is implemented. Otherwise
// it is not possible to run tests in a portable
// way (see detectIfaces() method).
- throw ex;
+ throw;
}
}
@@ -198,7 +198,7 @@ void IfaceMgr::stubDetectIfaces() {
// TODO Do LOG_FATAL here
std::cerr << "Interface detection failed." << std::endl;
- throw ex;
+ throw;
}
}
diff --git a/src/lib/dhcp/iface_mgr_linux.cc b/src/lib/dhcp/iface_mgr_linux.cc
index 8da89c0..a04708b 100644
--- a/src/lib/dhcp/iface_mgr_linux.cc
+++ b/src/lib/dhcp/iface_mgr_linux.cc
@@ -22,7 +22,6 @@
#include <stdint.h>
#include <net/if.h>
-#include <linux/if_link.h>
#include <linux/rtnetlink.h>
using namespace std;
diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc
index 78fd372..03b4a3d 100644
--- a/src/lib/dhcp/option.cc
+++ b/src/lib/dhcp/option.cc
@@ -274,5 +274,5 @@ Option::~Option() {
}
-} // end of namespace isc::dhcp
-} // end of namespace isc
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option4_addrlst.cc b/src/lib/dhcp/option4_addrlst.cc
index 7157e1d..4b0224f 100644
--- a/src/lib/dhcp/option4_addrlst.cc
+++ b/src/lib/dhcp/option4_addrlst.cc
@@ -23,10 +23,12 @@
#include <dhcp/option4_addrlst.h>
using namespace std;
-using namespace isc::dhcp;
using namespace isc::util;
using namespace isc::asiolink;
+namespace isc {
+namespace dhcp {
+
Option4AddrLst::Option4AddrLst(uint8_t type)
:Option(V4, type) {
}
@@ -132,3 +134,6 @@ std::string Option4AddrLst::toText(int indent /* =0 */ ) {
return tmp.str();
}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option6_ia.cc b/src/lib/dhcp/option6_ia.cc
index 7fd6d06..65be711 100644
--- a/src/lib/dhcp/option6_ia.cc
+++ b/src/lib/dhcp/option6_ia.cc
@@ -107,5 +107,5 @@ uint16_t Option6IA::len() {
return (length);
}
-} // end of namespace isc::dhcp
-} // end of namespace isc
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
index eda08f4..7e1c691 100644
--- a/src/lib/dhcp/pkt6.cc
+++ b/src/lib/dhcp/pkt6.cc
@@ -13,10 +13,10 @@
// PERFORMANCE OF THIS SOFTWARE.
-#include "dhcp/dhcp6.h"
-#include "dhcp/pkt6.h"
-#include "dhcp/libdhcp++.h"
-#include "exceptions/exceptions.h"
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/libdhcp++.h>
+#include <exceptions/exceptions.h>
#include <iostream>
#include <sstream>
@@ -33,8 +33,8 @@ Pkt6::Pkt6(const uint8_t* buf, uint32_t buf_len, DHCPv6Proto proto /* = UDP */)
ifindex_(-1),
local_addr_("::"),
remote_addr_("::"),
- local_port_(-1),
- remote_port_(-1),
+ local_port_(0),
+ remote_port_(0),
bufferOut_(0) {
data_.resize(buf_len);
memcpy(&data_[0], buf, buf_len);
@@ -48,8 +48,8 @@ Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/) :
ifindex_(-1),
local_addr_("::"),
remote_addr_("::"),
- local_port_(-1),
- remote_port_(-1),
+ local_port_(0),
+ remote_port_(0),
bufferOut_(0) {
}
@@ -173,7 +173,7 @@ Pkt6::toText() {
}
boost::shared_ptr<isc::dhcp::Option>
-Pkt6::getOption(unsigned short opt_type) {
+Pkt6::getOption(uint16_t opt_type) {
isc::dhcp::Option::OptionCollection::const_iterator x = options_.find(opt_type);
if (x!=options_.end()) {
return (*x).second;
@@ -187,7 +187,7 @@ Pkt6::addOption(boost::shared_ptr<Option> opt) {
}
bool
-Pkt6::delOption(unsigned short type) {
+Pkt6::delOption(uint16_t type) {
isc::dhcp::Option::OptionCollection::iterator x = options_.find(type);
if (x!=options_.end()) {
options_.erase(x);
@@ -202,5 +202,5 @@ void Pkt6::repack() {
bufferOut_.writeData(&data_[0], data_.size());
}
-} // end of namespace isc::dhcp
-} // end of namespace isc
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index e1b4aff..97ac996 100644
--- a/src/lib/dhcp/pkt6.h
+++ b/src/lib/dhcp/pkt6.h
@@ -272,10 +272,10 @@ protected:
DHCPv6Proto proto_;
/// DHCPv6 message type
- int msg_type_;
+ uint8_t msg_type_;
/// DHCPv6 transaction-id
- unsigned int transid_;
+ uint32_t transid_;
/// unparsed data (in received packets)
OptionBuffer data_;
@@ -298,14 +298,13 @@ protected:
isc::asiolink::IOAddress remote_addr_;
/// local TDP or UDP port
- int local_port_;
+ uint16_t local_port_;
/// remote TCP or UDP port
- int remote_port_;
+ uint16_t remote_port_;
/// output buffer (used during message transmission)
isc::util::OutputBuffer bufferOut_;
-
}; // Pkt6 class
typedef boost::shared_ptr<Pkt6> Pkt6Ptr;
diff --git a/src/lib/dhcp/tests/.gitignore b/src/lib/dhcp/tests/.gitignore
new file mode 100644
index 0000000..313429d
--- /dev/null
+++ b/src/lib/dhcp/tests/.gitignore
@@ -0,0 +1 @@
+/libdhcp++_unittests
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index d9382e0..98fc3bf 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -773,7 +773,8 @@ void parse_ifconfig(const std::string& textFile, IfaceMgr::IfaceCollection& ifac
// Things that are not tested:
// - ifindex (ifconfig does not print it out)
// - address scopes and lifetimes (we don't need it, so it is not implemented in IfaceMgr)
-TEST_F(IfaceMgrTest, detectIfaces_linux) {
+// TODO: temporarily disabled, see ticket #1529
+TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
IfaceMgr::IfaceCollection& detectedIfaces = ifacemgr->getIfacesLst();
diff --git a/src/lib/dns/.gitignore b/src/lib/dns/.gitignore
new file mode 100644
index 0000000..cdf707c
--- /dev/null
+++ b/src/lib/dns/.gitignore
@@ -0,0 +1,6 @@
+/gen-rdatacode.py
+/rdataclass.cc
+/rdataclass.h
+/rrclass.h
+/rrparamregistry.cc
+/rrtype.h
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index 5b93f75..2cd889d 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -23,6 +23,8 @@ EXTRA_DIST += rdata/generic/cname_5.cc
EXTRA_DIST += rdata/generic/cname_5.h
EXTRA_DIST += rdata/generic/detail/nsec_bitmap.cc
EXTRA_DIST += rdata/generic/detail/nsec_bitmap.h
+EXTRA_DIST += rdata/generic/detail/nsec3param_common.cc
+EXTRA_DIST += rdata/generic/detail/nsec3param_common.h
EXTRA_DIST += rdata/generic/detail/txt_like.h
EXTRA_DIST += rdata/generic/detail/ds_like.h
EXTRA_DIST += rdata/generic/dlv_32769.cc
@@ -59,6 +61,8 @@ EXTRA_DIST += rdata/generic/soa_6.cc
EXTRA_DIST += rdata/generic/soa_6.h
EXTRA_DIST += rdata/generic/spf_99.cc
EXTRA_DIST += rdata/generic/spf_99.h
+EXTRA_DIST += rdata/generic/sshfp_44.cc
+EXTRA_DIST += rdata/generic/sshfp_44.h
EXTRA_DIST += rdata/generic/txt_16.cc
EXTRA_DIST += rdata/generic/txt_16.h
EXTRA_DIST += rdata/generic/minfo_14.cc
@@ -84,15 +88,18 @@ BUILT_SOURCES += rdataclass.h rdataclass.cc
lib_LTLIBRARIES = libdns++.la
-libdns___la_LDFLAGS = -no-undefined -version-info 1:0:1
+libdns___la_LDFLAGS = -no-undefined -version-info 2:0:0
libdns___la_SOURCES =
libdns___la_SOURCES += edns.h edns.cc
libdns___la_SOURCES += exceptions.h exceptions.cc
+libdns___la_SOURCES += labelsequence.h labelsequence.cc
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
+libdns___la_SOURCES += name_internal.h
+libdns___la_SOURCES += nsec3hash.h nsec3hash.cc
libdns___la_SOURCES += opcode.h opcode.cc
libdns___la_SOURCES += rcode.h rcode.cc
libdns___la_SOURCES += rdata.h rdata.cc
@@ -112,6 +119,8 @@ libdns___la_SOURCES += tsigrecord.h tsigrecord.cc
libdns___la_SOURCES += character_string.h character_string.cc
libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc
+libdns___la_SOURCES += rdata/generic/detail/nsec3param_common.cc
+libdns___la_SOURCES += rdata/generic/detail/nsec3param_common.h
libdns___la_SOURCES += rdata/generic/detail/txt_like.h
libdns___la_SOURCES += rdata/generic/detail/ds_like.h
@@ -131,10 +140,11 @@ rrparamregistry.cc: rrparamregistry-placeholder.cc
rrclass.h rrtype.h rrparamregistry.cc rdataclass.h rdataclass.cc: Makefile
./gen-rdatacode.py
-libdns___includedir = $(includedir)/dns
+libdns___includedir = $(includedir)/$(PACKAGE_NAME)/dns
libdns___include_HEADERS = \
edns.h \
exceptions.h \
+ labelsequence.h \
message.h \
messagerenderer.h \
name.h \
@@ -147,7 +157,7 @@ libdns___include_HEADERS = \
rrttl.h \
tsigkey.h
# Purposely not installing these headers:
-# util/*.h: used only internally, and not actually DNS specific
+# name_internal.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/benchmarks/.gitignore b/src/lib/dns/benchmarks/.gitignore
new file mode 100644
index 0000000..c1c840b
--- /dev/null
+++ b/src/lib/dns/benchmarks/.gitignore
@@ -0,0 +1,2 @@
+/message_renderer_bench
+/rdatarender_bench
diff --git a/src/lib/dns/benchmarks/Makefile.am b/src/lib/dns/benchmarks/Makefile.am
index 0d7856f..ff591cc 100644
--- a/src/lib/dns/benchmarks/Makefile.am
+++ b/src/lib/dns/benchmarks/Makefile.am
@@ -9,10 +9,16 @@ endif
CLEANFILES = *.gcno *.gcda
-noinst_PROGRAMS = rdatarender_bench
+noinst_PROGRAMS = rdatarender_bench message_renderer_bench
+
rdatarender_bench_SOURCES = rdatarender_bench.cc
rdatarender_bench_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
rdatarender_bench_LDADD += $(top_builddir)/src/lib/util/libutil.la
rdatarender_bench_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
-rdatarender_bench_LDADD += $(SQLITE_LIBS)
+
+message_renderer_bench_SOURCES = message_renderer_bench.cc
+message_renderer_bench_SOURCES += oldmessagerenderer.h oldmessagerenderer.cc
+message_renderer_bench_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
+message_renderer_bench_LDADD += $(top_builddir)/src/lib/util/libutil.la
+message_renderer_bench_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
diff --git a/src/lib/dns/benchmarks/message_renderer_bench.cc b/src/lib/dns/benchmarks/message_renderer_bench.cc
new file mode 100644
index 0000000..33cd65b
--- /dev/null
+++ b/src/lib/dns/benchmarks/message_renderer_bench.cc
@@ -0,0 +1,176 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <bench/benchmark.h>
+
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <oldmessagerenderer.h>
+
+#include <cassert>
+#include <vector>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::bench;
+using namespace isc::dns;
+
+namespace {
+// This templated test performs rendering given set of names using
+// a given (templated) MessageRenderer implementation. We can check the
+// performance when we modify the renderer implementation by comparing the
+// old and new implementation for the same data.
+template <typename T>
+class MessageRendererBenchMark {
+public:
+ MessageRendererBenchMark(const vector<Name>& names) :
+ names_(names)
+ {}
+ unsigned int run() {
+ renderer_.clear();
+ vector<Name>::const_iterator it = names_.begin();
+ const vector<Name>::const_iterator it_end = names_.end();
+ for (; it != it_end; ++it) {
+ renderer_.writeName(*it);
+ }
+ // Make sure truncation didn't accidentally happen.
+ assert(!renderer_.isTruncated());
+ return (names_.size());
+ }
+private:
+ T renderer_;
+ const vector<Name>& names_;
+};
+
+//
+// Builtin benchmark data.
+//
+// This consists of all names contained in a response from a root server for
+// the query for "www.example.com" (as of this implementing).
+const char* const root_to_com_names[] = {
+ // question section
+ "www.example.com",
+ // authority section
+ "com", "a.gtld-servers.net", "com", "b.gtld-servers.net",
+ "com", "c.gtld-servers.net", "com", "d.gtld-servers.net",
+ "com", "e.gtld-servers.net", "com", "f.gtld-servers.net",
+ "com", "g.gtld-servers.net", "com", "h.gtld-servers.net",
+ "com", "i.gtld-servers.net", "com", "j.gtld-servers.net",
+ "com", "k.gtld-servers.net", "com", "l.gtld-servers.net",
+ "com", // owner name of DS
+ "com", // owner name of RRSIG(DS)
+ // additional section. a and b has both AAAA and A; others have A only.
+ "a.gtld-servers.net", "a.gtld-servers.net",
+ "b.gtld-servers.net", "b.gtld-servers.net",
+ "c.gtld-servers.net", "d.gtld-servers.net", "e.gtld-servers.net",
+ "f.gtld-servers.net", "g.gtld-servers.net", "h.gtld-servers.net",
+ "i.gtld-servers.net", "j.gtld-servers.net", "k.gtld-servers.net",
+ "l.gtld-servers.net", "m.gtld-servers.net",
+ NULL
+};
+
+// Names contained a typical "NXDOMAIN" response: the question, the owner
+// name of SOA, and its MNAME and RNAME.
+const char* const example_nxdomain_names[] = {
+ "www.example.com", "example.com", "ns.example.com", "root.example.com",
+ NULL
+};
+
+// Names contained a typical "SERVFAIL" response: only the question.
+const char* const example_servfail_names[] = {
+ "www.example.com", NULL
+};
+
+// An experimental "dumb" renderer for comparison. It doesn't do any name
+// compression. It simply ignores all setter method, returns a dummy value
+// for getter methods, and write names to the internal buffer as plain binary
+// data.
+class DumbMessageRenderer : public AbstractMessageRenderer {
+public:
+ virtual void clear() {}
+ virtual size_t getLengthLimit() const { return (512); }
+ virtual void setLengthLimit(const size_t) {}
+ virtual bool isTruncated() const { return (false); }
+ virtual void setTruncated() {}
+ virtual CompressMode getCompressMode() const { return (CASE_INSENSITIVE); }
+ virtual void setCompressMode(const CompressMode) {}
+ virtual void writeName(const Name& name, const bool = false) {
+ name.toWire(getBuffer());
+ }
+};
+
+void
+usage() {
+ cerr << "Usage: message_renderer_bench [-n iterations]" << endl;
+ exit (1);
+}
+}
+
+int
+main(int argc, char* argv[]) {
+ int ch;
+ int iteration = 100000;
+ while ((ch = getopt(argc, argv, "n:")) != -1) {
+ switch (ch) {
+ case 'n':
+ iteration = atoi(optarg);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 0) {
+ usage();
+ }
+
+ cout << "Parameters:" << endl;
+ cout << " Iterations: " << iteration << endl;
+
+ typedef pair<const char* const*, string> DataSpec;
+ vector<DataSpec> spec_list;
+ spec_list.push_back(DataSpec(root_to_com_names, "(positive response)"));
+ spec_list.push_back(DataSpec(example_nxdomain_names,
+ "(NXDOMAIN response)"));
+ spec_list.push_back(DataSpec(example_servfail_names,
+ "(SERVFAIL response)"));
+ for (vector<DataSpec>::const_iterator it = spec_list.begin();
+ it != spec_list.end();
+ ++it) {
+ vector<Name> names;
+ for (size_t i = 0; it->first[i] != NULL; ++i) {
+ names.push_back(Name(it->first[i]));
+ }
+
+ typedef MessageRendererBenchMark<OldMessageRenderer>
+ OldRendererBenchMark;
+ cout << "Benchmark for old MessageRenderer " << it->second << endl;
+ BenchMark<OldRendererBenchMark>(iteration,
+ OldRendererBenchMark(names));
+
+ typedef MessageRendererBenchMark<DumbMessageRenderer>
+ DumbRendererBenchMark;
+ cout << "Benchmark for dumb MessageRenderer " << it->second << endl;
+ BenchMark<DumbRendererBenchMark>(iteration,
+ DumbRendererBenchMark(names));
+
+ typedef MessageRendererBenchMark<MessageRenderer> RendererBenchMark;
+ cout << "Benchmark for new MessageRenderer " << it->second << endl;
+ BenchMark<RendererBenchMark>(iteration, RendererBenchMark(names));
+ }
+
+ return (0);
+}
diff --git a/src/lib/dns/benchmarks/oldmessagerenderer.cc b/src/lib/dns/benchmarks/oldmessagerenderer.cc
new file mode 100644
index 0000000..cd5c4e2
--- /dev/null
+++ b/src/lib/dns/benchmarks/oldmessagerenderer.cc
@@ -0,0 +1,278 @@
+// 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 <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <dns/name.h>
+#include <oldmessagerenderer.h>
+
+#include <cctype>
+#include <cassert>
+#include <set>
+
+using namespace isc::util;
+
+namespace isc {
+namespace dns {
+
+namespace { // hide internal-only names from the public namespaces
+///
+/// \brief The \c NameCompressNode class represents a pointer to a name
+/// rendered in the internal buffer for the \c MessageRendererImpl object.
+///
+/// A \c MessageRendererImpl object maintains a set of the \c NameCompressNode
+/// objects, and searches the set for the position of the longest match
+/// (ancestor) name against each new name to be rendered into the buffer.
+struct NameCompressNode {
+ NameCompressNode(const OldMessageRenderer& renderer,
+ const OutputBuffer& buffer, const size_t pos,
+ const size_t len) :
+ renderer_(renderer), buffer_(buffer), pos_(pos), len_(len) {}
+ /// The renderer that performs name compression using the node.
+ /// This is kept in each node to detect the compression mode
+ /// (case-sensitive or not) in the comparison functor (\c NameCompare).
+ const OldMessageRenderer& renderer_;
+ /// The buffer in which the corresponding name is rendered.
+ const OutputBuffer& buffer_;
+ /// The position (offset from the beginning) in the buffer where the
+ /// name starts.
+ uint16_t pos_;
+ /// The length of the corresponding name.
+ uint16_t len_;
+};
+
+///
+/// \brief The \c NameCompare class is a functor that gives ordering among
+/// \c NameCompressNode objects stored in \c MessageRendererImpl::nodeset_.
+///
+/// Its only public method as a functor, \c operator(), gives the ordering
+/// between two \c NameCompressNode objects in terms of equivalence, that is,
+/// returns whether one is "less than" the other.
+/// For our purpose we only need to distinguish two different names, so the
+/// ordering is different from the canonical DNS name order used in DNSSEC;
+/// basically, it gives the case-insensitive ordering of the two names as their
+/// textual representation.
+struct NameCompare : public std::binary_function<NameCompressNode,
+ NameCompressNode,
+ bool> {
+ ///
+ /// Returns true if n1 < n2 as a result of case-insensitive comparison;
+ /// otherwise return false.
+ ///
+ /// The name corresponding to \c n1 or \c n2 may be compressed, in which
+ /// case we must follow the compression pointer in the associated buffer.
+ /// The helper private method \c nextPosition() gives the position in the
+ /// buffer for the next character, taking into account compression.
+ ///
+ bool operator()(const NameCompressNode& n1,
+ const NameCompressNode& n2) const
+ {
+ if (n1.len_ < n2.len_) {
+ return (true);
+ } else if (n1.len_ > n2.len_) {
+ return (false);
+ }
+
+ const bool case_sensitive =
+ (n1.renderer_.getCompressMode() == OldMessageRenderer::CASE_SENSITIVE);
+
+ uint16_t pos1 = n1.pos_;
+ uint16_t pos2 = n2.pos_;
+ uint16_t l1 = 0;
+ uint16_t l2 = 0;
+ for (uint16_t i = 0; i < n1.len_; i++, pos1++, pos2++) {
+ pos1 = nextPosition(n1.buffer_, pos1, l1);
+ pos2 = nextPosition(n2.buffer_, pos2, l2);
+ if (case_sensitive) {
+ if (n1.buffer_[pos1] < n2.buffer_[pos2]) {
+ return (true);
+ } else if (n1.buffer_[pos1] > n2.buffer_[pos2]) {
+ return (false);
+ }
+ } else {
+ if (tolower(n1.buffer_[pos1]) < tolower(n2.buffer_[pos2])) {
+ return (true);
+ } else if (tolower(n1.buffer_[pos1]) >
+ tolower(n2.buffer_[pos2])) {
+ return (false);
+ }
+ }
+ }
+
+ return (false);
+ }
+
+private:
+ uint16_t nextPosition(const OutputBuffer& buffer,
+ uint16_t pos, uint16_t& llen) const
+ {
+ if (llen == 0) {
+ size_t i = 0;
+
+ while ((buffer[pos] & Name::COMPRESS_POINTER_MARK8) ==
+ Name::COMPRESS_POINTER_MARK8) {
+ pos = (buffer[pos] & ~Name::COMPRESS_POINTER_MARK8) *
+ 256 + buffer[pos + 1];
+
+ // This loop should stop as long as the buffer has been
+ // constructed validly and the search/insert argument is based
+ // on a valid name, which is an assumption for this class.
+ // But we'll abort if a bug could cause an infinite loop.
+ i += 2;
+ assert(i < Name::MAX_WIRE);
+ }
+ llen = buffer[pos];
+ } else {
+ --llen;
+ }
+ return (pos);
+ }
+};
+}
+
+///
+/// \brief The \c MessageRendererImpl class is the actual implementation of
+/// \c MessageRenderer.
+///
+/// The implementation is hidden from applications. We can refer to specific
+/// members of this class only within the implementation source file.
+///
+struct OldMessageRenderer::MessageRendererImpl {
+ /// \brief Constructor from an output buffer.
+ ///
+ MessageRendererImpl() :
+ nbuffer_(Name::MAX_WIRE), msglength_limit_(512),
+ truncated_(false), compress_mode_(OldMessageRenderer::CASE_INSENSITIVE)
+ {}
+ /// A local working buffer to convert each given name into wire format.
+ /// This could be a local variable of the \c writeName() method, but
+ /// we keep it in the class so that we can reuse it and avoid construction
+ /// overhead.
+ OutputBuffer nbuffer_;
+ /// A set of compression pointers.
+ std::set<NameCompressNode, NameCompare> nodeset_;
+ /// The maximum length of rendered data that can fit without
+ /// truncation.
+ uint16_t msglength_limit_;
+ /// A boolean flag that indicates truncation has occurred while rendering
+ /// the data.
+ bool truncated_;
+ /// The name compression mode.
+ CompressMode compress_mode_;
+};
+
+OldMessageRenderer::OldMessageRenderer() :
+ AbstractMessageRenderer(),
+ impl_(new MessageRendererImpl)
+{}
+
+OldMessageRenderer::~OldMessageRenderer() {
+ delete impl_;
+}
+
+void
+OldMessageRenderer::clear() {
+ AbstractMessageRenderer::clear();
+ impl_->nbuffer_.clear();
+ impl_->nodeset_.clear();
+ impl_->msglength_limit_ = 512;
+ impl_->truncated_ = false;
+ impl_->compress_mode_ = CASE_INSENSITIVE;
+}
+
+size_t
+OldMessageRenderer::getLengthLimit() const {
+ return (impl_->msglength_limit_);
+}
+
+void
+OldMessageRenderer::setLengthLimit(const size_t len) {
+ impl_->msglength_limit_ = len;
+}
+
+bool
+OldMessageRenderer::isTruncated() const {
+ return (impl_->truncated_);
+}
+
+void
+OldMessageRenderer::setTruncated() {
+ impl_->truncated_ = true;
+}
+
+OldMessageRenderer::CompressMode
+OldMessageRenderer::getCompressMode() const {
+ return (impl_->compress_mode_);
+}
+
+void
+OldMessageRenderer::setCompressMode(const CompressMode mode) {
+ impl_->compress_mode_ = mode;
+}
+
+void
+OldMessageRenderer::writeName(const Name& name, const bool compress) {
+ impl_->nbuffer_.clear();
+ name.toWire(impl_->nbuffer_);
+
+ unsigned int i;
+ std::set<NameCompressNode, NameCompare>::const_iterator notfound =
+ impl_->nodeset_.end();
+ std::set<NameCompressNode, NameCompare>::const_iterator n = notfound;
+
+ // Find the longest ancestor name in the rendered set that matches the
+ // given name.
+ for (i = 0; i < impl_->nbuffer_.getLength(); i += impl_->nbuffer_[i] + 1) {
+ // skip the trailing null label
+ if (impl_->nbuffer_[i] == 0) {
+ continue;
+ }
+ n = impl_->nodeset_.find(NameCompressNode(*this, impl_->nbuffer_, i,
+ impl_->nbuffer_.getLength() -
+ i));
+ if (n != notfound) {
+ break;
+ }
+ }
+
+ // Record the current offset before extending the buffer.
+ const size_t offset = getLength();
+ // Write uncompress part...
+ writeData(impl_->nbuffer_.getData(),
+ compress ? i : impl_->nbuffer_.getLength());
+ if (compress && n != notfound) {
+ // ...and compression pointer if available.
+ uint16_t pointer = (*n).pos_;
+ pointer |= Name::COMPRESS_POINTER_MARK16;
+ writeUint16(pointer);
+ }
+
+ // Finally, add to the set the newly rendered name and its ancestors that
+ // have not been in the set.
+ for (unsigned int j = 0; j < i; j += impl_->nbuffer_[j] + 1) {
+ if (impl_->nbuffer_[j] == 0) {
+ continue;
+ }
+ if (offset + j > Name::MAX_COMPRESS_POINTER) {
+ break;
+ }
+ impl_->nodeset_.insert(NameCompressNode(*this, getBuffer(),
+ offset + j,
+ impl_->nbuffer_.getLength() -
+ j));
+ }
+}
+
+}
+}
diff --git a/src/lib/dns/benchmarks/oldmessagerenderer.h b/src/lib/dns/benchmarks/oldmessagerenderer.h
new file mode 100644
index 0000000..14e7aee
--- /dev/null
+++ b/src/lib/dns/benchmarks/oldmessagerenderer.h
@@ -0,0 +1,55 @@
+// 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 __OLDMESSAGERENDERER_H
+#define __OLDMESSAGERENDERER_H 1
+
+//
+// This is a copy of an older version of MessageRenderer class. It is kept
+// here to provide a benchmark target.
+//
+
+#include <dns/messagerenderer.h>
+
+namespace isc {
+namespace dns {
+
+class OldMessageRenderer : public AbstractMessageRenderer {
+public:
+ using AbstractMessageRenderer::CASE_INSENSITIVE;
+ using AbstractMessageRenderer::CASE_SENSITIVE;
+
+ /// \brief Constructor from an output buffer.
+ OldMessageRenderer();
+
+ virtual ~OldMessageRenderer();
+ virtual bool isTruncated() const;
+ virtual size_t getLengthLimit() const;
+ virtual CompressMode getCompressMode() const;
+ virtual void setTruncated();
+ virtual void setLengthLimit(size_t len);
+ virtual void setCompressMode(CompressMode mode);
+ virtual void clear();
+ virtual void writeName(const Name& name, bool compress = true);
+private:
+ struct MessageRendererImpl;
+ MessageRendererImpl* impl_;
+};
+}
+}
+#endif // __OLDMESSAGERENDERER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/benchmarks/rdatarender_bench.cc b/src/lib/dns/benchmarks/rdatarender_bench.cc
index d1fb0f2..368ea6a 100644
--- a/src/lib/dns/benchmarks/rdatarender_bench.cc
+++ b/src/lib/dns/benchmarks/rdatarender_bench.cc
@@ -42,7 +42,7 @@ template <typename T>
class RdataRenderBenchMark {
public:
RdataRenderBenchMark(const vector<T>& dataset) :
- dataset_(dataset), buffer_(4096), renderer_(buffer_)
+ dataset_(dataset)
{}
unsigned int run() {
typename vector<T>::const_iterator data;
@@ -55,7 +55,6 @@ public:
}
private:
const vector<T>& dataset_;
- OutputBuffer buffer_;
MessageRenderer renderer_;
};
diff --git a/src/lib/dns/labelsequence.cc b/src/lib/dns/labelsequence.cc
new file mode 100644
index 0000000..0ec450f
--- /dev/null
+++ b/src/lib/dns/labelsequence.cc
@@ -0,0 +1,114 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/labelsequence.h>
+#include <dns/name_internal.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/functional/hash.hpp>
+
+namespace isc {
+namespace dns {
+
+const char*
+LabelSequence::getData(size_t *len) const {
+ *len = getDataLength();
+ return (&name_.ndata_[name_.offsets_[first_label_]]);
+}
+
+size_t
+LabelSequence::getDataLength() const {
+ // If the labelsequence is absolute, the current last_label_ falls
+ // out of the vector (since it points to the 'label' after the
+ // root label, which doesn't exist; in that case, return
+ // the length for the 'previous' label (the root label) plus
+ // one (for the root label zero octet)
+ if (isAbsolute()) {
+ return (name_.offsets_[last_label_ - 1] -
+ name_.offsets_[first_label_] + 1);
+ } else {
+ return (name_.offsets_[last_label_] - name_.offsets_[first_label_]);
+ }
+}
+
+bool
+LabelSequence::equals(const LabelSequence& other, bool case_sensitive) const {
+ size_t len, other_len;
+ const char* data = getData(&len);
+ const char* other_data = other.getData(&other_len);
+
+ if (len != other_len) {
+ return (false);
+ }
+ if (case_sensitive) {
+ return (strncmp(data, other_data, len) == 0);
+ }
+
+ // As long as the data was originally validated as (part of) a name,
+ // label length must never be a capital ascii character, so we can
+ // simply compare them after converting to lower characters.
+ for (size_t i = 0; i < len; ++i) {
+ const unsigned char ch = data[i];
+ const unsigned char other_ch = other_data[i];
+ if (isc::dns::name::internal::maptolower[ch] !=
+ isc::dns::name::internal::maptolower[other_ch]) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+void
+LabelSequence::stripLeft(size_t i) {
+ if (i >= getLabelCount()) {
+ isc_throw(OutOfRange, "Cannot strip to zero or less labels; " << i <<
+ " (labelcount: " << getLabelCount() << ")");
+ }
+ first_label_ += i;
+}
+
+void
+LabelSequence::stripRight(size_t i) {
+ if (i >= getLabelCount()) {
+ isc_throw(OutOfRange, "Cannot strip to zero or less labels; " << i <<
+ " (labelcount: " << getLabelCount() << ")");
+ }
+ last_label_ -= i;
+}
+
+bool
+LabelSequence::isAbsolute() const {
+ return (last_label_ == name_.offsets_.size());
+}
+
+size_t
+LabelSequence::getHash(bool case_sensitive) const {
+ size_t length;
+ const char* s = getData(&length);
+ if (length > 16) {
+ length = 16;
+ }
+
+ size_t hash_val = 0;
+ while (length > 0) {
+ const unsigned char c = *s++;
+ boost::hash_combine(hash_val, case_sensitive ? c :
+ isc::dns::name::internal::maptolower[c]);
+ --length;
+ }
+ return (hash_val);
+}
+
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/labelsequence.h b/src/lib/dns/labelsequence.h
new file mode 100644
index 0000000..6b10b67
--- /dev/null
+++ b/src/lib/dns/labelsequence.h
@@ -0,0 +1,177 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __LABELSEQUENCE_H
+#define __LABELSEQUENCE_H 1
+
+#include <dns/name.h>
+#include <util/buffer.h>
+
+namespace isc {
+namespace dns {
+
+/// \brief Light-weight Accessor to Name object
+///
+/// The purpose of this class is to easily match Names and parts of Names,
+/// without needing to copy the underlying data on each label strip.
+///
+/// It can only work on existing Name objects, and the Name object MUST
+/// remain in scope during the entire lifetime of its associated
+/// LabelSequence(s).
+///
+/// Upon creation of a LabelSequence, it records the offsets of the
+/// labels in the wireformat data of the Name. When stripLeft() or
+/// stripRight() is called on the LabelSequence, no changes in the
+/// Name's data occur, but the internal pointers of the
+/// LabelSequence are modified.
+///
+/// LabelSequences can be compared to other LabelSequences, and their
+/// data can be requested (which then points to part of the original
+/// data of the associated Name object).
+///
+class LabelSequence {
+public:
+ /// \brief Constructs a LabelSequence for the given name
+ ///
+ /// \note The associated Name MUST remain in scope during the lifetime
+ /// of this LabelSequence, since getData() refers to data from the
+ /// Name object (the only data the LabelSequence stores are pointers
+ /// to the labels in the Name object).
+ ///
+ /// \param name The Name to construct a LabelSequence for
+ LabelSequence(const Name& name): name_(name),
+ first_label_(0),
+ last_label_(name.getLabelCount())
+ {}
+
+ /// \brief Return the wire-format data for this LabelSequence
+ ///
+ /// The data, is returned as a pointer to the original wireformat
+ /// data of the original Name object, and the given len value is
+ /// set to the number of octets that match this labelsequence.
+ ///
+ /// \note The data pointed to is only valid if the original Name
+ /// object is still in scope
+ ///
+ /// \param len Pointer to a size_t where the length of the data
+ /// will be stored (in number of octets)
+ /// \return Pointer to the wire-format data of this label sequence
+ const char* getData(size_t* len) const;
+
+ /// \brief Return the length of the wire-format data of this LabelSequence
+ ///
+ /// This method returns the number of octets for the data that would
+ /// be returned by the \c getData() method.
+ ///
+ /// Note that the return value of this method is always positive.
+ /// Note also that if the return value of this method is 1, it means the
+ /// sequence consists of the null label, i.e., a single "dot", and vice
+ /// versa.
+ ///
+ /// \note The data pointed to is only valid if the original Name
+ /// object is still in scope
+ ///
+ /// \return The length of the data of the label sequence in octets.
+ size_t getDataLength() const;
+
+ /// \brief Compares two label sequences.
+ ///
+ /// Performs a (optionally case-insensitive) comparison between this
+ /// LabelSequence and another LabelSequence.
+ ///
+ /// \param other The LabelSequence to compare with
+ /// \param case_sensitive If true, comparison is case-insensitive
+ /// \return true if The label sequences consist are the same length,
+ /// and contain the same data.
+ bool equals(const LabelSequence& other, bool case_sensitive = false) const;
+
+ /// \brief Remove labels from the front of this LabelSequence
+ ///
+ /// \note No actual memory is changed, this operation merely updates the
+ /// internal pointers based on the offsets in the Name object.
+ ///
+ /// \exeption OutOfRange if i is greater than or equal to the number
+ /// of labels currently pointed to by this LabelSequence
+ ///
+ /// \param i The number of labels to remove.
+ void stripLeft(size_t i);
+
+ /// \brief Remove labels from the end of this LabelSequence
+ ///
+ /// \note No actual memory is changed, this operation merely updates the
+ /// internal pointers based on the offsets in the Name object.
+ ///
+ /// \exeption OutOfRange if i is greater than or equal to the number
+ /// of labels currently pointed to by this LabelSequence
+ ///
+ /// \param i The number of labels to remove.
+ void stripRight(size_t i);
+
+ /// \brief Returns the current number of labels for this LabelSequence
+ ///
+ /// \return The number of labels
+ size_t getLabelCount() const { return (last_label_ - first_label_); }
+
+ /// \brief Returns the original Name object associated with this
+ /// LabelSequence
+ ///
+ /// While the Name should still be in scope during the lifetime of
+ /// the LabelSequence, it can still be useful to have access to it,
+ /// for instance in helper functions that are only passed the
+ /// LabelSequence itself.
+ ///
+ /// \return Reference to the original Name object
+ const Name& getName() const { return (name_); }
+
+ /// \brief Calculate a simple hash for the label sequence.
+ ///
+ /// This method calculates a hash value for the label sequence as binary
+ /// data. If \c case_sensitive is false, it ignores the case stored in
+ /// the labels; specifically, it normalizes the labels by converting all
+ /// upper case characters to lower case ones and calculates the hash value
+ /// for the result.
+ ///
+ /// This method is intended to provide a lightweight way to store a
+ /// relatively small number of label sequences in a hash table.
+ /// For this reason it only takes into account data up to 16 octets
+ /// (16 was derived from BIND 9's implementation). Also, the function does
+ /// not provide any unpredictability; a specific sequence will always have
+ /// the same hash value. It should therefore not be used in the context
+ /// where an untrusted third party can mount a denial of service attack by
+ /// forcing the application to create a very large number of label
+ /// sequences that have the same hash value and expected to be stored in
+ /// a hash table.
+ ///
+ /// \exception None
+ ///
+ /// \param case_sensitive
+ /// \return A hash value for this label sequence.
+ size_t getHash(bool case_sensitive) const;
+
+ /// \brief Checks whether the label sequence is absolute
+ ///
+ /// \return true if the last label is the root label
+ bool isAbsolute() const;
+
+private:
+ const Name& name_;
+ size_t first_label_;
+ size_t last_label_;
+};
+
+
+} // end namespace dns
+} // end namespace isc
+
+#endif
diff --git a/src/lib/dns/masterload.cc b/src/lib/dns/masterload.cc
index a32d4b7..0b195f6 100644
--- a/src/lib/dns/masterload.cc
+++ b/src/lib/dns/masterload.cc
@@ -25,6 +25,7 @@
#include <dns/masterload.h>
#include <dns/name.h>
#include <dns/rdata.h>
+#include <dns/rdataclass.h>
#include <dns/rrclass.h>
#include <dns/rrset.h>
#include <dns/rrttl.h>
@@ -36,6 +37,29 @@ using namespace isc::dns::rdata;
namespace isc {
namespace dns {
+namespace {
+// A helper function that strips off any comment or whitespace at the end of
+// an RR.
+// This is an incomplete implementation, and cannot handle all such comments;
+// it's considered a short term workaround to deal with some real world
+// cases.
+string
+stripLine(string& s, const Exception& ex) {
+ // Find any ';' in the text data, and locate the position of the last
+ // occurrence. Note that unless/until we support empty RDATA it
+ // shouldn't be placed at the beginning of the data.
+ const size_t pos_semicolon = s.rfind(';');
+ if (pos_semicolon == 0) {
+ throw ex;
+ } else if (pos_semicolon != string::npos) {
+ s.resize(pos_semicolon);
+ }
+ // Remove any trailing whitespace return the resulting text.
+ s.resize(s.find_last_not_of(" \t") + 1);
+ return (s);
+}
+}
+
void
masterLoad(const char* const filename, const Name& origin,
const RRClass& zone_class, MasterLoadCallback callback)
@@ -58,6 +82,7 @@ masterLoad(istream& input, const Name& origin, const RRClass& zone_class,
MasterLoadCallback callback)
{
RRsetPtr rrset;
+ ConstRdataPtr prev_rdata; // placeholder for special case of RRSIGs
string line;
unsigned int line_count = 1;
@@ -114,7 +139,15 @@ masterLoad(istream& input, const Name& origin, const RRClass& zone_class,
ttl.reset(new RRTTL(ttl_txt));
rrclass.reset(new RRClass(rrclass_txt));
rrtype.reset(new RRType(rrtype_txt));
- rdata = createRdata(*rrtype, *rrclass, rdatabuf.str());
+ string rdtext = rdatabuf.str();
+ try {
+ rdata = createRdata(*rrtype, *rrclass, rdtext);
+ } catch (const Exception& ex) {
+ // If the parse for the RDATA fails, check if it has comments
+ // or whitespace at the end, and if so, retry the conversion
+ // after stripping off the comment or whitespace
+ rdata = createRdata(*rrtype, *rrclass, stripLine(rdtext, ex));
+ }
} catch (const Exception& ex) {
isc_throw(MasterLoadError, "Invalid RR text at line " << line_count
<< ": " << ex.what());
@@ -145,8 +178,20 @@ masterLoad(istream& input, const Name& origin, const RRClass& zone_class,
// 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.
+ bool new_rrset = false;
if (!rrset || rrset->getType() != *rrtype ||
rrset->getName() != *owner) {
+ new_rrset = true;
+ } else if (rrset->getType() == RRType::RRSIG()) {
+ // We are seeing two consecutive RRSIGs of the same name.
+ // They can be combined iff they have the same type covered.
+ if (dynamic_cast<const generic::RRSIG&>(*rdata).typeCovered() !=
+ dynamic_cast<const generic::RRSIG&>(*prev_rdata).typeCovered())
+ {
+ new_rrset = true;
+ }
+ }
+ if (new_rrset) {
// Commit the previous RRset, if any.
if (rrset) {
callback(rrset);
@@ -154,6 +199,7 @@ masterLoad(istream& input, const Name& origin, const RRClass& zone_class,
rrset = RRsetPtr(new RRset(*owner, *rrclass, *rrtype, *ttl));
}
rrset->addRdata(rdata);
+ prev_rdata = rdata;
} while (++line_count, !input.eof());
// Commit the last RRset, if any.
diff --git a/src/lib/dns/masterload.h b/src/lib/dns/masterload.h
index fe5c08f..41d145b 100644
--- a/src/lib/dns/masterload.h
+++ b/src/lib/dns/masterload.h
@@ -206,9 +206,8 @@ typedef boost::function<void(RRsetPtr)> MasterLoadCallback;
/// - 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.
+/// - RRSIGs are handled as separate RRsets, i.e. they are not included in
+/// the RRset they cover.
///
/// \param filename A path to a master zone file to be loaded.
/// \param origin The origin name of the zone.
diff --git a/src/lib/dns/message.cc b/src/lib/dns/message.cc
index b3e9229..0db68c6 100644
--- a/src/lib/dns/message.cc
+++ b/src/lib/dns/message.cc
@@ -489,6 +489,10 @@ Message::getRRCount(const Section section) const {
void
Message::addRRset(const Section section, RRsetPtr rrset, const bool sign) {
+ if (!rrset) {
+ isc_throw(InvalidParameter,
+ "NULL RRset is given to Message::addRRset");
+ }
if (impl_->mode_ != Message::RENDER) {
isc_throw(InvalidMessageOperation,
"addRRset performed in non-render mode");
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h
index 47632cb..33551c0 100644
--- a/src/lib/dns/message.h
+++ b/src/lib/dns/message.h
@@ -462,15 +462,19 @@ public:
/// This interface takes into account the RRSIG possibly attached to
/// \c rrset. This interface design needs to be revisited later.
///
- /// 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 \c addRRset() does not currently check for duplicate
/// data before inserting RRsets. The caller is responsible for
/// checking for these (see \c hasRRset() below).
+ ///
+ /// \throw InvalidParameter rrset is NULL
+ /// \throw InvalidMessageOperation The message is not in the \c RENDER
+ /// mode.
+ /// \throw OutOfRange \c section doesn't specify a valid \c Section value.
+ ///
+ /// \param section The message section to which the rrset is to be added
+ /// \param rrset The rrset to be added. Must not be NULL.
+ /// \param sign If true, and if \c rrset has associated RRSIGs, the
+ /// RRSIGs will also be added to the same section of the message.
void addRRset(const Section section, RRsetPtr rrset, bool sign = false);
/// \brief Determine whether the given section already has an RRset
diff --git a/src/lib/dns/messagerenderer.cc b/src/lib/dns/messagerenderer.cc
index 1378ba9..d5c7c69 100644
--- a/src/lib/dns/messagerenderer.cc
+++ b/src/lib/dns/messagerenderer.cc
@@ -12,104 +12,108 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <cctype>
-#include <cassert>
-#include <set>
-
+#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <dns/name.h>
+#include <dns/name_internal.h>
+#include <dns/labelsequence.h>
#include <dns/messagerenderer.h>
+#include <boost/array.hpp>
+#include <boost/static_assert.hpp>
+
+#include <limits>
+#include <cassert>
+#include <vector>
+
+using namespace std;
using namespace isc::util;
+using isc::dns::name::internal::maptolower;
namespace isc {
namespace dns {
namespace { // hide internal-only names from the public namespaces
///
-/// \brief The \c NameCompressNode class represents a pointer to a name
+/// \brief The \c OffsetItem class represents a pointer to a name
/// rendered in the internal buffer for the \c MessageRendererImpl object.
///
-/// A \c MessageRendererImpl object maintains a set of the \c NameCompressNode
-/// objects, and searches the set for the position of the longest match
-/// (ancestor) name against each new name to be rendered into the buffer.
-struct NameCompressNode {
- NameCompressNode(const MessageRenderer& renderer,
- const OutputBuffer& buffer, const size_t pos,
- const size_t len) :
- renderer_(renderer), buffer_(buffer), pos_(pos), len_(len) {}
- /// The renderer that performs name compression using the node.
- /// This is kept in each node to detect the compression mode
- /// (case-sensitive or not) in the comparison functor (\c NameCompare).
- const MessageRenderer& renderer_;
- /// The buffer in which the corresponding name is rendered.
- const OutputBuffer& buffer_;
+/// A \c MessageRendererImpl object maintains a set of \c OffsetItem
+/// objects in a hash table, and searches the table for the position of the
+/// longest match (ancestor) name against each new name to be rendered into
+/// the buffer.
+struct OffsetItem {
+ OffsetItem(size_t hash, size_t pos, size_t len) :
+ hash_(hash), pos_(pos), len_(len)
+ {}
+
+ /// The hash value for the stored name calculated by LabelSequence.getHash.
+ /// This will help make name comparison in \c NameCompare more efficient.
+ size_t hash_;
+
/// The position (offset from the beginning) in the buffer where the
/// name starts.
uint16_t pos_;
- /// The length of the corresponding name.
+
+ /// The length of the corresponding sequence (which is a domain name).
uint16_t len_;
};
+/// \brief The \c NameCompare class is a functor that checks equality
+/// between the name corresponding to an \c OffsetItem object and the name
+/// consists of labels represented by a \c LabelSequence object.
///
-/// \brief The \c NameCompare class is a functor that gives ordering among
-/// \c NameCompressNode objects stored in \c MessageRendererImpl::nodeset_.
-///
-/// Its only public method as a functor, \c operator(), gives the ordering
-/// between two \c NameCompressNode objects in terms of equivalence, that is,
-/// returns whether one is "less than" the other.
-/// For our purpose we only need to distinguish two different names, so the
-/// ordering is different from the canonical DNS name order used in DNSSEC;
-/// basically, it gives the case-insensitive ordering of the two names as their
-/// textual representation.
-struct NameCompare : public std::binary_function<NameCompressNode,
- NameCompressNode,
- bool> {
+/// Template parameter CASE_SENSITIVE determines whether to ignore the case
+/// of the names. This policy doesn't change throughout the lifetime of
+/// this object, so we separate these using template to avoid unnecessary
+/// condition check.
+template <bool CASE_SENSITIVE>
+struct NameCompare {
+ /// \brief Constructor
///
- /// Returns true if n1 < n2 as a result of case-insensitive comparison;
- /// otherwise return false.
- ///
- /// The name corresponding to \c n1 or \c n2 may be compressed, in which
- /// case we must follow the compression pointer in the associated buffer.
- /// The helper private method \c nextPosition() gives the position in the
- /// buffer for the next character, taking into account compression.
- ///
- bool operator()(const NameCompressNode& n1,
- const NameCompressNode& n2) const
- {
- if (n1.len_ < n2.len_) {
- return (true);
- } else if (n1.len_ > n2.len_) {
+ /// \param buffer The buffer for rendering used in the caller renderer
+ /// \param name_buf An input buffer storing the wire-format data of the
+ /// name to be newly rendered (and only that data).
+ /// \param hash The hash value for the name.
+ NameCompare(const OutputBuffer& buffer, InputBuffer& name_buf,
+ size_t hash) :
+ buffer_(&buffer), name_buf_(&name_buf), hash_(hash)
+ {}
+
+ bool operator()(const OffsetItem& item) const {
+ // Trivial inequality check. If either the hash or the total length
+ // doesn't match, the names are obviously different.
+ if (item.hash_ != hash_ || item.len_ != name_buf_->getLength()) {
return (false);
}
- const bool case_sensitive =
- (n1.renderer_.getCompressMode() == MessageRenderer::CASE_SENSITIVE);
-
- uint16_t pos1 = n1.pos_;
- uint16_t pos2 = n2.pos_;
- uint16_t l1 = 0;
- uint16_t l2 = 0;
- for (uint16_t i = 0; i < n1.len_; i++, pos1++, pos2++) {
- pos1 = nextPosition(n1.buffer_, pos1, l1);
- pos2 = nextPosition(n2.buffer_, pos2, l2);
- if (case_sensitive) {
- if (n1.buffer_[pos1] < n2.buffer_[pos2]) {
- return (true);
- } else if (n1.buffer_[pos1] > n2.buffer_[pos2]) {
+ // Compare the name data, character-by-character.
+ // item_pos keeps track of the position in the buffer corresponding to
+ // the character to compare. item_label_len is the number of
+ // characters in the labels where the character pointed by item_pos
+ // belongs. When it reaches zero, nextPosition() identifies the
+ // position for the subsequent label, taking into account name
+ // compression, and resets item_label_len to the length of the new
+ // label.
+ name_buf_->setPosition(0); // buffer can be reused, so reset position
+ uint16_t item_pos = item.pos_;
+ uint16_t item_label_len = 0;
+ for (size_t i = 0; i < item.len_; ++i, ++item_pos) {
+ item_pos = nextPosition(*buffer_, item_pos, item_label_len);
+ const unsigned char ch1 = (*buffer_)[item_pos];
+ const unsigned char ch2 = name_buf_->readUint8();
+ if (CASE_SENSITIVE) {
+ if (ch1 != ch2) {
return (false);
}
} else {
- if (tolower(n1.buffer_[pos1]) < tolower(n2.buffer_[pos2])) {
- return (true);
- } else if (tolower(n1.buffer_[pos1]) >
- tolower(n2.buffer_[pos2])) {
+ if (maptolower[ch1] != maptolower[ch2]) {
return (false);
}
}
}
- return (false);
+ return (true);
}
private:
@@ -137,6 +141,10 @@ private:
}
return (pos);
}
+
+ const OutputBuffer* buffer_;
+ InputBuffer* name_buf_;
+ const size_t hash_;
};
}
@@ -147,20 +155,60 @@ private:
/// The implementation is hidden from applications. We can refer to specific
/// members of this class only within the implementation source file.
///
+/// It internally holds a hash table for OffsetItem objects corresponding
+/// to portions of names rendered in this renderer. The offset information
+/// is used to compress subsequent names to be rendered.
struct MessageRenderer::MessageRendererImpl {
- /// \brief Constructor from an output buffer.
- ///
+ // The size of hash buckets and number of hash entries per bucket for
+ // which space is preallocated and kept reserved for subsequent rendering
+ // to provide better performance. These values are derived from the
+ // BIND 9 implementation that uses a similar hash table.
+ static const size_t BUCKETS = 64;
+ static const size_t RESERVED_ITEMS = 16;
+ static const uint16_t NO_OFFSET = 65535; // used as a marker of 'not found'
+
+ /// \brief Constructor
MessageRendererImpl() :
- nbuffer_(Name::MAX_WIRE), msglength_limit_(512),
- truncated_(false), compress_mode_(MessageRenderer::CASE_INSENSITIVE)
- {}
- /// A local working buffer to convert each given name into wire format.
- /// This could be a local variable of the \c writeName() method, but
- /// we keep it in the class so that we can reuse it and avoid construction
- /// overhead.
- OutputBuffer nbuffer_;
- /// A set of compression pointers.
- std::set<NameCompressNode, NameCompare> nodeset_;
+ msglength_limit_(512), truncated_(false),
+ compress_mode_(MessageRenderer::CASE_INSENSITIVE)
+ {
+ // Reserve some spaces for hash table items.
+ for (size_t i = 0; i < BUCKETS; ++i) {
+ table_[i].reserve(RESERVED_ITEMS);
+ }
+ }
+
+ uint16_t findOffset(const OutputBuffer& buffer, InputBuffer& name_buf,
+ size_t hash, bool case_sensitive) const
+ {
+ // Find a matching entry, if any. We use some heuristics here: often
+ // the same name appers consecutively (like repeating the same owner
+ // name for a single RRset), so in case there's a collision in the
+ // bucket it will be more likely to find it in the tail side of the
+ // bucket.
+ const size_t bucket_id = hash % BUCKETS;
+ vector<OffsetItem>::const_reverse_iterator found;
+ if (case_sensitive) {
+ found = find_if(table_[bucket_id].rbegin(),
+ table_[bucket_id].rend(),
+ NameCompare<true>(buffer, name_buf, hash));
+ } else {
+ found = find_if(table_[bucket_id].rbegin(),
+ table_[bucket_id].rend(),
+ NameCompare<false>(buffer, name_buf, hash));
+ }
+ if (found != table_[bucket_id].rend()) {
+ return (found->pos_);
+ }
+ return (NO_OFFSET);
+ }
+
+ void addOffset(size_t hash, size_t offset, size_t len) {
+ table_[hash % BUCKETS].push_back(OffsetItem(hash, offset, len));
+ }
+
+ // The hash table for the (offset + position in the buffer) entries
+ vector<OffsetItem> table_[BUCKETS];
/// The maximum length of rendered data that can fit without
/// truncation.
uint16_t msglength_limit_;
@@ -169,10 +217,15 @@ struct MessageRenderer::MessageRendererImpl {
bool truncated_;
/// The name compression mode.
CompressMode compress_mode_;
+
+ // Placeholder for hash values as they are calculated in writeName().
+ // Note: we may want to make it a local variable of writeName() if it
+ // works more efficiently.
+ boost::array<size_t, Name::MAX_LABELS> seq_hashes_;
};
-MessageRenderer::MessageRenderer(OutputBuffer& buffer) :
- AbstractMessageRenderer(buffer),
+MessageRenderer::MessageRenderer() :
+ AbstractMessageRenderer(),
impl_(new MessageRendererImpl)
{}
@@ -183,11 +236,22 @@ MessageRenderer::~MessageRenderer() {
void
MessageRenderer::clear() {
AbstractMessageRenderer::clear();
- impl_->nbuffer_.clear();
- impl_->nodeset_.clear();
impl_->msglength_limit_ = 512;
impl_->truncated_ = false;
impl_->compress_mode_ = CASE_INSENSITIVE;
+
+ // Clear the hash table. We reserve the minimum space for possible
+ // subsequent use of the renderer.
+ for (size_t i = 0; i < MessageRendererImpl::BUCKETS; ++i) {
+ if (impl_->table_[i].size() > MessageRendererImpl::RESERVED_ITEMS) {
+ // Trim excessive capacity: swap ensures the new capacity is only
+ // reasonably large for the reserved space.
+ vector<OffsetItem> new_table;
+ new_table.reserve(MessageRendererImpl::RESERVED_ITEMS);
+ new_table.swap(impl_->table_[i]);
+ }
+ impl_->table_[i].clear();
+ }
}
size_t
@@ -217,65 +281,112 @@ MessageRenderer::getCompressMode() const {
void
MessageRenderer::setCompressMode(const CompressMode mode) {
+ if (getLength() != 0) {
+ isc_throw(isc::InvalidParameter,
+ "compress mode cannot be changed during rendering");
+ }
impl_->compress_mode_ = mode;
}
void
MessageRenderer::writeName(const Name& name, const bool compress) {
- impl_->nbuffer_.clear();
- name.toWire(impl_->nbuffer_);
-
- unsigned int i;
- std::set<NameCompressNode, NameCompare>::const_iterator notfound =
- impl_->nodeset_.end();
- std::set<NameCompressNode, NameCompare>::const_iterator n = notfound;
-
- // Find the longest ancestor name in the rendered set that matches the
- // given name.
- for (i = 0; i < impl_->nbuffer_.getLength(); i += impl_->nbuffer_[i] + 1) {
- // skip the trailing null label
- if (impl_->nbuffer_[i] == 0) {
- continue;
+ LabelSequence sequence(name);
+ const size_t nlabels = sequence.getLabelCount();
+ size_t data_len;
+ const char* data;
+
+ // Find the offset in the offset table whose name gives the longest
+ // match against the name to be rendered.
+ size_t nlabels_uncomp;
+ uint16_t ptr_offset = MessageRendererImpl::NO_OFFSET;
+ const bool case_sensitive = (impl_->compress_mode_ ==
+ MessageRenderer::CASE_SENSITIVE);
+ for (nlabels_uncomp = 0; nlabels_uncomp < nlabels; ++nlabels_uncomp) {
+ data = sequence.getData(&data_len);
+ if (data_len == 1) { // trailing dot.
+ ++nlabels_uncomp;
+ break;
}
- n = impl_->nodeset_.find(NameCompressNode(*this, impl_->nbuffer_, i,
- impl_->nbuffer_.getLength() -
- i));
- if (n != notfound) {
+ // write with range check for safety
+ impl_->seq_hashes_.at(nlabels_uncomp) =
+ sequence.getHash(impl_->compress_mode_);
+ InputBuffer name_buf(data, data_len);
+ ptr_offset = impl_->findOffset(getBuffer(), name_buf,
+ impl_->seq_hashes_[nlabels_uncomp],
+ case_sensitive);
+ if (ptr_offset != MessageRendererImpl::NO_OFFSET) {
break;
}
+ sequence.stripLeft(1);
}
- // Record the current offset before extending the buffer.
- const size_t offset = getLength();
- // Write uncompress part...
- writeData(impl_->nbuffer_.getData(),
- compress ? i : impl_->nbuffer_.getLength());
- if (compress && n != notfound) {
- // ...and compression pointer if available.
- uint16_t pointer = (*n).pos_;
- pointer |= Name::COMPRESS_POINTER_MARK16;
- writeUint16(pointer);
+ // Record the current offset before updating the offset table
+ size_t offset = getLength();
+ // Write uncompress part:
+ if (nlabels_uncomp > 0 || !compress) {
+ LabelSequence uncomp_sequence(name);
+ if (compress && nlabels > nlabels_uncomp) {
+ // If there's compressed part, strip off that part.
+ uncomp_sequence.stripRight(nlabels - nlabels_uncomp);
+ }
+ data = uncomp_sequence.getData(&data_len);
+ writeData(data, data_len);
+ }
+ // And write compression pointer if available:
+ if (compress && ptr_offset != MessageRendererImpl::NO_OFFSET) {
+ ptr_offset |= Name::COMPRESS_POINTER_MARK16;
+ writeUint16(ptr_offset);
}
- // Finally, add to the set the newly rendered name and its ancestors that
- // have not been in the set.
- for (unsigned int j = 0; j < i; j += impl_->nbuffer_[j] + 1) {
- if (impl_->nbuffer_[j] == 0) {
- continue;
+ // Finally, record the offset and length for each uncompressed sequence
+ // in the hash table. The renderer's buffer has just stored the
+ // corresponding data, so we use the rendered data to get the length
+ // of each label of the names.
+ size_t seqlen = name.getLength();
+ for (size_t i = 0; i < nlabels_uncomp; ++i) {
+ const uint8_t label_len = getBuffer()[offset];
+ if (label_len == 0) { // offset for root doesn't need to be stored.
+ break;
}
- if (offset + j > Name::MAX_COMPRESS_POINTER) {
+ if (offset > Name::MAX_COMPRESS_POINTER) {
break;
}
- impl_->nodeset_.insert(NameCompressNode(*this, getBuffer(),
- offset + j,
- impl_->nbuffer_.getLength() -
- j));
+ // Store the tuple of <hash, offset, len> to the table. Note that we
+ // already know the hash value for each name.
+ impl_->addOffset(impl_->seq_hashes_[i], offset, seqlen);
+ offset += (label_len + 1);
+ seqlen -= (label_len + 1);
+ }
+}
+
+AbstractMessageRenderer::AbstractMessageRenderer() :
+ local_buffer_(0), buffer_(&local_buffer_)
+{
+}
+
+void
+AbstractMessageRenderer::setBuffer(OutputBuffer* buffer) {
+ if (buffer != NULL && buffer_->getLength() != 0) {
+ isc_throw(isc::InvalidParameter,
+ "MessageRenderer buffer cannot be set when in use");
+ } if (buffer == NULL && buffer_ == &local_buffer_) {
+ isc_throw(isc::InvalidParameter,
+ "Default MessageRenderer buffer cannot be reset");
+ }
+
+ if (buffer == NULL) {
+ // Reset to the default buffer, then clear other internal resources.
+ // The order is important; we need to keep the used buffer intact.
+ buffer_ = &local_buffer_;
+ clear();
+ } else {
+ buffer_ = buffer;
}
}
void
AbstractMessageRenderer::clear() {
- buffer_.clear();
+ buffer_->clear();
}
}
diff --git a/src/lib/dns/messagerenderer.h b/src/lib/dns/messagerenderer.h
index 52d9245..4c1c92a 100644
--- a/src/lib/dns/messagerenderer.h
+++ b/src/lib/dns/messagerenderer.h
@@ -37,9 +37,15 @@ class Name;
/// comprehensive \c Message class internally; normal applications won't have
/// to care about details of this class.
///
-/// Once a renderer class object is constructed with a buffer, it is
-/// generally expected that all rendering operations are performed via that
-/// object. If the application modifies the buffer in
+/// By default any (derived) renderer class object is associated with
+/// an internal buffer, and subsequent write operations will be performed
+/// on that buffer. The rendering result can be retrieved via the
+/// \c getData() method.
+///
+/// If an application wants a separate buffer can be (normally temporarily)
+/// set for rendering operations via the \c setBuffer() method. In that case,
+/// it is generally expected that all rendering operations are performed via
+/// that object. If the application modifies the buffer in
/// parallel with the renderer, the result will be undefined.
///
/// Note to developers: we introduced a separate class for name compression
@@ -101,30 +107,30 @@ protected:
///
/// This is intentionally defined as \c protected as this base class should
/// never be instantiated (except as part of a derived class).
- /// \param buffer The buffer where the data should be rendered into.
- /// \todo We might want to revisit this API at some point and remove the
- /// buffer parameter. In that case it would create it's own buffer and
- /// a function to extract the data would be available instead. It seems
- /// like a cleaner design, but it's left undone until we would actually
- /// benefit from the change.
- AbstractMessageRenderer(isc::util::OutputBuffer& buffer) :
- buffer_(buffer)
- {}
+ AbstractMessageRenderer();
+
public:
/// \brief The destructor.
virtual ~AbstractMessageRenderer() {}
//@}
protected:
/// \brief Return the output buffer we render into.
- const isc::util::OutputBuffer& getBuffer() const { return (buffer_); }
- isc::util::OutputBuffer& getBuffer() { return (buffer_); }
+ const isc::util::OutputBuffer& getBuffer() const { return (*buffer_); }
+ isc::util::OutputBuffer& getBuffer() { return (*buffer_); }
private:
- /// \short Buffer to store data
+ /// \brief Local (default) buffer to store data.
+ isc::util::OutputBuffer local_buffer_;
+
+ /// \brief Buffer to store data.
+ ///
+ /// Note that the class interface ensures this pointer is never NULL;
+ /// it either refers to \c local_buffer_ or to an application-supplied
+ /// buffer by \c setBuffer().
///
/// It was decided that there's no need to have this in every subclass,
- /// at least not now, and this reduces code size and gives compiler a better
- /// chance to optimise.
- isc::util::OutputBuffer& buffer_;
+ /// at least not now, and this reduces code size and gives compiler a
+ /// better chance to optimise.
+ isc::util::OutputBuffer* buffer_;
public:
///
/// \name Getter Methods
@@ -136,12 +142,12 @@ public:
/// This method works exactly same as the same method of the \c OutputBuffer
/// class; all notes for \c OutputBuffer apply.
const void* getData() const {
- return (buffer_.getData());
+ return (buffer_->getData());
}
/// \brief Return the length of data written in the internal buffer.
size_t getLength() const {
- return (buffer_.getLength());
+ return (buffer_->getLength());
}
/// \brief Return whether truncation has occurred while rendering.
@@ -175,6 +181,35 @@ public:
/// \name Setter Methods
///
//@{
+ /// \brief Set or reset a temporary output buffer.
+ ///
+ /// This method can be used for an application that manages an output
+ /// buffer separately from the message renderer and wants to keep reusing
+ /// the renderer. When the renderer is associated with the default buffer
+ /// and the given pointer is non NULL, the given buffer will be
+ /// (temporarily) used for subsequent message rendering; if the renderer
+ /// is associated with a temporary buffer and the given pointer is NULL,
+ /// the renderer will be reset with the default buffer. In the latter
+ /// case any additional resources (possibly specific to a derived renderer
+ /// class) will be cleared, but the temporary buffer is kept as the latest
+ /// state (which would normally store the rendering result).
+ ///
+ /// This method imposes some restrictions to prevent accidental misuse
+ /// that could cause disruption such as dereferencing an invalid object.
+ /// First, a temporary buffer must not be set when the associated buffer
+ /// is in use, that is, any data are stored in the buffer. Also, the
+ /// default buffer cannot be "reset"; when NULL is specified a temporary
+ /// buffer must have been set beforehand. If these conditions aren't met
+ /// an isc::InvalidParameter exception will be thrown. This method is
+ /// exception free otherwise.
+ ///
+ /// \throw isc::InvalidParameter A restrictions of the method usage isn't
+ /// met.
+ ///
+ /// \param buffer A pointer to a temporary output buffer or NULL for reset
+ /// it.
+ void setBuffer(isc::util::OutputBuffer* buffer);
+
/// \brief Mark the renderer to indicate truncation has occurred while
/// rendering.
///
@@ -209,7 +244,7 @@ public:
///
/// \param len The length of the gap to be inserted in bytes.
void skip(size_t len) {
- buffer_.skip(len);
+ buffer_->skip(len);
}
/// \brief Trim the specified length of data from the end of the internal
@@ -223,7 +258,7 @@ public:
///
/// \param len The length of data that should be trimmed.
void trim(size_t len) {
- buffer_.trim(len);
+ buffer_->trim(len);
}
/// \brief Clear the internal buffer and other internal resources.
@@ -236,7 +271,7 @@ public:
///
/// \param data The 8-bit integer to be written into the internal buffer.
void writeUint8(const uint8_t data) {
- buffer_.writeUint8(data);
+ buffer_->writeUint8(data);
}
/// \brief Write an unsigned 16-bit integer in host byte order into the
@@ -244,7 +279,7 @@ public:
///
/// \param data The 16-bit integer to be written into the buffer.
void writeUint16(uint16_t data) {
- buffer_.writeUint16(data);
+ buffer_->writeUint16(data);
}
/// \brief Write an unsigned 16-bit integer in host byte order at the
@@ -259,7 +294,7 @@ public:
/// \param data The 16-bit integer to be written into the internal buffer.
/// \param pos The beginning position in the buffer to write the data.
void writeUint16At(uint16_t data, size_t pos) {
- buffer_.writeUint16At(data, pos);
+ buffer_->writeUint16At(data, pos);
}
/// \brief Write an unsigned 32-bit integer in host byte order into the
@@ -267,7 +302,7 @@ public:
///
/// \param data The 32-bit integer to be written into the buffer.
void writeUint32(uint32_t data) {
- buffer_.writeUint32(data);
+ buffer_->writeUint32(data);
}
/// \brief Copy an arbitrary length of data into the internal buffer
@@ -278,7 +313,7 @@ public:
/// \param data A pointer to the data to be copied into the internal buffer.
/// \param len The length of the data in bytes.
void writeData(const void *data, size_t len) {
- buffer_.writeData(data, len);
+ buffer_->writeData(data, len);
}
/// \brief Write a \c Name object into the internal buffer in wire format,
@@ -316,10 +351,7 @@ public:
using AbstractMessageRenderer::CASE_SENSITIVE;
/// \brief Constructor from an output buffer.
- ///
- /// \param buffer An \c OutputBuffer object to which wire format data is
- /// written.
- MessageRenderer(isc::util::OutputBuffer& buffer);
+ MessageRenderer();
virtual ~MessageRenderer();
virtual bool isTruncated() const;
@@ -327,7 +359,17 @@ public:
virtual CompressMode getCompressMode() const;
virtual void setTruncated();
virtual void setLengthLimit(size_t len);
+
+ /// This implementation does not allow this call in the middle of
+ /// rendering (i.e. after at least one name is rendered) due to
+ /// restriction specific to the internal implementation. Such attempts
+ /// will result in an \c isc::InvalidParameter exception.
+ ///
+ /// This shouldn't be too restrictive in practice; there's no known
+ /// practical case for such a mixed compression policy in a single
+ /// message.
virtual void setCompressMode(CompressMode mode);
+
virtual void clear();
virtual void writeName(const Name& name, bool compress = true);
private:
diff --git a/src/lib/dns/name.cc b/src/lib/dns/name.cc
index 772417f..d642e97 100644
--- a/src/lib/dns/name.cc
+++ b/src/lib/dns/name.cc
@@ -23,11 +23,13 @@
#include <util/buffer.h>
#include <dns/exceptions.h>
#include <dns/name.h>
+#include <dns/name_internal.h>
#include <dns/messagerenderer.h>
using namespace std;
using namespace isc::util;
using isc::dns::NameComparisonResult;
+using namespace isc::dns::name::internal;
namespace isc {
namespace dns {
@@ -46,12 +48,12 @@ namespace {
/// we chose the naive but simple hardcoding approach.
///
/// These definitions are derived from BIND 9's libdns module.
-/// Note: it was not clear why the maptolower array was needed rather than
-/// using the standard tolower() function. It was perhaps due performance
-/// concern, but we were not sure. Even if it was performance reasons, we
-/// should carefully assess the effect via benchmarking to avoid the pitfall of
-/// premature optimization. We should revisit this point later.
-static const char digitvalue[256] = {
+/// Note: we could use the standard tolower() function instead of the
+/// maptolower array, but a benchmark indicated that the private array could
+/// improve the performance of message rendering (which internally uses the
+/// array heavily) about 27%. Since we want to achieve very good performance
+/// for message rendering in some cases, we'll keep using it.
+const char digitvalue[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 48
@@ -69,8 +71,11 @@ static const char digitvalue[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 256
};
+}
-static const unsigned char maptolower[] = {
+namespace name {
+namespace internal {
+const unsigned char maptolower[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
@@ -104,7 +109,8 @@ static const unsigned char maptolower[] = {
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};
-}
+} // end of internal
+} // end of name
namespace {
///
@@ -163,7 +169,8 @@ Name::Name(const std::string &namestring, bool downcase) {
//
if (c == '.') {
if (s != send) {
- isc_throw(EmptyLabel, "non terminating empty label");
+ isc_throw(EmptyLabel,
+ "non terminating empty label in " << namestring);
}
is_root = true;
} else if (c == '@' && s == send) {
@@ -191,7 +198,8 @@ Name::Name(const std::string &namestring, bool downcase) {
case ft_ordinary:
if (c == '.') {
if (count == 0) {
- isc_throw(EmptyLabel, "duplicate period");
+ isc_throw(EmptyLabel,
+ "duplicate period in " << namestring);
}
ndata.at(offsets.back()) = count;
offsets.push_back(ndata.size());
@@ -204,7 +212,8 @@ Name::Name(const std::string &namestring, bool downcase) {
state = ft_escape;
} else {
if (++count > MAX_LABELLEN) {
- isc_throw(TooLongLabel, "label is too long");
+ isc_throw(TooLongLabel,
+ "label is too long in " << namestring);
}
ndata.push_back(downcase ? maptolower[c] : c);
}
@@ -213,14 +222,16 @@ Name::Name(const std::string &namestring, bool downcase) {
if (c == '[') {
// This looks like a bitstring label, which was deprecated.
// Intentionally drop it.
- isc_throw(BadLabelType, "invalid label type");
+ isc_throw(BadLabelType,
+ "invalid label type in " << namestring);
}
state = ft_escape;
// FALLTHROUGH
case ft_escape:
if (!isdigit(c & 0xff)) {
if (++count > MAX_LABELLEN) {
- isc_throw(TooLongLabel, "label is too long");
+ isc_throw(TooLongLabel,
+ "label is too long in " << namestring);
}
ndata.push_back(downcase ? maptolower[c] : c);
state = ft_ordinary;
@@ -232,17 +243,22 @@ Name::Name(const std::string &namestring, bool downcase) {
// FALLTHROUGH
case ft_escdecimal:
if (!isdigit(c & 0xff)) {
- isc_throw(BadEscape, "mixture of escaped digit and non-digit");
+ isc_throw(BadEscape,
+ "mixture of escaped digit and non-digit in "
+ << namestring);
}
value *= 10;
value += digitvalue[c];
digits++;
if (digits == 3) {
if (value > 255) {
- isc_throw(BadEscape, "escaped decimal is too large");
+ isc_throw(BadEscape,
+ "escaped decimal is too large in "
+ << namestring);
}
if (++count > MAX_LABELLEN) {
- isc_throw(TooLongLabel, "label is too long");
+ isc_throw(TooLongLabel,
+ "label is too long in " << namestring);
}
ndata.push_back(downcase ? maptolower[value] : value);
state = ft_ordinary;
@@ -256,11 +272,14 @@ Name::Name(const std::string &namestring, bool downcase) {
if (!done) { // no trailing '.' was found.
if (ndata.size() == Name::MAX_WIRE) {
- isc_throw(TooLongName, "name is too long for termination");
+ isc_throw(TooLongName,
+ "name is too long for termination in " << namestring);
}
assert(s == send);
if (state != ft_ordinary && state != ft_at) {
- isc_throw(IncompleteName, "incomplete textual name");
+ isc_throw(IncompleteName,
+ "incomplete textual name in " <<
+ (namestring.empty() ? "<empty>" : namestring));
}
if (state == ft_ordinary) {
assert(count != 0);
diff --git a/src/lib/dns/name.h b/src/lib/dns/name.h
index 4ff7fe5..ef32f90 100644
--- a/src/lib/dns/name.h
+++ b/src/lib/dns/name.h
@@ -32,33 +32,42 @@ namespace dns {
class AbstractMessageRenderer;
///
+/// \brief Base class for name parser exceptions.
+///
+class NameParserException : public Exception {
+public:
+ NameParserException(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+///
/// \brief A standard DNS module exception that is thrown if the name parser
/// encounters an empty label in the middle of a name.
///
-class EmptyLabel : public Exception {
+class EmptyLabel : public NameParserException {
public:
EmptyLabel(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
+ NameParserException(file, line, what) {}
};
///
/// \brief A standard DNS module exception that is thrown if the name parser
/// encounters too long a name.
///
-class TooLongName : public Exception {
+class TooLongName : public NameParserException {
public:
TooLongName(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
+ NameParserException(file, line, what) {}
};
///
/// \brief A standard DNS module exception that is thrown if the name parser
/// encounters too long a label.
///
-class TooLongLabel : public Exception {
+class TooLongLabel : public NameParserException {
public:
TooLongLabel(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
+ NameParserException(file, line, what) {}
};
///
@@ -67,20 +76,20 @@ public:
/// applies to bitstring labels, which would begin with "\[". Incomplete cases
/// include an incomplete escaped sequence such as "\12".
///
-class BadLabelType : public Exception {
+class BadLabelType : public NameParserException {
public:
BadLabelType(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
+ NameParserException(file, line, what) {}
};
///
/// \brief A standard DNS module exception that is thrown if the name parser
/// fails to decode a "\"-escaped sequence.
///
-class BadEscape : public Exception {
+class BadEscape : public NameParserException {
public:
BadEscape(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
+ NameParserException(file, line, what) {}
};
///
@@ -90,10 +99,10 @@ public:
/// An attempt of constructing a name from an empty string will trigger this
/// exception.
///
-class IncompleteName : public Exception {
+class IncompleteName : public NameParserException {
public:
IncompleteName(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
+ NameParserException(file, line, what) {}
};
///
@@ -210,6 +219,11 @@ private:
/// names as a special case.
///
class Name {
+ // LabelSequences use knowledge about the internal data structure
+ // of this class for efficiency (they use the offsets_ vector and
+ // the ndata_ string)
+ friend class LabelSequence;
+
///
/// \name Constructors and Destructor
///
@@ -298,6 +312,7 @@ public:
}
return (ndata_[pos]);
}
+
/// \brief Gets the length of the <code>Name</code> in its wire format.
///
/// This method never throws an exception.
diff --git a/src/lib/dns/name_internal.h b/src/lib/dns/name_internal.h
new file mode 100644
index 0000000..e1eab8c
--- /dev/null
+++ b/src/lib/dns/name_internal.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __NAME_INTERNAL_H
+#define __NAME_INTERNAL_H 1
+
+// This is effectively a "private" namespace for the Name class implementation,
+// but exposed publicly so the definitions in it can be shared with other
+// modules of the library (as of its introduction, used by LabelSequence and
+// MessageRenderer). It's not expected to be used even by normal applications.
+// This header file is therefore not expected to be installed as part of the
+// library.
+//
+// Note: if it turns out that we need this shortcut for many other places
+// we may even want to make it expose to other BIND 10 modules, but for now
+// we'll keep it semi-private (note also that except for very performance
+// sensitive applications the standard std::tolower() function should be just
+// sufficient).
+namespace isc {
+namespace dns {
+namespace name {
+namespace internal {
+extern const unsigned char maptolower[];
+} // end of internal
+} // end of name
+} // end of dns
+} // end of isc
+#endif // __NAME_INTERNAL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/nsec3hash.cc b/src/lib/dns/nsec3hash.cc
new file mode 100644
index 0000000..159dff3
--- /dev/null
+++ b/src/lib/dns/nsec3hash.cc
@@ -0,0 +1,195 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <stdint.h>
+
+#include <cassert>
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include <boost/noncopyable.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/base32hex.h>
+#include <util/hash/sha1.h>
+
+#include <dns/name.h>
+#include <dns/nsec3hash.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::util::hash;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+
+/// \brief A derived class of \c NSEC3Hash that implements the standard hash
+/// calculation specified in RFC5155.
+///
+/// Currently the only pre-defined algorithm in the RFC is SHA1. So we don't
+/// over-generalize it at the moment, and rather hardocde it and assume that
+/// specific algorithm.
+///
+/// The implementation details are only open within this file, but to avoid
+/// an accidental error in this implementation we explicitly make it non
+/// copyable.
+class NSEC3HashRFC5155 : boost::noncopyable, public NSEC3Hash {
+private:
+ // This is the algorithm number for SHA1/NSEC3 as defined in RFC5155.
+ static const uint8_t NSEC3_HASH_SHA1 = 1;
+
+public:
+ NSEC3HashRFC5155(uint8_t algorithm, uint16_t iterations,
+ const vector<uint8_t>& salt) :
+ algorithm_(algorithm), iterations_(iterations),
+ salt_(salt), digest_(SHA1_HASHSIZE), obuf_(Name::MAX_WIRE)
+ {
+ if (algorithm_ != NSEC3_HASH_SHA1) {
+ isc_throw(UnknownNSEC3HashAlgorithm, "Unknown NSEC3 algorithm: " <<
+ static_cast<unsigned int>(algorithm_));
+ }
+ SHA1Reset(&sha1_ctx_);
+ }
+
+ virtual std::string calculate(const Name& name) const;
+
+ virtual bool match(const generic::NSEC3& nsec3) const;
+ virtual bool match(const generic::NSEC3PARAM& nsec3param) const;
+ bool match(uint8_t algorithm, uint16_t iterations,
+ const vector<uint8_t>& salt) const;
+
+private:
+ const uint8_t algorithm_;
+ const uint16_t iterations_;
+ const vector<uint8_t> salt_;
+
+ // The following members are placeholder of work place and don't hold
+ // any state over multiple calls so can be mutable without breaking
+ // constness.
+ mutable SHA1Context sha1_ctx_;
+ mutable vector<uint8_t> digest_;
+ mutable OutputBuffer obuf_;
+};
+
+inline void
+iterateSHA1(SHA1Context* ctx, const uint8_t* input, size_t inlength,
+ const uint8_t* salt, size_t saltlen,
+ uint8_t output[SHA1_HASHSIZE])
+{
+ SHA1Reset(ctx);
+ SHA1Input(ctx, input, inlength);
+ SHA1Input(ctx, salt, saltlen); // this works whether saltlen == or > 0
+ SHA1Result(ctx, output);
+}
+
+string
+NSEC3HashRFC5155::calculate(const Name& name) const {
+ // We first need to normalize the name by converting all upper case
+ // characters in the labels to lower ones.
+ obuf_.clear();
+ Name name_copy(name);
+ name_copy.downcase();
+ name_copy.toWire(obuf_);
+
+ const uint8_t saltlen = salt_.size();
+ const uint8_t* const salt = (saltlen > 0) ? &salt_[0] : NULL;
+ uint8_t* const digest = &digest_[0];
+ assert(digest_.size() == SHA1_HASHSIZE);
+
+ iterateSHA1(&sha1_ctx_, static_cast<const uint8_t*>(obuf_.getData()),
+ obuf_.getLength(), salt, saltlen, digest);
+ for (unsigned int n = 0; n < iterations_; ++n) {
+ iterateSHA1(&sha1_ctx_, digest, SHA1_HASHSIZE, salt, saltlen, digest);
+ }
+
+ return (encodeBase32Hex(digest_));
+}
+
+bool
+NSEC3HashRFC5155::match(uint8_t algorithm, uint16_t iterations,
+ const vector<uint8_t>& salt) const
+{
+ return (algorithm_ == algorithm && iterations_ == iterations &&
+ salt_.size() == salt.size() &&
+ (salt_.empty() || memcmp(&salt_[0], &salt[0], salt_.size()) == 0));
+}
+
+bool
+NSEC3HashRFC5155::match(const generic::NSEC3& nsec3) const {
+ return (match(nsec3.getHashalg(), nsec3.getIterations(),
+ nsec3.getSalt()));
+}
+
+bool
+NSEC3HashRFC5155::match(const generic::NSEC3PARAM& nsec3param) const {
+ return (match(nsec3param.getHashalg(), nsec3param.getIterations(),
+ nsec3param.getSalt()));
+}
+
+// A static pointer that refers to the currently usable creator.
+// Only get/setNSEC3HashCreator are expected to get access to this variable
+// directly.
+const NSEC3HashCreator* creator;
+
+// The accessor to the current creator. If it's not explicitly set or has
+// been reset from a customized one, the default creator will be used.
+const NSEC3HashCreator*
+getNSEC3HashCreator() {
+ static DefaultNSEC3HashCreator default_creator;
+ if (creator == NULL) {
+ creator = &default_creator;
+ }
+ return (creator);
+}
+
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+
+NSEC3Hash*
+NSEC3Hash::create(const generic::NSEC3PARAM& param) {
+ return (getNSEC3HashCreator()->create(param));
+}
+
+NSEC3Hash*
+NSEC3Hash::create(const generic::NSEC3& nsec3) {
+ return (getNSEC3HashCreator()->create(nsec3));
+}
+
+NSEC3Hash*
+DefaultNSEC3HashCreator::create(const generic::NSEC3PARAM& param) const {
+ return (new NSEC3HashRFC5155(param.getHashalg(), param.getIterations(),
+ param.getSalt()));
+}
+
+NSEC3Hash*
+DefaultNSEC3HashCreator::create(const generic::NSEC3& nsec3) const {
+ return (new NSEC3HashRFC5155(nsec3.getHashalg(), nsec3.getIterations(),
+ nsec3.getSalt()));
+}
+
+void
+setNSEC3HashCreator(const NSEC3HashCreator* new_creator) {
+ creator = new_creator;
+}
+
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/nsec3hash.h b/src/lib/dns/nsec3hash.h
new file mode 100644
index 0000000..2056708
--- /dev/null
+++ b/src/lib/dns/nsec3hash.h
@@ -0,0 +1,253 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __NSEC3HASH_H
+#define __NSEC3HASH_H 1
+
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dns {
+class Name;
+
+namespace rdata {
+namespace generic {
+class NSEC3;
+class NSEC3PARAM;
+}
+}
+
+/// \brief An exception that is thrown for when an \c NSEC3Hash object is
+/// constructed with an unknown hash algorithm.
+///
+/// A specific exception class is used so that the caller can selectively
+/// catch this exception, e.g., while loading a zone, and handle it
+/// accordingly.
+class UnknownNSEC3HashAlgorithm : public isc::Exception {
+public:
+ UnknownNSEC3HashAlgorithm(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief A calculator of NSEC3 hashes.
+///
+/// This is an abstract base class that defines a simple interface to
+/// calculating NSEC3 hash values as defined in RFC5155.
+///
+/// (Derived classes of) this class is designed to be "stateless" in that it
+/// basically doesn't hold mutable state once constructed, and hash
+/// calculation solely depends on the parameters given on construction and
+/// input to the \c calculate() method. In that sense this could be a
+/// single free function rather than a class, but we decided to provide the
+/// functionality as a class for two reasons: NSEC3 hash calculations would
+/// often take place more than one time in a single query or validation
+/// process, so it would be more efficient if we could hold some internal
+/// resources used for the calculation and reuse it over multiple calls to
+/// \c calculate() (a concrete implementation in this library actually does
+/// this); Second, we may want to customize the hash calculation logic for
+/// testing purposes or for other future extensions. For example, we may
+/// want to use a fake calculator for tests that returns pre-defined hash
+/// values (so a slight change to the test input wouldn't affect the test
+/// result). Using classes from this base would make it possible more
+/// transparently to the application.
+///
+/// A specific derived class instance must be created by the factory method,
+/// \c create().
+///
+/// There can be several ways to extend this class in future. Those include:
+/// - Allow customizing the factory method so the application change the
+/// behavior dynamically.
+/// - Allow to construct the class from a tuple of parameters, that is,
+/// integers for algorithm, iterations and flags, and opaque salt data.
+/// For example, we might want to use that version for validators.
+/// - Allow producing hash value as binary data
+/// - Allow updating NSEC3 parameters of a class object so we can still reuse
+/// the internal resources for different sets of parameters.
+class NSEC3Hash {
+protected:
+ /// \brief The default constructor.
+ ///
+ /// This is defined as protected to prevent this class from being directly
+ /// instantiated even if the class definition is modified (accidentally
+ /// or intentionally) to have no pure virtual methods.
+ NSEC3Hash() {}
+
+public:
+ /// \brief Factory method of NSECHash from NSEC3PARAM RDATA.
+ ///
+ /// The hash algorithm given via \c param must be known to the
+ /// implementation. Otherwise \c UnknownNSEC3HashAlgorithm exception
+ /// will be thrown.
+ ///
+ /// This method creates an \c NSEC3Hash object using \c new. The caller
+ /// is responsible for releasing it with \c delete that is compatible to
+ /// the one used in this library. In practice, the application would
+ /// generally need to store the returned pointer in some form of smart
+ /// pointer; otherwise the resulting code will be quite fragile against
+ /// exceptions (and in this case the application doesn't have to worry
+ /// about explicit \c delete).
+ ///
+ /// \throw UnknownNSEC3HashAlgorithm The specified algorithm in \c param
+ /// is unknown.
+ /// \throw std::bad_alloc Internal resource allocation failure.
+ ///
+ /// \param param NSEC3 parameters used for subsequent calculation.
+ /// \return A pointer to a concrete derived object of \c NSEC3Hash.
+ static NSEC3Hash* create(const rdata::generic::NSEC3PARAM& param);
+
+ /// \brief Factory method of NSECHash from NSEC3 RDATA.
+ ///
+ /// This is similar to the other version, but extracts the parameters
+ /// for hash calculation from an NSEC3 RDATA object.
+ static NSEC3Hash* create(const rdata::generic::NSEC3& nsec3);
+
+ /// \brief The destructor.
+ virtual ~NSEC3Hash() {}
+
+ /// \brief Calculate the NSEC3 hash.
+ ///
+ /// This method calculates the NSEC3 hash value for the given \c name
+ /// with the hash parameters (algorithm, iterations and salt) given at
+ /// construction, and returns the value as a base32hex-encoded string
+ /// (without containing any white spaces). All US-ASCII letters in the
+ /// string will be upper cased.
+ ///
+ /// \param name The domain name for which the hash value is to be
+ /// calculated.
+ /// \return Base32hex-encoded string of the hash value.
+ virtual std::string calculate(const Name& name) const = 0;
+
+ /// \brief Match given NSEC3 parameters with that of the hash.
+ ///
+ /// This method compares NSEC3 parameters used for hash calculation
+ /// in the object with those in the given NSEC3 RDATA, and return
+ /// true iff they completely match. In the current implementation
+ /// only the algorithm, iterations and salt are compared; the flags
+ /// are ignored (as they don't affect hash calculation per RFC5155).
+ ///
+ /// \throw None
+ ///
+ /// \param nsec3 An NSEC3 RDATA object whose hash parameters are to be
+ /// matched
+ /// \return true If the given parameters match the local ones; false
+ /// otherwise.
+ virtual bool match(const rdata::generic::NSEC3& nsec3) const = 0;
+
+ /// \brief Match given NSEC3PARAM parameters with that of the hash.
+ ///
+ /// This is similar to the other version, but extracts the parameters
+ /// to compare from an NSEC3PARAM RDATA object.
+ virtual bool match(const rdata::generic::NSEC3PARAM& nsec3param) const = 0;
+};
+
+/// \brief Factory class of NSEC3Hash.
+///
+/// This class is an abstract base class that provides the creation interfaces
+/// of \c NSEC3Hash objects. By defining a specific derived class of the
+/// creator, normally with a different specific class of \c NSEC3Hash,
+/// the application can use a customized implementation of \c NSEC3Hash
+/// without changing the library itself. The intended primary application of
+/// such customization is tests (it would be convenient for a test to produce
+/// a faked hash value regardless of the input so it doesn't have to identify
+/// a specific input value to produce a particular hash). Another possibility
+/// would be an experimental extension for a newer hash algorithm or
+/// implementation.
+///
+/// The two main methods named \c create() correspond to the static factory
+/// methods of \c NSEC3Hash of the same name.
+///
+/// By default, the library uses the \c DefaultNSEC3HashCreator creator.
+/// The \c setNSEC3HashCreator() function can be used to replace it with a
+/// user defined version. For such customization purposes as implementing
+/// experimental new hash algorithms, the application may internally want to
+/// use the \c DefaultNSEC3HashCreator in general cases while creating a
+/// customized type of \c NSEC3Hash object for that particular hash algorithm.
+///
+/// The creator objects are generally expected to be stateless; they will
+/// only encapsulate the factory logic. The \c create() methods are declared
+/// as const member functions for this reason. But if we see the need for
+/// having a customized creator that benefits from its own state in future,
+/// this condition can be loosened.
+class NSEC3HashCreator {
+protected:
+ /// \brief The default constructor.
+ ///
+ /// Make very sure this isn't directly instantiated by making it protected
+ /// even if this class is modified to lose all pure virtual methods.
+ NSEC3HashCreator() {}
+
+public:
+ /// \brief The destructor.
+ ///
+ /// This does nothing; defined only for allowing derived classes to
+ /// specialize its behavior.
+ virtual ~NSEC3HashCreator() {}
+
+ /// \brief Factory method of NSECHash from NSEC3PARAM RDATA.
+ ///
+ /// See
+ /// <code>NSEC3Hash::create(const rdata::generic::NSEC3PARAM& param)</code>
+ virtual NSEC3Hash* create(const rdata::generic::NSEC3PARAM& nsec3param)
+ const = 0;
+
+ /// \brief Factory method of NSECHash from NSEC3 RDATA.
+ ///
+ /// See
+ /// <code>NSEC3Hash::create(const rdata::generic::NSEC3& param)</code>
+ virtual NSEC3Hash* create(const rdata::generic::NSEC3& nsec3)
+ const = 0;
+};
+
+/// \brief The default NSEC3Hash creator.
+///
+/// This derived class implements the \c NSEC3HashCreator interfaces for
+/// the standard NSEC3 hash calculator as defined in RFC5155. The library
+/// will use this creator by default, so normal applications don't have to
+/// be aware of this class at all. This class is publicly visible for the
+/// convenience of special applications that want to customize the creator
+/// behavior for a particular type of parameters while preserving the default
+/// behavior for others.
+class DefaultNSEC3HashCreator : public NSEC3HashCreator {
+public:
+ virtual NSEC3Hash* create(const rdata::generic::NSEC3PARAM& param) const;
+ virtual NSEC3Hash* create(const rdata::generic::NSEC3& nsec3) const;
+};
+
+/// \brief The registrar of \c NSEC3HashCreator.
+///
+/// This function sets or resets the system-wide \c NSEC3HashCreator that
+/// is used by \c NSEC3Hash::create().
+///
+/// If \c new_creator is non NULL, the given creator object will replace
+/// any existing creator. If it's NULL, the default builtin creator will be
+/// used again from that point.
+///
+/// When \c new_creator is non NULL, the caller is responsible for keeping
+/// the referenced object valid as long as it can be used via
+/// \c NSEC3Hash::create().
+///
+/// \exception None
+/// \param new_creator A pointer to the new creator object or NULL.
+void setNSEC3HashCreator(const NSEC3HashCreator* new_creator);
+
+}
+}
+#endif // __NSEC3HASH_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/Makefile.am b/src/lib/dns/python/Makefile.am
index dd14991..2846659 100644
--- a/src/lib/dns/python/Makefile.am
+++ b/src/lib/dns/python/Makefile.am
@@ -7,6 +7,7 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
lib_LTLIBRARIES = libpydnspp.la
libpydnspp_la_SOURCES = pydnspp_common.cc pydnspp_common.h pydnspp_towire.h
libpydnspp_la_SOURCES += name_python.cc name_python.h
+libpydnspp_la_SOURCES += nsec3hash_python.cc nsec3hash_python.h
libpydnspp_la_SOURCES += rrset_python.cc rrset_python.h
libpydnspp_la_SOURCES += rrclass_python.cc rrclass_python.h
libpydnspp_la_SOURCES += rrtype_python.cc rrtype_python.h
@@ -41,10 +42,11 @@ pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
EXTRA_DIST = tsigerror_python_inc.cc
EXTRA_DIST += message_python_inc.cc
+EXTRA_DIST += nsec3hash_python_inc.cc
# Python prefers .so, while some OSes (specifically MacOS) use a different
# suffix for dynamic objects. -module is necessary to work this around.
-pydnspp_la_LDFLAGS += -module
+pydnspp_la_LDFLAGS += -module -avoid-version
pydnspp_la_LIBADD = $(top_builddir)/src/lib/dns/libdns++.la
pydnspp_la_LIBADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
pydnspp_la_LIBADD += libpydnspp.la
diff --git a/src/lib/dns/python/message_python.cc b/src/lib/dns/python/message_python.cc
index 48fff94..c7ad2ff 100644
--- a/src/lib/dns/python/message_python.cc
+++ b/src/lib/dns/python/message_python.cc
@@ -377,8 +377,9 @@ Message_getTSIGRecord(s_Message* self) {
if (tsig_record == NULL) {
Py_RETURN_NONE;
+ } else {
+ return (createTSIGRecordObject(*tsig_record));
}
- return (createTSIGRecordObject(*tsig_record));
} catch (const InvalidMessageOperation& ex) {
PyErr_SetString(po_InvalidMessageOperation, ex.what());
} catch (const exception& ex) {
@@ -433,7 +434,7 @@ private:
};
typedef SectionInserter<ConstQuestionPtr, Question> QuestionInserter;
-typedef SectionInserter<ConstRRsetPtr, RRset> RRsetInserter;
+typedef SectionInserter<ConstRRsetPtr, AbstractRRset> RRsetInserter;
// TODO use direct iterators for these? (or simply lists for now?)
PyObject*
diff --git a/src/lib/dns/python/messagerenderer_python.cc b/src/lib/dns/python/messagerenderer_python.cc
index bb89622..5561c12 100644
--- a/src/lib/dns/python/messagerenderer_python.cc
+++ b/src/lib/dns/python/messagerenderer_python.cc
@@ -36,7 +36,6 @@ namespace {
class s_MessageRenderer : public PyObject {
public:
s_MessageRenderer();
- isc::util::OutputBuffer* outputbuffer;
MessageRenderer* cppobj;
};
@@ -78,17 +77,14 @@ PyMethodDef MessageRenderer_methods[] = {
int
MessageRenderer_init(s_MessageRenderer* self) {
- self->outputbuffer = new OutputBuffer(4096);
- self->cppobj = new MessageRenderer(*self->outputbuffer);
+ self->cppobj = new MessageRenderer;
return (0);
}
void
MessageRenderer_destroy(s_MessageRenderer* self) {
delete self->cppobj;
- delete self->outputbuffer;
self->cppobj = NULL;
- self->outputbuffer = NULL;
Py_TYPE(self)->tp_free(self);
}
diff --git a/src/lib/dns/python/nsec3hash_python.cc b/src/lib/dns/python/nsec3hash_python.cc
new file mode 100644
index 0000000..01e8ae5
--- /dev/null
+++ b/src/lib/dns/python/nsec3hash_python.cc
@@ -0,0 +1,271 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/nsec3hash.h>
+#include <dns/rdataclass.h>
+
+#include "pydnspp_common.h"
+#include "name_python.h"
+#include "nsec3hash_python.h"
+#include "rdata_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::dns::python;
+
+// Import pydoc text
+#include "nsec3hash_python_inc.cc"
+
+// Trivial constructor.
+s_NSEC3Hash::s_NSEC3Hash() : cppobj(NULL) {
+}
+
+namespace {
+int
+NSEC3Hash_init(PyObject* po_self, PyObject* args, PyObject*) {
+ s_NSEC3Hash* const self = static_cast<s_NSEC3Hash*>(po_self);
+ try {
+ PyObject* po_rdata;
+ if (PyArg_ParseTuple(args, "O", &po_rdata)) {
+ if (!PyRdata_Check(po_rdata)) {
+ PyErr_Format(PyExc_TypeError,
+ "param must be an Rdata of type NSEC3/NSEC3PARAM,"
+ " not %.200s", po_rdata->ob_type->tp_name);
+ return (-1);
+ }
+ const Rdata& rdata = PyRdata_ToRdata(po_rdata);
+ const generic::NSEC3PARAM* nsec3param =
+ dynamic_cast<const generic::NSEC3PARAM*>(&rdata);
+ const generic::NSEC3* nsec3 =
+ dynamic_cast<const generic::NSEC3*>(&rdata);
+ if (nsec3param != NULL) {
+ self->cppobj = NSEC3Hash::create(*nsec3param);
+ } else if (nsec3 != NULL) {
+ self->cppobj = NSEC3Hash::create(*nsec3);
+ } else {
+ PyErr_Format(PyExc_TypeError,
+ "param must be an Rdata of type NSEC3/NSEC3HASH");
+ return (-1);
+ }
+ return (0);
+ }
+ } catch (const UnknownNSEC3HashAlgorithm& ex) {
+ PyErr_SetString(po_UnknownNSEC3HashAlgorithm, ex.what());
+ return (-1);
+ } catch (const exception& ex) {
+ const string ex_what = "Failed to construct NSEC3Hash object: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (-1);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (-1);
+ }
+
+ return (-1);
+}
+
+void
+NSEC3Hash_destroy(PyObject* po_self) {
+ s_NSEC3Hash* self = static_cast<s_NSEC3Hash*>(po_self);
+ delete self->cppobj;
+ self->cppobj = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+NSEC3Hash_calculate(PyObject* po_self, PyObject* args) {
+ s_NSEC3Hash* const self = static_cast<s_NSEC3Hash*>(po_self);
+
+ try {
+ PyObject* po_name;
+ if (PyArg_ParseTuple(args, "O", &po_name)) {
+ if (!PyName_Check(po_name)) {
+ PyErr_Format(PyExc_TypeError,
+ "name must be a Name, not %.200s",
+ po_name->ob_type->tp_name);
+ return (NULL);
+ }
+ const string hash =
+ self->cppobj->calculate(PyName_ToName(po_name));
+ return (Py_BuildValue("s", hash.c_str()));
+ }
+ } catch (const exception& ex) {
+ const string ex_what = "Unexpected failure in NSEC3Hash.calculate: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (NULL);
+ }
+
+ return (NULL);
+}
+
+PyObject*
+NSEC3Hash_match(PyObject* po_self, PyObject* args) {
+ s_NSEC3Hash* const self = static_cast<s_NSEC3Hash*>(po_self);
+
+ try {
+ PyObject* po_rdata;
+ if (PyArg_ParseTuple(args, "O", &po_rdata)) {
+ if (!PyRdata_Check(po_rdata)) {
+ PyErr_Format(PyExc_TypeError,
+ "param must be an Rdata of type NSEC3/NSEC3PARAM,"
+ " not %.200s", po_rdata->ob_type->tp_name);
+ return (NULL);
+ }
+ const Rdata& rdata = PyRdata_ToRdata(po_rdata);
+ const generic::NSEC3PARAM* nsec3param =
+ dynamic_cast<const generic::NSEC3PARAM*>(&rdata);
+ const generic::NSEC3* nsec3 =
+ dynamic_cast<const generic::NSEC3*>(&rdata);
+ bool matched;
+ if (nsec3param != NULL) {
+ matched = self->cppobj->match(*nsec3param);
+ } else if (nsec3 != NULL) {
+ matched = self->cppobj->match(*nsec3);
+ } else {
+ PyErr_Format(PyExc_TypeError,
+ "param must be an Rdata of type NSEC3/NSEC3HASH");
+ return (NULL);
+ }
+ PyObject* ret = matched ? Py_True : Py_False;
+ Py_INCREF(ret);
+ return (ret);
+ }
+ } catch (const exception& ex) {
+ const string ex_what = "Unexpected failure in NSEC3Hash.match: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (NULL);
+ }
+
+ return (NULL);
+}
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef NSEC3Hash_methods[] = {
+ { "calculate", NSEC3Hash_calculate, METH_VARARGS, NSEC3Hash_calculate_doc },
+ { "match", NSEC3Hash_match, METH_VARARGS, NSEC3Hash_match_doc },
+ { NULL, NULL, 0, NULL }
+};
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+//
+// Declaration of the custom exceptions
+// Initialization and addition of these go in pydnspp.cc
+//
+PyObject* po_UnknownNSEC3HashAlgorithm;
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_NSEC3Hash
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject nsec3hash_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "dns.NSEC3Hash",
+ sizeof(s_NSEC3Hash), // tp_basicsize
+ 0, // tp_itemsize
+ NSEC3Hash_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
+ NSEC3Hash_doc,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ NSEC3Hash_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
+ NSEC3Hash_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+// Module Initialization, all statics (nothing right now) are initialized here
+bool
+initModulePart_NSEC3Hash(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(&nsec3hash_type) < 0) {
+ return (false);
+ }
+ void* p = &nsec3hash_type;
+ if (PyModule_AddObject(mod, "NSEC3Hash", static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&nsec3hash_type);
+
+ return (true);
+}
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/nsec3hash_python.h b/src/lib/dns/python/nsec3hash_python.h
new file mode 100644
index 0000000..fa9b9b6
--- /dev/null
+++ b/src/lib/dns/python/nsec3hash_python.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_NSEC3HASH_H
+#define __PYTHON_NSEC3HASH_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class NSEC3Hash;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_NSEC3Hash : public PyObject {
+public:
+ s_NSEC3Hash();
+ NSEC3Hash* cppobj;
+};
+
+extern PyTypeObject nsec3hash_type;
+
+// Public exception object.
+extern PyObject* po_UnknownNSEC3HashAlgorithm;
+
+bool initModulePart_NSEC3Hash(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_NSEC3HASH_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/nsec3hash_python_inc.cc b/src/lib/dns/python/nsec3hash_python_inc.cc
new file mode 100644
index 0000000..7d0dfd4
--- /dev/null
+++ b/src/lib/dns/python/nsec3hash_python_inc.cc
@@ -0,0 +1,68 @@
+namespace {
+// Modifications
+// - removed intermediate details note, mainly for brevity
+// - removed std::bad_alloc
+const char* const NSEC3Hash_doc = "\
+A calculator of NSEC3 hashes.\n\
+\n\
+This is a simple class that encapsulates the algorithm of calculating\n\
+NSEC3 hash values as defined in RFC5155.\n\
+\n\
+NSEC3Hash(param)\n\
+\n\
+ Constructor.\n\
+\n\
+ The hash algorithm given via param must be known to the\n\
+ implementation. Otherwise UnknownNSEC3HashAlgorithm exception will\n\
+ be thrown.\n\
+\n\
+ Exceptions:\n\
+ UnknownNSEC3HashAlgorithm The specified algorithm in param is\n\
+ unknown.\n\
+\n\
+ Parameters:\n\
+ param NSEC3PARAM or NSEC3 Rdata object whose parameters are\n\
+ to be used for subsequent calculation.\n\
+\n\
+";
+
+const char* const NSEC3Hash_calculate_doc = "\
+calculate(name) -> string\n\
+\n\
+Calculate the NSEC3 hash.\n\
+\n\
+This method calculates the NSEC3 hash value for the given name with\n\
+the hash parameters (algorithm, iterations and salt) given at\n\
+construction, and returns the value in a base32hex-encoded string\n\
+(without containing any white spaces). All US-ASCII letters in the\n\
+string will be upper cased.\n\
+\n\
+Parameters:\n\
+ name The domain name for which the hash value is to be\n\
+ calculated.\n\
+\n\
+Return Value(s): Base32hex-encoded string of the hash value.\n\
+";
+
+const char* const NSEC3Hash_match_doc = "\
+match(rdata) -> bool\n \
+\n\
+Match given NSEC3 or NSEC3PARAM parameters with that of the hash.\n\
+\n\
+This method compares NSEC3 parameters used for hash calculation in the\n\
+object with those in the given RDATA, and return true iff they\n\
+completely match. In the current implementation only the algorithm,\n\
+iterations and salt are compared; the flags are ignored (as they don't\n\
+affect hash calculation per RFC5155).\n\
+\n\
+Exceptions:\n\
+ None\n\
+\n\
+Parameters:\n\
+ rdata An NSEC3 or NSEC3PARAM Rdata object whose hash parameters\n\
+ are to be matched\n\
+\n\
+Return Value(s): true If the given parameters match the local ones;\n\
+false otherwise.\n\
+";
+} // unnamed namespace
diff --git a/src/lib/dns/python/pydnspp.cc b/src/lib/dns/python/pydnspp.cc
index 212141c..64e3cae 100644
--- a/src/lib/dns/python/pydnspp.cc
+++ b/src/lib/dns/python/pydnspp.cc
@@ -39,6 +39,7 @@
#include "message_python.h"
#include "messagerenderer_python.h"
#include "name_python.h"
+#include "nsec3hash_python.h"
#include "opcode_python.h"
#include "pydnspp_common.h"
#include "pydnspp_towire.h"
@@ -164,6 +165,10 @@ initModulePart_Message(PyObject* mod) {
PyErr_NewException("pydnspp.DNSMessageBADVERS", NULL, NULL);
PyObjectContainer(po_DNSMessageBADVERS).installToModule(
mod, "DNSMessageBADVERS");
+ po_UnknownNSEC3HashAlgorithm =
+ PyErr_NewException("pydnspp.UnknownNSEC3HashAlgorithm", NULL, NULL);
+ PyObjectContainer(po_UnknownNSEC3HashAlgorithm).installToModule(
+ mod, "UnknownNSEC3HashAlgorithm");
} catch (const std::exception& ex) {
const std::string ex_what =
"Unexpected failure in Message initialization: " +
@@ -777,6 +782,10 @@ PyInit_pydnspp(void) {
return (NULL);
}
+ if (!initModulePart_NSEC3Hash(mod)) {
+ return (NULL);
+ }
+
if (!initModulePart_RRClass(mod)) {
return (NULL);
}
diff --git a/src/lib/dns/python/rrset_python.cc b/src/lib/dns/python/rrset_python.cc
index 77d520b..2992522 100644
--- a/src/lib/dns/python/rrset_python.cc
+++ b/src/lib/dns/python/rrset_python.cc
@@ -415,7 +415,7 @@ PyTypeObject rrset_type = {
};
PyObject*
-createRRsetObject(const RRset& source) {
+createRRsetObject(const AbstractRRset& source) {
// RRsets are noncopyable, so as a workaround we recreate a new one
// and copy over all content
@@ -450,7 +450,7 @@ PyRRset_Check(PyObject* obj) {
return (PyObject_TypeCheck(obj, &rrset_type));
}
-RRset&
+AbstractRRset&
PyRRset_ToRRset(PyObject* rrset_obj) {
s_RRset* rrset = static_cast<s_RRset*>(rrset_obj);
return (*rrset->cppobj);
diff --git a/src/lib/dns/python/rrset_python.h b/src/lib/dns/python/rrset_python.h
index 4268678..2435397 100644
--- a/src/lib/dns/python/rrset_python.h
+++ b/src/lib/dns/python/rrset_python.h
@@ -36,7 +36,7 @@ extern PyTypeObject rrset_type;
/// returns a NULL pointer).
/// This function is expected to be called within a try block
/// followed by necessary setup for python exception.
-PyObject* createRRsetObject(const RRset& source);
+PyObject* createRRsetObject(const AbstractRRset& source);
/// \brief Checks if the given python object is a RRset object
///
@@ -56,7 +56,7 @@ bool PyRRset_Check(PyObject* obj);
/// may be destroyed, the caller must copy it itself.
///
/// \param rrset_obj The rrset object to convert
-RRset& PyRRset_ToRRset(PyObject* rrset_obj);
+AbstractRRset& PyRRset_ToRRset(PyObject* rrset_obj);
/// \brief Returns the shared_ptr of the RRset object contained within the
/// given Python object.
diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am
index 3338727..4b0ea9f 100644
--- a/src/lib/dns/python/tests/Makefile.am
+++ b/src/lib/dns/python/tests/Makefile.am
@@ -3,6 +3,7 @@ PYTESTS = edns_python_test.py
PYTESTS += message_python_test.py
PYTESTS += messagerenderer_python_test.py
PYTESTS += name_python_test.py
+PYTESTS += nsec3hash_python_test.py
PYTESTS += question_python_test.py
PYTESTS += opcode_python_test.py
PYTESTS += rcode_python_test.py
diff --git a/src/lib/dns/python/tests/nsec3hash_python_test.py b/src/lib/dns/python/tests/nsec3hash_python_test.py
new file mode 100644
index 0000000..1a247d0
--- /dev/null
+++ b/src/lib/dns/python/tests/nsec3hash_python_test.py
@@ -0,0 +1,128 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import unittest
+from pydnspp import *
+
+class NSEC3HashTest(unittest.TestCase):
+ '''These tests are mostly straightforward conversion of C++ tests
+ except for python specific type checks.
+
+ '''
+
+ def setUp(self):
+ self.nsec3_common = "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG"
+ self.test_hash = NSEC3Hash(Rdata(RRType.NSEC3PARAM(), RRClass.IN(),
+ "1 0 12 aabbccdd"))
+ self.test_hash_nsec3 = NSEC3Hash(Rdata(RRType.NSEC3(), RRClass.IN(),
+ "1 0 12 aabbccdd " +
+ self.nsec3_common))
+ def test_bad_construct(self):
+ # missing parameter
+ self.assertRaises(TypeError, NSEC3Hash)
+
+ # invalid type of argument
+ self.assertRaises(TypeError, NSEC3Hash, "1 0 12 aabbccdd")
+
+ # additional parameter
+ self.assertRaises(TypeError, NSEC3Hash, Rdata(RRType.NSEC3PARAM(),
+ RRClass.IN(),
+ "1 0 12 aabbccdd"), 1)
+
+ # Invaid type of RDATA
+ self.assertRaises(TypeError, NSEC3Hash, Rdata(RRType.A(), RRClass.IN(),
+ "192.0.2.1"))
+
+ def test_unknown_algorithm(self):
+ self.assertRaises(UnknownNSEC3HashAlgorithm, NSEC3Hash,
+ Rdata(RRType.NSEC3PARAM(), RRClass.IN(),
+ "2 0 12 aabbccdd"))
+ self.assertRaises(UnknownNSEC3HashAlgorithm, NSEC3Hash,
+ Rdata(RRType.NSEC3(), RRClass.IN(),
+ "2 0 12 aabbccdd " + self.nsec3_common))
+
+ def calculate_check(self, hash):
+ # A couple of normal cases from the RFC5155 example.
+ self.assertEqual("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ hash.calculate(Name("example")))
+ self.assertEqual("35MTHGPGCU1QG68FAB165KLNSNK3DPVL",
+ hash.calculate(Name("a.example")))
+
+ # Check case-insensitiveness
+ self.assertEqual("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ hash.calculate(Name("EXAMPLE")))
+
+
+ def test_calculate(self):
+ self.calculate_check(self.test_hash)
+ self.calculate_check(self.test_hash_nsec3)
+
+ # Using unusually large iterations, something larger than the 8-bit
+ #range. (expected hash value generated by BIND 9's dnssec-signzone)
+ self.test_hash = NSEC3Hash(Rdata(RRType.NSEC3PARAM(),
+ RRClass.IN(), "1 0 256 AABBCCDD"))
+ self.assertEqual("COG6A52MJ96MNMV3QUCAGGCO0RHCC2Q3",
+ self.test_hash.calculate(Name("example.org")))
+
+ # Some boundary cases: 0-iteration and empty salt. Borrowed from the
+ # .com zone data.
+ self.test_hash = NSEC3Hash(Rdata(RRType.NSEC3PARAM(),
+ RRClass.IN(),"1 0 0 -"))
+ self.assertEqual("CK0POJMG874LJREF7EFN8430QVIT8BSM",
+ self.test_hash.calculate(Name("com")))
+
+ def test_calculate_badparam(self):
+ self.assertRaises(TypeError, self.test_hash.calculate, "example")
+ self.assertRaises(TypeError, self.test_hash.calculate)
+ self.assertRaises(TypeError, self.test_hash.calculate, Name("."), 1)
+
+ def check_match(self, hash, rrtype, postfix):
+ # If all parameters match, it's considered to be matched.
+ self.assertTrue(hash.match(Rdata(rrtype, RRClass.IN(),
+ "1 0 12 aabbccdd" + postfix)))
+ # Algorithm doesn't match
+ self.assertFalse(hash.match(Rdata(rrtype, RRClass.IN(),
+ "2 0 12 aabbccdd" + postfix)))
+ # Iterations doesn't match
+ self.assertFalse(hash.match(Rdata(rrtype, RRClass.IN(),
+ "1 0 1 aabbccdd" + postfix)))
+ # Salt doesn't match
+ self.assertFalse(hash.match(Rdata(rrtype, RRClass.IN(),
+ "1 0 12 aabbccde" + postfix)))
+ # Salt doesn't match: the other has an empty salt
+ self.assertFalse(hash.match(Rdata(rrtype, RRClass.IN(),
+ "1 0 12 -" + postfix)))
+ # Flag doesn't matter
+ self.assertTrue(hash.match(Rdata(rrtype, RRClass.IN(),
+ "1 1 12 aabbccdd" + postfix)))
+
+ def test_match(self):
+ self.check_match(self.test_hash, RRType.NSEC3(),
+ " " + self.nsec3_common)
+ self.check_match(self.test_hash_nsec3, RRType.NSEC3(),
+ " " + self.nsec3_common)
+ self.check_match(self.test_hash, RRType.NSEC3PARAM(), "")
+ self.check_match(self.test_hash_nsec3, RRType.NSEC3PARAM(), "")
+
+ # bad parameter checks
+ self.assertRaises(TypeError, self.test_hash.match, 1)
+ self.assertRaises(TypeError, self.test_hash.match,
+ Rdata(RRType.NSEC3(), RRClass.IN(),
+ "1 0 12 aabbccdd " + self.nsec3_common), 1)
+ self.assertRaises(TypeError, self.test_hash.match,
+ Rdata(RRType.A(), RRClass.IN(), "192.0.2.1"))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/lib/dns/rdata/generic/detail/nsec3param_common.cc b/src/lib/dns/rdata/generic/detail/nsec3param_common.cc
new file mode 100644
index 0000000..a7a0bb4
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/nsec3param_common.cc
@@ -0,0 +1,130 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+
+#include <util/encode/hex.h>
+#include <util/buffer.h>
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/rdata/generic/detail/nsec3param_common.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <sstream>
+#include <vector>
+#include <stdint.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+namespace nsec3 {
+
+ParseNSEC3ParamResult
+parseNSEC3ParamText(const char* const rrtype_name,
+ const string& rdata_str, istringstream& iss,
+ vector<uint8_t>& salt)
+{
+ unsigned int hashalg, flags, iterations;
+ string iterations_str, salthex;
+
+ iss >> hashalg >> flags >> iterations_str >> salthex;
+ if (iss.bad() || iss.fail()) {
+ isc_throw(InvalidRdataText, "Invalid " << rrtype_name <<
+ " text: " << rdata_str);
+ }
+ if (hashalg > 0xff) {
+ isc_throw(InvalidRdataText, rrtype_name <<
+ " hash algorithm out of range: " << hashalg);
+ }
+ if (flags > 0xff) {
+ isc_throw(InvalidRdataText, rrtype_name << " 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 " << rrtype_name <<
+ " iteration: " << iterations_str);
+ }
+ if (iterations > 0xffff) {
+ isc_throw(InvalidRdataText, rrtype_name <<
+ " iterations out of range: " <<
+ iterations);
+ }
+
+ // Salt is up to 255 bytes, and space is not allowed in the HEX encoding,
+ // so the encoded string cannot be longer than the double of max length
+ // of the actual salt.
+ if (salthex.size() > 255 * 2) {
+ isc_throw(InvalidRdataText, rrtype_name << " salt is too long: "
+ << salthex.size() << " (encoded) bytes");
+ }
+ if (salthex != "-") { // "-" means a 0-length salt
+ decodeHex(salthex, salt);
+ }
+
+ return (ParseNSEC3ParamResult(hashalg, flags, iterations));
+}
+
+ParseNSEC3ParamResult
+parseNSEC3ParamWire(const char* const rrtype_name,
+ InputBuffer& buffer,
+ size_t& rdata_len, std::vector<uint8_t>& salt)
+{
+ // NSEC3/NSEC3PARAM RR must have at least 5 octets:
+ // hash algorithm(1), flags(1), iteration(2), saltlen(1)
+ if (rdata_len < 5) {
+ isc_throw(DNSMessageFORMERR, rrtype_name << " too short, length: "
+ << 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(DNSMessageFORMERR, rrtype_name <<
+ " salt length is too large: " <<
+ static_cast<unsigned int>(saltlen));
+ }
+
+ salt.resize(saltlen);
+ if (saltlen > 0) {
+ buffer.readData(&salt[0], saltlen);
+ rdata_len -= saltlen;
+ }
+
+ return (ParseNSEC3ParamResult(hashalg, flags, iterations));
+}
+
+} // end of nsec3
+} // end of detail
+} // end of generic
+} // end of rdata
+} // end of dns
+} // end of isc
+
diff --git a/src/lib/dns/rdata/generic/detail/nsec3param_common.h b/src/lib/dns/rdata/generic/detail/nsec3param_common.h
new file mode 100644
index 0000000..515777b
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/nsec3param_common.h
@@ -0,0 +1,134 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __NSEC3PARAM_COMMON_H
+#define __NSEC3PARAM_COMMON_H 1
+
+#include <util/buffer.h>
+
+#include <stdint.h>
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+namespace nsec3 {
+
+/// \file
+///
+/// This helper module provides some utilities that handle NSEC3 and
+/// NSEC3PARAM RDATA. They share the first few fields, and some operations
+/// on these fields are sufficiently complicated, so it would make sense to
+/// consolidate the processing logic into a single implementation module.
+///
+/// The functions defined here are essentially private and are only expected
+/// to be called from the \c NSEC3 and \c NSEC3PARAM class implementations.
+
+/// \brief Result values of the utilities.
+///
+/// This structure encapsulates a tuple of NSEC3/NSEC3PARAM algorithm,
+/// flags and iterations field values. This is used as the return value
+/// of the utility functions defined in this module so the caller can
+/// use it for constructing the corresponding RDATA.
+struct ParseNSEC3ParamResult {
+ ParseNSEC3ParamResult(uint8_t param_algorithm, uint8_t param_flags,
+ uint16_t param_iterations) :
+ algorithm(param_algorithm), flags(param_flags),
+ iterations(param_iterations)
+ {}
+ const uint8_t algorithm;
+ const uint8_t flags;
+ const uint16_t iterations;
+};
+
+/// \brief Convert textual representation of NSEC3 parameters.
+///
+/// This function takes an input string stream that consists of a complete
+/// textual representation of an NSEC3 or NSEC3PARAM RDATA and parses it
+/// extracting the hash algorithm, flags, iterations, and salt fields.
+///
+/// The first three fields are returned as the return value of this function.
+/// The salt will be stored in the given vector. The vector is expected
+/// to be empty, but if not, the existing content will be overridden.
+///
+/// On successful return the given input stream will reach the end of the
+/// salt field.
+///
+/// \exception isc::BadValue The salt is not a valid hex string.
+/// \exception InvalidRdataText The given string is otherwise invalid for
+/// NSEC3 or NSEC3PARAM fields.
+///
+/// \param rrtype_name Either "NSEC3" or "NSEC3PARAM"; used as part of
+/// exception messages.
+/// \param rdata_str A complete textual string of the RDATA; used as part of
+/// exception messages.
+/// \param iss Input stream that consists of a complete textual string of
+/// the RDATA.
+/// \param salt A placeholder for the salt field value of the RDATA.
+/// Expected to be empty, but it's not checked (and will be overridden).
+///
+/// \return The hash algorithm, flags, iterations in the form of
+/// ParseNSEC3ParamResult.
+ParseNSEC3ParamResult parseNSEC3ParamText(const char* const rrtype_name,
+ const std::string& rdata_str,
+ std::istringstream& iss,
+ std::vector<uint8_t>& salt);
+
+/// \brief Extract NSEC3 parameters from wire-format data.
+///
+/// This function takes an input buffer that stores wire-format NSEC3 or
+/// NSEC3PARAM RDATA and parses it extracting the hash algorithm, flags,
+/// iterations, and salt fields.
+///
+/// The first three fields are returned as the return value of this function.
+/// The salt will be stored in the given vector. The vector is expected
+/// to be empty, but if not, the existing content will be overridden.
+///
+/// On successful return the input buffer will point to the end of the
+/// salt field; rdata_len will be the length of the rest of RDATA
+/// (in the case of a valid NSEC3PARAM, it should be 0).
+///
+/// \exception DNSMessageFORMERR The wire data is invalid.
+///
+/// \param rrtype_name Either "NSEC3" or "NSEC3PARAM"; used as part of
+/// exception messages.
+/// \param buffer An input buffer that stores wire-format RDATA. It must
+/// point to the beginning of the data.
+/// \param rdata_len The total length of the RDATA.
+/// \param salt A placeholder for the salt field value of the RDATA.
+/// Expected to be empty, but it's not checked (and will be overridden).
+///
+/// \return The hash algorithm, flags, iterations in the form of
+/// ParseNSEC3ParamResult.
+ParseNSEC3ParamResult parseNSEC3ParamWire(const char* const rrtype_name,
+ isc::util::InputBuffer& buffer,
+ size_t& rdata_len,
+ std::vector<uint8_t>& salt);
+}
+}
+}
+}
+}
+}
+
+#endif // __NSEC3PARAM_COMMON_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc b/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
index a72058f..bb48705 100644
--- a/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
+++ b/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
@@ -12,11 +12,17 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <stdint.h>
-
-#include <vector>
+#include <exceptions/exceptions.h>
#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/rrtype.h>
+
+#include <cassert>
+#include <sstream>
+#include <vector>
+#include <cstring>
+#include <stdint.h>
using namespace std;
@@ -70,6 +76,78 @@ checkRRTypeBitmaps(const char* const rrtype_name,
first = false;
}
}
+
+void
+buildBitmapsFromText(const char* const rrtype_name,
+ istringstream& iss, vector<uint8_t>& typebits)
+{
+ uint8_t bitmap[8 * 1024]; // 64k bits
+ memset(bitmap, 0, sizeof(bitmap));
+
+ do {
+ string type;
+ iss >> type;
+ if (iss.bad() || iss.fail()) {
+ isc_throw(InvalidRdataText, "Unexpected input for "
+ << rrtype_name << " bitmap");
+ }
+ try {
+ const int code = RRType(type).getCode();
+ bitmap[code / 8] |= (0x80 >> (code % 8));
+ } catch (const InvalidRRType&) {
+ isc_throw(InvalidRdataText, "Invalid RRtype in "
+ << rrtype_name << " bitmap: " << type);
+ }
+ } while (!iss.eof());
+
+ for (int window = 0; window < 256; ++window) {
+ int octet;
+ for (octet = 31; octet >= 0; octet--) {
+ if (bitmap[window * 32 + octet] != 0) {
+ break;
+ }
+ }
+ if (octet < 0) {
+ continue;
+ }
+ typebits.push_back(window);
+ typebits.push_back(octet + 1);
+ for (int i = 0; i <= octet; ++i) {
+ typebits.push_back(bitmap[window * 32 + i]);
+ }
+ }
+}
+
+void
+bitmapsToText(const vector<uint8_t>& typebits, ostringstream& oss) {
+ // In the following loop we use string::at() rather than operator[].
+ // Since the index calculation is a bit complicated, it will be safer
+ // and easier to find a bug (if any). Note that this conversion method
+ // is generally not expected to be very efficient, so the slight overhead
+ // of at() should be acceptable.
+ const size_t typebits_len = typebits.size();
+ size_t len = 0;
+ for (size_t i = 0; i < typebits_len; i += len) {
+ assert(i + 2 <= typebits.size());
+ const unsigned int block = typebits.at(i);
+ len = typebits.at(i + 1);
+ assert(len > 0 && len <= 32);
+ i += 2;
+ for (size_t j = 0; j < len; ++j) {
+ if (typebits.at(i + j) == 0) {
+ continue;
+ }
+ for (size_t k = 0; k < 8; ++k) {
+ if ((typebits.at(i + j) & (0x80 >> k)) == 0) {
+ continue;
+ }
+ const unsigned int t = block * 256 + j * 8 + k;
+ assert(t < 65536);
+ oss << " " << RRType(t);
+ }
+ }
+ }
+}
}
}
}
diff --git a/src/lib/dns/rdata/generic/detail/nsec_bitmap.h b/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
index 6431e10..85cae2e 100644
--- a/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
+++ b/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
@@ -12,8 +12,12 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#ifndef __NSECBITMAP_H
+#define __NSECBITMAP_H 1
+
#include <stdint.h>
+#include <sstream>
#include <vector>
namespace isc {
@@ -22,14 +26,22 @@ namespace rdata {
namespace generic {
namespace detail {
namespace nsec {
-/// Check if a given "type bitmap" for NSEC/NSEC3 is valid.
+
+/// \file
+///
+/// This helper module provides some utilities that handle NSEC and NSEC3
+/// type bitmaps. The format and processing of the type bitmaps are generally
+/// the same for these two RRs, so it would make sense to consolidate
+/// the processing logic into a single implementation module.
+///
+/// The functions defined here are essentially private and are only expected
+/// to be called from the \c NSEC and \c NSEC3 class implementations.
+
+/// \brief Check if a given "type bitmap" for NSEC/NSEC3 is valid.
///
-/// This helper function checks given wire format data (stored in a
+/// This 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.
+/// according to RFC4034 and RFC5155.
///
/// \exception DNSMessageFORMERR The bitmap is not valid.
///
@@ -39,6 +51,48 @@ namespace nsec {
/// is the total length of the bitmaps.
void checkRRTypeBitmaps(const char* const rrtype_name,
const std::vector<uint8_t>& typebits);
+
+/// \brief Convert textual sequence of RR types into type bitmaps.
+///
+/// This function extracts a sequence of strings, converts each sequence
+/// into an RR type, and builds NSEC/NSEC3 type bitmaps with the corresponding
+/// bits for the extracted types being on. The resulting bitmaps (which are
+/// in the wire format according to RFC4034 and RFC5155) are stored in the
+/// given vector. This function expects the given string stream ends with
+/// the sequence.
+///
+/// \exception InvalidRdataText The given input stream does not meet the
+/// assumption (e.g. including invalid form of RR type, not ending with
+/// an RR type string).
+///
+/// \param rrtype_name Either "NSEC" or "NSEC3"; used as part of exception
+/// messages.
+/// \param iss Input stream that consists of a complete sequence of textual
+/// RR types for which the corresponding bits are set.
+/// \param typebits A placeholder for the resulting bitmaps. Expected to be
+/// empty, but it's not checked.
+void buildBitmapsFromText(const char* const rrtype_name,
+ std::istringstream& iss,
+ std::vector<uint8_t>& typebits);
+
+/// \brief Convert type bitmaps to textual sequence of RR types.
+///
+/// This function converts wire-format data of type bitmaps for NSEC/NSEC3
+/// into a sequence of corresponding RR type strings, and inserts them
+/// into the given output stream with separating them by a single space
+/// character.
+///
+/// This function assumes the given bitmaps are valid in terms of RFC4034
+/// and RFC5155 (in practice, it assumes it's from a validly constructed
+/// NSEC or NSEC3 object); if it detects a format error, it aborts the
+/// program with assert().
+///
+/// \param typebits The type bitmaps in wire format. The size of vector
+/// is the total length of the bitmaps.
+/// \param oss The output stream to which the converted RR type sequence
+/// are to be inserted.
+void bitmapsToText(const std::vector<uint8_t>& typebits,
+ std::ostringstream& oss);
}
}
}
@@ -46,6 +100,8 @@ void checkRRTypeBitmaps(const char* const rrtype_name,
}
}
+#endif // __NSECBITMAP_H
+
// Local Variables:
// mode: c++
// End:
diff --git a/src/lib/dns/rdata/generic/nsec3_50.cc b/src/lib/dns/rdata/generic/nsec3_50.cc
index 092695c..b569d91 100644
--- a/src/lib/dns/rdata/generic/nsec3_50.cc
+++ b/src/lib/dns/rdata/generic/nsec3_50.cc
@@ -17,6 +17,7 @@
#include <string>
#include <sstream>
#include <vector>
+#include <cassert>
#include <boost/lexical_cast.hpp>
@@ -32,12 +33,14 @@
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/rdata/generic/detail/nsec_bitmap.h>
+#include <dns/rdata/generic/detail/nsec3param_common.h>
#include <stdio.h>
#include <time.h>
using namespace std;
using namespace isc::dns::rdata::generic::detail::nsec;
+using namespace isc::dns::rdata::generic::detail::nsec3;
using namespace isc::util::encode;
using namespace isc::util;
@@ -53,54 +56,32 @@ struct NSEC3Impl {
salt_(salt), next_(next), typebits_(typebits)
{}
- uint8_t hashalg_;
- uint8_t flags_;
- uint16_t iterations_;
- vector<uint8_t> salt_;
- vector<uint8_t> next_;
- vector<uint8_t> typebits_;
+ const uint8_t hashalg_;
+ const uint8_t flags_;
+ const uint16_t iterations_;
+ const vector<uint8_t> salt_;
+ const vector<uint8_t> next_;
+ const vector<uint8_t> typebits_;
};
NSEC3::NSEC3(const string& nsec3_str) :
impl_(NULL)
{
istringstream iss(nsec3_str);
- unsigned int hashalg, flags, iterations;
- string iterations_str, salthex, nexthash;
+ vector<uint8_t> salt;
+ const ParseNSEC3ParamResult params =
+ parseNSEC3ParamText("NSEC3", nsec3_str, iss, salt);
- iss >> hashalg >> flags >> iterations_str >> salthex >> nexthash;
+ // Extract Next hash. It must be an unpadded base32hex string.
+ string nexthash;
+ iss >> nexthash;
if (iss.bad() || iss.fail()) {
isc_throw(InvalidRdataText, "Invalid NSEC3 text: " << nsec3_str);
}
- if (hashalg > 0xff) {
- isc_throw(InvalidRdataText,
- "NSEC3 hash algorithm out of range: " << hashalg);
- }
- if (flags > 0xff) {
- 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: " <<
- iterations);
- }
-
- vector<uint8_t> 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");
+ assert(!nexthash.empty());
+ if (*nexthash.rbegin() == '=') {
+ isc_throw(InvalidRdataText, "NSEC3 hash has padding: " << nsec3_str);
}
-
vector<uint8_t> next;
decodeBase32Hex(nexthash, next);
if (next.size() > 255) {
@@ -110,70 +91,28 @@ NSEC3::NSEC3(const string& nsec3_str) :
// For NSEC3 empty bitmap is possible and allowed.
if (iss.eof()) {
- impl_ = new NSEC3Impl(hashalg, flags, iterations, salt, next,
+ impl_ = new NSEC3Impl(params.algorithm, params.flags,
+ params.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;
- iss >> type;
- if (type.length() != 0) {
- try {
- const int code = RRType(type).getCode();
- bitmap[code / 8] |= (0x80 >> (code % 8));
- } catch (...) {
- isc_throw(InvalidRdataText, "Invalid RRtype in NSEC3");
- }
- }
- } while (!iss.eof());
-
- for (int window = 0; window < 256; window++) {
- int octet;
- for (octet = 31; octet >= 0; octet--) {
- if (bitmap[window * 32 + octet] != 0) {
- break;
- }
- }
- if (octet < 0)
- continue;
- typebits.push_back(window);
- typebits.push_back(octet + 1);
- for (int i = 0; i <= octet; i++) {
- typebits.push_back(bitmap[window * 32 + i]);
- }
- }
+ buildBitmapsFromText("NSEC3", iss, typebits);
- impl_ = new NSEC3Impl(hashalg, flags, iterations, salt, next, typebits);
+ impl_ = new NSEC3Impl(params.algorithm, params.flags, params.iterations,
+ salt, next, typebits);
}
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(DNSMessageFORMERR, "NSEC3 too short, length: " << 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(DNSMessageFORMERR, "NSEC3 salt length is too large: " <<
- static_cast<unsigned int>(saltlen));
- }
+ vector<uint8_t> salt;
+ const ParseNSEC3ParamResult params =
+ parseNSEC3ParamWire("NSEC3", buffer, rdata_len, salt);
- vector<uint8_t> salt(saltlen);
- if (saltlen > 0) {
- buffer.readData(&salt[0], saltlen);
- rdata_len -= saltlen;
+ if (rdata_len < 1) {
+ isc_throw(DNSMessageFORMERR, "NSEC3 too short to contain hash length, "
+ "length: " << rdata_len + salt.size() + 5);
}
-
const uint8_t nextlen = buffer.readUint8();
--rdata_len;
if (nextlen == 0 || rdata_len < nextlen) {
@@ -193,7 +132,8 @@ NSEC3::NSEC3(InputBuffer& buffer, size_t rdata_len) {
checkRRTypeBitmaps("NSEC3", typebits);
}
- impl_ = new NSEC3Impl(hashalg, flags, iterations, salt, next, typebits);
+ impl_ = new NSEC3Impl(params.algorithm, params.flags, params.iterations,
+ salt, next, typebits);
}
NSEC3::NSEC3(const NSEC3& source) :
@@ -220,57 +160,78 @@ NSEC3::~NSEC3() {
string
NSEC3::toText() const {
ostringstream s;
- int len = 0;
- for (size_t i = 0; i < impl_->typebits_.size(); i += len) {
- assert(i + 2 <= impl_->typebits_.size());
- int window = impl_->typebits_[i];
- len = impl_->typebits_[i + 1];
- assert(len >= 0 && len < 32);
- i += 2;
- for (int j = 0; j < len; j++) {
- if (impl_->typebits_[i + j] == 0) {
- continue;
- }
- for (int k = 0; k < 8; k++) {
- if ((impl_->typebits_[i + j] & (0x80 >> k)) == 0) {
- continue;
- }
- int t = window * 256 + j * 8 + k;
- s << " " << RRType(t).toText();
- }
- }
- }
+ bitmapsToText(impl_->typebits_, s);
using namespace boost;
return (lexical_cast<string>(static_cast<int>(impl_->hashalg_)) +
- " " + lexical_cast<string>(static_cast<int>(impl_->flags_)) +
- " " + lexical_cast<string>(static_cast<int>(impl_->iterations_)) +
- " " + encodeHex(impl_->salt_) +
- " " + encodeBase32Hex(impl_->next_) + s.str());
+ " " + lexical_cast<string>(static_cast<int>(impl_->flags_)) +
+ " " + lexical_cast<string>(static_cast<int>(impl_->iterations_)) +
+ " " + (impl_->salt_.empty() ? "-" : encodeHex(impl_->salt_)) +
+ " " + encodeBase32Hex(impl_->next_) + s.str());
+}
+
+template <typename OUTPUT_TYPE>
+void
+toWireHelper(const NSEC3Impl& impl, OUTPUT_TYPE& output) {
+ output.writeUint8(impl.hashalg_);
+ output.writeUint8(impl.flags_);
+ output.writeUint16(impl.iterations_);
+ output.writeUint8(impl.salt_.size());
+ if (!impl.salt_.empty()) {
+ output.writeData(&impl.salt_[0], impl.salt_.size());
+ }
+ assert(!impl.next_.empty());
+ output.writeUint8(impl.next_.size());
+ output.writeData(&impl.next_[0], impl.next_.size());
+ if (!impl.typebits_.empty()) {
+ output.writeData(&impl.typebits_[0], impl.typebits_.size());
+ }
}
void
NSEC3::toWire(OutputBuffer& buffer) const {
- buffer.writeUint8(impl_->hashalg_);
- buffer.writeUint8(impl_->flags_);
- buffer.writeUint16(impl_->iterations_);
- buffer.writeUint8(impl_->salt_.size());
- buffer.writeData(&impl_->salt_[0], impl_->salt_.size());
- buffer.writeUint8(impl_->next_.size());
- buffer.writeData(&impl_->next_[0], impl_->next_.size());
- buffer.writeData(&impl_->typebits_[0], impl_->typebits_.size());
+ toWireHelper(*impl_, buffer);
}
void
NSEC3::toWire(AbstractMessageRenderer& renderer) const {
- renderer.writeUint8(impl_->hashalg_);
- renderer.writeUint8(impl_->flags_);
- renderer.writeUint16(impl_->iterations_);
- renderer.writeUint8(impl_->salt_.size());
- renderer.writeData(&impl_->salt_[0], impl_->salt_.size());
- renderer.writeUint8(impl_->next_.size());
- renderer.writeData(&impl_->next_[0], impl_->next_.size());
- renderer.writeData(&impl_->typebits_[0], impl_->typebits_.size());
+ toWireHelper(*impl_, renderer);
+}
+
+namespace {
+// This is a helper subroutine for compare(). It compares two binary
+// data stored in vector<uint8_t> objects based on the "Canonical RR Ordering"
+// as defined in Section 6.3 of RFC4034, that is, the data are treated
+// "as a left-justified unsigned octet sequence in which the absence of an
+// octet sorts before a zero octet."
+//
+// If check_length_first is true, it treats the compared data as if they
+// began with a single-octet "length" field whose value is the size of the
+// corresponding vector. In this case, if the sizes of the two vectors are
+// different the shorter one is always considered the "smaller"; the contents
+// of the vector don't matter.
+//
+// This function returns:
+// -1 if v1 is considered smaller than v2
+// 1 if v1 is considered larger than v2
+// 0 otherwise
+int
+compareVectors(const vector<uint8_t>& v1, const vector<uint8_t>& v2,
+ bool check_length_first = true)
+{
+ const size_t len1 = v1.size();
+ const size_t len2 = v2.size();
+ if (check_length_first && len1 != len2) {
+ return (len1 - len2);
+ }
+ const size_t cmplen = min(len1, len2);
+ const int cmp = cmplen == 0 ? 0 : memcmp(&v1.at(0), &v2.at(0), cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return (len1 - len2);
+ }
+}
}
int
@@ -287,44 +248,18 @@ NSEC3::compare(const Rdata& other) const {
return (impl_->iterations_ < other_nsec3.impl_->iterations_ ? -1 : 1);
}
- size_t this_len = impl_->salt_.size();
- size_t other_len = other_nsec3.impl_->salt_.size();
- size_t cmplen = min(this_len, other_len);
- int cmp = memcmp(&impl_->salt_[0], &other_nsec3.impl_->salt_[0], cmplen);
- if (cmp != 0) {
- return (cmp);
- } else if (this_len < other_len) {
- return (-1);
- } else if (this_len > other_len) {
- return (1);
- }
-
- this_len = impl_->salt_.size();
- other_len = other_nsec3.impl_->salt_.size();
- cmplen = min(this_len, other_len);
- cmp = memcmp(&impl_->next_[0], &other_nsec3.impl_->next_[0], cmplen);
+ int cmp = compareVectors(impl_->salt_, other_nsec3.impl_->salt_);
if (cmp != 0) {
return (cmp);
- } else if (this_len < other_len) {
- return (-1);
- } else if (this_len > other_len) {
- return (1);
}
-
- this_len = impl_->typebits_.size();
- other_len = other_nsec3.impl_->typebits_.size();
- cmplen = min(this_len, other_len);
- cmp = memcmp(&impl_->typebits_[0], &other_nsec3.impl_->typebits_[0],
- cmplen);
+ cmp = compareVectors(impl_->next_, other_nsec3.impl_->next_);
if (cmp != 0) {
return (cmp);
- } else if (this_len < other_len) {
- return (-1);
- } else if (this_len > other_len) {
- return (1);
- } else {
- return (0);
}
+ // Note that bitmap doesn't have a dedicated length field, so we shouldn't
+ // terminate the comparison just because the lengths are different.
+ return (compareVectors(impl_->typebits_, other_nsec3.impl_->typebits_,
+ false));
}
uint8_t
diff --git a/src/lib/dns/rdata/generic/nsec3param_51.cc b/src/lib/dns/rdata/generic/nsec3param_51.cc
index 49f666b..ac09b57 100644
--- a/src/lib/dns/rdata/generic/nsec3param_51.cc
+++ b/src/lib/dns/rdata/generic/nsec3param_51.cc
@@ -12,22 +12,19 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <iostream>
-#include <string>
-#include <sstream>
-#include <vector>
-
-#include <boost/lexical_cast.hpp>
-
#include <util/buffer.h>
#include <util/encode/hex.h>
+
#include <dns/messagerenderer.h>
-#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/nsec3param_common.h>
-#include <stdio.h>
-#include <time.h>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <sstream>
+#include <vector>
using namespace std;
using namespace isc::util;
@@ -43,9 +40,9 @@ struct NSEC3PARAMImpl {
hashalg_(hashalg), flags_(flags), iterations_(iterations), salt_(salt)
{}
- uint8_t hashalg_;
- uint8_t flags_;
- uint16_t iterations_;
+ const uint8_t hashalg_;
+ const uint8_t flags_;
+ const uint16_t iterations_;
const vector<uint8_t> salt_;
};
@@ -53,47 +50,26 @@ NSEC3PARAM::NSEC3PARAM(const string& nsec3param_str) :
impl_(NULL)
{
istringstream iss(nsec3param_str);
- uint16_t hashalg, flags, iterations;
- stringbuf saltbuf;
+ vector<uint8_t> salt;
+ const ParseNSEC3ParamResult params =
+ parseNSEC3ParamText("NSEC3PARAM", nsec3param_str, iss, salt);
- iss >> hashalg >> flags >> iterations >> &saltbuf;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid NSEC3PARAM text");
- }
- if (hashalg > 0xf) {
- isc_throw(InvalidRdataText, "NSEC3PARAM hash algorithm out of range");
- }
- if (flags > 0xff) {
- isc_throw(InvalidRdataText, "NSEC3PARAM flags out of range");
+ if (!iss.eof()) {
+ isc_throw(InvalidRdataText, "Invalid NSEC3PARAM (redundant text): "
+ << nsec3param_str);
}
- vector<uint8_t> salt;
- decodeHex(saltbuf.str(), salt);
-
- impl_ = new NSEC3PARAMImpl(hashalg, flags, iterations, salt);
+ impl_ = new NSEC3PARAMImpl(params.algorithm, params.flags,
+ params.iterations, salt);
}
NSEC3PARAM::NSEC3PARAM(InputBuffer& buffer, size_t rdata_len) {
- if (rdata_len < 4) {
- isc_throw(InvalidRdataLength, "NSEC3PARAM too short");
- }
-
- 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;
-
- if (rdata_len < saltlen) {
- isc_throw(InvalidRdataLength, "NSEC3PARAM salt too short");
- }
-
- vector<uint8_t> salt(saltlen);
- buffer.readData(&salt[0], saltlen);
+ vector<uint8_t> salt;
+ const ParseNSEC3ParamResult params =
+ parseNSEC3ParamWire("NSEC3PARAM", buffer, rdata_len, salt);
- impl_ = new NSEC3PARAMImpl(hashalg, flags, iterations, salt);
+ impl_ = new NSEC3PARAMImpl(params.algorithm, params.flags,
+ params.iterations, salt);
}
NSEC3PARAM::NSEC3PARAM(const NSEC3PARAM& source) :
@@ -121,27 +97,31 @@ string
NSEC3PARAM::toText() const {
using namespace boost;
return (lexical_cast<string>(static_cast<int>(impl_->hashalg_)) +
- " " + lexical_cast<string>(static_cast<int>(impl_->flags_)) +
- " " + lexical_cast<string>(static_cast<int>(impl_->iterations_)) +
- " " + encodeHex(impl_->salt_));
+ " " + lexical_cast<string>(static_cast<int>(impl_->flags_)) +
+ " " + lexical_cast<string>(static_cast<int>(impl_->iterations_)) +
+ " " + (impl_->salt_.empty() ? "-" : encodeHex(impl_->salt_)));
+}
+
+template <typename OUTPUT_TYPE>
+void
+toWireHelper(const NSEC3PARAMImpl& impl, OUTPUT_TYPE& output) {
+ output.writeUint8(impl.hashalg_);
+ output.writeUint8(impl.flags_);
+ output.writeUint16(impl.iterations_);
+ output.writeUint8(impl.salt_.size());
+ if (!impl.salt_.empty()) {
+ output.writeData(&impl.salt_[0], impl.salt_.size());
+ }
}
void
NSEC3PARAM::toWire(OutputBuffer& buffer) const {
- buffer.writeUint8(impl_->hashalg_);
- buffer.writeUint8(impl_->flags_);
- buffer.writeUint16(impl_->iterations_);
- buffer.writeUint8(impl_->salt_.size());
- buffer.writeData(&impl_->salt_[0], impl_->salt_.size());
+ toWireHelper(*impl_, buffer);
}
void
NSEC3PARAM::toWire(AbstractMessageRenderer& renderer) const {
- renderer.writeUint8(impl_->hashalg_);
- renderer.writeUint8(impl_->flags_);
- renderer.writeUint16(impl_->iterations_);
- renderer.writeUint8(impl_->salt_.size());
- renderer.writeData(&impl_->salt_[0], impl_->salt_.size());
+ toWireHelper(*impl_, renderer);
}
int
@@ -158,15 +138,18 @@ NSEC3PARAM::compare(const Rdata& other) const {
return (impl_->iterations_ < other_param.impl_->iterations_ ? -1 : 1);
}
- size_t this_len = impl_->salt_.size();
- size_t other_len = other_param.impl_->salt_.size();
- size_t cmplen = min(this_len, other_len);
- int cmp = memcmp(&impl_->salt_[0], &other_param.impl_->salt_[0],
- cmplen);
+ const size_t this_len = impl_->salt_.size();
+ const size_t other_len = other_param.impl_->salt_.size();
+ if (this_len != other_len) {
+ return (this_len - other_len);
+ }
+ const size_t cmplen = min(this_len, other_len);
+ const int cmp = (cmplen == 0) ? 0 :
+ memcmp(&impl_->salt_.at(0), &other_param.impl_->salt_.at(0), cmplen);
if (cmp != 0) {
return (cmp);
} else {
- return ((this_len == other_len) ? 0 : (this_len < other_len) ? -1 : 1);
+ return (this_len - other_len);
}
}
@@ -190,6 +173,5 @@ NSEC3PARAM::getSalt() const {
return (impl_->salt_);
}
-
// END_RDATA_NAMESPACE
// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/nsec_47.cc b/src/lib/dns/rdata/generic/nsec_47.cc
index a9a9f75..08825db 100644
--- a/src/lib/dns/rdata/generic/nsec_47.cc
+++ b/src/lib/dns/rdata/generic/nsec_47.cc
@@ -54,42 +54,18 @@ NSEC::NSEC(const string& nsec_str) :
{
istringstream iss(nsec_str);
string nextname;
- uint8_t bitmap[8 * 1024]; // 64k bits
- vector<uint8_t> typebits;
iss >> nextname;
if (iss.bad() || iss.fail()) {
isc_throw(InvalidRdataText, "Invalid NSEC name");
}
-
- memset(bitmap, 0, sizeof(bitmap));
- do {
- string type;
- iss >> type;
- try {
- const int code = RRType(type).getCode();
- bitmap[code / 8] |= (0x80 >> (code % 8));
- } catch (...) {
- isc_throw(InvalidRdataText, "Invalid RRtype in NSEC");
- }
- } while (!iss.eof());
-
- for (int window = 0; window < 256; window++) {
- int octet;
- for (octet = 31; octet >= 0; octet--) {
- if (bitmap[window * 32 + octet] != 0) {
- break;
- }
- }
- if (octet < 0)
- continue;
- typebits.push_back(window);
- typebits.push_back(octet + 1);
- for (int i = 0; i <= octet; i++) {
- typebits.push_back(bitmap[window * 32 + i]);
- }
+ if (iss.eof()) {
+ isc_throw(InvalidRdataText, "NSEC bitmap is missing");
}
+ vector<uint8_t> typebits;
+ buildBitmapsFromText("NSEC", iss, typebits);
+
impl_ = new NSECImpl(Name(nextname), typebits);
}
@@ -135,34 +111,8 @@ NSEC::~NSEC() {
string
NSEC::toText() const {
ostringstream s;
- int len = 0;
s << impl_->nextname_;
-
- // In the following loop we use string::at() rather than operator[].
- // Since the index calculation is a bit complicated, it will be safer
- // and easier to find a bug (if any). Note that this conversion method
- // is generally not expected to be very efficient, so the slight overhead
- // of at() should be acceptable.
- for (size_t i = 0; i < impl_->typebits_.size(); i += len) {
- assert(i + 2 <= impl_->typebits_.size());
- const int block = impl_->typebits_.at(i);
- len = impl_->typebits_.at(i + 1);
- assert(len > 0 && len <= 32);
- i += 2;
- for (int j = 0; j < len; j++) {
- if (impl_->typebits_.at(i + j) == 0) {
- continue;
- }
- for (int k = 0; k < 8; k++) {
- if ((impl_->typebits_.at(i + j) & (0x80 >> k)) == 0) {
- continue;
- }
- const int t = block * 256 + j * 8 + k;
- s << " " << RRType(t);
- }
- }
- }
-
+ bitmapsToText(impl_->typebits_, s);
return (s.str());
}
diff --git a/src/lib/dns/rdata/generic/sshfp_44.cc b/src/lib/dns/rdata/generic/sshfp_44.cc
new file mode 100644
index 0000000..6320fd9
--- /dev/null
+++ b/src/lib/dns/rdata/generic/sshfp_44.cc
@@ -0,0 +1,164 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <string>
+
+#include <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace boost;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+SSHFP::SSHFP(InputBuffer& buffer, size_t rdata_len)
+{
+ if (rdata_len < 2) {
+ isc_throw(InvalidRdataLength, "SSHFP record too short");
+ }
+
+ algorithm_ = buffer.readUint8();
+ fingerprint_type_ = buffer.readUint8();
+
+ rdata_len -= 2;
+ fingerprint_.resize(rdata_len);
+ buffer.readData(&fingerprint_[0], rdata_len);
+}
+
+SSHFP::SSHFP(const std::string& sshfp_str)
+{
+ std::istringstream iss(sshfp_str);
+ // peekc should be of iss's char_type for isspace to work
+ std::istringstream::char_type peekc;
+ std::stringbuf fingerprintbuf;
+ uint32_t algorithm, fingerprint_type;
+
+ iss >> algorithm >> fingerprint_type;
+ if (iss.bad() || iss.fail()) {
+ isc_throw(InvalidRdataText, "Invalid SSHFP text");
+ }
+ if ((algorithm < 1) || (algorithm > 2)) {
+ isc_throw(InvalidRdataText, "SSHFP algorithm number out of range");
+ }
+ if (fingerprint_type != 1) {
+ isc_throw(InvalidRdataText, "SSHFP fingerprint type out of range");
+ }
+
+ iss.read(&peekc, 1);
+ if (!iss.good() || !isspace(peekc, iss.getloc())) {
+ isc_throw(InvalidRdataText, "SSHFP presentation format error");
+ }
+
+ iss >> &fingerprintbuf;
+
+ algorithm_ = algorithm;
+ fingerprint_type_ = fingerprint_type;
+ decodeHex(fingerprintbuf.str(), fingerprint_);
+}
+
+SSHFP::SSHFP(uint8_t algorithm, uint8_t fingerprint_type, const string& fingerprint)
+{
+ if ((algorithm < 1) || (algorithm > 2)) {
+ isc_throw(InvalidRdataText, "SSHFP algorithm number out of range");
+ }
+ if (fingerprint_type != 1) {
+ isc_throw(InvalidRdataText, "SSHFP fingerprint type out of range");
+ }
+
+ algorithm_ = algorithm;
+ fingerprint_type_ = fingerprint_type;
+ decodeHex(fingerprint, fingerprint_);
+}
+
+SSHFP::SSHFP(const SSHFP& other) :
+ Rdata(), algorithm_(other.algorithm_), fingerprint_type_(other.fingerprint_type_), fingerprint_(other.fingerprint_)
+{}
+
+void
+SSHFP::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint8(algorithm_);
+ buffer.writeUint8(fingerprint_type_);
+ buffer.writeData(&fingerprint_[0], fingerprint_.size());
+}
+
+void
+SSHFP::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint8(algorithm_);
+ renderer.writeUint8(fingerprint_type_);
+ renderer.writeData(&fingerprint_[0], fingerprint_.size());
+}
+
+string
+SSHFP::toText() const {
+ return (lexical_cast<string>(static_cast<int>(algorithm_)) +
+ " " + lexical_cast<string>(static_cast<int>(fingerprint_type_)) +
+ " " + encodeHex(fingerprint_));
+}
+
+int
+SSHFP::compare(const Rdata& other) const {
+ const SSHFP& other_sshfp = dynamic_cast<const SSHFP&>(other);
+
+ /* This doesn't really make any sort of sense, but in the name of
+ consistency... */
+
+ if (algorithm_ < other_sshfp.algorithm_) {
+ return (-1);
+ } else if (algorithm_ > other_sshfp.algorithm_) {
+ return (1);
+ }
+
+ if (fingerprint_type_ < other_sshfp.fingerprint_type_) {
+ return (-1);
+ } else if (fingerprint_type_ > other_sshfp.fingerprint_type_) {
+ return (1);
+ }
+
+ size_t this_len = fingerprint_.size();
+ size_t other_len = other_sshfp.fingerprint_.size();
+ size_t cmplen = min(this_len, other_len);
+ int cmp = memcmp(&fingerprint_[0], &other_sshfp.fingerprint_[0], cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return ((this_len == other_len)
+ ? 0 : (this_len < other_len) ? -1 : 1);
+ }
+}
+
+uint8_t
+SSHFP::getSSHFPAlgorithmNumber() const {
+ return (algorithm_);
+}
+
+uint8_t
+SSHFP::getSSHFPFingerprintType() const {
+ return (fingerprint_type_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/sshfp_44.h b/src/lib/dns/rdata/generic/sshfp_44.h
new file mode 100644
index 0000000..c3ba944
--- /dev/null
+++ b/src/lib/dns/rdata/generic/sshfp_44.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+class SSHFP : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ SSHFP(uint8_t algorithm, uint8_t fingerprint_type, const std::string& fingerprint);
+
+ ///
+ /// Specialized methods
+ ///
+ uint8_t getSSHFPAlgorithmNumber() const;
+ uint8_t getSSHFPFingerprintType() const;
+
+private:
+ /// Note: this is a prototype version; we may reconsider
+ /// this representation later.
+ uint8_t algorithm_;
+ uint8_t fingerprint_type_;
+ std::vector<uint8_t> fingerprint_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/template.cc b/src/lib/dns/rdata/template.cc
index e85f82c..ee1097e 100644
--- a/src/lib/dns/rdata/template.cc
+++ b/src/lib/dns/rdata/template.cc
@@ -58,6 +58,7 @@ MyType::toWire(AbstractMessageRenderer& renderer) const {
int
MyType::compare(const Rdata& other) const {
// The compare method normally begins with this dynamic cast.
+ // cppcheck-suppress unreadVariable
const MyType& other_mytype = dynamic_cast<const MyType&>(other);
// ...
}
diff --git a/src/lib/dns/rdatafields.cc b/src/lib/dns/rdatafields.cc
index af9ba2e..94c9dbf 100644
--- a/src/lib/dns/rdatafields.cc
+++ b/src/lib/dns/rdatafields.cc
@@ -70,8 +70,7 @@ namespace {
// it's hopefully an acceptable practice.
class RdataFieldComposer : public AbstractMessageRenderer {
public:
- RdataFieldComposer(OutputBuffer& buffer) :
- AbstractMessageRenderer(buffer),
+ RdataFieldComposer() :
truncated_(false), length_limit_(65535),
mode_(CASE_INSENSITIVE), last_data_pos_(0)
{}
@@ -128,8 +127,7 @@ public:
}
RdataFields::RdataFields(const Rdata& rdata) {
- OutputBuffer buffer(0);
- RdataFieldComposer field_composer(buffer);
+ RdataFieldComposer field_composer;
rdata.toWire(field_composer);
nfields_ = field_composer.getFields().size();
data_length_ = field_composer.getLength();
diff --git a/src/lib/dns/rrset.cc b/src/lib/dns/rrset.cc
index 776d49f..9a55f5f 100644
--- a/src/lib/dns/rrset.cc
+++ b/src/lib/dns/rrset.cc
@@ -113,6 +113,16 @@ AbstractRRset::toWire(AbstractMessageRenderer& renderer) const {
return (rrs_written);
}
+bool
+AbstractRRset::isSameKind(const AbstractRRset& other) const {
+ // Compare classes last as they're likely to be identical. Compare
+ // names late in the list too, as these are expensive. So we compare
+ // types first, names second and classes last.
+ return (getType() == other.getType() &&
+ getName() == other.getName() &&
+ getClass() == other.getClass());
+}
+
ostream&
operator<<(ostream& os, const AbstractRRset& rrset) {
os << rrset.toText();
diff --git a/src/lib/dns/rrset.h b/src/lib/dns/rrset.h
index 1586465..43ade58 100644
--- a/src/lib/dns/rrset.h
+++ b/src/lib/dns/rrset.h
@@ -58,14 +58,14 @@ class RRset;
///
/// This type is commonly used as an argument of various functions defined
/// in this library in order to handle RRsets in a polymorphic manner.
-typedef boost::shared_ptr<RRset> RRsetPtr;
+typedef boost::shared_ptr<AbstractRRset> RRsetPtr;
/// \brief A pointer-like type pointing to an (immutable) \c RRset
/// object.
///
/// This type is commonly used as an argument of various functions defined
/// in this library in order to handle RRsets in a polymorphic manner.
-typedef boost::shared_ptr<const RRset> ConstRRsetPtr;
+typedef boost::shared_ptr<const AbstractRRset> ConstRRsetPtr;
/// \brief A pointer-like type point to an \c RdataIterator object.
typedef boost::shared_ptr<RdataIterator> RdataIteratorPtr;
@@ -400,6 +400,90 @@ public:
/// object.
virtual RdataIteratorPtr getRdataIterator() const = 0;
//@}
+
+ ///
+ /// \name Associated RRSIG methods
+ ///
+ /// These methods access an "associated" RRset, that containing the DNSSEC
+ /// signatures for this RRset. It can be argued that this is not a
+ /// fundamental part of the RRset abstraction, since RFC 2181 defined an
+ /// RRset as a group of records with the same label, class and type but
+ /// different data. However, BIND 10 has to deal with DNSSEC and in
+ /// practice, including the information at the AbstractRRset level makes
+ /// implementation easier. (If a class is ever needed that must be
+ /// ignorant of the idea of an associated RRSIG RRset - e.g. a specialised
+ /// RRSIG RRset class - these methods can just throw a "NotImplemented"
+ /// exception.)
+ //@{
+ /// \brief Return pointer to this RRset's RRSIG RRset
+ ///
+ /// \return Pointer to the associated RRSIG RRset or null if there is none.
+ virtual RRsetPtr getRRsig() const = 0;
+
+ /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset
+ ///
+ /// Adds the (assumed) RRSIG rdata the RRSIG RRset associated with this
+ /// RRset. If one does not exist, it is created using the data given.
+ ///
+ /// \param rdata Pointer to RRSIG rdata to be added.
+ virtual void addRRsig(const rdata::ConstRdataPtr& rdata) = 0;
+
+ /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset
+ ///
+ /// Adds the (assumed) RRSIG rdata the RRSIG RRset associated with this
+ /// RRset. If one does not exist, it is created using the data given.
+ ///
+ /// (This overload is for an older version of boost that doesn't support
+ /// conversion from shared_ptr<X> to shared_ptr<const X>.)
+ ///
+ /// \param rdata Pointer to RRSIG rdata to be added.
+ virtual void addRRsig(const rdata::RdataPtr& rdata) = 0;
+
+ /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset
+ ///
+ /// Adds the signatures in the given (assumed) RRSIG RRset to the RRSIG
+ /// RRset associated with this RRset. If one does not exist, it is created
+ /// using the data given.
+ ///
+ /// \param sigs RRSIG RRset containing signatures to be added to the
+ /// RRSIG RRset associated with this class.
+ virtual void addRRsig(const AbstractRRset& sigs) = 0;
+
+ /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset
+ ///
+ /// Adds the signatures in the given (assumed) RRSIG RRset to the RRSIG
+ /// RRset associated with this RRset. If one does not exist, it is created
+ /// using the data given.
+ ///
+ /// \param sigs Pointer to a RRSIG RRset containing signatures to be added
+ /// to the RRSIG RRset associated with this class.
+ virtual void addRRsig(const ConstRRsetPtr& sigs) = 0;
+
+ /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset
+ ///
+ /// Adds the signatures in the given (assumed) RRSIG RRset to the RRSIG
+ /// RRset associated with this RRset. If one does not exist, it is created
+ /// using the data given.
+ ///
+ /// (This overload is for an older version of boost that doesn't support
+ /// conversion from shared_ptr<X> to shared_ptr<const X>.)
+ ///
+ /// \param sigs Pointer to a RRSIG RRset containing signatures to be added
+ /// to the RRSIG RRset associated with this class.
+ virtual void addRRsig(const RRsetPtr& sigs) = 0;
+
+ /// \brief Clear the RRSIGs for this RRset
+ virtual void removeRRsig() = 0;
+
+ /// \brief Check whether two RRsets are of the same kind
+ ///
+ /// Checks if two RRsets have the same name, RR type, and RR class.
+ ///
+ /// \param other Pointer to another AbstractRRset to compare
+ /// against.
+ virtual bool isSameKind(const AbstractRRset& other) const;
+ //@}
+
};
/// \brief The \c RdataIterator class is an abstract base class that
@@ -660,6 +744,56 @@ public:
/// object for the \c BasicRRset class.
virtual RdataIteratorPtr getRdataIterator() const;
//@}
+
+ ///
+ /// \name Associated RRSIG methods
+ ///
+ /// The associated RRSIG RRset is not supported in BasicRRset. For
+ /// ease of use, getRRsig() returns a null pointer (indicating no RRset).
+ /// The addRRsig()/removeRRsig() methods throw a "NotImplemented"
+ /// exception - if you are using a BasicRRset, you should not be trying
+ /// to modify signatures on it.
+ //@{
+ /// \brief Return pointer to this RRset's RRSIG RRset
+ ///
+ /// \exception NotImplemented Always thrown. Associated RRSIG RRsets are
+ /// not supported in this class.
+ ///
+ /// \return Null pointer, as this class does not support RRSIG records.
+ virtual RRsetPtr getRRsig() const {
+ return (RRsetPtr());
+ }
+
+ virtual void addRRsig(const rdata::ConstRdataPtr&) {
+ isc_throw(NotImplemented,
+ "BasicRRset does not implement the addRRsig() method");
+ }
+
+ virtual void addRRsig(const rdata::RdataPtr&) {
+ isc_throw(NotImplemented,
+ "BasicRRset does not implement the addRRsig() method");
+ }
+
+ virtual void addRRsig(const AbstractRRset&) {
+ isc_throw(NotImplemented,
+ "BasicRRset does not implement the addRRsig() method");
+ }
+
+ virtual void addRRsig(const ConstRRsetPtr&) {
+ isc_throw(NotImplemented,
+ "BasicRRset does not implement the addRRsig() method");
+ }
+
+ virtual void addRRsig(const RRsetPtr&) {
+ isc_throw(NotImplemented,
+ "BasicRRset does not implement the addRRsig() method");
+ }
+
+ virtual void removeRRsig() {
+ isc_throw(NotImplemented,
+ "BasicRRset does not implement the removeRRsig() method");
+ }
+ //@}
private:
BasicRRsetImpl* impl_;
};
@@ -693,7 +827,7 @@ public:
}
/// \brief Adds an RRSIG RR to this RRset's signatures
- virtual void addRRsig(const rdata::RdataPtr rdata) {
+ virtual void addRRsig(const rdata::ConstRdataPtr& rdata) {
if (!rrsig_) {
rrsig_ = RRsetPtr(new RRset(getName(), getClass(),
RRType::RRSIG(), getTTL()));
@@ -701,8 +835,17 @@ public:
rrsig_->addRdata(rdata);
}
+ // Workaround for older versions of boost: some don't support implicit
+ // conversion from shared_ptr<X> to shared_ptr<const X>. Note: we should
+ // revisit the interface of managing RRset signatures, at which point this
+ // problem may go away.
+ virtual void addRRsig(const rdata::RdataPtr& rdata) {
+ // Don't try to convert as a reference here. SunStudio will reject it.
+ addRRsig(static_cast<const rdata::ConstRdataPtr>(rdata));
+ }
+
/// \brief Adds an RRSIG RRset to this RRset
- void addRRsig(AbstractRRset& sigs) {
+ virtual void addRRsig(const AbstractRRset& sigs) {
RdataIteratorPtr it = sigs.getRdataIterator();
if (!rrsig_) {
@@ -715,10 +858,13 @@ public:
}
}
- void addRRsig(RRsetPtr sigs) { addRRsig(*sigs); }
+ virtual void addRRsig(const ConstRRsetPtr& sigs) { addRRsig(*sigs); }
+
+ // Another workaround for older boost (see above)
+ virtual void addRRsig(const RRsetPtr& sigs) { addRRsig(*sigs); }
/// \brief Clear the RRSIGs for this RRset
- void removeRRsig() { rrsig_ = RRsetPtr(); }
+ virtual void removeRRsig() { rrsig_ = RRsetPtr(); }
/// \brief Return a pointer to this RRset's RRSIG RRset
RRsetPtr getRRsig() const { return (rrsig_); }
diff --git a/src/lib/dns/tests/.gitignore b/src/lib/dns/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/lib/dns/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index cfd1286..26b4630 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -19,8 +19,10 @@ if HAVE_GTEST
TESTS += run_unittests
run_unittests_SOURCES = unittest_util.h unittest_util.cc
run_unittests_SOURCES += edns_unittest.cc
+run_unittests_SOURCES += labelsequence_unittest.cc
run_unittests_SOURCES += messagerenderer_unittest.cc
run_unittests_SOURCES += name_unittest.cc
+run_unittests_SOURCES += nsec3hash_unittest.cc
run_unittests_SOURCES += rrclass_unittest.cc rrtype_unittest.cc
run_unittests_SOURCES += rrttl_unittest.cc
run_unittests_SOURCES += opcode_unittest.cc
@@ -31,6 +33,7 @@ run_unittests_SOURCES += rdata_in_a_unittest.cc rdata_in_aaaa_unittest.cc
run_unittests_SOURCES += rdata_ns_unittest.cc rdata_soa_unittest.cc
run_unittests_SOURCES += rdata_txt_like_unittest.cc
run_unittests_SOURCES += rdata_mx_unittest.cc
+run_unittests_SOURCES += rdata_sshfp_unittest.cc
run_unittests_SOURCES += rdata_ptr_unittest.cc rdata_cname_unittest.cc
run_unittests_SOURCES += rdata_dname_unittest.cc
run_unittests_SOURCES += rdata_afsdb_unittest.cc
@@ -42,6 +45,7 @@ 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_nsec3param_like_unittest.cc
run_unittests_SOURCES += rdata_rrsig_unittest.cc
run_unittests_SOURCES += rdata_rp_unittest.cc
run_unittests_SOURCES += rdata_srv_unittest.cc
diff --git a/src/lib/dns/tests/edns_unittest.cc b/src/lib/dns/tests/edns_unittest.cc
index 26cc01c..de2d244 100644
--- a/src/lib/dns/tests/edns_unittest.cc
+++ b/src/lib/dns/tests/edns_unittest.cc
@@ -43,9 +43,7 @@ const uint8_t EDNS::SUPPORTED_VERSION;
namespace {
class EDNSTest : public ::testing::Test {
protected:
- EDNSTest() : rrtype(RRType::OPT()), buffer(NULL, 0), obuffer(0),
- renderer(obuffer), rcode(0)
- {
+ EDNSTest() : rrtype(RRType::OPT()), buffer(NULL, 0), obuffer(0), rcode(0) {
opt_rdata = ConstRdataPtr(new generic::OPT());
edns_base.setUDPSize(4096);
}
diff --git a/src/lib/dns/tests/labelsequence_unittest.cc b/src/lib/dns/tests/labelsequence_unittest.cc
new file mode 100644
index 0000000..98bb99c
--- /dev/null
+++ b/src/lib/dns/tests/labelsequence_unittest.cc
@@ -0,0 +1,348 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/labelsequence.h>
+#include <dns/name.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/functional/hash.hpp>
+
+#include <string>
+#include <set>
+
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+
+class LabelSequenceTest : public ::testing::Test {
+public:
+ LabelSequenceTest() : n1("example.org"), n2("example.com"),
+ n3("example.org"), n4("foo.bar.test.example"),
+ n5("example.ORG"), n6("ExAmPlE.org"),
+ n7("."), n8("foo.example.org.bar"),
+ ls1(n1), ls2(n2), ls3(n3), ls4(n4), ls5(n5),
+ ls6(n6), ls7(n7), ls8(n8)
+ {};
+ // Need to keep names in scope for at least the lifetime of
+ // the labelsequences
+ Name n1, n2, n3, n4, n5, n6, n7, n8;
+
+ LabelSequence ls1, ls2, ls3, ls4, ls5, ls6, ls7, ls8;
+};
+
+// Basic equality tests
+TEST_F(LabelSequenceTest, equals_sensitive) {
+ EXPECT_TRUE(ls1.equals(ls1, true));
+ EXPECT_FALSE(ls1.equals(ls2, true));
+ EXPECT_TRUE(ls1.equals(ls3, true));
+ EXPECT_FALSE(ls1.equals(ls4, true));
+ EXPECT_FALSE(ls1.equals(ls5, true));
+ EXPECT_FALSE(ls1.equals(ls6, true));
+ EXPECT_FALSE(ls1.equals(ls7, true));
+ EXPECT_FALSE(ls1.equals(ls8, true));
+
+ EXPECT_FALSE(ls2.equals(ls1, true));
+ EXPECT_TRUE(ls2.equals(ls2, true));
+ EXPECT_FALSE(ls2.equals(ls3, true));
+ EXPECT_FALSE(ls2.equals(ls4, true));
+ EXPECT_FALSE(ls2.equals(ls5, true));
+ EXPECT_FALSE(ls2.equals(ls6, true));
+ EXPECT_FALSE(ls2.equals(ls7, true));
+ EXPECT_FALSE(ls2.equals(ls8, true));
+
+ EXPECT_FALSE(ls4.equals(ls1, true));
+ EXPECT_FALSE(ls4.equals(ls2, true));
+ EXPECT_FALSE(ls4.equals(ls3, true));
+ EXPECT_TRUE(ls4.equals(ls4, true));
+ EXPECT_FALSE(ls4.equals(ls5, true));
+ EXPECT_FALSE(ls4.equals(ls6, true));
+ EXPECT_FALSE(ls4.equals(ls7, true));
+ EXPECT_FALSE(ls4.equals(ls8, true));
+
+ EXPECT_FALSE(ls5.equals(ls1, true));
+ EXPECT_FALSE(ls5.equals(ls2, true));
+ EXPECT_FALSE(ls5.equals(ls3, true));
+ EXPECT_FALSE(ls5.equals(ls4, true));
+ EXPECT_TRUE(ls5.equals(ls5, true));
+ EXPECT_FALSE(ls5.equals(ls6, true));
+ EXPECT_FALSE(ls5.equals(ls7, true));
+ EXPECT_FALSE(ls5.equals(ls8, true));
+}
+
+TEST_F(LabelSequenceTest, equals_insensitive) {
+ EXPECT_TRUE(ls1.equals(ls1));
+ EXPECT_FALSE(ls1.equals(ls2));
+ EXPECT_TRUE(ls1.equals(ls3));
+ EXPECT_FALSE(ls1.equals(ls4));
+ EXPECT_TRUE(ls1.equals(ls5));
+ EXPECT_TRUE(ls1.equals(ls6));
+ EXPECT_FALSE(ls1.equals(ls7));
+
+ EXPECT_FALSE(ls2.equals(ls1));
+ EXPECT_TRUE(ls2.equals(ls2));
+ EXPECT_FALSE(ls2.equals(ls3));
+ EXPECT_FALSE(ls2.equals(ls4));
+ EXPECT_FALSE(ls2.equals(ls5));
+ EXPECT_FALSE(ls2.equals(ls6));
+ EXPECT_FALSE(ls2.equals(ls7));
+
+ EXPECT_TRUE(ls3.equals(ls1));
+ EXPECT_FALSE(ls3.equals(ls2));
+ EXPECT_TRUE(ls3.equals(ls3));
+ EXPECT_FALSE(ls3.equals(ls4));
+ EXPECT_TRUE(ls3.equals(ls5));
+ EXPECT_TRUE(ls3.equals(ls6));
+ EXPECT_FALSE(ls3.equals(ls7));
+
+ EXPECT_FALSE(ls4.equals(ls1));
+ EXPECT_FALSE(ls4.equals(ls2));
+ EXPECT_FALSE(ls4.equals(ls3));
+ EXPECT_TRUE(ls4.equals(ls4));
+ EXPECT_FALSE(ls4.equals(ls5));
+ EXPECT_FALSE(ls4.equals(ls6));
+ EXPECT_FALSE(ls4.equals(ls7));
+
+ EXPECT_TRUE(ls5.equals(ls1));
+ EXPECT_FALSE(ls5.equals(ls2));
+ EXPECT_TRUE(ls5.equals(ls3));
+ EXPECT_FALSE(ls5.equals(ls4));
+ EXPECT_TRUE(ls5.equals(ls5));
+ EXPECT_TRUE(ls5.equals(ls6));
+ EXPECT_FALSE(ls5.equals(ls7));
+}
+
+void
+getDataCheck(const char* expected_data, size_t expected_len,
+ const LabelSequence& ls)
+{
+ size_t len;
+ const char* data = ls.getData(&len);
+ ASSERT_EQ(expected_len, len) << "Expected data: " << expected_data <<
+ " name: " << ls.getName().toText();
+ EXPECT_EQ(expected_len, ls.getDataLength()) <<
+ "Expected data: " << expected_data <<
+ " name: " << ls.getName().toText();
+ for (size_t i = 0; i < len; ++i) {
+ EXPECT_EQ(expected_data[i], data[i]) << "Difference at pos " << i <<
+ ": Expected data: " <<
+ expected_data <<
+ " name: " <<
+ ls.getName().toText();;
+ }
+}
+
+TEST_F(LabelSequenceTest, getData) {
+ getDataCheck("\007example\003org\000", 13, ls1);
+ getDataCheck("\007example\003com\000", 13, ls2);
+ getDataCheck("\007example\003org\000", 13, ls3);
+ getDataCheck("\003foo\003bar\004test\007example\000", 22, ls4);
+ getDataCheck("\007example\003ORG\000", 13, ls5);
+ getDataCheck("\007ExAmPlE\003org\000", 13, ls6);
+ getDataCheck("\000", 1, ls7);
+};
+
+TEST_F(LabelSequenceTest, stripLeft) {
+ EXPECT_TRUE(ls1.equals(ls3));
+ ls1.stripLeft(0);
+ getDataCheck("\007example\003org\000", 13, ls1);
+ EXPECT_TRUE(ls1.equals(ls3));
+ ls1.stripLeft(1);
+ getDataCheck("\003org\000", 5, ls1);
+ EXPECT_FALSE(ls1.equals(ls3));
+ ls1.stripLeft(1);
+ getDataCheck("\000", 1, ls1);
+ EXPECT_TRUE(ls1.equals(ls7));
+
+ ls2.stripLeft(2);
+ getDataCheck("\000", 1, ls2);
+ EXPECT_TRUE(ls2.equals(ls7));
+}
+
+TEST_F(LabelSequenceTest, stripRight) {
+ EXPECT_TRUE(ls1.equals(ls3));
+ ls1.stripRight(1);
+ getDataCheck("\007example\003org", 12, ls1);
+ EXPECT_FALSE(ls1.equals(ls3));
+ ls1.stripRight(1);
+ getDataCheck("\007example", 8, ls1);
+ EXPECT_FALSE(ls1.equals(ls3));
+
+ ASSERT_FALSE(ls1.equals(ls2));
+ ls2.stripRight(2);
+ getDataCheck("\007example", 8, ls2);
+ EXPECT_TRUE(ls1.equals(ls2));
+}
+
+TEST_F(LabelSequenceTest, stripOutOfRange) {
+ EXPECT_THROW(ls1.stripLeft(100), isc::OutOfRange);
+ EXPECT_THROW(ls1.stripLeft(5), isc::OutOfRange);
+ EXPECT_THROW(ls1.stripLeft(4), isc::OutOfRange);
+ EXPECT_THROW(ls1.stripLeft(3), isc::OutOfRange);
+ getDataCheck("\007example\003org\000", 13, ls1);
+
+ EXPECT_THROW(ls1.stripRight(100), isc::OutOfRange);
+ EXPECT_THROW(ls1.stripRight(5), isc::OutOfRange);
+ EXPECT_THROW(ls1.stripRight(4), isc::OutOfRange);
+ EXPECT_THROW(ls1.stripRight(3), isc::OutOfRange);
+ getDataCheck("\007example\003org\000", 13, ls1);
+}
+
+TEST_F(LabelSequenceTest, getLabelCount) {
+ EXPECT_EQ(3, ls1.getLabelCount());
+ ls1.stripLeft(0);
+ EXPECT_EQ(3, ls1.getLabelCount());
+ ls1.stripLeft(1);
+ EXPECT_EQ(2, ls1.getLabelCount());
+ ls1.stripLeft(1);
+ EXPECT_EQ(1, ls1.getLabelCount());
+
+ EXPECT_EQ(3, ls2.getLabelCount());
+ ls2.stripRight(1);
+ EXPECT_EQ(2, ls2.getLabelCount());
+ ls2.stripRight(1);
+ EXPECT_EQ(1, ls2.getLabelCount());
+
+ EXPECT_EQ(3, ls3.getLabelCount());
+ ls3.stripRight(2);
+ EXPECT_EQ(1, ls3.getLabelCount());
+
+ EXPECT_EQ(5, ls4.getLabelCount());
+ ls4.stripRight(3);
+ EXPECT_EQ(2, ls4.getLabelCount());
+
+ EXPECT_EQ(3, ls5.getLabelCount());
+ ls5.stripLeft(2);
+ EXPECT_EQ(1, ls5.getLabelCount());
+}
+
+TEST_F(LabelSequenceTest, comparePart) {
+ EXPECT_FALSE(ls1.equals(ls8));
+
+ // strip root label from example.org.
+ ls1.stripRight(1);
+ // strip foo from foo.example.org.bar.
+ ls8.stripLeft(1);
+ // strip bar. (i.e. bar and root) too
+ ls8.stripRight(2);
+
+ EXPECT_TRUE(ls1.equals(ls8));
+
+ // Data comparison
+ size_t len;
+ const char* data = ls1.getData(&len);
+ getDataCheck(data, len, ls8);
+}
+
+TEST_F(LabelSequenceTest, isAbsolute) {
+ ASSERT_TRUE(ls1.isAbsolute());
+
+ ls1.stripLeft(1);
+ ASSERT_TRUE(ls1.isAbsolute());
+ ls1.stripRight(1);
+ ASSERT_FALSE(ls1.isAbsolute());
+
+ ASSERT_TRUE(ls2.isAbsolute());
+ ls2.stripRight(1);
+ ASSERT_FALSE(ls2.isAbsolute());
+
+ ASSERT_TRUE(ls3.isAbsolute());
+ ls3.stripLeft(2);
+ ASSERT_TRUE(ls3.isAbsolute());
+}
+
+// The following are test data used in the getHash test below. Normally
+// we use example/documentation domain names for testing, but in this case
+// we'd specifically like to use more realistic data, and are intentionally
+// using real-world samples: They are the NS names of root and some top level
+// domains as of this test.
+const char* const root_servers[] = {
+ "a.root-servers.net", "b.root-servers.net", "c.root-servers.net",
+ "d.root-servers.net", "e.root-servers.net", "f.root-servers.net",
+ "g.root-servers.net", "h.root-servers.net", "i.root-servers.net",
+ "j.root-servers.net", "k.root-servers.net", "l.root-servers.net",
+ "m.root-servers.net", NULL
+};
+const char* const gtld_servers[] = {
+ "a.gtld-servers.net", "b.gtld-servers.net", "c.gtld-servers.net",
+ "d.gtld-servers.net", "e.gtld-servers.net", "f.gtld-servers.net",
+ "g.gtld-servers.net", "h.gtld-servers.net", "i.gtld-servers.net",
+ "j.gtld-servers.net", "k.gtld-servers.net", "l.gtld-servers.net",
+ "m.gtld-servers.net", NULL
+};
+const char* const jp_servers[] = {
+ "a.dns.jp", "b.dns.jp", "c.dns.jp", "d.dns.jp", "e.dns.jp",
+ "f.dns.jp", "g.dns.jp", NULL
+};
+const char* const cn_servers[] = {
+ "a.dns.cn", "b.dns.cn", "c.dns.cn", "d.dns.cn", "e.dns.cn",
+ "ns.cernet.net", NULL
+};
+const char* const ca_servers[] = {
+ "k.ca-servers.ca", "e.ca-servers.ca", "a.ca-servers.ca", "z.ca-servers.ca",
+ "tld.isc-sns.net", "c.ca-servers.ca", "j.ca-servers.ca", "l.ca-servers.ca",
+ "sns-pb.isc.org", "f.ca-servers.ca", NULL
+};
+
+// A helper function used in the getHash test below.
+void
+hashDistributionCheck(const char* const* servers) {
+ const size_t BUCKETS = 64; // constant used in the MessageRenderer
+ set<Name> names;
+ vector<size_t> hash_counts(BUCKETS);
+
+ // Store all test names and their super domain names (excluding the
+ // "root" label) in the set, calculates their hash values, and increments
+ // the counter for the corresponding hash "bucket".
+ for (size_t i = 0; servers[i] != NULL; ++i) {
+ const Name name(servers[i]);
+ for (size_t l = 0; l < name.getLabelCount() - 1; ++l) {
+ pair<set<Name>::const_iterator, bool> ret =
+ names.insert(name.split(l));
+ if (ret.second) {
+ hash_counts[LabelSequence((*ret.first)).getHash(false) %
+ BUCKETS]++;
+ }
+ }
+ }
+
+ // See how many conflicts we have in the buckets. For the testing purpose
+ // we expect there's at most 2 conflicts in each set, which is an
+ // arbitrary choice (it should happen to succeed with the hash function
+ // and data we are using; if it's not the case, maybe with an update to
+ // the hash implementation, we should revise the test).
+ for (size_t i = 0; i < BUCKETS; ++i) {
+ EXPECT_GE(3, hash_counts[i]);
+ }
+}
+
+TEST_F(LabelSequenceTest, getHash) {
+ // Trivial case. The same sequence should have the same hash.
+ EXPECT_EQ(ls1.getHash(true), ls1.getHash(true));
+
+ // Check the case-insensitive mode behavior.
+ EXPECT_EQ(ls1.getHash(false), ls5.getHash(false));
+
+ // Check that the distribution of hash values is "not too bad" (such as
+ // everything has the same hash value due to a stupid bug). It's
+ // difficult to check such things reliably. We do some ad hoc tests here.
+ hashDistributionCheck(root_servers);
+ hashDistributionCheck(jp_servers);
+ hashDistributionCheck(cn_servers);
+ hashDistributionCheck(ca_servers);
+}
+
+}
diff --git a/src/lib/dns/tests/masterload_unittest.cc b/src/lib/dns/tests/masterload_unittest.cc
index c47debd..95ce6f3 100644
--- a/src/lib/dns/tests/masterload_unittest.cc
+++ b/src/lib/dns/tests/masterload_unittest.cc
@@ -25,6 +25,7 @@
#include <dns/masterload.h>
#include <dns/name.h>
+#include <dns/rdata.h>
#include <dns/rrclass.h>
#include <dns/rrset.h>
@@ -72,6 +73,18 @@ 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";
+// A couple of RRSIGs, different type covered
+const char* const rrsig_rr1 =
+ "www.example.com. 60 IN RRSIG A 5 3 3600 20000101000000 20000201000000 "
+ "12345 example.com. FAKEFAKEFAKE\n";
+const char* const rrsig_rr2 =
+ "www.example.com. 60 IN RRSIG AAAA 5 3 3600 20000101000000 20000201000000 "
+ "12345 example.com. FAKEFAKEFAKE\n";
+
+// Commonly used for some tests to check the constructed RR content.
+const char* const dnskey_rdata =
+ "256 3 7 AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LUsAD0QPWU+wzt89epO6tH "
+ "zkMBVDkC7qphQO2hTY4hHn9npWFRw5BYubE=\n";
TEST_F(MasterLoadTest, loadRRs) {
// a simple case: loading 3 RRs, each consists of a single RRset.
@@ -147,6 +160,105 @@ TEST_F(MasterLoadTest, loadRRsetsInterleaved) {
EXPECT_EQ(a_rr2, results[2]->toText());
}
+TEST_F(MasterLoadTest, loadRRsigs) {
+ // RRSIGs of different types covered should be separated
+ rr_stream << rrsig_rr1 << rrsig_rr2;
+ masterLoad(rr_stream, origin, zclass, callback);
+ EXPECT_EQ(2, results.size());
+}
+
+TEST_F(MasterLoadTest, loadRRWithComment) {
+ // Comment at the end of line should be ignored and the RR should be
+ // accepted.
+ rr_stream << "example.com. 3600 IN DNSKEY 256 3 7 "
+ "AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LUsAD0QPWU+wzt89epO6tH "
+ "zkMBVDkC7qphQO2hTY4hHn9npWFRw5BYubE= ; key id = 40430\n";
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(0, results[0]->getRdataIterator()->getCurrent().compare(
+ *rdata::createRdata(RRType::DNSKEY(), zclass,
+ dnskey_rdata)));
+}
+
+TEST_F(MasterLoadTest, loadRRWithCommentNoSpace) {
+ // Similar to the previous one, but there's no space before comments.
+ // It should still work.
+ rr_stream << "example.com. 3600 IN DNSKEY 256 3 7 "
+ "AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LUsAD0QPWU+wzt89epO6tH "
+ "zkMBVDkC7qphQO2hTY4hHn9npWFRw5BYubE=; key id = 40430\n";
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(0, results[0]->getRdataIterator()->getCurrent().compare(
+ *rdata::createRdata(RRType::DNSKEY(), zclass,
+ dnskey_rdata)));
+}
+
+TEST_F(MasterLoadTest, loadRRWithCommentEmptyComment) {
+ // Similar to the previous one, but there's no data after the ;
+ // It should still work.
+ rr_stream << "example.com. 3600 IN DNSKEY 256 3 7 "
+ "AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LUsAD0QPWU+wzt89epO6tH "
+ "zkMBVDkC7qphQO2hTY4hHn9npWFRw5BYubE= ;\n";
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(0, results[0]->getRdataIterator()->getCurrent().compare(
+ *rdata::createRdata(RRType::DNSKEY(), zclass,
+ dnskey_rdata)));
+}
+
+TEST_F(MasterLoadTest, loadRRWithCommentEmptyCommentNoSpace) {
+ // Similar to the previous one, but there's no space before or after ;
+ // It should still work.
+ rr_stream << "example.com. 3600 IN DNSKEY 256 3 7 "
+ "AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LUsAD0QPWU+wzt89epO6tH "
+ "zkMBVDkC7qphQO2hTY4hHn9npWFRw5BYubE=;\n";
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(0, results[0]->getRdataIterator()->getCurrent().compare(
+ *rdata::createRdata(RRType::DNSKEY(), zclass,
+ dnskey_rdata)));
+}
+
+TEST_F(MasterLoadTest, loadRRWithEOLWhitespace) {
+ // Test with whitespace after rdata
+ // It should still work.
+ rr_stream << "example.com. 3600 IN NSEC3PARAM 1 0 1 beef \n";
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(0, results[0]->getRdataIterator()->getCurrent().compare(
+ *rdata::createRdata(RRType::NSEC3PARAM(), zclass,
+ "1 0 1 beef")));
+}
+
+TEST_F(MasterLoadTest, loadRRWithEOLWhitespaceTab) {
+ // Similar to the previous one, tab instead of space.
+ // It should still work.
+ rr_stream << "example.com. 3600 IN NSEC3PARAM 1 0 1 beef\t\n";
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(0, results[0]->getRdataIterator()->getCurrent().compare(
+ *rdata::createRdata(RRType::NSEC3PARAM(), zclass,
+ "1 0 1 beef")));
+}
+
+TEST_F(MasterLoadTest, loadRRNoComment) {
+ // A semicolon in a character-string shouldn't confuse the parser.
+ rr_stream << "example.com. 3600 IN TXT \"aaa;bbb\"\n";
+ masterLoad(rr_stream, origin, zclass, callback);
+ EXPECT_EQ(1, results.size());
+ EXPECT_EQ(0, results[0]->getRdataIterator()->getCurrent().compare(
+ *rdata::createRdata(RRType::TXT(), zclass,
+ "\"aaa;bbb\"")));
+}
+
+TEST_F(MasterLoadTest, loadRREmptyAndComment) {
+ // There's no RDATA (invalid in this case) but a comment. This position
+ // shouldn't cause any disruption and should be treated as a normal error.
+ rr_stream << "example.com. 3600 IN A ;\n";
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+ MasterLoadError);
+}
+
TEST_F(MasterLoadTest, loadWithNoEOF) {
// the input stream doesn't end with a new line (and the following blank
// line). It should be accepted.
diff --git a/src/lib/dns/tests/message_unittest.cc b/src/lib/dns/tests/message_unittest.cc
index c4d4984..3be1436 100644
--- a/src/lib/dns/tests/message_unittest.cc
+++ b/src/lib/dns/tests/message_unittest.cc
@@ -79,7 +79,6 @@ namespace {
class MessageTest : public ::testing::Test {
protected:
MessageTest() : test_name("test.example.com"), obuffer(0),
- renderer(obuffer),
message_parse(Message::PARSE),
message_render(Message::RENDER),
bogus_section(static_cast<Message::Section>(
@@ -324,6 +323,10 @@ TEST_F(MessageTest, badAddRRset) {
rrset_a), InvalidMessageOperation);
// out-of-band section ID
EXPECT_THROW(message_render.addRRset(bogus_section, rrset_a), OutOfRange);
+
+ // NULL RRset
+ EXPECT_THROW(message_render.addRRset(Message::SECTION_ANSWER, RRsetPtr()),
+ InvalidParameter);
}
TEST_F(MessageTest, hasRRset) {
@@ -727,8 +730,8 @@ TEST_F(MessageTest, toWire) {
message_render.toWire(renderer);
vector<unsigned char> data;
UnitTestUtil::readWireData("message_toWire1", data);
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
- obuffer.getLength(), &data[0], data.size());
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+ renderer.getLength(), &data[0], data.size());
}
TEST_F(MessageTest, toWireInParseMode) {
@@ -957,9 +960,6 @@ TEST_F(MessageTest, toWireTSIGNoTruncation) {
// rendering fail unexpectedly in the test that follows.
class BadRenderer : public MessageRenderer {
public:
- BadRenderer(isc::util::OutputBuffer& buffer) :
- MessageRenderer(buffer)
- {}
virtual void setLengthLimit(size_t len) {
if (getLength() > 0) {
MessageRenderer::setLengthLimit(getLength());
@@ -990,8 +990,7 @@ TEST_F(MessageTest, toWireTSIGLengthErrors) {
InvalidParameter);
// Trying to render a message with TSIG using a buggy renderer.
- obuffer.clear();
- BadRenderer bad_renderer(obuffer);
+ BadRenderer bad_renderer;
bad_renderer.setLengthLimit(512);
message_render.clear(Message::RENDER);
EXPECT_THROW(commonTSIGToWireCheck(message_render, bad_renderer, tsig_ctx,
diff --git a/src/lib/dns/tests/messagerenderer_unittest.cc b/src/lib/dns/tests/messagerenderer_unittest.cc
index fe790fe..bc526af 100644
--- a/src/lib/dns/tests/messagerenderer_unittest.cc
+++ b/src/lib/dns/tests/messagerenderer_unittest.cc
@@ -12,8 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <vector>
-
+#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <dns/name.h>
#include <dns/messagerenderer.h>
@@ -22,23 +21,27 @@
#include <gtest/gtest.h>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <vector>
+
using isc::UnitTestUtil;
using isc::dns::Name;
using isc::dns::MessageRenderer;
using isc::util::OutputBuffer;
+using boost::lexical_cast;
namespace {
class MessageRendererTest : public ::testing::Test {
protected:
- MessageRendererTest() : expected_size(0), buffer(0), renderer(buffer)
- {
+ MessageRendererTest() : expected_size(0) {
data16 = (2 << 8) | 3;
data32 = (4 << 24) | (5 << 16) | (6 << 8) | 7;
}
size_t expected_size;
uint16_t data16;
uint32_t data32;
- OutputBuffer buffer;
MessageRenderer renderer;
std::vector<unsigned char> data;
static const uint8_t testdata[5];
@@ -60,21 +63,22 @@ TEST_F(MessageRendererTest, writeName) {
renderer.writeName(Name("a.example.com."));
renderer.writeName(Name("b.example.com."));
renderer.writeName(Name("a.example.org."));
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
- buffer.getLength(), &data[0], data.size());
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+ renderer.getLength(), &data[0], data.size());
}
TEST_F(MessageRendererTest, writeNameInLargeBuffer) {
size_t offset = 0x3fff;
- buffer.skip(offset);
+ renderer.skip(offset);
UnitTestUtil::readWireData("name_toWire2", data);
renderer.writeName(Name("a.example.com."));
renderer.writeName(Name("a.example.com."));
renderer.writeName(Name("b.example.com."));
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- static_cast<const uint8_t*>(buffer.getData()) + offset,
- buffer.getLength() - offset,
+ static_cast<const uint8_t*>(renderer.getData()) +
+ offset,
+ renderer.getLength() - offset,
&data[0], data.size());
}
@@ -83,8 +87,8 @@ TEST_F(MessageRendererTest, writeNameWithUncompressed) {
renderer.writeName(Name("a.example.com."));
renderer.writeName(Name("b.example.com."), false);
renderer.writeName(Name("b.example.com."));
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
- buffer.getLength(), &data[0], data.size());
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+ renderer.getLength(), &data[0], data.size());
}
TEST_F(MessageRendererTest, writeNamePointerChain) {
@@ -92,8 +96,8 @@ TEST_F(MessageRendererTest, writeNamePointerChain) {
renderer.writeName(Name("a.example.com."));
renderer.writeName(Name("b.example.com."));
renderer.writeName(Name("b.example.com."));
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
- buffer.getLength(), &data[0], data.size());
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+ renderer.getLength(), &data[0], data.size());
}
TEST_F(MessageRendererTest, compressMode) {
@@ -120,8 +124,8 @@ TEST_F(MessageRendererTest, writeNameCaseCompress) {
// this should match the first name in terms of compression:
renderer.writeName(Name("b.exAmple.CoM."));
renderer.writeName(Name("a.example.org."));
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
- buffer.getLength(), &data[0], data.size());
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+ renderer.getLength(), &data[0], data.size());
}
TEST_F(MessageRendererTest, writeNameCaseSensitiveCompress) {
@@ -132,8 +136,8 @@ TEST_F(MessageRendererTest, writeNameCaseSensitiveCompress) {
renderer.writeName(Name("a.example.com."));
renderer.writeName(Name("b.eXample.com."));
renderer.writeName(Name("c.eXample.com."));
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
- buffer.getLength(), &data[0], data.size());
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+ renderer.getLength(), &data[0], data.size());
}
TEST_F(MessageRendererTest, writeNameMixedCaseCompress) {
@@ -142,13 +146,15 @@ TEST_F(MessageRendererTest, writeNameMixedCaseCompress) {
renderer.writeName(Name("a.example.com."));
renderer.writeName(Name("b.eXample.com."));
- // Change the compression mode in the middle of rendering. This is an
- // unusual operation and is unlikely to happen in practice, but is still
- // allowed in this API.
- renderer.setCompressMode(MessageRenderer::CASE_INSENSITIVE);
- renderer.writeName(Name("c.b.EXAMPLE.com."));
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
- buffer.getLength(), &data[0], data.size());
+ // Change the compression mode in the middle of rendering. This is not
+ // allowed in this implementation.
+ EXPECT_THROW(renderer.setCompressMode(MessageRenderer::CASE_INSENSITIVE),
+ isc::InvalidParameter);
+
+ // Once the renderer is cleared, it's okay again.
+ renderer.clear();
+ EXPECT_NO_THROW(renderer.setCompressMode(
+ MessageRenderer::CASE_INSENSITIVE));
}
TEST_F(MessageRendererTest, writeRootName) {
@@ -164,9 +170,67 @@ TEST_F(MessageRendererTest, writeRootName) {
renderer.writeName(Name("."));
renderer.writeName(example_name);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- static_cast<const uint8_t*>(buffer.getData()),
- buffer.getLength(),
+ static_cast<const uint8_t*>(renderer.getData()),
+ renderer.getLength(),
static_cast<const uint8_t*>(expected.getData()),
expected.getLength());
}
+
+TEST_F(MessageRendererTest, setBuffer) {
+ OutputBuffer new_buffer(0);
+ renderer.setBuffer(&new_buffer);
+ EXPECT_EQ(0, new_buffer.getLength()); // the buffer should be still empty
+ renderer.writeUint32(42);
+ EXPECT_EQ(sizeof(uint32_t), new_buffer.getLength());
+ EXPECT_EQ(sizeof(uint32_t), renderer.getLength());
+
+ // Change some other internal state for the reset test below.
+ EXPECT_EQ(512, renderer.getLengthLimit());
+ renderer.setLengthLimit(4096);
+ EXPECT_EQ(4096, renderer.getLengthLimit());
+
+ // Reset the buffer to the default again. Other internal states and
+ // resources should be cleared. The used buffer should be intact.
+ renderer.setBuffer(NULL);
+ EXPECT_EQ(sizeof(uint32_t), new_buffer.getLength());
+ EXPECT_EQ(0, renderer.getLength());
+ EXPECT_EQ(512, renderer.getLengthLimit());
+}
+
+TEST_F(MessageRendererTest, setBufferErrors) {
+ OutputBuffer new_buffer(0);
+
+ // Buffer cannot be reset when the renderer is in use.
+ renderer.writeUint32(10);
+ EXPECT_THROW(renderer.setBuffer(&new_buffer), isc::InvalidParameter);
+
+ renderer.clear();
+ renderer.setBuffer(&new_buffer);
+ renderer.writeUint32(10);
+ EXPECT_THROW(renderer.setBuffer(&new_buffer), isc::InvalidParameter);
+
+ // Resetting the buffer isn't allowed for the default buffer.
+ renderer.setBuffer(NULL);
+ EXPECT_THROW(renderer.setBuffer(NULL), isc::InvalidParameter);
+
+ // It's okay to reset a temporary buffer without using it.
+ renderer.setBuffer(&new_buffer);
+ EXPECT_NO_THROW(renderer.setBuffer(NULL));
+}
+
+TEST_F(MessageRendererTest, manyRRs) {
+ // Render a large number of names, and the confirm the resulting wire
+ // data store the expected names in the correct order (1000 is an
+ // arbitrary choice).
+ for (size_t i = 0; i < 1000; ++i) {
+ renderer.writeName(Name(lexical_cast<std::string>(i) + ".example"));
+ }
+ isc::util::InputBuffer b(renderer.getData(), renderer.getLength());
+ for (size_t i = 0; i < 1000; ++i) {
+ EXPECT_EQ(Name(lexical_cast<std::string>(i) + ".example"), Name(b));
+ }
+ // This will trigger trimming excessive hash items. It shouldn't cause
+ // any disruption.
+ EXPECT_NO_THROW(renderer.clear());
+}
}
diff --git a/src/lib/dns/tests/name_unittest.cc b/src/lib/dns/tests/name_unittest.cc
index 6434229..c327bdc 100644
--- a/src/lib/dns/tests/name_unittest.cc
+++ b/src/lib/dns/tests/name_unittest.cc
@@ -130,6 +130,15 @@ TEST_F(NameTest, nonlocalObject) {
EXPECT_EQ("\\255.example.com.", downcased_global.toText());
}
+template <typename ExceptionType>
+void
+checkBadTextName(const string& txt) {
+ // Check it results in the specified type of exception as well as
+ // NameParserException.
+ EXPECT_THROW(Name(txt, false), ExceptionType);
+ EXPECT_THROW(Name(txt, false), NameParserException);
+}
+
TEST_F(NameTest, fromText) {
vector<string> strnames;
strnames.push_back("www.example.com");
@@ -151,45 +160,46 @@ TEST_F(NameTest, fromText) {
EXPECT_EQ(Name("Www.eXample.coM", true).toText(), example_name.toText());
//
- // Tests for bogus names. These should trigger an exception.
+ // Tests for bogus names. These should trigger exceptions.
//
// empty label cannot be followed by another label
- EXPECT_THROW(Name(".a"), EmptyLabel);
+ checkBadTextName<EmptyLabel>(".a");
// duplicate period
- EXPECT_THROW(Name("a.."), EmptyLabel);
+ checkBadTextName<EmptyLabel>("a..");
// label length must be < 64
- EXPECT_THROW(Name("012345678901234567890123456789"
- "012345678901234567890123456789"
- "0123"), TooLongLabel);
+ checkBadTextName<TooLongLabel>("012345678901234567890123456789"
+ "012345678901234567890123456789"
+ "0123");
// now-unsupported bitstring labels
- EXPECT_THROW(Name("\\[b11010000011101]"), BadLabelType);
+ checkBadTextName<BadLabelType>("\\[b11010000011101]");
// label length must be < 64
- EXPECT_THROW(Name("012345678901234567890123456789"
- "012345678901234567890123456789"
- "012\\x"), TooLongLabel);
+ checkBadTextName<TooLongLabel>("012345678901234567890123456789"
+ "012345678901234567890123456789"
+ "012\\x");
// but okay as long as resulting len < 64 even if the original string is
// "too long"
EXPECT_NO_THROW(Name("012345678901234567890123456789"
"012345678901234567890123456789"
"01\\x"));
// incomplete \DDD pattern (exactly 3 D's must appear)
- EXPECT_THROW(Name("\\12abc"), BadEscape);
+ checkBadTextName<BadEscape>("\\12abc");
// \DDD must not exceed 255
- EXPECT_THROW(Name("\\256"), BadEscape);
+ checkBadTextName<BadEscape>("\\256");
// Same tests for \111 as for \\x above
- EXPECT_THROW(Name("012345678901234567890123456789"
- "012345678901234567890123456789"
- "012\\111"), TooLongLabel);
+ checkBadTextName<TooLongLabel>("012345678901234567890123456789"
+ "012345678901234567890123456789"
+ "012\\111");
EXPECT_NO_THROW(Name("012345678901234567890123456789"
"012345678901234567890123456789"
"01\\111"));
// A domain name must be 255 octets or less
- EXPECT_THROW(Name("123456789.123456789.123456789.123456789.123456789."
- "123456789.123456789.123456789.123456789.123456789."
- "123456789.123456789.123456789.123456789.123456789."
- "123456789.123456789.123456789.123456789.123456789."
- "123456789.123456789.123456789.123456789.123456789."
- "1234"), TooLongName);
+ checkBadTextName<TooLongName>("123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789."
+ "123456789.1234");
// This is a possible longest name and should be accepted
EXPECT_NO_THROW(Name("123456789.123456789.123456789.123456789.123456789."
"123456789.123456789.123456789.123456789.123456789."
@@ -198,7 +208,7 @@ TEST_F(NameTest, fromText) {
"123456789.123456789.123456789.123456789.123456789."
"123"));
// \DDD must consist of 3 digits.
- EXPECT_THROW(Name("\\12"), IncompleteName);
+ checkBadTextName<IncompleteName>("\\12");
// a name with the max number of labels. should be constructed without
// an error, and its length should be the max value.
@@ -357,13 +367,12 @@ TEST_F(NameTest, toWireBuffer) {
//
TEST_F(NameTest, toWireRenderer) {
vector<unsigned char> data;
- OutputBuffer buffer(0);
- MessageRenderer renderer(buffer);
+ MessageRenderer renderer;
UnitTestUtil::readWireData(string("01610376697803636f6d00"), data);
Name("a.vix.com.").toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &data[0], data.size(),
- buffer.getData(), buffer.getLength());
+ renderer.getData(), renderer.getLength());
}
//
@@ -524,8 +533,8 @@ TEST_F(NameTest, at) {
example_name.toWire(buffer_expected);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- &data[0], data.size(),
- buffer_expected.getData(), buffer_expected.getLength());
+ &data[0], data.size(), buffer_expected.getData(),
+ buffer_expected.getLength());
// Out-of-range access: should trigger an exception.
EXPECT_THROW(example_name.at(example_name.getLength()), OutOfRange);
@@ -543,7 +552,7 @@ TEST_F(NameTest, leq) {
// small <= small is true
EXPECT_TRUE(small_name.leq(small_name));
- EXPECT_TRUE(small_name <= small_name);
+ EXPECT_LE(small_name, small_name);
// large <= small is false
EXPECT_FALSE(large_name.leq(small_name));
@@ -555,7 +564,7 @@ TEST_F(NameTest, geq) {
EXPECT_TRUE(large_name >= small_name);
EXPECT_TRUE(large_name.geq(large_name));
- EXPECT_TRUE(large_name >= large_name);
+ EXPECT_GE(large_name, large_name);
EXPECT_FALSE(small_name.geq(large_name));
EXPECT_FALSE(small_name >= large_name);
@@ -566,6 +575,7 @@ TEST_F(NameTest, lthan) {
EXPECT_TRUE(small_name < large_name);
EXPECT_FALSE(small_name.lthan(small_name));
+ // cppcheck-suppress duplicateExpression
EXPECT_FALSE(small_name < small_name);
EXPECT_FALSE(large_name.lthan(small_name));
@@ -577,6 +587,7 @@ TEST_F(NameTest, gthan) {
EXPECT_TRUE(large_name > small_name);
EXPECT_FALSE(large_name.gthan(large_name));
+ // cppcheck-suppress duplicateExpression
EXPECT_FALSE(large_name > large_name);
EXPECT_FALSE(small_name.gthan(large_name));
diff --git a/src/lib/dns/tests/nsec3hash_unittest.cc b/src/lib/dns/tests/nsec3hash_unittest.cc
new file mode 100644
index 0000000..e607c74
--- /dev/null
+++ b/src/lib/dns/tests/nsec3hash_unittest.cc
@@ -0,0 +1,222 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <dns/nsec3hash.h>
+#include <dns/rdataclass.h>
+
+using boost::scoped_ptr;
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+typedef scoped_ptr<NSEC3Hash> NSEC3HashPtr;
+
+// Commonly used NSEC3 suffix, defined to reduce the amount of typing
+const char* const nsec3_common = "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG";
+
+class NSEC3HashTest : public ::testing::Test {
+protected:
+ NSEC3HashTest() :
+ test_hash(NSEC3Hash::create(generic::NSEC3PARAM("1 0 12 aabbccdd"))),
+ test_hash_nsec3(NSEC3Hash::create(generic::NSEC3
+ ("1 0 12 aabbccdd " +
+ string(nsec3_common))))
+ {}
+
+ ~NSEC3HashTest() {
+ // Make sure we reset the hash creator to the default
+ setNSEC3HashCreator(NULL);
+ }
+
+ // An NSEC3Hash object commonly used in tests. Parameters are borrowed
+ // from the RFC5155 example. Construction of this object implicitly
+ // checks a successful case of the creation.
+ NSEC3HashPtr test_hash;
+
+ // Similar to test_hash, but created from NSEC3 RR.
+ NSEC3HashPtr test_hash_nsec3;
+};
+
+TEST_F(NSEC3HashTest, unknownAlgorithm) {
+ EXPECT_THROW(NSEC3HashPtr(
+ NSEC3Hash::create(
+ generic::NSEC3PARAM("2 0 12 aabbccdd"))),
+ UnknownNSEC3HashAlgorithm);
+ EXPECT_THROW(NSEC3HashPtr(
+ NSEC3Hash::create(
+ generic::NSEC3("2 0 12 aabbccdd " +
+ string(nsec3_common)))),
+ UnknownNSEC3HashAlgorithm);
+}
+
+// Common checks for NSEC3 hash calculation
+void
+calculateCheck(NSEC3Hash& hash) {
+ // A couple of normal cases from the RFC5155 example.
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ hash.calculate(Name("example")));
+ EXPECT_EQ("35MTHGPGCU1QG68FAB165KLNSNK3DPVL",
+ hash.calculate(Name("a.example")));
+
+ // Check case-insensitiveness
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ hash.calculate(Name("EXAMPLE")));
+}
+
+TEST_F(NSEC3HashTest, calculate) {
+ {
+ SCOPED_TRACE("calculate check with NSEC3PARAM based hash");
+ calculateCheck(*test_hash);
+ }
+ {
+ SCOPED_TRACE("calculate check with NSEC3 based hash");
+ calculateCheck(*test_hash_nsec3);
+ }
+
+ // Some boundary cases: 0-iteration and empty salt. Borrowed from the
+ // .com zone data.
+ EXPECT_EQ("CK0POJMG874LJREF7EFN8430QVIT8BSM",
+ NSEC3HashPtr(NSEC3Hash::create(generic::NSEC3PARAM("1 0 0 -")))
+ ->calculate(Name("com")));
+
+ // Using unusually large iterations, something larger than the 8-bit range.
+ // (expected hash value generated by BIND 9's dnssec-signzone)
+ EXPECT_EQ("COG6A52MJ96MNMV3QUCAGGCO0RHCC2Q3",
+ NSEC3HashPtr(NSEC3Hash::create(
+ generic::NSEC3PARAM("1 0 256 AABBCCDD")))
+ ->calculate(Name("example.org")));
+}
+
+// Common checks for match cases
+template <typename RDATAType>
+void
+matchCheck(NSEC3Hash& hash, const string& postfix) {
+ // If all parameters match, it's considered to be matched.
+ EXPECT_TRUE(hash.match(RDATAType("1 0 12 aabbccdd" + postfix)));
+
+ // Algorithm doesn't match
+ EXPECT_FALSE(hash.match(RDATAType("2 0 12 aabbccdd" + postfix)));
+ // Iterations doesn't match
+ EXPECT_FALSE(hash.match(RDATAType("1 0 1 aabbccdd" + postfix)));
+ // Salt doesn't match
+ EXPECT_FALSE(hash.match(RDATAType("1 0 12 aabbccde" + postfix)));
+ // Salt doesn't match: the other has an empty salt
+ EXPECT_FALSE(hash.match(RDATAType("1 0 12 -" + postfix)));
+ // Flags don't matter
+ EXPECT_TRUE(hash.match(RDATAType("1 1 12 aabbccdd" + postfix)));
+}
+
+TEST_F(NSEC3HashTest, matchWithNSEC3) {
+ {
+ SCOPED_TRACE("match NSEC3PARAM based hash against NSEC3 parameters");
+ matchCheck<generic::NSEC3>(*test_hash, " " + string(nsec3_common));
+ }
+ {
+ SCOPED_TRACE("match NSEC3 based hash against NSEC3 parameters");
+ matchCheck<generic::NSEC3>(*test_hash_nsec3,
+ " " + string(nsec3_common));
+ }
+}
+
+TEST_F(NSEC3HashTest, matchWithNSEC3PARAM) {
+ {
+ SCOPED_TRACE("match NSEC3PARAM based hash against NSEC3 parameters");
+ matchCheck<generic::NSEC3PARAM>(*test_hash, "");
+ }
+ {
+ SCOPED_TRACE("match NSEC3 based hash against NSEC3 parameters");
+ matchCheck<generic::NSEC3PARAM>(*test_hash_nsec3, "");
+ }
+}
+
+// A simple faked hash calculator and a dedicated creator for it.
+class TestNSEC3Hash : public NSEC3Hash {
+ virtual string calculate(const Name&) const {
+ return ("00000000000000000000000000000000");
+ }
+ virtual bool match(const generic::NSEC3PARAM&) const {
+ return (true);
+ }
+ virtual bool match(const generic::NSEC3&) const {
+ return (true);
+ }
+};
+
+// This faked creator basically creates the faked calculator regardless of
+// the passed NSEC3PARAM or NSEC3. But if the most significant bit of flags
+// is set, it will behave like the default creator.
+class TestNSEC3HashCreator : public NSEC3HashCreator {
+public:
+ virtual NSEC3Hash* create(const generic::NSEC3PARAM& param) const {
+ if ((param.getFlags() & 0x80) != 0) {
+ return (default_creator_.create(param));
+ }
+ return (new TestNSEC3Hash);
+ }
+ virtual NSEC3Hash* create(const generic::NSEC3& nsec3) const {
+ if ((nsec3.getFlags() & 0x80) != 0) {
+ return (default_creator_.create(nsec3));
+ }
+ return (new TestNSEC3Hash);
+ }
+private:
+ DefaultNSEC3HashCreator default_creator_;
+};
+
+TEST_F(NSEC3HashTest, setCreator) {
+ // Re-check an existing case using the default creator/hash implementation
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ test_hash->calculate(Name("example")));
+
+ // Replace the creator, and confirm the hash values are faked
+ TestNSEC3HashCreator test_creator;
+ setNSEC3HashCreator(&test_creator);
+ // Re-create the hash object with the new creator
+ test_hash.reset(NSEC3Hash::create(generic::NSEC3PARAM("1 0 12 aabbccdd")));
+ EXPECT_EQ("00000000000000000000000000000000",
+ test_hash->calculate(Name("example")));
+ // Same for hash from NSEC3 RDATA
+ test_hash.reset(NSEC3Hash::create(generic::NSEC3
+ ("1 0 12 aabbccdd " +
+ string(nsec3_common))));
+ EXPECT_EQ("00000000000000000000000000000000",
+ test_hash->calculate(Name("example")));
+
+ // If we set a special flag big (0x80) on creation, it will act like the
+ // default creator.
+ test_hash.reset(NSEC3Hash::create(generic::NSEC3PARAM(
+ "1 128 12 aabbccdd")));
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ test_hash->calculate(Name("example")));
+ test_hash.reset(NSEC3Hash::create(generic::NSEC3
+ ("1 128 12 aabbccdd " +
+ string(nsec3_common))));
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ test_hash->calculate(Name("example")));
+
+ // Reset the creator to default, and confirm that
+ setNSEC3HashCreator(NULL);
+ test_hash.reset(NSEC3Hash::create(generic::NSEC3PARAM("1 0 12 aabbccdd")));
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ test_hash->calculate(Name("example")));
+}
+
+} // end namespace
diff --git a/src/lib/dns/tests/question_unittest.cc b/src/lib/dns/tests/question_unittest.cc
index 1d483f2..54d0942 100644
--- a/src/lib/dns/tests/question_unittest.cc
+++ b/src/lib/dns/tests/question_unittest.cc
@@ -37,7 +37,7 @@ using namespace isc::util;
namespace {
class QuestionTest : public ::testing::Test {
protected:
- QuestionTest() : obuffer(0), renderer(obuffer),
+ QuestionTest() : obuffer(0),
example_name1(Name("foo.example.com")),
example_name2(Name("bar.example.com")),
test_question1(example_name1, RRClass::IN(),
@@ -102,8 +102,8 @@ TEST_F(QuestionTest, toWireRenderer) {
test_question1.toWire(renderer);
test_question2.toWire(renderer);
UnitTestUtil::readWireData("question_toWire2", wiredata);
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
- obuffer.getLength(), &wiredata[0], wiredata.size());
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+ renderer.getLength(), &wiredata[0], wiredata.size());
}
TEST_F(QuestionTest, toWireTruncated) {
diff --git a/src/lib/dns/tests/rdata_afsdb_unittest.cc b/src/lib/dns/tests/rdata_afsdb_unittest.cc
index 7df8d83..521bec5 100644
--- a/src/lib/dns/tests/rdata_afsdb_unittest.cc
+++ b/src/lib/dns/tests/rdata_afsdb_unittest.cc
@@ -162,7 +162,7 @@ TEST_F(Rdata_AFSDB_Test, toWireRenderer) {
renderer.clear();
// construct actual data
- Name("example.com.").toWire(obuffer);
+ renderer.writeName(Name("example.com."));
rdata_afsdb2.toWire(renderer);
// construct expected data
diff --git a/src/lib/dns/tests/rdata_cname_unittest.cc b/src/lib/dns/tests/rdata_cname_unittest.cc
index d6b02aa..2cce9bc 100644
--- a/src/lib/dns/tests/rdata_cname_unittest.cc
+++ b/src/lib/dns/tests/rdata_cname_unittest.cc
@@ -97,11 +97,11 @@ TEST_F(Rdata_CNAME_Test, toWireBuffer) {
TEST_F(Rdata_CNAME_Test, toWireRenderer) {
rdata_cname.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata_cname, sizeof(wiredata_cname));
rdata_cname2.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata_cname2, sizeof(wiredata_cname2));
}
diff --git a/src/lib/dns/tests/rdata_dhcid_unittest.cc b/src/lib/dns/tests/rdata_dhcid_unittest.cc
index 9df7043..38b1459 100644
--- a/src/lib/dns/tests/rdata_dhcid_unittest.cc
+++ b/src/lib/dns/tests/rdata_dhcid_unittest.cc
@@ -93,6 +93,7 @@ TEST_F(Rdata_DHCID_Test, getDHCIDDigest) {
TEST_F(Rdata_DHCID_Test, compare) {
// trivial case: self equivalence
+ // cppcheck-suppress uselessCallsCompare
EXPECT_EQ(0, rdata_dhcid.compare(rdata_dhcid));
in::DHCID rdata_dhcid1("0YLQvtC/0L7Qu9GPINC00LLQsCDRgNGD0LHQu9GP");
diff --git a/src/lib/dns/tests/rdata_dname_unittest.cc b/src/lib/dns/tests/rdata_dname_unittest.cc
index ebd9e0e..cf3001c 100644
--- a/src/lib/dns/tests/rdata_dname_unittest.cc
+++ b/src/lib/dns/tests/rdata_dname_unittest.cc
@@ -97,11 +97,11 @@ TEST_F(Rdata_DNAME_Test, toWireBuffer) {
TEST_F(Rdata_DNAME_Test, toWireRenderer) {
rdata_dname.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata_dname, sizeof(wiredata_dname));
rdata_dname2.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata_dname2, sizeof(wiredata_dname2));
}
diff --git a/src/lib/dns/tests/rdata_dnskey_unittest.cc b/src/lib/dns/tests/rdata_dnskey_unittest.cc
index f0596ed..86b8f69 100644
--- a/src/lib/dns/tests/rdata_dnskey_unittest.cc
+++ b/src/lib/dns/tests/rdata_dnskey_unittest.cc
@@ -90,8 +90,8 @@ TEST_F(Rdata_DNSKEY_Test, toWireRenderer) {
vector<unsigned char> data;
UnitTestUtil::readWireData("rdata_dnskey_fromWire", data);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- static_cast<const uint8_t *>(obuffer.getData()) + 2,
- obuffer.getLength() - 2, &data[2], data.size() - 2);
+ static_cast<const uint8_t *>(renderer.getData()) + 2,
+ renderer.getLength() - 2, &data[2], data.size() - 2);
}
TEST_F(Rdata_DNSKEY_Test, toWireBuffer) {
diff --git a/src/lib/dns/tests/rdata_ds_like_unittest.cc b/src/lib/dns/tests/rdata_ds_like_unittest.cc
index 9b29446..6172431 100644
--- a/src/lib/dns/tests/rdata_ds_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_ds_like_unittest.cc
@@ -115,8 +115,8 @@ TYPED_TEST(Rdata_DS_LIKE_Test, toWireRenderer) {
UnitTestUtil::readWireData("rdata_ds_fromWire", data);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
static_cast<const uint8_t*>
- (this->obuffer.getData()) + 2,
- this->obuffer.getLength() - 2,
+ (this->renderer.getData()) + 2,
+ this->renderer.getLength() - 2,
&data[2], data.size() - 2);
}
diff --git a/src/lib/dns/tests/rdata_hinfo_unittest.cc b/src/lib/dns/tests/rdata_hinfo_unittest.cc
index c52b2a0..c934a4f 100644
--- a/src/lib/dns/tests/rdata_hinfo_unittest.cc
+++ b/src/lib/dns/tests/rdata_hinfo_unittest.cc
@@ -94,8 +94,9 @@ TEST_F(Rdata_HINFO_Test, toWireRenderer) {
HINFO hinfo(hinfo_str);
hinfo.toWire(renderer);
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
- obuffer.getLength(), hinfo_rdata, sizeof(hinfo_rdata));
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+ renderer.getLength(), hinfo_rdata,
+ sizeof(hinfo_rdata));
}
TEST_F(Rdata_HINFO_Test, compare) {
diff --git a/src/lib/dns/tests/rdata_in_a_unittest.cc b/src/lib/dns/tests/rdata_in_a_unittest.cc
index 47e2bfa..2fea9a3 100644
--- a/src/lib/dns/tests/rdata_in_a_unittest.cc
+++ b/src/lib/dns/tests/rdata_in_a_unittest.cc
@@ -78,7 +78,7 @@ TEST_F(Rdata_IN_A_Test, toWireBuffer) {
TEST_F(Rdata_IN_A_Test, toWireRenderer) {
rdata_in_a.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata_in_a, sizeof(wiredata_in_a));
}
@@ -95,6 +95,7 @@ TEST_F(Rdata_IN_A_Test, compare) {
in::A large2("4.3.2.1");
// trivial case: self equivalence
+ // cppcheck-suppress uselessCallsCompare
EXPECT_EQ(0, small1.compare(small1));
// confirm these are compared as unsigned values
diff --git a/src/lib/dns/tests/rdata_in_aaaa_unittest.cc b/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
index 6fd4d0e..d8ed1d6 100644
--- a/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
+++ b/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
@@ -76,7 +76,7 @@ TEST_F(Rdata_IN_AAAA_Test, toWireBuffer) {
TEST_F(Rdata_IN_AAAA_Test, toWireRenderer) {
rdata_in_aaaa.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata_in_aaaa, sizeof(wiredata_in_aaaa));
}
@@ -91,6 +91,7 @@ TEST_F(Rdata_IN_AAAA_Test, compare) {
in::AAAA large2("8:7:6:5:4:3:2:1");
// trivial case: self equivalence
+ // cppcheck-suppress uselessCallsCompare
EXPECT_EQ(0, small1.compare(small1));
// confirm these are compared as unsigned values
diff --git a/src/lib/dns/tests/rdata_minfo_unittest.cc b/src/lib/dns/tests/rdata_minfo_unittest.cc
index 30c7c39..78e8325 100644
--- a/src/lib/dns/tests/rdata_minfo_unittest.cc
+++ b/src/lib/dns/tests/rdata_minfo_unittest.cc
@@ -142,15 +142,15 @@ TEST_F(Rdata_MINFO_Test, toWireRenderer) {
vector<unsigned char> data;
UnitTestUtil::readWireData("rdata_minfo_toWire1.wire", data);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- static_cast<const uint8_t *>(obuffer.getData()),
- obuffer.getLength(), &data[0], data.size());
+ static_cast<const uint8_t *>(renderer.getData()),
+ renderer.getLength(), &data[0], data.size());
renderer.clear();
rdata_minfo2.toWire(renderer);
vector<unsigned char> data2;
UnitTestUtil::readWireData("rdata_minfo_toWire2.wire", data2);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- static_cast<const uint8_t *>(obuffer.getData()),
- obuffer.getLength(), &data2[0], data2.size());
+ static_cast<const uint8_t *>(renderer.getData()),
+ renderer.getLength(), &data2[0], data2.size());
}
TEST_F(Rdata_MINFO_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_mx_unittest.cc b/src/lib/dns/tests/rdata_mx_unittest.cc
index c4c9757..7dc774d 100644
--- a/src/lib/dns/tests/rdata_mx_unittest.cc
+++ b/src/lib/dns/tests/rdata_mx_unittest.cc
@@ -68,12 +68,12 @@ TEST_F(Rdata_MX_Test, toWireRenderer) {
vector<unsigned char> data;
UnitTestUtil::readWireData("rdata_mx_toWire1", data);
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
- obuffer.getLength(), &data[0], data.size());
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+ renderer.getLength(), &data[0], data.size());
}
TEST_F(Rdata_MX_Test, toWireBuffer) {
- renderer.writeName(Name("example.com"));
+ Name("example.com").toWire(obuffer);
rdata_mx.toWire(obuffer);
vector<unsigned char> data;
@@ -101,6 +101,7 @@ TEST_F(Rdata_MX_Test, compare) {
generic::MX large2(256, Name("mx.example.com"));
// trivial case: self equivalence
+ // cppcheck-suppress uselessCallsCompare
EXPECT_EQ(0, small1.compare(small1));
// confirm these are compared as unsigned values
diff --git a/src/lib/dns/tests/rdata_naptr_unittest.cc b/src/lib/dns/tests/rdata_naptr_unittest.cc
index f905943..5abcaef 100644
--- a/src/lib/dns/tests/rdata_naptr_unittest.cc
+++ b/src/lib/dns/tests/rdata_naptr_unittest.cc
@@ -140,8 +140,9 @@ TEST_F(Rdata_NAPTR_Test, toWireRenderer) {
NAPTR naptr(naptr_str);
naptr.toWire(renderer);
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
- obuffer.getLength(), naptr_rdata, sizeof(naptr_rdata));
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+ renderer.getLength(), naptr_rdata,
+ sizeof(naptr_rdata));
}
TEST_F(Rdata_NAPTR_Test, toText) {
diff --git a/src/lib/dns/tests/rdata_ns_unittest.cc b/src/lib/dns/tests/rdata_ns_unittest.cc
index b805783..47582ce 100644
--- a/src/lib/dns/tests/rdata_ns_unittest.cc
+++ b/src/lib/dns/tests/rdata_ns_unittest.cc
@@ -96,11 +96,11 @@ TEST_F(Rdata_NS_Test, toWireBuffer) {
TEST_F(Rdata_NS_Test, toWireRenderer) {
rdata_ns.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata_ns, sizeof(wiredata_ns));
rdata_ns2.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata_ns2, sizeof(wiredata_ns2));
}
diff --git a/src/lib/dns/tests/rdata_nsec3_unittest.cc b/src/lib/dns/tests/rdata_nsec3_unittest.cc
index 441c6d8..edd2d4b 100644
--- a/src/lib/dns/tests/rdata_nsec3_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3_unittest.cc
@@ -39,13 +39,19 @@ using namespace isc::util::encode;
using namespace isc::dns::rdata;
namespace {
+
+// Note: some tests can be shared with NSEC3PARAM. They are unified as
+// typed tests defined in nsec3param_like_unittest.
class Rdata_NSEC3_Test : public RdataTest {
// there's nothing to specialize
public:
Rdata_NSEC3_Test() :
nsec3_txt("1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
- "NS SOA RRSIG DNSKEY NSEC3PARAM") {}
- string nsec3_txt;
+ "NS SOA RRSIG DNSKEY NSEC3PARAM"),
+ nsec3_nosalt_txt("1 1 1 - H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A" )
+ {}
+ const string nsec3_txt;
+ const string nsec3_nosalt_txt;
};
TEST_F(Rdata_NSEC3_Test, fromText) {
@@ -53,21 +59,6 @@ TEST_F(Rdata_NSEC3_Test, fromText) {
// 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 " +
@@ -79,43 +70,20 @@ TEST_F(Rdata_NSEC3_Test, fromText) {
"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("1 1 1 ADDAFEEE "
"0123456789ABCDEFGHIJKLMNOPQRSTUV "
"BIFF POW SPOON"),
InvalidRdataText);
- EXPECT_THROW(generic::NSEC3("1 1 1 ADDAFEE "
- "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW A NS SOA"),
- BadValue); // bad hex
- 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("1000000 1 1 ADDAFEEE "
- "0123456789ABCDEFGHIJKLMNOPQRSTUV A NS SOA"),
- InvalidRdataText);
- EXPECT_THROW(generic::NSEC3("1 1000000 1 ADDAFEEE "
- "0123456789ABCDEFGHIJKLMNOPQRSTUV A NS SOA"),
- InvalidRdataText);
EXPECT_THROW(generic::NSEC3("1 1 1000000 ADDAFEEE "
"0123456789ABCDEFGHIJKLMNOPQRSTUV A NS SOA"),
InvalidRdataText);
- // 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"),
+ // Next hash shouldn't be padded
+ EXPECT_THROW(generic::NSEC3("1 1 1 ADDAFEEE CPNMU=== A NS SOA"),
InvalidRdataText);
// Hash is too long. Max = 255 bytes, base32-hex converts each 5 bytes
@@ -127,34 +95,12 @@ TEST_F(Rdata_NSEC3_Test, badText) {
}
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")));
-
// 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_fromWire12.wire"),
@@ -165,7 +111,11 @@ TEST_F(Rdata_NSEC3_Test, createFromWire) {
"rdata_nsec3_fromWire14.wire"),
DNSMessageFORMERR);
- //
+ // RDLEN is too short to hold the hash length field
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire17.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.
@@ -180,27 +130,35 @@ TEST_F(Rdata_NSEC3_Test, createFromWire) {
}
}
-TEST_F(Rdata_NSEC3_Test, toWireRenderer) {
- renderer.skip(2);
- const generic::NSEC3 rdata_nsec3(nsec3_txt);
- rdata_nsec3.toWire(renderer);
-
- vector<unsigned char> data;
- UnitTestUtil::readWireData("rdata_nsec3_fromWire1", data);
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- static_cast<const uint8_t *>(obuffer.getData()) + 2,
- obuffer.getLength() - 2, &data[2], data.size() - 2);
-}
-
-TEST_F(Rdata_NSEC3_Test, toWireBuffer) {
- const generic::NSEC3 rdata_nsec3(nsec3_txt);
- rdata_nsec3.toWire(obuffer);
-}
-
TEST_F(Rdata_NSEC3_Test, assign) {
generic::NSEC3 rdata_nsec3(nsec3_txt);
generic::NSEC3 other_nsec3 = rdata_nsec3;
EXPECT_EQ(0, rdata_nsec3.compare(other_nsec3));
}
+TEST_F(Rdata_NSEC3_Test, compare) {
+ // trivial case: self equivalence
+ EXPECT_EQ(0, generic::NSEC3(nsec3_txt).compare(generic::NSEC3(nsec3_txt)));
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(generic::NSEC3(nsec3_txt).compare(*rdata_nomatch),
+ bad_cast);
+
+ // test RDATAs, sorted in the ascendent order. We only check comparison
+ // on NSEC3-specific fields. Bitmap comparison is tested in the bitmap
+ // tests. Common cases for NSEC3 and NSECPARAM3 are in their shared tests.
+ vector<generic::NSEC3> compare_set;
+ compare_set.push_back(generic::NSEC3("1 1 1 FF99EA0000 D1K6GQ38"));
+ compare_set.push_back(generic::NSEC3("1 1 1 FF99EA0000 D1K6GQ0000000000"));
+ compare_set.push_back(generic::NSEC3("1 1 1 FF99EA0000 D1K6GQ00UUUUUUUU"));
+
+ vector<generic::NSEC3>::const_iterator it;
+ const vector<generic::NSEC3>::const_iterator it_end = compare_set.end();
+ for (it = compare_set.begin(); it != it_end - 1; ++it) {
+ SCOPED_TRACE("compare " + it->toText() + " to " + (it + 1)->toText());
+ EXPECT_GT(0, (*it).compare(*(it + 1)));
+ EXPECT_LT(0, (*(it + 1)).compare(*it));
+ }
+}
+
}
diff --git a/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc b/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
new file mode 100644
index 0000000..51fef01
--- /dev/null
+++ b/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
@@ -0,0 +1,260 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/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/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+
+#include <string>
+#include <vector>
+
+using namespace std;
+using isc::UnitTestUtil;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+
+// Template for shared tests for NSEC3 and NSEC3PARAM
+template <typename RDATA_TYPE>
+class NSEC3PARAMLikeTest : public RdataTest {
+protected:
+ NSEC3PARAMLikeTest() :
+ salt_txt("1 1 1 D399EAAB" + getCommonText()),
+ nosalt_txt("1 1 1 -" + getCommonText()),
+ obuffer(0)
+ {}
+
+ RDATA_TYPE fromText(const string& rdata_text) {
+ return (RDATA_TYPE(rdata_text));
+ }
+
+ void compareCheck() const {
+ typename vector<RDATA_TYPE>::const_iterator it;
+ typename vector<RDATA_TYPE>::const_iterator const it_end =
+ compare_set.end();
+ for (it = compare_set.begin(); it != it_end - 1; ++it) {
+ SCOPED_TRACE("compare " + it->toText() + " to " +
+ (it + 1)->toText());
+ EXPECT_GT(0, (*it).compare(*(it + 1)));
+ EXPECT_LT(0, (*(it + 1)).compare(*it));
+ }
+ }
+
+ const string salt_txt; // RDATA text with salt
+ const string nosalt_txt; // RDATA text without salt
+ OutputBuffer obuffer; // used in toWire() tests
+ MessageRenderer renderer; // ditto
+ vector<RDATA_TYPE> compare_set; // used in compare() tests
+
+ // Convert generic Rdata to the corresponding derived Rdata class object.
+ // Defined here because it depends on the template parameter.
+ static const RDATA_TYPE& convert(const Rdata& rdata) {
+ return (dynamic_cast<const RDATA_TYPE&>(rdata));
+ }
+
+ // These depend on the specific RR type. We use specialized methods
+ // for them.
+ static RRType getType(); // return either RRType::NSEC3() or NSEC3PARAM()
+ static string getWireFilePrefix();
+ static string getCommonText(); // commonly used part of textual form
+};
+
+// Instantiate specific typed tests
+typedef ::testing::Types<generic::NSEC3, generic::NSEC3PARAM> TestRdataTypes;
+TYPED_TEST_CASE(NSEC3PARAMLikeTest, TestRdataTypes);
+
+template <>
+RRType
+NSEC3PARAMLikeTest<generic::NSEC3>::getType() {
+ return (RRType::NSEC3());
+}
+
+template <>
+RRType
+NSEC3PARAMLikeTest<generic::NSEC3PARAM>::getType() {
+ return (RRType::NSEC3PARAM());
+}
+
+template <>
+string
+NSEC3PARAMLikeTest<generic::NSEC3>::getWireFilePrefix() {
+ return ("rdata_nsec3_");
+}
+
+template <>
+string
+NSEC3PARAMLikeTest<generic::NSEC3PARAM>::getWireFilePrefix() {
+ return ("rdata_nsec3param_");
+}
+
+template <>
+string
+NSEC3PARAMLikeTest<generic::NSEC3>::getCommonText() {
+ // next hash + RR type bitmap
+ return (" H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
+ "NS SOA RRSIG DNSKEY NSEC3PARAM");
+}
+
+template <>
+string
+NSEC3PARAMLikeTest<generic::NSEC3PARAM>::getCommonText() {
+ // there's no more text for NSEC3PARAM
+ return ("");
+}
+
+TYPED_TEST(NSEC3PARAMLikeTest, fromText) {
+ // Numeric parameters have possible maximum values. Unusual, but must
+ // be accepted.
+ EXPECT_NO_THROW(this->fromText("255 255 65535 D399EAAB" +
+ this->getCommonText()));
+
+ // 0-length salt
+ EXPECT_EQ(0, this->fromText(this->nosalt_txt).getSalt().size());
+
+ // salt that has the possible max length
+ EXPECT_EQ(255, this->fromText("1 1 1 " + string(255 * 2, '0') +
+ this->getCommonText()).getSalt().size());
+}
+
+TYPED_TEST(NSEC3PARAMLikeTest, badText) {
+ // Bad salt hex
+ EXPECT_THROW(this->fromText("1 1 1 SPORK0" + this->getCommonText()),
+ isc::BadValue);
+ EXPECT_THROW(this->fromText("1 1 1 ADDAFEE" + this->getCommonText()),
+ isc::BadValue);
+
+ // Space within salt
+ EXPECT_THROW(this->fromText("1 1 1 ADDAFE ADDAFEEE" +
+ this->getCommonText()),
+ InvalidRdataText);
+
+ // Similar to empty salt, but not really. This shouldn't cause confusion.
+ EXPECT_THROW(this->fromText("1 1 1 --" + this->getCommonText()),
+ isc::BadValue);
+
+ // Too large algorithm
+ EXPECT_THROW(this->fromText("1000000 1 1 ADDAFEEE" + this->getCommonText()),
+ InvalidRdataText);
+
+ // Too large flags
+ EXPECT_THROW(this->fromText("1 1000000 1 ADDAFEEE" + this->getCommonText()),
+ InvalidRdataText);
+
+ // Too large iterations
+ EXPECT_THROW(this->fromText("1 1 65536 ADDAFEEE" + this->getCommonText()),
+ InvalidRdataText);
+
+ // There should be a space between "1" and "D399EAAB" (salt)
+ EXPECT_THROW(this->fromText("1 1 1D399EAAB" + this->getCommonText()),
+ InvalidRdataText);
+
+ // Salt is too long (possible max + 1 bytes)
+ EXPECT_THROW(this->fromText("1 1 1 " + string(256 * 2, '0') +
+ this->getCommonText()),
+ InvalidRdataText);
+}
+
+TYPED_TEST(NSEC3PARAMLikeTest, toText) {
+ // normal case
+ EXPECT_EQ(this->salt_txt, this->fromText(this->salt_txt).toText());
+
+ // empty salt case
+ EXPECT_EQ(this->nosalt_txt, this->fromText(this->nosalt_txt).toText());
+}
+
+TYPED_TEST(NSEC3PARAMLikeTest, createFromWire) {
+ // Normal case
+ EXPECT_EQ(0, this->fromText(this->salt_txt).compare(
+ *this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire1").c_str())));
+
+ // Too short RDLENGTH: it doesn't even contain the first 5 octets.
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire2.wire").c_str()),
+ DNSMessageFORMERR);
+
+ // salt length is too large
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire11.wire").c_str()),
+ DNSMessageFORMERR);
+
+ // empty salt. not so usual, but valid.
+ ConstRdataPtr rdata =
+ this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire13.wire").c_str());
+ EXPECT_EQ(0, this->convert(*rdata).getSalt().size());
+}
+
+template <typename OUTPUT_TYPE>
+void
+toWireCheck(RRType rrtype, OUTPUT_TYPE& output, const string& data_file) {
+ vector<uint8_t> data;
+ UnitTestUtil::readWireData(data_file.c_str(), data);
+ InputBuffer buffer(&data[0], data.size());
+ const uint16_t rdlen = buffer.readUint16();
+
+ output.clear();
+ output.writeUint16(rdlen);
+ createRdata(rrtype, RRClass::IN(), buffer, rdlen)->toWire(output);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, output.getData(),
+ output.getLength(), &data[0], data.size());
+}
+
+TYPED_TEST(NSEC3PARAMLikeTest, toWire) {
+ // normal case
+ toWireCheck(this->getType(), this->renderer,
+ this->getWireFilePrefix() + "fromWire1");
+ toWireCheck(this->getType(), this->obuffer,
+ this->getWireFilePrefix() + "fromWire1");
+
+ // empty salt
+ toWireCheck(this->getType(), this->renderer,
+ this->getWireFilePrefix() + "fromWire13.wire");
+ toWireCheck(this->getType(), this->obuffer,
+ this->getWireFilePrefix() + "fromWire13.wire");
+}
+
+TYPED_TEST(NSEC3PARAMLikeTest, compare) {
+ // test RDATAs, sorted in the ascendent order.
+ this->compare_set.push_back(this->fromText("0 0 0 D399EAAB" +
+ this->getCommonText()));
+ this->compare_set.push_back(this->fromText("1 0 0 D399EAAB" +
+ this->getCommonText()));
+ this->compare_set.push_back(this->fromText("1 1 0 D399EAAB" +
+ this->getCommonText()));
+ this->compare_set.push_back(this->fromText("1 1 1 -" +
+ this->getCommonText()));
+ this->compare_set.push_back(this->fromText("1 1 1 D399EAAB" +
+ this->getCommonText()));
+ this->compare_set.push_back(this->fromText("1 1 1 FF99EAAB" +
+ this->getCommonText()));
+ this->compare_set.push_back(this->fromText("1 1 1 FF99EA0000" +
+ this->getCommonText()));
+
+ this->compareCheck();
+}
+
+}
diff --git a/src/lib/dns/tests/rdata_nsec3param_unittest.cc b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
index 8d802d6..7558b42 100644
--- a/src/lib/dns/tests/rdata_nsec3param_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
@@ -40,9 +40,20 @@ using namespace isc::dns::rdata;
namespace {
class Rdata_NSEC3PARAM_Test : public RdataTest {
- // there's nothing to specialize
+public:
+ Rdata_NSEC3PARAM_Test() : nsec3param_txt("1 1 1 D399EAAB") {}
+ const string nsec3param_txt;
};
-string nsec3param_txt("1 0 1 D399EAAB");
+
+TEST_F(Rdata_NSEC3PARAM_Test, fromText) {
+ // With a salt
+ EXPECT_EQ(1, generic::NSEC3PARAM(nsec3param_txt).getHashalg());
+ EXPECT_EQ(1, generic::NSEC3PARAM(nsec3param_txt).getFlags());
+ // (salt is checked in the toText test)
+
+ // With an empty salt
+ EXPECT_EQ(0, generic::NSEC3PARAM("1 0 0 -").getSalt().size());
+}
TEST_F(Rdata_NSEC3PARAM_Test, toText) {
const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
@@ -50,16 +61,9 @@ TEST_F(Rdata_NSEC3PARAM_Test, toText) {
}
TEST_F(Rdata_NSEC3PARAM_Test, badText) {
- EXPECT_THROW(generic::NSEC3PARAM("1 1 1 SPORK"), BadValue); // bad hex
- EXPECT_THROW(generic::NSEC3PARAM("100000 1 1 ADDAFEE"), InvalidRdataText);
- EXPECT_THROW(generic::NSEC3PARAM("1 100000 1 ADDAFEE"), InvalidRdataText);
- EXPECT_THROW(generic::NSEC3PARAM("1 1 100000 ADDAFEE"), InvalidRdataText);
- EXPECT_THROW(generic::NSEC3PARAM("1"), InvalidRdataText);
-}
-
-TEST_F(Rdata_NSEC3PARAM_Test, DISABLED_badText) {
- // this currently fails
- EXPECT_THROW(generic::NSEC3PARAM("1 0 1D399EAAB"), InvalidRdataText);
+ // garbage space at the end
+ EXPECT_THROW(generic::NSEC3PARAM("1 1 1 D399EAAB "),
+ InvalidRdataText);
}
TEST_F(Rdata_NSEC3PARAM_Test, createFromWire) {
@@ -67,6 +71,19 @@ TEST_F(Rdata_NSEC3PARAM_Test, createFromWire) {
EXPECT_EQ(0, rdata_nsec3param.compare(
*rdataFactoryFromFile(RRType::NSEC3PARAM(), RRClass::IN(),
"rdata_nsec3param_fromWire1")));
+
+ // Short buffer cases. The data is valid NSEC3PARAM 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_nsec3param_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::NSEC3PARAM(), RRClass::IN(), b, 9),
+ InvalidBufferPosition);
+ }
}
TEST_F(Rdata_NSEC3PARAM_Test, toWireRenderer) {
@@ -77,8 +94,8 @@ TEST_F(Rdata_NSEC3PARAM_Test, toWireRenderer) {
vector<unsigned char> data;
UnitTestUtil::readWireData("rdata_nsec3param_fromWire1", data);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- static_cast<const uint8_t *>(obuffer.getData()) + 2,
- obuffer.getLength() - 2, &data[2], data.size() - 2);
+ static_cast<const uint8_t *>(renderer.getData()) + 2,
+ renderer.getLength() - 2, &data[2], data.size() - 2);
}
TEST_F(Rdata_NSEC3PARAM_Test, toWireBuffer) {
@@ -92,4 +109,16 @@ TEST_F(Rdata_NSEC3PARAM_Test, assign) {
EXPECT_EQ(0, rdata_nsec3param.compare(other_nsec3param));
}
+TEST_F(Rdata_NSEC3PARAM_Test, compare) {
+ // trivial case: self equivalence
+ EXPECT_EQ(0, generic::NSEC3PARAM(nsec3param_txt).
+ compare(generic::NSEC3PARAM(nsec3param_txt)));
+ EXPECT_EQ(0, generic::NSEC3PARAM("1 1 1 -").
+ compare(generic::NSEC3PARAM("1 1 1 -")));
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(generic::NSEC3PARAM(nsec3param_txt).compare(*rdata_nomatch),
+ bad_cast);
+}
+
}
diff --git a/src/lib/dns/tests/rdata_nsec_unittest.cc b/src/lib/dns/tests/rdata_nsec_unittest.cc
index f081cd8..88c6201 100644
--- a/src/lib/dns/tests/rdata_nsec_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec_unittest.cc
@@ -38,7 +38,7 @@ class Rdata_NSEC_Test : public RdataTest {
// there's nothing to specialize
};
-string nsec_txt("www2.isc.org. CNAME RRSIG NSEC");
+const char* const nsec_txt = "www2.isc.org. CNAME RRSIG NSEC";
TEST_F(Rdata_NSEC_Test, toText_NSEC) {
const generic::NSEC rdata_nsec(nsec_txt);
@@ -74,8 +74,8 @@ TEST_F(Rdata_NSEC_Test, toWireRenderer_NSEC) {
vector<unsigned char> data;
UnitTestUtil::readWireData("rdata_nsec_fromWire1", data);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- static_cast<const uint8_t *>(obuffer.getData()) + 2,
- obuffer.getLength() - 2, &data[2], data.size() - 2);
+ static_cast<const uint8_t *>(renderer.getData()) + 2,
+ renderer.getLength() - 2, &data[2], data.size() - 2);
}
TEST_F(Rdata_NSEC_Test, toWireBuffer_NSEC) {
@@ -95,4 +95,31 @@ TEST_F(Rdata_NSEC_Test, getNextName) {
EXPECT_EQ(Name("www2.isc.org"), generic::NSEC((nsec_txt)).getNextName());
}
+TEST_F(Rdata_NSEC_Test, compare) {
+ // trivial case: self equivalence
+ EXPECT_EQ(0, generic::NSEC("example A").
+ compare(generic::NSEC("example. A")));
+ EXPECT_EQ(0, generic::NSEC("EXAMPLE A"). // should be case insensitive
+ compare(generic::NSEC("example. A")));
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(generic::NSEC(nsec_txt).compare(*rdata_nomatch),
+ bad_cast);
+
+ // test RDATAs, sorted in the ascendent order. We only compare the
+ // next name here. Bitmap comparison is tested in the bitmap tests.
+ // Note that names are compared as wire-format data, not based on the
+ // domain name comparison.
+ vector<generic::NSEC> compare_set;
+ compare_set.push_back(generic::NSEC("a.example. A"));
+ compare_set.push_back(generic::NSEC("example. A"));
+ vector<generic::NSEC>::const_iterator it;
+ const vector<generic::NSEC>::const_iterator it_end = compare_set.end();
+ for (it = compare_set.begin(); it != it_end - 1; ++it) {
+ SCOPED_TRACE("compare " + it->toText() + " to " + (it + 1)->toText());
+ EXPECT_GT(0, (*it).compare(*(it + 1)));
+ EXPECT_LT(0, (*(it + 1)).compare(*it));
+ }
+}
+
}
diff --git a/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc b/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
index 8a90878..d7bce96 100644
--- a/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <dns/tests/unittest_util.h>
+
#include <dns/exceptions.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
@@ -22,82 +24,251 @@
#include <dns/tests/rdata_unittest.h>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <vector>
+
+using namespace std;
+using boost::lexical_cast;
+using isc::UnitTestUtil;
using namespace isc::dns;
using namespace isc::dns::rdata;
namespace {
-class Rdata_NSECBITMAP_Test : public RdataTest {
- // there's nothing to specialize
+
+// Template for shared tests for NSEC and NSEC3 bitmaps
+template <typename RDATA_TYPE>
+class NSECLikeBitmapTest : public RdataTest {
+protected:
+ RDATA_TYPE fromText(const string& rdata_text) {
+ return (RDATA_TYPE(rdata_text));
+ }
+
+ vector<RDATA_TYPE> compare_set; // used in compare() tests
+
+ void compareCheck() const {
+ typename vector<RDATA_TYPE>::const_iterator it;
+ typename vector<RDATA_TYPE>::const_iterator const it_end =
+ compare_set.end();
+ for (it = compare_set.begin(); it != it_end - 1; ++it) {
+ SCOPED_TRACE("compare " + it->toText() + " to " +
+ (it + 1)->toText());
+ EXPECT_GT(0, (*it).compare(*(it + 1)));
+ EXPECT_LT(0, (*(it + 1)).compare(*it));
+ }
+ }
+
+ // These depend on the specific RR type. We use specialized methods
+ // for them.
+ static RRType getType(); // return either RRType::NSEC() or NSEC3()
+ static string getWireFilePrefix();
+ static string getCommonText(); // commonly used part of textual form
};
+// Instantiate specific typed tests
+typedef ::testing::Types<generic::NSEC, generic::NSEC3> TestRdataTypes;
+TYPED_TEST_CASE(NSECLikeBitmapTest, TestRdataTypes);
+
+// NSEC and NSEC3 bitmaps have some subtle differences, in which case we
+// need to test them separately. Using these typedef type names with TEST_F
+// will do the trick.
+typedef NSECLikeBitmapTest<generic::NSEC3> NSEC3BitmapTest;
+typedef NSECLikeBitmapTest<generic::NSEC> NSECBitmapTest;
+
+template <>
+string
+NSECLikeBitmapTest<generic::NSEC>::getWireFilePrefix() {
+ return ("rdata_nsec_");
+}
+
+template <>
+RRType
+NSECLikeBitmapTest<generic::NSEC>::getType() {
+ return (RRType::NSEC());
+}
+
+template <>
+string
+NSECLikeBitmapTest<generic::NSEC3>::getWireFilePrefix() {
+ return ("rdata_nsec3_");
+}
+
+template <>
+RRType
+NSECLikeBitmapTest<generic::NSEC3>::getType() {
+ return (RRType::NSEC3());
+}
+
+template <>
+string
+NSECLikeBitmapTest<generic::NSEC>::getCommonText() {
+ return ("next. ");
+}
+
+template <>
+string
+NSECLikeBitmapTest<generic::NSEC3>::getCommonText() {
+ return ("1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR ");
+}
+
// 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) {
+TYPED_TEST(NSECLikeBitmapTest, createFromWire) {
// 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"),
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire4.wire").c_str()),
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"),
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire5.wire").c_str()),
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"),
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire6.wire").c_str()),
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"),
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire3").c_str()),
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"));
+ EXPECT_NO_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire7.wire").c_str()));
// 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"),
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire8.wire").c_str()),
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"),
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire9.wire").c_str()),
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"),
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire10.wire").c_str()),
DNSMessageFORMERR);
- EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
- "rdata_nsec3_fromWire10.wire"),
+}
+
+TYPED_TEST(NSECLikeBitmapTest, badText) {
+ // redundant space after the sequence
+ EXPECT_THROW(this->fromText(this->getCommonText() + "A "),
+ InvalidRdataText);
+}
+
+// This tests the result of toText() with various kinds of NSEC/NSEC3 bitmaps.
+// It also tests the "from text" constructor as a result.
+TYPED_TEST(NSECLikeBitmapTest, toText) {
+ // A simple case (some commonly seen RR types in NSEC(3) bitmaps)
+ string rdata_text = this->getCommonText() + "NS SOA RRSIG DNSKEY";
+ EXPECT_EQ(rdata_text, this->fromText(rdata_text).toText());
+
+ // Similar to above, but involves more than one bitmap window blocks.
+ rdata_text = this->getCommonText() + "NS DLV";
+ EXPECT_EQ(rdata_text, this->fromText(rdata_text).toText());
+
+ // Make sure all possible bits in a one-octet bitmap field are handled
+ // correctly.
+ // We use the range around 1024 (reasonably higher number) so it's
+ // unlikely that they have predefined mnemonic and can be safely converted
+ // to TYPEnnnn by toText().
+ for (unsigned int i = 1024; i < 1032; ++i) {
+ rdata_text = this->getCommonText() + "TYPE" + lexical_cast<string>(i);
+ EXPECT_EQ(rdata_text, this->fromText(rdata_text).toText());
+ }
+
+ // Make sure all possible 32 octets in a longest possible block are
+ // handled correctly.
+ for (unsigned int i = 1024; i < 1024 + 256; i += 8) {
+ rdata_text = this->getCommonText() + "TYPE" + lexical_cast<string>(i);
+ EXPECT_EQ(rdata_text, this->fromText(rdata_text).toText());
+ }
+
+ // Check for the highest window block.
+ rdata_text = this->getCommonText() + "TYPE65535";
+ EXPECT_EQ(rdata_text, this->fromText(rdata_text).toText());
+}
+
+TYPED_TEST(NSECLikeBitmapTest, compare) {
+ // Bit map: [win=0][len=1] 00000010
+ this->compare_set.push_back(this->fromText(this->getCommonText() + "SOA"));
+ // Bit map: [win=0][len=1] 00000010, [win=4][len=1] 10000000
+ this->compare_set.push_back(this->fromText(this->getCommonText() +
+ "SOA TYPE1024"));
+ // Bit map: [win=0][len=1] 00100000
+ this->compare_set.push_back(this->fromText(this->getCommonText() + "NS"));
+ // Bit map: [win=0][len=1] 00100010
+ this->compare_set.push_back(this->fromText(this->getCommonText() +
+ "NS SOA"));
+ // Bit map: [win=0][len=2] 00100000, 00000001
+ this->compare_set.push_back(this->fromText(this->getCommonText() +
+ "NS MX"));
+ // Bit map: [win=4][len=1] 10000000
+ this->compare_set.push_back(this->fromText(this->getCommonText() +
+ "TYPE1024"));
+
+ this->compareCheck();
+}
+
+// NSEC bitmaps must not be empty
+TEST_F(NSECBitmapTest, emptyMap) {
+ EXPECT_THROW(this->fromText("next.example").toText(), InvalidRdataText);
+
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire16.wire").c_str()),
DNSMessageFORMERR);
}
+
+// NSEC3 bitmaps can be empty
+TEST_F(NSEC3BitmapTest, emptyMap) {
+ // Read wire data wit an empty NSEC3 bitmap. This should succeed.
+ vector<uint8_t> data;
+ UnitTestUtil::readWireData((this->getWireFilePrefix() +
+ "fromWire16.wire").c_str(), data);
+ InputBuffer buffer(&data[0], data.size());
+ const uint16_t rdlen = buffer.readUint16();
+ const generic::NSEC3 empty_nsec3 =
+ dynamic_cast<const generic::NSEC3&>(*createRdata(
+ RRType::NSEC3(), RRClass::IN(),
+ buffer, rdlen));
+
+ // Check the toText() result.
+ EXPECT_EQ("1 0 1 7373737373 D1K6GQ38D1K6GQ38D1K6GQ38D1K6GQ38",
+ empty_nsec3.toText());
+
+ // Check the toWire() result.
+ OutputBuffer obuffer(0);
+ obuffer.writeUint16(rdlen);
+ empty_nsec3.toWire(obuffer);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
+ obuffer.getLength(), &data[0], data.size());
+
+ // Same for MessageRenderer.
+ obuffer.clear();
+ MessageRenderer renderer;
+ renderer.writeUint16(rdlen);
+ empty_nsec3.toWire(renderer);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+ renderer.getLength(), &data[0], data.size());
+}
+
}
diff --git a/src/lib/dns/tests/rdata_ptr_unittest.cc b/src/lib/dns/tests/rdata_ptr_unittest.cc
index 7f5de20..86160fb 100644
--- a/src/lib/dns/tests/rdata_ptr_unittest.cc
+++ b/src/lib/dns/tests/rdata_ptr_unittest.cc
@@ -100,11 +100,11 @@ TEST_F(Rdata_PTR_Test, toWireBuffer) {
TEST_F(Rdata_PTR_Test, toWireRenderer) {
rdata_ptr.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata_ptr, sizeof(wiredata_ptr));
rdata_ptr2.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata_ptr2, sizeof(wiredata_ptr2));
}
diff --git a/src/lib/dns/tests/rdata_rp_unittest.cc b/src/lib/dns/tests/rdata_rp_unittest.cc
index 07f5a93..20f32b9 100644
--- a/src/lib/dns/tests/rdata_rp_unittest.cc
+++ b/src/lib/dns/tests/rdata_rp_unittest.cc
@@ -38,7 +38,7 @@ protected:
// this also serves as a test for "from text" constructor in a normal
// case.
rdata_rp("root.example.com. rp-text.example.com."),
- obuffer(0), renderer(obuffer)
+ obuffer(0)
{}
const Name mailbox_name, text_name;
diff --git a/src/lib/dns/tests/rdata_soa_unittest.cc b/src/lib/dns/tests/rdata_soa_unittest.cc
index 07c24d5..a9d782c 100644
--- a/src/lib/dns/tests/rdata_soa_unittest.cc
+++ b/src/lib/dns/tests/rdata_soa_unittest.cc
@@ -56,8 +56,8 @@ TEST_F(Rdata_SOA_Test, toWireRenderer) {
vector<unsigned char> data;
UnitTestUtil::readWireData("rdata_soa_fromWire", data);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- static_cast<const uint8_t *>(obuffer.getData()) + 2,
- obuffer.getLength() - 2, &data[2], data.size() - 2);
+ static_cast<const uint8_t *>(renderer.getData()) + 2,
+ renderer.getLength() - 2, &data[2], data.size() - 2);
}
TEST_F(Rdata_SOA_Test, toWireBuffer) {
diff --git a/src/lib/dns/tests/rdata_srv_unittest.cc b/src/lib/dns/tests/rdata_srv_unittest.cc
index 3394f43..b194b1c 100644
--- a/src/lib/dns/tests/rdata_srv_unittest.cc
+++ b/src/lib/dns/tests/rdata_srv_unittest.cc
@@ -134,12 +134,12 @@ TEST_F(Rdata_SRV_Test, toWireBuffer) {
TEST_F(Rdata_SRV_Test, toWireRenderer) {
rdata_srv.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata_srv, sizeof(wiredata_srv));
renderer.clear();
rdata_srv2.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata_srv2, sizeof(wiredata_srv2));
}
diff --git a/src/lib/dns/tests/rdata_sshfp_unittest.cc b/src/lib/dns/tests/rdata_sshfp_unittest.cc
new file mode 100644
index 0000000..dd133ce
--- /dev/null
+++ b/src/lib/dns/tests/rdata_sshfp_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <algorithm>
+#include <string>
+
+#include <util/buffer.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>
+#include <boost/algorithm/string.hpp>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+namespace {
+class Rdata_SSHFP_Test : public RdataTest {
+ // there's nothing to specialize
+};
+
+const string sshfp_txt("2 1 123456789abcdef67890123456789abcdef67890");
+const generic::SSHFP rdata_sshfp(2, 1, "123456789abcdef67890123456789abcdef67890");
+
+TEST_F(Rdata_SSHFP_Test, createFromText) {
+ // Basic test
+ const generic::SSHFP rdata_sshfp2(sshfp_txt);
+ EXPECT_EQ(0, rdata_sshfp2.compare(rdata_sshfp));
+
+ // With different spacing
+ const generic::SSHFP rdata_sshfp3("2 1 123456789abcdef67890123456789abcdef67890");
+ EXPECT_EQ(0, rdata_sshfp3.compare(rdata_sshfp));
+
+ // Combination of lowercase and uppercase
+ const generic::SSHFP rdata_sshfp4("2 1 123456789ABCDEF67890123456789abcdef67890");
+ EXPECT_EQ(0, rdata_sshfp4.compare(rdata_sshfp));
+}
+
+TEST_F(Rdata_SSHFP_Test, badText) {
+ EXPECT_THROW(const generic::SSHFP rdata_sshfp("1"), InvalidRdataText);
+ EXPECT_THROW(const generic::SSHFP rdata_sshfp("1 2"), InvalidRdataText);
+ EXPECT_THROW(const generic::SSHFP rdata_sshfp("BUCKLE MY SHOES"), InvalidRdataText);
+ EXPECT_THROW(const generic::SSHFP rdata_sshfp("1 2 foo bar"), InvalidRdataText);
+}
+
+TEST_F(Rdata_SSHFP_Test, copy) {
+ const generic::SSHFP rdata_sshfp2(rdata_sshfp);
+ EXPECT_EQ(0, rdata_sshfp.compare(rdata_sshfp2));
+}
+
+TEST_F(Rdata_SSHFP_Test, createFromWire) {
+ // Basic test
+ EXPECT_EQ(0, rdata_sshfp.compare(
+ *rdataFactoryFromFile(RRType("SSHFP"), RRClass("IN"),
+ "rdata_sshfp_fromWire")));
+ // Combination of lowercase and uppercase
+ EXPECT_EQ(0, rdata_sshfp.compare(
+ *rdataFactoryFromFile(RRType("SSHFP"), RRClass("IN"),
+ "rdata_sshfp_fromWire2")));
+ // TBD: more tests
+}
+
+TEST_F(Rdata_SSHFP_Test, toText) {
+ EXPECT_TRUE(boost::iequals(sshfp_txt, rdata_sshfp.toText()));
+}
+
+TEST_F(Rdata_SSHFP_Test, getSSHFPAlgorithmNumber) {
+ EXPECT_EQ(2, rdata_sshfp.getSSHFPAlgorithmNumber());
+}
+
+TEST_F(Rdata_SSHFP_Test, getSSHFPFingerprintType) {
+ EXPECT_EQ(1, rdata_sshfp.getSSHFPFingerprintType());
+}
+}
diff --git a/src/lib/dns/tests/rdata_unittest.cc b/src/lib/dns/tests/rdata_unittest.cc
index fa791dc..bf1f5f7 100644
--- a/src/lib/dns/tests/rdata_unittest.cc
+++ b/src/lib/dns/tests/rdata_unittest.cc
@@ -38,8 +38,7 @@ namespace isc {
namespace dns {
namespace rdata {
RdataTest::RdataTest() :
- obuffer(0), renderer(obuffer),
- rdata_nomatch(createRdata(RRType(0), RRClass(1), "\\# 0"))
+ obuffer(0), rdata_nomatch(createRdata(RRType(0), RRClass(1), "\\# 0"))
{}
RdataPtr
@@ -245,12 +244,13 @@ TEST_F(Rdata_Unknown_Test, toWireBuffer) {
TEST_F(Rdata_Unknown_Test, toWireRenderer) {
rdata_unknown.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata_unknown, sizeof(wiredata_unknown));
}
TEST_F(Rdata_Unknown_Test, compare) {
// comparison as left-justified unsigned octet sequences:
+ // cppcheck-suppress uselessCallsCompare
EXPECT_EQ(0, rdata_unknown.compare(rdata_unknown));
generic::Generic rdata_unknown_small("\\# 4 00b2c3ff");
diff --git a/src/lib/dns/tests/rdatafields_unittest.cc b/src/lib/dns/tests/rdatafields_unittest.cc
index d619220..ef83ed4 100644
--- a/src/lib/dns/tests/rdatafields_unittest.cc
+++ b/src/lib/dns/tests/rdatafields_unittest.cc
@@ -36,9 +36,7 @@ using isc::util::InputBuffer;
namespace {
class RdataFieldsTest : public ::testing::Test {
protected:
- RdataFieldsTest() : obuffer(0), renderer_buffer(0),
- renderer(renderer_buffer),
- ns_name("example.com"),
+ RdataFieldsTest() : obuffer(0), ns_name("example.com"),
other_name("www.example.com")
{}
void constructCommonTests(const RdataFields& fields,
@@ -49,7 +47,6 @@ protected:
void constructCommonTestsRRSIG(const RdataFields& fields);
void constructCommonTestsOPT(const RdataFields& fields);
OutputBuffer obuffer;
- OutputBuffer renderer_buffer;
MessageRenderer renderer;
const Name ns_name;
const Name other_name;
diff --git a/src/lib/dns/tests/rrclass_unittest.cc b/src/lib/dns/tests/rrclass_unittest.cc
index 15f9a8c..6156be5 100644
--- a/src/lib/dns/tests/rrclass_unittest.cc
+++ b/src/lib/dns/tests/rrclass_unittest.cc
@@ -28,7 +28,7 @@ using namespace isc::util;
namespace {
class RRClassTest : public ::testing::Test {
protected:
- RRClassTest() : obuffer(0), renderer(obuffer) {}
+ RRClassTest() : obuffer(0) {}
OutputBuffer obuffer;
MessageRenderer renderer;
@@ -116,7 +116,7 @@ TEST_F(RRClassTest, toWireRenderer) {
rrclass_max.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata, sizeof(wiredata));
}
diff --git a/src/lib/dns/tests/rrset_unittest.cc b/src/lib/dns/tests/rrset_unittest.cc
index f951341..1603293 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.
-#include <stdexcept>
-
#include <util/buffer.h>
#include <dns/messagerenderer.h>
#include <dns/name.h>
@@ -24,9 +22,12 @@
#include <dns/rrttl.h>
#include <dns/rrset.h>
+#include <dns/tests/unittest_util.h>
+
#include <gtest/gtest.h>
-#include <dns/tests/unittest_util.h>
+#include <stdexcept>
+#include <sstream>
using isc::UnitTestUtil;
@@ -38,7 +39,7 @@ using namespace isc::dns::rdata;
namespace {
class RRsetTest : public ::testing::Test {
protected:
- RRsetTest() : buffer(0), renderer(buffer),
+ RRsetTest() : buffer(0),
test_name("test.example.com"),
test_domain("example.com"),
test_nsname("ns.example.com"),
@@ -112,6 +113,20 @@ TEST_F(RRsetTest, setName) {
EXPECT_EQ(test_nsname, rrset_a.getName());
}
+TEST_F(RRsetTest, isSameKind) {
+ RRset rrset_w(test_name, RRClass::IN(), RRType::A(), RRTTL(3600));
+ RRset rrset_x(test_name, RRClass::IN(), RRType::A(), RRTTL(3600));
+ RRset rrset_y(test_name, RRClass::IN(), RRType::NS(), RRTTL(3600));
+ RRset rrset_z(test_name, RRClass::CH(), RRType::A(), RRTTL(3600));
+ RRset rrset_p(test_nsname, RRClass::IN(), RRType::A(), RRTTL(3600));
+
+ EXPECT_TRUE(rrset_w.isSameKind(rrset_w));
+ EXPECT_TRUE(rrset_w.isSameKind(rrset_x));
+ EXPECT_FALSE(rrset_w.isSameKind(rrset_y));
+ EXPECT_FALSE(rrset_w.isSameKind(rrset_z));
+ EXPECT_FALSE(rrset_w.isSameKind(rrset_p));
+}
+
void
addRdataTestCommon(const RRset& rrset) {
EXPECT_EQ(2, rrset.getRdataCount());
@@ -201,8 +216,8 @@ TEST_F(RRsetTest, toWireRenderer) {
rrset_ns.toWire(renderer);
UnitTestUtil::readWireData("rrset_toWire2", wiredata);
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
- buffer.getLength(), &wiredata[0], wiredata.size());
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+ renderer.getLength(), &wiredata[0], wiredata.size());
// toWire() cannot be performed for an empty RRset.
renderer.clear();
diff --git a/src/lib/dns/tests/rrttl_unittest.cc b/src/lib/dns/tests/rrttl_unittest.cc
index fe75eb6..0e3ab44 100644
--- a/src/lib/dns/tests/rrttl_unittest.cc
+++ b/src/lib/dns/tests/rrttl_unittest.cc
@@ -28,7 +28,7 @@ using namespace isc::util;
namespace {
class RRTTLTest : public ::testing::Test {
protected:
- RRTTLTest() : obuffer(0), renderer(obuffer) {}
+ RRTTLTest() : obuffer(0) {}
OutputBuffer obuffer;
MessageRenderer renderer;
@@ -114,7 +114,7 @@ TEST_F(RRTTLTest, toWireRenderer) {
ttl_max.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata, sizeof(wiredata));
}
@@ -138,7 +138,7 @@ TEST_F(RRTTLTest, leq) {
// small <= small is true
EXPECT_TRUE(ttl_small.leq(ttl_small));
- EXPECT_TRUE(ttl_small <= ttl_small);
+ EXPECT_LE(ttl_small, ttl_small);
// large <= small is false
EXPECT_FALSE(ttl_large.leq(ttl_small));
@@ -150,7 +150,7 @@ TEST_F(RRTTLTest, geq) {
EXPECT_TRUE(ttl_large >= ttl_small);
EXPECT_TRUE(ttl_large.geq(ttl_large));
- EXPECT_TRUE(ttl_large >= ttl_large);
+ EXPECT_GE(ttl_large, ttl_large);
EXPECT_FALSE(ttl_small.geq(ttl_large));
EXPECT_FALSE(ttl_small >= ttl_large);
@@ -161,6 +161,7 @@ TEST_F(RRTTLTest, lthan) {
EXPECT_TRUE(ttl_small < ttl_large);
EXPECT_FALSE(ttl_small.lthan(ttl_small));
+ // cppcheck-suppress duplicateExpression
EXPECT_FALSE(ttl_small < ttl_small);
EXPECT_FALSE(ttl_large.lthan(ttl_small));
@@ -172,6 +173,7 @@ TEST_F(RRTTLTest, gthan) {
EXPECT_TRUE(ttl_large > ttl_small);
EXPECT_FALSE(ttl_large.gthan(ttl_large));
+ // cppcheck-suppress duplicateExpression
EXPECT_FALSE(ttl_large > ttl_large);
EXPECT_FALSE(ttl_small.gthan(ttl_large));
diff --git a/src/lib/dns/tests/rrtype_unittest.cc b/src/lib/dns/tests/rrtype_unittest.cc
index 12f6001..28ecee6 100644
--- a/src/lib/dns/tests/rrtype_unittest.cc
+++ b/src/lib/dns/tests/rrtype_unittest.cc
@@ -28,7 +28,7 @@ using namespace isc::util;
namespace {
class RRTypeTest : public ::testing::Test {
protected:
- RRTypeTest() : obuffer(0), renderer(obuffer) {}
+ RRTypeTest() : obuffer(0) {}
OutputBuffer obuffer;
MessageRenderer renderer;
@@ -120,7 +120,7 @@ TEST_F(RRTypeTest, toWireRenderer) {
rrtype_max.toWire(renderer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
+ renderer.getData(), renderer.getLength(),
wiredata, sizeof(wiredata));
}
diff --git a/src/lib/dns/tests/testdata/.gitignore b/src/lib/dns/tests/testdata/.gitignore
new file mode 100644
index 0000000..e56355b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/.gitignore
@@ -0,0 +1,117 @@
+/edns_toWire1.wire
+/edns_toWire2.wire
+/edns_toWire3.wire
+/edns_toWire4.wire
+/message_fromWire10.wire
+/message_fromWire11.wire
+/message_fromWire12.wire
+/message_fromWire13.wire
+/message_fromWire14.wire
+/message_fromWire15.wire
+/message_fromWire16.wire
+/message_fromWire17.wire
+/message_fromWire18.wire
+/message_fromWire19.wire
+/message_fromWire20.wire
+/message_fromWire21.wire
+/message_fromWire22.wire
+/message_toText1.wire
+/message_toText2.wire
+/message_toText3.wire
+/message_toWire2.wire
+/message_toWire3.wire
+/message_toWire4.wire
+/message_toWire5.wire
+/name_toWire5.wire
+/name_toWire6.wire
+/rdata_afsdb_fromWire1.wire
+/rdata_afsdb_fromWire2.wire
+/rdata_afsdb_fromWire3.wire
+/rdata_afsdb_fromWire4.wire
+/rdata_afsdb_fromWire5.wire
+/rdata_afsdb_toWire1.wire
+/rdata_afsdb_toWire2.wire
+/rdata_minfo_fromWire1.wire
+/rdata_minfo_fromWire2.wire
+/rdata_minfo_fromWire3.wire
+/rdata_minfo_fromWire4.wire
+/rdata_minfo_fromWire5.wire
+/rdata_minfo_fromWire6.wire
+/rdata_minfo_toWire1.wire
+/rdata_minfo_toWire2.wire
+/rdata_minfo_toWireUncompressed1.wire
+/rdata_minfo_toWireUncompressed2.wire
+/rdata_nsec3_fromWire10.wire
+/rdata_nsec3_fromWire11.wire
+/rdata_nsec3_fromWire12.wire
+/rdata_nsec3_fromWire13.wire
+/rdata_nsec3_fromWire14.wire
+/rdata_nsec3_fromWire15.wire
+/rdata_nsec3_fromWire16.wire
+/rdata_nsec3_fromWire17.wire
+/rdata_nsec3_fromWire2.wire
+/rdata_nsec3_fromWire4.wire
+/rdata_nsec3_fromWire5.wire
+/rdata_nsec3_fromWire6.wire
+/rdata_nsec3_fromWire7.wire
+/rdata_nsec3_fromWire8.wire
+/rdata_nsec3_fromWire9.wire
+/rdata_nsec3param_fromWire11.wire
+/rdata_nsec3param_fromWire13.wire
+/rdata_nsec3param_fromWire2.wire
+/rdata_nsec_fromWire10.wire
+/rdata_nsec_fromWire16.wire
+/rdata_nsec_fromWire4.wire
+/rdata_nsec_fromWire5.wire
+/rdata_nsec_fromWire6.wire
+/rdata_nsec_fromWire7.wire
+/rdata_nsec_fromWire8.wire
+/rdata_nsec_fromWire9.wire
+/rdata_rp_fromWire1.wire
+/rdata_rp_fromWire2.wire
+/rdata_rp_fromWire3.wire
+/rdata_rp_fromWire4.wire
+/rdata_rp_fromWire5.wire
+/rdata_rp_fromWire6.wire
+/rdata_rp_toWire1.wire
+/rdata_rp_toWire2.wire
+/rdata_rrsig_fromWire2.wire
+/rdata_soa_toWireUncompressed.wire
+/rdata_sshfp_fromWire1.wire
+/rdata_sshfp_fromWire2.wire
+/rdata_tsig_fromWire1.wire
+/rdata_tsig_fromWire2.wire
+/rdata_tsig_fromWire3.wire
+/rdata_tsig_fromWire4.wire
+/rdata_tsig_fromWire5.wire
+/rdata_tsig_fromWire6.wire
+/rdata_tsig_fromWire7.wire
+/rdata_tsig_fromWire8.wire
+/rdata_tsig_fromWire9.wire
+/rdata_tsig_toWire1.wire
+/rdata_tsig_toWire2.wire
+/rdata_tsig_toWire3.wire
+/rdata_tsig_toWire4.wire
+/rdata_tsig_toWire5.wire
+/rdata_txt_fromWire2.wire
+/rdata_txt_fromWire3.wire
+/rdata_txt_fromWire4.wire
+/rdata_txt_fromWire5.wire
+/rdatafields1.wire
+/rdatafields2.wire
+/rdatafields3.wire
+/rdatafields4.wire
+/rdatafields5.wire
+/rdatafields6.wire
+/tsig_verify1.wire
+/tsig_verify10.wire
+/tsig_verify2.wire
+/tsig_verify3.wire
+/tsig_verify4.wire
+/tsig_verify5.wire
+/tsig_verify6.wire
+/tsig_verify7.wire
+/tsig_verify8.wire
+/tsig_verify9.wire
+/tsigrecord_toWire1.wire
+/tsigrecord_toWire2.wire
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index 27edf5f..f450a59 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -20,6 +20,8 @@ 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
+# 11-15 are skipped to be consistent with NSEC3 test data
+BUILT_SOURCES += rdata_nsec_fromWire16.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
@@ -27,6 +29,10 @@ 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_nsec3_fromWire16.wire rdata_nsec3_fromWire17.wire
+BUILT_SOURCES += rdata_nsec3param_fromWire2.wire
+BUILT_SOURCES += rdata_nsec3param_fromWire11.wire
+BUILT_SOURCES += rdata_nsec3param_fromWire13.wire
BUILT_SOURCES += rdata_rrsig_fromWire2.wire
BUILT_SOURCES += rdata_minfo_fromWire1.wire rdata_minfo_fromWire2.wire
BUILT_SOURCES += rdata_minfo_fromWire3.wire rdata_minfo_fromWire4.wire
@@ -38,6 +44,7 @@ BUILT_SOURCES += rdata_rp_fromWire1.wire rdata_rp_fromWire2.wire
BUILT_SOURCES += rdata_rp_fromWire3.wire rdata_rp_fromWire4.wire
BUILT_SOURCES += rdata_rp_fromWire5.wire rdata_rp_fromWire6.wire
BUILT_SOURCES += rdata_rp_toWire1.wire rdata_rp_toWire2.wire
+BUILT_SOURCES += rdata_sshfp_fromWire1.wire rdata_sshfp_fromWire2.wire
BUILT_SOURCES += rdata_afsdb_fromWire1.wire rdata_afsdb_fromWire2.wire
BUILT_SOURCES += rdata_afsdb_fromWire3.wire rdata_afsdb_fromWire4.wire
BUILT_SOURCES += rdata_afsdb_fromWire5.wire
@@ -99,7 +106,11 @@ 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_nsec_fromWire16.spec
EXTRA_DIST += rdata_nsec3param_fromWire1
+EXTRA_DIST += rdata_nsec3param_fromWire2.spec
+EXTRA_DIST += rdata_nsec3param_fromWire11.spec
+EXTRA_DIST += rdata_nsec3param_fromWire13.spec
EXTRA_DIST += rdata_nsec3_fromWire1
EXTRA_DIST += rdata_nsec3_fromWire2.spec rdata_nsec3_fromWire3
EXTRA_DIST += rdata_nsec3_fromWire4.spec rdata_nsec3_fromWire5.spec
@@ -108,12 +119,15 @@ 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_nsec3_fromWire16.spec rdata_nsec3_fromWire17.spec
EXTRA_DIST += rdata_opt_fromWire rdata_rrsig_fromWire1
EXTRA_DIST += rdata_rrsig_fromWire2.spec
EXTRA_DIST += rdata_rp_fromWire1.spec rdata_rp_fromWire2.spec
EXTRA_DIST += rdata_rp_fromWire3.spec rdata_rp_fromWire4.spec
EXTRA_DIST += rdata_rp_fromWire5.spec rdata_rp_fromWire6.spec
EXTRA_DIST += rdata_rp_toWire1.spec rdata_rp_toWire2.spec
+EXTRA_DIST += rdata_sshfp_fromWire rdata_sshfp_fromWire2
+EXTRA_DIST += rdata_sshfp_fromWire1.spec rdata_sshfp_fromWire2.spec
EXTRA_DIST += rdata_afsdb_fromWire1.spec rdata_afsdb_fromWire2.spec
EXTRA_DIST += rdata_afsdb_fromWire3.spec rdata_afsdb_fromWire4.spec
EXTRA_DIST += rdata_afsdb_fromWire5.spec
diff --git a/src/lib/dns/tests/testdata/rdata_mx_fromWire b/src/lib/dns/tests/testdata/rdata_mx_fromWire
index f56387d..407350f 100644
--- a/src/lib/dns/tests/testdata/rdata_mx_fromWire
+++ b/src/lib/dns/tests/testdata/rdata_mx_fromWire
@@ -11,5 +11,5 @@
00 0a
# EXCHANGE: non compressed
# 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 (bytes)
-#(4) m x (7) e x a m p l e (3) c o m .
+#(2) m x (7) e x a m p l e (3) c o m .
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_fromWire16.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire16.spec
new file mode 100644
index 0000000..dac14ea
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire16.spec
@@ -0,0 +1,8 @@
+#
+# NSEC3 RDATA with an empty bitmap
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+nbitmap: 0
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.spec
new file mode 100644
index 0000000..4253349
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.spec
@@ -0,0 +1,8 @@
+#
+# An invalid NSEC3 RDATA: RDLEN is too short to include the hash len field.
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+rdlen: 10
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire1 b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire1
index 3f99f9d..1b8697f 100644
--- a/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire1
+++ b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire1
@@ -1,5 +1,5 @@
# RDLENGTH, 9 bytes
00 09
# NSEC3PARAM record
-# 1 0 1 D399EAAB
-01 00 00 01 04 d3 99 ea ab
+# 1 1 1 D399EAAB
+01 01 00 01 04 d3 99 ea ab
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire11.spec b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire11.spec
new file mode 100644
index 0000000..41e1784
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire11.spec
@@ -0,0 +1,8 @@
+#
+# An invalid NSEC3PARAM RDATA: Saltlen is too large
+#
+
+[custom]
+sections: nsec3param
+[nsec3param]
+rdlen: 7
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire13.spec b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire13.spec
new file mode 100644
index 0000000..311b2dd
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire13.spec
@@ -0,0 +1,9 @@
+#
+# A valid (but unusual) NSEC3PARAM RDATA: salt is empty.
+#
+
+[custom]
+sections: nsec3param
+[nsec3param]
+saltlen: 0
+salt: ''
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.spec
new file mode 100644
index 0000000..bce65ff
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.spec
@@ -0,0 +1,9 @@
+#
+# A malformed NSEC3PARAM RDATA: RDLEN indicates it doesn't even contain the
+# fixed 5 octects
+#
+
+[custom]
+sections: nsec3param
+[nsec3param]
+rdlen: 4
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire16.spec b/src/lib/dns/tests/testdata/rdata_nsec_fromWire16.spec
new file mode 100644
index 0000000..d7faeed
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire16.spec
@@ -0,0 +1,8 @@
+#
+# An invalid NSEC RDATA: with an empty bitmap
+#
+
+[custom]
+sections: nsec
+[nsec]
+nbitmap: 0
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire
new file mode 100644
index 0000000..added40
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire
@@ -0,0 +1,4 @@
+# SSHFP RDATA, RDLEN=22
+0016
+# ALGORITHM=2 FINGERPRINT_TYPE=1 FINGERPRINT=123456789abcdef67890123456789abcdef67890
+02 01 123456789abcdef67890123456789abcdef67890
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire1.spec
new file mode 100644
index 0000000..e28a62f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire1.spec
@@ -0,0 +1,6 @@
+#
+# A simplest form of SSHFP: all default parameters
+#
+[custom]
+sections: sshfp
+[sshfp]
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire2 b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire2
new file mode 100644
index 0000000..a695548
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire2
@@ -0,0 +1,4 @@
+# SSHFP RDATA, RDLEN=22
+0016
+# ALGORITHM=2 FINGERPRINT_TYPE=1 FINGERPRINT=123456789ABCDEF67890123456789abcdef67890
+02 01 123456789ABCDEF67890123456789abcdef67890
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire2.spec
new file mode 100644
index 0000000..59a336e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire2.spec
@@ -0,0 +1,7 @@
+#
+# SSHFP RDATA
+#
+[custom]
+sections: sshfp
+[sshfp]
+fingerprint: 123456789abcdef67890123456789abcdef67890
diff --git a/src/lib/dns/tests/tsig_unittest.cc b/src/lib/dns/tests/tsig_unittest.cc
index 7944b29..ac503e5 100644
--- a/src/lib/dns/tests/tsig_unittest.cc
+++ b/src/lib/dns/tests/tsig_unittest.cc
@@ -71,7 +71,7 @@ protected:
TSIGTest() :
tsig_ctx(NULL), qid(0x2d65), test_name("www.example.com"),
badkey_name("badkey.example.com"), test_class(RRClass::IN()),
- test_ttl(86400), message(Message::RENDER), buffer(0), renderer(buffer),
+ test_ttl(86400), message(Message::RENDER),
dummy_data(1024, 0xdd), // should be sufficiently large for all tests
dummy_record(badkey_name, any::TSIG(TSIGKey::HMACMD5_NAME(),
0x4da8877a,
@@ -125,7 +125,6 @@ protected:
const RRClass test_class;
const RRTTL test_ttl;
Message message;
- OutputBuffer buffer;
MessageRenderer renderer;
vector<uint8_t> secret;
vector<uint8_t> dummy_data;
diff --git a/src/lib/dns/tests/tsigrecord_unittest.cc b/src/lib/dns/tests/tsigrecord_unittest.cc
index e1b3e93..532681a 100644
--- a/src/lib/dns/tests/tsigrecord_unittest.cc
+++ b/src/lib/dns/tests/tsigrecord_unittest.cc
@@ -46,7 +46,7 @@ protected:
test_mac.size(), &test_mac[0],
0x2d65, 0, 0, NULL)),
test_record(test_name, test_rdata),
- buffer(0), renderer(buffer)
+ buffer(0)
{}
const Name test_name;
vector<unsigned char> test_mac;
diff --git a/src/lib/dns/tests/unittest_util.cc b/src/lib/dns/tests/unittest_util.cc
index ca9d347..66d49a8 100644
--- a/src/lib/dns/tests/unittest_util.cc
+++ b/src/lib/dns/tests/unittest_util.cc
@@ -191,3 +191,21 @@ UnitTestUtil::createRequestMessage(Message& message,
message.addQuestion(Question(name, rrclass, rrtype));
}
+void
+UnitTestUtil::createDNSSECRequestMessage(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));
+ EDNSPtr edns(new EDNS());
+ edns->setUDPSize(4096);
+ edns->setDNSSECAwareness(true);
+ message.setEDNS(edns);
+}
diff --git a/src/lib/dns/tests/unittest_util.h b/src/lib/dns/tests/unittest_util.h
index f85a921..ebb514d 100644
--- a/src/lib/dns/tests/unittest_util.h
+++ b/src/lib/dns/tests/unittest_util.h
@@ -93,6 +93,22 @@ public:
const isc::dns::Name& name,
const isc::dns::RRClass& rrclass,
const isc::dns::RRType& rrtype);
+
+ ///
+ /// Populate a DNSSEC 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.
+ /// EDNS will be added with DO=1 and bufsize 4096
+ static void
+ createDNSSECRequestMessage(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/exceptions/Makefile.am b/src/lib/exceptions/Makefile.am
index eff83b2..1d0ce2d 100644
--- a/src/lib/exceptions/Makefile.am
+++ b/src/lib/exceptions/Makefile.am
@@ -8,5 +8,5 @@ libexceptions_la_SOURCES = exceptions.h exceptions.cc
CLEANFILES = *.gcno *.gcda
-libexceptions_includedir = $(includedir)/exceptions
+libexceptions_includedir = $(includedir)/$(PACKAGE_NAME)/exceptions
libexceptions_include_HEADERS = exceptions.h
diff --git a/src/lib/exceptions/exceptions.h b/src/lib/exceptions/exceptions.h
index b68f3c4..010fd39 100644
--- a/src/lib/exceptions/exceptions.h
+++ b/src/lib/exceptions/exceptions.h
@@ -197,6 +197,38 @@ public:
throw type(__FILE__, __LINE__, oss__.str().c_str(), param1); \
} while (1)
+///
+/// Similar as isc_throw, but allows the exception to have two additional
+/// parameters (the stream/text goes first)
+#define isc_throw_2(type, stream, param1, param2) \
+ do { \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ throw type(__FILE__, __LINE__, oss__.str().c_str(), param1, param2); \
+ } while (1)
+
+///
+/// Similar as isc_throw, but allows the exception to have three additional
+/// parameters (the stream/text goes first)
+#define isc_throw_3(type, stream, param1, param2, param3) \
+ do { \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ throw type(__FILE__, __LINE__, oss__.str().c_str(), param1, param2,\
+ param3); \
+ } while (1)
+
+///
+/// Similar as isc_throw, but allows the exception to have four additional
+/// parameters (the stream/text goes first)
+#define isc_throw_4(type, stream, param1, param2, param3, param4) \
+ do { \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ throw type(__FILE__, __LINE__, oss__.str().c_str(), param1, param2,\
+ param3, param4); \
+ } while (1)
+
}
#endif // __EXCEPTIONS_H
diff --git a/src/lib/exceptions/tests/.gitignore b/src/lib/exceptions/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/lib/exceptions/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/lib/log/compiler/.gitignore b/src/lib/log/compiler/.gitignore
new file mode 100644
index 0000000..b6afc24
--- /dev/null
+++ b/src/lib/log/compiler/.gitignore
@@ -0,0 +1 @@
+/message
diff --git a/src/lib/log/compiler/Makefile.am b/src/lib/log/compiler/Makefile.am
index 1f47ba9..84cd4f6 100644
--- a/src/lib/log/compiler/Makefile.am
+++ b/src/lib/log/compiler/Makefile.am
@@ -16,3 +16,4 @@ noinst_PROGRAMS = message
message_SOURCES = message.cc
message_LDADD = $(top_builddir)/src/lib/log/liblog.la
message_LDADD += $(top_builddir)/src/lib/util/libutil.la
+message_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
diff --git a/src/lib/log/compiler/message.cc b/src/lib/log/compiler/message.cc
index 3c8a3f2..66dc9c7 100644
--- a/src/lib/log/compiler/message.cc
+++ b/src/lib/log/compiler/message.cc
@@ -25,6 +25,8 @@
#include <time.h>
#include <unistd.h>
+#include <exceptions/exceptions.h>
+
#include <util/filename.h>
#include <util/strutil.h>
@@ -245,7 +247,7 @@ writeClosingNamespace(ostream& output, const vector<string>& ns) {
}
}
-/// \breif Write python file
+/// \brief Write python file
///
/// Writes the python file containing the symbol definitions as module level
/// constants. These are objects which register themself at creation time,
@@ -301,8 +303,8 @@ writePythonFile(const string& file, MessageDictionary& dictionary,
///
/// \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 ns Namespace in which the definitions are to be placed. An empty
-/// string indicates no namespace.
+/// \param ns_components Namespace in which the definitions are to be placed.
+/// An empty string indicates no namespace.
/// \param dictionary Dictionary holding the message definitions.
/// \param output_directory if not null NULL, output files are written
/// to the given directory. If NULL, they are written to the current
@@ -325,8 +327,9 @@ writeHeaderFile(const string& file, const vector<string>& ns_components,
ofstream hfile(header_file.fullName().c_str());
if (hfile.fail()) {
- throw MessageException(LOG_OPEN_OUTPUT_FAIL, header_file.fullName(),
- strerror(errno));
+ isc_throw_4(MessageException, "Failed to open output file",
+ LOG_OPEN_OUTPUT_FAIL, header_file.fullName(),
+ strerror(errno), 0);
}
// Write the header preamble. If there is an error, we'll pick it up
@@ -359,8 +362,9 @@ writeHeaderFile(const string& file, const vector<string>& ns_components,
// Report errors (if any) and exit
if (hfile.fail()) {
- throw MessageException(LOG_WRITE_ERROR, header_file.fullName(),
- strerror(errno));
+ isc_throw_4(MessageException, "Error writing to output file",
+ LOG_WRITE_ERROR, header_file.fullName(), strerror(errno),
+ 0);
}
hfile.close();
@@ -404,8 +408,8 @@ replaceNonAlphaNum(char c) {
///
/// \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 ns Namespace in which the definitions are to be placed. An empty
-/// string indicates no namespace.
+/// \param ns_components Namespace in which the definitions are to be placed.
+/// An empty string indicates no namespace.
/// \param dictionary Dictionary holding the message definitions.
/// \param output_directory if not null NULL, output files are written
/// to the given directory. If NULL, they are written to the current
@@ -425,8 +429,9 @@ writeProgramFile(const string& file, const vector<string>& ns_components,
ofstream ccfile(program_file.fullName().c_str());
if (ccfile.fail()) {
- throw MessageException(LOG_OPEN_OUTPUT_FAIL, program_file.fullName(),
- strerror(errno));
+ isc_throw_4(MessageException, "Error opening output file",
+ LOG_OPEN_OUTPUT_FAIL, program_file.fullName(),
+ strerror(errno), 0);
}
// Write the preamble. If there is an error, we'll pick it up after
@@ -483,30 +488,31 @@ writeProgramFile(const string& file, const vector<string>& ns_components,
// Report errors (if any) and exit
if (ccfile.fail()) {
- throw MessageException(LOG_WRITE_ERROR, program_file.fullName(),
- strerror(errno));
+ isc_throw_4(MessageException, "Error writing to output file",
+ LOG_WRITE_ERROR, program_file.fullName(), strerror(errno),
+ 0);
}
ccfile.close();
}
-/// \brief Warn of Duplicate Entries
+/// \brief Error and exit if there are duplicate entries
///
-/// If the input file contained duplicate message IDs, only the first will be
-/// processed. However, we should warn about it.
+/// If the input file contained duplicate message IDs, we print an
+/// error for each of them, then exit the program with a non-0 value.
///
/// \param reader Message Reader used to read the file
void
-warnDuplicates(MessageReader& reader) {
+errorDuplicates(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";
+ if (!duplicates.empty()) {
+ cout << "Error: the following duplicate IDs were found:\n";
sort(duplicates.begin(), duplicates.end());
MessageReader::MessageIDCollection::iterator new_end =
@@ -515,6 +521,7 @@ warnDuplicates(MessageReader& reader) {
i != new_end; ++i) {
cout << " " << *i << "\n";
}
+ exit(1);
}
}
@@ -580,6 +587,9 @@ main(int argc, char* argv[]) {
MessageReader reader(&dictionary);
reader.readFile(message_file);
+ // Error (and quit) if there are of any duplicates encountered.
+ errorDuplicates(reader);
+
if (doPython) {
// Warn in case of ignored directives
if (!reader.getNamespace().empty()) {
@@ -604,8 +614,6 @@ main(int argc, char* argv[]) {
output_directory);
}
- // Finally, warn of any duplicates encountered.
- warnDuplicates(reader);
}
catch (const MessageException& e) {
// Create an error message from the ID and the text
diff --git a/src/lib/log/log_formatter.cc b/src/lib/log/log_formatter.cc
index 18c4741..c728cb5 100644
--- a/src/lib/log/log_formatter.cc
+++ b/src/lib/log/log_formatter.cc
@@ -12,8 +12,11 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include "config.h"
#include <log/log_formatter.h>
+#include <cassert>
+
using namespace std;
using namespace boost;
@@ -24,17 +27,42 @@ void
replacePlaceholder(string* message, const string& arg,
const unsigned placeholder)
{
- string mark("%" + lexical_cast<string>(placeholder));
+ const string mark("%" + lexical_cast<string>(placeholder));
size_t pos(message->find(mark));
if (pos != string::npos) {
do {
message->replace(pos, mark.size(), arg);
pos = message->find(mark, pos + arg.size());
} while (pos != string::npos);
- } else {
+ }
+#ifdef ENABLE_LOGGER_CHECKS
+ else {
+ // We're missing the placeholder, so throw an exception
+ isc_throw(MismatchedPlaceholders,
+ "Missing logger placeholder in message: " << *message);
+ }
+#else
+ else {
// We're missing the placeholder, so add some complain
- message->append(" @@Missing placeholder " + mark + " for '" + arg +
- "'@@");
+ message->append(" @@Missing placeholder " + mark + " for '" + arg + "'@@");
+ }
+#endif /* ENABLE_LOGGER_CHECKS */
+}
+
+void
+checkExcessPlaceholders(string* message, unsigned int placeholder) {
+ const string mark("%" + lexical_cast<string>(placeholder));
+ const size_t pos(message->find(mark));
+ if (pos != string::npos) {
+ // Excess placeholders were found. If we enable the harsh check,
+ // abort it. Note: ideally we'd like to throw MismatchedPlaceholders,
+ // but we can't at least for now because this function is called from
+ // the Formatter's destructor.
+#ifdef ENABLE_LOGGER_CHECKS
+ assert("Excess logger placeholders still exist in message" == NULL);
+#else
+ message->append(" @@Excess logger placeholders still exist@@");
+#endif /* ENABLE_LOGGER_CHECKS */
}
}
diff --git a/src/lib/log/log_formatter.h b/src/lib/log/log_formatter.h
index 7a9e5fa..fc60203 100644
--- a/src/lib/log/log_formatter.h
+++ b/src/lib/log/log_formatter.h
@@ -39,6 +39,27 @@ public:
};
+/// \brief Mismatched Placeholders
+///
+/// This exception is used when the number of placeholders do not match
+/// the number of arguments passed to the formatter.
+
+class MismatchedPlaceholders : public isc::Exception {
+public:
+ MismatchedPlaceholders(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+
+///
+/// \brief Internal excess placeholder checker
+///
+/// This is used internally by the Formatter to check for excess
+/// placeholders (and fewer arguments).
+void
+checkExcessPlaceholders(std::string* message, unsigned int placeholder);
+
///
/// \brief The internal replacement routine
///
@@ -142,6 +163,7 @@ public:
/// This is the place where output happens if the formatter is active.
~ Formatter() {
if (logger_) {
+ checkExcessPlaceholders(message_, ++nextPlaceholder_);
logger_->output(severity_, *message_);
delete message_;
}
diff --git a/src/lib/log/logger.h b/src/lib/log/logger.h
index 96168c0..5715bc4 100644
--- a/src/lib/log/logger.h
+++ b/src/lib/log/logger.h
@@ -15,14 +15,17 @@
#ifndef __LOGGER_H
#define __LOGGER_H
+#include <cassert>
#include <cstdlib>
#include <string>
+#include <cstring>
#include <exceptions/exceptions.h>
#include <log/logger_level.h>
#include <log/message_types.h>
#include <log/log_formatter.h>
+
namespace isc {
namespace log {
@@ -68,9 +71,19 @@ namespace log {
/// is referred to using a symbolic name. The logging code uses this name as
/// a key in a dictionary from which the message text is obtained. Such a
/// system allows for the optional replacement of message text at run time.
-/// More details about the message disction (and the compiler used to create
+/// More details about the message dictionary (and the compiler used to create
/// the symbol definitions) can be found in other modules in the src/lib/log
/// directory.
+///
+/// \section LoggingApiImplementationIssues Implementation Issues
+/// Owing to the way that the logging is implemented, notably that loggers can
+/// be declared as static external objects, there is a restriction on the
+/// length of the name of a logger component (i.e. the length of
+/// the string passed to the Logger constructor) to a maximum of 31 characters.
+/// There is no reason for this particular value other than limiting the amount
+/// of memory used. It is defined by the constant Logger::MAX_LOGGER_NAME_SIZE,
+/// and can be made larger (or smaller) if so desired. Note however, using a
+/// logger name larger than this limit will cause an assertion failure.
class LoggerImpl; // Forward declaration of the implementation class
@@ -99,6 +112,8 @@ public:
class Logger {
public:
+ /// Maximum size of a logger name
+ static const size_t MAX_LOGGER_NAME_SIZE = 31;
/// \brief Constructor
///
@@ -107,8 +122,26 @@ public:
/// \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.
- Logger(const std::string& name) : loggerptr_(NULL), name_(name)
- {}
+ ///
+ /// \note The name of the logger may be no longer than MAX_LOGGER_NAME_SIZE
+ /// else the program will halt with an assertion failure. This restriction
+ /// allows loggers to be declared statically: the name is stored in a
+ /// fixed-size array to avoid the need to allocate heap storage during
+ /// program initialization (which causes problems on some operating
+ /// systems).
+ ///
+ /// \note Note also that there is no constructor taking a std::string. This
+ /// minimises the possibility of initializing a static logger with a
+ /// string, so leading to problems mentioned above.
+ Logger(const char* name) : loggerptr_(NULL) {
+ assert(std::strlen(name) < sizeof(name_));
+ // Do the copy. Note that the assertion above has checked that the
+ // contents of "name" and a trailing null will fit within the space
+ // allocated for name_, so we could use strcpy here and be safe.
+ // However, a bit of extra paranoia doesn't hurt.
+ std::strncpy(name_, name, sizeof(name_));
+ assert(name_[sizeof(name_) - 1] == '\0');
+ }
/// \brief Destructor
virtual ~Logger();
@@ -256,8 +289,8 @@ private:
/// \brief Initialize Underlying Implementation and Set loggerptr_
void initLoggerImpl();
- LoggerImpl* loggerptr_; ///< Pointer to the underlying logger
- std::string name_; ///< Copy of the logger name
+ LoggerImpl* loggerptr_; ///< Pointer to underlying logger
+ char name_[MAX_LOGGER_NAME_SIZE + 1]; ///< Copy of the logger name
};
} // namespace log
diff --git a/src/lib/log/logger_manager.cc b/src/lib/log/logger_manager.cc
index a04fffb..8a8a36b 100644
--- a/src/lib/log/logger_manager.cc
+++ b/src/lib/log/logger_manager.cc
@@ -95,6 +95,10 @@ void
LoggerManager::init(const std::string& root, isc::log::Severity severity,
int dbglevel, const char* file)
{
+ // Load in the messages declared in the program and registered by
+ // statically-declared MessageInitializer objects.
+ MessageInitializer::loadDictionary();
+
// Save name, severity and debug level for later. No need to save the
// file name as once the local message file is read the messages will
// not be lost.
diff --git a/src/lib/log/logger_manager_impl.cc b/src/lib/log/logger_manager_impl.cc
index d69cec8..68f2bb8 100644
--- a/src/lib/log/logger_manager_impl.cc
+++ b/src/lib/log/logger_manager_impl.cc
@@ -115,8 +115,8 @@ void
LoggerManagerImpl::createFileAppender(log4cplus::Logger& logger,
const OutputOption& opt)
{
- LOG4CPLUS_OPEN_MODE_TYPE mode =
- LOG4CPLUS_FSTREAM_NAMESPACE::ios::app; // Append to existing file
+ // Append to existing file
+ std::ios::openmode mode = std::ios::app;
log4cplus::SharedAppenderPtr fileapp;
if (opt.maxsize == 0) {
diff --git a/src/lib/log/message_exception.h b/src/lib/log/message_exception.h
index eebee89..cd6caf2 100644
--- a/src/lib/log/message_exception.h
+++ b/src/lib/log/message_exception.h
@@ -15,12 +15,14 @@
#ifndef __MESSAGE_EXCEPTION_H
#define __MESSAGE_EXCEPTION_H
+#include <exceptions/exceptions.h>
+#include <log/message_types.h>
+
#include <stdexcept>
#include <string>
#include <vector>
#include <boost/lexical_cast.hpp>
-#include <log/message_types.h>
namespace isc {
namespace log {
@@ -31,16 +33,18 @@ namespace log {
/// code and its arguments to be encapsulated in an exception and thrown
/// up the stack.
-class MessageException : public std::exception {
+class MessageException : public isc::Exception {
public:
/// \brief Constructor
///
/// \param id Message identification.
/// \param lineno Line number on which error occurred (if > 0).
- MessageException(MessageID id, int lineno = 0) : id_(id)
+ MessageException(const char* file, size_t line, const char* what,
+ MessageID id, int lineno)
+ : isc::Exception(file, line, what), id_(id), lineno_(lineno)
{
- if (lineno > 0) {
+ if (lineno_ > 0) {
args_.push_back(boost::lexical_cast<std::string>(lineno));
}
}
@@ -50,8 +54,9 @@ public:
/// \param id Message identification.
/// \param arg1 First message argument.
/// \param lineno Line number on which error occurred (if > 0).
- MessageException(MessageID id, const std::string& arg1, int lineno = 0)
- : id_(id)
+ MessageException(const char* file, size_t line, const char* what,
+ MessageID id, const std::string& arg1, int lineno)
+ : isc::Exception(file, line, what), id_(id)
{
if (lineno > 0) {
args_.push_back(boost::lexical_cast<std::string>(lineno));
@@ -65,8 +70,10 @@ public:
/// \param arg1 First message argument.
/// \param arg2 Second message argument.
/// \param lineno Line number on which error occurred (if > 0).
- MessageException(MessageID id, const std::string& arg1,
- const std::string& arg2, int lineno = 0) : id_(id)
+ MessageException(const char* file, size_t line, const char *what,
+ MessageID id, const std::string& arg1,
+ const std::string& arg2, int lineno)
+ : isc::Exception(file, line, what), id_(id)
{
if (lineno > 0) {
args_.push_back(boost::lexical_cast<std::string>(lineno));
@@ -95,6 +102,7 @@ public:
private:
MessageID id_; // Exception ID
std::vector<std::string> args_; // Exception arguments
+ int lineno_;
};
} // namespace log
diff --git a/src/lib/log/message_initializer.cc b/src/lib/log/message_initializer.cc
index 0113497..3dd5da7 100644
--- a/src/lib/log/message_initializer.cc
+++ b/src/lib/log/message_initializer.cc
@@ -12,25 +12,80 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <cassert>
+#include <cstdlib>
#include <log/message_dictionary.h>
#include <log/message_initializer.h>
+
+// As explained in the header file, initialization of the message dictionary
+// is a two-stage process:
+// 1) In the MessageInitializer constructor, a pointer to the array of
+// messages is stored in a pre-defined array. Since the MessageInitializers
+// are declared statically outside a program unit, this takes place before
+// main() is called. As no heap storage is allocated in this process, we
+// should avoid the static initialization fiasco in cases where
+// initialization of system libraries is also carried out at the same time.
+// 2) After main() starts executing, loadDictionary() is called.
+//
+//
+
+namespace {
+
+// Declare the array of pointers to value arrays.
+const char** logger_values[isc::log::MessageInitializer::MAX_MESSAGE_ARRAYS];
+
+// Declare the index used to access the array. As this needs to be initialized
+// at first used, it is accessed it via a function.
+size_t& getIndex() {
+ static size_t index = 0;
+ return (index);
+}
+
+}
+
+
namespace isc {
namespace log {
-// Constructor. Just retrieve the global dictionary and load the IDs and
-// associated text into it.
+// Constructor. Add the pointer to the message array to the global array.
+// This method will trigger an assertion failure if the array overflows.
MessageInitializer::MessageInitializer(const char* values[]) {
+ assert(getIndex() < MAX_MESSAGE_ARRAYS);
+ logger_values[getIndex()++] = values;
+}
+
+// Return the number of arrays registered but not yet loaded.
+
+size_t
+MessageInitializer::getPendingCount() {
+ return (getIndex());
+}
+
+// Load the messages in the arrays registered in the logger_values array
+// into the global dictionary.
+
+void
+MessageInitializer::loadDictionary() {
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());
+ for (size_t i = 0; i < getIndex(); ++i) {
+ std::vector<std::string> repeats = global.load(logger_values[i]);
+
+ // 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());
+ }
}
+
+ // ... and mark that the messages have been loaded. (This avoids a lot
+ // of "duplicate message ID" messages in some of the unit tests where the
+ // logging initialization code may be called multiple times.)
+ getIndex() = 0;
}
// Return reference to duplicate array
diff --git a/src/lib/log/message_initializer.h b/src/lib/log/message_initializer.h
index 7516823..28b0e61 100644
--- a/src/lib/log/message_initializer.h
+++ b/src/lib/log/message_initializer.h
@@ -15,6 +15,7 @@
#ifndef __MESSAGEINITIALIZER_H
#define __MESSAGEINITIALIZER_H
+#include <cstdlib>
#include <string>
#include <vector>
#include <log/message_dictionary.h>
@@ -37,28 +38,63 @@ namespace log {
/// :
/// NULL
/// };
-/// MessageDictionaryHelper xyz(values);
+/// MessageInitializer 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.
+/// All that needed is for the module containing the definitions to be
+/// included in the execution unit.
///
-/// 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.
+/// To avoid static initialization fiasco problems, the initialization is
+/// carried out in two stages:
+/// - The constructor adds a pointer to the values array to a pre-defined array
+/// of pointers.
+/// - During the run-time initialization of the logging system, the static
+/// method loadDictionary() is called to load the message dictionary.
+/// This way, no heap storage is allocated during the static initialization,
+/// something that may give problems on some operating systems.
+///
+/// \note The maximum number of message arrays that can be added to the
+/// dictionary in this way is defined by the constant
+/// MessageInitializer::MAX_MESSAGE_ARRAYS. This is set to 256 as a compromise
+/// between wasted space and allowing for future expansion, but can be
+/// changed (by editing the source file) to any value desired.
+///
+/// When messages are added to the dictionary, the are added via the
+/// MessageDictionary::add() method, so any duplicates are stored in the
+/// global dictionary's overflow vector whence they can be retrieved at
+/// run-time.
class MessageInitializer {
public:
+ /// Maximum number of message arrays that can be initialized in this way
+ static const size_t MAX_MESSAGE_ARRAYS = 256;
/// \brief Constructor
///
- /// Adds the array of values to the global dictionary, and notes any
- /// duplicates.
+ /// Adds a pointer to the array of messages to the global array of
+ /// pointers to message arrays.
///
/// \param values NULL-terminated array of alternating identifier strings
- /// and associated message text.
+ /// and associated message text. N.B. This object stores a pointer to the
+ /// passed array; the array MUST remain valid at least until
+ /// loadDictionary() has been called.
MessageInitializer(const char* values[]);
+ /// \brief Obtain pending load count
+ ///
+ /// Returns the number of message arrays that will be loaded by the next
+ /// call to loadDictionary().
+ ///
+ /// \return Number of registered message arrays. This is reset to zero
+ /// when loadDictionary() is called.
+ static size_t getPendingCount();
+
+ /// \brief Run-Time Initialization
+ ///
+ /// Loops through the internal array of pointers to message arrays
+ /// and adds the messages to the internal dictionary. This is called
+ /// during run-time initialization.
+ static void loadDictionary();
+
/// \brief Return Duplicates
///
/// When messages are added to the global dictionary, any duplicates are
diff --git a/src/lib/log/message_reader.cc b/src/lib/log/message_reader.cc
index 2710ab8..b5a4d35 100644
--- a/src/lib/log/message_reader.cc
+++ b/src/lib/log/message_reader.cc
@@ -48,7 +48,8 @@ MessageReader::readFile(const string& file, MessageReader::Mode mode) {
// Open the file.
ifstream infile(file.c_str());
if (infile.fail()) {
- throw MessageException(LOG_INPUT_OPEN_FAIL, file, strerror(errno));
+ isc_throw_4(MessageException, "Failed to open message file",
+ LOG_INPUT_OPEN_FAIL, file, strerror(errno), 0);
}
// Loop round reading it. As we process the file one line at a time,
@@ -65,7 +66,8 @@ MessageReader::readFile(const string& file, MessageReader::Mode mode) {
// Why did the loop terminate?
if (!infile.eof()) {
- throw MessageException(LOG_READ_ERROR, file, strerror(errno));
+ isc_throw_4(MessageException, "Error reading message file",
+ LOG_READ_ERROR, file, strerror(errno), 0);
}
infile.close();
}
@@ -114,7 +116,9 @@ MessageReader::parseDirective(const std::string& text) {
} else {
// Unrecognised directive
- throw MessageException(LOG_UNRECOGNISED_DIRECTIVE, tokens[0], lineno_);
+ isc_throw_3(MessageException, "Unrecognized directive",
+ LOG_UNRECOGNISED_DIRECTIVE, tokens[0],
+ lineno_);
}
}
@@ -138,13 +142,15 @@ MessageReader::parsePrefix(const vector<string>& tokens) {
// and numeric characters (and underscores) and does not start with a
// digit.
if (invalidSymbol(prefix_)) {
- throw MessageException(LOG_PREFIX_INVALID_ARG, prefix_, lineno_);
+ isc_throw_3(MessageException, "Invalid prefix",
+ LOG_PREFIX_INVALID_ARG, prefix_, lineno_);
}
} else {
// Too many arguments
- throw MessageException(LOG_PREFIX_EXTRA_ARGS, lineno_);
+ isc_throw_2(MessageException, "Too many arguments",
+ LOG_PREFIX_EXTRA_ARGS, lineno_);
}
}
@@ -172,10 +178,12 @@ MessageReader::parseNamespace(const vector<string>& tokens) {
// Check argument count
if (tokens.size() < 2) {
- throw MessageException(LOG_NAMESPACE_NO_ARGS, lineno_);
+ isc_throw_2(MessageException, "No arguments", LOG_NAMESPACE_NO_ARGS,
+ lineno_);
} else if (tokens.size() > 2) {
- throw MessageException(LOG_NAMESPACE_EXTRA_ARGS, lineno_);
+ isc_throw_2(MessageException, "Too many arguments",
+ LOG_NAMESPACE_EXTRA_ARGS, lineno_);
}
@@ -187,12 +195,14 @@ MessageReader::parseNamespace(const vector<string>& tokens) {
"abcdefghijklmnopqrstuvwxyz"
"0123456789_:";
if (tokens[1].find_first_not_of(valid_chars) != string::npos) {
- throw MessageException(LOG_NAMESPACE_INVALID_ARG, tokens[1], lineno_);
+ isc_throw_3(MessageException, "Invalid argument",
+ LOG_NAMESPACE_INVALID_ARG, tokens[1], lineno_);
}
// All OK - unless the namespace has already been set.
if (ns_.size() != 0) {
- throw MessageException(LOG_DUPLICATE_NAMESPACE, lineno_);
+ isc_throw_2(MessageException, "Duplicate namespace",
+ LOG_DUPLICATE_NAMESPACE, lineno_);
}
// Prefix has not been set, so set it and return success.
@@ -219,7 +229,8 @@ MessageReader::parseMessage(const std::string& text, MessageReader::Mode mode) {
// A line comprising just the message introducer is not valid.
if (text.size() == 1) {
- throw MessageException(LOG_NO_MESSAGE_ID, text, lineno_);
+ isc_throw_3(MessageException, "No message ID", LOG_NO_MESSAGE_ID,
+ text, lineno_);
}
// Strip off the introducer and any leading space after that.
@@ -230,7 +241,8 @@ MessageReader::parseMessage(const std::string& text, MessageReader::Mode mode) {
if (first_delim == string::npos) {
// Just a single token in the line - this is not valid
- throw MessageException(LOG_NO_MESSAGE_TEXT, message_line, lineno_);
+ isc_throw_3(MessageException, "No message text", LOG_NO_MESSAGE_TEXT,
+ message_line, lineno_);
}
// Extract the first token into the message ID, preceding it with the
@@ -240,7 +252,8 @@ MessageReader::parseMessage(const std::string& text, MessageReader::Mode mode) {
string ident = prefix_ + message_line.substr(0, first_delim);
if (prefix_.empty()) {
if (invalidSymbol(ident)) {
- throw MessageException(LOG_INVALID_MESSAGE_ID, ident, lineno_);
+ isc_throw_3(MessageException, "Invalid message ID",
+ LOG_INVALID_MESSAGE_ID, ident, lineno_);
}
}
isc::util::str::uppercase(ident);
@@ -252,7 +265,8 @@ MessageReader::parseMessage(const std::string& text, MessageReader::Mode mode) {
// ?? 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(LOG_NO_MESSAGE_TEXT, message_line, lineno_);
+ isc_throw_3(MessageException, "No message text", LOG_NO_MESSAGE_TEXT,
+ message_line, lineno_);
}
// Add the result to the dictionary and to the non-added list if the add to
diff --git a/src/lib/log/message_reader.h b/src/lib/log/message_reader.h
index eded9c6..a468d43 100644
--- a/src/lib/log/message_reader.h
+++ b/src/lib/log/message_reader.h
@@ -61,7 +61,7 @@ public:
/// 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)
+ dictionary_(dictionary), lineno_(0)
{}
/// \brief Virtual Destructor
diff --git a/src/lib/log/tests/.gitignore b/src/lib/log/tests/.gitignore
new file mode 100644
index 0000000..41b863b
--- /dev/null
+++ b/src/lib/log/tests/.gitignore
@@ -0,0 +1,11 @@
+/console_test.sh
+/destination_test.sh
+/init_logger_test
+/init_logger_test.sh
+/initializer_unittests_1
+/initializer_unittests_2
+/local_file_test.sh
+/logger_example
+/run_unittests
+/severity_test.sh
+/tempdir.h
diff --git a/src/lib/log/tests/Makefile.am b/src/lib/log/tests/Makefile.am
index 53e97a1..6f3d768 100644
--- a/src/lib/log/tests/Makefile.am
+++ b/src/lib/log/tests/Makefile.am
@@ -3,15 +3,52 @@ SUBDIRS = .
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
+AM_LDADD =
+AM_LDFLAGS =
if USE_STATIC_LINK
-AM_LDFLAGS = -static
+AM_LDFLAGS += -static
endif
CLEANFILES = *.gcno *.gcda
-TESTS =
+noinst_PROGRAMS = logger_example
+logger_example_SOURCES = logger_example.cc
+logger_example_CPPFLAGS = $(AM_CPPFLAGS)
+logger_example_LDFLAGS = $(AM_LDFLAGS)
+logger_example_LDADD = $(top_builddir)/src/lib/log/liblog.la
+logger_example_LDADD += $(top_builddir)/src/lib/util/libutil.la
+logger_example_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+logger_example_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
+
+noinst_PROGRAMS += init_logger_test
+init_logger_test_SOURCES = init_logger_test.cc
+init_logger_test_CPPFLAGS = $(AM_CPPFLAGS)
+init_logger_test_LDFLAGS = $(AM_LDFLAGS)
+init_logger_test_LDADD = $(top_builddir)/src/lib/log/liblog.la
+init_logger_test_LDADD += $(top_builddir)/src/lib/util/libutil.la
+init_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+init_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
+
if HAVE_GTEST
+TESTS =
+
+# Define the flags used in each set of tests
+if USE_CLANGPP
+# Workaround unused variables tcout and tcerr in log4cplus's streams.h.
+AM_CXXFLAGS += -Wno-unused-variable
+endif
+AM_CPPFLAGS += $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
+AM_LDFLAGS += $(GTEST_LDFLAGS)
+
+AM_LDADD += $(top_builddir)/src/lib/log/liblog.la
+AM_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+AM_LDADD += $(top_builddir)/src/lib/util/libutil.la
+AM_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+AM_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+AM_LDADD += $(GTEST_LDADD)
+
+# Set of unit tests for the general logging classes
TESTS += run_unittests
run_unittests_SOURCES = run_unittests.cc
run_unittests_SOURCES += log_formatter_unittest.cc
@@ -23,47 +60,39 @@ run_unittests_SOURCES += logger_support_unittest.cc
run_unittests_SOURCES += logger_unittest.cc
run_unittests_SOURCES += logger_specification_unittest.cc
run_unittests_SOURCES += message_dictionary_unittest.cc
-run_unittests_SOURCES += message_initializer_unittest_2.cc
-run_unittests_SOURCES += message_initializer_unittest.cc
run_unittests_SOURCES += message_reader_unittest.cc
run_unittests_SOURCES += output_option_unittest.cc
-run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
-run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
-
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS)
run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
-if USE_CLANGPP
-# This is to workaround unused variables tcout and tcerr in
-# log4cplus's streams.h.
-run_unittests_CXXFLAGS += -Wno-unused-variable
-endif
-run_unittests_LDADD = $(GTEST_LDADD)
-run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
-run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
-run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
-run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
-run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
-endif
+run_unittests_LDADD = $(AM_LDADD)
+run_unittests_LDFLAGS = $(AM_LDFLAGS)
-noinst_PROGRAMS = logger_example
-logger_example_SOURCES = logger_example.cc
-logger_example_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-logger_example_LDFLAGS = $(AM_LDFLAGS)
-logger_example_LDADD = $(LOG4CPLUS_LIBS)
-logger_example_LDADD += $(top_builddir)/src/lib/log/liblog.la
-logger_example_LDADD += $(top_builddir)/src/lib/util/libutil.la
-logger_example_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+# logging initialization tests. These are put in separate programs to
+# ensure that the initialization status at the start of each test is known,
+# and to prevent circumstances where the execution of one test affects the
+# starting conditions of the next.
+TESTS += initializer_unittests_1
+initializer_unittests_1_SOURCES = run_initializer_unittests.cc
+initializer_unittests_1_SOURCES += message_initializer_1_unittest.cc
+initializer_unittests_1_SOURCES += message_initializer_1a_unittest.cc
-noinst_PROGRAMS += init_logger_test
-init_logger_test_SOURCES = init_logger_test.cc
-init_logger_test_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-init_logger_test_LDFLAGS = $(AM_LDFLAGS)
-init_logger_test_LDADD = $(LOG4CPLUS_LIBS)
-init_logger_test_LDADD += $(top_builddir)/src/lib/log/liblog.la
-init_logger_test_LDADD += $(top_builddir)/src/lib/util/libutil.la
-init_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+initializer_unittests_1_CPPFLAGS = $(AM_CPPFLAGS)
+initializer_unittests_1_CXXFLAGS = $(AM_CXXFLAGS)
+initializer_unittests_1_LDADD = $(AM_LDADD)
+initializer_unittests_1_LDFLAGS = $(AM_LDFLAGS)
+
+TESTS += initializer_unittests_2
+initializer_unittests_2_SOURCES = run_initializer_unittests.cc
+initializer_unittests_2_SOURCES += message_initializer_2_unittest.cc
+
+initializer_unittests_2_CPPFLAGS = $(AM_CPPFLAGS)
+initializer_unittests_2_CXXFLAGS = $(AM_CXXFLAGS)
+initializer_unittests_2_LDADD = $(AM_LDADD)
+initializer_unittests_2_LDFLAGS = $(AM_LDFLAGS)
noinst_PROGRAMS += $(TESTS)
+endif
# Additional test using the shell. These are principally tests
# where the global logging environment is affected, and where the
diff --git a/src/lib/log/tests/log_formatter_unittest.cc b/src/lib/log/tests/log_formatter_unittest.cc
index b91665d..83fc062 100644
--- a/src/lib/log/tests/log_formatter_unittest.cc
+++ b/src/lib/log/tests/log_formatter_unittest.cc
@@ -12,7 +12,11 @@
// 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 <util/unittests/resource.h>
+
#include <log/log_formatter.h>
#include <log/logger_level.h>
@@ -94,16 +98,66 @@ TEST_F(FormatterTest, multiArg) {
EXPECT_EQ("The arguments are switched", outputs[0].second);
}
-// Can survive and complains if placeholder is missing
-TEST_F(FormatterTest, missingPlace) {
- EXPECT_NO_THROW(Formatter(isc::log::INFO, s("Missing the first %2"), this).
- arg("missing").arg("argument"));
+#ifdef ENABLE_LOGGER_CHECKS
+
+TEST_F(FormatterTest, mismatchedPlaceholders) {
+ // Throws MismatchedPlaceholders exception if the placeholder is missing
+ // for a supplied argument.
+ EXPECT_THROW(Formatter(isc::log::INFO, s("Missing the second %1"), this).
+ arg("argument").arg("missing"),
+ isc::log::MismatchedPlaceholders);
+
+#ifdef EXPECT_DEATH
+ // Likewise, if there's a redundant placeholder (or missing argument), the
+ // check detects it and aborts the program. Due to the restriction of the
+ // current implementation, it doesn't throw.
+ EXPECT_DEATH({
+ isc::util::unittests::dontCreateCoreDumps();
+ Formatter(isc::log::INFO, s("Too many arguments in %1 %2"), this).
+ arg("only one");
+ }, ".*");
+
+ // Mixed case of above two: the exception will be thrown due to the missing
+ // placeholder, but before even it's caught the program will be aborted
+ // due to the unused placeholder as a result of the exception.
+ EXPECT_DEATH({
+ isc::util::unittests::dontCreateCoreDumps();
+ Formatter(isc::log::INFO, s("Missing the first %2"), this).
+ arg("missing").arg("argument");
+ }, ".*");
+#endif /* EXPECT_DEATH */
+}
+
+#else
+
+// If logger checks are not enabled, nothing is thrown
+TEST_F(FormatterTest, mismatchedPlaceholders) {
+ Formatter(isc::log::INFO, s("Missing the second %1"), this).
+ arg("argument").arg("missing");
ASSERT_EQ(1, outputs.size());
EXPECT_EQ(isc::log::INFO, outputs[0].first);
+ EXPECT_EQ("Missing the second argument "
+ "@@Missing placeholder %2 for 'missing'@@", outputs[0].second);
+
+ EXPECT_NO_THROW(Formatter(isc::log::INFO,
+ s("Too many arguments in %1 %2"), this).
+ arg("only one"));
+ ASSERT_EQ(2, outputs.size());
+ EXPECT_EQ(isc::log::INFO, outputs[1].first);
+ EXPECT_EQ("Too many arguments in only one %2 "
+ "@@Excess logger placeholders still exist@@",
+ outputs[1].second);
+
+ EXPECT_NO_THROW(Formatter(isc::log::INFO, s("Missing the first %2"), this).
+ arg("missing").arg("argument"));
+ ASSERT_EQ(3, outputs.size());
+ EXPECT_EQ(isc::log::INFO, outputs[2].first);
EXPECT_EQ("Missing the first argument "
- "@@Missing placeholder %1 for 'missing'@@", outputs[0].second);
+ "@@Missing placeholder %1 for 'missing'@@", outputs[2].second);
}
+#endif /* ENABLE_LOGGER_CHECKS */
+
// Can replace multiple placeholders
TEST_F(FormatterTest, multiPlaceholder) {
Formatter(isc::log::INFO, s("The %1 is the %1"), this).
diff --git a/src/lib/log/tests/logger_example.cc b/src/lib/log/tests/logger_example.cc
index 2170066..d3f08f3 100644
--- a/src/lib/log/tests/logger_example.cc
+++ b/src/lib/log/tests/logger_example.cc
@@ -105,7 +105,7 @@ void usage() {
// messages. Looking at the output determines whether the program worked.
int main(int argc, char** argv) {
- const string ROOT_NAME = "example";
+ const char* ROOT_NAME = "example";
bool sw_found = false; // Set true if switch found
bool c_found = false; // Set true if "-c" found
diff --git a/src/lib/log/tests/logger_manager_unittest.cc b/src/lib/log/tests/logger_manager_unittest.cc
index 9eb85ad..584d0f5 100644
--- a/src/lib/log/tests/logger_manager_unittest.cc
+++ b/src/lib/log/tests/logger_manager_unittest.cc
@@ -201,7 +201,7 @@ TEST_F(LoggerManagerTest, FileLogger) {
// Scope-limit the logger to ensure it is destroyed after the brief
// check. This adds weight to the idea that the logger will not
// keep the file open.
- Logger logger(file_spec.getLoggerName());
+ Logger logger(file_spec.getLoggerName().c_str());
LOG_FATAL(logger, LOG_DUPLICATE_MESSAGE_ID).arg("test");
ids.push_back(LOG_DUPLICATE_MESSAGE_ID);
@@ -223,7 +223,7 @@ TEST_F(LoggerManagerTest, FileLogger) {
manager.process(spec.begin(), spec.end());
// Create a new instance of the logger and log three more messages.
- Logger logger(file_spec.getLoggerName());
+ Logger logger(file_spec.getLoggerName().c_str());
LOG_FATAL(logger, LOG_NO_SUCH_MESSAGE).arg("test");
ids.push_back(LOG_NO_SUCH_MESSAGE);
@@ -275,7 +275,7 @@ TEST_F(LoggerManagerTest, FileSizeRollover) {
// three files as for the log4cplus implementation, the files appear to
// be rolled after the message is logged.
{
- Logger logger(file_spec.getLoggerName());
+ Logger logger(file_spec.getLoggerName().c_str());
LOG_FATAL(logger, LOG_NO_SUCH_MESSAGE).arg(big_arg);
LOG_FATAL(logger, LOG_DUPLICATE_NAMESPACE).arg(big_arg);
}
@@ -295,8 +295,8 @@ TEST_F(LoggerManagerTest, FileSizeRollover) {
// a .3 version does not exist.
manager.process(spec);
{
- Logger logger(file_spec.getLoggerName());
- LOG_FATAL(logger, LOG_NO_MESSAGE_TEXT).arg(big_arg);
+ Logger logger(file_spec.getLoggerName().c_str());
+ LOG_FATAL(logger, LOG_NO_MESSAGE_TEXT).arg("42").arg(big_arg);
}
LoggerManager::reset(); // Ensure files are closed
diff --git a/src/lib/log/tests/logger_support_unittest.cc b/src/lib/log/tests/logger_support_unittest.cc
index b418906..c626c6d 100644
--- a/src/lib/log/tests/logger_support_unittest.cc
+++ b/src/lib/log/tests/logger_support_unittest.cc
@@ -79,5 +79,5 @@ TEST_F(LoggerSupportTest, LoggingInitializationCheck) {
// ... and check that they work when logging is initialized.
setLoggingInitialized(true);
EXPECT_NO_THROW(test_logger.isDebugEnabled());
- EXPECT_NO_THROW(test_logger.info(LOG_INPUT_OPEN_FAIL));
+ EXPECT_NO_THROW(test_logger.info(LOG_INPUT_OPEN_FAIL).arg("foo").arg("bar"));
}
diff --git a/src/lib/log/tests/logger_unittest.cc b/src/lib/log/tests/logger_unittest.cc
index edca9ce..069205e 100644
--- a/src/lib/log/tests/logger_unittest.cc
+++ b/src/lib/log/tests/logger_unittest.cc
@@ -17,6 +17,8 @@
#include <gtest/gtest.h>
+#include <util/unittests/resource.h>
+
#include <log/logger.h>
#include <log/logger_manager.h>
#include <log/logger_name.h>
@@ -58,8 +60,8 @@ TEST_F(LoggerTest, Name) {
TEST_F(LoggerTest, GetLogger) {
- const string name1 = "alpha";
- const string name2 = "beta";
+ const char* name1 = "alpha";
+ const char* name2 = "beta";
// Instantiate two loggers that should be the same
Logger logger1(name1);
@@ -348,3 +350,32 @@ TEST_F(LoggerTest, IsDebugEnabledLevel) {
EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL));
EXPECT_TRUE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
}
+
+// Check that if a logger name is too long, it triggers the appropriate
+// assertion.
+
+TEST_F(LoggerTest, LoggerNameLength) {
+ // The following statements should just declare a logger and nothing
+ // should happen.
+ string ok1(Logger::MAX_LOGGER_NAME_SIZE - 1, 'x');
+ Logger l1(ok1.c_str());
+ EXPECT_EQ(getRootLoggerName() + "." + ok1, l1.getName());
+
+ string ok2(Logger::MAX_LOGGER_NAME_SIZE, 'x');
+ Logger l2(ok2.c_str());
+ EXPECT_EQ(getRootLoggerName() + "." + ok2, l2.getName());
+
+ // Note: Not all systems have EXPECT_DEATH. As it is a macro we can just
+ // test for its presence and bypass the test if not available.
+#ifdef EXPECT_DEATH
+ // Too long a logger name should trigger an assertion failure.
+ // Note that we just check that it dies - we don't check what message is
+ // output.
+ EXPECT_DEATH({
+ isc::util::unittests::dontCreateCoreDumps();
+
+ string ok3(Logger::MAX_LOGGER_NAME_SIZE + 1, 'x');
+ Logger l3(ok3.c_str());
+ }, ".*");
+#endif
+}
diff --git a/src/lib/log/tests/message_initializer_1_unittest.cc b/src/lib/log/tests/message_initializer_1_unittest.cc
new file mode 100644
index 0000000..994174c
--- /dev/null
+++ b/src/lib/log/tests/message_initializer_1_unittest.cc
@@ -0,0 +1,79 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log/message_dictionary.h>
+#include <log/message_initializer.h>
+#include <boost/lexical_cast.hpp>
+#include <gtest/gtest.h>
+#include <string>
+
+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 - declaring message IDs
+// GLOBAL5 and GLOBAL6) is declared in the separately-compiled file,
+// message_identifier_initializer_1a_unittest.cc.)
+
+const MessageInitializer init_message_initializer_unittest_1(values1);
+const MessageInitializer init_message_initializer_unittest_2(values2);
+
+// Check that the global dictionary is initialized with the specified
+// messages.
+
+TEST(MessageInitializerTest1, MessageTest) {
+ MessageDictionary& global = MessageDictionary::globalDictionary();
+
+ // Pointers to the message arrays should have been stored, but none of the
+ // messages should yet be in the dictionary.
+ for (int i = 1; i <= 6; ++i) {
+ string symbol = string("GLOBAL") + boost::lexical_cast<std::string>(i);
+ EXPECT_EQ(string(""), global.getText(symbol));
+ }
+
+ // Load the dictionary - this should clear the message array pending count.
+ // (N.B. We do not check for a known value before the call, only that the
+ // value is not zero. This is because libraries against which the test
+ // is linked may have registered their own message arrays.)
+ EXPECT_NE(0, MessageInitializer::getPendingCount());
+ MessageInitializer::loadDictionary();
+ EXPECT_EQ(0, MessageInitializer::getPendingCount());
+
+ // ... and check the messages loaded.
+ 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_1a_unittest.cc b/src/lib/log/tests/message_initializer_1a_unittest.cc
new file mode 100644
index 0000000..3360167
--- /dev/null
+++ b/src/lib/log/tests/message_initializer_1a_unittest.cc
@@ -0,0 +1,37 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// 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
+};
+
+}
+
+// Register the messages for loading into the global dictionary
+const MessageInitializer init_message_initializer_unittest_3(values3);
diff --git a/src/lib/log/tests/message_initializer_2_unittest.cc b/src/lib/log/tests/message_initializer_2_unittest.cc
new file mode 100644
index 0000000..b479eee
--- /dev/null
+++ b/src/lib/log/tests/message_initializer_2_unittest.cc
@@ -0,0 +1,52 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log/message_initializer.h>
+#include <gtest/gtest.h>
+
+#include <util/unittests/resource.h>
+
+using namespace isc::log;
+
+// Declare a set of messages to go into the global dictionary.
+
+namespace {
+const char* values[] = {
+ "GLOBAL1", "global message one",
+ "GLOBAL2", "global message two",
+ NULL
+};
+}
+
+TEST(MessageInitializerTest2, MessageLoadTest) {
+ // Load the maximum number of message arrays allowed. Some arrays may
+ // already have been loaded because of static initialization from modules
+ // in libraries linked against the test program, hence the reason for the
+ // loop starting from the value returned by getPendingCount() instead of 0.
+ for (size_t i = MessageInitializer::getPendingCount();
+ i < MessageInitializer::MAX_MESSAGE_ARRAYS; ++i) {
+ MessageInitializer initializer1(values);
+ }
+
+ // Note: Not all systems have EXPECT_DEATH. As it is a macro we can just
+ // test for its presence and bypass the test if not available.
+#ifdef EXPECT_DEATH
+ // Adding one more should take us over the limit.
+ EXPECT_DEATH({
+ isc::util::unittests::dontCreateCoreDumps();
+
+ MessageInitializer initializer2(values);
+ }, ".*");
+#endif
+}
diff --git a/src/lib/log/tests/message_initializer_unittest.cc b/src/lib/log/tests/message_initializer_unittest.cc
deleted file mode 100644
index 0cd1879..0000000
--- a/src/lib/log/tests/message_initializer_unittest.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <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
deleted file mode 100644
index 94abb08..0000000
--- a/src/lib/log/tests/message_initializer_unittest_2.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-// 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/run_initializer_unittests.cc b/src/lib/log/tests/run_initializer_unittests.cc
new file mode 100644
index 0000000..54ee120
--- /dev/null
+++ b/src/lib/log/tests/run_initializer_unittests.cc
@@ -0,0 +1,24 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+
+#include <log/logger_support.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/nsas/.gitignore b/src/lib/nsas/.gitignore
new file mode 100644
index 0000000..109ef04
--- /dev/null
+++ b/src/lib/nsas/.gitignore
@@ -0,0 +1,2 @@
+/nsas_messages.cc
+/nsas_messages.h
diff --git a/src/lib/nsas/glue_hints.cc b/src/lib/nsas/glue_hints.cc
index 02c27ee..3caae25 100644
--- a/src/lib/nsas/glue_hints.cc
+++ b/src/lib/nsas/glue_hints.cc
@@ -86,8 +86,8 @@ GlueHints::GlueHints(const std::string& zone_name,
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)));
+ return ((!addresses_v4.empty() && (family == ANY_OK || family == V4_ONLY)) ||
+ (!addresses_v6.empty() && (family == ANY_OK || family == V6_ONLY)));
}
NameserverAddress
diff --git a/src/lib/nsas/hash.cc b/src/lib/nsas/hash.cc
index dbd8eec..ac2af15 100644
--- a/src/lib/nsas/hash.cc
+++ b/src/lib/nsas/hash.cc
@@ -99,9 +99,8 @@ Hash::Hash(uint32_t tablesize, uint32_t maxkeylen, bool randomise) :
if (randomise) {
init_value.curtime = time(NULL);
- }
- else {
- init_value.seed = 0;
+ } else {
+ init_value.seed = 1;
}
srandom(init_value.seed);
diff --git a/src/lib/nsas/hash_table.h b/src/lib/nsas/hash_table.h
index c028fa4..6028473 100644
--- a/src/lib/nsas/hash_table.h
+++ b/src/lib/nsas/hash_table.h
@@ -59,7 +59,7 @@ struct HashTableSlot {
/// \brief Copy Constructor
///
/// ... which as noted in the class description does not copy.
- HashTableSlot(const HashTableSlot<T>&)
+ HashTableSlot(const HashTableSlot<T>&) : mutex_(), list_()
{ }
public:
diff --git a/src/lib/nsas/tests/.gitignore b/src/lib/nsas/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/lib/nsas/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/lib/nsas/tests/Makefile.am b/src/lib/nsas/tests/Makefile.am
index 420e897..afd91f6 100644
--- a/src/lib/nsas/tests/Makefile.am
+++ b/src/lib/nsas/tests/Makefile.am
@@ -46,11 +46,6 @@ 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/util/libutil.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
diff --git a/src/lib/nsas/tests/nameserver_address_store_unittest.cc b/src/lib/nsas/tests/nameserver_address_store_unittest.cc
index 4785627..6ddae72 100644
--- a/src/lib/nsas/tests/nameserver_address_store_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_address_store_unittest.cc
@@ -475,7 +475,7 @@ TEST_F(NameserverAddressStoreTest, updateRTT) {
// for ns.example.com (the nameserver set for example.net in the class
// initialization). We'll set two addresses.
Name ns_example_com(ns_name);
- RRsetPtr ns_address = boost::shared_ptr<RRset>(new RRset(
+ isc::dns::RRsetPtr ns_address = boost::shared_ptr<RRset>(new RRset(
ns_example_com, RRClass::IN(), RRType::A(), RRTTL(300)));
BOOST_FOREACH(string addr, address) {
ns_address->addRdata(rdata::in::A(addr));
diff --git a/src/lib/nsas/tests/nameserver_entry_unittest.cc b/src/lib/nsas/tests/nameserver_entry_unittest.cc
index 3435d26..3aca08f 100644
--- a/src/lib/nsas/tests/nameserver_entry_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_entry_unittest.cc
@@ -139,7 +139,7 @@ TEST_F(NameserverEntryTest, SetRTT) {
NameserverEntry::AddressVector vec;
alpha->getAddresses(vec);
- ASSERT_TRUE(vec.size() > 0);
+ ASSERT_FALSE(vec.empty());
// Take the first address and change the RTT.
IOAddress first_address = vec[0].getAddress();
@@ -174,7 +174,7 @@ TEST_F(NameserverEntryTest, Unreachable) {
NameserverEntry::AddressVector vec;
alpha->getAddresses(vec);
- ASSERT_TRUE(vec.size() > 0);
+ ASSERT_FALSE(vec.empty());
// Take the first address and mark as unreachable.
IOAddress first_address = vec[0].getAddress();
diff --git a/src/lib/nsas/zone_entry.cc b/src/lib/nsas/zone_entry.cc
index 1c5df03..8a72e5f 100644
--- a/src/lib/nsas/zone_entry.cc
+++ b/src/lib/nsas/zone_entry.cc
@@ -329,7 +329,7 @@ updateAddressSelector(std::vector<NameserverAddress>& addresses,
it != probabilities.end(); ++it){
(*it) /= sum;
}
- } else if(probabilities.size() > 0){
+ } else if(!probabilities.empty()){
// 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();
diff --git a/src/lib/python/.gitignore b/src/lib/python/.gitignore
new file mode 100644
index 0000000..9252d05
--- /dev/null
+++ b/src/lib/python/.gitignore
@@ -0,0 +1 @@
+/bind10_config.py
diff --git a/src/lib/python/Makefile.am b/src/lib/python/Makefile.am
index 893bb8c..e3ae4b5 100644
--- a/src/lib/python/Makefile.am
+++ b/src/lib/python/Makefile.am
@@ -3,7 +3,7 @@ SUBDIRS = isc
nodist_python_PYTHON = bind10_config.py
pythondir = $(pyexecdir)
-CLEANFILES = bind10_config.pyc
+CLEANFILES = bind10_config.pyc bind10_config.pyo
CLEANDIRS = __pycache__
clean-local:
diff --git a/src/lib/python/bind10_config.py.in b/src/lib/python/bind10_config.py.in
index e54b1a8..b8975cf 100644
--- a/src/lib/python/bind10_config.py.in
+++ b/src/lib/python/bind10_config.py.in
@@ -23,24 +23,37 @@ def reload():
global DATA_PATH
global PLUGIN_PATHS
global PREFIX
- global LIBEXECDIR
- LIBEXECDIR = ("@libexecdir@/@PACKAGE@"). \
- replace("${exec_prefix}", "@exec_prefix@"). \
- replace("${prefix}", "@prefix@")
+ global LIBEXECPATH
BIND10_MSGQ_SOCKET_FILE = os.path.join("@localstatedir@",
"@PACKAGE_NAME@",
"msgq_socket").replace("${prefix}",
"@prefix@")
PREFIX = "@prefix@"
- # If B10_FROM_SOURCE is set in the environment, we use data files
- # 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 is set in the environment for internal tests and
+ # an experimental run without installagion. In that case we need to
+ # specialize some configuration variables, generally so that they refer
+ # to somewhere in the source tree instead of the appropriate places
+ # after installation.
+ #
+ # DATA_PATH: used by the config manager to find configuration files.
+ # When "FROM_SOURCE", we use data files 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.
+ # PLUGIN_PATHS: configuration modules that are not associated to specific
+ # process
+ # LIBEXECPATH: Paths to programs invoked by the boss process
+ # The boss process (directly or via a helper module) uses this as
+ # the prefererred PATH before starting a child process.
+ # When "FROM_SOURCE", it lists the directories where the programs are
+ # built so that when BIND 10 is experimentally started on the source
+ # tree the programs in the tree (not installed ones) will be used.
+ #
# 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)
+ # 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:
if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
DATA_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
@@ -48,9 +61,17 @@ def reload():
DATA_PATH = os.environ["B10_FROM_SOURCE"]
PLUGIN_PATHS = [os.environ["B10_FROM_SOURCE"] +
'/src/bin/cfgmgr/plugins']
+ programdirs = ['auth', 'cfgmgr', 'cmdctl', 'ddns', 'dhcp6', 'msgq',
+ 'resolver', 'sockcreator', 'stats', 'xfrin', 'xfrout',
+ 'zonemgr']
+ LIBEXECPATH = ':'.join(['@abs_top_builddir@/src/bin/' + p for p in
+ programdirs])
else:
DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
PLUGIN_PATHS = ["@prefix@/share/@PACKAGE@/config_plugins"]
+ LIBEXECPATH = ("@libexecdir@/@PACKAGE@"). \
+ replace("${exec_prefix}", "@exec_prefix@"). \
+ replace("${prefix}", "@prefix@")
# For testing the plugins so they can find their own spec files
if "B10_TEST_PLUGIN_DIR" in os.environ:
PLUGIN_PATHS = os.environ["B10_TEST_PLUGIN_DIR"].split(':')
diff --git a/src/lib/python/isc/Makefile.am b/src/lib/python/isc/Makefile.am
index a3e74c5..aef5dc3 100644
--- a/src/lib/python/isc/Makefile.am
+++ b/src/lib/python/isc/Makefile.am
@@ -1,5 +1,5 @@
SUBDIRS = datasrc cc config dns log net notify util testutils acl bind10
-SUBDIRS += xfrin log_messages
+SUBDIRS += xfrin log_messages server_common
python_PYTHON = __init__.py
diff --git a/src/lib/python/isc/acl/Makefile.am b/src/lib/python/isc/acl/Makefile.am
index b1afa15..b9a0c81 100644
--- a/src/lib/python/isc/acl/Makefile.am
+++ b/src/lib/python/isc/acl/Makefile.am
@@ -26,11 +26,11 @@ _dns_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
# Python prefers .so, while some OSes (specifically MacOS) use a different
# suffix for dynamic objects. -module is necessary to work this around.
-acl_la_LDFLAGS += -module
+acl_la_LDFLAGS += -module -avoid-version
acl_la_LIBADD = $(top_builddir)/src/lib/acl/libacl.la
acl_la_LIBADD += $(PYTHON_LIB)
-_dns_la_LDFLAGS += -module
+_dns_la_LDFLAGS += -module -avoid-version
_dns_la_LIBADD = $(top_builddir)/src/lib/acl/libdnsacl.la
_dns_la_LIBADD += $(PYTHON_LIB)
diff --git a/src/lib/python/isc/acl/tests/dns_test.py b/src/lib/python/isc/acl/tests/dns_test.py
index 7ee3023..d225bee 100644
--- a/src/lib/python/isc/acl/tests/dns_test.py
+++ b/src/lib/python/isc/acl/tests/dns_test.py
@@ -321,7 +321,7 @@ class RequestACLTest(unittest.TestCase):
' "from": "192.0.2.0/24"},' +
' {"action": "DROP",' +
' "from": "2001:db8::1"},' +
- '] }')
+ ']')
self.assertEqual(ACCEPT, acl.execute(CONTEXT4))
self.assertEqual(REJECT, acl.execute(get_context('192.0.2.2')))
self.assertEqual(DROP, acl.execute(get_context('2001:db8::1')))
diff --git a/src/lib/python/isc/bind10/component.py b/src/lib/python/isc/bind10/component.py
index 91b7064..da2730c 100644
--- a/src/lib/python/isc/bind10/component.py
+++ b/src/lib/python/isc/bind10/component.py
@@ -22,14 +22,16 @@ Dependencies between them are not yet handled. It might turn out they are
needed, in that case they will be added sometime in future.
This framework allows for a single process to be started multiple times (by
-specifying multiple components with the same configuration). However, the rest
-of the system might not handle such situation well, so until it is made so,
-it would be better to start each process at most once.
+specifying multiple components with the same configuration). We might want
+to add a more convenient support (like providing a count argument to the
+configuration). This is yet to be designed.
"""
import isc.log
from isc.log_messages.bind10_messages import *
import time
+import os
+import signal
logger = isc.log.Logger("boss")
DBG_TRACE_DATA = 20
@@ -45,6 +47,14 @@ STATE_DEAD = 'dead'
STATE_STOPPED = 'stopped'
STATE_RUNNING = 'running'
+def get_signame(signal_number):
+ """Return the symbolic name for a signal."""
+ for sig in dir(signal):
+ if sig.startswith("SIG") and sig[3].isalnum():
+ if getattr(signal, sig) == signal_number:
+ return sig
+ return "unknown signal"
+
class BaseComponent:
"""
This represents a single component. This one is an abstract base class.
@@ -206,8 +216,24 @@ class BaseComponent:
it is considered a core or needed component, or because
the component is to be restarted later.
"""
+
+ if exit_code is not None:
+ if os.WIFEXITED(exit_code):
+ exit_str = "process exited normally with exit status %d" % (exit_code)
+ elif os.WIFSIGNALED(exit_code):
+ sig = os.WTERMSIG(exit_code)
+ signame = get_signame(sig)
+ if os.WCOREDUMP(exit_code):
+ exit_str = "process dumped core with exit status %d (killed by signal %d: %s)" % (exit_code, sig, signame)
+ else:
+ exit_str = "process terminated with exit status %d (killed by signal %d: %s)" % (exit_code, sig, signame)
+ else:
+ exit_str = "unknown condition with exit status %d" % (exit_code)
+ else:
+ exit_str = "unknown condition"
+
logger.error(BIND10_COMPONENT_FAILED, self.name(), self.pid(),
- exit_code if exit_code is not None else "unknown")
+ exit_str)
if not self.running():
raise ValueError("Can't fail component that isn't running")
self.__state = STATE_STOPPED
@@ -408,7 +434,7 @@ class Component(BaseComponent):
self._boss.register_process(self.pid(), self)
def _stop_internal(self):
- self._boss.stop_process(self._process, self._address)
+ self._boss.stop_process(self._process, self._address, self.pid())
# TODO Some way to wait for the process that doesn't want to
# terminate and kill it would prove nice (or add it to boss somewhere?)
diff --git a/src/lib/python/isc/bind10/socket_cache.py b/src/lib/python/isc/bind10/socket_cache.py
index 26e87d2..d6c1175 100644
--- a/src/lib/python/isc/bind10/socket_cache.py
+++ b/src/lib/python/isc/bind10/socket_cache.py
@@ -205,9 +205,9 @@ class Cache:
raise ShareError("Cached socket not compatible with mode " +
share_mode + " and name " + share_name)
# Grab yet unused token
- token = 't' + str(random.randint(0, 2^32-1))
+ token = 't' + str(random.randint(0, 2 ** 32-1))
while token in self._live_tokens:
- token = 't' + str(random.randint(0, 2^32-1))
+ token = 't' + str(random.randint(0, 2 ** 32-1))
self._waiting_tokens[token] = socket
self._live_tokens.add(token)
socket.shares[token] = (share_mode, share_name)
diff --git a/src/lib/python/isc/bind10/special_component.py b/src/lib/python/isc/bind10/special_component.py
index c9c7683..ebdc07f 100644
--- a/src/lib/python/isc/bind10/special_component.py
+++ b/src/lib/python/isc/bind10/special_component.py
@@ -15,7 +15,7 @@
from isc.bind10.component import Component, BaseComponent
import isc.bind10.sockcreator
-from bind10_config import LIBEXECDIR
+from bind10_config import LIBEXECPATH
import os
import posix
import isc.log
@@ -36,14 +36,18 @@ class SockCreator(BaseComponent):
def __init__(self, process, boss, kind, address=None, params=None):
BaseComponent.__init__(self, boss, kind)
self.__creator = None
+ self.__uid = boss.uid
def _start_internal(self):
self._boss.curproc = 'b10-sockcreator'
- self.__creator = isc.bind10.sockcreator.Creator(LIBEXECDIR + ':' +
+ self.__creator = isc.bind10.sockcreator.Creator(LIBEXECPATH + ':' +
os.environ['PATH'])
self._boss.register_process(self.pid(), self)
self._boss.set_creator(self.__creator)
self._boss.log_started(self.pid())
+ if self.__uid is not None:
+ logger.info(BIND10_SETUID, self.__uid)
+ posix.setuid(self.__uid)
def _stop_internal(self):
self.__creator.terminate()
@@ -108,32 +112,6 @@ class CmdCtl(Component):
def __init__(self, process, boss, kind, address=None, params=None):
Component.__init__(self, process, boss, kind, 'Cmdctl', None,
boss.start_cmdctl)
-
-class SetUID(BaseComponent):
- """
- This is a pseudo-component which drops root privileges when started
- and sets the uid stored in boss.
-
- This component does nothing when stopped.
- """
- def __init__(self, process, boss, kind, address=None, params=None):
- BaseComponent.__init__(self, boss, kind)
- self.uid = boss.uid
-
- def _start_internal(self):
- if self.uid is not None:
- logger.info(BIND10_SETUID, self.uid)
- posix.setuid(self.uid)
-
- def _stop_internal(self): pass
- def kill(self, forceful=False): pass
-
- def name(self):
- return "Set UID"
-
- def pid(self):
- return None
-
def get_specials():
"""
List of specially started components. Each one should be the class than can
@@ -147,7 +125,5 @@ def get_specials():
# They should not have any parameters anyway
'auth': Auth,
'resolver': Resolver,
- 'cmdctl': CmdCtl,
- # TODO: Remove when not needed, workaround before sockcreator works
- 'setuid': SetUID
+ 'cmdctl': CmdCtl
}
diff --git a/src/lib/python/isc/bind10/tests/component_test.py b/src/lib/python/isc/bind10/tests/component_test.py
index 3b49b18..ec0e8af 100644
--- a/src/lib/python/isc/bind10/tests/component_test.py
+++ b/src/lib/python/isc/bind10/tests/component_test.py
@@ -507,8 +507,7 @@ class ComponentTests(BossUtils, unittest.TestCase):
isc.bind10.special_component.CfgMgr,
isc.bind10.special_component.Auth,
isc.bind10.special_component.Resolver,
- isc.bind10.special_component.CmdCtl,
- isc.bind10.special_component.SetUID]:
+ isc.bind10.special_component.CmdCtl]:
component = component_type('none', self, 'needed')
self.assertIsNone(component.pid())
@@ -554,11 +553,11 @@ class ComponentTests(BossUtils, unittest.TestCase):
self.assertEqual(42, component.pid())
self.assertEqual(component, self.__registered_processes.get(42))
- def stop_process(self, process, address):
+ def stop_process(self, process, address, pid):
"""
Part of pretending to be boss.
"""
- self.__stop_process_params = (process, address)
+ self.__stop_process_params = (process, address, pid)
def start_simple(self, process):
"""
@@ -574,9 +573,11 @@ class ComponentTests(BossUtils, unittest.TestCase):
component.start()
self.assertTrue(component.running())
self.assertEqual('component', self.__start_simple_params)
+ component.pid = lambda: 42
component.stop()
self.assertFalse(component.running())
- self.assertEqual(('component', 'Address'), self.__stop_process_params)
+ self.assertEqual(('component', 'Address', 42),
+ self.__stop_process_params)
def test_component_kill(self):
"""
@@ -611,14 +612,38 @@ class ComponentTests(BossUtils, unittest.TestCase):
def setuid(self, uid):
self.__uid_set = uid
- def test_setuid(self):
+ class FakeCreator:
+ def pid(self):
+ return 42
+ def terminate(self): pass
+ def kill(self): pass
+
+ def set_creator(self, creator):
+ """
+ Part of faking being the boss. Check the creator (faked as well)
+ is passed here.
+ """
+ self.assertTrue(isinstance(creator, self.FakeCreator))
+
+ def log_started(self, pid):
+ """
+ Part of faking the boss. Check the pid is the one of the fake creator.
+ """
+ self.assertEqual(42, pid)
+
+ def test_creator(self):
"""
- Some tests around the SetUID pseudo-component.
+ Some tests around the SockCreator component.
"""
- component = isc.bind10.special_component.SetUID(None, self, 'needed',
- None)
+ component = isc.bind10.special_component.SockCreator(None, self,
+ 'needed', None)
orig_setuid = isc.bind10.special_component.posix.setuid
isc.bind10.special_component.posix.setuid = self.setuid
+ orig_creator = \
+ isc.bind10.special_component.isc.bind10.sockcreator.Creator
+ # Just ignore the creator call
+ isc.bind10.special_component.isc.bind10.sockcreator.Creator = \
+ lambda path: self.FakeCreator()
component.start()
# No uid set in boss, nothing called.
self.assertIsNone(self.__uid_set)
@@ -627,11 +652,14 @@ class ComponentTests(BossUtils, unittest.TestCase):
component.kill()
component.kill(True)
self.uid = 42
- component = isc.bind10.special_component.SetUID(None, self, 'needed',
- None)
+ component = isc.bind10.special_component.SockCreator(None, self,
+ 'needed', None)
component.start()
# This time, it get's called
self.assertEqual(42, self.__uid_set)
+ isc.bind10.special_component.posix.setuid = orig_setuid
+ isc.bind10.special_component.isc.bind10.sockcreator.Creator = \
+ orig_creator
class TestComponent(BaseComponent):
"""
diff --git a/src/lib/python/isc/cc/data.py b/src/lib/python/isc/cc/data.py
index 76ef942..636e9a9 100644
--- a/src/lib/python/isc/cc/data.py
+++ b/src/lib/python/isc/cc/data.py
@@ -21,6 +21,7 @@
#
import json
+import re
class DataNotFoundError(Exception):
"""Raised if an identifier does not exist according to a spec file,
@@ -86,6 +87,13 @@ def split_identifier(identifier):
id_parts[:] = (value for value in id_parts if value != "")
return id_parts
+def identifier_has_list_index(identifier):
+ """Returns True if the given identifier string has at least one
+ list index (with [I], where I is a number"""
+ return (type(identifier) == str and
+ re.search("\[\d+\]", identifier) is not None)
+
+
def split_identifier_list_indices(identifier):
"""Finds list indexes in the given identifier, which are of the
format [integer].
diff --git a/src/lib/python/isc/cc/session.py b/src/lib/python/isc/cc/session.py
index f6b6265..33a47bd 100644
--- a/src/lib/python/isc/cc/session.py
+++ b/src/lib/python/isc/cc/session.py
@@ -72,7 +72,7 @@ class Session:
self._lname = None
self._closed = True
- def sendmsg(self, env, msg = None):
+ def sendmsg(self, env, msg=None):
with self._lock:
if self._closed:
raise SessionError("Session has been closed.")
@@ -82,15 +82,24 @@ class Session:
raise ProtocolError("Envelope too large")
if type(msg) == dict:
msg = isc.cc.message.to_wire(msg)
- self._socket.setblocking(1)
length = 2 + len(env);
- if msg:
+ if msg is not None:
length += len(msg)
- self._socket.send(struct.pack("!I", length))
- self._socket.send(struct.pack("!H", len(env)))
- self._socket.send(env)
- if msg:
- self._socket.send(msg)
+
+ # Build entire message.
+ data = struct.pack("!I", length)
+ data += struct.pack("!H", len(env))
+ data += env
+ if msg is not None:
+ data += msg
+
+ # Send it in the blocking mode. On some systems send() may
+ # actually send only part of the data, so we need to repeat it
+ # until all data have been sent out.
+ self._socket.setblocking(1)
+ while len(data) > 0:
+ cc = self._socket.send(data)
+ data = data[cc:]
def recvmsg(self, nonblock = True, seq = None):
"""Reads a message. If nonblock is true, and there is no
diff --git a/src/lib/python/isc/cc/tests/.gitignore b/src/lib/python/isc/cc/tests/.gitignore
new file mode 100644
index 0000000..b7ac2ae
--- /dev/null
+++ b/src/lib/python/isc/cc/tests/.gitignore
@@ -0,0 +1 @@
+/cc_test
diff --git a/src/lib/python/isc/cc/tests/session_test.py b/src/lib/python/isc/cc/tests/session_test.py
index 772ed0c..e589085 100644
--- a/src/lib/python/isc/cc/tests/session_test.py
+++ b/src/lib/python/isc/cc/tests/session_test.py
@@ -29,6 +29,7 @@ class MySocket():
self.recvqueue = bytearray()
self.sendqueue = bytearray()
self._blocking = True
+ self.send_limit = None
def connect(self, to):
pass
@@ -40,7 +41,14 @@ class MySocket():
self._blocking = val
def send(self, data):
- self.sendqueue.extend(data);
+ # If the upper limit is specified, only "send" up to the specified
+ # limit
+ if self.send_limit is not None and len(data) > self.send_limit:
+ self.sendqueue.extend(data[0:self.send_limit])
+ return self.send_limit
+ else:
+ self.sendqueue.extend(data)
+ return len(data)
def readsent(self, length):
if length > len(self.sendqueue):
@@ -101,6 +109,17 @@ class MySocket():
def gettimeout(self):
return 0
+ def set_send_limit(self, limit):
+ '''Specify the upper limit of the transmittable data at once.
+
+ By default, the send() method of this class "sends" all given data.
+ If this method is called and the its parameter is not None,
+ subsequent calls to send() will only transmit the specified amount
+ of data. This can be used to emulate the situation where send()
+ on a real socket object results in partial write.
+ '''
+ self.send_limit = limit
+
#
# We subclass the Session class we're testing here, only
# to override the __init__() method, which wants a socket,
@@ -157,6 +176,16 @@ class testSession(unittest.TestCase):
#print(sent)
#self.assertRaises(SessionError, sess.sendmsg, {}, {"hello": "a"})
+ def test_session_sendmsg_shortwrite(self):
+ sess = MySession()
+ # Specify the upper limit of the size that can be transmitted at
+ # a single send() call on the faked socket (10 is an arbitrary choice,
+ # just reasonably small).
+ sess._socket.set_send_limit(10)
+ sess.sendmsg({'to': 'someone', 'reply': 1}, {"hello": "a"})
+ # The complete message should still have been transmitted in the end.
+ sent = sess._socket.readsentmsg();
+
def recv_and_compare(self, session, bytes, env, msg):
"""Adds bytes to the recvqueue (which will be read by the
session object, and compare the resultinv env and msg to
diff --git a/src/lib/python/isc/config/Makefile.am b/src/lib/python/isc/config/Makefile.am
index ef696fb..cda8b57 100644
--- a/src/lib/python/isc/config/Makefile.am
+++ b/src/lib/python/isc/config/Makefile.am
@@ -13,6 +13,7 @@ CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/cfgmgr_messages.py
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cfgmgr_messages.pyc
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/config_messages.py
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/config_messages.pyc
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/config_messages.pyo
CLEANDIRS = __pycache__
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index 2d998ce..703d196 100644
--- a/src/lib/python/isc/config/ccsession.py
+++ b/src/lib/python/isc/config/ccsession.py
@@ -38,6 +38,7 @@
from isc.cc import Session
from isc.config.config_data import ConfigData, MultiConfigData, BIND10_CONFIG_DATA_VERSION
+import isc.config.module_spec
import isc
from isc.util.file import path_search
import bind10_config
@@ -97,6 +98,7 @@ COMMAND_SET_CONFIG = "set_config"
COMMAND_GET_MODULE_SPEC = "get_module_spec"
COMMAND_MODULE_SPEC = "module_spec"
COMMAND_SHUTDOWN = "shutdown"
+COMMAND_MODULE_STOPPING = "stopping"
def parse_command(msg):
"""Parses what may be a command message. If it looks like one,
@@ -210,6 +212,24 @@ class ModuleCCSession(ConfigData):
self.__send_spec()
self.__request_config()
+ def send_stopping(self):
+ """Sends a 'stopping' message to the configuration manager. This
+ message is just an FYI, and no response is expected. Any errors
+ when sending this message (for instance if the msgq session has
+ previously been closed) are logged, but ignored."""
+ # create_command could raise an exception as well, but except for
+ # out of memory related errors, these should all be programming
+ # failures and are not caught
+ msg = create_command(COMMAND_MODULE_STOPPING,
+ self.get_module_spec().get_full_spec())
+ try:
+ self._session.group_sendmsg(msg, "ConfigManager")
+ except Exception as se:
+ # If the session was previously closed, obvously trying to send
+ # a message fails. (TODO: check if session is open so we can
+ # error on real problems?)
+ logger.error(CONFIG_SESSION_STOPPING_FAILED, se)
+
def get_socket(self):
"""Returns the socket from the command channel session. This
should *only* be used for select() loops to see if there
@@ -308,43 +328,97 @@ class ModuleCCSession(ConfigData):
and return an answer created with create_answer()"""
self._command_handler = command_handler
- def add_remote_config(self, spec_file_name, config_update_callback = None):
- """Gives access to the configuration of a different module.
- These remote module options can at this moment only be
- accessed through get_remote_config_value(). This function
- also subscribes to the channel of the remote module name
- to receive the relevant updates. It is not possible to
- specify your own handler for this right now.
- start() must have been called on this CCSession
- prior to the call to this method.
- Returns the name of the module."""
- module_spec = isc.config.module_spec_from_file(spec_file_name)
+ def _add_remote_config_internal(self, module_spec,
+ config_update_callback=None):
+ """The guts of add_remote_config and add_remote_config_by_name"""
module_cfg = ConfigData(module_spec)
module_name = module_spec.get_module_name()
+
self._session.group_subscribe(module_name)
# Get the current config for that module now
seq = self._session.group_sendmsg(create_command(COMMAND_GET_CONFIG, { "module_name": module_name }), "ConfigManager")
try:
- answer, env = self._session.group_recvmsg(False, seq)
+ answer, _ = self._session.group_recvmsg(False, seq)
except isc.cc.SessionTimeout:
raise ModuleCCSessionError("No answer from ConfigManager when "
"asking about Remote module " +
module_name)
+ call_callback = False
if answer:
rcode, value = parse_answer(answer)
if rcode == 0:
- if value != None and module_spec.validate_config(False, value):
- module_cfg.set_local_config(value)
- if config_update_callback is not None:
- config_update_callback(value, module_cfg)
+ if value != None:
+ if module_spec.validate_config(False, value):
+ module_cfg.set_local_config(value)
+ call_callback = True
+ else:
+ raise ModuleCCSessionError("Bad config data for " +
+ module_name + ": " +
+ str(value))
+ else:
+ raise ModuleCCSessionError("Failure requesting remote " +
+ "configuration data for " +
+ module_name)
# all done, add it
self._remote_module_configs[module_name] = module_cfg
self._remote_module_callbacks[module_name] = config_update_callback
+ if call_callback and config_update_callback is not None:
+ config_update_callback(value, module_cfg)
+
+ def add_remote_config_by_name(self, module_name,
+ config_update_callback=None):
+ """
+ This does the same as add_remote_config, but you provide the module name
+ instead of the name of the spec file.
+ """
+ seq = self._session.group_sendmsg(create_command(COMMAND_GET_MODULE_SPEC,
+ { "module_name":
+ module_name }),
+ "ConfigManager")
+ try:
+ answer, env = self._session.group_recvmsg(False, seq)
+ except isc.cc.SessionTimeout:
+ raise ModuleCCSessionError("No answer from ConfigManager when " +
+ "asking about for spec of Remote " +
+ "module " + module_name)
+ if answer:
+ rcode, value = parse_answer(answer)
+ if rcode == 0:
+ module_spec = isc.config.module_spec.ModuleSpec(value)
+ if module_spec.get_module_name() != module_name:
+ raise ModuleCCSessionError("Module name mismatch: " +
+ module_name + " and " +
+ module_spec.get_module_name())
+ self._add_remote_config_internal(module_spec,
+ config_update_callback)
+ else:
+ raise ModuleCCSessionError("Error code " + str(rcode) +
+ "when asking for module spec of " +
+ module_name)
+ else:
+ raise ModuleCCSessionError("No answer when asking for module " +
+ "spec of " + module_name)
+ # Just to be consistent with the add_remote_config
return module_name
-
+
+ def add_remote_config(self, spec_file_name, config_update_callback=None):
+ """Gives access to the configuration of a different module.
+ These remote module options can at this moment only be
+ accessed through get_remote_config_value(). This function
+ also subscribes to the channel of the remote module name
+ to receive the relevant updates. It is not possible to
+ specify your own handler for this right now, but you can
+ specify a callback that is called after the change happened.
+ start() must have been called on this CCSession
+ prior to the call to this method.
+ Returns the name of the module."""
+ module_spec = isc.config.module_spec_from_file(spec_file_name)
+ self._add_remote_config_internal(module_spec, config_update_callback)
+ return module_spec.get_module_name()
+
def remove_remote_config(self, module_name):
"""Removes the remote configuration access for this module"""
if module_name in self._remote_module_configs:
@@ -371,7 +445,7 @@ class ModuleCCSession(ConfigData):
except isc.cc.SessionTimeout:
# TODO: log an error?
pass
-
+
def __request_config(self):
"""Asks the configuration manager for the current configuration, and call the config handler if set.
Raises a ModuleCCSessionError if there is no answer from the configuration manager"""
@@ -410,30 +484,38 @@ class UIModuleCCSession(MultiConfigData):
passed must have send_GET and send_POST functions"""
MultiConfigData.__init__(self)
self._conn = conn
- self.request_specifications()
- self.request_current_config()
+ self.update_specs_and_config()
def request_specifications(self):
- """Request the module specifications from b10-cmdctl"""
- # this step should be unnecessary but is the current way cmdctl returns stuff
- # so changes are needed there to make this clean (we need a command to simply get the
- # full specs for everything, including commands etc, not separate gets for that)
+ """Clears the current list of specifications, and requests a new
+ list from b10-cmdctl. As other actions may have caused modules
+ to be stopped, or new modules to be added, this is expected to
+ be run after each interaction (at this moment). It is usually
+ also combined with request_current_config(). For that reason,
+ we provide update_specs_and_config() which calls both."""
specs = self._conn.send_GET('/module_spec')
+ self.clear_specifications()
for module in specs.keys():
self.set_specification(isc.config.ModuleSpec(specs[module]))
- def update_specs_and_config(self):
- self.request_specifications()
- self.request_current_config()
-
def request_current_config(self):
"""Requests the current configuration from the configuration
- manager through b10-cmdctl, and stores those as CURRENT"""
+ manager through b10-cmdctl, and stores those as CURRENT. This
+ does not modify any local changes, it just updates to the current
+ state of the server itself."""
config = self._conn.send_GET('/config_data')
if 'version' not in config or config['version'] != BIND10_CONFIG_DATA_VERSION:
raise ModuleCCSessionError("Bad config version")
self._set_current_config(config)
+ def update_specs_and_config(self):
+ """Convenience function to both clear and update the known list of
+ module specifications, and update the current configuration on
+ the server side. There are a few cases where the caller might only
+ want to run one of these tasks, but often they are both needed."""
+ self.request_specifications()
+ self.request_current_config()
+
def _add_value_to_list(self, identifier, value, module_spec):
cur_list, status = self.get_value(identifier)
if not cur_list:
@@ -451,9 +533,9 @@ class UIModuleCCSession(MultiConfigData):
cur_list.append(value)
self.set_value(identifier, cur_list)
else:
- raise isc.cc.data.DataAlreadyPresentError(value +
+ raise isc.cc.data.DataAlreadyPresentError(str(value) +
" already in "
- + identifier)
+ + str(identifier))
def _add_value_to_named_set(self, identifier, value, item_value):
if type(value) != str:
@@ -474,8 +556,8 @@ class UIModuleCCSession(MultiConfigData):
self.set_value(identifier, cur_map)
else:
raise isc.cc.data.DataAlreadyPresentError(value +
- " already in "
- + identifier)
+ " already in " +
+ identifier)
def add_value(self, identifier, value_str = None, set_value_str = None):
"""Add a value to a configuration list. Raises a DataTypeError
@@ -582,7 +664,6 @@ class UIModuleCCSession(MultiConfigData):
# 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:
raise ModuleCCSessionError("Error: " + str(answer["error"]) + "\n" + "Configuration not committed")
diff --git a/src/lib/python/isc/config/cfgmgr.py b/src/lib/python/isc/config/cfgmgr.py
index 4d568be..9f9ce68 100644
--- a/src/lib/python/isc/config/cfgmgr.py
+++ b/src/lib/python/isc/config/cfgmgr.py
@@ -148,6 +148,27 @@ class ConfigManagerData:
# Ok if we really can't delete it anymore, leave it
pass
+ def rename_config_file(self, old_file_name=None, new_file_name=None):
+ """Renames the given configuration file to the given new file name,
+ if it exists. If it does not exist, nothing happens.
+ If old_file_name is None (default), the file used in
+ read_from_file is used. If new_file_name is None (default), the
+ file old_file_name appended with .bak is used. If that file exists
+ already, .1 is appended. If that file exists, .2 is appended, etc.
+ """
+ if old_file_name is None:
+ old_file_name = self.db_filename
+ if new_file_name is None:
+ new_file_name = old_file_name + ".bak"
+ if os.path.exists(new_file_name):
+ i = 1
+ while os.path.exists(new_file_name + "." + str(i)):
+ i += 1
+ new_file_name = new_file_name + "." + str(i)
+ if os.path.exists(old_file_name):
+ logger.info(CFGMGR_RENAMED_CONFIG_FILE, old_file_name, new_file_name)
+ os.rename(old_file_name, new_file_name)
+
def __eq__(self, other):
"""Returns True if the data contained is equal. data_path and
db_filename may be different."""
@@ -163,14 +184,16 @@ class ConfigManager:
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, database_filename, session=None):
+ def __init__(self, data_path, database_filename, session=None,
+ clear_config=False):
"""Initialize the configuration manager. The data_path string
is the path to the directory where the configuration is
stored (in <data_path>/<database_filename> or in
- <database_filename>, if it is absolute). The dabase_filename
+ <database_filename>, if it is absolute). The database_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. If clear_config is True, the configuration file is
+ renamed and a new one is created."""
self.data_path = data_path
self.database_filename = database_filename
self.module_specs = {}
@@ -179,6 +202,8 @@ class ConfigManager:
# of some other process
self.virtual_modules = {}
self.config = ConfigManagerData(data_path, database_filename)
+ if clear_config:
+ self.config.rename_config_file()
if session:
self.cc = session
else:
@@ -297,7 +322,7 @@ class ConfigManager:
"""Write the current configuration to the file specificied at init()"""
self.config.write_to_file()
- def _handle_get_module_spec(self, cmd):
+ def __handle_get_module_spec(self, cmd):
"""Private function that handles the 'get_module_spec' command"""
answer = {}
if cmd != None:
@@ -318,7 +343,7 @@ class ConfigManager:
answer = ccsession.create_answer(0, self.get_module_spec())
return answer
- def _handle_get_config_dict(self, cmd):
+ def __handle_get_config_dict(self, cmd):
"""Private function that handles the 'get_config' command
where the command has been checked to be a dict"""
if 'module_name' in cmd and cmd['module_name'] != '':
@@ -332,17 +357,17 @@ class ConfigManager:
else:
return ccsession.create_answer(1, "Bad module_name in get_config command")
- def _handle_get_config(self, cmd):
+ def __handle_get_config(self, cmd):
"""Private function that handles the 'get_config' command"""
if cmd != None:
if type(cmd) == dict:
- return self._handle_get_config_dict(cmd)
+ return self.__handle_get_config_dict(cmd)
else:
return ccsession.create_answer(1, "Bad get_config command, argument not a dict")
else:
return ccsession.create_answer(0, self.config.data)
- def _handle_set_config_module(self, module_name, 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
@@ -405,7 +430,7 @@ class ConfigManager:
self.config.data = old_data
return answer
- def _handle_set_config_all(self, cmd):
+ def __handle_set_config_all(self, cmd):
old_data = copy.deepcopy(self.config.data)
got_error = False
err_list = []
@@ -413,7 +438,7 @@ class ConfigManager:
# 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])
+ answer = self.__handle_set_config_module(module, cmd[module])
if answer == None:
got_error = True
err_list.append("No answer message from " + module)
@@ -432,16 +457,16 @@ class ConfigManager:
self.config.data = old_data
return ccsession.create_answer(1, " ".join(err_list))
- def _handle_set_config(self, cmd):
+ 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[0], cmd[1])
+ answer = self.__handle_set_config_module(cmd[0], cmd[1])
elif len(cmd) == 1:
- answer = self._handle_set_config_all(cmd[0])
+ answer = self.__handle_set_config_all(cmd[0])
else:
answer = ccsession.create_answer(1, "Wrong number of arguments")
if not answer:
@@ -449,20 +474,41 @@ class ConfigManager:
return answer
- def _handle_module_spec(self, spec):
+ def __handle_module_spec(self, spec):
"""Private function that handles the 'module_spec' command"""
# todo: validate? (no direct access to spec as
# todo: use ModuleSpec class
# todo: error checking (like keyerrors)
answer = {}
self.set_module_spec(spec)
+ self._send_module_spec_to_cmdctl(spec.get_module_name(),
+ spec.get_full_spec())
+ return ccsession.create_answer(0)
- # We should make one general 'spec update for module' that
- # passes both specification and commands at once
+ def __handle_module_stopping(self, arg):
+ """Private function that handles a 'stopping' command;
+ The argument is of the form { 'module_name': <name> }.
+ If the module is known, it is removed from the known list,
+ and a message is sent to the Cmdctl channel to remove it as well.
+ If it is unknown, the message is ignored."""
+ if arg['module_name'] in self.module_specs:
+ del self.module_specs[arg['module_name']]
+ self._send_module_spec_to_cmdctl(arg['module_name'], None)
+ # This command is not expected to be answered
+ return None
+
+ def _send_module_spec_to_cmdctl(self, module_name, spec):
+ """Sends the given module spec for the given module name to Cmdctl.
+ Parameters:
+ module_name: A string with the name of the module
+ spec: dict containing full module specification, as returned by
+ ModuleSpec.get_full_spec(). This argument may also be None,
+ in which case it signals Cmdctl to remove said module from
+ its list.
+ No response from Cmdctl is expected."""
spec_update = ccsession.create_command(ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE,
- [ spec.get_module_name(), spec.get_full_spec() ])
+ [ module_name, spec ])
self.cc.group_sendmsg(spec_update, "Cmdctl")
- return ccsession.create_answer(0)
def handle_msg(self, msg):
"""Handle a command from the cc channel to the configuration manager"""
@@ -474,17 +520,19 @@ class ConfigManager:
elif cmd == ccsession.COMMAND_GET_STATISTICS_SPEC:
answer = ccsession.create_answer(0, self.get_statistics_spec())
elif cmd == ccsession.COMMAND_GET_MODULE_SPEC:
- answer = self._handle_get_module_spec(arg)
+ answer = self.__handle_get_module_spec(arg)
elif cmd == ccsession.COMMAND_GET_CONFIG:
- answer = self._handle_get_config(arg)
+ answer = self.__handle_get_config(arg)
elif cmd == ccsession.COMMAND_SET_CONFIG:
- answer = self._handle_set_config(arg)
+ answer = self.__handle_set_config(arg)
+ elif cmd == ccsession.COMMAND_MODULE_STOPPING:
+ answer = self.__handle_module_stopping(arg)
elif cmd == ccsession.COMMAND_SHUTDOWN:
self.running = False
answer = ccsession.create_answer(0)
elif cmd == ccsession.COMMAND_MODULE_SPEC:
try:
- answer = self._handle_module_spec(isc.config.ModuleSpec(arg))
+ answer = self.__handle_module_spec(isc.config.ModuleSpec(arg))
except isc.config.ModuleSpecError as dde:
answer = ccsession.create_answer(1, "Error in data definition: " + str(dde))
else:
@@ -508,4 +556,6 @@ class ConfigManager:
# not ask
if msg is not None and not 'result' in msg:
answer = self.handle_msg(msg);
- self.cc.group_reply(env, answer)
+ # Only respond if there actually is something to respond with
+ if answer is not None:
+ self.cc.group_reply(env, answer)
diff --git a/src/lib/python/isc/config/cfgmgr_messages.mes b/src/lib/python/isc/config/cfgmgr_messages.mes
index 61a63ed..ad78be0 100644
--- a/src/lib/python/isc/config/cfgmgr_messages.mes
+++ b/src/lib/python/isc/config/cfgmgr_messages.mes
@@ -51,7 +51,11 @@ error is given. The most likely cause is that the system does not have
write access to the configuration database file. The updated
configuration is not stored.
+% CFGMGR_RENAMED_CONFIG_FILE renamed configuration file %1 to %2, will create new %1
+BIND 10 has been started with the command to clear the configuration file.
+The existing file is backed up to the given file name, so that data is not
+immediately lost if this was done by accident.
+
% CFGMGR_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down
There was a keyboard interrupt signal to stop the cfgmgr daemon. The
daemon will now shut down.
-
diff --git a/src/lib/python/isc/config/config_data.py b/src/lib/python/isc/config/config_data.py
index b2cf048..2bec4ab 100644
--- a/src/lib/python/isc/config/config_data.py
+++ b/src/lib/python/isc/config/config_data.py
@@ -23,11 +23,28 @@ two through the classes in ccsession)
import isc.cc.data
import isc.config.module_spec
import ast
+import copy
class ConfigDataError(Exception): pass
BIND10_CONFIG_DATA_VERSION = 2
+# Helper functions
+def spec_part_is_list(spec_part):
+ """Returns True if the given spec_part is a dict that contains a
+ list specification, and False otherwise."""
+ return (type(spec_part) == dict and 'list_item_spec' in spec_part)
+
+def spec_part_is_map(spec_part):
+ """Returns True if the given spec_part is a dict that contains a
+ map specification, and False otherwise."""
+ return (type(spec_part) == dict and 'map_item_spec' in spec_part)
+
+def spec_part_is_named_set(spec_part):
+ """Returns True if the given spec_part is a dict that contains a
+ named_set specification, and False otherwise."""
+ return (type(spec_part) == dict and 'named_set_item_spec' in spec_part)
+
def check_type(spec_part, value):
"""Does nothing if the value is of the correct type given the
specification part relevant for the value. Raises an
@@ -93,14 +110,17 @@ def convert_type(spec_part, value):
return ret
elif data_type == "map":
- 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")
+ try:
+ 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")
+ except SyntaxError as se:
+ raise isc.cc.data.DataTypeError("Error parsing map: " + str(se))
else:
return value
except ValueError as err:
@@ -112,9 +132,9 @@ def _get_map_or_list(spec_part):
"""Returns the list or map specification if this is a list or a
map specification part. If not, returns the given spec_part
itself"""
- if "map_item_spec" in spec_part:
+ if spec_part_is_map(spec_part):
return spec_part["map_item_spec"]
- elif "list_item_spec" in spec_part:
+ elif spec_part_is_list(spec_part):
return spec_part["list_item_spec"]
else:
return spec_part
@@ -134,13 +154,13 @@ def _find_spec_part_single(cur_spec, id_part):
# list or a map, which is internally represented by a dict with
# an element 'map_item_spec', a dict with an element 'list_item_spec',
# or a list (when it is the 'main' config_data element of a module).
- if type(cur_spec) == dict and 'map_item_spec' in cur_spec.keys():
+ if spec_part_is_map(cur_spec):
for cur_spec_item in cur_spec['map_item_spec']:
if cur_spec_item['item_name'] == id:
return cur_spec_item
# not found
raise isc.cc.data.DataNotFoundError(id + " not found")
- elif type(cur_spec) == dict and 'list_item_spec' in cur_spec.keys():
+ elif spec_part_is_list(cur_spec):
if cur_spec['item_name'] == id:
return cur_spec['list_item_spec']
# not found
@@ -156,9 +176,22 @@ def _find_spec_part_single(cur_spec, id_part):
else:
raise isc.cc.data.DataNotFoundError("Not a correct config specification")
-def find_spec_part(element, identifier):
+def find_spec_part(element, identifier, strict_identifier = True):
"""find the data definition for the given identifier
- returns either a map with 'item_name' etc, or a list of those"""
+ returns either a map with 'item_name' etc, or a list of those
+ Parameters:
+ element: The specification element to start the search in
+ identifier: The element to find (relative to element above)
+ strict_identifier: If True (the default), additional checking occurs.
+ Currently the only check is whether a list index is
+ specified (except for the last part of the
+ identifier)
+ Raises a DataNotFoundError if the data is not found, or if
+ strict_identifier is True and any non-final identifier parts
+ (i.e. before the last /) identify a list element and do not contain
+ an index.
+ Returns the spec element identified by the given identifier.
+ """
if identifier == "":
return element
id_parts = identifier.split("/")
@@ -171,10 +204,15 @@ def find_spec_part(element, identifier):
# always want the 'full' spec of the item
for id_part in id_parts[:-1]:
cur_el = _find_spec_part_single(cur_el, id_part)
+ if strict_identifier and spec_part_is_list(cur_el) and\
+ not isc.cc.data.identifier_has_list_index(id_part):
+ raise isc.cc.data.DataNotFoundError(id_part +
+ " is a list and needs an index")
cur_el = _get_map_or_list(cur_el)
cur_el = _find_spec_part_single(cur_el, id_parts[-1])
- return cur_el
+ # Due to the raw datatypes we use, it is safer to return a deep copy here
+ return copy.deepcopy(cur_el)
def spec_name_list(spec, prefix="", recurse=False):
"""Returns a full list of all possible item identifiers in the
@@ -184,12 +222,12 @@ def spec_name_list(spec, prefix="", recurse=False):
if prefix != "" and not prefix.endswith("/"):
prefix += "/"
if type(spec) == dict:
- if 'map_item_spec' in spec:
+ if spec_part_is_map(spec):
for map_el in spec['map_item_spec']:
name = map_el['item_name']
if map_el['item_type'] == 'map':
name += "/"
- if recurse and 'map_item_spec' in map_el:
+ if recurse and spec_part_is_map(map_el):
result.extend(spec_name_list(map_el['map_item_spec'], prefix + map_el['item_name'], recurse))
else:
result.append(prefix + name)
@@ -244,7 +282,12 @@ class ConfigData:
def get_default_value(self, identifier):
"""Returns the default from the specification, or None if there
is no default"""
- spec = find_spec_part(self.specification.get_config_spec(), identifier)
+ # We are searching for the default value, so we can set
+ # strict_identifier to false (in fact, we need to; we may not know
+ # some list indices, or they may not exist, we are looking for
+ # a default value for a reason here).
+ spec = find_spec_part(self.specification.get_config_spec(),
+ identifier, False)
if spec and 'item_default' in spec:
return spec['item_default']
else:
@@ -313,6 +356,10 @@ class MultiConfigData:
self._current_config = {}
self._local_changes = {}
+ def clear_specifications(self):
+ """Remove all known module specifications"""
+ self._specifications = {}
+
def set_specification(self, spec):
"""Add or update a ModuleSpec. Raises a ConfigDataError is spec is not a ModuleSpec"""
if type(spec) != isc.config.ModuleSpec:
@@ -373,6 +420,14 @@ class MultiConfigData:
manager or the modules."""
return self._local_changes
+ def set_local_changes(self, new_local_changes):
+ """Sets the entire set of local changes, used when reverting
+ changes done automatically in case there was a problem (e.g.
+ when executing commands from a script that fails halfway
+ through).
+ """
+ self._local_changes = new_local_changes
+
def clear_local_changes(self):
"""Reverts all local changes"""
self._local_changes = {}
@@ -534,8 +589,10 @@ class MultiConfigData:
if item_type == "list" and (all or first):
spec_part_list = spec_part['list_item_spec']
list_value, status = self.get_value(identifier)
+ # If not set, and no default, lists will show up as 'None',
+ # but it's better to treat it as an empty list then
if list_value is None:
- raise isc.cc.data.DataNotFoundError(identifier + " not found")
+ list_value = []
if type(list_value) != list:
# the identifier specified a single element
@@ -603,7 +660,7 @@ class MultiConfigData:
Throws DataNotFoundError if the identifier is bad
"""
result = []
- if not identifier:
+ if not identifier or identifier == "/":
# No identifier, so we need the list of current modules
for module in self._specifications.keys():
if all:
@@ -615,8 +672,11 @@ class MultiConfigData:
entry = _create_value_map_entry(module, 'module', None)
result.append(entry)
else:
- if identifier[0] == '/':
+ # Strip off start and end slashes, if they are there
+ if len(identifier) > 0 and identifier[0] == '/':
identifier = identifier[1:]
+ if len(identifier) > 0 and identifier[-1] == '/':
+ identifier = identifier[:-1]
module, sep, id = identifier.partition('/')
spec = self.get_module_spec(module)
if spec:
@@ -624,6 +684,16 @@ class MultiConfigData:
self._append_value_item(result, spec_part, identifier, all, True)
return result
+ def unset(self, identifier):
+ """
+ Reset the value to default.
+ """
+ spec_part = self.find_spec_part(identifier)
+ if spec_part is not None:
+ isc.cc.data.unset(self._local_changes, identifier)
+ else:
+ raise isc.cc.data.DataNotFoundError(identifier + "not found")
+
def set_value(self, identifier, value):
"""Set the local value at the given identifier to value. If
there is a specification for the given identifier, the type
@@ -672,6 +742,15 @@ class MultiConfigData:
cur_id_part + id,
cur_value)
cur_id_part = cur_id_part + id_part + "/"
+
+ # We also need to copy to local if we are changing a named set,
+ # so that the other items in the set do not disappear
+ if spec_part_is_named_set(self.find_spec_part(cur_id_part)):
+ ns_value, ns_status = self.get_value(cur_id_part)
+ if ns_status != MultiConfigData.LOCAL:
+ isc.cc.data.set(self._local_changes,
+ cur_id_part,
+ ns_value)
isc.cc.data.set(self._local_changes, identifier, value)
def _get_list_items(self, item_name):
diff --git a/src/lib/python/isc/config/config_messages.mes b/src/lib/python/isc/config/config_messages.mes
index c52efb4..9e93ca3 100644
--- a/src/lib/python/isc/config/config_messages.mes
+++ b/src/lib/python/isc/config/config_messages.mes
@@ -31,3 +31,9 @@ The configuration manager returned an error response when the module
requested its configuration. The full error message answer from the
configuration manager is appended to the log error.
+% CONFIG_SESSION_STOPPING_FAILED error sending stopping message: %1
+There was a problem when sending a message signaling that the module using
+this CCSession is stopping. This message is sent so that the rest of the
+system is aware that the module is no longer running. Apart from logging
+this message, the error itself is ignored, and the ModuleCCSession is
+still stopped. The specific exception message is printed.
diff --git a/src/lib/python/isc/config/tests/.gitignore b/src/lib/python/isc/config/tests/.gitignore
new file mode 100644
index 0000000..52a9c5e
--- /dev/null
+++ b/src/lib/python/isc/config/tests/.gitignore
@@ -0,0 +1 @@
+/config_test
diff --git a/src/lib/python/isc/config/tests/ccsession_test.py b/src/lib/python/isc/config/tests/ccsession_test.py
index 8d616e2..d1060bf 100644
--- a/src/lib/python/isc/config/tests/ccsession_test.py
+++ b/src/lib/python/isc/config/tests/ccsession_test.py
@@ -250,6 +250,18 @@ class TestModuleCCSession(unittest.TestCase):
self.assertEqual({'command': ['get_config', {'module_name': 'Spec2'}]},
fake_session.get_message('ConfigManager', None))
+ def test_stop(self):
+ fake_session = FakeModuleCCSession()
+ self.assertFalse("Spec1" in fake_session.subscriptions)
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ self.assertTrue("Spec1" in fake_session.subscriptions)
+
+ self.assertEqual(len(fake_session.message_queue), 0)
+ mccs.send_stopping()
+ self.assertEqual(len(fake_session.message_queue), 1)
+ self.assertEqual({'command': ['stopping', {'module_name': 'Spec1'}]},
+ fake_session.get_message('ConfigManager', None))
+
def test_get_socket(self):
fake_session = FakeModuleCCSession()
mccs = self.create_session("spec1.spec", None, None, fake_session)
@@ -476,45 +488,6 @@ class TestModuleCCSession(unittest.TestCase):
self.assertEqual({'result': [0]},
fake_session.get_message('Spec2', None))
- def test_check_command_without_recvmsg_remote_module(self):
- "copied from test_check_command3"
- fake_session = FakeModuleCCSession()
- mccs = self.create_session("spec1.spec", None, None, fake_session)
- mccs.set_config_handler(self.my_config_handler_ok)
- self.assertEqual(len(fake_session.message_queue), 0)
-
- fake_session.group_sendmsg(None, 'Spec2')
- rmodname = mccs.add_remote_config(self.spec_file("spec2.spec"))
- print(fake_session.message_queue)
- self.assertEqual({'command': ['get_config', {'module_name': 'Spec2'}]},
- fake_session.get_message('ConfigManager', None))
- self.assertEqual(len(fake_session.message_queue), 0)
-
- cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'Spec2': { 'item1': 2 }})
- env = { 'group':'Spec2', 'from':None }
- self.assertEqual(len(fake_session.message_queue), 0)
- mccs.check_command_without_recvmsg(cmd, env)
- self.assertEqual(len(fake_session.message_queue), 0)
-
- def test_check_command_without_recvmsg_remote_module2(self):
- "copied from test_check_command3"
- fake_session = FakeModuleCCSession()
- mccs = self.create_session("spec1.spec", None, None, fake_session)
- mccs.set_config_handler(self.my_config_handler_ok)
- self.assertEqual(len(fake_session.message_queue), 0)
-
- fake_session.group_sendmsg(None, 'Spec2')
- rmodname = mccs.add_remote_config(self.spec_file("spec2.spec"))
- self.assertEqual({'command': ['get_config', {'module_name': 'Spec2'}]},
- fake_session.get_message('ConfigManager', None))
- self.assertEqual(len(fake_session.message_queue), 0)
-
- cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'Spec3': { 'item1': 2 }})
- env = { 'group':'Spec3', 'from':None }
- self.assertEqual(len(fake_session.message_queue), 0)
- mccs.check_command_without_recvmsg(cmd, env)
- self.assertEqual(len(fake_session.message_queue), 0)
-
def test_check_command_block_timeout(self):
"""Check it works if session has timeout and it sets it back."""
def cmd_check(mccs, session):
@@ -542,16 +515,65 @@ class TestModuleCCSession(unittest.TestCase):
mccs.set_command_handler(self.my_command_handler_ok)
self.assertRaises(WouldBlockForever, lambda: mccs.check_command(False))
- def test_remote_module(self):
+ # Now there's a group of tests testing both add_remote_config and
+ # add_remote_config_by_name. Since they are almost the same (they differ
+ # just in the parameter and that the second one asks one more question over
+ # the bus), the actual test code is shared.
+ #
+ # These three functions are helper functions to easy up the writing of them.
+ # To write a test, there need to be 3 functions. First, the function that
+ # does the actual test. It looks like:
+ # def _internal_test(self, function_lambda, param, fill_other_messages):
+ #
+ # The function_lambda provides the tested function if called on the
+ # ccsession. The param is the parameter to pass to the function (either
+ # the module name or the spec file name. The fill_other_messages fills
+ # needed messages (the answer containing the module spec in case of add by
+ # name, no messages in the case of adding by spec file) into the fake bus.
+ # So, the code would look like:
+ #
+ # * Create the fake session and tested ccsession object
+ # * function = function_lambda(ccsession object)
+ # * fill_other_messages(fake session)
+ # * Fill in answer to the get_module_config command
+ # * Test by calling function(param)
+ #
+ # Then you need two wrappers that do launch the tests. There are helpers
+ # for that, so you can just call:
+ # def test_by_spec(self)
+ # self._common_remote_module_test(self._internal_test)
+ # def test_by_name(self)
+ # self._common_remote_module_by_name_test(self._internal_test)
+ def _common_remote_module_test(self, internal_test):
+ internal_test(lambda ccs: ccs.add_remote_config,
+ self.spec_file("spec2.spec"),
+ lambda session: None)
+
+ def _prepare_spec_message(self, session, spec_name):
+ # It could have been one command, but the line would be way too long
+ # to even split it
+ spec_file = self.spec_file(spec_name)
+ spec = isc.config.module_spec_from_file(spec_file)
+ session.group_sendmsg({'result': [0, spec.get_full_spec()]}, "Spec1")
+
+ def _common_remote_module_by_name_test(self, internal_test):
+ internal_test(lambda ccs: ccs.add_remote_config_by_name, "Spec2",
+ lambda session: self._prepare_spec_message(session,
+ "spec2.spec"))
+
+ def _internal_remote_module(self, function_lambda, parameter,
+ fill_other_messages):
fake_session = FakeModuleCCSession()
mccs = self.create_session("spec1.spec", None, None, fake_session)
mccs.remove_remote_config("Spec2")
+ function = function_lambda(mccs)
self.assertRaises(ModuleCCSessionError, mccs.get_remote_config_value, "Spec2", "item1")
self.assertFalse("Spec2" in fake_session.subscriptions)
+ fill_other_messages(fake_session)
fake_session.group_sendmsg(None, 'Spec2')
- rmodname = mccs.add_remote_config(self.spec_file("spec2.spec"))
+ rmodname = function(parameter)
self.assertTrue("Spec2" in fake_session.subscriptions)
self.assertEqual("Spec2", rmodname)
self.assertRaises(isc.cc.data.DataNotFoundError, mccs.get_remote_config_value, rmodname, "asdf")
@@ -563,36 +585,77 @@ class TestModuleCCSession(unittest.TestCase):
self.assertFalse("Spec2" in fake_session.subscriptions)
self.assertRaises(ModuleCCSessionError, mccs.get_remote_config_value, "Spec2", "item1")
- # test if unsubscription is alse sent when object is deleted
+ # test if unsubscription is also sent when object is deleted
+ fill_other_messages(fake_session)
fake_session.group_sendmsg({'result' : [0]}, 'Spec2')
- rmodname = mccs.add_remote_config(self.spec_file("spec2.spec"))
+ rmodname = function(parameter)
self.assertTrue("Spec2" in fake_session.subscriptions)
mccs = None
+ function = None
self.assertFalse("Spec2" in fake_session.subscriptions)
- def test_remote_module_with_custom_config(self):
+ def test_remote_module(self):
+ """
+ Test we can add a remote config and get the configuration.
+ Remote module specified by the spec file name.
+ """
+ self._common_remote_module_test(self._internal_remote_module)
+
+ def test_remote_module_by_name(self):
+ """
+ Test we can add a remote config and get the configuration.
+ Remote module specified its name.
+ """
+ self._common_remote_module_by_name_test(self._internal_remote_module)
+
+ def _internal_remote_module_with_custom_config(self, function_lambda,
+ parameter,
+ fill_other_messages):
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
+ function = function_lambda(mccs)
+ # override the default config value for "item1". add_remote_config[_by_name]()
+ # should incorporate the overridden value, and we should be able to
# get it via get_remote_config_value().
+ fill_other_messages(fake_session)
fake_session.group_sendmsg({'result': [0, {"item1": 10}]}, 'Spec2')
- rmodname = mccs.add_remote_config(self.spec_file("spec2.spec"))
+ rmodname = function(parameter)
value, default = mccs.get_remote_config_value(rmodname, "item1")
self.assertEqual(10, value)
self.assertEqual(False, default)
- def test_ignore_command_remote_module(self):
+ def test_remote_module_with_custom_config(self):
+ """
+ Test the config of module will load non-default values on
+ initialization.
+ Remote module specified by the spec file name.
+ """
+ self._common_remote_module_test(
+ self._internal_remote_module_with_custom_config)
+
+ def test_remote_module_by_name_with_custom_config(self):
+ """
+ Test the config of module will load non-default values on
+ initialization.
+ Remote module its name.
+ """
+ self._common_remote_module_by_name_test(
+ self._internal_remote_module_with_custom_config)
+
+ def _internal_ignore_command_remote_module(self, function_lambda, param,
+ fill_other_messages):
# Create a Spec1 module and subscribe to remote config for Spec2
fake_session = FakeModuleCCSession()
mccs = self.create_session("spec1.spec", None, None, fake_session)
mccs.set_command_handler(self.my_command_handler_ok)
+ function = function_lambda(mccs)
+ fill_other_messages(fake_session)
fake_session.group_sendmsg(None, 'Spec2')
- rmodname = mccs.add_remote_config(self.spec_file("spec2.spec"))
+ rmodname = function(param)
- # remove the 'get config' from the queue
- self.assertEqual(len(fake_session.message_queue), 1)
- fake_session.get_message("ConfigManager")
+ # remove the commands from queue
+ while len(fake_session.message_queue) > 0:
+ fake_session.get_message("ConfigManager")
# check if the command for the module itself is received
cmd = isc.config.ccsession.create_command("just_some_command", { 'foo': 'a' })
@@ -610,6 +673,174 @@ class TestModuleCCSession(unittest.TestCase):
mccs.check_command()
self.assertEqual(len(fake_session.message_queue), 0)
+ def test_ignore_commant_remote_module(self):
+ """
+ Test that commands for remote modules aren't handled.
+ Remote module specified by the spec file name.
+ """
+ self._common_remote_module_test(
+ self._internal_ignore_command_remote_module)
+
+ def test_ignore_commant_remote_module_by_name(self):
+ """
+ Test that commands for remote modules aren't handled.
+ Remote module specified by its name.
+ """
+ self._common_remote_module_by_name_test(
+ self._internal_ignore_command_remote_module)
+
+ def _internal_check_command_without_recvmsg_remote_module(self,
+ function_lambda,
+ param,
+ fill_other_messages):
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ mccs.set_config_handler(self.my_config_handler_ok)
+ function = function_lambda(mccs)
+ self.assertEqual(len(fake_session.message_queue), 0)
+
+ fill_other_messages(fake_session)
+ fake_session.group_sendmsg(None, 'Spec2')
+ rmodname = function(param)
+ if (len(fake_session.message_queue) == 2):
+ self.assertEqual({'command': ['get_module_spec',
+ {'module_name': 'Spec2'}]},
+ fake_session.get_message('ConfigManager', None))
+ self.assertEqual({'command': ['get_config', {'module_name': 'Spec2'}]},
+ fake_session.get_message('ConfigManager', None))
+ self.assertEqual(len(fake_session.message_queue), 0)
+
+ cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'Spec2': { 'item1': 2 }})
+ env = { 'group':'Spec2', 'from':None }
+ self.assertEqual(len(fake_session.message_queue), 0)
+ mccs.check_command_without_recvmsg(cmd, env)
+ self.assertEqual(len(fake_session.message_queue), 0)
+
+ def test_check_command_without_recvmsg_remote_module(self):
+ """
+ Test updates on remote module.
+ The remote module is specified by the spec file name.
+ """
+ self._common_remote_module_test(
+ self._internal_check_command_without_recvmsg_remote_module)
+
+ def test_check_command_without_recvmsg_remote_module_by_name(self):
+ """
+ Test updates on remote module.
+ The remote module is specified by its name.
+ """
+ self._common_remote_module_by_name_test(
+ self._internal_check_command_without_recvmsg_remote_module)
+
+ def _internal_check_command_without_recvmsg_remote_module2(self,
+ function_lambda,
+ param,
+ fill_other_messages):
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ mccs.set_config_handler(self.my_config_handler_ok)
+ function = function_lambda(mccs)
+ self.assertEqual(len(fake_session.message_queue), 0)
+
+ fill_other_messages(fake_session)
+ fake_session.group_sendmsg(None, 'Spec2')
+ rmodname = function(param)
+ if (len(fake_session.message_queue) == 2):
+ self.assertEqual({'command': ['get_module_spec',
+ {'module_name': 'Spec2'}]},
+ fake_session.get_message('ConfigManager', None))
+ self.assertEqual({'command': ['get_config', {'module_name': 'Spec2'}]},
+ fake_session.get_message('ConfigManager', None))
+ self.assertEqual(len(fake_session.message_queue), 0)
+
+ cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'Spec3': { 'item1': 2 }})
+ env = { 'group':'Spec3', 'from':None }
+ self.assertEqual(len(fake_session.message_queue), 0)
+ mccs.check_command_without_recvmsg(cmd, env)
+ self.assertEqual(len(fake_session.message_queue), 0)
+
+ def test_check_command_without_recvmsg_remote_module2(self):
+ """
+ Test updates on remote module.
+ The remote module is specified by the spec file name.
+ """
+ self._common_remote_module_test(
+ self._internal_check_command_without_recvmsg_remote_module2)
+
+ def test_check_command_without_recvmsg_remote_module_by_name2(self):
+ """
+ Test updates on remote module.
+ The remote module is specified by its name.
+ """
+ self._common_remote_module_by_name_test(
+ self._internal_check_command_without_recvmsg_remote_module2)
+
+ def _internal_remote_module_bad_config(self, function_lambda, parameter,
+ fill_other_messages):
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ function = function_lambda(mccs)
+ # Provide wrong config data. It should be rejected.
+ fill_other_messages(fake_session)
+ fake_session.group_sendmsg({'result': [0, {"bad_item": -1}]}, 'Spec2')
+ self.assertRaises(isc.config.ModuleCCSessionError,
+ function, parameter)
+
+ def test_remote_module_bad_config(self):
+ """
+ Test the remote module rejects bad config data.
+ """
+ self._common_remote_module_test(
+ self._internal_remote_module_bad_config)
+
+ def test_remote_module_by_name_bad_config(self):
+ """
+ Test the remote module rejects bad config data.
+ """
+ self._common_remote_module_by_name_test(
+ self._internal_remote_module_bad_config)
+
+ def _internal_remote_module_error_response(self, function_lambda,
+ parameter, fill_other_messages):
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ function = function_lambda(mccs)
+ # Provide wrong config data. It should be rejected.
+ fill_other_messages(fake_session)
+ fake_session.group_sendmsg({'result': [1, "An error, and I mean it!"]},
+ 'Spec2')
+ self.assertRaises(isc.config.ModuleCCSessionError,
+ function, parameter)
+
+ def test_remote_module_bad_config(self):
+ """
+ Test the remote module complains if there's an error response."
+ """
+ self._common_remote_module_test(
+ self._internal_remote_module_error_response)
+
+ def test_remote_module_by_name_bad_config(self):
+ """
+ Test the remote module complains if there's an error response."
+ """
+ self._common_remote_module_by_name_test(
+ self._internal_remote_module_error_response)
+
+ def test_remote_module_bad_config(self):
+ """
+ Test the remote module rejects bad config data.
+ """
+ self._common_remote_module_by_name_test(
+ self._internal_remote_module_bad_config)
+
+ def test_module_name_mismatch(self):
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ mccs.set_config_handler(self.my_config_handler_ok)
+ self._prepare_spec_message(fake_session, 'spec1.spec')
+ self.assertRaises(isc.config.ModuleCCSessionError,
+ mccs.add_remote_config_by_name, "Spec2")
+
def test_logconfig_handler(self):
# test whether default_logconfig_handler reacts nicely to
# bad data. We assume the actual logger output is tested
@@ -701,6 +932,12 @@ class TestUIModuleCCSession(unittest.TestCase):
fake_conn.set_get_answer('/config_data', { 'version': BIND10_CONFIG_DATA_VERSION })
return UIModuleCCSession(fake_conn)
+ def create_uccs_listtest(self, fake_conn):
+ module_spec = isc.config.module_spec_from_file(self.spec_file("spec39.spec"))
+ fake_conn.set_get_answer('/module_spec', { module_spec.get_module_name(): module_spec.get_full_spec()})
+ fake_conn.set_get_answer('/config_data', { 'version': BIND10_CONFIG_DATA_VERSION })
+ return UIModuleCCSession(fake_conn)
+
def test_init(self):
fake_conn = fakeUIConn()
fake_conn.set_get_answer('/module_spec', {})
@@ -718,6 +955,38 @@ class TestUIModuleCCSession(unittest.TestCase):
fake_conn.set_get_answer('/config_data', { 'version': 123123 })
self.assertRaises(ModuleCCSessionError, UIModuleCCSession, fake_conn)
+ def test_request_specifications(self):
+ module_spec1 = isc.config.module_spec_from_file(
+ self.spec_file("spec1.spec"))
+ module_spec_dict1 = { "module_spec": module_spec1.get_full_spec() }
+ module_spec2 = isc.config.module_spec_from_file(
+ self.spec_file("spec2.spec"))
+ module_spec_dict2 = { "module_spec": module_spec2.get_full_spec() }
+
+ fake_conn = fakeUIConn()
+ # Set the first one in the answer
+ fake_conn.set_get_answer('/module_spec', module_spec_dict1)
+ fake_conn.set_get_answer('/config_data',
+ { 'version': BIND10_CONFIG_DATA_VERSION })
+ uccs = UIModuleCCSession(fake_conn)
+
+ # We should now have the first one, but not the second.
+ self.assertTrue("Spec1" in uccs._specifications)
+ self.assertEqual(module_spec1.get_full_spec(),
+ uccs._specifications["Spec1"].get_full_spec())
+ self.assertFalse("Spec2" in uccs._specifications)
+
+ # Now set an answer where only the second one is present
+ fake_conn.set_get_answer('/module_spec', module_spec_dict2)
+
+ uccs.request_specifications()
+
+ # Now Spec1 should have been removed, and spec2 should be there
+ self.assertFalse("Spec1" in uccs._specifications)
+ self.assertTrue("Spec2" in uccs._specifications)
+ self.assertEqual(module_spec2.get_full_spec(),
+ uccs._specifications["Spec2"].get_full_spec())
+
def test_add_remove_value(self):
fake_conn = fakeUIConn()
uccs = self.create_uccs2(fake_conn)
@@ -751,6 +1020,14 @@ class TestUIModuleCCSession(unittest.TestCase):
self.assertRaises(isc.cc.data.DataTypeError,
uccs.remove_value, "Spec2/item5", None)
+ def test_add_dup_value(self):
+ fake_conn = fakeUIConn()
+ uccs = self.create_uccs_listtest(fake_conn)
+
+ uccs.add_value("Spec39/list")
+ self.assertRaises(isc.cc.data.DataAlreadyPresentError, uccs.add_value,
+ "Spec39/list")
+
def test_add_remove_value_named_set(self):
fake_conn = fakeUIConn()
uccs = self.create_uccs_named_set(fake_conn)
@@ -787,6 +1064,8 @@ class TestUIModuleCCSession(unittest.TestCase):
self.assertRaises(isc.cc.data.DataNotFoundError,
uccs.remove_value, "/Spec32/named_set_item",
"no_such_item")
+ self.assertRaises(isc.cc.data.DataAlreadyPresentError,
+ uccs.add_value, "/Spec32/named_set_item", "c")
def test_set_value_named_set(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 589a398..891a7d7 100644
--- a/src/lib/python/isc/config/tests/cfgmgr_test.py
+++ b/src/lib/python/isc/config/tests/cfgmgr_test.py
@@ -74,6 +74,60 @@ class TestConfigManagerData(unittest.TestCase):
self.assertEqual(self.config_manager_data, new_config)
os.remove(output_file_name)
+ def check_existence(self, files, should_exist=[], should_not_exist=[]):
+ """Helper function for test_rename_config_file.
+ Arguments:
+ files: array of file names to check.
+ should_exist: array of indices, the files in 'files' with these
+ indices should exist.
+ should_not_exist: array of indices, the files in 'files' with
+ these indices should not exist."""
+ for n in should_exist:
+ self.assertTrue(os.path.exists(files[n]))
+ for n in should_not_exist:
+ self.assertFalse(os.path.exists(files[n]))
+
+ def test_rename_config_file(self):
+ # test file names, put in array for easy cleanup
+ filenames = [ "b10-config-rename-test",
+ "b10-config-rename-test.bak",
+ "b10-config-rename-test.bak.1",
+ "b10-config-rename-test.bak.2" ]
+
+ for filename in filenames:
+ if os.path.exists(filename):
+ os.remove(filename)
+
+ # The original does not exist, so the new one should not be created
+ self.config_manager_data.rename_config_file(filenames[0])
+ self.check_existence(filenames, [], [0, 1, 2, 3])
+
+ # now create a file to rename, and call rename again
+ self.config_manager_data.write_to_file(filenames[0])
+ self.config_manager_data.rename_config_file(filenames[0])
+ self.check_existence(filenames, [1], [0, 2, 3])
+
+ # If backup already exists, give it a new name automatically
+ self.config_manager_data.write_to_file(filenames[0])
+ self.config_manager_data.rename_config_file(filenames[0])
+ self.check_existence(filenames, [1, 2], [0, 3])
+
+ # If backup already exists, give it a new name automatically with
+ # increasing postfix
+ self.config_manager_data.write_to_file(filenames[0])
+ self.config_manager_data.rename_config_file(filenames[0])
+ self.check_existence(filenames, [1, 2, 3], [0])
+
+ # Test with explicit renamed file argument
+ self.config_manager_data.rename_config_file(filenames[1],
+ filenames[0])
+ self.check_existence(filenames, [0, 2, 3], [1])
+
+ # clean up again to be nice
+ for filename in filenames:
+ if os.path.exists(filename):
+ os.remove(filename)
+
def test_equality(self):
# tests the __eq__ function. Equality is only defined
# by equality of the .data element. If data_path or db_filename
@@ -240,9 +294,6 @@ class TestConfigManager(unittest.TestCase):
def test_read_config(self):
self.assertEqual(self.cm.config.data, {'version': config_data.BIND10_CONFIG_DATA_VERSION})
- self.cm.read_config()
- # due to what get written, the value here is what the last set_config command in test_handle_msg does
- self.assertEqual(self.cm.config.data, {'TestModule': {'test': 125}, 'version': config_data.BIND10_CONFIG_DATA_VERSION})
self.cm.data_path = "/no_such_path"
self.cm.read_config()
self.assertEqual(self.cm.config.data, {'version': config_data.BIND10_CONFIG_DATA_VERSION})
@@ -255,115 +306,195 @@ class TestConfigManager(unittest.TestCase):
answer = self.cm.handle_msg(msg)
self.assertEqual(expected_answer, answer)
- def test_handle_msg(self):
- self._handle_msg_helper({}, { 'result': [ 1, 'Unknown message format: {}']})
- self._handle_msg_helper("", { 'result': [ 1, 'Unknown message format: ']})
- self._handle_msg_helper({ "command": [ "badcommand" ] }, { 'result': [ 1, "Unknown command: badcommand"]})
- self._handle_msg_helper({ "command": [ "get_commands_spec" ] }, { 'result': [ 0, {} ]})
- self._handle_msg_helper({ "command": [ "get_statistics_spec" ] }, { 'result': [ 0, {} ]})
- self._handle_msg_helper({ "command": [ "get_module_spec" ] }, { 'result': [ 0, {} ]})
- self._handle_msg_helper({ "command": [ "get_module_spec", { "module_name": "Spec2" } ] }, { 'result': [ 0, {} ]})
- #self._handle_msg_helper({ "command": [ "get_module_spec", { "module_name": "nosuchmodule" } ] },
- # {'result': [1, 'No specification for module nosuchmodule']})
+ def test_handle_msg_basic_commands(self):
+ # Some basic commands, where not much interaction happens, just
+ # check the result
+ self._handle_msg_helper({},
+ { 'result': [ 1, 'Unknown message format: {}']})
+ self._handle_msg_helper("",
+ { 'result': [ 1, 'Unknown message format: ']})
+ self._handle_msg_helper({ "command": [ "badcommand" ] },
+ { 'result': [ 1, "Unknown command: badcommand"]})
+ self._handle_msg_helper({ "command": [ "get_commands_spec" ] },
+ { 'result': [ 0, {} ]})
+ self._handle_msg_helper({ "command": [ "get_statistics_spec" ] },
+ { 'result': [ 0, {} ]})
+ self._handle_msg_helper({ "command": [ "get_module_spec" ] },
+ { 'result': [ 0, {} ]})
+ self._handle_msg_helper({ "command": [ "get_module_spec",
+ { "module_name": "Spec2" } ] },
+ { 'result': [ 0, {} ]})
self._handle_msg_helper({ "command": [ "get_module_spec", 1 ] },
- {'result': [1, 'Bad get_module_spec command, argument not a dict']})
+ {'result': [1, 'Bad get_module_spec command, '+
+ 'argument not a dict']})
self._handle_msg_helper({ "command": [ "get_module_spec", { } ] },
- {'result': [1, 'Bad module_name in get_module_spec command']})
- self._handle_msg_helper({ "command": [ "get_config" ] }, { 'result': [ 0, { 'version': config_data.BIND10_CONFIG_DATA_VERSION } ]})
- self._handle_msg_helper({ "command": [ "get_config", { "module_name": "nosuchmodule" } ] },
- {'result': [0, { 'version': config_data.BIND10_CONFIG_DATA_VERSION }]})
+ {'result': [1, 'Bad module_name in '+
+ 'get_module_spec command']})
+ self._handle_msg_helper({ "command": [ "get_config" ] },
+ { 'result': [ 0, { 'version':
+ config_data.BIND10_CONFIG_DATA_VERSION }]})
+ self._handle_msg_helper({ "command": [ "get_config",
+ { "module_name": "nosuchmodule" } ] },
+ {'result': [0, { 'version':
+ config_data.BIND10_CONFIG_DATA_VERSION }]})
self._handle_msg_helper({ "command": [ "get_config", 1 ] },
- {'result': [1, 'Bad get_config command, argument not a dict']})
+ {'result': [1, 'Bad get_config command, '+
+ 'argument not a dict']})
self._handle_msg_helper({ "command": [ "get_config", { } ] },
- {'result': [1, 'Bad module_name in get_config command']})
+ {'result': [1, 'Bad module_name in '+
+ 'get_config command']})
self._handle_msg_helper({ "command": [ "set_config" ] },
{'result': [1, 'Wrong number of arguments']})
self._handle_msg_helper({ "command": [ "set_config", [{}]] },
{'result': [0]})
+
self.assertEqual(len(self.fake_session.message_queue), 0)
- # the targets of some of these tests expect specific answers, put
- # those in our fake msgq first.
- my_ok_answer = { 'result': [ 0 ] }
+ def test_handle_msg_module_and_stats_commands(self):
+ self._handle_msg_helper({ "command":
+ ["module_spec", self.spec.get_full_spec()]
+ },
+ {'result': [0]})
+ # There should be a message on the queue about the 'new' Spec2 module
+ # from ConfigManager to Cmdctl, containing its name and full
+ # specification
+ self.assertEqual(ccsession.create_command(
+ ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE,
+ [ self.spec.get_module_name(),
+ self.spec.get_full_spec()]),
+ self.fake_session.get_message("Cmdctl", None))
+
+ self._handle_msg_helper({ "command": [ "module_spec", { 'foo': 1 } ] },
+ {'result': [1, 'Error in data definition: no '+
+ 'module_name in module_spec']})
+ self._handle_msg_helper({ "command": [ "get_module_spec" ] },
+ { 'result': [ 0, { self.spec.get_module_name():
+ self.spec.get_full_spec() } ]})
+ self._handle_msg_helper({ "command": [ "get_module_spec",
+ { "module_name" : "Spec2" } ] },
+ { 'result': [ 0, self.spec.get_full_spec() ] })
+ self._handle_msg_helper({ "command": [ "get_commands_spec" ] },
+ { 'result': [ 0, { self.spec.get_module_name():
+ self.spec.get_commands_spec()}]})
+ self._handle_msg_helper({ "command": [ "get_statistics_spec" ] },
+ { 'result': [ 0, { self.spec.get_module_name():
+ self.spec.get_statistics_spec()}]})
+
+ def __test_handle_msg_update_config_helper(self, new_config):
+ # Helper function for the common pattern in
+ # test_handle_msg_update_config; send 'set config', check for
+ # update message, check if config has indeed been updated
+
+ my_ok_answer = { 'result': [ 0 ] }
# Send the 'ok' that cfgmgr expects back to the fake queue first
self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
- # then send the command
- self._handle_msg_helper({ "command": [ "set_config", [self.name, { "test": 123 }] ] },
+
+ config_version = config_data.BIND10_CONFIG_DATA_VERSION
+ self._handle_msg_helper({ "command": [ "set_config",
+ [ { "version": config_version,
+ self.name: new_config } ] ] },
my_ok_answer)
- # The cfgmgr should have eaten the ok message, and sent out an update again
+
+ # The cfgmgr should have eaten the ok message, and sent out an update
+ # message
self.assertEqual(len(self.fake_session.message_queue), 1)
- self.assertEqual({'command': [ 'config_update', {'test': 123}]},
+ self.assertEqual({'command': [ 'config_update', new_config]},
self.fake_session.get_message(self.name, None))
+
+ # Config should have been updated
+ self.assertEqual(self.cm.config.data, {self.name: new_config,
+ 'version': config_version})
+
# and the queue should now be empty again
self.assertEqual(len(self.fake_session.message_queue), 0)
- # below are variations of the theme above
- self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
- self._handle_msg_helper({ "command": [ "set_config", [self.name, { "test": 124 }] ] },
- my_ok_answer)
- self.assertEqual(len(self.fake_session.message_queue), 1)
- self.assertEqual({'command': [ 'config_update', {'test': 124}]},
- self.fake_session.get_message(self.name, None))
- self.assertEqual(len(self.fake_session.message_queue), 0)
+ def test_handle_msg_update_config(self):
+ # Update the configuration and check results a few times
+ # only work the first time
+ self.__test_handle_msg_update_config_helper({ "test": 123 })
+ self.__test_handle_msg_update_config_helper({ "test": 124 })
- # This is the last 'succes' one, the value set here is what test_read_config expects
- self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
- self._handle_msg_helper({ "command": [ "set_config", [ { self.name: { "test": 125 } }] ] },
- my_ok_answer )
- self.assertEqual(len(self.fake_session.message_queue), 1)
- self.assertEqual({'command': [ 'config_update', {'test': 125}]},
- self.fake_session.get_message(self.name, None))
- self.assertEqual(len(self.fake_session.message_queue), 0)
+ self.__test_handle_msg_update_config_helper({ "test": 125 })
+
+ self.__test_handle_msg_update_config_helper({ "test": 126 })
- my_bad_answer = { 'result': [1, "bad_answer"] }
+ # Now send an error result (i.e. config not accepted)
+ my_bad_answer = { 'result': [1, "bad config"] }
self.fake_session.group_sendmsg(my_bad_answer, "ConfigManager")
- self._handle_msg_helper({ "command": [ "set_config", [ self.name, { "test": 125 }] ] },
+ self._handle_msg_helper({ "command": [ "set_config",
+ [self.name, { "test": 127 }] ] },
my_bad_answer )
self.assertEqual(len(self.fake_session.message_queue), 1)
- self.assertEqual({'command': [ 'config_update', {'test': 125}]},
+ self.assertEqual({'command': [ 'config_update', {'test': 127}]},
self.fake_session.get_message(self.name, None))
+ # Config should not be updated due to the error
+ self.cm.read_config()
+ self.assertEqual(self.cm.config.data, { self.name: {'test': 126},
+ 'version': config_data.BIND10_CONFIG_DATA_VERSION})
+
self.assertEqual(len(self.fake_session.message_queue), 0)
self.fake_session.group_sendmsg(None, 'ConfigManager')
self._handle_msg_helper({ "command": [ "set_config", [ ] ] },
{'result': [1, 'Wrong number of arguments']} )
- self._handle_msg_helper({ "command": [ "set_config", [ self.name, { "test": 125 }] ] },
- { 'result': [1, 'No answer message from TestModule']} )
-
- #self.assertEqual(len(self.fake_session.message_queue), 1)
- #self.assertEqual({'config_update': {'test': 124}},
- # self.fake_session.get_message(self.name, None))
- #self.assertEqual({'version': 1, 'TestModule': {'test': 124}}, self.cm.config.data)
- #
+ self._handle_msg_helper({ "command": [ "set_config",
+ [ self.name, { "test": 128 }]]},
+ { 'result': [1, 'No answer message '+
+ 'from TestModule']} )
+
+ # This command should leave a message to the TestModule to update its
+ # configuration (since the TestModule did not eat it)
+ self.assertEqual(len(self.fake_session.message_queue), 1)
+ self.assertEqual(
+ ccsession.create_command(ccsession.COMMAND_CONFIG_UPDATE,
+ { "test": 128 }),
+ self.fake_session.get_message("TestModule", None))
+
+ # Make sure queue is empty now
+ self.assertEqual(len(self.fake_session.message_queue), 0)
+
+ # Shutdown should result in 'ok' answer
self._handle_msg_helper({ "command":
- ["module_spec", self.spec.get_full_spec()]
+ ["shutdown"]
},
{'result': [0]})
- self._handle_msg_helper({ "command": [ "module_spec", { 'foo': 1 } ] },
- {'result': [1, 'Error in data definition: no module_name in module_spec']})
- self._handle_msg_helper({ "command": [ "get_module_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_full_spec() } ]})
- self._handle_msg_helper({ "command": [ "get_module_spec",
- { "module_name" : "Spec2" } ] },
- { 'result': [ 0, self.spec.get_full_spec() ] })
- self._handle_msg_helper({ "command": [ "get_commands_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_commands_spec() } ]})
- self._handle_msg_helper({ "command": [ "get_statistics_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_statistics_spec() } ]})
- # re-add this once we have new way to propagate spec changes (1 instead of the current 2 messages)
- #self.assertEqual(len(self.fake_session.message_queue), 2)
- # the name here is actually wrong (and hardcoded), but needed in the current version
- # TODO: fix that
- #self.assertEqual({'specification_update': [ self.name, self.spec ] },
- # self.fake_session.get_message("Cmdctl", None))
- #self.assertEqual({'commands_update': [ self.name, self.commands ] },
- # self.fake_session.get_message("Cmdctl", None))
+ def test_stopping_message(self):
+ # Update the system by announcing this module
self._handle_msg_helper({ "command":
- ["shutdown"]
+ ["module_spec", self.spec.get_full_spec()]
},
{'result': [0]})
+ # This causes a update to be sent from the ConfigManager to the CmdCtl
+ # channel, containing the new module's name and full specification
+ self.assertEqual(ccsession.create_command(
+ ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE,
+ [ self.spec.get_module_name(),
+ self.spec.get_full_spec()]),
+ self.fake_session.get_message("Cmdctl", None))
+
+ # A stopping message should get no response, but should cause another
+ # message to be sent, if it is a known module
+ self._handle_msg_helper({ "command": [ "stopping",
+ { "module_name": "Spec2"}] },
+ None)
+ self.assertEqual(len(self.fake_session.message_queue), 1)
+ self.assertEqual({'command': [ 'module_specification_update',
+ ['Spec2', None] ] },
+ self.fake_session.get_message("Cmdctl", None))
+
+ # but if the 'stopping' module is either unknown or not running,
+ # no followup message should be sent
+ self._handle_msg_helper({ "command":
+ [ "stopping",
+ { "module_name": "NoSuchModule" } ] },
+ None)
+ self.assertEqual(len(self.fake_session.message_queue), 0)
+
def test_set_config_virtual(self):
"""Test that if the module is virtual, we don't send it over the
message bus, but call the checking function.
@@ -381,9 +512,9 @@ class TestConfigManager(unittest.TestCase):
self.cm.set_virtual_module(self.spec, check_test)
# The fake session will throw now if it tries to read a response.
# Handy, we don't need to find a complicated way to check for it.
- result = self.cm._handle_set_config_module(self.spec.
- get_module_name(),
- {'item1': value})
+ result = self.cm.handle_msg(ccsession.create_command(
+ ccsession.COMMAND_SET_CONFIG,
+ [self.spec.get_module_name(), { "item1": value }]))
# Check the correct result is passed and our function was called
# With correct data
self.assertEqual(self.called_with['item1'], value)
@@ -415,19 +546,22 @@ class TestConfigManager(unittest.TestCase):
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.cm.handle_msg(ccsession.create_command(
+ ccsession.COMMAND_SET_CONFIG, ["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.cm.handle_msg(ccsession.create_command(
+ ccsession.COMMAND_SET_CONFIG, ["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.cm.handle_msg(ccsession.create_command(
+ ccsession.COMMAND_SET_CONFIG, ["test", { "value2": True }]))
self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
"test": { "value1": 124,
"value2": True
@@ -435,7 +569,8 @@ class TestConfigManager(unittest.TestCase):
}, 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.cm.handle_msg(ccsession.create_command(
+ ccsession.COMMAND_SET_CONFIG, ["test", { "value3": [ 1, 2, 3 ] }]))
self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
"test": { "value1": 124,
"value2": True,
@@ -444,7 +579,8 @@ class TestConfigManager(unittest.TestCase):
}, self.cm.config.data)
self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
- self.cm._handle_set_config_all({"test": { "value2": False }})
+ self.cm.handle_msg(ccsession.create_command(
+ ccsession.COMMAND_SET_CONFIG, ["test", { "value2": False }]))
self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
"test": { "value1": 124,
"value2": False,
@@ -453,7 +589,8 @@ class TestConfigManager(unittest.TestCase):
}, self.cm.config.data)
self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
- self.cm._handle_set_config_all({"test": { "value1": None }})
+ self.cm.handle_msg(ccsession.create_command(
+ ccsession.COMMAND_SET_CONFIG, ["test", { "value1": None }]))
self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
"test": { "value2": False,
"value3": [ 1, 2, 3 ]
@@ -461,7 +598,8 @@ class TestConfigManager(unittest.TestCase):
}, self.cm.config.data)
self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
- self.cm._handle_set_config_all({"test": { "value3": [ 1 ] }})
+ self.cm.handle_msg(ccsession.create_command(
+ ccsession.COMMAND_SET_CONFIG, ["test", { "value3": [ 1 ] }]))
self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
"test": { "value2": False,
"value3": [ 1 ]
@@ -472,14 +610,20 @@ class TestConfigManager(unittest.TestCase):
def test_run(self):
self.fake_session.group_sendmsg({ "command": [ "get_commands_spec" ] }, "ConfigManager")
self.fake_session.group_sendmsg({ "command": [ "get_statistics_spec" ] }, "ConfigManager")
+ self.fake_session.group_sendmsg({ "command": [ "stopping", { "module_name": "FooModule" } ] }, "ConfigManager")
self.fake_session.group_sendmsg({ "command": [ "shutdown" ] }, "ConfigManager")
+ self.assertEqual(len(self.fake_session.message_queue), 4)
self.cm.run()
- pass
+ # All commands should have been read out by run()
+ # Three of the commands should have been responded to, so the queue
+ # should now contain three answers
+ self.assertEqual(len(self.fake_session.message_queue), 3)
if __name__ == '__main__':
if not 'CONFIG_TESTDATA_PATH' in os.environ or not 'CONFIG_WR_TESTDATA_PATH' in os.environ:
print("You need to set the environment variable CONFIG_TESTDATA_PATH and CONFIG_WR_TESTDATA_PATH to point to the directory containing the test data files")
exit(1)
+ isc.log.init("unittests")
+ isc.log.resetUnitTestRootLogger()
unittest.main()
-
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 bede625..221ffa6 100644
--- a/src/lib/python/isc/config/tests/config_data_test.py
+++ b/src/lib/python/isc/config/tests/config_data_test.py
@@ -157,6 +157,7 @@ class TestConfigData(unittest.TestCase):
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 })
+ 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'))
@@ -185,6 +186,47 @@ class TestConfigData(unittest.TestCase):
spec_part = find_spec_part(config_spec, "item6/value1")
self.assertEqual({'item_name': 'value1', 'item_type': 'string', 'item_optional': True, 'item_default': 'default'}, spec_part)
+ # make sure the returned data is a copy
+ spec_part['item_default'] = 'foo'
+ self.assertNotEqual(spec_part, find_spec_part(config_spec, "item6/value1"))
+
+ def test_find_spec_part_lists(self):
+ # A few specific tests for list data
+ module_spec = isc.config.module_spec_from_file(self.data_path +
+ os.sep +
+ "spec31.spec")
+ config_spec = module_spec.get_config_spec()
+
+ expected_spec_part = {'item_name': 'number',
+ 'item_type': 'integer',
+ 'item_default': 1,
+ 'item_optional': False}
+
+ # First a check for a correct fetch
+ spec_part = find_spec_part(config_spec,
+ "/first_list_items[0]/second_list_items[1]/"
+ "map_element/list1[1]/list2[1]")
+ self.assertEqual(expected_spec_part, spec_part)
+
+ # Leaving out an index should fail by default
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ find_spec_part, config_spec,
+ "/first_list_items[0]/second_list_items/"
+ "map_element/list1[1]/list2[1]")
+
+ # But not for the last element
+ spec_part = find_spec_part(config_spec,
+ "/first_list_items[0]/second_list_items[1]/"
+ "map_element/list1[1]/list2")
+ self.assertEqual(expected_spec_part, spec_part)
+
+ # And also not if strict_identifier is false (third argument)
+ spec_part = find_spec_part(config_spec,
+ "/first_list_items[0]/second_list_items/"
+ "map_element/list1[1]/list2[1]", False)
+ self.assertEqual(expected_spec_part, spec_part)
+
+
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)
@@ -312,6 +354,16 @@ class TestMultiConfigData(unittest.TestCase):
self.mcd.remove_specification(module_spec.get_module_name())
self.assertFalse(self.mcd.have_specification(module_spec.get_module_name()))
+ def test_clear_specifications(self):
+ self.assertEqual(0, len(self.mcd._specifications))
+ module_spec = isc.config.module_spec_from_file(self.data_path +
+ os.sep +
+ "spec1.spec")
+ self.mcd.set_specification(module_spec)
+ self.assertEqual(1, len(self.mcd._specifications))
+ self.mcd.clear_specifications()
+ self.assertEqual(0, len(self.mcd._specifications))
+
def test_get_module_spec(self):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
self.mcd.set_specification(module_spec)
@@ -372,7 +424,14 @@ class TestMultiConfigData(unittest.TestCase):
self.mcd.set_value("Spec2/item1", 2)
local_changes = self.mcd.get_local_changes()
self.assertEqual({"Spec2": { "item1": 2}}, local_changes)
-
+
+ def test_set_local_changes(self):
+ module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
+ self.mcd.set_specification(module_spec)
+ self.assertEqual({}, self.mcd.get_local_changes())
+ new_local_changes = {"Spec2": { "item1": 2}}
+ self.mcd.set_local_changes(new_local_changes)
+ self.assertEqual(new_local_changes, self.mcd.get_local_changes())
def test_clear_local_changes(self):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
@@ -473,15 +532,25 @@ class TestMultiConfigData(unittest.TestCase):
self.assertEqual(MultiConfigData.DEFAULT, status)
-
def test_get_value_maps(self):
maps = self.mcd.get_value_maps()
self.assertEqual([], maps)
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
self.mcd.set_specification(module_spec)
+
+ expected = [{'default': False,
+ 'type': 'module',
+ 'name': 'Spec1',
+ 'value': None,
+ 'modified': False}]
+
maps = self.mcd.get_value_maps()
- self.assertEqual([{'default': False, 'type': 'module', 'name': 'Spec1', 'value': None, 'modified': False}], maps)
+ self.assertEqual(expected, maps)
+
+ maps = self.mcd.get_value_maps("/")
+ self.assertEqual(expected, maps)
+
maps = self.mcd.get_value_maps('Spec2')
self.assertEqual([], maps)
maps = self.mcd.get_value_maps('Spec1')
@@ -527,8 +596,10 @@ class TestMultiConfigData(unittest.TestCase):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec24.spec")
self.mcd.set_specification(module_spec)
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.mcd.get_value_maps, "/Spec24/item", 4)
+ # optional list item that is not set should return as empty list
+ maps = self.mcd.get_value_maps("/Spec24/item", 4)
+ self.assertEqual([{'default': False, 'type': 'list', 'name': 'Spec24/item', 'value': [], 'modified': False}], maps)
+
self.mcd._set_current_config({ "Spec24": { "item": [] } })
maps = self.mcd.get_value_maps("/Spec24/item")
self.assertEqual([{'default': False, 'modified': False, 'name': 'Spec24/item', 'type': 'list', 'value': []}], maps)
@@ -556,6 +627,20 @@ class TestMultiConfigData(unittest.TestCase):
maps = self.mcd.get_value_maps("/Spec22/value9")
self.assertEqual(expected, maps)
+ # A slash at the end should not produce different output
+ maps = self.mcd.get_value_maps("/Spec22/value9/")
+ self.assertEqual(expected, maps)
+
+ # A slash at the end should not produce different output with
+ # indices too
+ expected2 = [{'default': True,
+ 'type': 'integer',
+ 'name': 'Spec22/value5[1]',
+ 'value': 'b',
+ 'modified': False}]
+ maps = self.mcd.get_value_maps("/Spec22/value5[1]/")
+ self.assertEqual(expected2, maps)
+
def test_get_value_maps_named_set(self):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec32.spec")
self.mcd.set_specification(module_spec)
@@ -595,7 +680,38 @@ class TestMultiConfigData(unittest.TestCase):
self.assertEqual(MultiConfigData.LOCAL, status)
self.assertRaises(isc.cc.data.DataTypeError, self.mcd.set_value, "Spec2/item5[a]", "asdf")
-
+
+
+ def test_unset(self):
+ """
+ Test the unset command works.
+ """
+ module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
+ self.mcd.set_specification(module_spec)
+ self.mcd.set_specification(module_spec)
+ value, status = self.mcd.get_value("Spec2/item1")
+ # This is the default first
+ self.assertEqual(1, value)
+ self.assertEqual(MultiConfigData.DEFAULT, status)
+ # Unseting a default item does nothing.
+ self.mcd.unset("Spec2/item1")
+ value, status = self.mcd.get_value("Spec2/item1")
+ # This should be the default
+ self.assertEqual(1, value)
+ self.assertEqual(MultiConfigData.DEFAULT, status)
+ # Set it to something else
+ self.mcd.set_value("Spec2/item1", 42)
+ value, status = self.mcd.get_value("Spec2/item1")
+ self.assertEqual(42, value)
+ self.assertEqual(MultiConfigData.LOCAL, status)
+ # Try to unset it
+ self.mcd.unset("Spec2/item1")
+ value, status = self.mcd.get_value("Spec2/item1")
+ # This should be the default
+ self.assertEqual(1, value)
+ self.assertEqual(MultiConfigData.DEFAULT, status)
+ # Unset a nonexisting item. Should raise.
+ self.assertRaises(isc.cc.data.DataNotFoundError, self.mcd.unset, "Spec2/doesnotexist")
def test_get_config_item_list(self):
config_items = self.mcd.get_config_item_list()
@@ -617,6 +733,12 @@ class TestMultiConfigData(unittest.TestCase):
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)
+ def test_is_named_set(self):
+ module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec32.spec")
+ self.mcd.set_specification(module_spec)
+ spec_part = self.mcd.find_spec_part("Spec32/named_set_item")
+ self.assertTrue(spec_part_is_named_set(spec_part))
+
def test_get_config_item_list_named_set(self):
config_items = self.mcd.get_config_item_list()
self.assertEqual([], config_items)
@@ -635,6 +757,20 @@ class TestMultiConfigData(unittest.TestCase):
'Spec32/named_set_item/bbbb',
], config_items)
+ def test_set_named_set_nonlocal(self):
+ # Test whether a default named set is copied to local if a subitem
+ # is changed, and that other items in the set do not get lost
+ module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + 'spec32.spec')
+ self.mcd.set_specification(module_spec)
+ value, status = self.mcd.get_value('Spec32/named_set_item')
+ self.assertEqual({'a': 1, 'b': 2}, value)
+ self.assertEqual(MultiConfigData.DEFAULT, status)
+
+ self.mcd.set_value('Spec32/named_set_item/b', 3)
+ value, status = self.mcd.get_value('Spec32/named_set_item')
+ self.assertEqual({'a': 1, 'b': 3}, value)
+ self.assertEqual(MultiConfigData.LOCAL, status)
+
if __name__ == '__main__':
unittest.main()
diff --git a/src/lib/python/isc/datasrc/Makefile.am b/src/lib/python/isc/datasrc/Makefile.am
index 47f3dbc..1d862db 100644
--- a/src/lib/python/isc/datasrc/Makefile.am
+++ b/src/lib/python/isc/datasrc/Makefile.am
@@ -22,7 +22,7 @@ datasrc_la_SOURCES += journal_reader_python.cc journal_reader_python.h
datasrc_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
datasrc_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
datasrc_la_LDFLAGS = $(PYTHON_LDFLAGS)
-datasrc_la_LDFLAGS += -module
+datasrc_la_LDFLAGS += -module -avoid-version
datasrc_la_LIBADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
datasrc_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/python/libpydnspp.la
diff --git a/src/lib/python/isc/datasrc/datasrc.cc b/src/lib/python/isc/datasrc/datasrc.cc
index 1573b81..f31d10a 100644
--- a/src/lib/python/isc/datasrc/datasrc.cc
+++ b/src/lib/python/isc/datasrc/datasrc.cc
@@ -129,12 +129,6 @@ initModulePart_ZoneFinder(PyObject* mod) {
Py_BuildValue("I", ZoneFinder::CNAME));
installClassVariable(zonefinder_type, "DNAME",
Py_BuildValue("I", ZoneFinder::DNAME));
- installClassVariable(zonefinder_type, "WILDCARD",
- Py_BuildValue("I", ZoneFinder::WILDCARD));
- installClassVariable(zonefinder_type, "WILDCARD_NXRRSET",
- Py_BuildValue("I", ZoneFinder::WILDCARD_NXRRSET));
- installClassVariable(zonefinder_type, "WILDCARD_CNAME",
- Py_BuildValue("I", ZoneFinder::WILDCARD_CNAME));
installClassVariable(zonefinder_type, "FIND_DEFAULT",
Py_BuildValue("I", ZoneFinder::FIND_DEFAULT));
@@ -144,6 +138,15 @@ initModulePart_ZoneFinder(PyObject* mod) {
Py_BuildValue("I", ZoneFinder::FIND_DNSSEC));
installClassVariable(zonefinder_type, "NO_WILDCARD",
Py_BuildValue("I", ZoneFinder::NO_WILDCARD));
+
+ installClassVariable(zonefinder_type, "RESULT_WILDCARD",
+ Py_BuildValue("I", ZoneFinder::RESULT_WILDCARD));
+ installClassVariable(zonefinder_type, "RESULT_NSEC_SIGNED",
+ Py_BuildValue("I",
+ ZoneFinder::RESULT_NSEC_SIGNED));
+ installClassVariable(zonefinder_type, "RESULT_NSEC3_SIGNED",
+ Py_BuildValue("I",
+ ZoneFinder::RESULT_NSEC3_SIGNED));
} catch (const std::exception& ex) {
const std::string ex_what =
"Unexpected failure in ZoneFinder initialization: " +
@@ -230,6 +233,7 @@ initModulePart_ZoneJournalReader(PyObject* mod) {
}
PyObject* po_DataSourceError;
+PyObject* po_OutOfZone;
PyObject* po_NotImplemented;
PyModuleDef iscDataSrc = {
@@ -284,6 +288,8 @@ PyInit_datasrc(void) {
po_DataSourceError = PyErr_NewException("isc.datasrc.Error", NULL,
NULL);
PyObjectContainer(po_DataSourceError).installToModule(mod, "Error");
+ po_OutOfZone = PyErr_NewException("isc.datasrc.OutOfZone", NULL, NULL);
+ PyObjectContainer(po_OutOfZone).installToModule(mod, "OutOfZone");
po_NotImplemented = PyErr_NewException("isc.datasrc.NotImplemented",
NULL, NULL);
PyObjectContainer(po_NotImplemented).installToModule(mod,
diff --git a/src/lib/python/isc/datasrc/finder_inc.cc b/src/lib/python/isc/datasrc/finder_inc.cc
index 25910e7..7caa144 100644
--- a/src/lib/python/isc/datasrc/finder_inc.cc
+++ b/src/lib/python/isc/datasrc/finder_inc.cc
@@ -47,7 +47,7 @@ Return the RR class of the zone.\n\
// - NULL->None
// - exceptions
const char* const ZoneFinder_find_doc = "\
-find(name, type, options=FIND_DEFAULT) -> (integer, RRset)\n\
+find(name, type, options=FIND_DEFAULT) -> (integer, RRset, integer)\n\
\n\
Search the zone for a given pair of domain name and RR type.\n\
\n\
@@ -58,12 +58,10 @@ answer for the search key. Specifically,\n\
\n\
- If the search name belongs under a zone cut, it returns the code of\n\
DELEGATION and the NS RRset at the zone cut.\n\
-- If there is no matching name, it returns the code of NXDOMAIN, and,\n\
- if DNSSEC is requested, the NSEC RRset that proves the non-\n\
- existence.\n\
+- If there is no matching name, it returns the code of NXDOMAIN.\n\
- If there is a matching name but no RRset of the search type, it\n\
- returns the code of NXRRSET, and, if DNSSEC is required, the NSEC\n\
- RRset for that name.\n\
+ returns the code of NXRRSET. This case includes the search name\n\
+ matches an empty node of the zone.\n\
- If there is a CNAME RR of the searched name but there is no RR of\n\
the searched type of the name (so this type is different from\n\
CNAME), it returns the code of CNAME and that CNAME RR. Note that if\n\
@@ -72,6 +70,16 @@ answer for the search key. Specifically,\n\
- If the search name matches a delegation point of DNAME, it returns\n\
the code of DNAME and that DNAME RR.\n\
\n\
+No RRset will be returned in the NXDOMAIN and NXRRSET cases (the\n\
+second element of the tuple will be None), unless DNSSEC data are\n\
+required. See below for the cases with DNSSEC.\n\
+\n\
+The third element of the returned tuple provides supplemental\n\
+information about the search result in the form of a bitmask (called\n\
+\"flags\"). Such information may be useful for the caller if the\n\
+caller wants to collect additional DNSSEC proofs based on the search\n\
+result.\n\
+\n\
The options parameter specifies customized behavior of the search.\n\
Their semantics is as follows (they are or bit-field):\n\
\n\
@@ -91,10 +99,94 @@ Their semantics is as follows (they are or bit-field):\n\
of the non existence of any matching wildcard or non existence of an\n\
exact match when a wildcard match is found.\n\
\n\
-In general, name is expected to be included in the zone, that is, it\n\
-should be equal to or a subdomain of the zone origin. Otherwise this\n\
-method will return NXDOMAIN with an empty RRset. But such a case\n\
-should rather be considered a caller's bug.\n\
+Name is expected to be included in the zone, that is, it\n\
+should be equal to or a subdomain of the zone origin. Otherwise an\n\
+OutOfZoneFind exception is raised.\n\
+\n\
+Note: For this reason it's probably better to throw an exception than\n\
+returning NXDOMAIN. This point should be revisited in a near future\n\
+version. In any case applications shouldn't call this method for an\n\
+out-of-zone name.\n\
+\n\
+DNSSEC considerations: The result when DNSSEC data are required can be\n\
+very complicated, especially if it involves negative result or\n\
+wildcard match. Specifically, if an application calls this method for\n\
+DNS query processing with DNSSEC data, and if the search result code\n\
+is either NXDOMAIN or NXRRRSET, and/or RESULT_WILDCARD\n\
+flag is set in the returned flags value,\n\
+then the application will need to find additional NSEC or NSEC3\n\
+records for supplemental proofs. This method helps the application for\n\
+such post search processing.\n\
+\n\
+First, it tells the application whether the zone is signed with NSEC\n\
+or NSEC3 via the RESULT_NSEC_SIGNED and RESULT_NSEC3_SIGNED flags\n\
+in the returned flags value. Any sanely signed zone\n\
+should be signed with either (and only one) of these two types of RRs;\n\
+however, the application should expect that the zone could be broken\n\
+and these methods could both return false. But this method should\n\
+ensure that not both of these methods return true.\n\
+\n\
+In case it's signed with NSEC3, there is no further information\n\
+returned from this method.\n\
+\n\
+In case it's signed with NSEC, this method will possibly return a\n\
+related NSEC RRset in the second element of the tuple. What kind of\n\
+NSEC is returned depends on the result code (NXDOMAIN or NXRRSET) and\n\
+on whether it's a wildcard match:\n\
+\n\
+- In case of NXDOMAIN, the returned NSEC covers the queried domain\n\
+ that proves that the query name does not exist in the zone. Note\n\
+ that this does not necessarily prove it doesn't even match a\n\
+ wildcard (even if the result of NXDOMAIN can only happen when\n\
+ there's no matching wildcard either). It is caller's responsibility\n\
+ to provide a proof that there is no matching wildcard if that proof\n\
+ is necessary.\n\
+- In case of NXRRSET, we need to consider the following cases\n\
+ referring to Section 3.1.3 of RFC4035:\n\
+\n\
+1. (Normal) no data: there is a matching non-wildcard name with a\n\
+ different RR type. This is the \"No Data\" case of the RFC.\n\
+2. (Normal) empty non terminal: there is no matching (exact or\n\
+ wildcard) name, but there is a subdomain with an RR of the query\n\
+ name. This is one case of \"Name Error\" of the RFC.\n\
+3. Wildcard empty non terminal: similar to 2, but the empty name is\n\
+ a wildcard, and matches the query name by wildcard expansion. This\n\
+ is a special case of \"Name Error\" of the RFC.\n\
+4. Wildcard no data: there is no exact match name, but there is a\n\
+ wildcard name that matches the query name with a different type of RR.\n\
+ This is the \"Wildcard No Data\" case of the RFC.\n\
+\n\
+In case 1, find() returns NSEC of the matching name.\n\
+\n\
+In case 2, find() will return NSEC for the interval where the empty\n\
+nonterminal lives. The end of the interval is the subdomain causing\n\
+existence of the empty nonterminal (if there's sub.x.example.com, and\n\
+no record in x.example.com, then x.example.com exists implicitly - is\n\
+the empty nonterminal and sub.x.example.com is the subdomain causing\n\
+it). Note that this NSEC proves not only the existence of empty non\n\
+terminal name but also the non existence of possibly matching wildcard\n\
+name, because there can be no better wildcard match than the exact\n\
+matching empty name.\n\
+\n\
+In case 3, find() will return NSEC for the interval where the wildcard\n\
+empty nonterminal lives. Cases 2 and 3 are especially complicated and\n\
+confusing. See the examples below.\n\
+\n\
+In case 4, find() will return NSEC of the matching wildcard name.\n\
+\n\
+Examples: if zone \"example.com\" has the following record:\n\
+\n\
+a.example.com. NSEC a.b.example.com.\n\
+\n\
+a call to find() for \"b.example.com.\" with the FIND_DNSSEC option\n\
+will result in NXRRSET, and this NSEC will be returned.\n\
+Likewise, if zone \"example.org\" has the following record,\n\
+\n\
+a.example.org. NSEC x.*.b.example.org.\n\
+\n\
+a call to find() for \"y.b.example.org\" with FIND_DNSSEC will\n\
+result in NXRRSET and this NSEC; RESULT_WILDCARD bit is set in the\n\
+returned flags.\n\
\n\
This method raises an isc.datasrc.Error exception if there is an\n\
internal error in the datasource.\n\
@@ -104,28 +196,34 @@ Parameters:\n\
type The RR type to be searched for.\n\
options The search options.\n\
\n\
-Return Value(s): A tuple of a result code (integer) and an RRset object\n\
-enclosing the search result (see above).\n\
+Return Value(s): A tuple of a result code (integer), an RRset object\n\
+and flags bitmask (integer).\n\
";
-const char* const ZoneFinder_find_all_doc = "\
-find_all(isc.dns.Name, options=FIND_DEFAULT) -> (integer, RRset) | (integer, [RRset])\
+const char* const ZoneFinder_findAll_doc = "\
+find_all(isc.dns.Name, options=FIND_DEFAULT) ->\n\
+ (integer, RRset, integer) | (integer, [RRset], integer)\
\n\
-This acts mostly the same as the find method. The main difference is,\n\
-when the lookup is successful (eg. the first part of the result is either\n\
-SUCCESS or WILDCARD), the second part is list of all RRsets in the given name\n\
-instead of a single RRset as in case of find.\n\
+Finds all RRsets in the given name.\n\
\n\
-This method raises an isc.datasrc.Error exception if there is an\n\
-internal error in the datasource.\n\
+This function works almost exactly in the same way as the find one.\n\
+The only difference is, when the lookup is successful (eg. the code is\n\
+SUCCESS), all the RRsets residing in the named node are returned in the\n\
+second element of the returned tuple. All\n\
+the other (unsuccessful) cases are handled the same, including\n\
+returning delegations, NSEC/NSEC3 availability and NSEC proofs,\n\
+wildcard information etc. The options parameter works the same way and\n\
+it should conform to the same exception restrictions.\n\
\n\
Parameters:\n\
name The domain name to be searched for.\n\
options The search options.\n\
\n\
-Return Value(s): A tuple of a result code (integer) and an either RRset object,\n\
-for cases where the result is some kind of delegation, CNAME or similar, or list\n\
-of RRset objects, containing all the results.\n\
+Return Value(s): A tuple of a result code (integer), an either\n\
+RRset object or a list of RRsets, and flags (integer).\n\
+In the second element a single RRset is returned for cases where the\n\
+result is some kind of delegation, CNAME or similar; in other cases\n\
+a list of RRsets is returned, containing all the results.\n\
";
const char* const ZoneFinder_find_previous_name_doc = "\
diff --git a/src/lib/python/isc/datasrc/finder_python.cc b/src/lib/python/isc/datasrc/finder_python.cc
index 5df944f..ed05fdb 100644
--- a/src/lib/python/isc/datasrc/finder_python.cc
+++ b/src/lib/python/isc/datasrc/finder_python.cc
@@ -45,6 +45,23 @@ using namespace isc::dns::python;
using namespace isc::datasrc;
using namespace isc::datasrc::python;
+namespace {
+ZoneFinder::FindResultFlags
+getFindResultFlags(const ZoneFinder::Context& context) {
+ ZoneFinder::FindResultFlags result_flags = ZoneFinder::RESULT_DEFAULT;
+ if (context.isWildcard()) {
+ result_flags = result_flags | ZoneFinder::RESULT_WILDCARD;
+ }
+ if (context.isNSECSigned()) {
+ result_flags = result_flags | ZoneFinder::RESULT_NSEC_SIGNED;
+ }
+ if (context.isNSEC3Signed()) {
+ result_flags = result_flags | ZoneFinder::RESULT_NSEC3_SIGNED;
+ }
+ return (result_flags);
+}
+}
+
namespace isc_datasrc_internal {
// This is the shared code for the find() call in the finder and the updater
// Is is intentionally not available through any header, nor at our standard
@@ -66,17 +83,23 @@ PyObject* ZoneFinder_helper(ZoneFinder* finder, PyObject* args) {
try {
ZoneFinder::FindOptions options =
static_cast<ZoneFinder::FindOptions>(options_int);
- const ZoneFinder::FindResult find_result(
+ ConstZoneFinderContextPtr find_ctx(
finder->find(PyName_ToName(name), PyRRType_ToRRType(rrtype),
options));
- const ZoneFinder::Result r = find_result.code;
- isc::dns::ConstRRsetPtr rrsp = find_result.rrset;
+ const ZoneFinder::Result r = find_ctx->code;
+ isc::dns::ConstRRsetPtr rrsp = find_ctx->rrset;
+ ZoneFinder::FindResultFlags result_flags =
+ getFindResultFlags(*find_ctx);
if (rrsp) {
// Use N instead of O so the refcount isn't increased twice
- return (Py_BuildValue("IN", r, createRRsetObject(*rrsp)));
+ return (Py_BuildValue("INI", r, createRRsetObject(*rrsp),
+ result_flags));
} else {
- return (Py_BuildValue("IO", r, Py_None));
+ return (Py_BuildValue("IOI", r, Py_None, result_flags));
}
+ } catch (const OutOfZone& ooz) {
+ PyErr_SetString(getDataSourceException("OutOfZone"), ooz.what());
+ return (NULL);
} catch (const DataSourceError& dse) {
PyErr_SetString(getDataSourceException("Error"), dse.what());
return (NULL);
@@ -107,11 +130,13 @@ PyObject* ZoneFinder_helper_all(ZoneFinder* finder, PyObject* args) {
ZoneFinder::FindOptions options =
static_cast<ZoneFinder::FindOptions>(options_int);
std::vector<isc::dns::ConstRRsetPtr> target;
- const ZoneFinder::FindResult find_result(
+ ConstZoneFinderContextPtr find_ctx(
finder->findAll(PyName_ToName(name), target, options));
- const ZoneFinder::Result r = find_result.code;
- isc::dns::ConstRRsetPtr rrsp = find_result.rrset;
- if (r == ZoneFinder::SUCCESS || r == ZoneFinder::WILDCARD) {
+ const ZoneFinder::Result r = find_ctx->code;
+ isc::dns::ConstRRsetPtr rrsp = find_ctx->rrset;
+ ZoneFinder::FindResultFlags result_flags =
+ getFindResultFlags(*find_ctx);
+ if (r == ZoneFinder::SUCCESS) {
// Copy all the RRsets to the result list
PyObjectContainer list_container(PyList_New(target.size()));
for (size_t i(0); i < target.size(); ++i) {
@@ -126,9 +151,10 @@ PyObject* ZoneFinder_helper_all(ZoneFinder* finder, PyObject* args) {
} else {
if (rrsp) {
// Use N instead of O so the refcount isn't increased twice
- return (Py_BuildValue("IN", r, createRRsetObject(*rrsp)));
+ return (Py_BuildValue("INI", r, createRRsetObject(*rrsp),
+ result_flags));
} else {
- return (Py_BuildValue("IO", r, Py_None));
+ return (Py_BuildValue("IOI", r, Py_None, result_flags));
}
}
} catch (const DataSourceError& dse) {
@@ -263,7 +289,7 @@ PyMethodDef ZoneFinder_methods[] = {
ZoneFinder_getOrigin_doc },
{ "get_class", ZoneFinder_getClass, METH_NOARGS, ZoneFinder_getClass_doc },
{ "find", ZoneFinder_find, METH_VARARGS, ZoneFinder_find_doc },
- { "find_all", ZoneFinder_find_all, METH_VARARGS, ZoneFinder_find_all_doc },
+ { "find_all", ZoneFinder_find_all, METH_VARARGS, ZoneFinder_findAll_doc },
{ "find_previous_name", ZoneFinder_findPreviousName, METH_VARARGS,
ZoneFinder_find_previous_name_doc },
{ NULL, NULL, 0, NULL }
diff --git a/src/lib/python/isc/datasrc/sqlite3_ds.py b/src/lib/python/isc/datasrc/sqlite3_ds.py
index daa12fc..f9b47c0 100644
--- a/src/lib/python/isc/datasrc/sqlite3_ds.py
+++ b/src/lib/python/isc/datasrc/sqlite3_ds.py
@@ -23,6 +23,10 @@ RR_NAME_INDEX = 2
RR_TTL_INDEX = 4
RR_RDATA_INDEX = 7
+# Current major and minor versions of schema
+SCHEMA_MAJOR_VERSION = 2
+SCHEMA_MINOR_VERSION = 0
+
class Sqlite3DSError(Exception):
""" Define exceptions."""
pass
@@ -47,40 +51,46 @@ def create(cur):
cur.execute("SELECT version FROM schema_version")
row = cur.fetchone()
except sqlite3.OperationalError:
- cur.execute("CREATE TABLE schema_version (version INTEGER NOT NULL)")
- cur.execute("INSERT INTO schema_version VALUES (1)")
+ cur.execute("""CREATE TABLE schema_version (version INTEGER NOT NULL,
+ minor INTEGER NOT NULL DEFAULT 0)""")
+ cur.execute("INSERT INTO schema_version VALUES (" +
+ str(SCHEMA_MAJOR_VERSION) + ", " +
+ str(SCHEMA_MINOR_VERSION) + ")")
cur.execute("""CREATE TABLE zones (id INTEGER PRIMARY KEY,
- name STRING NOT NULL COLLATE NOCASE,
- rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN',
+ name TEXT NOT NULL COLLATE NOCASE,
+ rdclass TEXT 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,
zone_id INTEGER NOT NULL,
- name STRING NOT NULL COLLATE NOCASE,
- rname STRING NOT NULL COLLATE NOCASE,
+ name TEXT NOT NULL COLLATE NOCASE,
+ rname TEXT NOT NULL COLLATE NOCASE,
ttl INTEGER NOT NULL,
- rdtype STRING NOT NULL COLLATE NOCASE,
- sigtype STRING COLLATE NOCASE,
- rdata STRING NOT NULL)""")
+ rdtype TEXT NOT NULL COLLATE NOCASE,
+ sigtype TEXT COLLATE NOCASE,
+ rdata TEXT NOT NULL)""")
cur.execute("CREATE INDEX records_byname ON records (name)")
cur.execute("CREATE INDEX records_byrname ON records (rname)")
+ cur.execute("""CREATE INDEX records_bytype_and_rname ON records
+ (rdtype, rname)""")
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,
+ hash TEXT NOT NULL COLLATE NOCASE,
+ owner TEXT NOT NULL COLLATE NOCASE,
ttl INTEGER NOT NULL,
- rdtype STRING NOT NULL COLLATE NOCASE,
- rdata STRING NOT NULL)""")
+ rdtype TEXT NOT NULL COLLATE NOCASE,
+ rdata TEXT NOT NULL)""")
cur.execute("CREATE INDEX nsec3_byhash ON nsec3 (hash)")
cur.execute("""CREATE TABLE diffs (id INTEGER PRIMARY KEY,
zone_id INTEGER NOT NULL,
version INTEGER NOT NULL,
operation INTEGER NOT NULL,
- name STRING NOT NULL COLLATE NOCASE,
- rrtype STRING NOT NULL COLLATE NOCASE,
+ name TEXT NOT NULL COLLATE NOCASE,
+ rrtype TEXT NOT NULL COLLATE NOCASE,
ttl INTEGER NOT NULL,
- rdata STRING NOT NULL)""")
- row = [1]
+ rdata TEXT NOT NULL)""")
+ cur.execute("SELECT version FROM schema_version")
+ row = cur.fetchone()
cur.execute("COMMIT TRANSACTION")
return row
@@ -115,8 +125,9 @@ def open(dbfile, connect_timeout=5.0):
row = create(cur)
conn.isolation_level = iso_lvl
- if row == None or row[0] != 1:
- raise Sqlite3DSError("Bad database schema version")
+ if row == None or row[0] != SCHEMA_MAJOR_VERSION:
+ bad_version = "(unknown)" if row is None else str(row[0])
+ raise Sqlite3DSError("Bad database schema version: " + bad_version)
return conn, cur
diff --git a/src/lib/python/isc/datasrc/tests/.gitignore b/src/lib/python/isc/datasrc/tests/.gitignore
new file mode 100644
index 0000000..58ea8cd
--- /dev/null
+++ b/src/lib/python/isc/datasrc/tests/.gitignore
@@ -0,0 +1 @@
+/*.sqlite3.copied
diff --git a/src/lib/python/isc/datasrc/tests/Makefile.am b/src/lib/python/isc/datasrc/tests/Makefile.am
index ab89b93..c996f2a 100644
--- a/src/lib/python/isc/datasrc/tests/Makefile.am
+++ b/src/lib/python/isc/datasrc/tests/Makefile.am
@@ -1,12 +1,14 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
# old tests, TODO remove or change to use new API?
-#PYTESTS = master_test.py sqlite3_ds_test.py
-PYTESTS = datasrc_test.py
+#PYTESTS = master_test.py
+PYTESTS = datasrc_test.py sqlite3_ds_test.py
EXTRA_DIST = $(PYTESTS)
EXTRA_DIST += testdata/brokendb.sqlite3
EXTRA_DIST += testdata/example.com.sqlite3
-EXTRA_DIST += testdata/test.sqlite3.nodiffs
+EXTRA_DIST += testdata/newschema.sqlite3
+EXTRA_DIST += testdata/oldschema.sqlite3
+EXTRA_DIST += testdata/new_minor_schema.sqlite3
CLEANFILES = $(abs_builddir)/rwtest.sqlite3.copied
# If necessary (rare cases), explicitly specify paths to dynamic libraries
diff --git a/src/lib/python/isc/datasrc/tests/datasrc_test.py b/src/lib/python/isc/datasrc/tests/datasrc_test.py
index 8b2a8c5..c7bf6b4 100644
--- a/src/lib/python/isc/datasrc/tests/datasrc_test.py
+++ b/src/lib/python/isc/datasrc/tests/datasrc_test.py
@@ -63,8 +63,8 @@ def test_findall_common(self, tested):
object.
"""
# Some "failure" responses
- result, rrset = tested.find_all(isc.dns.Name("www.sql1.example.com"),
- ZoneFinder.FIND_DEFAULT)
+ result, rrset, _ = tested.find_all(isc.dns.Name("www.sql1.example.com"),
+ ZoneFinder.FIND_DEFAULT)
self.assertEqual(ZoneFinder.DELEGATION, result)
expected = RRset(Name('sql1.example.com.'), RRClass.IN(), RRType.NS(),
RRTTL(3600))
@@ -76,8 +76,8 @@ def test_findall_common(self, tested):
'dns03.example.com.'))
self.assertTrue(rrsets_equal(expected, rrset))
- result, rrset = tested.find_all(isc.dns.Name("nxdomain.example.com"),
- ZoneFinder.FIND_DEFAULT)
+ result, rrset, _ = tested.find_all(isc.dns.Name("nxdomain.example.com"),
+ ZoneFinder.FIND_DEFAULT)
self.assertEqual(ZoneFinder.NXDOMAIN, result)
self.assertIsNone(None, rrset)
@@ -262,16 +262,16 @@ class DataSrcClient(unittest.TestCase):
rrets = dsc.get_iterator(isc.dns.Name("example.com"))
# there are more than 80 RRs in this zone... let's just count them
# (already did a full check of the smaller zone above)
- self.assertEqual(55, len(list(rrets)))
+ # There are 40 non-RRSIG RRsets and 32 dinstinct RRSIGs.
+ self.assertEqual(72, len(list(rrets)))
# same test, but now with explicit False argument for separate_rrs
dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
rrets = dsc.get_iterator(isc.dns.Name("example.com"), False)
# there are more than 80 RRs in this zone... let's just count them
# (already did a full check of the smaller zone above)
- self.assertEqual(55, len(list(rrets)))
+ self.assertEqual(72, len(list(rrets)))
- # Count should be 71 if we request individual rrsets for differing ttls
dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
rrets = dsc.get_iterator(isc.dns.Name("example.com"), True)
# there are more than 80 RRs in this zone... let's just count them
@@ -319,10 +319,15 @@ class DataSrcClient(unittest.TestCase):
self.assertNotEqual(ZoneFinder.NXDOMAIN, ZoneFinder.NXRRSET)
self.assertNotEqual(ZoneFinder.NXRRSET, ZoneFinder.CNAME)
self.assertNotEqual(ZoneFinder.CNAME, ZoneFinder.DNAME)
- self.assertNotEqual(ZoneFinder.DNAME, ZoneFinder.WILDCARD)
- self.assertNotEqual(ZoneFinder.WILDCARD, ZoneFinder.WILDCARD_CNAME)
- self.assertNotEqual(ZoneFinder.WILDCARD_CNAME,
- ZoneFinder.WILDCARD_NXRRSET)
+
+ def test_findresultflags(self):
+ '''A simple test just confirming the flags are all different.'''
+ self.assertNotEqual(ZoneFinder.RESULT_WILDCARD,
+ ZoneFinder.RESULT_NSEC_SIGNED)
+ self.assertNotEqual(ZoneFinder.RESULT_NSEC_SIGNED,
+ ZoneFinder.RESULT_NSEC3_SIGNED)
+ self.assertNotEqual(ZoneFinder.RESULT_NSEC3_SIGNED,
+ ZoneFinder.RESULT_WILDCARD)
def test_findall(self):
"""
@@ -345,66 +350,67 @@ class DataSrcClient(unittest.TestCase):
self.assertEqual(isc.dns.RRClass.IN(), finder.get_class())
self.assertEqual("example.com.", finder.get_origin().to_text())
- result, rrset = finder.find(isc.dns.Name("www.example.com"),
- isc.dns.RRType.A(),
- finder.FIND_DEFAULT)
+ result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRType.A(),
+ finder.FIND_DEFAULT)
self.assertEqual(finder.SUCCESS, result)
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
rrset.to_text())
# Check the optional parameters are optional
- result, rrset = finder.find(isc.dns.Name("www.example.com"),
- isc.dns.RRType.A())
+ result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRType.A())
self.assertEqual(finder.SUCCESS, result)
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
rrset.to_text())
- result, rrset = finder.find(isc.dns.Name("www.sql1.example.com"),
- isc.dns.RRType.A(),
- finder.FIND_DEFAULT)
+ result, rrset, _ = finder.find(isc.dns.Name("www.sql1.example.com"),
+ isc.dns.RRType.A(),
+ finder.FIND_DEFAULT)
self.assertEqual(finder.DELEGATION, result)
self.assertEqual("sql1.example.com. 3600 IN NS dns01.example.com.\n" +
"sql1.example.com. 3600 IN NS dns02.example.com.\n" +
"sql1.example.com. 3600 IN NS dns03.example.com.\n",
rrset.to_text())
- result, rrset = finder.find(isc.dns.Name("doesnotexist.example.com"),
- isc.dns.RRType.A(),
- finder.FIND_DEFAULT)
+ result, rrset, _ = finder.find(isc.dns.Name("doesnotexist.example.com"),
+ isc.dns.RRType.A(),
+ finder.FIND_DEFAULT)
self.assertEqual(finder.NXDOMAIN, result)
self.assertEqual(None, rrset)
- result, rrset = finder.find(isc.dns.Name("www.some.other.domain"),
- isc.dns.RRType.A(),
- finder.FIND_DEFAULT)
- self.assertEqual(finder.NXDOMAIN, result)
- self.assertEqual(None, rrset)
- result, rrset = finder.find(isc.dns.Name("www.example.com"),
- isc.dns.RRType.TXT(),
- finder.FIND_DEFAULT)
+ self.assertRaises(isc.datasrc.OutOfZone, finder.find,
+ isc.dns.Name("www.some.other.domain"),
+ isc.dns.RRType.A())
+
+ result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRType.TXT(),
+ finder.FIND_DEFAULT)
self.assertEqual(finder.NXRRSET, result)
self.assertEqual(None, rrset)
- result, rrset = finder.find(isc.dns.Name("cname-ext.example.com"),
- isc.dns.RRType.A(),
- finder.FIND_DEFAULT)
+ result, rrset, _ = finder.find(isc.dns.Name("cname-ext.example.com"),
+ isc.dns.RRType.A(),
+ finder.FIND_DEFAULT)
self.assertEqual(finder.CNAME, result)
self.assertEqual(
"cname-ext.example.com. 3600 IN CNAME www.sql1.example.com.\n",
rrset.to_text())
- result, rrset = finder.find(isc.dns.Name("foo.wild.example.com"),
- isc.dns.RRType.A(),
- finder.FIND_DEFAULT)
- self.assertEqual(finder.WILDCARD, result)
+ result, rrset, flags = \
+ finder.find(isc.dns.Name("foo.wild.example.com"),
+ isc.dns.RRType.A(), finder.FIND_DEFAULT)
+ self.assertEqual(finder.SUCCESS, result)
+ self.assertEqual(finder.RESULT_WILDCARD, flags)
self.assertEqual("foo.wild.example.com. 3600 IN A 192.0.2.255\n",
rrset.to_text())
- result, rrset = finder.find(isc.dns.Name("foo.wild.example.com"),
- isc.dns.RRType.TXT(),
- finder.FIND_DEFAULT)
- self.assertEqual(finder.WILDCARD_NXRRSET, result)
+ result, rrset, _ = finder.find(isc.dns.Name("foo.wild.example.com"),
+ isc.dns.RRType.TXT(),
+ finder.FIND_DEFAULT)
+ self.assertEqual(finder.NXRRSET, result)
+ self.assertTrue(finder.RESULT_WILDCARD, flags)
self.assertEqual(None, rrset)
self.assertRaises(TypeError, finder.find,
@@ -463,16 +469,16 @@ class DataSrcUpdater(unittest.TestCase):
# Check basic behavior of updater's finder
dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
updater = dsc.get_updater(isc.dns.Name("example.com"), False)
- result, rrset = updater.find(isc.dns.Name("www.example.com"),
- isc.dns.RRType.A(),
- ZoneFinder.FIND_DEFAULT)
+ result, rrset, _ = updater.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRType.A(),
+ ZoneFinder.FIND_DEFAULT)
self.assertEqual(ZoneFinder.SUCCESS, result)
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
rrset.to_text())
# Omit optional parameters
- result, rrset = updater.find(isc.dns.Name("www.example.com"),
- isc.dns.RRType.A())
+ result, rrset, _ = updater.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRType.A())
self.assertEqual(ZoneFinder.SUCCESS, result)
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
rrset.to_text())
@@ -487,9 +493,9 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual(isc.dns.RRClass.IN(), finder.get_class())
self.assertEqual("example.com.", finder.get_origin().to_text())
- result, rrset = finder.find(isc.dns.Name("www.example.com"),
- isc.dns.RRType.A(),
- finder.FIND_DEFAULT)
+ result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRType.A(),
+ finder.FIND_DEFAULT)
self.assertEqual(finder.SUCCESS, result)
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
rrset.to_text())
@@ -508,15 +514,15 @@ class DataSrcUpdater(unittest.TestCase):
# The record should be gone in the updater, but not in the original
# finder (since we have not committed)
- result, rrset = updater.find(isc.dns.Name("www.example.com"),
- isc.dns.RRType.A(),
- finder.FIND_DEFAULT)
+ result, rrset, _ = updater.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRType.A(),
+ finder.FIND_DEFAULT)
self.assertEqual(finder.NXDOMAIN, result)
self.assertEqual(None, rrset)
- result, rrset = finder.find(isc.dns.Name("www.example.com"),
- isc.dns.RRType.A(),
- finder.FIND_DEFAULT)
+ result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRType.A(),
+ finder.FIND_DEFAULT)
self.assertEqual(finder.SUCCESS, result)
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
rrset.to_text())
@@ -526,9 +532,9 @@ class DataSrcUpdater(unittest.TestCase):
self.assertRaises(isc.datasrc.Error, updater.commit)
# the record should be gone now in the 'real' finder as well
- result, rrset = finder.find(isc.dns.Name("www.example.com"),
- isc.dns.RRType.A(),
- finder.FIND_DEFAULT)
+ result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRType.A(),
+ finder.FIND_DEFAULT)
self.assertEqual(finder.NXDOMAIN, result)
self.assertEqual(None, rrset)
@@ -540,9 +546,9 @@ class DataSrcUpdater(unittest.TestCase):
# second commit should throw
self.assertRaises(isc.datasrc.Error, updater.commit)
- result, rrset = finder.find(isc.dns.Name("www.example.com"),
- isc.dns.RRType.A(),
- finder.FIND_DEFAULT)
+ result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRType.A(),
+ finder.FIND_DEFAULT)
self.assertEqual(finder.SUCCESS, result)
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
rrset.to_text())
@@ -564,9 +570,9 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual(finder.SUCCESS, result)
self.assertEqual(isc.dns.RRClass.IN(), finder.get_class())
self.assertEqual("example.com.", finder.get_origin().to_text())
- result, rrset = finder.find(isc.dns.Name("www.example.com"),
- isc.dns.RRType.A(),
- finder.FIND_DEFAULT)
+ result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRType.A(),
+ finder.FIND_DEFAULT)
self.assertEqual(finder.SUCCESS, result)
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
rrset.to_text())
@@ -587,9 +593,9 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual(isc.dns.RRClass.IN(), finder.get_class())
self.assertEqual("example.com.", finder.get_origin().to_text())
- result, rrset = finder.find(isc.dns.Name("www.example.com"),
- isc.dns.RRType.A(),
- finder.FIND_DEFAULT)
+ result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRType.A(),
+ finder.FIND_DEFAULT)
self.assertEqual(finder.SUCCESS, result)
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
rrset.to_text())
@@ -608,9 +614,9 @@ class DataSrcUpdater(unittest.TestCase):
# The record should be gone in the updater, but not in the original
# finder (since we have not committed)
- result, rrset = updater.find(isc.dns.Name("www.example.com"),
- isc.dns.RRType.A(),
- finder.FIND_DEFAULT)
+ result, rrset, _ = updater.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRType.A(),
+ finder.FIND_DEFAULT)
self.assertEqual(finder.NXDOMAIN, result)
self.assertEqual(None, rrset)
@@ -618,9 +624,9 @@ class DataSrcUpdater(unittest.TestCase):
updater = None
# the record should still be available in the 'real' finder as well
- result, rrset = finder.find(isc.dns.Name("www.example.com"),
- isc.dns.RRType.A(),
- finder.FIND_DEFAULT)
+ result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRType.A(),
+ finder.FIND_DEFAULT)
self.assertEqual(finder.SUCCESS, result)
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
rrset.to_text())
@@ -874,15 +880,6 @@ class JournalRead(unittest.TestCase):
# ZoneJournalReader can only be constructed via a factory
self.assertRaises(TypeError, ZoneJournalReader)
- def test_journal_reader_old_schema(self):
- # The database doesn't have a "diffs" table.
- dbfile = TESTDATA_PATH + 'test.sqlite3.nodiffs'
- client = isc.datasrc.DataSourceClient("sqlite3",
- "{ \"database_file\": \"" + \
- dbfile + "\" }")
- self.assertRaises(isc.datasrc.Error, client.get_journal_reader,
- self.zname, 0, 1)
-
if __name__ == "__main__":
isc.log.init("bind10")
isc.log.resetUnitTestRootLogger()
diff --git a/src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py b/src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py
index 10c61cf..5604c32 100644
--- a/src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py
+++ b/src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py
@@ -22,122 +22,18 @@ import sqlite3
TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
TESTDATA_WRITE_PATH = os.environ['TESTDATA_WRITE_PATH'] + os.sep
-READ_ZONE_DB_FILE = TESTDATA_PATH + "example.com.sqlite3"
-BROKEN_DB_FILE = TESTDATA_PATH + "brokendb.sqlite3"
-WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "example.com.out.sqlite3"
-NEW_DB_FILE = TESTDATA_WRITE_PATH + "new_db.sqlite3"
-
-def example_reader():
- my_zone = [
- ("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")
- ]
- for rr in my_zone:
- yield rr
-
-def example_reader_nested():
- # this iterator is used in the 'locked' test; it will cause
- # the load() method to try and write to the same database
- sqlite3_ds.load(WRITE_ZONE_DB_FILE,
- ".",
- example_reader)
- return example_reader()
-
-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",
- BROKEN_DB_FILE)
- self.assertTrue(sqlite3_ds.zone_exist("example.com.",
- READ_ZONE_DB_FILE))
- self.assertFalse(sqlite3_ds.zone_exist("example.org.",
- READ_ZONE_DB_FILE))
-
- def test_load_db(self):
- sqlite3_ds.load(WRITE_ZONE_DB_FILE, ".", example_reader)
-
- def test_locked_db(self):
- # load it first to make sure it exists
- sqlite3_ds.load(WRITE_ZONE_DB_FILE, ".", example_reader)
-
- # and manually create a writing session as well
- con = sqlite3.connect(WRITE_ZONE_DB_FILE);
- cur = con.cursor()
- cur.execute("delete from records")
-
- self.assertRaises(sqlite3_ds.Sqlite3DSError,
- sqlite3_ds.load, WRITE_ZONE_DB_FILE, ".",
- example_reader)
-
- con.rollback()
-
- # and make sure lock does not stay
- sqlite3_ds.load(WRITE_ZONE_DB_FILE, ".", example_reader)
-
- # force locked db by nested loads
- self.assertRaises(sqlite3_ds.Sqlite3DSError,
- sqlite3_ds.load, WRITE_ZONE_DB_FILE, ".",
- example_reader_nested)
-
- # and make sure lock does not stay
- sqlite3_ds.load(WRITE_ZONE_DB_FILE, ".", example_reader)
+DBFILE_NEWSCHEMA = TESTDATA_PATH + "/newschema.sqlite3";
+DBFILE_OLDSCHEMA = TESTDATA_PATH + "/oldschema.sqlite3";
+DBFILE_NEW_MINOR_SCHEMA = TESTDATA_PATH + "/new_minor_schema.sqlite3";
class NewDBFile(unittest.TestCase):
- def tearDown(self):
- # remove the created database after every test
- if (os.path.exists(NEW_DB_FILE)):
- os.remove(NEW_DB_FILE)
-
- def setUp(self):
- # remove the created database before every test too, just
- # in case a test got aborted half-way, and cleanup didn't occur
- if (os.path.exists(NEW_DB_FILE)):
- os.remove(NEW_DB_FILE)
-
- def test_new_db(self):
- self.assertFalse(os.path.exists(NEW_DB_FILE))
- sqlite3_ds.open(NEW_DB_FILE)
- self.assertTrue(os.path.exists(NEW_DB_FILE))
-
- def test_new_db_locked(self):
- self.assertFalse(os.path.exists(NEW_DB_FILE))
- con = sqlite3.connect(NEW_DB_FILE);
- con.isolation_level = None
- cur = con.cursor()
- cur.execute("BEGIN IMMEDIATE TRANSACTION")
-
- # load should now fail, since the database is locked,
- # and the open() call needs an exclusive lock
- self.assertRaises(sqlite3.OperationalError,
- sqlite3_ds.open, NEW_DB_FILE, 0.1)
-
- con.rollback()
- cur.close()
- con.close()
- self.assertTrue(os.path.exists(NEW_DB_FILE))
-
- # now that we closed our connection, load should work again
- sqlite3_ds.open(NEW_DB_FILE)
-
- # the database should now have been created, and a new load should
- # not require an exclusive lock anymore, so we lock it again
- con = sqlite3.connect(NEW_DB_FILE);
- cur = con.cursor()
- cur.execute("BEGIN IMMEDIATE TRANSACTION")
- sqlite3_ds.open(NEW_DB_FILE, 0.1)
- con.rollback()
- cur.close()
- con.close()
+ def test_different_version(self):
+ self.assertTrue(os.path.exists(DBFILE_NEWSCHEMA))
+ self.assertRaises(sqlite3_ds.Sqlite3DSError, sqlite3_ds.open,
+ DBFILE_NEWSCHEMA)
+ self.assertRaises(sqlite3_ds.Sqlite3DSError, sqlite3_ds.open,
+ DBFILE_OLDSCHEMA)
+ self.assertNotEqual(None, sqlite3_ds.open(DBFILE_NEW_MINOR_SCHEMA)[0])
if __name__ == '__main__':
unittest.main()
diff --git a/src/lib/python/isc/datasrc/tests/testdata/example.com.sqlite3 b/src/lib/python/isc/datasrc/tests/testdata/example.com.sqlite3
index 521cf31..9c71cb5 100644
Binary files a/src/lib/python/isc/datasrc/tests/testdata/example.com.sqlite3 and b/src/lib/python/isc/datasrc/tests/testdata/example.com.sqlite3 differ
diff --git a/src/lib/python/isc/datasrc/tests/testdata/new_minor_schema.sqlite3 b/src/lib/python/isc/datasrc/tests/testdata/new_minor_schema.sqlite3
new file mode 100644
index 0000000..1542c20
Binary files /dev/null and b/src/lib/python/isc/datasrc/tests/testdata/new_minor_schema.sqlite3 differ
diff --git a/src/lib/python/isc/datasrc/tests/testdata/newschema.sqlite3 b/src/lib/python/isc/datasrc/tests/testdata/newschema.sqlite3
new file mode 100644
index 0000000..460cfa8
Binary files /dev/null and b/src/lib/python/isc/datasrc/tests/testdata/newschema.sqlite3 differ
diff --git a/src/lib/python/isc/datasrc/tests/testdata/oldschema.sqlite3 b/src/lib/python/isc/datasrc/tests/testdata/oldschema.sqlite3
new file mode 100644
index 0000000..b44c5eb
Binary files /dev/null and b/src/lib/python/isc/datasrc/tests/testdata/oldschema.sqlite3 differ
diff --git a/src/lib/python/isc/datasrc/tests/testdata/test.sqlite3.nodiffs b/src/lib/python/isc/datasrc/tests/testdata/test.sqlite3.nodiffs
deleted file mode 100644
index cc8cfc3..0000000
Binary files a/src/lib/python/isc/datasrc/tests/testdata/test.sqlite3.nodiffs and /dev/null differ
diff --git a/src/lib/python/isc/datasrc/updater_python.cc b/src/lib/python/isc/datasrc/updater_python.cc
index c528a0c..97ffa00 100644
--- a/src/lib/python/isc/datasrc/updater_python.cc
+++ b/src/lib/python/isc/datasrc/updater_python.cc
@@ -217,7 +217,7 @@ PyMethodDef ZoneUpdater_methods[] = {
METH_NOARGS, ZoneFinder_getClass_doc },
{ "find", ZoneUpdater_find, METH_VARARGS, ZoneFinder_find_doc },
{ "find_all", ZoneUpdater_find_all, METH_VARARGS,
- ZoneFinder_find_all_doc },
+ ZoneFinder_findAll_doc },
{ NULL, NULL, 0, NULL }
};
diff --git a/src/lib/python/isc/log/Makefile.am b/src/lib/python/isc/log/Makefile.am
index 5ff2c28..3658c17 100644
--- a/src/lib/python/isc/log/Makefile.am
+++ b/src/lib/python/isc/log/Makefile.am
@@ -13,7 +13,7 @@ log_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
# placed after -Wextra defined in AM_CXXFLAGS
log_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
log_la_LDFLAGS = $(PYTHON_LDFLAGS)
-log_la_LDFLAGS += -module
+log_la_LDFLAGS += -module -avoid-version
log_la_LIBADD = $(top_builddir)/src/lib/log/liblog.la
log_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
log_la_LIBADD += $(top_builddir)/src/lib/config/libcfgclient.la
@@ -23,15 +23,6 @@ log_la_LIBADD += $(PYTHON_LIB)
# This is not installed, it helps locate the module during tests
EXTRA_DIST = __init__.py
-# We're going to abuse install-data-local for a pre-install check.
-# This is to be considered a short term hack and is expected to be removed
-# in a near future version.
-install-data-local:
- if test -d @pyexecdir@/isc/log; then \
- echo "@pyexecdir@/isc/log is deprecated, and will confuse newer versions. Please (re)move it by hand."; \
- exit 1; \
- fi
-
pytest:
$(SHELL) tests/log_test
diff --git a/src/lib/python/isc/log/tests/.gitignore b/src/lib/python/isc/log/tests/.gitignore
new file mode 100644
index 0000000..b9cf241
--- /dev/null
+++ b/src/lib/python/isc/log/tests/.gitignore
@@ -0,0 +1 @@
+/log_console.py
diff --git a/src/lib/python/isc/log_messages/Makefile.am b/src/lib/python/isc/log_messages/Makefile.am
index 4b084cc..6b4be94 100644
--- a/src/lib/python/isc/log_messages/Makefile.am
+++ b/src/lib/python/isc/log_messages/Makefile.am
@@ -13,6 +13,8 @@ EXTRA_DIST += cfgmgr_messages.py
EXTRA_DIST += config_messages.py
EXTRA_DIST += notify_out_messages.py
EXTRA_DIST += libxfrin_messages.py
+EXTRA_DIST += server_common_messages.py
+EXTRA_DIST += dbutil_messages.py
CLEANFILES = __init__.pyc
CLEANFILES += bind10_messages.pyc
@@ -27,6 +29,8 @@ CLEANFILES += cfgmgr_messages.pyc
CLEANFILES += config_messages.pyc
CLEANFILES += notify_out_messages.pyc
CLEANFILES += libxfrin_messages.pyc
+CLEANFILES += server_common_messages.pyc
+CLEANFILES += dbutil_messages.pyc
CLEANDIRS = __pycache__
diff --git a/src/lib/python/isc/log_messages/dbutil_messages.py b/src/lib/python/isc/log_messages/dbutil_messages.py
new file mode 100644
index 0000000..c06dfef
--- /dev/null
+++ b/src/lib/python/isc/log_messages/dbutil_messages.py
@@ -0,0 +1 @@
+from work.dbutil_messages import *
diff --git a/src/lib/python/isc/log_messages/server_common_messages.py b/src/lib/python/isc/log_messages/server_common_messages.py
new file mode 100644
index 0000000..a491071
--- /dev/null
+++ b/src/lib/python/isc/log_messages/server_common_messages.py
@@ -0,0 +1 @@
+from work.server_common_messages import *
diff --git a/src/lib/python/isc/log_messages/work/.gitignore b/src/lib/python/isc/log_messages/work/.gitignore
new file mode 100644
index 0000000..05a7653
--- /dev/null
+++ b/src/lib/python/isc/log_messages/work/.gitignore
@@ -0,0 +1,2 @@
+/__init__.py
+/*_messages.py
diff --git a/src/lib/python/isc/log_messages/work/Makefile.am b/src/lib/python/isc/log_messages/work/Makefile.am
index 9bc5e0f..ad5ee0c 100644
--- a/src/lib/python/isc/log_messages/work/Makefile.am
+++ b/src/lib/python/isc/log_messages/work/Makefile.am
@@ -5,7 +5,7 @@ python_PYTHON = __init__.py
pythondir = $(pyexecdir)/isc/log_messages/
-CLEANFILES = __init__.pyc
+CLEANFILES = __init__.pyc __init__.pyo
CLEANDIRS = __pycache__
clean-local:
diff --git a/src/lib/python/isc/notify/notify_out.py b/src/lib/python/isc/notify/notify_out.py
index 64a4b3e..bfa7167 100644
--- a/src/lib/python/isc/notify/notify_out.py
+++ b/src/lib/python/isc/notify/notify_out.py
@@ -34,7 +34,7 @@ logger = isc.log.Logger("notify_out")
# initialized yet. see trac ticket #1103
from isc.dns import *
-ZONE_NEW_DATA_READY_CMD = 'zone_new_data_ready'
+ZONE_NEW_DATA_READY_CMD = 'notify'
_MAX_NOTIFY_NUM = 30
_MAX_NOTIFY_TRY_NUM = 5
_EVENT_NONE = 0
@@ -164,17 +164,19 @@ class NotifyOut:
the only interface for class NotifyOut which can be called
by other object.
Internally, the function only set the zone's notify-reply
- timeout to now, then notify message will be sent out. '''
+ timeout to now, then notify message will be sent out.
+ Returns False if the zone/class is not known, True if it is
+ (even if there are no slaves)'''
if zone_name[len(zone_name) - 1] != '.':
zone_name += '.'
zone_id = (zone_name, zone_class)
if zone_id not in self._notify_infos:
- return
+ return False
# Has no slave servers, skip it.
if (len(self._notify_infos[zone_id].notify_slaves) <= 0):
- return
+ return True
with self._lock:
if (self.notify_num >= _MAX_NOTIFY_NUM) or (zone_id in self._notifying_zones):
@@ -186,6 +188,7 @@ class NotifyOut:
self._notifying_zones.append(zone_id)
if not self._nonblock_event.isSet():
self._nonblock_event.set()
+ return True
def _dispatcher(self, started_event):
started_event.set() # Let the master know we are alive already
@@ -250,7 +253,9 @@ class NotifyOut:
self._thread.join()
# Clean up
+ self._write_sock.close()
self._write_sock = None
+ self._read_sock.close()
self._read_sock = None
self._thread = None
@@ -273,23 +278,23 @@ class NotifyOut:
# data sources.
datasrc_config = '{ "database_file": "' + self._db_file + '"}'
try:
- result, finder = DataSourceClient('sqlite3',
- datasrc_config).find_zone(
- zone_name)
+ ds_client = DataSourceClient('sqlite3', datasrc_config)
except isc.datasrc.Error as ex:
logger.error(NOTIFY_OUT_DATASRC_ACCESS_FAILURE, ex)
return []
+
+ result, finder = ds_client.find_zone(zone_name)
if result is not DataSourceClient.SUCCESS:
logger.error(NOTIFY_OUT_DATASRC_ZONE_NOT_FOUND,
format_zone_str(zone_name, zone_class))
return []
- result, ns_rrset = finder.find(zone_name, RRType.NS())
+ result, ns_rrset, _ = finder.find(zone_name, RRType.NS())
if result is not finder.SUCCESS or ns_rrset is None:
logger.warn(NOTIFY_OUT_ZONE_NO_NS,
format_zone_str(zone_name, zone_class))
return []
- result, soa_rrset = finder.find(zone_name, RRType.SOA())
+ result, soa_rrset, _ = finder.find(zone_name, RRType.SOA())
if result is not finder.SUCCESS or soa_rrset is None or \
soa_rrset.get_rdata_count() != 1:
logger.warn(NOTIFY_OUT_ZONE_BAD_SOA,
@@ -302,13 +307,17 @@ class NotifyOut:
ns_name = Name(ns_rdata.to_text())
if soa_mname == ns_name:
continue
- result, rrset = finder.find(ns_name, RRType.A())
- if result is finder.SUCCESS and rrset is not None:
- addrs.extend([a.to_text() for a in rrset.get_rdata()])
-
- result, rrset = finder.find(ns_name, RRType.AAAA())
- if result is finder.SUCCESS and rrset is not None:
- addrs.extend([aaaa.to_text() for aaaa in rrset.get_rdata()])
+ ns_result, ns_finder = ds_client.find_zone(ns_name)
+ if ns_result is DataSourceClient.SUCCESS or \
+ ns_result is DataSourceClient.PARTIALMATCH:
+ result, rrset, _ = ns_finder.find(ns_name, RRType.A())
+ if result is ns_finder.SUCCESS and rrset is not None:
+ addrs.extend([a.to_text() for a in rrset.get_rdata()])
+
+ result, rrset, _ = ns_finder.find(ns_name, RRType.AAAA())
+ if result is ns_finder.SUCCESS and rrset is not None:
+ addrs.extend([aaaa.to_text()
+ for aaaa in rrset.get_rdata()])
return addrs
@@ -500,7 +509,7 @@ class NotifyOut:
zone_name.to_text() + '/' +
zone_class.to_text() + ' not found')
- result, soa_rrset = finder.find(zone_name, RRType.SOA())
+ result, soa_rrset, _ = finder.find(zone_name, RRType.SOA())
if result is not finder.SUCCESS or soa_rrset is None or \
soa_rrset.get_rdata_count() != 1:
raise NotifyOutDataSourceError('_get_zone_soa: Zone ' +
diff --git a/src/lib/python/isc/notify/tests/.gitignore b/src/lib/python/isc/notify/tests/.gitignore
new file mode 100644
index 0000000..69f6d95
--- /dev/null
+++ b/src/lib/python/isc/notify/tests/.gitignore
@@ -0,0 +1 @@
+/notify_out_test
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 d64c203..1b3a4a1 100644
--- a/src/lib/python/isc/notify/tests/notify_out_test.py
+++ b/src/lib/python/isc/notify/tests/notify_out_test.py
@@ -114,38 +114,48 @@ class TestNotifyOut(unittest.TestCase):
notify_out._MAX_NOTIFY_NUM = 2
self._notify._nonblock_event.clear()
- self._notify.send_notify('example.net')
+ self.assertTrue(self._notify.send_notify('example.net'))
self.assertTrue(self._notify._nonblock_event.isSet())
self.assertEqual(self._notify.notify_num, 1)
self.assertEqual(self._notify._notifying_zones[0], ('example.net.', 'IN'))
- self._notify.send_notify('example.com')
+ self.assertTrue(self._notify.send_notify('example.com'))
self.assertEqual(self._notify.notify_num, 2)
self.assertEqual(self._notify._notifying_zones[1], ('example.com.', 'IN'))
# notify_num is equal to MAX_NOTIFY_NUM, append it to waiting_zones list.
self._notify._nonblock_event.clear()
- self._notify.send_notify('example.com', 'CH')
+ self.assertTrue(self._notify.send_notify('example.com', 'CH'))
# add waiting zones won't set nonblock_event.
self.assertFalse(self._notify._nonblock_event.isSet())
self.assertEqual(self._notify.notify_num, 2)
self.assertEqual(1, len(self._notify._waiting_zones))
# zone_id is already in notifying_zones list, append it to waiting_zones list.
- self._notify.send_notify('example.net')
+ self.assertTrue(self._notify.send_notify('example.net'))
self.assertEqual(2, len(self._notify._waiting_zones))
self.assertEqual(self._notify._waiting_zones[1], ('example.net.', 'IN'))
# zone_id is already in waiting_zones list, skip it.
- self._notify.send_notify('example.net')
+ self.assertTrue(self._notify.send_notify('example.net'))
self.assertEqual(2, len(self._notify._waiting_zones))
# has no slave masters, skip it.
- self._notify.send_notify('example.org.', 'CH')
+ self.assertTrue(self._notify.send_notify('example.org.', 'CH'))
self.assertEqual(self._notify.notify_num, 2)
self.assertEqual(2, len(self._notify._waiting_zones))
- self._notify.send_notify('example.org.')
+ self.assertTrue(self._notify.send_notify('example.org.'))
+ self.assertEqual(self._notify.notify_num, 2)
+ self.assertEqual(2, len(self._notify._waiting_zones))
+
+ # zone does not exist, should return False, and no change in other
+ # values
+ self.assertFalse(self._notify.send_notify('does.not.exist.'))
+ self.assertEqual(self._notify.notify_num, 2)
+ self.assertEqual(2, len(self._notify._waiting_zones))
+
+ self.assertFalse(self._notify.send_notify('example.net.', 'CH'))
self.assertEqual(self._notify.notify_num, 2)
self.assertEqual(2, len(self._notify._waiting_zones))
@@ -185,6 +195,11 @@ class TestNotifyOut(unittest.TestCase):
# Now make one socket be readable
self._notify._notify_infos[('example.net.', 'IN')].notify_timeout = time.time() + 10
self._notify._notify_infos[('example.com.', 'IN')].notify_timeout = time.time() + 10
+
+ if self._notify._read_sock is not None:
+ self._notify._read_sock.close()
+ if self._notify._write_sock is not None:
+ self._notify._write_sock.close()
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()
diff --git a/src/lib/python/isc/notify/tests/testdata/brokentest.sqlite3 b/src/lib/python/isc/notify/tests/testdata/brokentest.sqlite3
index 61e766c..10d64c1 100644
Binary files a/src/lib/python/isc/notify/tests/testdata/brokentest.sqlite3 and b/src/lib/python/isc/notify/tests/testdata/brokentest.sqlite3 differ
diff --git a/src/lib/python/isc/notify/tests/testdata/test.sqlite3 b/src/lib/python/isc/notify/tests/testdata/test.sqlite3
index e3cadb0..d659181 100644
Binary files a/src/lib/python/isc/notify/tests/testdata/test.sqlite3 and b/src/lib/python/isc/notify/tests/testdata/test.sqlite3 differ
diff --git a/src/lib/python/isc/server_common/Makefile.am b/src/lib/python/isc/server_common/Makefile.am
new file mode 100644
index 0000000..a9eca2e
--- /dev/null
+++ b/src/lib/python/isc/server_common/Makefile.am
@@ -0,0 +1,24 @@
+SUBDIRS = tests
+
+python_PYTHON = __init__.py tsig_keyring.py
+
+pythondir = $(pyexecdir)/isc/server_common
+
+BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.py
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.py
+
+pylogmessagedir = $(pyexecdir)/isc/log_messages/
+
+CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.pyc
+
+CLEANDIRS = __pycache__
+
+EXTRA_DIST = server_common_messages.mes
+
+$(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.py : server_common_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message \
+ -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/server_common_messages.mes
+
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/server_common/__init__.py b/src/lib/python/isc/server_common/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/lib/python/isc/server_common/server_common_messages.mes b/src/lib/python/isc/server_common/server_common_messages.mes
new file mode 100644
index 0000000..b32205c
--- /dev/null
+++ b/src/lib/python/isc/server_common/server_common_messages.mes
@@ -0,0 +1,36 @@
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# No namespace declaration - these constants go in the global namespace
+# of the config_messages python module.
+
+# since these messages are for the python server_common library, care must
+# be taken that names do not conflict with the messages from the c++
+# server_common library. A checker script should verify that, but we do not
+# have that at this moment. So when adding a message, make sure that
+# the name is not already used in src/lib/config/config_messages.mes
+
+% PYSERVER_COMMON_TSIG_KEYRING_DEINIT Deinitializing global TSIG keyring
+A debug message noting that the global TSIG keyring is being removed from
+memory. Most programs don't do that, they just exit, which is OK.
+
+% PYSERVER_COMMON_TSIG_KEYRING_INIT Initializing global TSIG keyring
+A debug message noting the TSIG keyring storage is being prepared. It should
+appear at most once in the lifetime of a program. The keyring still needs
+to be loaded from configuration.
+
+% PYSERVER_COMMON_TSIG_KEYRING_UPDATE Updating global TSIG keyring
+A debug message. The TSIG keyring is being (re)loaded from configuration.
+This happens at startup or when the configuration changes. The old keyring
+is removed and new one created with all the keys.
diff --git a/src/lib/python/isc/server_common/tests/Makefile.am b/src/lib/python/isc/server_common/tests/Makefile.am
new file mode 100644
index 0000000..4829edc
--- /dev/null
+++ b/src/lib/python/isc/server_common/tests/Makefile.am
@@ -0,0 +1,24 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = tsig_keyring_test.py
+EXTRA_DIST = $(PYTESTS)
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+LIBRARY_PATH_PLACEHOLDER =
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
diff --git a/src/lib/python/isc/server_common/tests/tsig_keyring_test.py b/src/lib/python/isc/server_common/tests/tsig_keyring_test.py
new file mode 100644
index 0000000..e9a2174
--- /dev/null
+++ b/src/lib/python/isc/server_common/tests/tsig_keyring_test.py
@@ -0,0 +1,193 @@
+# Copyright (C) 2012 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
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+Tests for isc.server_common.tsig_keyring.
+"""
+
+import unittest
+import isc.log
+from isc.server_common.tsig_keyring import *
+import isc.dns
+from isc.testutils.ccsession_mock import MockModuleCCSession
+
+class Session(MockModuleCCSession):
+ """
+ A class pretending to be the config session.
+ """
+ def __init__(self):
+ MockModuleCCSession.__init__(self)
+ self._name = None
+ self._callback = None
+ self._remove_name = None
+ self._data = None
+
+ def add_remote_config_by_name(self, name, callback):
+ self._name = name
+ self._callback = callback
+
+ def remove_remote_config(self, name):
+ self._remove_name = name
+
+ def get_remote_config_value(self, module, name):
+ if module != 'tsig_keys' or name != 'keys':
+ raise Exception("Asked for bad data element")
+ return (self._data, False)
+
+class TSIGKeyRingTest(unittest.TestCase):
+ """
+ Tests for the isc.server_common.tsig_keyring module.
+ """
+ def setUp(self):
+ self.__session = Session()
+ self.__sha1name = isc.dns.Name('hmac-sha1')
+ self.__md5name = isc.dns.Name('hmac-md5.sig-alg.reg.int')
+
+ def tearDown(self):
+ deinit_keyring()
+
+ def __do_init(self):
+ init_keyring(self.__session)
+ # Some initialization happened
+ self.assertEqual('tsig_keys', self.__session._name)
+
+ def test_initialization(self):
+ """
+ Test we can initialize and deintialize the keyring. It also
+ tests the interaction with the keyring() function.
+ """
+ # The keyring function raises until initialized
+ self.assertRaises(Unexpected, get_keyring)
+ self.__do_init()
+ current_keyring = get_keyring()
+ self.assertTrue(isinstance(current_keyring, isc.dns.TSIGKeyRing))
+ # Another initialization does nothing
+ self.__do_init()
+ self.assertEqual(current_keyring, get_keyring())
+ # When we deinitialize it, it no longer provides the keyring
+ deinit_keyring()
+ self.assertEqual('tsig_keys', self.__session._remove_name)
+ self.__session._remove_name = None
+ self.assertRaises(Unexpected, get_keyring)
+ # Another deinitialization doesn't change anything
+ deinit_keyring()
+ self.assertRaises(Unexpected, get_keyring)
+ self.assertIsNone(self.__session._remove_name)
+ # Test we can init it again (not expected, but not forbidden)
+ self.__do_init()
+ self.assertTrue(isinstance(get_keyring(), isc.dns.TSIGKeyRing))
+
+ def test_load(self):
+ """
+ Test it can load the keys from the configuration and reload them
+ when the data change.
+ """
+ # Initial load
+ self.__session._data = ['key:MTIzNAo=:hmac-sha1']
+ self.__do_init()
+ keys = get_keyring()
+ self.assertEqual(1, keys.size())
+ (rcode, key) = keys.find(isc.dns.Name('key'), self.__sha1name)
+ self.assertEqual(isc.dns.TSIGKeyRing.SUCCESS, rcode)
+ self.assertEqual(isc.dns.Name('key'), key.get_key_name())
+ # There's a change in the configuration
+ # (The key has a different name)
+ self.__session._data = ['key.example:MTIzNAo=:hmac-sha1']
+ self.__session._callback()
+ orig_keys = keys
+ keys = get_keyring()
+ self.assertNotEqual(keys, orig_keys)
+ self.assertEqual(1, keys.size())
+ # The old key is not here
+ (rcode, key) = keys.find(isc.dns.Name('key'), self.__sha1name)
+ self.assertEqual(isc.dns.TSIGKeyRing.NOTFOUND, rcode)
+ self.assertIsNone(key)
+ # But the new one is
+ (rcode, key) = keys.find(isc.dns.Name('key.example'), self.__sha1name)
+ self.assertEqual(isc.dns.TSIGKeyRing.SUCCESS, rcode)
+ self.assertEqual(isc.dns.Name('key.example'), key.get_key_name())
+
+ def test_empty_update(self):
+ """
+ Test an update that doesn't carry the correct element doesn't change
+ anything.
+ """
+ self.__session._data = ['key:MTIzNAo=:hmac-sha1']
+ self.__do_init()
+ keys = get_keyring()
+ self.__session._data = None
+ self.__session._callback()
+ self.assertEqual(keys, get_keyring())
+
+ def test_no_keys_update(self):
+ """
+ Test we can update the keyring to be empty.
+ """
+ self.__session._data = ['key:MTIzNAo=:hmac-sha1']
+ self.__do_init()
+ keys = get_keyring()
+ self.assertEqual(1, keys.size())
+ self.__session._data = []
+ self.__session._callback()
+ keys = get_keyring()
+ self.assertEqual(0, keys.size())
+
+ def test_update_multi(self):
+ """
+ Test we can handle multiple keys in startup/update.
+ """
+ # Init
+ self.__session._data = ['key:MTIzNAo=:hmac-sha1', 'key2:MTIzNAo=']
+ self.__do_init()
+ keys = get_keyring()
+ self.assertEqual(2, keys.size())
+ (rcode, key) = keys.find(isc.dns.Name('key'), self.__sha1name)
+ self.assertEqual(isc.dns.TSIGKeyRing.SUCCESS, rcode)
+ self.assertEqual(isc.dns.Name('key'), key.get_key_name())
+ (rcode, key) = keys.find(isc.dns.Name('key2'), self.__md5name)
+ self.assertEqual(isc.dns.TSIGKeyRing.SUCCESS, rcode)
+ self.assertEqual(isc.dns.Name('key2'), key.get_key_name())
+ # Update
+ self.__session._data = ['key1:MTIzNAo=:hmac-sha1', 'key3:MTIzNAo=']
+ self.__session._callback()
+ keys = get_keyring()
+ self.assertEqual(2, keys.size())
+ (rcode, key) = keys.find(isc.dns.Name('key1'), self.__sha1name)
+ self.assertEqual(isc.dns.TSIGKeyRing.SUCCESS, rcode)
+ self.assertEqual(isc.dns.Name('key1'), key.get_key_name())
+ (rcode, key) = keys.find(isc.dns.Name('key3'), self.__md5name)
+ self.assertEqual(isc.dns.TSIGKeyRing.SUCCESS, rcode)
+ self.assertEqual(isc.dns.Name('key3'), key.get_key_name())
+
+ def test_update_bad(self):
+ """
+ Test it raises on bad updates and doesn't change anything.
+ """
+ self.__session._data = ['key:MTIzNAo=:hmac-sha1']
+ self.__do_init()
+ keys = get_keyring()
+ # Bad TSIG string
+ self.__session._data = ['key:this makes no sense:really']
+ self.assertRaises(isc.dns.InvalidParameter, self.__session._callback)
+ self.assertEqual(keys, get_keyring())
+ # A duplicity
+ self.__session._data = ['key:MTIzNAo=:hmac-sha1', 'key:MTIzNAo=:hmac-sha1']
+ self.assertRaises(AddError, self.__session._callback)
+ self.assertEqual(keys, get_keyring())
+
+if __name__ == "__main__":
+ isc.log.init("bind10") # FIXME Should this be needed?
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/lib/python/isc/server_common/tsig_keyring.py b/src/lib/python/isc/server_common/tsig_keyring.py
new file mode 100644
index 0000000..308cfd4
--- /dev/null
+++ b/src/lib/python/isc/server_common/tsig_keyring.py
@@ -0,0 +1,121 @@
+# Copyright (C) 2012 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
+# 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 module conveniently keeps a copy of TSIG keyring loaded from the
+tsig_keys module.
+"""
+
+import isc.dns
+import isc.log
+from isc.log_messages.server_common_messages import *
+
+updater = None
+logger = isc.log.Logger("server_common")
+
+class Unexpected(Exception):
+ """
+ Raised when an unexpected operation is requested by the user of this
+ module. For example if calling keyring() before init_keyring().
+ """
+ pass
+
+class AddError(Exception):
+ """
+ Raised when a key can not be added. This usually means there's a
+ duplicate.
+ """
+ pass
+
+class Updater:
+ """
+ The updater of tsig key ring. Not to be used directly.
+ """
+ def __init__(self, session):
+ """
+ Constructor. Pass the ccsession object so the key ring can be
+ downloaded.
+ """
+ logger.debug(logger.DBGLVL_TRACE_BASIC,
+ PYSERVER_COMMON_TSIG_KEYRING_INIT)
+ self.__session = session
+ self.__keyring = isc.dns.TSIGKeyRing()
+ session.add_remote_config_by_name('tsig_keys', self.__update)
+ self.__update()
+
+ def __update(self, value=None, module_cfg=None):
+ """
+ Update the key ring by the configuration.
+
+ Note that this function is used as a callback, but can raise
+ on bad data. The bad data is expected to be handled by the
+ configuration plugin and not be allowed as far as here.
+
+ The parameters are there just to match the signature which
+ the callback should have (i.e. they are ignored).
+ """
+ logger.debug(logger.DBGLVL_TRACE_BASIC,
+ PYSERVER_COMMON_TSIG_KEYRING_UPDATE)
+ (data, _) = self.__session.get_remote_config_value('tsig_keys', 'keys')
+ if data is not None: # There's an update
+ keyring = isc.dns.TSIGKeyRing()
+ for key_data in data:
+ key = isc.dns.TSIGKey(key_data)
+ if keyring.add(key) != isc.dns.TSIGKeyRing.SUCCESS:
+ raise AddError("Can't add key " + str(key))
+ self.__keyring = keyring
+
+ def get_keyring(self):
+ """
+ Return the current key ring.
+ """
+ return self.__keyring
+
+ def deinit(self):
+ """
+ Unregister from getting updates. The object will not be
+ usable any more after this.
+ """
+ logger.debug(logger.DBGLVL_TRACE_BASIC,
+ PYSERVER_COMMON_TSIG_KEYRING_DEINIT)
+ self.__session.remove_remote_config('tsig_keys')
+
+def get_keyring():
+ """
+ Get the current key ring. You need to call init_keyring first.
+ """
+ if updater is None:
+ raise Unexpected("You need to initialize the keyring first by " +
+ "init_keyring()")
+ return updater.get_keyring()
+
+def init_keyring(session):
+ """
+ Initialize the key ring for future use. It does nothing if already
+ initialized.
+ """
+ global updater
+ if updater is None:
+ updater = Updater(session)
+
+def deinit_keyring():
+ """
+ Deinit key ring. Yoeu can no longer access keyring() after this.
+ Does nothing if not initialized.
+ """
+ global updater
+ if updater is not None:
+ updater.deinit()
+ updater = None
diff --git a/src/lib/python/isc/testutils/Makefile.am b/src/lib/python/isc/testutils/Makefile.am
index 5479d83..7abc1bc 100644
--- a/src/lib/python/isc/testutils/Makefile.am
+++ b/src/lib/python/isc/testutils/Makefile.am
@@ -1,4 +1,5 @@
-EXTRA_DIST = __init__.py parse_args.py tsigctx_mock.py rrset_utils.py
+EXTRA_DIST = __init__.py ccsession_mock.py parse_args.py tsigctx_mock.py \
+ rrset_utils.py
CLEANDIRS = __pycache__
diff --git a/src/lib/python/isc/testutils/ccsession_mock.py b/src/lib/python/isc/testutils/ccsession_mock.py
new file mode 100644
index 0000000..5f88678
--- /dev/null
+++ b/src/lib/python/isc/testutils/ccsession_mock.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+class MockModuleCCSession():
+ """Fake ModuleCCSession with a minimal implementation as needed by the
+ tests. Currently this module only stores whether some methods have
+ been called on it (send_stopping(), and close())"""
+ def __init__(self):
+ """Will be set to True when send_stopping() is called"""
+ self.stopped = False
+ """Will be set to True when close() is called"""
+ self.closed = False
+
+ def send_stopping(self):
+ """Fake send_stopping() call. No message is sent, but only stores
+ that this method has been called."""
+ self.stopped = True
+
+ def close(self):
+ """Fake close() call. Nothing is closed, but only stores
+ that this method has been called."""
+ self.closed = True
diff --git a/src/lib/python/isc/util/Makefile.am b/src/lib/python/isc/util/Makefile.am
index 703879a..3eaaa12 100644
--- a/src/lib/python/isc/util/Makefile.am
+++ b/src/lib/python/isc/util/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = . io tests
+SUBDIRS = . cio tests
python_PYTHON = __init__.py process.py socketserver_mixin.py file.py
diff --git a/src/lib/python/isc/util/cio/Makefile.am b/src/lib/python/isc/util/cio/Makefile.am
new file mode 100644
index 0000000..0a2e735
--- /dev/null
+++ b/src/lib/python/isc/util/cio/Makefile.am
@@ -0,0 +1,41 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+python_PYTHON = __init__.py
+pythondir = $(PYTHON_SITEPKG_DIR)/isc/util/cio
+
+pyexec_LTLIBRARIES = socketsession.la
+pyexecdir = $(PYTHON_SITEPKG_DIR)/isc/util/cio
+
+socketsession_la_SOURCES = socketsession_python.cc socketsession_python.h
+socketsession_la_SOURCES += socketsessionforwarder_python.cc
+socketsession_la_SOURCES += socketsessionforwarder_python.h
+socketsession_la_SOURCES += socketsessionreceiver_python.cc
+socketsession_la_SOURCES += socketsessionreceiver_python.h
+socketsession_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
+socketsession_la_LDFLAGS = $(PYTHON_LDFLAGS)
+# Note: PYTHON_CXXFLAGS may have some -Wno... workaround, which must be
+# placed after -Wextra defined in AM_CXXFLAGS
+socketsession_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
+
+# Python prefers .so, while some OSes (specifically MacOS) use a different
+# suffix for dynamic objects. -module is necessary to work this around.
+socketsession_la_LDFLAGS += -module -avoid-version
+socketsession_la_LIBADD = $(top_builddir)/src/lib/util/io/libutil_io.la
+socketsession_la_LIBADD += $(PYTHON_LIB)
+
+# This is not installed, it helps locate the module during tests
+EXTRA_DIST = __init__.py socketsession.py
+
+EXTRA_DIST += socketsession_inc.cc
+EXTRA_DIST += socketsessionforwarder_inc.cc socketsessionreceiver_inc.cc
+
+CLEANFILES = __init__.pyc socketsession.pyc
+
+CLEANDIRS = __pycache__
+
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/util/cio/__init__.py b/src/lib/python/isc/util/cio/__init__.py
new file mode 100644
index 0000000..935160a
--- /dev/null
+++ b/src/lib/python/isc/util/cio/__init__.py
@@ -0,0 +1,3 @@
+"""
+Here are function and classes for forwarding socket sessions between processes.
+"""
diff --git a/src/lib/python/isc/util/cio/socketsession.py b/src/lib/python/isc/util/cio/socketsession.py
new file mode 100644
index 0000000..5017d90
--- /dev/null
+++ b/src/lib/python/isc/util/cio/socketsession.py
@@ -0,0 +1,26 @@
+# 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.
+
+# This file is not installed. See python/isc/log/__init__.py for the trick.
+
+import os
+import sys
+
+for base in sys.path[:]:
+ libdir = os.path.join(base, 'isc/util/cio/.libs')
+ if os.path.exists(libdir):
+ sys.path.insert(0, libdir)
+
+from socketsession import *
diff --git a/src/lib/python/isc/util/cio/socketsession_inc.cc b/src/lib/python/isc/util/cio/socketsession_inc.cc
new file mode 100644
index 0000000..e200063
--- /dev/null
+++ b/src/lib/python/isc/util/cio/socketsession_inc.cc
@@ -0,0 +1,122 @@
+namespace {
+const char* const socketsession_doc = "\
+This module defines a set of classes that support forwarding a\n\
+\"socket session\" from one process to another. A socket session is a\n\
+conceptual tuple of the following elements:\n\
+\n\
+- A network socket\n\
+- The local and remote endpoints of a (IP) communication taking place\n\
+ on the socket. In practice an endpoint is a pair of an IP address\n\
+ and TCP or UDP port number.\n\
+- Some amount of data sent from the remote endpoint and received on\n\
+ the socket. We call it (socket) session data in this documentation.\n\
+\n\
+Note that this is a conceptual definition. Depending on the underlying\n\
+implementation and/or the network protocol, some of the elements could\n\
+be part of others; for example, if it's an established TCP connection,\n\
+the local and remote endpoints would be able to be retrieved from the\n\
+socket using the standard getsockname() and getpeername() system\n\
+calls. But in this definition we separate these to be more generic.\n\
+Also, as a matter of fact our intended usage includes non-connected\n\
+UDP communications, in which case at least the remote endpoint should\n\
+be provided separately from the socket.\n\
+\n\
+In the actual implementation we represent a socket as a Python socket\n\
+object, which contains the information of the address family\n\
+(e.g. AF_INET6), socket type (e.g. SOCK_STREAM), and protocol\n\
+(e.g. IPPROTO_TCP).\n\
+\n\
+We use the Python socket address tuple to represent endpoints.\n\
+\n\
+Socket session data is an opaque blob in the form of a Python byte\n\
+object.\n\
+\n\
+To forward a socket session between processes, we use connected UNIX\n\
+domain sockets established between the processes. The file descriptor\n\
+will be forwarded through the sockets as an ancillary data item of\n\
+type SCM_RIGHTS. Other elements of the session will be transferred as\n\
+normal data over the connection.\n\
+\n\
+We provide two classes to help applications forward socket sessions:\n\
+SocketSessionForwarder is the sender of the UNIX domain connection,\n\
+while SocketSessionReceiver is the receiver (this interface assumes\n\
+one direction of forwarding).\n\
+\n\
+Note: this paragraph and following discussions on the internal\n\
+protocol are for reference purposes only; it's not necessary to\n\
+understand how to use the API.\n\
+SocketSessionForwarder and SocketSessionReceiver objects (internally)\n\
+use a straightforward protocol to pass elements of socket sessions.\n\
+Once the connection is established, the forwarder object first forwards\n\
+the file descriptor with 1-byte dummy data. It then forwards a\n\
+\"(socket) session header\", which contains all other elements of\n\
+the session except the file descriptor (already forwarded) and session\n\
+data. The wire format of the header is as follows:\n\
+\n\
+- The length of the header (16-bit unsigned integer)\n\
+- Address family\n\
+- Socket type\n\
+- Protocol\n\
+- Size of the local endpoint in bytes\n\
+- Local endpoint (a copy of the memory image of the corresponding\n\
+ sockaddr)\n\
+- Size of the remote endpoint in bytes\n\
+- Remote endpoint (same as local endpoint)\n\
+- Size of session data in bytes\n\
+\n\
+The type of the fields is 32-bit unsigned integer unless explicitly\n\
+noted, and all fields are formatted in the network byte order.\n\
+\n\
+The socket session data immediately follows the session header.\n\
+\n\
+Note that the fields do not necessarily be in the network byte order\n\
+because they are expected to be exchanged on the same machine.\n\
+Likewise, integer elements such as address family do not necessarily\n\
+be represented as an fixed-size value (i.e., 32-bit). But fixed size\n\
+fields are used in order to ensure maximum portability in such a\n\
+(rare) case where the forwarder and the receiver are built with\n\
+different compilers that have different definitions of int. Also,\n\
+since sockaddr fields are generally formatted in the network byte\n\
+order, other fields are defined so to be consistent.\n\
+\n\
+One basic assumption in the API of this module is socket sessions\n\
+should be forwarded without blocking, thus eliminating the need for\n\
+incremental read/write or blocking other important services such as\n\
+responding to requests from the application's clients. This assumption\n\
+should be held as long as both the forwarder and receiver have\n\
+sufficient resources to handle the forwarding process since the\n\
+communication is local. But a forward attempt could still block if the\n\
+receiver is busy (or even hang up) and cannot keep up with the volume\n\
+of incoming sessions.\n\
+\n\
+So, in this implementation, the forwarder uses non blocking writes to\n\
+forward sessions. If a write attempt could block, it immediately gives\n\
+up the operation with an exception. The corresponding application is\n\
+expected to catch it, close the connection, and perform any necessary\n\
+recovery for that application (that would normally be re-establish the\n\
+connection with a new receiver, possibly after confirming the\n\
+receiving side is still alive). On the other hand, the receiver\n\
+implementation assumes it's possible that it only receive incomplete\n\
+elements of a session (such as in the case where the forwarder writes\n\
+part of the entire session and gives up the connection). The receiver\n\
+implementation throws an exception when it encounters an incomplete\n\
+session. Like the case of the forwarder application, the receiver\n\
+application is expected to catch it, close the connection, and perform\n\
+any necessary recovery steps.\n\
+\n\
+Note that the receiver implementation uses blocking read. So it's\n\
+application's responsibility to ensure that there's at least some data\n\
+in the connection when the receiver object is requested to receive a\n\
+session (unless this operation can be blocking, e.g., by the use of a\n\
+separate thread). Also, if the forwarder implementation or application\n\
+is malicious or extremely buggy and intentionally sends partial\n\
+session and keeps the connection, the receiver could block in\n\
+receiving a session. In general, we assume the forwarder doesn't do\n\
+intentional blocking as it's a local node and is generally a module of\n\
+the same (BIND 10) system. The minimum requirement for the forwarder\n\
+implementation (and application) is to make sure the connection is\n\
+closed once it detects an error on it. Even a naive implementation\n\
+that simply dies due to the exception will meet this requirement.\n\
+\n\
+";
+} // unnamed namespace
diff --git a/src/lib/python/isc/util/cio/socketsession_python.cc b/src/lib/python/isc/util/cio/socketsession_python.cc
new file mode 100644
index 0000000..8fdfbc1
--- /dev/null
+++ b/src/lib/python/isc/util/cio/socketsession_python.cc
@@ -0,0 +1,79 @@
+// 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 <Python.h>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include "socketsessionreceiver_python.h"
+#include "socketsessionforwarder_python.h"
+
+using namespace isc::util::io::python;
+using namespace isc::util::python;
+
+#include "socketsession_inc.cc"
+
+namespace isc {
+namespace util {
+namespace io {
+namespace python {
+PyObject* po_SocketSessionError;
+}
+}
+}
+}
+
+namespace {
+
+PyModuleDef socketsession = {
+ { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
+ "isc.util.cio.socketsession",
+ socketsession_doc,
+ -1,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+} // end of unnamed namespace
+
+PyMODINIT_FUNC
+PyInit_socketsession(void) {
+ PyObject* mod = PyModule_Create(&socketsession);
+ if (mod == NULL) {
+ return (NULL);
+ }
+
+ try {
+ po_SocketSessionError =
+ PyErr_NewException("isc.util.cio.SocketSessionError", NULL, NULL);
+ PyObjectContainer(po_SocketSessionError).
+ installToModule(mod, "SocketSessionError");
+ } catch (...) {
+ Py_DECREF(mod);
+ return (NULL);
+ }
+
+ if (!initModulePart_SocketSessionForwarder(mod)) {
+ Py_DECREF(mod);
+ return (NULL);
+ }
+ if (!initModulePart_SocketSessionReceiver(mod)) {
+ Py_DECREF(mod);
+ return (NULL);
+ }
+
+ return (mod);
+}
diff --git a/src/lib/python/isc/util/cio/socketsession_python.h b/src/lib/python/isc/util/cio/socketsession_python.h
new file mode 100644
index 0000000..b0703ac
--- /dev/null
+++ b/src/lib/python/isc/util/cio/socketsession_python.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 __PYTHON_SOCKETSESSION_H
+#define __PYTHON_SOCKETSESSION_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace util {
+namespace io {
+namespace python {
+
+extern PyObject* po_SocketSessionError;
+
+} // namespace python
+} // namespace io
+} // namespace util
+} // namespace isc
+#endif // __PYTHON_SOCKETSESSION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/python/isc/util/cio/socketsessionforwarder_inc.cc b/src/lib/python/isc/util/cio/socketsessionforwarder_inc.cc
new file mode 100644
index 0000000..6b9de01
--- /dev/null
+++ b/src/lib/python/isc/util/cio/socketsessionforwarder_inc.cc
@@ -0,0 +1,136 @@
+namespace {
+// Modifications:
+// reference to the module description (instead of "utility")
+// exception description
+const char* const SocketSessionForwarder_doc = "\
+The forwarder of socket sessions.\n\
+\n\
+An object of this class maintains a UNIX domain socket (normally\n\
+expected to be connected to a SocketSessionReceiver object) and\n\
+forwards socket sessions to the receiver.\n\
+\n\
+See the description of socketsession module for other details of how\n\
+the session forwarding works.\n\
+\n\
+SocketSessionForwarder(unix_file)\n\
+\n\
+ The constructor.\n\
+\n\
+ It's constructed with path information of the intended receiver,\n\
+ but does not immediately establish a connection to the receiver;\n\
+ connect_to_receiver() must be called to establish it. These are\n\
+ separated so that an object of class can be initialized (possibly\n\
+ as an attribute of a higher level application class object)\n\
+ without knowing the receiver is ready for accepting new\n\
+ forwarders. The separate connect interface allows the object to be\n\
+ reused when it detects connection failure and tries to re-\n\
+ establish it after closing the failed one.\n\
+\n\
+ On construction, it also installs a signal filter for SIGPIPE to\n\
+ ignore it. Since this class uses a stream-type connected UNIX\n\
+ domain socket, if the receiver (abruptly) closes the connection a\n\
+ subsequent write operation on the socket would trigger a SIGPIPE\n\
+ signal, which kills the caller process by default. This behavior\n\
+ would be undesirable in many cases, so this implementation always\n\
+ disables the signal.\n\
+\n\
+ This approach has some drawbacks, however; first, since signal\n\
+ handling is process (or thread) wide, ignoring it may not what the\n\
+ application wants. On the other hand, if the application changes\n\
+ how the signal is handled after instantiating this class, the new\n\
+ behavior affects the class operation. Secondly, even if ignoring\n\
+ the signal is the desired operation, it's a waste to set the\n\
+ filter every time this class object is constructed. It's\n\
+ sufficient to do it once. We still adopt this behavior based on\n\
+ the observation that in most cases applications would like to\n\
+ ignore SIGPIPE (or simply doesn't care about it) and that this\n\
+ class is not instantiated so often (so the wasteful setting\n\
+ overhead should be marginal). On the other hand, doing it every\n\
+ time is beneficial if the application is threaded and different\n\
+ threads create different forwarder objects (and if signals work\n\
+ per thread).\n\
+\n\
+ Exceptions:\n\
+ SocketSessionError unix_file is invalid as a path name of a UNIX\n\
+ domain socket or error happens in setting a filter for\n\
+ SIGPIPE (see above)\n\
+ SystemError Unexpected errors such as resource allocation failure\n\
+\n\
+ Parameters:\n\
+ unix_file Path name of the receiver.\n\
+\n\
+";
+
+// Modifications:
+// exception description
+const char* const SocketSessionForwarder_connectToReceiver_doc = "\
+connect_to_receiver()\n\
+\n\
+Establish a connection to the receiver.\n\
+\n\
+This method establishes a connection to the receiver at the path given\n\
+on construction. It makes the underlying UNIX domain socket non\n\
+blocking, so this method (or subsequent push() calls) does not block.\n\
+\n\
+Exceptions:\n\
+ TypeError The method is called while an already established\n\
+ connection is still active.\n\
+ SocketSessionError A system error in socket operation.\n\
+ SystemError Unexpected errors such as resource allocation failure\n\
+\n\
+";
+
+// Modifications:
+// bullet description
+// parameters
+// exception description
+const char* const SocketSessionForwarder_push_doc = "\
+push(sock, family, type, protocol, local_end, remote_end, data)\n\
+\n\
+Forward a socket session to the receiver.\n\
+\n\
+This method takes a set of parameters that represent a single socket\n\
+session, renders them in the \"wire\" format according to the internal\n\
+protocol (see socketsession module) and forwards them to the\n\
+receiver through the UNIX domain connection.\n\
+\n\
+The connection must have been established by connect_to_receiver().\n\
+\n\
+For simplicity and for the convenience of detecting application\n\
+errors, this method imposes some restrictions on the parameters:\n\
+\n\
+- Socket family must be either AF_INET or AF_INET6\n\
+- The address family (sa_family) member of the local and remote end\n\
+ points must be equal to the family parameter\n\
+- Socket session data must not be empty\n\
+- Data length must not exceed 65535\n\
+\n\
+These are not architectural limitation, and might be loosened in future\n\
+versions as we see the need for flexibility.\n\
+\n\
+Since the underlying UNIX domain socket is non blocking (see the\n\
+description for the constructor), a call to this method should either\n\
+return immediately or result in exception (in case of \"would\n\
+block\").\n\
+\n\
+Exceptions:\n\
+ TypeError The method is called before establishing a connection or\n\
+ given parameters are invalid, or the given socket address\n\
+ is valid.\n\
+ SocketSessionError A system error in socket operation, including the\n\
+ case where the write operation would block.\n\
+\n\
+Parameters:\n\
+ sock (int) The socket file descriptor\n\
+ family (int) The address family (such as socket.AF_INET6) of the\n\
+ socket\n\
+ type (int) The socket type (such as socket.SOCK_DGRAM) of the\n\
+ socket\n\
+ protocol (int) The transport protocol (such as socket.IPPROTO_UDP)\n\
+ of the socket\n\
+ local_end (socket address) The local end point of the session\n\
+ remote_end (socket address) The remote end point of the session\n\
+ data (byte) the session data\n\
+\n\
+";
+} // unnamed namespace
diff --git a/src/lib/python/isc/util/cio/socketsessionforwarder_python.cc b/src/lib/python/isc/util/cio/socketsessionforwarder_python.cc
new file mode 100644
index 0000000..583a877
--- /dev/null
+++ b/src/lib/python/isc/util/cio/socketsessionforwarder_python.cc
@@ -0,0 +1,309 @@
+// 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.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/io/sockaddr_util.h>
+#include <util/io/socketsession.h>
+#include <util/python/pycppwrapper_util.h>
+
+#include "socketsession_python.h"
+#include "socketsessionforwarder_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::util::io;
+using namespace isc::util::io::internal;
+using namespace isc::util::io::python;
+using boost::lexical_cast;
+
+// Trivial constructor.
+s_SocketSessionForwarder::s_SocketSessionForwarder() : cppobj(NULL) {
+}
+
+// Import pydoc text
+#include "socketsessionforwarder_inc.cc"
+
+// See python/isc/log/log.cc for the use of namespace
+namespace clang_unnamed_namespace_workaround {
+// Internal exception class thrown when address parsing fails
+class AddressParseError: public isc::Exception {
+public:
+ AddressParseError(const char *file, size_t line, const char *what):
+ isc::Exception(file, line, what) {}
+};
+}
+using namespace clang_unnamed_namespace_workaround;
+
+namespace {
+
+int
+SocketSessionForwarder_init(PyObject* po_self, PyObject* args, PyObject*) {
+ s_SocketSessionForwarder* self =
+ static_cast<s_SocketSessionForwarder*>(po_self);
+ try {
+ const char* unix_file;
+ if (PyArg_ParseTuple(args, "s", &unix_file)) {
+ self->cppobj = new SocketSessionForwarder(unix_file);
+ return (0);
+ }
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Failed to construct SocketSessionForwarder object: " +
+ string(ex.what());
+ PyErr_SetString(po_SocketSessionError, ex_what.c_str());
+ return (-1);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (-1);
+ }
+
+ return (-1);
+}
+
+void
+SocketSessionForwarder_destroy(PyObject* po_self) {
+ s_SocketSessionForwarder* self =
+ static_cast<s_SocketSessionForwarder*>(po_self);
+ delete self->cppobj;
+ self->cppobj = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+// Convert a Python socket address object to an addrinfo structure by
+// getaddrinfo.
+void
+parsePySocketAddress(PyObject* obj, int type, int protocol,
+ struct sockaddr_storage* ss)
+{
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_socktype = type;
+ hints.ai_protocol = protocol;
+ hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
+
+ const char* addr;
+ int port, flowinfo, scopeid;
+ struct addrinfo *res;
+ if (PyArg_ParseTuple(obj, "si", &addr, &port)) {
+ // Possibly an IPv4 address.
+ hints.ai_family = AF_INET;
+ const int error = getaddrinfo(addr,
+ lexical_cast<string>(port).c_str(),
+ &hints, &res);
+ if (error == 0) {
+ assert(res->ai_addrlen <= sizeof(*ss));
+ memcpy(ss, res->ai_addr, res->ai_addrlen);
+ return;
+ }
+ isc_throw(AddressParseError, "Invalid or unsupported socket address: "
+ << gai_strerror(error));
+ }
+ PyErr_Clear();
+ if (PyArg_ParseTuple(obj, "siii", &addr, &port, &flowinfo, &scopeid)) {
+ // Possibly an IPv6 address. We ignore flowinfo.
+ hints.ai_family = AF_INET6;
+ const int error = getaddrinfo(addr,
+ lexical_cast<string>(port).c_str(),
+ &hints, &res);
+ if (error == 0) {
+ assert(res->ai_addrlen <= sizeof(*ss));
+ memcpy(ss, res->ai_addr, res->ai_addrlen);
+ void* p = ss;
+ static_cast<struct sockaddr_in6*>(p)->sin6_scope_id = scopeid;
+ return;
+ }
+ isc_throw(AddressParseError, "Invalid or unsupported socket address: "
+ << gai_strerror(error));
+ }
+ PyErr_Clear();
+ isc_throw(AddressParseError, "Invalid or unsupported socket address, must "
+ "be AF_INET or AF_INET6 socket address.");
+}
+
+PyObject*
+SocketSessionForwarder_connectToReceiver(PyObject* po_self, PyObject*) {
+ s_SocketSessionForwarder* const self =
+ static_cast<s_SocketSessionForwarder*>(po_self);
+
+ try {
+ self->cppobj->connectToReceiver();
+ Py_RETURN_NONE;
+ } catch (const isc::BadValue& ex) {
+ PyErr_SetString(PyExc_TypeError, ex.what());
+ return (NULL);
+ } catch (const SocketSessionError& ex) {
+ PyErr_SetString(po_SocketSessionError, ex.what());
+ return (NULL);
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure in connecting to receiver: " +
+ string(ex.what());
+ PyErr_SetString(PyExc_SystemError, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (NULL);
+ }
+}
+
+PyObject*
+SocketSessionForwarder_push(PyObject* po_self, PyObject* args) {
+ s_SocketSessionForwarder* const self =
+ static_cast<s_SocketSessionForwarder*>(po_self);
+
+ try {
+ int fd, family, type, protocol;
+ PyObject* po_local_end;
+ PyObject* po_remote_end;
+ Py_buffer py_buf;
+
+ if (!PyArg_ParseTuple(args, "iiiiOOy*", &fd, &family, &type, &protocol,
+ &po_local_end, &po_remote_end, &py_buf)) {
+ return (NULL);
+ }
+ struct sockaddr_storage ss_local, ss_remote;
+ parsePySocketAddress(po_local_end, type, protocol, &ss_local);
+ parsePySocketAddress(po_remote_end, type, protocol, &ss_remote);
+ self->cppobj->push(fd, family, type, protocol,
+ *convertSockAddr(&ss_local),
+ *convertSockAddr(&ss_remote),
+ py_buf.buf, py_buf.len);
+ Py_RETURN_NONE;
+ } catch (const AddressParseError& ex) {
+ PyErr_SetString(PyExc_TypeError, ex.what());
+ return (NULL);
+ } catch (const isc::BadValue& ex) {
+ PyErr_SetString(PyExc_TypeError, ex.what());
+ return (NULL);
+ } catch (const SocketSessionError& ex) {
+ PyErr_SetString(po_SocketSessionError, ex.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (NULL);
+ }
+}
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef SocketSessionForwarder_methods[] = {
+ { "push", SocketSessionForwarder_push, METH_VARARGS,
+ SocketSessionForwarder_push_doc },
+ { "connect_to_receiver", SocketSessionForwarder_connectToReceiver,
+ METH_NOARGS, SocketSessionForwarder_connectToReceiver_doc },
+ { NULL, NULL, 0, NULL }
+};
+} // end of unnamed namespace
+
+namespace isc {
+namespace util {
+namespace io {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_SocketSessionForwarder
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject socketsessionforwarder_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "isc.util.cio.SocketSessionForwarder",
+ sizeof(s_SocketSessionForwarder), // tp_basicsize
+ 0, // tp_itemsize
+ SocketSessionForwarder_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
+ SocketSessionForwarder_doc,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ SocketSessionForwarder_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
+ SocketSessionForwarder_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_SocketSessionForwarder(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(&socketsessionforwarder_type) < 0) {
+ return (false);
+ }
+ void* p = &socketsessionforwarder_type;
+ if (PyModule_AddObject(mod, "SocketSessionForwarder",
+ static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&socketsessionforwarder_type);
+
+ return (true);
+}
+} // namespace python
+} // namespace io
+} // namespace util
+} // namespace isc
diff --git a/src/lib/python/isc/util/cio/socketsessionforwarder_python.h b/src/lib/python/isc/util/cio/socketsessionforwarder_python.h
new file mode 100644
index 0000000..2ce220a
--- /dev/null
+++ b/src/lib/python/isc/util/cio/socketsessionforwarder_python.h
@@ -0,0 +1,45 @@
+// 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 __PYTHON_SOCKETSESSIONFORWARDER_H
+#define __PYTHON_SOCKETSESSIONFORWARDER_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace util {
+namespace io {
+class SocketSessionForwarder;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_SocketSessionForwarder : public PyObject {
+public:
+ s_SocketSessionForwarder();
+ SocketSessionForwarder* cppobj;
+};
+
+extern PyTypeObject socketsessionforwarder_type;
+
+bool initModulePart_SocketSessionForwarder(PyObject* mod);
+} // namespace python
+} // namespace io
+} // namespace util
+} // namespace isc
+#endif // __PYTHON_SOCKETSESSIONFORWARDER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/python/isc/util/cio/socketsessionreceiver_inc.cc b/src/lib/python/isc/util/cio/socketsessionreceiver_inc.cc
new file mode 100644
index 0000000..ed29d3e
--- /dev/null
+++ b/src/lib/python/isc/util/cio/socketsessionreceiver_inc.cc
@@ -0,0 +1,89 @@
+namespace {
+// Modifications
+// - about return value
+// - socket session "utility" => module
+const char* const SocketSessionReceiver_doc = "\
+The receiver of socket sessions.\n\
+\n\
+An object of this class holds a UNIX domain socket for an established\n\
+connection, receives socket sessions from the remote forwarder, and\n\
+provides the session to the application as a tuple of corresponding\n\
+elements.\n\
+\n\
+Note that this class is instantiated with an already connected socket;\n\
+it's not a listening socket that is accepting connection requests from\n\
+forwarders. It's application's responsibility to create the listening\n\
+socket, listen on it and accept connections. Once the connection is\n\
+established, the application would construct a SocketSessionReceiver\n\
+object with the socket for the newly established connection. This\n\
+behavior is based on the design decision that the application should\n\
+decide when it performs (possibly) blocking operations (see\n\
+socketsession module for more details).\n\
+\n\
+See the description of socketsession module for other details of how\n\
+the session forwarding works.\n\
+\n\
+SocketSessionReceiver(socket)\n\
+\n\
+ The constructor.\n\
+\n\
+ Exceptions:\n\
+ TypeError The given parameter is not a valid socket object\n\
+ SocketSessionError Any error on an operation that is performed\n\
+ on the given socket as part of initialization.\n\
+ SystemError Unexpected errors such as resource allocation failure\n\
+\n\
+ Parameters:\n\
+ socket A python socket object of a UNIX domain family for an\n\
+ established connection with a forwarder.\n\
+\n\
+";
+
+// Modifications
+// - socket session utility -> module
+// - return value (not a SocketSession object, but a Python tuple)
+// - remove the validity note (we copy it here, so there's no such
+// restriction)
+// - caller's responsibility: only responsible for closing the socket.
+// - text around the bullets
+// - exception
+const char* const SocketSessionReceiver_pop_doc = "\
+pop() -> (socket, socket address, socket address, byte)\n\
+\n\
+Receive a socket session from the forwarder.\n\
+\n\
+This method receives wire-format data (see socketsession module) for\n\
+a socket session on the UNIX domain socket, performs some validation\n\
+on the data, and returns the session information as a tuple.\n\
+\n\
+The caller is responsible for closing the received socket.\n\
+\n\
+It ensures the following:\n\
+\n\
+- The socket's address family is either AF_INET or AF_INET6\n\
+- The family element of the socket addresses for the local and remote\n\
+ end points must be equal to the socket's address family\n\
+- The socket session data is not empty and does not exceed 65535\n\
+ bytes.\n\
+\n\
+If the validation fails or an unexpected system error happens\n\
+(including a connection close in the meddle of reception), it throws\n\
+an SocketSessionError exception. When this happens, it's very\n\
+unlikely that a subsequent call to this method succeeds, so in\n\
+reality the application is expected to destruct it and close the\n\
+socket in such a case.\n\
+\n\
+Exceptions:\n\
+ SocketSessionError Invalid data is received or a system error on\n\
+ socket operation happens.\n\
+ SystemError Unexpected errors such as resource allocation failure\n\
+\n\
+Return Value(s): A tuple corresponding to the extracted socket session:\n\
+ socket A Python socket object corresponding to the socket passed\n\
+ by the forwarder\n\
+ socket address A Python socket address (which is a tuple) for the local\n\
+ end point\n\
+ socket address A Python socket address for the remote endpoint\n\
+ data A Python byte object that stores the session data\n\
+";
+} // unnamed namespace
diff --git a/src/lib/python/isc/util/cio/socketsessionreceiver_python.cc b/src/lib/python/isc/util/cio/socketsessionreceiver_python.cc
new file mode 100644
index 0000000..c79f6e0
--- /dev/null
+++ b/src/lib/python/isc/util/cio/socketsessionreceiver_python.cc
@@ -0,0 +1,327 @@
+// 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.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <boost/lexical_cast.hpp>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <util/io/socketsession.h>
+
+#include "socketsession_python.h"
+#include "socketsessionreceiver_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::util::io;
+using namespace isc::util::io::python;
+using boost::lexical_cast;
+
+// Trivial constructor.
+s_SocketSessionReceiver::s_SocketSessionReceiver() : cppobj(NULL) {
+}
+
+// Import pydoc text
+#include "socketsessionreceiver_inc.cc"
+
+namespace {
+// This C structure corresponds to a Python callable object for
+// socket.fromfd().
+// See json_dumps_obj in dns_requestloader_python.cc for background rationale
+// of this trick.
+PyObject* socket_fromfd_obj = NULL;
+
+int
+SocketSessionReceiver_init(PyObject* po_self, PyObject* args, PyObject*) {
+ s_SocketSessionReceiver* self =
+ static_cast<s_SocketSessionReceiver*>(po_self);
+ try {
+ // The constructor expects a Python socket object. We'll extract
+ // the underlying file descriptor using the fileno method (in the
+ // duck typing manner) and pass it to the C++ constructor.
+ // PyObject_CallMethod() could return NULL (especially if the given
+ // object is of the wrong type and doesn't have the "fileno" method),
+ // in which case PyObjectContainer will detect it and throw
+ // PyCPPWrapperException, which will be converted to the Python
+ // TypeError below.
+ PyObject* po_sock;
+ if (PyArg_ParseTuple(args, "O", &po_sock)) {
+ PyObjectContainer fd_container(PyObject_CallMethod(
+ po_sock,
+ const_cast<char*>("fileno"),
+ NULL));
+ PyObjectContainer fdarg_container(
+ Py_BuildValue("(O)", fd_container.get()));
+ int fd;
+ if (PyArg_ParseTuple(fdarg_container.get(), "i", &fd)) {
+ self->cppobj = new SocketSessionReceiver(fd);
+ return (0);
+ }
+ PyErr_SetString(PyExc_TypeError, "Given object's fileno() doesn't "
+ "return an integer, probably not a valid socket "
+ "object");
+ }
+ } catch (const PyCPPWrapperException& ex) {
+ // This could happen due to memory allocation failure, but it's more
+ // likely that the object doesn't have the "fileno()" method or it
+ // returns an unexpected type of value. So we adjust the error
+ // message accordingly.
+ PyErr_SetString(PyExc_TypeError, "Failed to parse parameter, "
+ "probably not a valid socket object");
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Failed to construct SocketSessionReceiver object: " +
+ string(ex.what());
+ PyErr_SetString(po_SocketSessionError, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ }
+
+ return (-1);
+}
+
+PyObject*
+createPySocketAddress(const struct sockaddr& sa) {
+ socklen_t salen;
+ if (sa.sa_family == AF_INET) {
+ salen = sizeof(struct sockaddr_in);
+ } else if (sa.sa_family == AF_INET6) {
+ salen = sizeof(struct sockaddr_in6);
+ } else {
+ isc_throw(SocketSessionError, "Unsupported socket address family: "
+ << static_cast<int>(sa.sa_family));
+ }
+
+ char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
+ const int error = getnameinfo(&sa, salen, hbuf, sizeof(hbuf), sbuf,
+ sizeof(sbuf),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (error != 0) {
+ isc_throw(SocketSessionError, "Unrecognized socket address format: "
+ << gai_strerror(error));
+ }
+ if (sa.sa_family == AF_INET) {
+ return (Py_BuildValue("(si)", hbuf, lexical_cast<int>(sbuf)));
+ }
+ // We know it's AF_INET6 at this point. We need some special trick for
+ // non-0 scope (zone) ID: getnameinfo() may convert the address to a
+ // textual representation using the extension described in RFC 4007,
+ // in which case it contains a delimiter character '%'. We need to remove
+ // it before constructing the tuple. The scope (zone) ID is preserved
+ // in the corresponding field of the tuple.
+ const void* p = &sa;
+ const struct sockaddr_in6* sin6 =
+ static_cast<const struct sockaddr_in6*>(p);
+ char* cp = strchr(hbuf, '%');
+ if (cp != NULL) {
+ *cp = '\0';
+ }
+ return (Py_BuildValue("(siii)", hbuf, lexical_cast<int>(sbuf), 0,
+ sin6->sin6_scope_id));
+}
+
+void
+SocketSessionReceiver_destroy(PyObject* po_self) {
+ s_SocketSessionReceiver* self =
+ static_cast<s_SocketSessionReceiver*>(po_self);
+ delete self->cppobj;
+ self->cppobj = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+// A helper struct to automatically close a socket in an RAII manner.
+struct ScopedSocket : boost::noncopyable {
+ ScopedSocket(int fd) : fd_(fd) {}
+ ~ScopedSocket() {
+ close(fd_);
+ }
+ const int fd_;
+};
+
+PyObject*
+SocketSessionReceiver_pop(PyObject* po_self, PyObject*) {
+ s_SocketSessionReceiver* const self =
+ static_cast<s_SocketSessionReceiver*>(po_self);
+
+ try {
+ // retrieve the session, and the convert it to a corresponding
+ // Python tuple.
+ const SocketSession session = self->cppobj->pop();
+
+ // We need to immediately store the socket file descriptor in a
+ // ScopedSocket object. socket.fromfd() will dup() the FD, so we need
+ // to close our copy even if an exception is thrown.
+ ScopedSocket sock(session.getSocket());
+
+ // Build Python socket object
+ PyObjectContainer c_args(Py_BuildValue("(iiii)", sock.fd_,
+ session.getFamily(),
+ session.getType(),
+ session.getProtocol()));
+ PyObjectContainer c_sock(PyObject_CallObject(socket_fromfd_obj,
+ c_args.get()));
+ // Convert the local and remote sockaddr to Python socket address objs
+ PyObjectContainer c_local(createPySocketAddress(
+ session.getLocalEndpoint()));
+ PyObjectContainer c_remote(createPySocketAddress(
+ session.getRemoteEndpoint()));
+ // Convert the session data to Python byte object.
+ PyObjectContainer c_data(Py_BuildValue("y#", session.getData(),
+ session.getDataLength()));
+
+ // Build a tuple from them and return it.
+ return (Py_BuildValue("(OOOO)", c_sock.get(), c_local.get(),
+ c_remote.get(), c_data.get()));
+ } catch (const SocketSessionError& ex) {
+ PyErr_SetString(po_SocketSessionError, ex.what());
+ return (NULL);
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure in receiving a socket session: " +
+ string(ex.what());
+ PyErr_SetString(PyExc_SystemError, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (NULL);
+ }
+}
+
+// These are the functions we export
+
+// 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 SocketSessionReceiver_methods[] = {
+ { "pop", SocketSessionReceiver_pop, METH_NOARGS,
+ SocketSessionReceiver_pop_doc },
+ { NULL, NULL, 0, NULL }
+};
+} // end of unnamed namespace
+
+namespace isc {
+namespace util {
+namespace io {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_SocketSessionReceiver
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject socketsessionreceiver_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "isc.util.cio.SocketSessionReceiver",
+ sizeof(s_SocketSessionReceiver), // tp_basicsize
+ 0, // tp_itemsize
+ SocketSessionReceiver_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
+ SocketSessionReceiver_doc,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ SocketSessionReceiver_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
+ SocketSessionReceiver_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_SocketSessionReceiver(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(&socketsessionreceiver_type) < 0) {
+ return (false);
+ }
+ void* p = &socketsessionreceiver_type;
+ if (PyModule_AddObject(mod, "SocketSessionReceiver",
+ static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+
+ PyObject* socket_module = PyImport_AddModule("socket");
+ if (socket_module != NULL) {
+ PyObject* socket_dict = PyModule_GetDict(socket_module);
+ if (socket_dict != NULL) {
+ socket_fromfd_obj = PyDict_GetItemString(socket_dict, "fromfd");
+ }
+ }
+ if (socket_fromfd_obj != NULL) {
+ Py_INCREF(socket_fromfd_obj);
+ } else {
+ PyErr_SetString(PyExc_RuntimeError,
+ "isc.util.cio.SocketSessionReceiver needs "
+ "socket.fromfd(), but it's missing");
+ return (false);
+ }
+
+ Py_INCREF(&socketsessionreceiver_type);
+
+ return (true);
+}
+
+} // namespace python
+} // namespace io
+} // namespace util
+} // namespace isc
diff --git a/src/lib/python/isc/util/cio/socketsessionreceiver_python.h b/src/lib/python/isc/util/cio/socketsessionreceiver_python.h
new file mode 100644
index 0000000..14e8a1b
--- /dev/null
+++ b/src/lib/python/isc/util/cio/socketsessionreceiver_python.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 __PYTHON_SOCKETSESSIONRECEIVER_H
+#define __PYTHON_SOCKETSESSIONRECEIVER_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace util {
+namespace io {
+class SocketSessionReceiver;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_SocketSessionReceiver : public PyObject {
+public:
+ s_SocketSessionReceiver();
+ SocketSessionReceiver* cppobj;
+};
+
+extern PyTypeObject socketsessionreceiver_type;
+
+bool initModulePart_SocketSessionReceiver(PyObject* mod);
+
+} // namespace io
+} // namespace python
+} // namespace util
+} // namespace isc
+#endif // __PYTHON_SOCKETSESSIONRECEIVER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/python/isc/util/cio/tests/Makefile.am b/src/lib/python/isc/util/cio/tests/Makefile.am
new file mode 100644
index 0000000..3429009
--- /dev/null
+++ b/src/lib/python/isc/util/cio/tests/Makefile.am
@@ -0,0 +1,36 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = socketsession_test.py
+EXTRA_DIST = $(PYTESTS)
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+LIBRARY_PATH_PLACEHOLDER =
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+# Note: below we intentionally use a non absolute path for TESTDATAOBJDIR.
+# It will be used as part of the path for a UNIX domain socket. Due to the
+# relatively lower limit on the length it's better to keep it as short as
+# possible.
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/isc/python/util/io/.libs \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ TESTDATAOBJDIR=$(builddir) \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
+
+CLEANFILES = $(builddir)/ssessiontest.unix
+
+CLEANDIRS = __pycache__
+
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/util/cio/tests/socketsession_test.py b/src/lib/python/isc/util/cio/tests/socketsession_test.py
new file mode 100644
index 0000000..66b43d5
--- /dev/null
+++ b/src/lib/python/isc/util/cio/tests/socketsession_test.py
@@ -0,0 +1,253 @@
+# 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.
+
+import os, signal, socket, unittest
+from socket import AF_INET, AF_INET6, SOCK_STREAM, SOCK_DGRAM, IPPROTO_UDP, \
+ IPPROTO_TCP
+from isc.util.cio.socketsession import *
+
+TESTDATA_OBJDIR = os.getenv("TESTDATAOBJDIR")
+TEST_UNIX_FILE = TESTDATA_OBJDIR + '/ssessiontest.unix'
+TEST_DATA = b'BIND10 test'
+TEST_PORT = 53535
+
+class TestForwarder(unittest.TestCase):
+ '''In general, this is a straightforward port of the C++ counterpart.
+
+ In some cases test cases are simplified or have Python specific cases.
+
+ '''
+
+ def setUp(self):
+ self.forwarder = SocketSessionForwarder(TEST_UNIX_FILE)
+ if os.path.exists(TEST_UNIX_FILE):
+ os.unlink(TEST_UNIX_FILE)
+ self.large_text = b'a' * 65535
+
+ def tearDown(self):
+ if os.path.exists(TEST_UNIX_FILE):
+ os.unlink(TEST_UNIX_FILE)
+
+ def start_listen(self):
+ self.listen_sock = socket.socket(socket.AF_UNIX, SOCK_STREAM, 0)
+ self.listen_sock.bind(TEST_UNIX_FILE)
+ self.listen_sock.listen(10)
+
+ def accept_forwarder(self):
+ self.listen_sock.setblocking(False)
+ s, _ = self.listen_sock.accept()
+ s.setblocking(True)
+ return s
+
+ def test_init(self):
+ # check bad arguments. valid cases will covered in other tests.
+ self.assertRaises(TypeError, SocketSessionForwarder, 1)
+ self.assertRaises(TypeError, SocketSessionForwarder,
+ 'test.unix', 'test.unix')
+
+ def test_badpush(self):
+ # bad numbers of parameters
+ self.assertRaises(TypeError, self.forwarder.push, 1)
+ self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
+ SOCK_DGRAM, IPPROTO_UDP, ('127.0.0.1', 53),
+ ('192.0.2.1', 5300), TEST_DATA, 0)
+ # contain a bad type of parameter
+ self.assertRaises(TypeError, self.forwarder.push, 0, 'AF_INET',
+ SOCK_DGRAM, IPPROTO_UDP, ('127.0.0.1', 53),
+ ('192.0.2.1', 5300), TEST_DATA)
+ # bad local address
+ self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
+ SOCK_DGRAM, IPPROTO_UDP, ('127.0.0..1', 53),
+ ('192.0.2.1', 5300), TEST_DATA)
+ self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
+ SOCK_DGRAM, IPPROTO_UDP, '127.0.0.1',
+ ('192.0.2.1', 5300), TEST_DATA)
+ # bad remote address
+ self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET6,
+ SOCK_DGRAM, IPPROTO_UDP, ('2001:db8::1', 53),
+ ('2001:db8:::3', 5300), TEST_DATA)
+
+ # push before connect
+ self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
+ SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
+ ('192.0.2.2', 53), TEST_DATA)
+
+ # Now connect the forwarder for the rest of tests
+ self.start_listen()
+ self.forwarder.connect_to_receiver()
+
+ # Inconsistent address family
+ self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET,
+ SOCK_DGRAM, IPPROTO_UDP, ('2001:db8::1', 53, 0, 1),
+ ('192.0.2.2', 53), TEST_DATA)
+ self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET6,
+ SOCK_DGRAM, IPPROTO_UDP, ('2001:db8::1', 53, 0, 1),
+ ('192.0.2.2', 53), TEST_DATA)
+
+ # Empty data: we reject them at least for now
+ self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET,
+ SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
+ ('192.0.2.2', 53), b'')
+
+ # Too big data: we reject them at least for now
+ self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET,
+ SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
+ ('192.0.2.2', 53), b'd' * 65536)
+
+ # Close the receptor before push. It will result in SIGPIPE (should be
+ # ignored) and EPIPE, which will be converted to SocketSessionError.
+ self.listen_sock.close()
+ self.assertRaises(SocketSessionError, self.forwarder.push, 1, AF_INET,
+ SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
+ ('192.0.2.2', 53), TEST_DATA)
+
+ def create_socket(self, family, type, protocol, addr, do_listen):
+ s = socket.socket(family, type, protocol)
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ s.bind(addr)
+ if do_listen and protocol == IPPROTO_TCP:
+ s.listen(1)
+ return s
+
+ def check_push_and_pop(self, family, type, protocol, local, remote,
+ data, new_connection):
+ sock = self.create_socket(family, type, protocol, local, True)
+ fwd_fd = sock.fileno()
+ if protocol == IPPROTO_TCP:
+ client_addr = ('::1', 0, 0, 0) if family == AF_INET6 \
+ else ('127.0.0.1', 0)
+ client_sock = self.create_socket(family, type, protocol,
+ client_addr, False)
+ client_sock.setblocking(False)
+ try:
+ client_sock.connect(local)
+ except socket.error:
+ pass
+ server_sock, _ = sock.accept()
+ fwd_fd = server_sock.fileno()
+
+ # If a new connection is required, start the "server", have the
+ # internal forwarder connect to it, and then internally accept it.
+ if new_connection:
+ self.start_listen()
+ self.forwarder.connect_to_receiver()
+ self.accept_sock = self.accept_forwarder()
+
+ # Then push one socket session via the forwarder.
+ self.forwarder.push(fwd_fd, family, type, protocol, local, remote,
+ data)
+
+ # Pop the socket session we just pushed from a local receiver, and
+ # check the content.
+ receiver = SocketSessionReceiver(self.accept_sock)
+ signal.alarm(1)
+ sock_session = receiver.pop()
+ signal.alarm(0)
+ passed_sock = sock_session[0]
+ self.assertNotEqual(fwd_fd, passed_sock.fileno())
+ self.assertEqual(family, passed_sock.family)
+ self.assertEqual(type, passed_sock.type)
+ self.assertEqual(protocol, passed_sock.proto)
+ self.assertEqual(local, sock_session[1])
+ self.assertEqual(remote, sock_session[2])
+ self.assertEqual(data, sock_session[3])
+
+ # Check if the passed FD is usable by sending some data from it.
+ passed_sock.setblocking(True)
+ if protocol == IPPROTO_UDP:
+ self.assertEqual(len(TEST_DATA), passed_sock.sendto(TEST_DATA,
+ local))
+ sock.settimeout(10)
+ self.assertEqual(TEST_DATA, sock.recvfrom(len(TEST_DATA))[0])
+ else:
+ server_sock.close()
+ self.assertEqual(len(TEST_DATA), passed_sock.send(TEST_DATA))
+ client_sock.setblocking(True)
+ client_sock.settimeout(10)
+ self.assertEqual(TEST_DATA, client_sock.recv(len(TEST_DATA)))
+
+ def test_push_and_pop(self):
+ # This is a straightforward port of C++ pushAndPop test.
+ local6 = ('::1', TEST_PORT, 0, 0)
+ remote6 = ('2001:db8::1', 5300, 0, 0)
+ self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
+ local6, remote6, TEST_DATA, True)
+ self.check_push_and_pop(AF_INET6, SOCK_STREAM, IPPROTO_TCP,
+ local6, remote6, TEST_DATA, False)
+
+ local4 = ('127.0.0.1', TEST_PORT)
+ remote4 = ('192.0.2.2', 5300)
+ self.check_push_and_pop(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ local4, remote4, TEST_DATA, False)
+ self.check_push_and_pop(AF_INET, SOCK_STREAM, IPPROTO_TCP,
+ local4, remote4, TEST_DATA, False)
+
+ self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
+ local6, remote6, self.large_text, False)
+ self.check_push_and_pop(AF_INET6, SOCK_STREAM, IPPROTO_TCP,
+ local6, remote6, self.large_text, False)
+ self.check_push_and_pop(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ local4, remote4, self.large_text, False)
+ self.check_push_and_pop(AF_INET, SOCK_STREAM, IPPROTO_TCP,
+ local4, remote4, self.large_text, False)
+
+ # Python specific: check for an IPv6 scoped address with non 0
+ # scope (zone) ID
+ scope6 = ('fe80::1', TEST_PORT, 0, 1)
+ self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
+ local6, scope6, TEST_DATA, False)
+
+ def test_push_too_fast(self):
+ # A straightforward port of C++ pushTooFast test.
+ def multi_push(forwarder, addr, data):
+ for i in range(0, 10):
+ forwarder.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP, addr,
+ addr, data)
+ self.start_listen()
+ self.forwarder.connect_to_receiver()
+ self.assertRaises(SocketSessionError, multi_push, self.forwarder,
+ ('192.0.2.1', 53), self.large_text)
+
+ def test_bad_pop(self):
+ # This is a subset of C++ badPop test. We only check pop() raises
+ # SocketSessionError when it internally fails to get the FD.
+ # Other cases would require passing a valid FD from the test,
+ # which would make the test too complicated. As a wrapper checking
+ # one common failure case should be reasonably sufficient.
+
+ self.start_listen()
+ s = socket.socket(socket.AF_UNIX, SOCK_STREAM, 0)
+ s.setblocking(False)
+ s.connect(TEST_UNIX_FILE)
+ accept_sock = self.accept_forwarder()
+ receiver = SocketSessionReceiver(accept_sock)
+ s.close()
+ self.assertRaises(SocketSessionError, receiver.pop)
+
+class TestReceiver(unittest.TestCase):
+ # We only check a couple of failure cases on construction. Valid cases
+ # are covered in TestForwarder.
+
+ def test_bad_init(self):
+ class FakeSocket:
+ # pretending to be th standard socket class, but its fileno() is
+ # bogus.
+ def fileno(self):
+ return None
+ self.assertRaises(TypeError, SocketSessionReceiver, 1)
+ self.assertRaises(TypeError, SocketSessionReceiver, FakeSocket())
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/lib/python/isc/util/io/Makefile.am b/src/lib/python/isc/util/io/Makefile.am
deleted file mode 100644
index 6e2b263..0000000
--- a/src/lib/python/isc/util/io/Makefile.am
+++ /dev/null
@@ -1,41 +0,0 @@
-SUBDIRS = . tests
-
-AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
-AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CXXFLAGS = $(B10_CXXFLAGS)
-
-python_PYTHON = __init__.py
-pythondir = $(PYTHON_SITEPKG_DIR)/isc/util/io
-
-pyexec_LTLIBRARIES = socketsession.la
-pyexecdir = $(PYTHON_SITEPKG_DIR)/isc/util/io
-
-socketsession_la_SOURCES = socketsession_python.cc socketsession_python.h
-socketsession_la_SOURCES += socketsessionforwarder_python.cc
-socketsession_la_SOURCES += socketsessionforwarder_python.h
-socketsession_la_SOURCES += socketsessionreceiver_python.cc
-socketsession_la_SOURCES += socketsessionreceiver_python.h
-socketsession_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
-socketsession_la_LDFLAGS = $(PYTHON_LDFLAGS)
-# Note: PYTHON_CXXFLAGS may have some -Wno... workaround, which must be
-# placed after -Wextra defined in AM_CXXFLAGS
-socketsession_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
-
-# Python prefers .so, while some OSes (specifically MacOS) use a different
-# suffix for dynamic objects. -module is necessary to work this around.
-socketsession_la_LDFLAGS += -module
-socketsession_la_LIBADD = $(top_builddir)/src/lib/util/io/libutil_io.la
-socketsession_la_LIBADD += $(PYTHON_LIB)
-
-# This is not installed, it helps locate the module during tests
-EXTRA_DIST = __init__.py socketsession.py
-
-EXTRA_DIST += socketsession_inc.cc
-EXTRA_DIST += socketsessionforwarder_inc.cc socketsessionreceiver_inc.cc
-
-CLEANFILES = __init__.pyc socketsession.pyc
-
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/util/io/__init__.py b/src/lib/python/isc/util/io/__init__.py
deleted file mode 100644
index 935160a..0000000
--- a/src/lib/python/isc/util/io/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-Here are function and classes for forwarding socket sessions between processes.
-"""
diff --git a/src/lib/python/isc/util/io/socketsession.py b/src/lib/python/isc/util/io/socketsession.py
deleted file mode 100644
index ccb16ec..0000000
--- a/src/lib/python/isc/util/io/socketsession.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# 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.
-
-# This file is not installed. See python/isc/log/__init__.py for the trick.
-
-import os
-import sys
-
-for base in sys.path[:]:
- libdir = os.path.join(base, 'isc/util/io/.libs')
- if os.path.exists(libdir):
- sys.path.insert(0, libdir)
-
-from socketsession import *
diff --git a/src/lib/python/isc/util/io/socketsession_inc.cc b/src/lib/python/isc/util/io/socketsession_inc.cc
deleted file mode 100644
index e200063..0000000
--- a/src/lib/python/isc/util/io/socketsession_inc.cc
+++ /dev/null
@@ -1,122 +0,0 @@
-namespace {
-const char* const socketsession_doc = "\
-This module defines a set of classes that support forwarding a\n\
-\"socket session\" from one process to another. A socket session is a\n\
-conceptual tuple of the following elements:\n\
-\n\
-- A network socket\n\
-- The local and remote endpoints of a (IP) communication taking place\n\
- on the socket. In practice an endpoint is a pair of an IP address\n\
- and TCP or UDP port number.\n\
-- Some amount of data sent from the remote endpoint and received on\n\
- the socket. We call it (socket) session data in this documentation.\n\
-\n\
-Note that this is a conceptual definition. Depending on the underlying\n\
-implementation and/or the network protocol, some of the elements could\n\
-be part of others; for example, if it's an established TCP connection,\n\
-the local and remote endpoints would be able to be retrieved from the\n\
-socket using the standard getsockname() and getpeername() system\n\
-calls. But in this definition we separate these to be more generic.\n\
-Also, as a matter of fact our intended usage includes non-connected\n\
-UDP communications, in which case at least the remote endpoint should\n\
-be provided separately from the socket.\n\
-\n\
-In the actual implementation we represent a socket as a Python socket\n\
-object, which contains the information of the address family\n\
-(e.g. AF_INET6), socket type (e.g. SOCK_STREAM), and protocol\n\
-(e.g. IPPROTO_TCP).\n\
-\n\
-We use the Python socket address tuple to represent endpoints.\n\
-\n\
-Socket session data is an opaque blob in the form of a Python byte\n\
-object.\n\
-\n\
-To forward a socket session between processes, we use connected UNIX\n\
-domain sockets established between the processes. The file descriptor\n\
-will be forwarded through the sockets as an ancillary data item of\n\
-type SCM_RIGHTS. Other elements of the session will be transferred as\n\
-normal data over the connection.\n\
-\n\
-We provide two classes to help applications forward socket sessions:\n\
-SocketSessionForwarder is the sender of the UNIX domain connection,\n\
-while SocketSessionReceiver is the receiver (this interface assumes\n\
-one direction of forwarding).\n\
-\n\
-Note: this paragraph and following discussions on the internal\n\
-protocol are for reference purposes only; it's not necessary to\n\
-understand how to use the API.\n\
-SocketSessionForwarder and SocketSessionReceiver objects (internally)\n\
-use a straightforward protocol to pass elements of socket sessions.\n\
-Once the connection is established, the forwarder object first forwards\n\
-the file descriptor with 1-byte dummy data. It then forwards a\n\
-\"(socket) session header\", which contains all other elements of\n\
-the session except the file descriptor (already forwarded) and session\n\
-data. The wire format of the header is as follows:\n\
-\n\
-- The length of the header (16-bit unsigned integer)\n\
-- Address family\n\
-- Socket type\n\
-- Protocol\n\
-- Size of the local endpoint in bytes\n\
-- Local endpoint (a copy of the memory image of the corresponding\n\
- sockaddr)\n\
-- Size of the remote endpoint in bytes\n\
-- Remote endpoint (same as local endpoint)\n\
-- Size of session data in bytes\n\
-\n\
-The type of the fields is 32-bit unsigned integer unless explicitly\n\
-noted, and all fields are formatted in the network byte order.\n\
-\n\
-The socket session data immediately follows the session header.\n\
-\n\
-Note that the fields do not necessarily be in the network byte order\n\
-because they are expected to be exchanged on the same machine.\n\
-Likewise, integer elements such as address family do not necessarily\n\
-be represented as an fixed-size value (i.e., 32-bit). But fixed size\n\
-fields are used in order to ensure maximum portability in such a\n\
-(rare) case where the forwarder and the receiver are built with\n\
-different compilers that have different definitions of int. Also,\n\
-since sockaddr fields are generally formatted in the network byte\n\
-order, other fields are defined so to be consistent.\n\
-\n\
-One basic assumption in the API of this module is socket sessions\n\
-should be forwarded without blocking, thus eliminating the need for\n\
-incremental read/write or blocking other important services such as\n\
-responding to requests from the application's clients. This assumption\n\
-should be held as long as both the forwarder and receiver have\n\
-sufficient resources to handle the forwarding process since the\n\
-communication is local. But a forward attempt could still block if the\n\
-receiver is busy (or even hang up) and cannot keep up with the volume\n\
-of incoming sessions.\n\
-\n\
-So, in this implementation, the forwarder uses non blocking writes to\n\
-forward sessions. If a write attempt could block, it immediately gives\n\
-up the operation with an exception. The corresponding application is\n\
-expected to catch it, close the connection, and perform any necessary\n\
-recovery for that application (that would normally be re-establish the\n\
-connection with a new receiver, possibly after confirming the\n\
-receiving side is still alive). On the other hand, the receiver\n\
-implementation assumes it's possible that it only receive incomplete\n\
-elements of a session (such as in the case where the forwarder writes\n\
-part of the entire session and gives up the connection). The receiver\n\
-implementation throws an exception when it encounters an incomplete\n\
-session. Like the case of the forwarder application, the receiver\n\
-application is expected to catch it, close the connection, and perform\n\
-any necessary recovery steps.\n\
-\n\
-Note that the receiver implementation uses blocking read. So it's\n\
-application's responsibility to ensure that there's at least some data\n\
-in the connection when the receiver object is requested to receive a\n\
-session (unless this operation can be blocking, e.g., by the use of a\n\
-separate thread). Also, if the forwarder implementation or application\n\
-is malicious or extremely buggy and intentionally sends partial\n\
-session and keeps the connection, the receiver could block in\n\
-receiving a session. In general, we assume the forwarder doesn't do\n\
-intentional blocking as it's a local node and is generally a module of\n\
-the same (BIND 10) system. The minimum requirement for the forwarder\n\
-implementation (and application) is to make sure the connection is\n\
-closed once it detects an error on it. Even a naive implementation\n\
-that simply dies due to the exception will meet this requirement.\n\
-\n\
-";
-} // unnamed namespace
diff --git a/src/lib/python/isc/util/io/socketsession_python.cc b/src/lib/python/isc/util/io/socketsession_python.cc
deleted file mode 100644
index 7011b53..0000000
--- a/src/lib/python/isc/util/io/socketsession_python.cc
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <Python.h>
-
-#include <util/python/pycppwrapper_util.h>
-
-#include "socketsessionreceiver_python.h"
-#include "socketsessionforwarder_python.h"
-
-using namespace isc::util::io::python;
-using namespace isc::util::python;
-
-#include "socketsession_inc.cc"
-
-namespace isc {
-namespace util {
-namespace io {
-namespace python {
-PyObject* po_SocketSessionError;
-}
-}
-}
-}
-
-namespace {
-
-PyModuleDef socketsession = {
- { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
- "isc.util.io.socketsession",
- socketsession_doc,
- -1,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL
-};
-} // end of unnamed namespace
-
-PyMODINIT_FUNC
-PyInit_socketsession(void) {
- PyObject* mod = PyModule_Create(&socketsession);
- if (mod == NULL) {
- return (NULL);
- }
-
- try {
- po_SocketSessionError =
- PyErr_NewException("isc.util.io.SocketSessionError", NULL, NULL);
- PyObjectContainer(po_SocketSessionError).
- installToModule(mod, "SocketSessionError");
- } catch (...) {
- Py_DECREF(mod);
- return (NULL);
- }
-
- if (!initModulePart_SocketSessionForwarder(mod)) {
- Py_DECREF(mod);
- return (NULL);
- }
- if (!initModulePart_SocketSessionReceiver(mod)) {
- Py_DECREF(mod);
- return (NULL);
- }
-
- return (mod);
-}
diff --git a/src/lib/python/isc/util/io/socketsession_python.h b/src/lib/python/isc/util/io/socketsession_python.h
deleted file mode 100644
index b0703ac..0000000
--- a/src/lib/python/isc/util/io/socketsession_python.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef __PYTHON_SOCKETSESSION_H
-#define __PYTHON_SOCKETSESSION_H 1
-
-#include <Python.h>
-
-namespace isc {
-namespace util {
-namespace io {
-namespace python {
-
-extern PyObject* po_SocketSessionError;
-
-} // namespace python
-} // namespace io
-} // namespace util
-} // namespace isc
-#endif // __PYTHON_SOCKETSESSION_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/python/isc/util/io/socketsessionforwarder_inc.cc b/src/lib/python/isc/util/io/socketsessionforwarder_inc.cc
deleted file mode 100644
index 6b9de01..0000000
--- a/src/lib/python/isc/util/io/socketsessionforwarder_inc.cc
+++ /dev/null
@@ -1,136 +0,0 @@
-namespace {
-// Modifications:
-// reference to the module description (instead of "utility")
-// exception description
-const char* const SocketSessionForwarder_doc = "\
-The forwarder of socket sessions.\n\
-\n\
-An object of this class maintains a UNIX domain socket (normally\n\
-expected to be connected to a SocketSessionReceiver object) and\n\
-forwards socket sessions to the receiver.\n\
-\n\
-See the description of socketsession module for other details of how\n\
-the session forwarding works.\n\
-\n\
-SocketSessionForwarder(unix_file)\n\
-\n\
- The constructor.\n\
-\n\
- It's constructed with path information of the intended receiver,\n\
- but does not immediately establish a connection to the receiver;\n\
- connect_to_receiver() must be called to establish it. These are\n\
- separated so that an object of class can be initialized (possibly\n\
- as an attribute of a higher level application class object)\n\
- without knowing the receiver is ready for accepting new\n\
- forwarders. The separate connect interface allows the object to be\n\
- reused when it detects connection failure and tries to re-\n\
- establish it after closing the failed one.\n\
-\n\
- On construction, it also installs a signal filter for SIGPIPE to\n\
- ignore it. Since this class uses a stream-type connected UNIX\n\
- domain socket, if the receiver (abruptly) closes the connection a\n\
- subsequent write operation on the socket would trigger a SIGPIPE\n\
- signal, which kills the caller process by default. This behavior\n\
- would be undesirable in many cases, so this implementation always\n\
- disables the signal.\n\
-\n\
- This approach has some drawbacks, however; first, since signal\n\
- handling is process (or thread) wide, ignoring it may not what the\n\
- application wants. On the other hand, if the application changes\n\
- how the signal is handled after instantiating this class, the new\n\
- behavior affects the class operation. Secondly, even if ignoring\n\
- the signal is the desired operation, it's a waste to set the\n\
- filter every time this class object is constructed. It's\n\
- sufficient to do it once. We still adopt this behavior based on\n\
- the observation that in most cases applications would like to\n\
- ignore SIGPIPE (or simply doesn't care about it) and that this\n\
- class is not instantiated so often (so the wasteful setting\n\
- overhead should be marginal). On the other hand, doing it every\n\
- time is beneficial if the application is threaded and different\n\
- threads create different forwarder objects (and if signals work\n\
- per thread).\n\
-\n\
- Exceptions:\n\
- SocketSessionError unix_file is invalid as a path name of a UNIX\n\
- domain socket or error happens in setting a filter for\n\
- SIGPIPE (see above)\n\
- SystemError Unexpected errors such as resource allocation failure\n\
-\n\
- Parameters:\n\
- unix_file Path name of the receiver.\n\
-\n\
-";
-
-// Modifications:
-// exception description
-const char* const SocketSessionForwarder_connectToReceiver_doc = "\
-connect_to_receiver()\n\
-\n\
-Establish a connection to the receiver.\n\
-\n\
-This method establishes a connection to the receiver at the path given\n\
-on construction. It makes the underlying UNIX domain socket non\n\
-blocking, so this method (or subsequent push() calls) does not block.\n\
-\n\
-Exceptions:\n\
- TypeError The method is called while an already established\n\
- connection is still active.\n\
- SocketSessionError A system error in socket operation.\n\
- SystemError Unexpected errors such as resource allocation failure\n\
-\n\
-";
-
-// Modifications:
-// bullet description
-// parameters
-// exception description
-const char* const SocketSessionForwarder_push_doc = "\
-push(sock, family, type, protocol, local_end, remote_end, data)\n\
-\n\
-Forward a socket session to the receiver.\n\
-\n\
-This method takes a set of parameters that represent a single socket\n\
-session, renders them in the \"wire\" format according to the internal\n\
-protocol (see socketsession module) and forwards them to the\n\
-receiver through the UNIX domain connection.\n\
-\n\
-The connection must have been established by connect_to_receiver().\n\
-\n\
-For simplicity and for the convenience of detecting application\n\
-errors, this method imposes some restrictions on the parameters:\n\
-\n\
-- Socket family must be either AF_INET or AF_INET6\n\
-- The address family (sa_family) member of the local and remote end\n\
- points must be equal to the family parameter\n\
-- Socket session data must not be empty\n\
-- Data length must not exceed 65535\n\
-\n\
-These are not architectural limitation, and might be loosened in future\n\
-versions as we see the need for flexibility.\n\
-\n\
-Since the underlying UNIX domain socket is non blocking (see the\n\
-description for the constructor), a call to this method should either\n\
-return immediately or result in exception (in case of \"would\n\
-block\").\n\
-\n\
-Exceptions:\n\
- TypeError The method is called before establishing a connection or\n\
- given parameters are invalid, or the given socket address\n\
- is valid.\n\
- SocketSessionError A system error in socket operation, including the\n\
- case where the write operation would block.\n\
-\n\
-Parameters:\n\
- sock (int) The socket file descriptor\n\
- family (int) The address family (such as socket.AF_INET6) of the\n\
- socket\n\
- type (int) The socket type (such as socket.SOCK_DGRAM) of the\n\
- socket\n\
- protocol (int) The transport protocol (such as socket.IPPROTO_UDP)\n\
- of the socket\n\
- local_end (socket address) The local end point of the session\n\
- remote_end (socket address) The remote end point of the session\n\
- data (byte) the session data\n\
-\n\
-";
-} // unnamed namespace
diff --git a/src/lib/python/isc/util/io/socketsessionforwarder_python.cc b/src/lib/python/isc/util/io/socketsessionforwarder_python.cc
deleted file mode 100644
index bdc2bf7..0000000
--- a/src/lib/python/isc/util/io/socketsessionforwarder_python.cc
+++ /dev/null
@@ -1,309 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-// Enable this if you use s# variants with PyArg_ParseTuple(), see
-// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
-//#define PY_SSIZE_T_CLEAN
-
-// Python.h needs to be placed at the head of the program file, see:
-// http://docs.python.org/py3k/extending/extending.html#a-simple-example
-#include <Python.h>
-
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netdb.h>
-
-#include <string>
-#include <stdexcept>
-
-#include <boost/lexical_cast.hpp>
-
-#include <exceptions/exceptions.h>
-
-#include <util/io/sockaddr_util.h>
-#include <util/io/socketsession.h>
-#include <util/python/pycppwrapper_util.h>
-
-#include "socketsession_python.h"
-#include "socketsessionforwarder_python.h"
-
-using namespace std;
-using namespace isc::util::python;
-using namespace isc::util::io;
-using namespace isc::util::io::internal;
-using namespace isc::util::io::python;
-using boost::lexical_cast;
-
-// Trivial constructor.
-s_SocketSessionForwarder::s_SocketSessionForwarder() : cppobj(NULL) {
-}
-
-// Import pydoc text
-#include "socketsessionforwarder_inc.cc"
-
-// See python/isc/log/log.cc for the use of namespace
-namespace clang_unnamed_namespace_workaround {
-// Internal exception class thrown when address parsing fails
-class AddressParseError: public isc::Exception {
-public:
- AddressParseError(const char *file, size_t line, const char *what):
- isc::Exception(file, line, what) {}
-};
-}
-using namespace clang_unnamed_namespace_workaround;
-
-namespace {
-
-int
-SocketSessionForwarder_init(PyObject* po_self, PyObject* args, PyObject*) {
- s_SocketSessionForwarder* self =
- static_cast<s_SocketSessionForwarder*>(po_self);
- try {
- const char* unix_file;
- if (PyArg_ParseTuple(args, "s", &unix_file)) {
- self->cppobj = new SocketSessionForwarder(unix_file);
- return (0);
- }
- } catch (const exception& ex) {
- const string ex_what =
- "Failed to construct SocketSessionForwarder object: " +
- string(ex.what());
- PyErr_SetString(po_SocketSessionError, ex_what.c_str());
- return (-1);
- } catch (...) {
- PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
- return (-1);
- }
-
- return (-1);
-}
-
-void
-SocketSessionForwarder_destroy(PyObject* po_self) {
- s_SocketSessionForwarder* self =
- static_cast<s_SocketSessionForwarder*>(po_self);
- delete self->cppobj;
- self->cppobj = NULL;
- Py_TYPE(self)->tp_free(self);
-}
-
-// Convert a Python socket address object to an addrinfo structure by
-// getaddrinfo.
-void
-parsePySocketAddress(PyObject* obj, int type, int protocol,
- struct sockaddr_storage* ss)
-{
- struct addrinfo hints;
- memset(&hints, 0, sizeof(hints));
- hints.ai_socktype = type;
- hints.ai_protocol = protocol;
- hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
-
- const char* addr;
- int port, flowinfo, scopeid;
- struct addrinfo *res;
- if (PyArg_ParseTuple(obj, "si", &addr, &port)) {
- // Possibly an IPv4 address.
- hints.ai_family = AF_INET;
- const int error = getaddrinfo(addr,
- lexical_cast<string>(port).c_str(),
- &hints, &res);
- if (error == 0) {
- assert(res->ai_addrlen <= sizeof(*ss));
- memcpy(ss, res->ai_addr, res->ai_addrlen);
- return;
- }
- isc_throw(AddressParseError, "Invalid or unsupported socket address: "
- << gai_strerror(error));
- }
- PyErr_Clear();
- if (PyArg_ParseTuple(obj, "siii", &addr, &port, &flowinfo, &scopeid)) {
- // Possibly an IPv6 address. We ignore flowinfo.
- hints.ai_family = AF_INET6;
- const int error = getaddrinfo(addr,
- lexical_cast<string>(port).c_str(),
- &hints, &res);
- if (error == 0) {
- assert(res->ai_addrlen <= sizeof(*ss));
- memcpy(ss, res->ai_addr, res->ai_addrlen);
- void* p = ss;
- static_cast<struct sockaddr_in6*>(p)->sin6_scope_id = scopeid;
- return;
- }
- isc_throw(AddressParseError, "Invalid or unsupported socket address: "
- << gai_strerror(error));
- }
- PyErr_Clear();
- isc_throw(AddressParseError, "Invalid or unsupported socket address, must "
- "be AF_INET or AF_INET6 socket address.");
-}
-
-PyObject*
-SocketSessionForwarder_connectToReceiver(PyObject* po_self, PyObject*) {
- s_SocketSessionForwarder* const self =
- static_cast<s_SocketSessionForwarder*>(po_self);
-
- try {
- self->cppobj->connectToReceiver();
- Py_RETURN_NONE;
- } catch (const isc::BadValue& ex) {
- PyErr_SetString(PyExc_TypeError, ex.what());
- return (NULL);
- } catch (const SocketSessionError& ex) {
- PyErr_SetString(po_SocketSessionError, ex.what());
- return (NULL);
- } catch (const exception& ex) {
- const string ex_what =
- "Unexpected failure in connecting to receiver: " +
- string(ex.what());
- PyErr_SetString(PyExc_SystemError, ex_what.c_str());
- return (NULL);
- } catch (...) {
- PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
- return (NULL);
- }
-}
-
-PyObject*
-SocketSessionForwarder_push(PyObject* po_self, PyObject* args) {
- s_SocketSessionForwarder* const self =
- static_cast<s_SocketSessionForwarder*>(po_self);
-
- try {
- int fd, family, type, protocol;
- PyObject* po_local_end;
- PyObject* po_remote_end;
- Py_buffer py_buf;
-
- if (!PyArg_ParseTuple(args, "iiiiOOy*", &fd, &family, &type, &protocol,
- &po_local_end, &po_remote_end, &py_buf)) {
- return (NULL);
- }
- struct sockaddr_storage ss_local, ss_remote;
- parsePySocketAddress(po_local_end, type, protocol, &ss_local);
- parsePySocketAddress(po_remote_end, type, protocol, &ss_remote);
- self->cppobj->push(fd, family, type, protocol,
- *convertSockAddr(&ss_local),
- *convertSockAddr(&ss_remote),
- py_buf.buf, py_buf.len);
- Py_RETURN_NONE;
- } catch (const AddressParseError& ex) {
- PyErr_SetString(PyExc_TypeError, ex.what());
- return (NULL);
- } catch (const isc::BadValue& ex) {
- PyErr_SetString(PyExc_TypeError, ex.what());
- return (NULL);
- } catch (const SocketSessionError& ex) {
- PyErr_SetString(po_SocketSessionError, ex.what());
- return (NULL);
- } catch (...) {
- PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
- return (NULL);
- }
-}
-
-// This list contains the actual set of functions we have in
-// python. Each entry has
-// 1. Python method name
-// 2. Our static function here
-// 3. Argument type
-// 4. Documentation
-PyMethodDef SocketSessionForwarder_methods[] = {
- { "push", SocketSessionForwarder_push, METH_VARARGS,
- SocketSessionForwarder_push_doc },
- { "connect_to_receiver", SocketSessionForwarder_connectToReceiver,
- METH_NOARGS, SocketSessionForwarder_connectToReceiver_doc },
- { NULL, NULL, 0, NULL }
-};
-} // end of unnamed namespace
-
-namespace isc {
-namespace util {
-namespace io {
-namespace python {
-// This defines the complete type for reflection in python and
-// parsing of PyObject* to s_SocketSessionForwarder
-// Most of the functions are not actually implemented and NULL here.
-PyTypeObject socketsessionforwarder_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "isc.util.io.SocketSessionForwarder",
- sizeof(s_SocketSessionForwarder), // tp_basicsize
- 0, // tp_itemsize
- SocketSessionForwarder_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
- SocketSessionForwarder_doc,
- NULL, // tp_traverse
- NULL, // tp_clear
- NULL, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- SocketSessionForwarder_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
- SocketSessionForwarder_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
-// Module Initialization, all statics are initialized here
-bool
-initModulePart_SocketSessionForwarder(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(&socketsessionforwarder_type) < 0) {
- return (false);
- }
- void* p = &socketsessionforwarder_type;
- if (PyModule_AddObject(mod, "SocketSessionForwarder",
- static_cast<PyObject*>(p)) < 0) {
- return (false);
- }
- Py_INCREF(&socketsessionforwarder_type);
-
- return (true);
-}
-} // namespace python
-} // namespace io
-} // namespace util
-} // namespace isc
diff --git a/src/lib/python/isc/util/io/socketsessionforwarder_python.h b/src/lib/python/isc/util/io/socketsessionforwarder_python.h
deleted file mode 100644
index 2ce220a..0000000
--- a/src/lib/python/isc/util/io/socketsessionforwarder_python.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef __PYTHON_SOCKETSESSIONFORWARDER_H
-#define __PYTHON_SOCKETSESSIONFORWARDER_H 1
-
-#include <Python.h>
-
-namespace isc {
-namespace util {
-namespace io {
-class SocketSessionForwarder;
-
-namespace python {
-
-// The s_* Class simply covers one instantiation of the object
-class s_SocketSessionForwarder : public PyObject {
-public:
- s_SocketSessionForwarder();
- SocketSessionForwarder* cppobj;
-};
-
-extern PyTypeObject socketsessionforwarder_type;
-
-bool initModulePart_SocketSessionForwarder(PyObject* mod);
-} // namespace python
-} // namespace io
-} // namespace util
-} // namespace isc
-#endif // __PYTHON_SOCKETSESSIONFORWARDER_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/python/isc/util/io/socketsessionreceiver_inc.cc b/src/lib/python/isc/util/io/socketsessionreceiver_inc.cc
deleted file mode 100644
index ed29d3e..0000000
--- a/src/lib/python/isc/util/io/socketsessionreceiver_inc.cc
+++ /dev/null
@@ -1,89 +0,0 @@
-namespace {
-// Modifications
-// - about return value
-// - socket session "utility" => module
-const char* const SocketSessionReceiver_doc = "\
-The receiver of socket sessions.\n\
-\n\
-An object of this class holds a UNIX domain socket for an established\n\
-connection, receives socket sessions from the remote forwarder, and\n\
-provides the session to the application as a tuple of corresponding\n\
-elements.\n\
-\n\
-Note that this class is instantiated with an already connected socket;\n\
-it's not a listening socket that is accepting connection requests from\n\
-forwarders. It's application's responsibility to create the listening\n\
-socket, listen on it and accept connections. Once the connection is\n\
-established, the application would construct a SocketSessionReceiver\n\
-object with the socket for the newly established connection. This\n\
-behavior is based on the design decision that the application should\n\
-decide when it performs (possibly) blocking operations (see\n\
-socketsession module for more details).\n\
-\n\
-See the description of socketsession module for other details of how\n\
-the session forwarding works.\n\
-\n\
-SocketSessionReceiver(socket)\n\
-\n\
- The constructor.\n\
-\n\
- Exceptions:\n\
- TypeError The given parameter is not a valid socket object\n\
- SocketSessionError Any error on an operation that is performed\n\
- on the given socket as part of initialization.\n\
- SystemError Unexpected errors such as resource allocation failure\n\
-\n\
- Parameters:\n\
- socket A python socket object of a UNIX domain family for an\n\
- established connection with a forwarder.\n\
-\n\
-";
-
-// Modifications
-// - socket session utility -> module
-// - return value (not a SocketSession object, but a Python tuple)
-// - remove the validity note (we copy it here, so there's no such
-// restriction)
-// - caller's responsibility: only responsible for closing the socket.
-// - text around the bullets
-// - exception
-const char* const SocketSessionReceiver_pop_doc = "\
-pop() -> (socket, socket address, socket address, byte)\n\
-\n\
-Receive a socket session from the forwarder.\n\
-\n\
-This method receives wire-format data (see socketsession module) for\n\
-a socket session on the UNIX domain socket, performs some validation\n\
-on the data, and returns the session information as a tuple.\n\
-\n\
-The caller is responsible for closing the received socket.\n\
-\n\
-It ensures the following:\n\
-\n\
-- The socket's address family is either AF_INET or AF_INET6\n\
-- The family element of the socket addresses for the local and remote\n\
- end points must be equal to the socket's address family\n\
-- The socket session data is not empty and does not exceed 65535\n\
- bytes.\n\
-\n\
-If the validation fails or an unexpected system error happens\n\
-(including a connection close in the meddle of reception), it throws\n\
-an SocketSessionError exception. When this happens, it's very\n\
-unlikely that a subsequent call to this method succeeds, so in\n\
-reality the application is expected to destruct it and close the\n\
-socket in such a case.\n\
-\n\
-Exceptions:\n\
- SocketSessionError Invalid data is received or a system error on\n\
- socket operation happens.\n\
- SystemError Unexpected errors such as resource allocation failure\n\
-\n\
-Return Value(s): A tuple corresponding to the extracted socket session:\n\
- socket A Python socket object corresponding to the socket passed\n\
- by the forwarder\n\
- socket address A Python socket address (which is a tuple) for the local\n\
- end point\n\
- socket address A Python socket address for the remote endpoint\n\
- data A Python byte object that stores the session data\n\
-";
-} // unnamed namespace
diff --git a/src/lib/python/isc/util/io/socketsessionreceiver_python.cc b/src/lib/python/isc/util/io/socketsessionreceiver_python.cc
deleted file mode 100644
index 24e3021..0000000
--- a/src/lib/python/isc/util/io/socketsessionreceiver_python.cc
+++ /dev/null
@@ -1,327 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-// Enable this if you use s# variants with PyArg_ParseTuple(), see
-// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
-//#define PY_SSIZE_T_CLEAN
-
-// Python.h needs to be placed at the head of the program file, see:
-// http://docs.python.org/py3k/extending/extending.html#a-simple-example
-#include <Python.h>
-
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netdb.h>
-
-#include <string>
-#include <stdexcept>
-
-#include <boost/lexical_cast.hpp>
-
-#include <util/python/pycppwrapper_util.h>
-
-#include <util/io/socketsession.h>
-
-#include "socketsession_python.h"
-#include "socketsessionreceiver_python.h"
-
-using namespace std;
-using namespace isc::util::python;
-using namespace isc::util::io;
-using namespace isc::util::io::python;
-using boost::lexical_cast;
-
-// Trivial constructor.
-s_SocketSessionReceiver::s_SocketSessionReceiver() : cppobj(NULL) {
-}
-
-// Import pydoc text
-#include "socketsessionreceiver_inc.cc"
-
-namespace {
-// This C structure corresponds to a Python callable object for
-// socket.fromfd().
-// See json_dumps_obj in dns_requestloader_python.cc for background rationale
-// of this trick.
-PyObject* socket_fromfd_obj = NULL;
-
-int
-SocketSessionReceiver_init(PyObject* po_self, PyObject* args, PyObject*) {
- s_SocketSessionReceiver* self =
- static_cast<s_SocketSessionReceiver*>(po_self);
- try {
- // The constructor expects a Python socket object. We'll extract
- // the underlying file descriptor using the fileno method (in the
- // duck typing manner) and pass it to the C++ constructor.
- // PyObject_CallMethod() could return NULL (especially if the given
- // object is of the wrong type and doesn't have the "fileno" method),
- // in which case PyObjectContainer will detect it and throw
- // PyCPPWrapperException, which will be converted to the Python
- // TypeError below.
- PyObject* po_sock;
- if (PyArg_ParseTuple(args, "O", &po_sock)) {
- PyObjectContainer fd_container(PyObject_CallMethod(
- po_sock,
- const_cast<char*>("fileno"),
- NULL));
- PyObjectContainer fdarg_container(
- Py_BuildValue("(O)", fd_container.get()));
- int fd;
- if (PyArg_ParseTuple(fdarg_container.get(), "i", &fd)) {
- self->cppobj = new SocketSessionReceiver(fd);
- return (0);
- }
- PyErr_SetString(PyExc_TypeError, "Given object's fileno() doesn't "
- "return an integer, probably not a valid socket "
- "object");
- }
- } catch (const PyCPPWrapperException& ex) {
- // This could happen due to memory allocation failure, but it's more
- // likely that the object doesn't have the "fileno()" method or it
- // returns an unexpected type of value. So we adjust the error
- // message accordingly.
- PyErr_SetString(PyExc_TypeError, "Failed to parse parameter, "
- "probably not a valid socket object");
- } catch (const exception& ex) {
- const string ex_what =
- "Failed to construct SocketSessionReceiver object: " +
- string(ex.what());
- PyErr_SetString(po_SocketSessionError, ex_what.c_str());
- } catch (...) {
- PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
- }
-
- return (-1);
-}
-
-PyObject*
-createPySocketAddress(const struct sockaddr& sa) {
- socklen_t salen;
- if (sa.sa_family == AF_INET) {
- salen = sizeof(struct sockaddr_in);
- } else if (sa.sa_family == AF_INET6) {
- salen = sizeof(struct sockaddr_in6);
- } else {
- isc_throw(SocketSessionError, "Unsupported socket address family: "
- << static_cast<int>(sa.sa_family));
- }
-
- char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
- const int error = getnameinfo(&sa, salen, hbuf, sizeof(hbuf), sbuf,
- sizeof(sbuf),
- NI_NUMERICHOST | NI_NUMERICSERV);
- if (error != 0) {
- isc_throw(SocketSessionError, "Unrecognized socket address format: "
- << gai_strerror(error));
- }
- if (sa.sa_family == AF_INET) {
- return (Py_BuildValue("(si)", hbuf, lexical_cast<int>(sbuf)));
- }
- // We know it's AF_INET6 at this point. We need some special trick for
- // non-0 scope (zone) ID: getnameinfo() may convert the address to a
- // textual representation using the extension described in RFC 4007,
- // in which case it contains a delimiter character '%'. We need to remove
- // it before constructing the tuple. The scope (zone) ID is preserved
- // in the corresponding field of the tuple.
- const void* p = &sa;
- const struct sockaddr_in6* sin6 =
- static_cast<const struct sockaddr_in6*>(p);
- char* cp = strchr(hbuf, '%');
- if (cp != NULL) {
- *cp = '\0';
- }
- return (Py_BuildValue("(siii)", hbuf, lexical_cast<int>(sbuf), 0,
- sin6->sin6_scope_id));
-}
-
-void
-SocketSessionReceiver_destroy(PyObject* po_self) {
- s_SocketSessionReceiver* self =
- static_cast<s_SocketSessionReceiver*>(po_self);
- delete self->cppobj;
- self->cppobj = NULL;
- Py_TYPE(self)->tp_free(self);
-}
-
-// A helper struct to automatically close a socket in an RAII manner.
-struct ScopedSocket : boost::noncopyable {
- ScopedSocket(int fd) : fd_(fd) {}
- ~ScopedSocket() {
- close(fd_);
- }
- const int fd_;
-};
-
-PyObject*
-SocketSessionReceiver_pop(PyObject* po_self, PyObject*) {
- s_SocketSessionReceiver* const self =
- static_cast<s_SocketSessionReceiver*>(po_self);
-
- try {
- // retrieve the session, and the convert it to a corresponding
- // Python tuple.
- const SocketSession session = self->cppobj->pop();
-
- // We need to immediately store the socket file descriptor in a
- // ScopedSocket object. socket.fromfd() will dup() the FD, so we need
- // to close our copy even if an exception is thrown.
- ScopedSocket sock(session.getSocket());
-
- // Build Python socket object
- PyObjectContainer c_args(Py_BuildValue("(iiii)", sock.fd_,
- session.getFamily(),
- session.getType(),
- session.getProtocol()));
- PyObjectContainer c_sock(PyObject_CallObject(socket_fromfd_obj,
- c_args.get()));
- // Convert the local and remote sockaddr to Python socket address objs
- PyObjectContainer c_local(createPySocketAddress(
- session.getLocalEndpoint()));
- PyObjectContainer c_remote(createPySocketAddress(
- session.getRemoteEndpoint()));
- // Convert the session data to Python byte object.
- PyObjectContainer c_data(Py_BuildValue("y#", session.getData(),
- session.getDataLength()));
-
- // Build a tuple from them and return it.
- return (Py_BuildValue("(OOOO)", c_sock.get(), c_local.get(),
- c_remote.get(), c_data.get()));
- } catch (const SocketSessionError& ex) {
- PyErr_SetString(po_SocketSessionError, ex.what());
- return (NULL);
- } catch (const exception& ex) {
- const string ex_what =
- "Unexpected failure in receiving a socket session: " +
- string(ex.what());
- PyErr_SetString(PyExc_SystemError, ex_what.c_str());
- return (NULL);
- } catch (...) {
- PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
- return (NULL);
- }
-}
-
-// These are the functions we export
-
-// 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 SocketSessionReceiver_methods[] = {
- { "pop", SocketSessionReceiver_pop, METH_NOARGS,
- SocketSessionReceiver_pop_doc },
- { NULL, NULL, 0, NULL }
-};
-} // end of unnamed namespace
-
-namespace isc {
-namespace util {
-namespace io {
-namespace python {
-// This defines the complete type for reflection in python and
-// parsing of PyObject* to s_SocketSessionReceiver
-// Most of the functions are not actually implemented and NULL here.
-PyTypeObject socketsessionreceiver_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "isc.util.io.SocketSessionReceiver",
- sizeof(s_SocketSessionReceiver), // tp_basicsize
- 0, // tp_itemsize
- SocketSessionReceiver_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
- SocketSessionReceiver_doc,
- NULL, // tp_traverse
- NULL, // tp_clear
- NULL, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- SocketSessionReceiver_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
- SocketSessionReceiver_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
-// Module Initialization, all statics are initialized here
-bool
-initModulePart_SocketSessionReceiver(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(&socketsessionreceiver_type) < 0) {
- return (false);
- }
- void* p = &socketsessionreceiver_type;
- if (PyModule_AddObject(mod, "SocketSessionReceiver",
- static_cast<PyObject*>(p)) < 0) {
- return (false);
- }
-
- PyObject* socket_module = PyImport_AddModule("socket");
- if (socket_module != NULL) {
- PyObject* socket_dict = PyModule_GetDict(socket_module);
- if (socket_dict != NULL) {
- socket_fromfd_obj = PyDict_GetItemString(socket_dict, "fromfd");
- }
- }
- if (socket_fromfd_obj != NULL) {
- Py_INCREF(socket_fromfd_obj);
- } else {
- PyErr_SetString(PyExc_RuntimeError,
- "isc.util.io.SocketSessionReceiver needs "
- "socket.fromfd(), but it's missing");
- return (false);
- }
-
- Py_INCREF(&socketsessionreceiver_type);
-
- return (true);
-}
-
-} // namespace python
-} // namespace io
-} // namespace util
-} // namespace isc
diff --git a/src/lib/python/isc/util/io/socketsessionreceiver_python.h b/src/lib/python/isc/util/io/socketsessionreceiver_python.h
deleted file mode 100644
index 14e8a1b..0000000
--- a/src/lib/python/isc/util/io/socketsessionreceiver_python.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef __PYTHON_SOCKETSESSIONRECEIVER_H
-#define __PYTHON_SOCKETSESSIONRECEIVER_H 1
-
-#include <Python.h>
-
-namespace isc {
-namespace util {
-namespace io {
-class SocketSessionReceiver;
-
-namespace python {
-
-// The s_* Class simply covers one instantiation of the object
-class s_SocketSessionReceiver : public PyObject {
-public:
- s_SocketSessionReceiver();
- SocketSessionReceiver* cppobj;
-};
-
-extern PyTypeObject socketsessionreceiver_type;
-
-bool initModulePart_SocketSessionReceiver(PyObject* mod);
-
-} // namespace io
-} // namespace python
-} // namespace util
-} // namespace isc
-#endif // __PYTHON_SOCKETSESSIONRECEIVER_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/python/isc/util/io/tests/Makefile.am b/src/lib/python/isc/util/io/tests/Makefile.am
deleted file mode 100644
index 3429009..0000000
--- a/src/lib/python/isc/util/io/tests/Makefile.am
+++ /dev/null
@@ -1,36 +0,0 @@
-PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = socketsession_test.py
-EXTRA_DIST = $(PYTESTS)
-
-# If necessary (rare cases), explicitly specify paths to dynamic libraries
-# required by loadable python modules.
-LIBRARY_PATH_PLACEHOLDER =
-if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
-endif
-
-# test using command-line arguments, so use check-local target instead of TESTS
-check-local:
-if ENABLE_PYTHON_COVERAGE
- touch $(abs_top_srcdir)/.coverage
- rm -f .coverage
- ${LN_S} $(abs_top_srcdir)/.coverage .coverage
-endif
-# Note: below we intentionally use a non absolute path for TESTDATAOBJDIR.
-# It will be used as part of the path for a UNIX domain socket. Due to the
-# relatively lower limit on the length it's better to keep it as short as
-# possible.
- for pytest in $(PYTESTS) ; do \
- echo Running test: $$pytest ; \
- PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/isc/python/util/io/.libs \
- $(LIBRARY_PATH_PLACEHOLDER) \
- TESTDATAOBJDIR=$(builddir) \
- $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
- done
-
-CLEANFILES = $(builddir)/ssessiontest.unix
-
-CLEANDIRS = __pycache__
-
-clean-local:
- rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/util/io/tests/socketsession_test.py b/src/lib/python/isc/util/io/tests/socketsession_test.py
deleted file mode 100644
index 9b5f12a..0000000
--- a/src/lib/python/isc/util/io/tests/socketsession_test.py
+++ /dev/null
@@ -1,253 +0,0 @@
-# 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.
-
-import os, signal, socket, unittest
-from socket import AF_INET, AF_INET6, SOCK_STREAM, SOCK_DGRAM, IPPROTO_UDP, \
- IPPROTO_TCP
-from isc.util.io.socketsession import *
-
-TESTDATA_OBJDIR = os.getenv("TESTDATAOBJDIR")
-TEST_UNIX_FILE = TESTDATA_OBJDIR + '/ssessiontest.unix'
-TEST_DATA = b'BIND10 test'
-TEST_PORT = 53535
-
-class TestForwarder(unittest.TestCase):
- '''In general, this is a straightforward port of the C++ counterpart.
-
- In some cases test cases are simplified or have Python specific cases.
-
- '''
-
- def setUp(self):
- self.forwarder = SocketSessionForwarder(TEST_UNIX_FILE)
- if os.path.exists(TEST_UNIX_FILE):
- os.unlink(TEST_UNIX_FILE)
- self.large_text = b'a' * 65535
-
- def tearDown(self):
- if os.path.exists(TEST_UNIX_FILE):
- os.unlink(TEST_UNIX_FILE)
-
- def start_listen(self):
- self.listen_sock = socket.socket(socket.AF_UNIX, SOCK_STREAM, 0)
- self.listen_sock.bind(TEST_UNIX_FILE)
- self.listen_sock.listen(10)
-
- def accept_forwarder(self):
- self.listen_sock.setblocking(False)
- s, _ = self.listen_sock.accept()
- s.setblocking(True)
- return s
-
- def test_init(self):
- # check bad arguments. valid cases will covered in other tests.
- self.assertRaises(TypeError, SocketSessionForwarder, 1)
- self.assertRaises(TypeError, SocketSessionForwarder,
- 'test.unix', 'test.unix')
-
- def test_badpush(self):
- # bad numbers of parameters
- self.assertRaises(TypeError, self.forwarder.push, 1)
- self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, ('127.0.0.1', 53),
- ('192.0.2.1', 5300), TEST_DATA, 0)
- # contain a bad type of parameter
- self.assertRaises(TypeError, self.forwarder.push, 0, 'AF_INET',
- SOCK_DGRAM, IPPROTO_UDP, ('127.0.0.1', 53),
- ('192.0.2.1', 5300), TEST_DATA)
- # bad local address
- self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, ('127.0.0..1', 53),
- ('192.0.2.1', 5300), TEST_DATA)
- self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, '127.0.0.1',
- ('192.0.2.1', 5300), TEST_DATA)
- # bad remote address
- self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET6,
- SOCK_DGRAM, IPPROTO_UDP, ('2001:db8::1', 53),
- ('2001:db8:::3', 5300), TEST_DATA)
-
- # push before connect
- self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
- ('192.0.2.2', 53), TEST_DATA)
-
- # Now connect the forwarder for the rest of tests
- self.start_listen()
- self.forwarder.connect_to_receiver()
-
- # Inconsistent address family
- self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, ('2001:db8::1', 53, 0, 1),
- ('192.0.2.2', 53), TEST_DATA)
- self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET6,
- SOCK_DGRAM, IPPROTO_UDP, ('2001:db8::1', 53, 0, 1),
- ('192.0.2.2', 53), TEST_DATA)
-
- # Empty data: we reject them at least for now
- self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
- ('192.0.2.2', 53), b'')
-
- # Too big data: we reject them at least for now
- self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
- ('192.0.2.2', 53), b'd' * 65536)
-
- # Close the receptor before push. It will result in SIGPIPE (should be
- # ignored) and EPIPE, which will be converted to SocketSessionError.
- self.listen_sock.close()
- self.assertRaises(SocketSessionError, self.forwarder.push, 1, AF_INET,
- SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
- ('192.0.2.2', 53), TEST_DATA)
-
- def create_socket(self, family, type, protocol, addr, do_listen):
- s = socket.socket(family, type, protocol)
- s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- s.bind(addr)
- if do_listen and protocol == IPPROTO_TCP:
- s.listen(1)
- return s
-
- def check_push_and_pop(self, family, type, protocol, local, remote,
- data, new_connection):
- sock = self.create_socket(family, type, protocol, local, True)
- fwd_fd = sock.fileno()
- if protocol == IPPROTO_TCP:
- client_addr = ('::1', 0, 0, 0) if family == AF_INET6 \
- else ('127.0.0.1', 0)
- client_sock = self.create_socket(family, type, protocol,
- client_addr, False)
- client_sock.setblocking(False)
- try:
- client_sock.connect(local)
- except socket.error:
- pass
- server_sock, _ = sock.accept()
- fwd_fd = server_sock.fileno()
-
- # If a new connection is required, start the "server", have the
- # internal forwarder connect to it, and then internally accept it.
- if new_connection:
- self.start_listen()
- self.forwarder.connect_to_receiver()
- self.accept_sock = self.accept_forwarder()
-
- # Then push one socket session via the forwarder.
- self.forwarder.push(fwd_fd, family, type, protocol, local, remote,
- data)
-
- # Pop the socket session we just pushed from a local receiver, and
- # check the content.
- receiver = SocketSessionReceiver(self.accept_sock)
- signal.alarm(1)
- sock_session = receiver.pop()
- signal.alarm(0)
- passed_sock = sock_session[0]
- self.assertNotEqual(fwd_fd, passed_sock.fileno())
- self.assertEqual(family, passed_sock.family)
- self.assertEqual(type, passed_sock.type)
- self.assertEqual(protocol, passed_sock.proto)
- self.assertEqual(local, sock_session[1])
- self.assertEqual(remote, sock_session[2])
- self.assertEqual(data, sock_session[3])
-
- # Check if the passed FD is usable by sending some data from it.
- passed_sock.setblocking(True)
- if protocol == IPPROTO_UDP:
- self.assertEqual(len(TEST_DATA), passed_sock.sendto(TEST_DATA,
- local))
- sock.settimeout(10)
- self.assertEqual(TEST_DATA, sock.recvfrom(len(TEST_DATA))[0])
- else:
- server_sock.close()
- self.assertEqual(len(TEST_DATA), passed_sock.send(TEST_DATA))
- client_sock.setblocking(True)
- client_sock.settimeout(10)
- self.assertEqual(TEST_DATA, client_sock.recv(len(TEST_DATA)))
-
- def test_push_and_pop(self):
- # This is a straightforward port of C++ pushAndPop test.
- local6 = ('::1', TEST_PORT, 0, 0)
- remote6 = ('2001:db8::1', 5300, 0, 0)
- self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
- local6, remote6, TEST_DATA, True)
- self.check_push_and_pop(AF_INET6, SOCK_STREAM, IPPROTO_TCP,
- local6, remote6, TEST_DATA, False)
-
- local4 = ('127.0.0.1', TEST_PORT)
- remote4 = ('192.0.2.2', 5300)
- self.check_push_and_pop(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
- local4, remote4, TEST_DATA, False)
- self.check_push_and_pop(AF_INET, SOCK_STREAM, IPPROTO_TCP,
- local4, remote4, TEST_DATA, False)
-
- self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
- local6, remote6, self.large_text, False)
- self.check_push_and_pop(AF_INET6, SOCK_STREAM, IPPROTO_TCP,
- local6, remote6, self.large_text, False)
- self.check_push_and_pop(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
- local4, remote4, self.large_text, False)
- self.check_push_and_pop(AF_INET, SOCK_STREAM, IPPROTO_TCP,
- local4, remote4, self.large_text, False)
-
- # Python specific: check for an IPv6 scoped address with non 0
- # scope (zone) ID
- scope6 = ('fe80::1', TEST_PORT, 0, 1)
- self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
- local6, scope6, TEST_DATA, False)
-
- def test_push_too_fast(self):
- # A straightforward port of C++ pushTooFast test.
- def multi_push(forwarder, addr, data):
- for i in range(0, 10):
- forwarder.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP, addr,
- addr, data)
- self.start_listen()
- self.forwarder.connect_to_receiver()
- self.assertRaises(SocketSessionError, multi_push, self.forwarder,
- ('192.0.2.1', 53), self.large_text)
-
- def test_bad_pop(self):
- # This is a subset of C++ badPop test. We only check pop() raises
- # SocketSessionError when it internally fails to get the FD.
- # Other cases would require passing a valid FD from the test,
- # which would make the test too complicated. As a wrapper checking
- # one common failure case should be reasonably sufficient.
-
- self.start_listen()
- s = socket.socket(socket.AF_UNIX, SOCK_STREAM, 0)
- s.setblocking(False)
- s.connect(TEST_UNIX_FILE)
- accept_sock = self.accept_forwarder()
- receiver = SocketSessionReceiver(accept_sock)
- s.close()
- self.assertRaises(SocketSessionError, receiver.pop)
-
-class TestReceiver(unittest.TestCase):
- # We only check a couple of failure cases on construction. Valid cases
- # are covered in TestForwarder.
-
- def test_bad_init(self):
- class FakeSocket:
- # pretending to be th standard socket class, but its fileno() is
- # bogus.
- def fileno(self):
- return None
- self.assertRaises(TypeError, SocketSessionReceiver, 1)
- self.assertRaises(TypeError, SocketSessionReceiver, FakeSocket())
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/src/lib/resolve/.gitignore b/src/lib/resolve/.gitignore
new file mode 100644
index 0000000..292ed1a
--- /dev/null
+++ b/src/lib/resolve/.gitignore
@@ -0,0 +1,2 @@
+/resolve_messages.cc
+/resolve_messages.h
diff --git a/src/lib/resolve/Makefile.am b/src/lib/resolve/Makefile.am
index ceccce8..4b81862 100644
--- a/src/lib/resolve/Makefile.am
+++ b/src/lib/resolve/Makefile.am
@@ -34,6 +34,7 @@ nodist_libresolve_la_SOURCES = resolve_messages.h resolve_messages.cc
libresolve_la_LIBADD = $(top_builddir)/src/lib/dns/libdns++.la
libresolve_la_LIBADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
libresolve_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
+libresolve_la_LIBADD += $(top_builddir)/src/lib/asiodns/libasiodns.la
# The message file should be in the distribution.
EXTRA_DIST = resolve_messages.mes
diff --git a/src/lib/resolve/recursive_query.cc b/src/lib/resolve/recursive_query.cc
index ea7d528..8d03c1c 100644
--- a/src/lib/resolve/recursive_query.cc
+++ b/src/lib/resolve/recursive_query.cc
@@ -14,8 +14,8 @@
#include <config.h>
-#include <netinet/in.h>
#include <stdlib.h>
+#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h> // for some IPC/network system calls
#include <string>
@@ -128,7 +128,7 @@ typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
// 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,
+RecursiveQuery::RecursiveQuery(DNSServiceBase& dns_service,
isc::nsas::NameserverAddressStore& nsas,
isc::cache::ResolverCache& cache,
const std::vector<std::pair<std::string, uint16_t> >& upstream,
diff --git a/src/lib/resolve/recursive_query.h b/src/lib/resolve/recursive_query.h
index 9af2d72..a819a94 100644
--- a/src/lib/resolve/recursive_query.h
+++ b/src/lib/resolve/recursive_query.h
@@ -87,7 +87,7 @@ public:
/// \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,
+ RecursiveQuery(DNSServiceBase& dns_service,
isc::nsas::NameserverAddressStore& nsas,
isc::cache::ResolverCache& cache,
const std::vector<std::pair<std::string, uint16_t> >&
@@ -178,7 +178,7 @@ public:
void setTestServer(const std::string& address, uint16_t port);
private:
- DNSService& dns_service_;
+ DNSServiceBase& dns_service_;
isc::nsas::NameserverAddressStore& nsas_;
isc::cache::ResolverCache& cache_;
boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
diff --git a/src/lib/resolve/tests/.gitignore b/src/lib/resolve/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/lib/resolve/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/lib/resolve/tests/recursive_query_unittest.cc b/src/lib/resolve/tests/recursive_query_unittest.cc
index 4e939fa..a8b8057 100644
--- a/src/lib/resolve/tests/recursive_query_unittest.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest.cc
@@ -14,13 +14,16 @@
#include <config.h>
+#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
-#include <string.h>
+#include <cstring>
+#include <boost/noncopyable.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <gtest/gtest.h>
@@ -61,6 +64,7 @@ using namespace isc::asiodns;
using namespace isc::asiolink;
using namespace isc::dns;
using namespace isc::util;
+using boost::scoped_ptr;
namespace isc {
namespace asiodns {
@@ -84,18 +88,14 @@ const char* const TEST_IPV4_ADDR = "127.0.0.1";
// 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.
+// This function returns an addrinfo structure for use by tests.
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;
-
+resolveAddress(const int protocol, const char* const addr,
+ const char* const port)
+{
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
- hints.ai_family = family;
+ hints.ai_family = AF_UNSPEC; // let the address decide it.
hints.ai_socktype = (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
hints.ai_protocol = protocol;
hints.ai_flags = AI_NUMERICSERV;
@@ -109,6 +109,51 @@ resolveAddress(const int family, const int protocol, const bool client) {
return (res);
}
+// convenience shortcut of the other version 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) {
+ return (resolveAddress(protocol,
+ (family == AF_INET6) ? TEST_IPV6_ADDR :
+ TEST_IPV4_ADDR,
+ client ? TEST_CLIENT_PORT : TEST_SERVER_PORT));
+}
+
+// A helper holder of addrinfo so we can safely release the resource
+// either when leaving the defined scope either normally or due to exception.
+struct ScopedAddrInfo {
+ ScopedAddrInfo(struct addrinfo* res) : res_(res) {}
+ ~ScopedAddrInfo() { freeaddrinfo(res_); }
+ struct addrinfo* res_;
+};
+
+// Similar to ScopedAddrInfo but for socket FD. It also supports the "release"
+// operation so it can release the ownership of the FD.
+// This is made non copyable to avoid making an accidental copy, which could
+// result in duplicate close.
+struct ScopedSocket : private boost::noncopyable {
+ ScopedSocket() : s_(-1) {}
+ ScopedSocket(int s) : s_(s) {}
+ ~ScopedSocket() {
+ if (s_ >= 0) {
+ close(s_);
+ }
+ }
+ void reset(int new_s) {
+ if (s_ >= 0) {
+ close(s_);
+ }
+ s_ = new_s;
+ }
+ int release() {
+ int s = s_;
+ s_ = -1;
+ return (s);
+ }
+ int s_;
+};
+
// 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
@@ -128,27 +173,20 @@ protected:
// It would delete itself, but after the io_service_, which could
// segfailt in case there were unhandled requests
resolver_.reset();
- 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);
+ ScopedAddrInfo sai(resolveAddress(family, IPPROTO_UDP, false));
+ struct addrinfo* res = sai.res_;
- sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
+ sock_.reset(socket(res->ai_family, res->ai_socktype,
+ res->ai_protocol));
+ if (sock_.s_ < 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);
+ const int cc = sendto(sock_.s_, test_data, sizeof(test_data), 0,
+ res->ai_addr, res->ai_addrlen);
if (cc != sizeof(test_data)) {
isc_throw(IOError, "unexpected sendto result: " << cc);
}
@@ -157,16 +195,18 @@ protected:
// Send a test TCP packet to a mock server
void sendTCP(const int family) {
- res_ = resolveAddress(family, IPPROTO_TCP, false);
+ ScopedAddrInfo sai(resolveAddress(family, IPPROTO_TCP, false));
+ struct addrinfo* res = sai.res_;
- sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
+ sock_.reset(socket(res->ai_family, res->ai_socktype,
+ res->ai_protocol));
+ if (sock_.s_ < 0) {
isc_throw(IOError, "failed to open test socket");
}
- if (connect(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+ if (connect(sock_.s_, 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);
+ const int cc = send(sock_.s_, test_data, sizeof(test_data), 0);
if (cc != sizeof(test_data)) {
isc_throw(IOError, "unexpected send result: " << cc);
}
@@ -177,14 +217,16 @@ protected:
// 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);
+ ScopedAddrInfo sai(resolveAddress(family, IPPROTO_UDP, true));
+ struct addrinfo* res = sai.res_;
- sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
+ sock_.reset(socket(res->ai_family, res->ai_socktype,
+ res->ai_protocol));
+ if (sock_.s_ < 0) {
isc_throw(IOError, "failed to open test socket");
}
- if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+ if (bind(sock_.s_, res->ai_addr, res->ai_addrlen) < 0) {
isc_throw(IOError, "bind failed: " << strerror(errno));
}
@@ -204,7 +246,7 @@ protected:
// we add an ad hoc timeout.
const struct timeval timeo = { 10, 0 };
int recv_options = 0;
- if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo,
+ if (setsockopt(sock_.s_, SOL_SOCKET, SO_RCVTIMEO, &timeo,
sizeof(timeo))) {
if (errno == ENOPROTOOPT) {
// Workaround for Solaris: it doesn't accept SO_RCVTIMEO
@@ -217,7 +259,7 @@ protected:
isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
}
}
- const int ret = recv(sock_, buffer, size, recv_options);
+ const int ret = recv(sock_.s_, buffer, size, recv_options);
if (ret < 0) {
isc_throw(IOError, "recvfrom failed: " << strerror(errno));
}
@@ -226,38 +268,68 @@ protected:
size = ret;
}
+ void
+ addServer(const string& address, const char* const port, int protocol) {
+ ScopedAddrInfo sai(resolveAddress(protocol, address.c_str(), port));
+ struct addrinfo* res = sai.res_;
+ const int family = res->ai_family;
+
+ ScopedSocket sock(socket(res->ai_family, res->ai_socktype,
+ res->ai_protocol));
+ const int s = sock.s_;
+ if (s < 0) {
+ isc_throw(isc::Unexpected, "failed to open a test socket");
+ }
+ const int on = 1;
+ if (family == AF_INET6) {
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) ==
+ -1) {
+ isc_throw(isc::Unexpected,
+ "failed to set socket option(IPV6_V6ONLY)");
+ }
+ }
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+ isc_throw(isc::Unexpected,
+ "failed to set socket option(SO_REUSEADDR)");
+ }
+ if (bind(s, res->ai_addr, res->ai_addrlen) != 0) {
+ isc_throw(isc::Unexpected, "failed to bind a test socket");
+ }
+ if (protocol == IPPROTO_TCP) {
+ dns_service_->addServerTCPFromFD(sock.release(), family);
+ } else {
+ dns_service_->addServerUDPFromFD(sock.release(), family);
+ }
+ }
// 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);
+ void setDNSService(const string& address) {
+ setDNSService();
+ addServer(address, TEST_SERVER_PORT, IPPROTO_TCP);
+ addServer(address, TEST_SERVER_PORT, IPPROTO_UDP);
}
// 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);
+ setDNSService();
+ if (use_ipv6) {
+ addServer("::", TEST_SERVER_PORT, IPPROTO_TCP);
+ addServer("::", TEST_SERVER_PORT, IPPROTO_UDP);
+ }
+ if (use_ipv4) {
+ addServer("0.0.0.0", TEST_SERVER_PORT, IPPROTO_TCP);
+ addServer("0.0.0.0", TEST_SERVER_PORT, IPPROTO_UDP);
+ }
}
// 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);
+ io_service_.reset(new IOService());
+ callback_.reset(new ASIOCallBack(this));
+ dns_service_.reset(new DNSService(*io_service_, callback_.get(), NULL,
+ NULL));
}
// Run a simple server test, on either IPv4 or IPv6, and over either
@@ -276,7 +348,7 @@ protected:
// 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_NE(sock_.s_, callback_native_);
EXPECT_EQ(protocol, callback_protocol_);
EXPECT_EQ(family == AF_INET6 ? TEST_IPV6_ADDR : TEST_IPV4_ADDR,
callback_address_);
@@ -424,28 +496,26 @@ private:
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_;
+ scoped_ptr<IOService> io_service_;
+ scoped_ptr<DNSService> dns_service_;
+ scoped_ptr<isc::nsas::NameserverAddressStore> nsas_;
isc::cache::ResolverCache cache_;
- ASIOCallBack* callback_;
+ scoped_ptr<ASIOCallBack> callback_;
int callback_protocol_;
int callback_native_;
string callback_address_;
vector<uint8_t> callback_data_;
- int sock_;
- struct addrinfo* res_;
+ ScopedSocket sock_;
boost::shared_ptr<isc::util::unittests::TestResolver> resolver_;
};
RecursiveQueryTest::RecursiveQueryTest() :
dns_service_(NULL), callback_(NULL), callback_protocol_(0),
- callback_native_(-1), sock_(-1), res_(NULL),
- resolver_(new isc::util::unittests::TestResolver())
+ callback_native_(-1), resolver_(new isc::util::unittests::TestResolver())
{
- io_service_ = new IOService();
+ io_service_.reset(new IOService());
setDNSService(true, true);
- nsas_ = new isc::nsas::NameserverAddressStore(resolver_);
+ nsas_.reset(new isc::nsas::NameserverAddressStore(resolver_));
}
TEST_F(RecursiveQueryTest, v6UDPSend) {
@@ -476,24 +546,24 @@ TEST_F(RecursiveQueryTest, v6UDPSendSpecific) {
// 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);
+ setDNSService(TEST_IPV6_ADDR);
doTest(AF_INET6, IPPROTO_UDP);
}
TEST_F(RecursiveQueryTest, v6TCPSendSpecific) {
- setDNSService(*TEST_IPV6_ADDR);
+ setDNSService(TEST_IPV6_ADDR);
doTest(AF_INET6, IPPROTO_TCP);
EXPECT_THROW(sendTCP(AF_INET), IOError);
}
TEST_F(RecursiveQueryTest, v4UDPSendSpecific) {
- setDNSService(*TEST_IPV4_ADDR);
+ setDNSService(TEST_IPV4_ADDR);
doTest(AF_INET, IPPROTO_UDP);
}
TEST_F(RecursiveQueryTest, v4TCPSendSpecific) {
- setDNSService(*TEST_IPV4_ADDR);
+ setDNSService(TEST_IPV4_ADDR);
doTest(AF_INET, IPPROTO_TCP);
EXPECT_THROW(sendTCP(AF_INET6), IOError);
@@ -501,7 +571,7 @@ TEST_F(RecursiveQueryTest, v4TCPSendSpecific) {
TEST_F(RecursiveQueryTest, v6AddServer) {
setDNSService();
- dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV6_ADDR);
+ addServer(TEST_IPV6_ADDR, TEST_SERVER_PORT, IPPROTO_TCP);
doTest(AF_INET6, IPPROTO_TCP);
EXPECT_THROW(sendTCP(AF_INET), IOError);
@@ -509,7 +579,7 @@ TEST_F(RecursiveQueryTest, v6AddServer) {
TEST_F(RecursiveQueryTest, v4AddServer) {
setDNSService();
- dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV4_ADDR);
+ addServer(TEST_IPV4_ADDR, TEST_SERVER_PORT, IPPROTO_TCP);
doTest(AF_INET, IPPROTO_TCP);
EXPECT_THROW(sendTCP(AF_INET6), IOError);
@@ -606,41 +676,43 @@ TEST_F(RecursiveQueryTest, forwarderSend) {
}
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) {
+createTestSocket() {
+ ScopedAddrInfo sai(resolveAddress(AF_INET, IPPROTO_UDP, true));
+ struct addrinfo* res = sai.res_;
+
+ ScopedSocket sock(socket(res->ai_family, res->ai_socktype,
+ res->ai_protocol));
+ if (sock.s_ < 0) {
isc_throw(IOError, "failed to open test socket");
}
- if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+ if (bind(sock.s_, res->ai_addr, res->ai_addrlen) < 0) {
isc_throw(IOError, "failed to bind test socket");
}
- return sock_;
+ return (sock.release());
}
int
-setSocketTimeout(int sock_, size_t tv_sec, size_t tv_usec) {
+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 (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;
+ 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) {
+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) {
+ if (recv(sock, inbuff, sizeof(inbuff), recv_options) < 0) {
return false;
} else {
++i;
@@ -690,7 +762,7 @@ TEST_F(RecursiveQueryTest, forwardQueryTimeout) {
setDNSService();
// Prepare the socket
- sock_ = createTestSocket();
+ sock_.reset(createTestSocket());
// Prepare the server
bool done(true);
@@ -724,7 +796,7 @@ TEST_F(RecursiveQueryTest, forwardClientTimeout) {
// Prepare the service (we do not use the common setup, we do not answer
setDNSService();
- sock_ = createTestSocket();
+ sock_.reset(createTestSocket());
// Prepare the server
bool done1(true);
@@ -758,7 +830,7 @@ TEST_F(RecursiveQueryTest, forwardLookupTimeout) {
setDNSService();
// Prepare the socket
- sock_ = createTestSocket();
+ sock_.reset(createTestSocket());
// Prepare the server
bool done(true);
@@ -793,7 +865,7 @@ TEST_F(RecursiveQueryTest, lowtimeouts) {
setDNSService();
// Prepare the socket
- sock_ = createTestSocket();
+ sock_.reset(createTestSocket());
// Prepare the server
bool done(true);
diff --git a/src/lib/resolve/tests/recursive_query_unittest_2.cc b/src/lib/resolve/tests/recursive_query_unittest_2.cc
index a222240..2b3d129 100644
--- a/src/lib/resolve/tests/recursive_query_unittest_2.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest_2.cc
@@ -343,7 +343,8 @@ public:
// Convert to wire format
udp_send_buffer_->clear();
- MessageRenderer renderer(*udp_send_buffer_);
+ MessageRenderer renderer;
+ renderer.setBuffer(udp_send_buffer_.get());
msg.toWire(renderer);
if (mangle_response) {
@@ -477,10 +478,9 @@ public:
setReferralExampleOrg(msg);
// Convert to wire format
- // Use a temporary buffer for the dns wire data (we copy it
+ // Use a temporary renderer for the dns wire data (we copy it
// to the 'real' buffer below)
- OutputBuffer msg_buf(BUFFER_SIZE);
- MessageRenderer renderer(msg_buf);
+ MessageRenderer renderer;
msg.toWire(renderer);
// Expected next state (when checked) is the UDP query to example.org.
@@ -496,12 +496,13 @@ public:
// 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_send_buffer_->writeUint16(renderer.getLength());
+ tcp_send_buffer_->writeData(renderer.getData(), renderer.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));
+ boost::bind(
+ &RecursiveQueryTest2::tcpSendHandler, this,
+ tcp_send_buffer_->getLength(), _1, _2));
}
/// \brief Completion Handler for Sending TCP data
diff --git a/src/lib/resolve/tests/recursive_query_unittest_3.cc b/src/lib/resolve/tests/recursive_query_unittest_3.cc
index 3602b03..168ec80 100644
--- a/src/lib/resolve/tests/recursive_query_unittest_3.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest_3.cc
@@ -240,8 +240,10 @@ public:
// Convert to wire format
udp_send_buffer_->clear();
- MessageRenderer renderer(*udp_send_buffer_);
+ MessageRenderer renderer;
+ renderer.setBuffer(udp_send_buffer_.get());
message.toWire(renderer);
+ renderer.setBuffer(NULL);
// Return a message back to the IOFetch object (after setting the
// expected length of data for the check in the send handler).
@@ -353,8 +355,7 @@ public:
// 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);
+ MessageRenderer renderer;
message.toWire(renderer);
// Also, take this opportunity to clear the accumulated read count in
@@ -368,12 +369,14 @@ public:
// 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_send_buffer_->writeUint16(renderer.getLength());
+ tcp_send_buffer_->writeData(renderer.getData(), renderer.getLength());
tcp_socket_.async_send(asio::buffer(tcp_send_buffer_->getData(),
tcp_send_buffer_->getLength()),
- boost::bind(&RecursiveQueryTest3::tcpSendHandler,
- this, tcp_send_buffer_->getLength(), _1, _2));
+ boost::bind(
+ &RecursiveQueryTest3::tcpSendHandler,
+ this,
+ tcp_send_buffer_->getLength(), _1, _2));
}
/// \brief Completion Handler for Sending TCP data
diff --git a/src/lib/server_common/.gitignore b/src/lib/server_common/.gitignore
new file mode 100644
index 0000000..e25a98f
--- /dev/null
+++ b/src/lib/server_common/.gitignore
@@ -0,0 +1,2 @@
+/server_common_messages.cc
+/server_common_messages.h
diff --git a/src/lib/server_common/Makefile.am b/src/lib/server_common/Makefile.am
index c2779b4..6010316 100644
--- a/src/lib/server_common/Makefile.am
+++ b/src/lib/server_common/Makefile.am
@@ -21,6 +21,7 @@ libserver_common_la_SOURCES = client.h client.cc
libserver_common_la_SOURCES += keyring.h keyring.cc
libserver_common_la_SOURCES += portconfig.h portconfig.cc
libserver_common_la_SOURCES += logger.h logger.cc
+libserver_common_la_SOURCES += socket_request.h socket_request.cc
nodist_libserver_common_la_SOURCES = server_common_messages.h
nodist_libserver_common_la_SOURCES += server_common_messages.cc
libserver_common_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
@@ -30,6 +31,7 @@ libserver_common_la_LIBADD += $(top_builddir)/src/lib/config/libcfgclient.la
libserver_common_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
libserver_common_la_LIBADD += $(top_builddir)/src/lib/acl/libacl.la
libserver_common_la_LIBADD += $(top_builddir)/src/lib/dns/libdns++.la
+libserver_common_la_LIBADD += $(top_builddir)/src/lib/util/io/libutil_io.la
BUILT_SOURCES = server_common_messages.h server_common_messages.cc
server_common_messages.h server_common_messages.cc: server_common_messages.mes
$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/server_common/server_common_messages.mes
diff --git a/src/lib/server_common/portconfig.cc b/src/lib/server_common/portconfig.cc
index fba8e1a..530c919 100644
--- a/src/lib/server_common/portconfig.cc
+++ b/src/lib/server_common/portconfig.cc
@@ -14,6 +14,7 @@
#include <server_common/portconfig.h>
#include <server_common/logger.h>
+#include <server_common/socket_request.h>
#include <asiolink/io_address.h>
#include <asiodns/dns_service.h>
@@ -58,8 +59,7 @@ parseAddresses(isc::data::ConstElementPtr addresses,
}
result.push_back(AddressPair(addr->stringValue(),
port->intValue()));
- }
- catch (const TypeError&) { // Better error message
+ } catch (const TypeError&) { // Better error message
LOG_ERROR(logger, SRVCOMM_ADDRESS_TYPE).
arg(addrPair->str());
isc_throw(TypeError,
@@ -76,31 +76,53 @@ parseAddresses(isc::data::ConstElementPtr addresses,
namespace {
+vector<string> current_sockets;
+
void
-setAddresses(DNSService& service, const AddressList& addresses) {
+setAddresses(DNSServiceBase& service, const AddressList& addresses,
+ DNSService::ServerFlag server_options)
+{
service.clearServers();
+ BOOST_FOREACH(const string& token, current_sockets) {
+ socketRequestor().releaseSocket(token);
+ }
+ current_sockets.clear();
BOOST_FOREACH(const AddressPair &address, addresses) {
- service.addServer(address.second, address.first);
+ const int af(IOAddress(address.first).getFamily());
+ // We use the application name supplied to the socket requestor on
+ // creation. So we can freely use the SHARE_SAME
+ const SocketRequestor::SocketID
+ tcp(socketRequestor().requestSocket(SocketRequestor::TCP,
+ address.first, address.second,
+ SocketRequestor::SHARE_SAME));
+ current_sockets.push_back(tcp.second);
+ service.addServerTCPFromFD(tcp.first, af);
+ const SocketRequestor::SocketID
+ udp(socketRequestor().requestSocket(SocketRequestor::UDP,
+ address.first, address.second,
+ SocketRequestor::SHARE_SAME));
+ current_sockets.push_back(udp.second);
+ service.addServerUDPFromFD(udp.first, af, server_options);
}
}
}
void
-installListenAddresses(const AddressList& newAddresses,
- AddressList& addressStore,
- isc::asiodns::DNSService& service)
+installListenAddresses(const AddressList& new_addresses,
+ AddressList& address_store,
+ DNSServiceBase& service,
+ DNSService::ServerFlag server_options)
{
try {
LOG_DEBUG(logger, DBG_TRACE_BASIC, SRVCOMM_SET_LISTEN);
- BOOST_FOREACH(const AddressPair& addr, newAddresses) {
+ BOOST_FOREACH(const AddressPair& addr, new_addresses) {
LOG_DEBUG(logger, DBG_TRACE_VALUES, SRVCOMM_ADDRESS_VALUE).
arg(addr.first).arg(addr.second);
}
- setAddresses(service, newAddresses);
- addressStore = newAddresses;
- }
- catch (const exception& e) {
+ setAddresses(service, new_addresses, server_options);
+ address_store = new_addresses;
+ } catch (const SocketRequestor::NonFatalSocketError& e) {
/*
* If one of the addresses isn't set successfully, we will restore
* the old addresses, the behavior is that either all address are
@@ -116,14 +138,28 @@ installListenAddresses(const AddressList& newAddresses,
*/
LOG_ERROR(logger, SRVCOMM_ADDRESS_FAIL).arg(e.what());
try {
- setAddresses(service, addressStore);
- }
- catch (const exception& e2) {
+ setAddresses(service, address_store, server_options);
+ } catch (const SocketRequestor::NonFatalSocketError& e2) {
LOG_FATAL(logger, SRVCOMM_ADDRESS_UNRECOVERABLE).arg(e2.what());
+ // If we can't set the new ones, nor the old ones, at least
+ // releasing everything should work. If it doesn't, there isn't
+ // anything else we could do.
+ setAddresses(service, AddressList(), server_options);
+ address_store.clear();
}
//Anyway the new configure has problem, we need to notify configure
//manager the new configure doesn't work
throw;
+ } catch (const exception& e) {
+ // Any other kind of exception is fatal. It might mean we are in
+ // inconsistent state with the boss/socket creator, so we abort
+ // to make sure it doesn't last.
+ LOG_FATAL(logger, SRVCOMM_EXCEPTION_ALLOC).arg(e.what());
+ abort();
+ } catch (...) {
+ // As the previous one, but we know even less info
+ LOG_FATAL(logger, SRVCOMM_UNKNOWN_EXCEPTION_ALLOC);
+ abort();
}
}
diff --git a/src/lib/server_common/portconfig.h b/src/lib/server_common/portconfig.h
index e4e7bf6..0795728 100644
--- a/src/lib/server_common/portconfig.h
+++ b/src/lib/server_common/portconfig.h
@@ -15,22 +15,15 @@
#ifndef ISC_SERVER_COMMON_PORTCONFIG_H
#define ISC_SERVER_COMMON_PORTCONFIG_H
+#include <cc/data.h>
+
+#include <asiodns/dns_service.h>
+
#include <utility>
#include <string>
#include <stdint.h>
#include <vector>
-#include <cc/data.h>
-
-/*
- * Some forward declarations.
- */
-namespace isc {
-namespace asiodns {
-class DNSService;
-}
-}
-
namespace isc {
namespace server_common {
/**
@@ -88,36 +81,49 @@ 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.
- */
+/// \brief Changes current listening addresses and ports.
+///
+/// Removes all sockets we currently listen on and starts listening on the
+/// addresses and ports requested in new_addresses.
+///
+/// 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 doesn't abort the application (to allow reconfiguration),
+/// but removes all the sockets it listened on. One of the exceptions is
+/// propagated.
+///
+/// The ports are requested from the socket creator through boss. Therefore
+/// you need to initialize the SocketRequestor before using this function.
+///
+/// \param new_addresses are the addresses you want to listen on.
+/// \param address_store is the place you store your current addresses. It is
+/// used when there's a need for rollback. The new_addresses are copied
+/// here when the change is successful.
+/// \param dns_service is the DNSService object we use now. The requests from
+/// the new sockets are handled using this dns_service (and all current
+/// sockets on the service are closed first).
+/// \param server_options specifies optional properties for the servers
+/// created via \c dns_service.
+///
+/// \throw asiolink::IOError when initialization or closing of socket fails.
+/// \throw isc::server_common::SocketRequestor::Socket error when the
+/// boss/socket creator doesn't want to give us the socket.
+/// \throw std::bad_alloc when allocation fails.
+/// \throw isc::InvalidOperation when the function is called and the
+/// SocketRequestor isn't initialized yet.
void
-installListenAddresses(const AddressList& newAddresses,
- AddressList& addressStore,
- asiodns::DNSService& dnsService);
+installListenAddresses(const AddressList& new_addresses,
+ AddressList& address_store,
+ asiodns::DNSServiceBase& dns_service,
+ asiodns::DNSService::ServerFlag server_options =
+ asiodns::DNSService::SERVER_DEFAULT);
}
}
}
#endif
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/server_common/server_common_messages.mes b/src/lib/server_common/server_common_messages.mes
index 5fbbb0b..0817928 100644
--- a/src/lib/server_common/server_common_messages.mes
+++ b/src/lib/server_common/server_common_messages.mes
@@ -16,6 +16,31 @@ $NAMESPACE isc::server_common
# \brief Messages for the server_common library
+% SOCKETREQUESTOR_CREATED Socket requestor created for application %1
+Debug message. A socket requesor (client of the socket creator) is created
+for the corresponding application. Normally this should happen at most
+one time throughout the lifetime of the application.
+
+% SOCKETREQUESTOR_DESTROYED Socket requestor destoryed
+Debug message. The socket requestor created at SOCKETREQUESTOR_CREATED
+has been destroyed. This event is generally unexpected other than in
+test cases.
+
+% SOCKETREQUESTOR_GETSOCKET Received a %1 socket for [%2]:%3, FD=%4, token=%5, path=%6
+Debug message. The socket requestor for the corresponding application
+has requested a socket for a set of address, port and protocol (shown
+in the log message) and successfully got it from the creator. The
+corresponding file descriptor and the associated "token" (an internal
+ID used between the creator and requestor) are shown in the log
+message.
+
+% SOCKETREQUESTOR_RELEASESOCKET Released a socket of token %1
+Debug message. The socket requestor has released a socket passed by
+the creator. The associated token of the socket is shown in the
+log message. If the corresponding SOCKETREQUESTOR_GETSOCKET was logged
+more detailed information of the socket can be identified by matching
+the token.
+
% SRVCOMM_ADDRESSES_NOT_LIST the address and port specification is not a list in %1
This points to an error in configuration. What was supposed to be a list of
IP address - port pairs isn't a list at all but something else.
@@ -38,7 +63,7 @@ message. A valid specification contains an address part (which must be a string
and must represent a valid IPv4 or IPv6 address) and port (which must be an
integer in the range valid for TCP/UDP ports on your system).
-% SRVCOMM_ADDRESS_UNRECOVERABLE failed to recover original addresses also (%2)
+% SRVCOMM_ADDRESS_UNRECOVERABLE failed to recover original addresses also (%1)
The recovery of old addresses after SRVCOMM_ADDRESS_FAIL also failed for
the reason listed.
@@ -53,6 +78,22 @@ addresses we are going to listen on (eg. there will be one log message
per pair). This appears only after SRVCOMM_SET_LISTEN, but might
be hidden, as it has higher debug level.
+% SRVCOMM_EXCEPTION_ALLOC exception when allocating a socket: %1
+The process tried to allocate a socket using the socket creator, but an error
+occurred. But it is not one of the errors we are sure are "safe". In this case
+it is unclear if the unsuccessful communication left the process and the bind10
+process in inconsistent state, so the process is going to abort to prevent
+further problems in that area.
+
+This is probably a bug in the code, but it could be caused by other unusual
+conditions (like insufficient memory, deleted socket file used for
+communication).
+
+% SRVCOMM_UNKNOWN_EXCEPTION_ALLOC unknown exception when allocating a socket
+The situation is the same as in the SRVCOMM_EXCEPTION_ALLOC case, but further
+details about the error are unknown, because it was signaled by throwing
+something not being an exception. This is definitely a bug.
+
% SRVCOMM_KEYS_DEINIT deinitializing TSIG keyring
Debug message indicating that the server is deinitializing the TSIG keyring.
diff --git a/src/lib/server_common/socket_request.cc b/src/lib/server_common/socket_request.cc
new file mode 100644
index 0000000..e471ad0
--- /dev/null
+++ b/src/lib/server_common/socket_request.cc
@@ -0,0 +1,428 @@
+// 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 "socket_request.h"
+#include <server_common/logger.h>
+
+#include <config/ccsession.h>
+#include <cc/session.h>
+#include <cc/data.h>
+#include <util/io/fd.h>
+#include <util/io/fd_share.h>
+
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <cerrno>
+#include <csignal>
+#include <cstddef>
+
+namespace isc {
+namespace server_common {
+
+namespace {
+SocketRequestor* requestor(NULL);
+
+// Before the boss process calls send_fd, it first sends this
+// string to indicate success, followed by the file descriptor
+const std::string& CREATOR_SOCKET_OK() {
+ static const std::string str("1\n");
+ return (str);
+}
+
+// Before the boss process calls send_fd, it sends this
+// string to indicate failure. It will not send a file descriptor.
+const std::string& CREATOR_SOCKET_UNAVAILABLE() {
+ static const std::string str("0\n");
+ return (str);
+}
+
+// The name of the ccsession command to request a socket from boss
+// (the actual format of command and response are hardcoded in their
+// respective methods)
+const std::string& REQUEST_SOCKET_COMMAND() {
+ static const std::string str("get_socket");
+ return (str);
+}
+
+// The name of the ccsession command to tell boss we no longer need
+// a socket (the actual format of command and response are hardcoded
+// in their respective methods)
+const std::string& RELEASE_SOCKET_COMMAND() {
+ static const std::string str("drop_socket");
+ return (str);
+}
+
+// RCode constants for the get_token command
+const size_t SOCKET_ERROR_CODE = 2;
+const size_t SHARE_ERROR_CODE = 3;
+
+// A helper converter from numeric protocol ID to the corresponding string.
+// used both for generating a message for the boss process and for logging.
+inline const char*
+protocolString(SocketRequestor::Protocol protocol) {
+ switch (protocol) {
+ case SocketRequestor::TCP:
+ return ("TCP");
+ case SocketRequestor::UDP:
+ return ("UDP");
+ default:
+ return ("unknown protocol");
+ }
+}
+
+// Creates the cc session message to request a socket.
+// The actual command format is hardcoded, and should match
+// the format as read in bind10_src.py.in
+isc::data::ConstElementPtr
+createRequestSocketMessage(SocketRequestor::Protocol protocol,
+ const std::string& address, uint16_t port,
+ SocketRequestor::ShareMode share_mode,
+ const std::string& share_name)
+{
+ const isc::data::ElementPtr request = isc::data::Element::createMap();
+ request->set("address", isc::data::Element::create(address));
+ request->set("port", isc::data::Element::create(port));
+ if (protocol != SocketRequestor::TCP && protocol != SocketRequestor::UDP) {
+ isc_throw(InvalidParameter, "invalid protocol: " << protocol);
+ }
+ request->set("protocol",
+ isc::data::Element::create(protocolString(protocol)));
+ switch (share_mode) {
+ case SocketRequestor::DONT_SHARE:
+ request->set("share_mode", isc::data::Element::create("NO"));
+ break;
+ case SocketRequestor::SHARE_SAME:
+ request->set("share_mode", isc::data::Element::create("SAMEAPP"));
+ break;
+ case SocketRequestor::SHARE_ANY:
+ request->set("share_mode", isc::data::Element::create("ANY"));
+ break;
+ default:
+ isc_throw(InvalidParameter, "invalid share mode: " << share_mode);
+ }
+ request->set("share_name", isc::data::Element::create(share_name));
+
+ return (isc::config::createCommand(REQUEST_SOCKET_COMMAND(), request));
+}
+
+isc::data::ConstElementPtr
+createReleaseSocketMessage(const std::string& token) {
+ const isc::data::ElementPtr release = isc::data::Element::createMap();
+ release->set("token", isc::data::Element::create(token));
+
+ return (isc::config::createCommand(RELEASE_SOCKET_COMMAND(), release));
+}
+
+// Checks and parses the response receive from Boss
+// If successful, token and path will be set to the values found in the
+// answer.
+// If the response was an error response, or does not contain the
+// expected elements, a CCSessionError is raised.
+void
+readRequestSocketAnswer(isc::data::ConstElementPtr recv_msg,
+ std::string& token, std::string& path)
+{
+ int rcode;
+ isc::data::ConstElementPtr answer = isc::config::parseAnswer(rcode,
+ recv_msg);
+ // Translate known rcodes to the corresponding exceptions
+ if (rcode == SOCKET_ERROR_CODE) {
+ isc_throw(SocketRequestor::SocketAllocateError, answer->str());
+ }
+ if (rcode == SHARE_ERROR_CODE) {
+ isc_throw(SocketRequestor::ShareError, answer->str());
+ }
+ // The unknown exceptions
+ if (rcode != 0) {
+ isc_throw(isc::config::CCSessionError,
+ "Error response when requesting socket: " << answer->str());
+ }
+
+ if (!answer || !answer->contains("token") || !answer->contains("path")) {
+ isc_throw(isc::config::CCSessionError,
+ "Malformed answer when requesting socket");
+ }
+ token = answer->get("token")->stringValue();
+ path = answer->get("path")->stringValue();
+}
+
+// Connect to the domain socket that has been received from Boss.
+// (i.e. the one that is used to pass created sockets over).
+//
+// This should only be called if the socket had not been connected to
+// already. To get the socket and reuse existing ones, use
+// getFdShareSocket()
+//
+// \param path The domain socket to connect to
+// \exception SocketError if the socket cannot be connected to
+// \return the socket file descriptor
+int
+createFdShareSocket(const std::string& path) {
+ // TODO: Current master has socketsession code and better way
+ // of handling errors without potential leaks for this. It is
+ // not public there at this moment, but when this is merged
+ // we should make a ticket to move this functionality to the
+ // SocketSessionReceiver and use that.
+ const int sock_pass_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sock_pass_fd == -1) {
+ isc_throw(SocketRequestor::SocketError,
+ "Unable to open domain socket " << path <<
+ ": " << strerror(errno));
+ }
+ struct sockaddr_un sock_pass_addr;
+ sock_pass_addr.sun_family = AF_UNIX;
+ if (path.size() >= sizeof(sock_pass_addr.sun_path)) {
+ close(sock_pass_fd);
+ isc_throw(SocketRequestor::SocketError,
+ "Unable to open domain socket " << path <<
+ ": path too long");
+ }
+#ifdef HAVE_SA_LEN
+ sock_pass_addr.sun_len = path.size();
+#endif
+ strcpy(sock_pass_addr.sun_path, path.c_str());
+ const socklen_t len = path.size() + offsetof(struct sockaddr_un, sun_path);
+ // Yes, C-style cast bad. See previous comment about SocketSessionReceiver.
+ if (connect(sock_pass_fd, (const struct sockaddr*)&sock_pass_addr,
+ len) == -1) {
+ close(sock_pass_fd);
+ isc_throw(SocketRequestor::SocketError,
+ "Unable to open domain socket " << path <<
+ ": " << strerror(errno));
+ }
+ return (sock_pass_fd);
+}
+
+// Reads a socket fd over the given socket (using recv_fd()).
+//
+// \exception SocketError if the socket cannot be read
+// \return the socket fd that has been read
+int
+getSocketFd(const std::string& token, int sock_pass_fd) {
+ // Tell the boss the socket token.
+ const std::string token_data = token + "\n";
+ if (!isc::util::io::write_data(sock_pass_fd, token_data.c_str(),
+ token_data.size())) {
+ isc_throw(SocketRequestor::SocketError, "Error writing socket token");
+ }
+
+ // Boss first sends some data to signal that getting the socket
+ // from its cache succeeded
+ char status[3]; // We need a space for trailing \0, hence 3
+ memset(status, 0, 3);
+ if (isc::util::io::read_data(sock_pass_fd, status, 2) < 2) {
+ isc_throw(SocketRequestor::SocketError,
+ "Error reading status code while requesting socket");
+ }
+ // Actual status value hardcoded by boss atm.
+ if (CREATOR_SOCKET_UNAVAILABLE() == status) {
+ isc_throw(SocketRequestor::SocketError,
+ "CREATOR_SOCKET_UNAVAILABLE returned");
+ } else if (CREATOR_SOCKET_OK() != status) {
+ isc_throw(SocketRequestor::SocketError,
+ "Unknown status code returned before recv_fd '" << status <<
+ "'");
+ }
+
+ const int passed_sock_fd = isc::util::io::recv_fd(sock_pass_fd);
+
+ // check for error values of passed_sock_fd (see fd_share.h)
+ if (passed_sock_fd < 0) {
+ switch (passed_sock_fd) {
+ case isc::util::io::FD_SYSTEM_ERROR:
+ isc_throw(SocketRequestor::SocketError,
+ "FD_SYSTEM_ERROR while requesting socket");
+ break;
+ case isc::util::io::FD_OTHER_ERROR:
+ isc_throw(SocketRequestor::SocketError,
+ "FD_OTHER_ERROR while requesting socket");
+ break;
+ default:
+ isc_throw(SocketRequestor::SocketError,
+ "Unknown error while requesting socket");
+ }
+ }
+ return (passed_sock_fd);
+}
+
+// This implementation class for SocketRequestor uses
+// a CC session for communication with the boss process,
+// and fd_share to read out the socket(s).
+// Since we only use a reference to the session, it must never
+// be closed during the lifetime of this class
+class SocketRequestorCCSession : public SocketRequestor {
+public:
+ SocketRequestorCCSession(cc::AbstractSession& session,
+ const std::string& app_name) :
+ session_(session),
+ app_name_(app_name)
+ {
+ // We need to filter SIGPIPE to prevent it from happening in
+ // getSocketFd() while writing to the UNIX domain socket after the
+ // remote end closed it. See lib/util/io/socketsession for more
+ // background details.
+ // Note: we should eventually unify this level of details into a single
+ // module. Setting a single filter here should be considered a short
+ // term workaround.
+ if (std::signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
+ isc_throw(Unexpected, "Failed to filter SIGPIPE: " <<
+ strerror(errno));
+ }
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, SOCKETREQUESTOR_CREATED).
+ arg(app_name);
+ }
+
+ ~SocketRequestorCCSession() {
+ closeFdShareSockets();
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, SOCKETREQUESTOR_DESTROYED);
+ }
+
+ virtual SocketID requestSocket(Protocol protocol,
+ const std::string& address,
+ uint16_t port, ShareMode share_mode,
+ const std::string& share_name)
+ {
+ const isc::data::ConstElementPtr request_msg =
+ createRequestSocketMessage(protocol, address, port,
+ share_mode,
+ share_name.empty() ? app_name_ :
+ share_name);
+
+ // Send it to boss
+ const int seq = session_.group_sendmsg(request_msg, "Boss");
+
+ // Get the answer from the boss.
+ // Just do a blocking read, we can't really do much anyway
+ isc::data::ConstElementPtr env, recv_msg;
+ if (!session_.group_recvmsg(env, recv_msg, false, seq)) {
+ isc_throw(isc::config::CCSessionError,
+ "Incomplete response when requesting socket");
+ }
+
+ // Read the socket file from the answer
+ std::string token, path;
+ readRequestSocketAnswer(recv_msg, token, path);
+ // get the domain socket over which we will receive the
+ // real socket
+ const int sock_pass_fd = getFdShareSocket(path);
+
+ // and finally get the socket itself
+ const int passed_sock_fd = getSocketFd(token, sock_pass_fd);
+ LOG_DEBUG(logger, DBGLVL_TRACE_DETAIL, SOCKETREQUESTOR_GETSOCKET).
+ arg(protocolString(protocol)).arg(address).arg(port).
+ arg(passed_sock_fd).arg(token).arg(path);
+ return (SocketID(passed_sock_fd, token));
+ }
+
+ virtual void releaseSocket(const std::string& token) {
+ const isc::data::ConstElementPtr release_msg =
+ createReleaseSocketMessage(token);
+
+ // Send it to boss
+ const int seq = session_.group_sendmsg(release_msg, "Boss");
+ LOG_DEBUG(logger, DBGLVL_TRACE_DETAIL, SOCKETREQUESTOR_RELEASESOCKET).
+ arg(token);
+
+ // Get the answer from the boss.
+ // Just do a blocking read, we can't really do much anyway
+ isc::data::ConstElementPtr env, recv_msg;
+ if (!session_.group_recvmsg(env, recv_msg, false, seq)) {
+ isc_throw(isc::config::CCSessionError,
+ "Incomplete response when sending drop socket command");
+ }
+
+ // Answer should just be success
+ int rcode;
+ isc::data::ConstElementPtr error = isc::config::parseAnswer(rcode,
+ recv_msg);
+ if (rcode != 0) {
+ isc_throw(SocketError,
+ "Error requesting release of socket: " << error->str());
+ }
+ }
+
+private:
+ // Returns the domain socket file descriptor
+ // If we had not opened it yet, opens it now
+ int
+ getFdShareSocket(const std::string& path) {
+ if (fd_share_sockets_.find(path) == fd_share_sockets_.end()) {
+ const int new_fd = createFdShareSocket(path);
+ // Technically, the (creation and) assignment of the new map entry
+ // could thrown an exception and lead to FD leak. This should be
+ // cleaned up later (see comment about SocketSessionReceiver above)
+ fd_share_sockets_[path] = new_fd;
+ return (new_fd);
+ } else {
+ return (fd_share_sockets_[path]);
+ }
+ }
+
+ // Closes the sockets that has been used for fd_share
+ void
+ closeFdShareSockets() {
+ for (std::map<std::string, int>::const_iterator it =
+ fd_share_sockets_.begin();
+ it != fd_share_sockets_.end();
+ ++it) {
+ close((*it).second);
+ }
+ }
+
+ cc::AbstractSession& session_;
+ const std::string app_name_;
+ std::map<std::string, int> fd_share_sockets_;
+};
+
+}
+
+SocketRequestor&
+socketRequestor() {
+ if (requestor != NULL) {
+ return (*requestor);
+ } else {
+ isc_throw(InvalidOperation, "The socket requestor is not initialized");
+ }
+}
+
+void
+initSocketRequestor(cc::AbstractSession& session,
+ const std::string& app_name)
+{
+ if (requestor != NULL) {
+ isc_throw(InvalidOperation,
+ "The socket requestor was already initialized");
+ } else {
+ requestor = new SocketRequestorCCSession(session, app_name);
+ }
+}
+
+void
+initTestSocketRequestor(SocketRequestor* new_requestor) {
+ requestor = new_requestor;
+}
+
+void
+cleanupSocketRequestor() {
+ if (requestor != NULL) {
+ delete requestor;
+ requestor = NULL;
+ } else {
+ isc_throw(InvalidOperation, "The socket requestor is not initialized");
+ }
+}
+
+}
+}
diff --git a/src/lib/server_common/socket_request.h b/src/lib/server_common/socket_request.h
new file mode 100644
index 0000000..aac95d1
--- /dev/null
+++ b/src/lib/server_common/socket_request.h
@@ -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.
+
+#ifndef __SOCKET_REQUEST_H
+#define __SOCKET_REQUEST_H 1
+
+#include <exceptions/exceptions.h>
+
+#include <boost/noncopyable.hpp>
+#include <utility>
+#include <string>
+#include <stdint.h>
+
+namespace isc {
+
+namespace cc {
+class AbstractSession;
+};
+
+namespace server_common {
+
+/// \brief A singleton class for requesting sockets
+///
+/// This class allows requesting sockets from the socket creator.
+///
+/// It is considered to be a singleton - a class which is instantiated
+/// at most once in the whole application. This is because it makes no
+/// sense to have two of them.
+///
+/// This is actually an abstract base class. There'll be one with
+/// hidden implementation and we expect the tests to create its own
+/// subclass when needed.
+///
+/// \see socketRequestor function to access the object of this class.
+class SocketRequestor : boost::noncopyable {
+protected:
+ /// \brief Protected constructor
+ ///
+ /// The constructor is protected so this class is not created by accident
+ /// (which it can't anyway, as it has pure virtual methods, but just to
+ /// be sure).
+ SocketRequestor() {}
+
+public:
+ /// \brief virtual destructor
+ ///
+ /// A virtual destructor, as we have virtual methods, to make sure it is
+ /// destroyed by the destructor of the subclass. This shouldn't matter, as
+ /// a singleton class wouldn't get destroyed, but just to be sure.
+ virtual ~ SocketRequestor() {}
+
+ /// \brief A representation of received socket
+ ///
+ /// The pair holds two parts. The OS-level file descriptor acting as the
+ /// socket (you might want to use it directly with functions like recv,
+ /// or fill it into an asio socket). The other part is the token
+ /// representing the socket, which allows it to be given up again.
+ typedef std::pair<int, std::string> SocketID;
+
+ /// \brief The protocol of requested socket
+ ///
+ /// This describes which protocol the socket should have when created.
+ enum Protocol {
+ UDP,
+ TCP
+ };
+
+ /// \brief The share mode of the requested socket
+ ///
+ /// The socket creator is able to "borrow" the same socket to multiple
+ /// applications at once. However, it isn't always what is required. This
+ /// describes the restrains we want to have on our socket regarding the
+ /// sharing. Union of restriction of all requests on the given socket
+ /// is taken (so you still don't have to get your socket even if you
+ /// say SHARE_ANY, because someone else might already asked for the socket
+ /// with DONT_SHARE).
+ enum ShareMode {
+ DONT_SHARE, //< Request an exclusive ownership of the socket.
+ SHARE_SAME, //< It is possible to share the socket with anybody who
+ //< provided the same share_name.
+ SHARE_ANY //< Any sharing is allowed.
+ };
+
+ /// \brief Exception when we can't manipulate a socket
+ ///
+ /// This is thrown if the other side doesn't want to comply to our
+ /// requests, like when we ask for a socket already held by someone
+ /// else or ask for nonsense (releasing a socket we don't own).
+ class SocketError : public Exception {
+ public:
+ SocketError(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ { }
+ };
+
+ /// \brief Exception when we can't return a requested socket, but we're
+ /// sure we could return others
+ ///
+ /// This is thrown if the requested socket can't be granted, but it is only
+ /// that one socket, not that the system would be broken or anything. This
+ /// exception is a common base class for the concrete exceptions actually
+ /// thrown. You can safely keep using the SocketRequestor after this
+ /// exception (or anything derived from it) is thrown.
+ ///
+ /// \see ShareError
+ /// \see SocketAllocateError
+ class NonFatalSocketError : public SocketError {
+ public:
+ NonFatalSocketError(const char* file, size_t line, const char* what) :
+ SocketError(file, line, what)
+ { }
+ };
+
+ /// \brief Exception when the socket is allocated by other bind10 module
+ /// and it doesn't want to share it.
+ ///
+ /// This is thrown if a socket is requested and the socket is already
+ /// allocated by bind10, but other bind10 module(s) is using it and
+ /// the sharing parameters are incompatible (the socket can't be shared
+ /// between the module and our module).
+ class ShareError : public NonFatalSocketError {
+ public:
+ ShareError(const char* file, size_t line, const char* what) :
+ NonFatalSocketError(file, line, what)
+ { }
+ };
+
+ /// \brief Exception when the operating system doesn't allow us to create
+ /// the requested socket.
+ ///
+ /// This happens when the socket() or bind() call fails in the socket
+ /// creator. This can happen when the address/port pair is already taken
+ /// by a different application, the socket creator doesn't have enough
+ /// privileges, or for some kind of similar reason.
+ class SocketAllocateError : public NonFatalSocketError {
+ public:
+ SocketAllocateError(const char* file, size_t line, const char* what) :
+ NonFatalSocketError(file, line, what)
+ { }
+ };
+
+ /// \brief Ask for a socket
+ ///
+ /// Asks the socket creator to give us a socket. The socket will be bound
+ /// to the given address and port.
+ ///
+ /// \param protocol specifies the protocol of the socket. This must be
+ /// either UDP or TCP.
+ /// \param address to which the socket should be bound.
+ /// \param port the port to which the socket should be bound (native endian,
+ /// not network byte order).
+ /// \param share_mode how the socket can be shared with other requests.
+ /// This must be one of the defined values of ShareMode..
+ /// \param share_name the name of sharing group, relevant for SHARE_SAME
+ /// (specified by us or someone else). If left empty (the default),
+ /// the app_name parameter of initSocketRequestor is used. If that one
+ /// is empty as well, it is accepted, but not recommended, as such
+ /// a non-descriptive name has a high chance of collisions between
+ /// applications. Note that you should provide a name (by share_name
+ /// or app_name) even when you set it to DONT_SHARE (for logs and
+ /// debugging) and you need to provide one with SHARE_SAME (to know
+ /// what is same) and SHARE_ANY (someone else might want SHARE_SAME,
+ /// so it would check against this)
+ /// \return the socket, as a file descriptor and token representing it on
+ /// the socket creator side.
+ ///
+ /// To understand the modes better:
+ /// - If mode is DONT_SHARE, it succeeds if no one else has opened an FD
+ /// for requested protocol, address and port.
+ /// - If mode is SHARE_SAME, it succeeds if all applications who opened an
+ /// FD for the requested protocol, address and port provided the same
+ /// share_name as this one and none of them had mode DONT_SHARE.
+ /// - If mode is SHARE_ANY, it succeeds if no applications who requested
+ /// the same potocol, address and port provided DONT_SHARE and all the
+ /// applications who provided SHARE_SAME also provided the same
+ /// share_name as this process did.
+ ///
+ /// \throw InvalidParameter protocol or share_mode is invalid
+ /// \throw CCSessionError when we have a problem talking over the CC
+ /// session.
+ /// \throw SocketError in case we have some other problems receiving the
+ /// socket (eg. inconsistency in the protocol, the socket got stuck
+ /// in the transport, etc). If the exception is not of the following
+ /// derived ones, it usualy means something serious happened.
+ /// \throw SocketAllocateError if the other side can't create the socket.
+ /// \throw ShareError if the socket is used by other bind10 module and
+ /// that one doesn't want to share it with us.
+ virtual SocketID requestSocket(Protocol protocol,
+ const std::string& address,
+ uint16_t port, ShareMode share_mode,
+ const std::string& share_name = "") = 0;
+
+ /// \brief Tell the socket creator we no longer need the socket
+ ///
+ /// Releases the identified socket. This must be called *after*
+ /// the file descriptor was closed on our side. This will allow
+ /// the remote side to either give it to some other application
+ /// or close it, depending on the situation.
+ ///
+ /// \param token the token representing the socket, as received
+ /// in the second part of the requestSocket result.
+ /// \throw CCSessionError when we have a problem talking over the CC
+ /// session.
+ /// \throw SocketError in case the other side doesn't like the
+ /// release (like we're trying to release a socket that doesn't
+ /// belong to us or exist at all).
+ virtual void releaseSocket(const std::string& token) = 0;
+};
+
+/// \brief Access the requestor object.
+///
+/// This returns the singleton object for the Requestor.
+///
+/// \return the active socket requestor object.
+/// \throw InvalidOperation if the object was not yet initialized.
+/// \see SocketRequestor::init to initialize the object.
+SocketRequestor& socketRequestor();
+
+/// \brief Initialize the singleton object
+///
+/// This creates the object that will be used to request sockets.
+/// It can be called only once per the life of application.
+///
+/// \param session the CC session that'll be used to talk to the
+/// socket creator.
+/// \param app_name default share name if one is not provided with
+/// requestSocket. You can leave this as empty string,
+/// but then you should provide a reasonably descriptive
+/// name to requestSocket. Empty names work like any others,
+/// but have a high chance of collisions, so it is recommended
+/// to avoid them and provide the name of the application
+/// here.
+/// \throw InvalidOperation when it is called more than once
+void initSocketRequestor(cc::AbstractSession& session,
+ const std::string& app_name);
+
+/// \brief Initialization for tests
+///
+/// This is to support different subclasses in tests. It replaces
+/// the object used by socketRequestor() function by this one provided
+/// as parameter. The ownership is not taken, eg. it's up to the caller
+/// to delete it when necessary.
+///
+/// This is not to be used in production applications. It is meant as
+/// an replacement of init.
+///
+/// This never throws.
+///
+/// \param requestor the object to be used. It can be NULL to reset to
+/// an "virgin" state (which acts as if initTest or init was never
+/// called before).
+void initTestSocketRequestor(SocketRequestor* requestor);
+
+/// \brief Destroy the singleton instance
+///
+/// Calling this function is not strictly necessary; the socket
+/// requestor is a singleton anyway. However, for some tests it
+/// is useful to destroy and recreate it, as well as for programs
+/// that want to be completely clean on exit.
+/// After this function has been called, all operations except init
+/// will fail.
+void cleanupSocketRequestor();
+
+}
+}
+
+#endif // __SOCKET_REQUEST_H
diff --git a/src/lib/server_common/tests/.gitignore b/src/lib/server_common/tests/.gitignore
new file mode 100644
index 0000000..0749d37
--- /dev/null
+++ b/src/lib/server_common/tests/.gitignore
@@ -0,0 +1,2 @@
+/data_path.h
+/run_unittests
diff --git a/src/lib/server_common/tests/Makefile.am b/src/lib/server_common/tests/Makefile.am
index d7e113a..b059d47 100644
--- a/src/lib/server_common/tests/Makefile.am
+++ b/src/lib/server_common/tests/Makefile.am
@@ -29,6 +29,7 @@ run_unittests_SOURCES = run_unittests.cc
run_unittests_SOURCES += client_unittest.cc
run_unittests_SOURCES += portconfig_unittest.cc
run_unittests_SOURCES += keyring_test.cc
+run_unittests_SOURCES += socket_requestor_test.cc
nodist_run_unittests_SOURCES = data_path.h
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
diff --git a/src/lib/server_common/tests/client_unittest.cc b/src/lib/server_common/tests/client_unittest.cc
index 287a926..c8db846 100644
--- a/src/lib/server_common/tests/client_unittest.cc
+++ b/src/lib/server_common/tests/client_unittest.cc
@@ -12,6 +12,7 @@
// 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 <string.h>
diff --git a/src/lib/server_common/tests/portconfig_unittest.cc b/src/lib/server_common/tests/portconfig_unittest.cc
index 65963eb..ac880c0 100644
--- a/src/lib/server_common/tests/portconfig_unittest.cc
+++ b/src/lib/server_common/tests/portconfig_unittest.cc
@@ -12,22 +12,30 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <gtest/gtest.h>
+
#include <server_common/portconfig.h>
+#include <testutils/socket_request.h>
+#include <testutils/mockups.h>
+
+#include <util/unittests/resource.h>
#include <cc/data.h>
#include <exceptions/exceptions.h>
#include <asiolink/asiolink.h>
#include <asiodns/asiodns.h>
-#include <gtest/gtest.h>
#include <string>
using namespace isc::server_common::portconfig;
+using namespace isc::server_common;
using namespace isc::data;
using namespace isc;
using namespace std;
using namespace isc::asiolink;
using namespace isc::asiodns;
+using namespace isc::testutils;
+using boost::lexical_cast;
namespace {
@@ -129,26 +137,31 @@ TEST_F(ParseAddresses, invalid) {
// Test fixture for installListenAddresses
struct InstallListenAddresses : public ::testing::Test {
InstallListenAddresses() :
- dnss_(ios_, NULL, NULL, NULL)
+ // The empty string is expected parameter of requestSocket,
+ // not app_name - the request does not fall back to this, it
+ // is checked to be the same.
+ sock_requestor_(dnss_, store_, 5288, "")
{
valid_.push_back(AddressPair("127.0.0.1", 5288));
valid_.push_back(AddressPair("::1", 5288));
+ invalid_.push_back(AddressPair("127.0.0.1", 5288));
invalid_.push_back(AddressPair("192.0.2.2", 1));
}
- IOService ios_;
- DNSService dnss_;
+ MockDNSService dnss_;
AddressList store_;
+ isc::testutils::TestSocketRequestor sock_requestor_;
// 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) {
+ void checkAddresses(const AddressList& expected, const string& name) const
+ {
SCOPED_TRACE(name);
ASSERT_EQ(expected.size(), store_.size()) <<
"Different amount of elements, not checking content";
- // Run in parallel trough the vectors
+ // Run in parallel through the vectors
for (AddressList::const_iterator ei(expected.begin()),
si(store_.begin()); ei != expected.end(); ++ei, ++si) {
EXPECT_EQ(ei->first, si->first);
@@ -158,17 +171,46 @@ struct InstallListenAddresses : public ::testing::Test {
};
// Try switching valid addresses
+// Check the sockets are correctly requested and returned
TEST_F(InstallListenAddresses, valid) {
// First, bind to the valid addresses
EXPECT_NO_THROW(installListenAddresses(valid_, store_, dnss_));
checkAddresses(valid_, "Valid addresses");
+ const char* tokens1[] = {
+ "TCP:127.0.0.1:5288:1",
+ "UDP:127.0.0.1:5288:2",
+ "TCP:::1:5288:3",
+ "UDP:::1:5288:4",
+ NULL
+ };
+ const char* no_tokens[] = { NULL };
+ sock_requestor_.checkTokens(tokens1, sock_requestor_.given_tokens_,
+ "Valid given tokens 1");
+ sock_requestor_.checkTokens(no_tokens, sock_requestor_.released_tokens_,
+ "Valid no released tokens 1");
// TODO Maybe some test to actually connect to them
// Try setting it back to nothing
+ sock_requestor_.given_tokens_.clear();
EXPECT_NO_THROW(installListenAddresses(AddressList(), store_, dnss_));
checkAddresses(AddressList(), "No addresses");
+ sock_requestor_.checkTokens(no_tokens, sock_requestor_.given_tokens_,
+ "Valid no given tokens");
+ sock_requestor_.checkTokens(tokens1, sock_requestor_.released_tokens_,
+ "Valid released tokens");
// Try switching back again
EXPECT_NO_THROW(installListenAddresses(valid_, store_, dnss_));
checkAddresses(valid_, "Valid addresses");
+ const char* tokens2[] = {
+ "TCP:127.0.0.1:5288:5",
+ "UDP:127.0.0.1:5288:6",
+ "TCP:::1:5288:7",
+ "UDP:::1:5288:8",
+ NULL
+ };
+ sock_requestor_.checkTokens(tokens2, sock_requestor_.given_tokens_,
+ "Valid given tokens 2");
+ sock_requestor_.checkTokens(tokens1, sock_requestor_.released_tokens_,
+ "Valid released tokens");
}
// Try if rollback works
@@ -176,9 +218,138 @@ TEST_F(InstallListenAddresses, rollback) {
// Set some addresses
EXPECT_NO_THROW(installListenAddresses(valid_, store_, dnss_));
checkAddresses(valid_, "Before rollback");
+ const char* tokens1[] = {
+ "TCP:127.0.0.1:5288:1",
+ "UDP:127.0.0.1:5288:2",
+ "TCP:::1:5288:3",
+ "UDP:::1:5288:4",
+ NULL
+ };
+ const char* no_tokens[] = { NULL };
+ sock_requestor_.checkTokens(tokens1, sock_requestor_.given_tokens_,
+ "Given before rollback");
+ sock_requestor_.checkTokens(no_tokens, sock_requestor_.released_tokens_,
+ "Released before rollback");
+ sock_requestor_.given_tokens_.clear();
// This should not bind them, but should leave the original addresses
- EXPECT_THROW(installListenAddresses(invalid_, store_, dnss_), exception);
+ EXPECT_THROW(installListenAddresses(invalid_, store_, dnss_),
+ SocketRequestor::SocketError);
checkAddresses(valid_, "After rollback");
+ // Now, it should have requested first pair of sockets from the invalids
+ // and, as the second failed, it should have returned them right away.
+ const char* released1[] = {
+ "TCP:127.0.0.1:5288:1",
+ "UDP:127.0.0.1:5288:2",
+ "TCP:::1:5288:3",
+ "UDP:::1:5288:4",
+ "TCP:127.0.0.1:5288:5",
+ "UDP:127.0.0.1:5288:6",
+ NULL
+ };
+ // It should request the first pair of sockets, and then request the
+ // complete set of valid addresses to rollback
+ const char* tokens2[] = {
+ "TCP:127.0.0.1:5288:5",
+ "UDP:127.0.0.1:5288:6",
+ "TCP:127.0.0.1:5288:7",
+ "UDP:127.0.0.1:5288:8",
+ "TCP:::1:5288:9",
+ "UDP:::1:5288:10",
+ NULL
+ };
+ sock_requestor_.checkTokens(tokens2, sock_requestor_.given_tokens_,
+ "Given after rollback");
+ sock_requestor_.checkTokens(released1, sock_requestor_.released_tokens_,
+ "Released after rollback");
+}
+
+// Try it at least releases everything when even the rollback fails.
+TEST_F(InstallListenAddresses, brokenRollback) {
+ EXPECT_NO_THROW(installListenAddresses(valid_, store_, dnss_));
+ checkAddresses(valid_, "Before rollback");
+ // Don't check the tokens now, we already do it in rollback and valid tests
+ sock_requestor_.given_tokens_.clear();
+ sock_requestor_.break_rollback_ = true;
+ EXPECT_THROW(installListenAddresses(invalid_, store_, dnss_),
+ SocketRequestor::NonFatalSocketError);
+ // No addresses here
+ EXPECT_TRUE(store_.empty());
+ // The first pair should be requested in the first part of the failure to
+ // bind and the second pair in the first part of rollback
+ const char* tokens[] = {
+ "TCP:127.0.0.1:5288:5",
+ "UDP:127.0.0.1:5288:6",
+ "TCP:127.0.0.1:5288:7",
+ "UDP:127.0.0.1:5288:8",
+ NULL
+ };
+ // The first set should be released, as well as all the ones we request now
+ const char* released[] = {
+ "TCP:127.0.0.1:5288:1",
+ "UDP:127.0.0.1:5288:2",
+ "TCP:::1:5288:3",
+ "UDP:::1:5288:4",
+ "TCP:127.0.0.1:5288:5",
+ "UDP:127.0.0.1:5288:6",
+ "TCP:127.0.0.1:5288:7",
+ "UDP:127.0.0.1:5288:8",
+ NULL
+ };
+ sock_requestor_.checkTokens(tokens, sock_requestor_.given_tokens_,
+ "given");
+ sock_requestor_.checkTokens(released, sock_requestor_.released_tokens_,
+ "released");
+}
+
+// Make sure the death tests are filterable away.
+typedef InstallListenAddresses InstallListenAddressesDeathTest;
+
+// There are systems which don't have EXPECT_DEATH. We skip the tests there.
+// We're lucky, EXPECT_DEATH is a macro, so we can test for its existence this
+// easily.
+#ifdef EXPECT_DEATH
+// We make the socket requestor throw a "fatal" exception, one where we can't be
+// sure the state between processes is consistent. So we abort in that case.
+TEST_F(InstallListenAddressesDeathTest, inconsistent) {
+ AddressList deathAddresses;
+ deathAddresses.push_back(AddressPair("192.0.2.3", 5288));
+ // Make sure it actually kills the application (there should be an abort
+ // in this case)
+ EXPECT_DEATH({
+ isc::util::unittests::dontCreateCoreDumps();
+
+ try {
+ installListenAddresses(deathAddresses, store_, dnss_);
+ } catch (...) {
+ // Prevent exceptions killing the application, we need
+ // to make sure it dies the real hard way
+ };
+ }, "");
+}
+
+// If we are unable to tell the boss we closed a socket, we abort, as we are
+// not consistent with the boss most probably.
+TEST_F(InstallListenAddressesDeathTest, cantClose) {
+ installListenAddresses(valid_, store_, dnss_);
+ AddressList empty;
+ // Instruct it to fail on close
+ sock_requestor_.break_release_ = true;
+ EXPECT_DEATH({
+ isc::util::unittests::dontCreateCoreDumps();
+
+ try {
+ // Setting to empty will close all current sockets.
+ // And thanks to the break_release_, the close will
+ // throw, which will make it crash.
+ installListenAddresses(empty, store_, dnss_);
+ } catch (...) {
+ // To make sure it is killed by abort, not by some
+ // (unhandled) exception
+ };
+ }, "");
+ // And reset it back, so it can safely clean up itself.
+ sock_requestor_.break_release_ = false;
}
+#endif // EXPECT_DEATH
}
diff --git a/src/lib/server_common/tests/socket_requestor_test.cc b/src/lib/server_common/tests/socket_requestor_test.cc
new file mode 100644
index 0000000..829b6d9
--- /dev/null
+++ b/src/lib/server_common/tests/socket_requestor_test.cc
@@ -0,0 +1,587 @@
+// 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 <server_common/socket_request.h>
+
+#include <gtest/gtest.h>
+
+#include <config/tests/fake_session.h>
+#include <config/ccsession.h>
+#include <exceptions/exceptions.h>
+
+#include <server_common/tests/data_path.h>
+
+#include <cstdlib>
+#include <cstddef>
+#include <cerrno>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <util/io/fd.h>
+#include <util/io/fd_share.h>
+
+using namespace isc::data;
+using namespace isc::config;
+using namespace isc::server_common;
+using namespace isc;
+
+namespace {
+
+// Check it throws an exception when it is not initialized
+TEST(SocketRequestorAccess, unitialized) {
+ // Make sure it is not initialized
+ initTestSocketRequestor(NULL);
+ EXPECT_THROW(socketRequestor(), InvalidOperation);
+}
+
+// It returns whatever it is initialized to
+TEST(SocketRequestorAccess, initialized) {
+ // A concrete implementation that does nothing, just can exist
+ class DummyRequestor : public SocketRequestor {
+ public:
+ DummyRequestor() : SocketRequestor() {}
+ virtual void releaseSocket(const std::string&) {}
+ virtual SocketID requestSocket(Protocol, const std::string&, uint16_t,
+ ShareMode, const std::string&)
+ {
+ return (SocketID(0, "")); // Just to silence warnings
+ }
+ };
+ DummyRequestor requestor;
+ // Make sure it is initialized (the test way, of course)
+ initTestSocketRequestor(&requestor);
+ // It returs the same "pointer" as inserted
+ // The casts are there as the template system seemed to get confused
+ // without them, the types should be correct even without them, but
+ // the EXPECT_EQ wanted to use long long int instead of pointers.
+ EXPECT_EQ(static_cast<const SocketRequestor*>(&requestor),
+ static_cast<const SocketRequestor*>(&socketRequestor()));
+ // Just that we don't have an invalid pointer anyway
+ initTestSocketRequestor(NULL);
+}
+
+// This class contains a fake (module)ccsession to emulate answers from Boss
+class SocketRequestorTest : public ::testing::Test {
+public:
+ SocketRequestorTest() : session(ElementPtr(new ListElement),
+ ElementPtr(new ListElement),
+ ElementPtr(new ListElement))
+ {
+ initSocketRequestor(session, "tests");
+ }
+
+ ~SocketRequestorTest() {
+ cleanupSocketRequestor();
+ }
+
+ // Do a standard request with some default values
+ SocketRequestor::SocketID
+ doRequest() {
+ return (socketRequestor().requestSocket(SocketRequestor::UDP,
+ "192.0.2.1", 12345,
+ SocketRequestor::DONT_SHARE,
+ "test"));
+ }
+
+ // Creates a valid socket request answer, as it would be sent by
+ // Boss. 'valid' in terms of format, not values
+ void
+ addAnswer(const std::string& token, const std::string& path) {
+ ElementPtr answer_part = Element::createMap();
+ answer_part->set("token", Element::create(token));
+ answer_part->set("path", Element::create(path));
+ session.getMessages()->add(createAnswer(0, answer_part));
+ }
+
+ // Clears the messages the client sent so far on the fake msgq
+ // (for easier access to new messages later)
+ void
+ clearMsgQueue() {
+ while (session.getMsgQueue()->size() > 0) {
+ session.getMsgQueue()->remove(0);
+ }
+ }
+
+ isc::cc::FakeSession session;
+ const std::string specfile;
+};
+
+// helper function to create the request packet as we expect the
+// socket requestor to send
+ConstElementPtr
+createExpectedRequest(const std::string& address,
+ int port,
+ const std::string& protocol,
+ const std::string& share_mode,
+ const std::string& share_name)
+{
+ // create command arguments
+ const ElementPtr command_args = Element::createMap();
+ command_args->set("address", Element::create(address));
+ command_args->set("port", Element::create(port));
+ command_args->set("protocol", Element::create(protocol));
+ command_args->set("share_mode", Element::create(share_mode));
+ command_args->set("share_name", Element::create(share_name));
+
+ // create the envelope
+ const ElementPtr packet = Element::createList();
+ packet->add(Element::create("Boss"));
+ packet->add(Element::create("*"));
+ packet->add(createCommand("get_socket", command_args));
+
+ return (packet);
+}
+
+TEST_F(SocketRequestorTest, testSocketRequestMessages) {
+ // For each request, it will raise CCSessionError, since we don't
+ // answer here.
+ // We are only testing the request messages that are sent,
+ // so for this test that is no problem
+ clearMsgQueue();
+ ConstElementPtr expected_request;
+
+ expected_request = createExpectedRequest("192.0.2.1", 12345, "UDP",
+ "NO", "test");
+ EXPECT_THROW(socketRequestor().requestSocket(SocketRequestor::UDP,
+ "192.0.2.1", 12345,
+ SocketRequestor::DONT_SHARE,
+ "test"),
+ CCSessionError);
+ ASSERT_EQ(1, session.getMsgQueue()->size());
+ EXPECT_EQ(*expected_request, *(session.getMsgQueue()->get(0)));
+
+ clearMsgQueue();
+ expected_request = createExpectedRequest("192.0.2.2", 1, "TCP",
+ "ANY", "test2");
+ EXPECT_THROW(socketRequestor().requestSocket(SocketRequestor::TCP,
+ "192.0.2.2", 1,
+ SocketRequestor::SHARE_ANY,
+ "test2"),
+ CCSessionError);
+ ASSERT_EQ(1, session.getMsgQueue()->size());
+ EXPECT_EQ(*expected_request, *(session.getMsgQueue()->get(0)));
+
+ clearMsgQueue();
+ expected_request = createExpectedRequest("::1", 2, "UDP",
+ "SAMEAPP", "test3");
+ EXPECT_THROW(socketRequestor().requestSocket(SocketRequestor::UDP,
+ "::1", 2,
+ SocketRequestor::SHARE_SAME,
+ "test3"),
+ CCSessionError);
+ ASSERT_EQ(1, session.getMsgQueue()->size());
+ EXPECT_EQ(*expected_request, *(session.getMsgQueue()->get(0)));
+
+ // A default share name equal to the app name passed on construction
+ clearMsgQueue();
+ expected_request = createExpectedRequest("::1", 2, "UDP",
+ "SAMEAPP", "tests");
+ EXPECT_THROW(socketRequestor().requestSocket(SocketRequestor::UDP,
+ "::1", 2,
+ SocketRequestor::SHARE_SAME),
+ CCSessionError);
+ ASSERT_EQ(1, session.getMsgQueue()->size());
+ EXPECT_EQ(*expected_request, *(session.getMsgQueue()->get(0)));
+}
+
+TEST_F(SocketRequestorTest, invalidParameterForSocketRequest) {
+ // Bad protocol
+ EXPECT_THROW(socketRequestor().
+ requestSocket(static_cast<SocketRequestor::Protocol>(2),
+ "192.0.2.1", 12345,
+ SocketRequestor::DONT_SHARE,
+ "test"),
+ InvalidParameter);
+
+ // Bad share mode
+ EXPECT_THROW(socketRequestor().
+ requestSocket(SocketRequestor::UDP,
+ "192.0.2.1", 12345,
+ static_cast<SocketRequestor::ShareMode>(3),
+ "test"),
+ InvalidParameter);
+}
+
+TEST_F(SocketRequestorTest, testBadRequestAnswers) {
+ // Test various scenarios where the requestor gets back bad answers
+
+ // Should raise CCSessionError if there is no answer
+ EXPECT_THROW(doRequest(), CCSessionError);
+
+ // Also if the answer does not match the format
+ session.getMessages()->add(createAnswer());
+ EXPECT_THROW(doRequest(), CCSessionError);
+
+ // Now a 'real' answer, should fail on socket connect (no such file)
+ addAnswer("foo", "/does/not/exist");
+ EXPECT_THROW(doRequest(), SocketRequestor::SocketError);
+
+ // Another failure (domain socket path too long)
+ addAnswer("foo", std::string(1000, 'x'));
+ EXPECT_THROW(doRequest(), SocketRequestor::SocketError);
+
+ // Test values around path boundary
+ struct sockaddr_un sock_un;
+ const std::string max_len(sizeof(sock_un.sun_path) - 1, 'x');
+ addAnswer("foo", max_len);
+ // The failure should NOT contain 'too long'
+ // (explicitly checking for existance of nonexistence of 'too long',
+ // as opposed to the actual error, since 'too long' is a value we set).
+ try {
+ doRequest();
+ FAIL() << "doRequest did not throw an exception";
+ } catch (const SocketRequestor::SocketError& se) {
+ EXPECT_EQ(std::string::npos, std::string(se.what()).find("too long"));
+ }
+
+ const std::string too_long(sizeof(sock_un.sun_path), 'x');
+ addAnswer("foo", too_long);
+ // The failure SHOULD contain 'too long'
+ try {
+ doRequest();
+ FAIL() << "doRequest did not throw an exception";
+ } catch (const SocketRequestor::SocketError& se) {
+ EXPECT_NE(std::string::npos, std::string(se.what()).find("too long"));
+ }
+
+ // Send back an error response
+ // A generic one first
+ session.getMessages()->add(createAnswer(1, "error"));
+ EXPECT_THROW(doRequest(), CCSessionError);
+ // Now some with specific exceptions
+ session.getMessages()->add(createAnswer(2, "error"));
+ EXPECT_THROW(doRequest(), SocketRequestor::SocketAllocateError);
+ session.getMessages()->add(createAnswer(3, "error"));
+ EXPECT_THROW(doRequest(), SocketRequestor::ShareError);
+}
+
+// Helper function to create the release commands as we expect
+// them to be sent by the SocketRequestor class
+ConstElementPtr
+createExpectedRelease(const std::string& token) {
+ // create command arguments
+ const ElementPtr command_args = Element::createMap();
+ command_args->set("token", Element::create(token));
+
+ // create the envelope
+ const ElementPtr packet = Element::createList();
+ packet->add(Element::create("Boss"));
+ packet->add(Element::create("*"));
+ packet->add(createCommand("drop_socket", command_args));
+
+ return (packet);
+}
+
+TEST_F(SocketRequestorTest, testSocketReleaseMessages) {
+ ConstElementPtr expected_release;
+
+ session.getMessages()->add(createAnswer());
+
+ clearMsgQueue();
+ expected_release = createExpectedRelease("foo");
+ socketRequestor().releaseSocket("foo");
+ ASSERT_EQ(1, session.getMsgQueue()->size());
+ EXPECT_EQ(*expected_release, *(session.getMsgQueue()->get(0)));
+
+ session.getMessages()->add(createAnswer());
+ clearMsgQueue();
+ expected_release = createExpectedRelease("bar");
+ socketRequestor().releaseSocket("bar");
+ ASSERT_EQ(1, session.getMsgQueue()->size());
+ EXPECT_EQ(*expected_release, *(session.getMsgQueue()->get(0)));
+}
+
+TEST_F(SocketRequestorTest, testBadSocketReleaseAnswers) {
+ // Should fail if there is no answer at all
+ EXPECT_THROW(socketRequestor().releaseSocket("bar"),
+ CCSessionError);
+
+ // Should also fail if the answer is an error
+ session.getMessages()->add(createAnswer(1, "error"));
+ EXPECT_THROW(socketRequestor().releaseSocket("bar"),
+ SocketRequestor::SocketError);
+}
+
+// A helper function to impose a read timeout for the server socket
+// in order to avoid deadlock when the client side has a bug and doesn't
+// send expected data.
+// It returns true when the timeout is set successfully; otherwise false.
+bool
+setRecvTimo(int s) {
+ const struct timeval timeo = { 10, 0 }; // 10sec, arbitrary choice
+ if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)) == 0) {
+ return (true);
+ }
+ if (errno == ENOPROTOOPT) { // deviant OS, give up using it.
+ return (false);
+ }
+ isc_throw(isc::Unexpected, "set RCVTIMEO failed: " << strerror(errno));
+}
+
+// Helper test class that creates a randomly named domain socket
+// Upon init, it will only reserve the name (and place an empty file in its
+// place).
+// When run() is called, it creates the socket, forks, and the child will
+// listen for a connection, then send all the data passed to run to that
+// connection, and then close the socket
+class TestSocket {
+public:
+ TestSocket() : fd_(-1) {
+ path_ = strdup("test_socket.XXXXXX");
+ // Misuse mkstemp to generate a file name.
+ const int f = mkstemp(path_);
+ if (f == -1) {
+ isc_throw(Unexpected, "mkstemp failed: " << strerror(errno));
+ }
+ // Just need the name, so immediately close
+ close(f);
+ }
+
+ ~TestSocket() {
+ cleanup();
+ }
+
+ void
+ cleanup() {
+ unlink(path_);
+ if (path_ != NULL) {
+ free(path_);
+ path_ = NULL;
+ }
+ if (fd_ != -1) {
+ close(fd_);
+ fd_ = -1;
+ }
+ }
+
+ // Returns the path used for the socket
+ const char* getPath() const {
+ return (path_);
+ }
+
+ // create socket, fork, and serve if child (child will exit when done).
+ // If the underlying system doesn't allow to set read timeout, tell the
+ // caller that via a false return value so that the caller can avoid
+ // performing tests that could result in a dead lock.
+ bool run(const std::vector<std::pair<std::string, int> >& data) {
+ create();
+ const bool timo_ok = setRecvTimo(fd_);
+ const int child_pid = fork();
+ if (child_pid == 0) {
+ serve(data);
+ exit(0);
+ } else {
+ // parent does not need fd anymore
+ close(fd_);
+ fd_ = -1;
+ }
+ return (timo_ok);
+ }
+private:
+ // Actually create the socket and listen on it
+ void
+ create() {
+ fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd_ == -1) {
+ isc_throw(Unexpected, "Unable to create socket");
+ }
+ struct sockaddr_un socket_address;
+ socket_address.sun_family = AF_UNIX;
+ socklen_t len = strlen(path_);
+ if (len > sizeof(socket_address.sun_path)) {
+ isc_throw(Unexpected,
+ "mkstemp() created a filename too long for sun_path");
+ }
+ strncpy(socket_address.sun_path, path_, len);
+#ifdef HAVE_SA_LEN
+ socket_address.sun_len = len;
+#endif
+
+ len += offsetof(struct sockaddr_un, sun_path);
+ // Remove the random file we created so we can reuse it for
+ // a domain socket connection. This contains a minor race condition
+ // but for the purposes of this test it should be small enough
+ unlink(path_);
+ if (bind(fd_, (const struct sockaddr*)&socket_address, len) == -1) {
+ isc_throw(Unexpected,
+ "unable to bind to test domain socket " << path_ <<
+ ": " << strerror(errno));
+ }
+
+ if (listen(fd_, 1) == -1) {
+ isc_throw(Unexpected,
+ "unable to listen on test domain socket " << path_ <<
+ ": " << strerror(errno));
+ }
+ }
+
+ // Accept one connection, then for each value of the vector,
+ // read the socket token from the connection and match the string
+ // part of the vector element, and send the integer part of the element
+ // using send_fd() (prepended by a status code 'ok'). For simplicity
+ // we assume the tokens are 4 bytes long; if the test case uses a
+ // different size of token the test will fail.
+ //
+ // There are a few specific exceptions;
+ // when the value is -1, it will send back an error value (signaling
+ // CREATOR_SOCKET_UNAVAILABLE)
+ // when the value is -2, it will send a byte signaling CREATOR_SOCKET_OK
+ // first, and then one byte from some string (i.e. bad data, not using
+ // send_fd())
+ //
+ // NOTE: client_fd could leak on exception. This should be cleaned up.
+ // See the note about SocketSessionReceiver in socket_request.cc.
+ void
+ serve(const std::vector<std::pair<std::string, int> > data) {
+ const int client_fd = accept(fd_, NULL, NULL);
+ if (client_fd == -1) {
+ isc_throw(Unexpected, "Error in accept(): " << strerror(errno));
+ }
+ if (!setRecvTimo(client_fd)) {
+ // In the loop below we do blocking read. To avoid deadlock
+ // when the parent is buggy we'll skip it unless we can
+ // set a read timeout on the socket.
+ return;
+ }
+ typedef std::pair<std::string, int> DataPair;
+ BOOST_FOREACH(DataPair cur_data, data) {
+ char buf[5];
+ memset(buf, 0, 5);
+ if (isc::util::io::read_data(client_fd, buf, 4) != 4) {
+ isc_throw(Unexpected, "unable to receive socket token");
+ }
+ if (cur_data.first != buf) {
+ isc_throw(Unexpected, "socket token mismatch: expected="
+ << cur_data.first << ", actual=" << buf);
+ }
+
+ bool result;
+ if (cur_data.second == -1) {
+ // send 'CREATOR_SOCKET_UNAVAILABLE'
+ result = isc::util::io::write_data(client_fd, "0\n", 2);
+ } else if (cur_data.second == -2) {
+ // send 'CREATOR_SOCKET_OK' first
+ result = isc::util::io::write_data(client_fd, "1\n", 2);
+ if (result) {
+ if (send(client_fd, "a", 1, 0) != 1) {
+ result = false;
+ }
+ }
+ } else {
+ // send 'CREATOR_SOCKET_OK' first
+ result = isc::util::io::write_data(client_fd, "1\n", 2);
+ if (result) {
+ if (isc::util::io::send_fd(client_fd,
+ cur_data.second) != 0) {
+ result = false;
+ }
+ }
+ }
+ if (!result) {
+ isc_throw(Exception, "Error in send_fd(): " <<
+ strerror(errno));
+ }
+ }
+ close(client_fd);
+ }
+
+ int fd_;
+ char* path_;
+};
+
+TEST_F(SocketRequestorTest, testSocketPassing) {
+ TestSocket ts;
+ std::vector<std::pair<std::string, int> > data;
+ data.push_back(std::pair<std::string, int>("foo\n", 1));
+ data.push_back(std::pair<std::string, int>("bar\n", 2));
+ data.push_back(std::pair<std::string, int>("foo\n", 3));
+ data.push_back(std::pair<std::string, int>("foo\n", 1));
+ data.push_back(std::pair<std::string, int>("foo\n", -1));
+ data.push_back(std::pair<std::string, int>("foo\n", -2));
+
+ // run() returns true iff we can specify read timeout so we avoid a
+ // deadlock. Unless there's a bug the test should succeed even without the
+ // timeout, but we don't want to make the test hang up in case with an
+ // unexpected bug, so we'd rather skip most of the tests in that case.
+ const bool timo_ok = ts.run(data);
+ SocketRequestor::SocketID socket_id;
+ if (timo_ok) {
+ // 1 should be ok
+ addAnswer("foo", ts.getPath());
+ socket_id = doRequest();
+ EXPECT_EQ("foo", socket_id.second);
+ EXPECT_EQ(0, close(socket_id.first));
+
+ // 2 should be ok too
+ addAnswer("bar", ts.getPath());
+ socket_id = doRequest();
+ EXPECT_EQ("bar", socket_id.second);
+ EXPECT_EQ(0, close(socket_id.first));
+
+ // 3 should be ok too (reuse earlier token)
+ addAnswer("foo", ts.getPath());
+ socket_id = doRequest();
+ EXPECT_EQ("foo", socket_id.second);
+ EXPECT_EQ(0, close(socket_id.first));
+ }
+
+ // Create a second socket server, to test that multiple different
+ // domains sockets would work as well (even though we don't actually
+ // use that feature)
+ TestSocket ts2;
+ std::vector<std::pair<std::string, int> > data2;
+ data2.push_back(std::pair<std::string, int>("foo\n", 1));
+ const bool timo_ok2 = ts2.run(data2);
+
+ if (timo_ok2) {
+ // 1 should be ok
+ addAnswer("foo", ts2.getPath());
+ socket_id = doRequest();
+ EXPECT_EQ("foo", socket_id.second);
+ EXPECT_EQ(0, close(socket_id.first));
+ }
+
+ if (timo_ok) {
+ // Now use first socket again
+ addAnswer("foo", ts.getPath());
+ socket_id = doRequest();
+ EXPECT_EQ("foo", socket_id.second);
+ EXPECT_EQ(0, close(socket_id.first));
+
+ // -1 is a "normal" error
+ addAnswer("foo", ts.getPath());
+ EXPECT_THROW(doRequest(), SocketRequestor::SocketError);
+
+ // -2 is an unexpected error. After this point it's not guaranteed the
+ // connection works as intended.
+ addAnswer("foo", ts.getPath());
+ EXPECT_THROW(doRequest(), SocketRequestor::SocketError);
+ }
+
+ // Vector is of first socket is now empty, so the socket should be gone
+ addAnswer("foo", ts.getPath());
+ EXPECT_THROW(doRequest(), SocketRequestor::SocketError);
+
+ // Vector is of second socket is now empty too, so the socket should be
+ // gone
+ addAnswer("foo", ts2.getPath());
+ EXPECT_THROW(doRequest(), SocketRequestor::SocketError);
+}
+
+}
diff --git a/src/lib/statistics/tests/.gitignore b/src/lib/statistics/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/lib/statistics/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/lib/testutils/Makefile.am b/src/lib/testutils/Makefile.am
index a511d24..7a4c8d7 100644
--- a/src/lib/testutils/Makefile.am
+++ b/src/lib/testutils/Makefile.am
@@ -14,4 +14,4 @@ libtestutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
libtestutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libasiolink.la
endif
-EXTRA_DIST = portconfig.h
+EXTRA_DIST = portconfig.h socket_request.h
diff --git a/src/lib/testutils/dnsmessage_test.cc b/src/lib/testutils/dnsmessage_test.cc
index af354d5..ec6914d 100644
--- a/src/lib/testutils/dnsmessage_test.cc
+++ b/src/lib/testutils/dnsmessage_test.cc
@@ -23,6 +23,12 @@
#include <testutils/dnsmessage_test.h>
+#include <boost/bind.hpp>
+
+#include <string>
+#include <sstream>
+
+using namespace std;
using namespace isc::dns;
namespace isc {
@@ -80,12 +86,35 @@ matchRdata(const char*, const char*,
}
return (::testing::AssertionSuccess());
}
+
+// A helper callback of masterLoad() used by textToRRset() below.
+void
+setRRset(RRsetPtr rrset, RRsetPtr* rrsetp) {
+ if (*rrsetp) {
+ isc_throw(isc::Unexpected,
+ "multiple RRsets are given to textToRRset");
+ }
+ *rrsetp = rrset;
+}
+}
+
+RRsetPtr
+textToRRset(const string& text_rrset, const RRClass& rrclass,
+ const Name& origin)
+{
+ stringstream ss(text_rrset);
+ RRsetPtr rrset;
+ masterLoad(ss, origin, rrclass, boost::bind(setRRset, _1, &rrset));
+ return (rrset);
}
void
rrsetCheck(isc::dns::ConstRRsetPtr expected_rrset,
isc::dns::ConstRRsetPtr actual_rrset)
{
+ SCOPED_TRACE("Comparing RRsets\n"
+ " Actual: " + actual_rrset->toText() +
+ " Expected: " + expected_rrset->toText());
EXPECT_EQ(expected_rrset->getName(), actual_rrset->getName());
EXPECT_EQ(expected_rrset->getClass(), actual_rrset->getClass());
EXPECT_EQ(expected_rrset->getType(), actual_rrset->getType());
diff --git a/src/lib/testutils/dnsmessage_test.h b/src/lib/testutils/dnsmessage_test.h
index 1aba526..57cb72c 100644
--- a/src/lib/testutils/dnsmessage_test.h
+++ b/src/lib/testutils/dnsmessage_test.h
@@ -12,6 +12,9 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#ifndef __ISC_TESTUTILS_DNSMESSAGETEST_H
+#define __ISC_TESTUTILS_DNSMESSAGETEST_H 1
+
#include <algorithm>
#include <functional>
#include <iosfwd>
@@ -157,8 +160,43 @@ public:
private:
std::vector<isc::dns::ConstRRsetPtr>& rrsets_;
};
+
+class RRsetDumper {
+public:
+ RRsetDumper(std::string& output) :
+ output_(output)
+ {}
+ void operator()(isc::dns::ConstRRsetPtr rrset) {
+ output_ += " " + rrset->toText();
+ }
+private:
+ std::string& output_;
+};
}
+/// \brief A converter from a string to RRset.
+///
+/// This is a convenient shortcut for tests that need to create an RRset
+/// from textual representation with a single call to a function.
+///
+/// An RRset consisting of multiple RRs can be constructed, but only one
+/// RRset is allowed. If the given string contains mixed types of RRs
+/// it throws an \c isc::Unexpected exception.
+///
+/// \param text_rrset A complete textual representation of an RRset.
+/// It must meets the assumption of the \c dns::masterLoad() function.
+/// \param rrclass The RR class of the RRset. Note that \c text_rrset should
+/// contain the RR class, but it's needed for \c dns::masterLoad().
+/// \param origin The zone origin where the RR is expected to belong. This
+/// parameter normally doesn't have to be specified, but for an SOA RR it
+/// must be set to its owner name, due to the internal check of
+/// \c dns::masterLoad().
+isc::dns::RRsetPtr textToRRset(const std::string& text_rrset,
+ const isc::dns::RRClass& rrclass =
+ isc::dns::RRClass::IN(),
+ const isc::dns::Name& origin =
+ isc::dns::Name::ROOT_NAME());
+
/// Set of unit tests to check if two sets of RRsets are identical.
///
/// This templated function takes two sets of sequences, each defined by
@@ -195,6 +233,10 @@ 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
+ std::string expected_text, actual_text;
+ std::for_each(expected_begin, expected_end,
+ detail::RRsetDumper(expected_text));
+ std::for_each(actual_begin, actual_end, detail::RRsetDumper(actual_text));
unsigned int rrset_matched = 0;
ACTUAL_ITERATOR it;
for (it = actual_begin; it != actual_end; ++it) {
@@ -217,11 +259,16 @@ rrsetsCheck(EXPECTED_ITERATOR expected_begin, EXPECTED_ITERATOR expected_end,
}
}
- // 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));
+ {
+ SCOPED_TRACE(std::string("Comparing two RRsets:\n") +
+ "Actual:\n" + actual_text +
+ "Expected:\n" + expected_text);
+ // 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
@@ -318,6 +365,7 @@ rrsetsCheck(const std::string& expected,
} // end of namespace testutils
} // end of namespace isc
+#endif // __ISC_TESTUTILS_DNSMESSAGETEST_H
// Local Variables:
// mode: c++
diff --git a/src/lib/testutils/mockups.h b/src/lib/testutils/mockups.h
index 2441ad7..b5def4b 100644
--- a/src/lib/testutils/mockups.h
+++ b/src/lib/testutils/mockups.h
@@ -12,8 +12,13 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#ifndef __ISC_TESTUTILS_MOCKUPS_H
+#define __ISC_TESTUTILS_MOCKUPS_H 1
+
#include <config.h>
+#include <exceptions/exceptions.h>
+
#include <cc/data.h>
#include <cc/session.h>
@@ -21,6 +26,12 @@
#include <asiodns/asiodns.h>
+#include <utility>
+#include <vector>
+
+namespace isc {
+namespace testutils {
+
// 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.
@@ -93,6 +104,45 @@ private:
bool receive_ok_;
};
+// This mock object does nothing except for recording passed parameters
+// to addServerXXX methods so the test code subsequently checks the parameters.
+class MockDNSService : public isc::asiodns::DNSServiceBase {
+public:
+ // A helper tuple of parameters passed to addServerUDPFromFD().
+ struct UDPFdParams {
+ int fd;
+ int af;
+ ServerFlag options;
+ };
+
+ virtual void addServerTCPFromFD(int fd, int af) {
+ tcp_fd_params_.push_back(std::pair<int, int>(fd, af));
+ }
+ virtual void addServerUDPFromFD(int fd, int af, ServerFlag options) {
+ UDPFdParams params = { fd, af, options };
+ udp_fd_params_.push_back(params);
+ }
+ virtual void clearServers() {}
+
+ virtual asiolink::IOService& getIOService() {
+ isc_throw(isc::Unexpected,
+ "MockDNSService::getIOService() shouldn't be called");
+ }
+
+ // These two allow the tests to check how the servers have been created
+ // through this object.
+ const std::vector<std::pair<int, int> >& getTCPFdParams() const {
+ return (tcp_fd_params_);
+ }
+ const std::vector<UDPFdParams>& getUDPFdParams() const {
+ return (udp_fd_params_);
+ }
+
+private:
+ std::vector<std::pair<int, int> > tcp_fd_params_;
+ std::vector<UDPFdParams> udp_fd_params_;
+};
+
// A nonoperative DNSServer object to be used in calls to processMessage().
class MockServer : public isc::asiodns::DNSServer {
public:
@@ -149,3 +199,10 @@ private:
bool disconnect_ok_;
};
+} // end of testutils
+} // end of isc
+#endif // __ISC_TESTUTILS_MOCKUPS_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/testutils/portconfig.h b/src/lib/testutils/portconfig.h
index 8e61ffc..ce1bb39 100644
--- a/src/lib/testutils/portconfig.h
+++ b/src/lib/testutils/portconfig.h
@@ -12,8 +12,8 @@
// 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
+#ifndef __ISC_TESTUTILS_PORTCONFIG_H
+#define __ISC_TESTUTILS_PORTCONFIG_H
#include <gtest/gtest.h>
#include <cc/data.h>
@@ -46,7 +46,7 @@ template<class Server>
void
listenAddresses(Server& server) {
using namespace isc::server_common::portconfig;
- // Default value should be fully recursive
+ // In this test we assume the address list is originally empty.
EXPECT_TRUE(server.getListenAddresses().empty());
// Try putting there some addresses
@@ -61,7 +61,8 @@ listenAddresses(Server& server) {
addresses.clear();
EXPECT_EQ(2, server.getListenAddresses().size());
- // Did it return to fully recursive?
+ // If we set to an empty list next, the server configuration should
+ // become empty, too.
server.setListenAddresses(addresses);
EXPECT_TRUE(server.getListenAddresses().empty());
}
@@ -95,12 +96,11 @@ listenAddressConfig(Server& server) {
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
+ // This address is rejected by the test socket requestor
config = Element::fromJSON("{"
"\"listen_on\": ["
" {"
- " \"address\": \"192.0.2.0\","
+ " \"address\": \"192.0.2.2\","
" \"port\": 53210"
" }"
"]"
@@ -186,4 +186,4 @@ invalidListenAddressConfig(Server& server) {
}
}
-#endif
+#endif // __ISC_TESTUTILS_PORTCONFIG_H
diff --git a/src/lib/testutils/socket_request.h b/src/lib/testutils/socket_request.h
new file mode 100644
index 0000000..5c76d30
--- /dev/null
+++ b/src/lib/testutils/socket_request.h
@@ -0,0 +1,219 @@
+// 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_TESTUTILS_SOCKETREQUEST_H
+#define __ISC_TESTUTILS_SOCKETREQUEST_H 1
+
+#include <server_common/socket_request.h>
+#include <server_common/portconfig.h>
+
+#include <asiodns/asiodns.h>
+
+#include <gtest/gtest.h>
+#include <boost/lexical_cast.hpp>
+
+#include <vector>
+#include <string>
+
+namespace isc {
+namespace testutils {
+
+/// \brief A testcase part for faking the SocketRequestor in tests
+///
+/// It's awkward to request real sockets from the real socket creator
+/// during tests (for one, because it would have to be running, for
+/// another, we need to block real ports). If you instantiate this class in
+/// a test case, the socket requestor will be initialized to a test one which
+/// handles fake socket FDs and stores what was requested, etc.
+///
+/// Furthermore, you can check if the code requested or released the correct
+/// list of sockets using the checkTokens() method.
+///
+/// Some member variables are intentionally made public so that test cases
+/// can easily check the value of them. We prefer convenience for tests over
+/// class integrity here.
+class TestSocketRequestor : public isc::server_common::SocketRequestor {
+public:
+ /// \brief Constructor
+ ///
+ /// \param dnss The DNS service. It is expected this gets initialized
+ /// after the TestSocketRequestor constructor is called, as the
+ /// TestSocketRequestor should be a base class and the service only
+ /// a member.
+ /// \param store Address store used when cleaning up.
+ /// \param expect_port The port which is expected to be requested. If
+ /// the application requests a different port, it is considered
+ /// a failure.
+ /// \param expeted_app The share name for which all the requests should
+ /// be made. This is not the usual app_name - the requestSocket does
+ /// not fall back to this value if its share_name is left empty, if
+ /// you want to check the code relies on the requestor to use the
+ /// app name, you set this to empty string.
+ TestSocketRequestor(asiodns::DNSServiceBase& dnss,
+ server_common::portconfig::AddressList& store,
+ uint16_t expect_port,
+ const std::string& expected_app) :
+ last_token_(0), break_rollback_(false), break_release_(false),
+ dnss_(dnss), store_(store), expect_port_(expect_port),
+ expected_app_(expected_app)
+ {
+ // Prepare the requestor (us) for the test
+ server_common::initTestSocketRequestor(this);
+ }
+
+ /// \brief Destructor
+ ///
+ /// Removes the addresses (if any) installed by installListenAddresses,
+ /// resets the socket requestor to uninitialized state and turns off
+ /// the portconfig test mode.
+ virtual ~TestSocketRequestor() {
+ // Make sure no sockets are left inside (if installListenAddresses
+ // wasn't used, this is NOP, so it won't hurt).
+ server_common::portconfig::AddressList list;
+ server_common::portconfig::installListenAddresses(list, store_, dnss_);
+ // Don't leave invalid pointers here
+ server_common::initTestSocketRequestor(NULL);
+ }
+
+ /// \brief Tokens released by releaseSocket
+ ///
+ /// They are stored here by this class and you can examine them.
+ std::vector<std::string> released_tokens_;
+
+ /// \brief Tokens returned from requestSocket
+ ///
+ /// They are stored here by this class and you can examine them.
+ std::vector<std::string> given_tokens_;
+private:
+ // Last token number and fd given out
+ size_t last_token_;
+public:
+ /// \brief Support a broken rollback case
+ ///
+ /// If this is set to true, the requestSocket will throw when the
+ /// ::1 address is requested.
+ bool break_rollback_;
+
+ /// \brief Throw on releaseSocket
+ ///
+ /// If this is set to true, the releaseSocket will throw SocketError.
+ /// Defaults to false.
+ bool break_release_;
+
+ /// \brief Release a socket
+ ///
+ /// This only stores the token passed.
+ /// \param token The socket to release
+ ///
+ /// \throw SocketError in case the break_release_ is set to true. This is
+ /// to test exception handling.
+ void releaseSocket(const std::string& token) {
+ if (break_release_) {
+ isc_throw(SocketError, "Fatal test socket error");
+ }
+ released_tokens_.push_back(token);
+ }
+
+ /// \brief Request a socket
+ ///
+ /// This creates a new token and fakes a new socket and returns it.
+ /// The token is stored.
+ ///
+ /// In case the address is 192.0.2.2, it throws SocketAllocateError
+ /// or if the break_rollback_ is true and address is ::1, it throws
+ /// ShareError. If the address is 192.0.2.3, it throws SocketError.
+ ///
+ /// The tokens produced are in form of protocol:address:port:fd. The fds
+ /// start at 1 and increase by each successfull call.
+ ///
+ /// \param protocol The protocol to request
+ /// \param address to bind to
+ /// \param port to bind to
+ /// \param mode checked to be SHARE_SAME for now
+ /// \param name checked to be the same as expected_app parameter of the
+ /// constructor. Note that this class does not provide the fallback
+ /// to an app_name if this is empty string. To check the code relies
+ /// on the fallback (wants to use the app_name instead of providing
+ /// its own share name), you need to create this class with empty
+ /// expected_app.
+ /// \return The token and FD
+ /// \throw SocketAllocateError as described above, to test error handling
+ /// \throw ShareError as described above, to test error handling
+ /// \throw SocketError as described above, to test error handling
+ SocketID requestSocket(Protocol protocol, const std::string& address,
+ uint16_t port, ShareMode mode,
+ const std::string& name)
+ {
+ if (address == "192.0.2.2") {
+ isc_throw(SocketAllocateError, "This address is not allowed");
+ }
+ if (address == "192.0.2.3") {
+ isc_throw(SocketError, "Fatal test error");
+ }
+ if (address == "::1" && break_rollback_) {
+ // This is valid address, but in case we need to break the
+ // rollback, it needs to be busy or whatever
+ //
+ // We break the second address to see the first one was
+ // allocated and then returned
+ isc_throw(ShareError,
+ "This address is available, but not for you");
+ }
+ const std::string proto(protocol == TCP ? "TCP" : "UDP");
+ const size_t number = ++ last_token_;
+ EXPECT_EQ(expect_port_, port);
+ EXPECT_EQ(SHARE_SAME, mode);
+ EXPECT_EQ(expected_app_, name);
+ const std::string token(proto + ":" + address + ":" +
+ boost::lexical_cast<std::string>(port) + ":" +
+ boost::lexical_cast<std::string>(number));
+ given_tokens_.push_back(token);
+ return (SocketID(number, token));
+ }
+
+ /// \brief Check the list of tokens is as expected
+ ///
+ /// Compares the expected and real tokens.
+ ///
+ /// \param expected List of the expected tokens, as NULL-terminated array
+ /// of C strings (it is more convenient to type as a constant than to
+ /// manually push_back all the strings to a vector).
+ /// \param real The token list that was produced by this class (usually
+ /// either given_tokens_ or released_tokens_).
+ /// \param scope Human readable identifier of which checkTokens call it is.
+ /// It is printed as a part of failure message.
+ void checkTokens(const char** expected,
+ const std::vector<std::string>& real,
+ const char* scope) const
+ {
+ SCOPED_TRACE(scope);
+ size_t position(0);
+ while (expected[position] != NULL) {
+ ASSERT_LT(position, real.size());
+ EXPECT_EQ(expected[position], real[position]) << position;
+ position ++;
+ }
+ EXPECT_EQ(position, real.size());
+ }
+
+private:
+ asiodns::DNSServiceBase& dnss_;
+ server_common::portconfig::AddressList& store_;
+ const uint16_t expect_port_;
+ const std::string expected_app_;
+};
+
+}
+}
+#endif // __ISC_TESTUTILS_SOCKETREQUEST_H
diff --git a/src/lib/testutils/srv_test.cc b/src/lib/testutils/srv_test.cc
index dd3e425..03aec01 100644
--- a/src/lib/testutils/srv_test.cc
+++ b/src/lib/testutils/srv_test.cc
@@ -14,6 +14,7 @@
#include <config.h>
+#include <sys/types.h>
#include <netinet/in.h>
#include <dns/message.h>
@@ -43,8 +44,6 @@ SrvTestBase::SrvTestBase() : request_message(Message::RENDER),
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))
{}
@@ -86,6 +85,7 @@ SrvTestBase::createRequestPacket(Message& message,
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);
diff --git a/src/lib/testutils/srv_test.h b/src/lib/testutils/srv_test.h
index c92e876..b5f64b5 100644
--- a/src/lib/testutils/srv_test.h
+++ b/src/lib/testutils/srv_test.h
@@ -12,6 +12,9 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#ifndef __ISC_TESTUTILS_SRVTEST_H
+#define __ISC_TESTUTILS_SRVTEST_H 1
+
#include <util/buffer.h>
#include <dns/name.h>
#include <dns/message.h>
@@ -44,7 +47,7 @@ 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
+/// \brief The base class for Auth and Recurse test case
class SrvTestBase : public ::testing::Test {
protected:
SrvTestBase();
@@ -100,13 +103,13 @@ protected:
asiolink::IOSocket* io_sock;
asiolink::IOMessage* io_message;
const asiolink::IOEndpoint* endpoint;
- isc::util::OutputBuffer request_obuffer;
isc::dns::MessageRenderer request_renderer;
isc::util::OutputBufferPtr response_obuffer;
std::vector<uint8_t> data;
};
} // end of namespace testutils
} // end of namespace isc
+#endif // __ISC_TESTUTILS_SRVTEST_H
// Local Variables:
// mode: c++
diff --git a/src/lib/testutils/testdata/.gitignore b/src/lib/testutils/testdata/.gitignore
new file mode 100644
index 0000000..4d41a44
--- /dev/null
+++ b/src/lib/testutils/testdata/.gitignore
@@ -0,0 +1,15 @@
+/auth_test.sqlite3.copied
+/badExampleQuery_fromWire.wire
+/examplequery_fromWire.wire
+/iquery_fromWire.wire
+/iquery_response_fromWire.wire
+/iqueryresponse_fromWire.wire
+/multiquestion_fromWire.wire
+/nsec3query_fromWire.wire
+/nsec3query_nodnssec_fromWire.wire
+/queryBadEDNS_fromWire.wire
+/shortanswer_fromWire.wire
+/simplequery_fromWire.wire
+/simpleresponse_fromWire.wire
+/test1.zone.copied
+/test2.zone.copied
diff --git a/src/lib/testutils/testdata/Makefile.am b/src/lib/testutils/testdata/Makefile.am
index 918d5c5..b9ef53f 100644
--- a/src/lib/testutils/testdata/Makefile.am
+++ b/src/lib/testutils/testdata/Makefile.am
@@ -5,6 +5,7 @@ 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
+BUILT_SOURCES += nsec3query_nodnssec_fromWire.wire nsec3query_fromWire.wire
# NOTE: keep this in sync with real file listing
# so is included in tarball
@@ -19,11 +20,14 @@ EXTRA_DIST += shortquestion_fromWire
EXTRA_DIST += shortresponse_fromWire
EXTRA_DIST += simplequery_fromWire.spec
EXTRA_DIST += simpleresponse_fromWire.spec
+EXTRA_DIST += nsec3query_nodnssec_fromWire.spec nsec3query_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 += rfc5155-example.zone.signed
EXTRA_DIST += example.com
EXTRA_DIST += example.sqlite3
+EXTRA_DIST += rwtest.sqlite3 # SQLite3 DB file as a template data source
EXTRA_DIST += test1.zone.in
EXTRA_DIST += test1-new.zone.in
diff --git a/src/lib/testutils/testdata/auth_test.sqlite3 b/src/lib/testutils/testdata/auth_test.sqlite3
new file mode 100755
index 0000000..5eeb2c3
Binary files /dev/null and b/src/lib/testutils/testdata/auth_test.sqlite3 differ
diff --git a/src/lib/testutils/testdata/example.sqlite3 b/src/lib/testutils/testdata/example.sqlite3
index e8e255b..0f6ee02 100644
Binary files a/src/lib/testutils/testdata/example.sqlite3 and b/src/lib/testutils/testdata/example.sqlite3 differ
diff --git a/src/lib/testutils/testdata/nsec3query_fromWire.spec b/src/lib/testutils/testdata/nsec3query_fromWire.spec
new file mode 100644
index 0000000..f68a09e
--- /dev/null
+++ b/src/lib/testutils/testdata/nsec3query_fromWire.spec
@@ -0,0 +1,11 @@
+#
+# A simple QUERY message (with DO bit on) for "example" zone signed with NSEC3
+#
+
+[header]
+arcount: 1
+[question]
+# use default
+name: ns2.example
+[edns]
+do: 1
diff --git a/src/lib/testutils/testdata/nsec3query_nodnssec_fromWire.spec b/src/lib/testutils/testdata/nsec3query_nodnssec_fromWire.spec
new file mode 100644
index 0000000..06a9561
--- /dev/null
+++ b/src/lib/testutils/testdata/nsec3query_nodnssec_fromWire.spec
@@ -0,0 +1,9 @@
+#
+# A simple QUERY message (without DO bit) for "example" zone signed with NSEC3
+#
+
+[header]
+# use default
+[question]
+# use default
+name: ns2.example
diff --git a/src/lib/testutils/testdata/rfc5155-example.zone.signed b/src/lib/testutils/testdata/rfc5155-example.zone.signed
new file mode 100644
index 0000000..595c441
--- /dev/null
+++ b/src/lib/testutils/testdata/rfc5155-example.zone.signed
@@ -0,0 +1,72 @@
+;; The example NSEC3-signed zone used in RFC5155.
+
+example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+example. 3600 IN NS ns1.example.
+example. 3600 IN NS ns2.example.
+example. 3600 IN RRSIG NS 7 1 3600 20150420235959 20051021000000 40430 example. PVOgtMK1HHeSTau+HwDWC8Ts+6C8qtqd4pQJqOtdEVgg+MA+ai4fWDEh u3qHJyLcQ9tbD2vvCnMXjtz6SyObxA==
+example. 3600 IN MX 1 xx.example.
+example. 3600 IN RRSIG MX 7 1 3600 20150420235959 20051021000000 40430 example. GgQ1A9xs47k42VPvpL/a1BWUz/6XsnHkjotw9So8MQtZtl2wJBsnOQsa oHrRCrRbyriEl/GZn9Mto/Kx+wBo+w==
+example. 3600 IN DNSKEY 256 3 7 AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LUsAD0QPWU+wzt89epO6tH zkMBVDkC7qphQO2hTY4hHn9npWFRw5BYubE= ; key id = 40430
+example. 3600 IN DNSKEY 257 3 7 AwEAAcUlFV1vhmqx6NSOUOq2R/dsR7Xm3upJj7IommWSpJABVfW8Q0rO vXdM6kzt+TAu92L9AbsUdblMFin8CVF3n4s= ; key id = 12708
+example. 3600 IN RRSIG DNSKEY 7 1 3600 20150420235959 20051021000000 12708 example. AuU4juU9RaxescSmStrQks3Gh9FblGBlVU31uzMZ/U/FpsUb8aC6QZS+ sTsJXnLnz7flGOsmMGQZf3bH+QsCtg==
+example. 3600 IN NSEC3PARAM 1 0 12 AABBCCDD
+example. 3600 IN RRSIG NSEC3PARAM 7 1 3600 20150420235959 20051021000000 40430 example. C1Gl8tPZNtnjlrYWDeeUV/sGLCyy/IHie2rerN05XSA3Pq0U3+4VvGWY WdUMfflOdxqnXHwJTLQsjlkynhG6Cg==
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN A 192.0.2.127
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. h6c++bzhRuWWt2bykN6mjaTNBcXNq5UuL5EdK+iDP4eY8I0kSiKaCjg3 tC1SQkeloMeub2GWk8p6xHMPZumXlw==
+a.example. 3600 IN NS ns1.a.example.
+a.example. 3600 IN NS ns2.a.example.
+a.example. 3600 IN DS 58470 5 1 3079F1593EBAD6DC121E202A8B766A6A4837206C
+a.example. 3600 IN RRSIG DS 7 2 3600 20150420235959 20051021000000 40430 example. XacFcQVHLVzdoc45EJhN616zQ4mEXtE8FzUhM2KWjfy1VfRKD9r1MeVG wwoukOKgJxBPFsWoo722vZ4UZ2dIdA==
+ns1.a.example. 3600 IN A 192.0.2.5
+ns2.a.example. 3600 IN A 192.0.2.6
+ai.example. 3600 IN A 192.0.2.9
+ai.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. hVe+wKYMlObTRPhX0NL67GxeZfdxqr/QeR6FtfdAj5+FgYxyzPEjIzvK Wy00hWIl6wD3Vws+rznEn8sQ64UdqA==
+ai.example. 3600 IN HINFO "KLH-10" "ITS"
+ai.example. 3600 IN RRSIG HINFO 7 2 3600 20150420235959 20051021000000 40430 example. Yi42uOq43eyO6qXHNvwwfFnIustWgV5urFcxenkLvs6pKRh00VBjODmf 3Z4nMO7IOl6nHSQ1v0wLHpEZG7Xj2w==
+ai.example. 3600 IN AAAA 2001:db8::f00:baa9
+ai.example. 3600 IN RRSIG AAAA 7 2 3600 20150420235959 20051021000000 40430 example. LcdxKaCB5bGZwPDg+3JJ4O02zoMBrjxqlf6WuaHQZZfTUpb9Nf2nxFGe 2XRPfR5tpJT6GdRGcHueLuXkMjBArQ==
+c.example. 3600 IN NS ns1.c.example.
+c.example. 3600 IN NS ns2.c.example.
+ns1.c.example. 3600 IN A 192.0.2.7
+ns2.c.example. 3600 IN A 192.0.2.8
+ns1.example. 3600 IN A 192.0.2.1
+ns1.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. bu6kx73n6XEunoVGuRfAgY7EF/AJqHy7hj0jkiqJjB0dOrx3wuz9SaBe GfqWIdn/uta3SavN4FRvZR9SCFHF5Q==
+ns2.example. 3600 IN A 192.0.2.2
+ns2.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. ktQ3TqE0CfRfki0Rb/Ip5BM0VnxelbuejCC4zpLbFKA/7eD7UNAwxMgx JPtbdST+syjYSJaj4IHfeX6n8vfoGA==
+*.w.example. 3600 IN MX 1 ai.example.
+*.w.example. 3600 IN RRSIG MX 7 2 3600 20150420235959 20051021000000 40430 example. CikebjQwGQPwijVcxgcZcSJKtfynugtlBiKb9FcBTrmOoyQ4InoWVudh CWsh/URX3lc4WRUMivEBP6+4KS3ldA==
+x.w.example. 3600 IN MX 1 xx.example.
+x.w.example. 3600 IN RRSIG MX 7 3 3600 20150420235959 20051021000000 40430 example. IrK3tq/tHFIBF0scHiE/1IwMAvckS/55hAVvQyxTFbkAdDloP3NbZzu+ yoSsr3b3OX6qbBpY7WCtwwekLKRAwQ==
+x.y.w.example. 3600 IN MX 1 xx.example.
+x.y.w.example. 3600 IN RRSIG MX 7 4 3600 20150420235959 20051021000000 40430 example. MqSt5HqJIN8+SLlzTOImrh5h9Xa6gDvAW/GnnbdPc6Z7nXvCpLPJj/5l Cwx3VuzVOjkbvXze8/8Ccl2Zn2hbug==
+xx.example. 3600 IN A 192.0.2.10
+xx.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. T35hBWEZ017VC5u2c4OriKyVn/pu+fVK4AlXYOxJ6iQylfV2HQIKjv6b 7DzINB3aF/wjJqgXpQvhq+Ac6+ZiFg==
+xx.example. 3600 IN HINFO "KLH-10" "TOPS-20"
+xx.example. 3600 IN RRSIG HINFO 7 2 3600 20150420235959 20051021000000 40430 example. KimG+rDd+7VA1zRsu0ITNAQUTRlpnsmqWrihFRnU+bRa93v2e5oFNFYC s3Rqgv62K93N7AhW6Jfqj/8NzWjvKg==
+xx.example. 3600 IN AAAA 2001:db8::f00:baaa
+xx.example. 3600 IN RRSIG AAAA 7 2 3600 20150420235959 20051021000000 40430 example. IXBcXORITNwd8h3gNwyxtYFvAupS/CYWufVeuBUX0O25ivBCULjZjpDx FSxfohb/KA7YRdxENzYfMItpILl/Xw==
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN NSEC3 1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR NS SOA MX RRSIG DNSKEY NSEC3PARAM
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKLIBHYH6blRxK9rC0bMJPw Q4mLIuw85H2EY762BOCXJZMnpuwhpA==
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN NSEC3 1 1 12 AABBCCDD 2VPTU5TIMAMQTTGL4LUU9KG21E0AOR3S A RRSIG
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OmBvJ1Vgg1hCKMXHFiNeIYHK9XVW0iLDLwJN4TFoNxZuP03gAXEI634Y wOc4YBNITrj413iqNI6mRk/r1dOSUw==
+2vptu5timamqttgl4luu9kg21e0aor3s.example. 3600 IN NSEC3 1 1 12 AABBCCDD 35MTHGPGCU1QG68FAB165KLNSNK3DPVL MX RRSIG
+2vptu5timamqttgl4luu9kg21e0aor3s.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. KL1V2oFYghNV0Hm7Tf2vpJjM6l+0g1JCcVYGVfI0lKrhPmTsOA96cLEA Cgo1x8I7kApJX+obTuktZ+sdsZPY1w==
+35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN NSEC3 1 1 12 AABBCCDD B4UM86EGHHDS6NEA196SMVMLO4ORS995 NS DS RRSIG
+35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQAynzo8EUWH+z6hEIBlUT PGj15eZll6VhQqgZXtAIR3chwgW+SA==
+b4um86eghhds6nea196smvmlo4ors995.example. 3600 IN NSEC3 1 1 12 AABBCCDD GJEQE526PLBF1G8MKLP59ENFD789NJGI MX RRSIG
+b4um86eghhds6nea196smvmlo4ors995.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. ZkPG3M32lmoHM6pa3D6gZFGB/rhL//Bs3Omh5u4m/CUiwtblEVOaAKKZ d7S959OeiX43aLX3pOv0TSTyiTxIZg==
+gjeqe526plbf1g8mklp59enfd789njgi.example. 3600 IN NSEC3 1 1 12 AABBCCDD JI6NEOAEPV8B5O6K4EV33ABHA8HT9FGC A HINFO AAAA RRSIG
+gjeqe526plbf1g8mklp59enfd789njgi.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. IVnezTJ9iqblFF97vPSmfXZ5Zozngx3KX3byLTZC4QBH2dFWhf6scrGF ZB980AfCxoD9qbbKDy+rdGIeRSVNyw==
+ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. 3600 IN NSEC3 1 1 12 AABBCCDD K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H
+ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. gPkFp1s2QDQ6wQzcg1uSebZ61W33rUBDcTj72F3kQ490fEdp7k1BUIfb cZtPbX3YCpE+sIt0MpzVSKfTwx4uYA==
+k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN NSEC3 1 1 12 AABBCCDD KOHAR7MBB8DC2CE8A9QVL8HON4K53UHI
+k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. FtXGbvF0+wf8iWkyo73enAuVx03klN+pILBKS6qCcftVtfH4yVzsEZqu J27NHR7ruxJWDNMtOtx7w9WfcIg62A==
+kohar7mbb8dc2ce8a9qvl8hon4k53uhi.example. 3600 IN NSEC3 1 1 12 AABBCCDD Q04JKCEVQVMU85R014C7DKBA38O0JI5R A RRSIG
+kohar7mbb8dc2ce8a9qvl8hon4k53uhi.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. VrDXs2uVW21N08SyQIz88zml+y4ZCInTwgDr6zz43yAg+LFERjOrj3Oj ct51ac7Dp4eZbf9FQJazmASFKGxGXg==
+q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN NSEC3 1 1 12 AABBCCDD R53BQ7CC2UVMUBFU5OCMM6PERS9TK9EN A RRSIG
+q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. hV5I89b+4FHJDATp09g4bbN0R1F845CaXpL3ZxlMKimoPAyqletMlEWw LfFia7sdpSzn+ZlNNlkxWcLsIlMmUg==
+r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN NSEC3 1 1 12 AABBCCDD T644EBQK9BIBCNA874GIVR6JOJ62MLHV MX RRSIG
+r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. aupviViruXs4bDg9rCbezzBMf9h1ZlDvbW/CZFKulIGXXLj8B/fsDJar XVDA9bnUoRhEbKp+HF1FWKW7RIJdtQ==
+t644ebqk9bibcna874givr6joj62mlhv.example. 3600 IN NSEC3 1 1 12 AABBCCDD 0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM A HINFO AAAA RRSIG
+t644ebqk9bibcna874givr6joj62mlhv.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. RAjGECB8P7O+F4Pa4Dx3tC0M+Z3KmlLKImcafb9XWwx+NWUNz7NBEDBQ HivIyKPVDkChcePIX1xPl1ATNa+8Dw==
diff --git a/src/lib/testutils/testdata/rwtest.sqlite3 b/src/lib/testutils/testdata/rwtest.sqlite3
new file mode 100644
index 0000000..5eeb2c3
Binary files /dev/null and b/src/lib/testutils/testdata/rwtest.sqlite3 differ
diff --git a/src/lib/util/buffer.h b/src/lib/util/buffer.h
index 7982fce..1ceeada 100644
--- a/src/lib/util/buffer.h
+++ b/src/lib/util/buffer.h
@@ -133,10 +133,10 @@ public:
/// an exception of class \c isc::dns::InvalidBufferPosition will be thrown.
/// \param position The new position (offset from the beginning of the
/// buffer).
- void setPosition(size_t position)
- {
- if (position > len_)
- isc_throw(InvalidBufferPosition, "position is too large");
+ void setPosition(size_t position) {
+ if (position > len_) {
+ throwError("position is too large");
+ }
position_ = position;
}
//@}
@@ -148,10 +148,9 @@ public:
///
/// If the remaining length of the buffer is smaller than 8-bit, an
/// exception of class \c isc::dns::InvalidBufferPosition will be thrown.
- uint8_t readUint8()
- {
+ uint8_t readUint8() {
if (position_ + sizeof(uint8_t) > len_) {
- isc_throw(InvalidBufferPosition, "read beyond end of buffer");
+ throwError("read beyond end of buffer");
}
return (data_[position_++]);
@@ -161,13 +160,12 @@ public:
///
/// If the remaining length of the buffer is smaller than 16-bit, an
/// exception of class \c isc::dns::InvalidBufferPosition will be thrown.
- uint16_t readUint16()
- {
+ uint16_t readUint16() {
uint16_t data;
const uint8_t* cp;
if (position_ + sizeof(data) > len_) {
- isc_throw(InvalidBufferPosition, "read beyond end of buffer");
+ throwError("read beyond end of buffer");
}
cp = &data_[position_];
@@ -182,13 +180,12 @@ public:
///
/// If the remaining length of the buffer is smaller than 32-bit, an
/// exception of class \c isc::dns::InvalidBufferPosition will be thrown.
- uint32_t readUint32()
- {
+ uint32_t readUint32() {
uint32_t data;
const uint8_t* cp;
if (position_ + sizeof(data) > len_) {
- isc_throw(InvalidBufferPosition, "read beyond end of buffer");
+ throwError("read beyond end of buffer");
}
cp = &data_[position_];
@@ -207,10 +204,9 @@ public:
/// If the remaining length of the buffer is smaller than the specified
/// length, an exception of class \c isc::dns::InvalidBufferPosition will
/// be thrown.
- void readData(void* data, size_t len)
- {
+ void readData(void* data, size_t len) {
if (position_ + len > len_) {
- isc_throw(InvalidBufferPosition, "read beyond end of buffer");
+ throwError("read beyond end of buffer");
}
memcpy(data, &data_[position_], len);
@@ -226,10 +222,9 @@ public:
/// @param Reference to a buffer (data will be stored there).
/// @param Size specified number of bytes to read in a vector.
///
- void readVector(std::vector<uint8_t>& data, size_t len)
- {
+ void readVector(std::vector<uint8_t>& data, size_t len) {
if (position_ + len > len_) {
- isc_throw(InvalidBufferPosition, "read beyond end of buffer");
+ throwError("read beyond end of buffer");
}
data.resize(len);
@@ -237,6 +232,15 @@ public:
}
private:
+ /// \brief A common helper to throw an exception on invalid operation.
+ ///
+ /// Experiments showed that throwing from each method makes the buffer
+ /// operation slower, so we consolidate it here, and let the methods
+ /// call this.
+ static void throwError(const char* msg) {
+ isc_throw(InvalidBufferPosition, msg);
+ }
+
size_t position_;
// XXX: The following must be private, but for a short term workaround with
@@ -387,9 +391,7 @@ public:
/// \param pos The position in the buffer to be returned.
uint8_t operator[](size_t pos) const
{
- if (pos >= size_) {
- isc_throw(InvalidBufferPosition, "read at invalid position");
- }
+ assert (pos < size_);
return (buffer_[pos]);
}
//@}
diff --git a/src/lib/util/io/Makefile.am b/src/lib/util/io/Makefile.am
index 96b9d25..2c3ed96 100644
--- a/src/lib/util/io/Makefile.am
+++ b/src/lib/util/io/Makefile.am
@@ -13,7 +13,7 @@ CLEANFILES = *.gcno *.gcda
pyexec_LTLIBRARIES = libutil_io_python.la
# Python prefers .so, while some OSes (specifically MacOS) use a different
# suffix for dynamic objects. -module is necessary to work this around.
-libutil_io_python_la_LDFLAGS = -module
+libutil_io_python_la_LDFLAGS = -module -avoid-version
libutil_io_python_la_SOURCES = fdshare_python.cc
libutil_io_python_la_LIBADD = libutil_io.la
libutil_io_python_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
diff --git a/src/lib/util/io/fd.cc b/src/lib/util/io/fd.cc
index f9b17a7..49aac39 100644
--- a/src/lib/util/io/fd.cc
+++ b/src/lib/util/io/fd.cc
@@ -23,23 +23,23 @@ namespace io {
bool
write_data(const int fd, const void *buffer_v, const size_t length) {
-
const unsigned char* buffer(static_cast<const unsigned char*>(buffer_v));
size_t remaining = length; // Amount remaining to be written
+ // Just keep writing until all is written
while (remaining > 0) {
- ssize_t amount = write(fd, buffer, remaining);
- if (amount == -1) {
- // Some error. Ignore interrupted system calls otherwise return
- // an error indication.
- if (errno != EINTR) {
- return false;
+ const int written = write(fd, buffer, remaining);
+ if (written == -1) {
+ if (errno == EINTR) { // Just keep going
+ continue;
+ } else {
+ return (false);
}
- } else if (amount > 0) {
- // Wrote "amount" bytes from the buffer
- remaining -= amount;
- buffer += amount;
+ } else if (written > 0) {
+ // Wrote "written" bytes from the buffer
+ remaining -= written;
+ buffer += written;
} else {
// Wrote zero bytes from the buffer. We should not get here as any
@@ -54,24 +54,21 @@ write_data(const int fd, const void *buffer_v, const size_t length) {
ssize_t
read_data(const int fd, void *buffer_v, const size_t length) {
-
unsigned char* buffer(static_cast<unsigned char*>(buffer_v));
size_t remaining = length; // Amount remaining to be read
while (remaining > 0) {
- ssize_t amount = read(fd, buffer, remaining);
+ const int amount = read(fd, buffer, remaining);
if (amount == -1) {
- // Some error. Ignore interrupted system calls otherwise return
- // an error indication.
- if (errno != EINTR) {
- return -1;
+ if (errno == EINTR) { // Continue on interrupted call
+ continue;
+ } else {
+ return (-1);
}
-
} else if (amount > 0) {
// Read "amount" bytes into the buffer
remaining -= amount;
buffer += amount;
-
} else {
// EOF - end the read
break;
diff --git a/src/lib/util/io/fd_share.cc b/src/lib/util/io/fd_share.cc
index 9c02a33..7adbbbe 100644
--- a/src/lib/util/io/fd_share.cc
+++ b/src/lib/util/io/fd_share.cc
@@ -20,6 +20,7 @@
#include <sys/uio.h>
#include <errno.h>
#include <stdlib.h> // for malloc and free
+#include <unistd.h>
#include "fd_share.h"
namespace isc {
@@ -106,7 +107,21 @@ recv_fd(const int sock) {
std::memcpy(&fd, CMSG_DATA(cmsg), sizeof(int));
}
free(msghdr.msg_control);
- return (fd);
+ // It is strange, but the call can return the same file descriptor as
+ // one returned previously, even if that one is not closed yet. So,
+ // we just re-number every one we get, so they are unique.
+ int new_fd(dup(fd));
+ int close_error(close(fd));
+ if (close_error == -1 || new_fd == -1) {
+ // We need to return an error, because something failed. But in case
+ // it was the previous close, we at least try to close the duped FD.
+ if (new_fd != -1) {
+ close(new_fd); // If this fails, nothing but returning error can't
+ // be done and we are doing that anyway.
+ }
+ return (FD_SYSTEM_ERROR);
+ }
+ return (new_fd);
}
int
diff --git a/src/lib/util/io/sockaddr_util.h b/src/lib/util/io/sockaddr_util.h
index 940fdd9..9b4a0cb 100644
--- a/src/lib/util/io/sockaddr_util.h
+++ b/src/lib/util/io/sockaddr_util.h
@@ -51,12 +51,26 @@ convertSockAddr(const SAType* sa) {
}
template <typename SAType>
+const SAType*
+convertSockAddr(const struct sockaddr* sa) {
+ const void* p = sa;
+ return (static_cast<const SAType*>(p));
+}
+
+template <typename SAType>
struct sockaddr*
convertSockAddr(SAType* sa) {
void* p = sa;
return (static_cast<struct sockaddr*>(p));
}
+template <typename SAType>
+SAType*
+convertSockAddr(struct sockaddr* sa) {
+ void* p = sa;
+ return (static_cast<SAType*>(p));
+}
+
}
}
}
diff --git a/src/lib/util/locks.h b/src/lib/util/locks.h
index 971c413..daaf216 100644
--- a/src/lib/util/locks.h
+++ b/src/lib/util/locks.h
@@ -15,13 +15,9 @@
/// 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.
+/// 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
@@ -30,8 +26,6 @@
#ifndef __LOCKS_
#define __LOCKS_
-#ifndef USE_BOOST_THREADS
-
namespace isc {
namespace util {
namespace locks {
@@ -64,52 +58,4 @@ public:
} // namespace util
} // namespace isc
-#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 util {
-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) { }
-};
-
-} // namespace locks
-} // namespace util
-} // namespace isc
-
-
-#endif // USE_BOOST_THREADS
-
#endif // __LOCKS_
diff --git a/src/lib/util/python/.gitignore b/src/lib/util/python/.gitignore
new file mode 100644
index 0000000..c54df80
--- /dev/null
+++ b/src/lib/util/python/.gitignore
@@ -0,0 +1,2 @@
+/gen_wiredata.py
+/mkpywrapper.py
diff --git a/src/lib/util/python/gen_wiredata.py.in b/src/lib/util/python/gen_wiredata.py.in
index 8bd2b3c..f997701 100755
--- a/src/lib/util/python/gen_wiredata.py.in
+++ b/src/lib/util/python/gen_wiredata.py.in
@@ -822,6 +822,29 @@ class RP(RR):
f.write('# MAILBOX=%s TEXT=%s\n' % (self.mailbox, self.text))
f.write('%s %s\n' % (mailbox_wire, text_wire))
+class SSHFP(RR):
+ '''Implements rendering SSHFP RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - algorithm (int): The algorithm number.
+ - fingerprint_type (int): The fingerprint type.
+ - fingerprint (string): The fingerprint.
+ '''
+ algorithm = 2
+ fingerprint_type = 1
+ fingerprint = '123456789abcdef67890123456789abcdef67890'
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = 2 + (len(self.fingerprint) / 2)
+ else:
+ self.rdlen = int(self.rdlen)
+ self.dump_header(f, self.rdlen)
+ f.write('# ALGORITHM=%d FINGERPRINT_TYPE=%d FINGERPRINT=%s\n' % (self.algorithm,
+ self.fingerprint_type,
+ self.fingerprint))
+ f.write('%02x %02x %s\n' % (self.algorithm, self.fingerprint_type, self.fingerprint))
+
class MINFO(RR):
'''Implements rendering MINFO RDATA in the test data format.
@@ -949,12 +972,11 @@ class NSEC(NSECBASE):
int(len(name_wire) / 2)))
f.write('%s\n' % name_wire)
-class NSEC3(NSECBASE):
- '''Implements rendering NSEC3 RDATA in the test data format.
+class NSEC3PARAM(RR):
+ '''Implements rendering NSEC3PARAM RDATA in the test data format.
Configurable parameters are as follows (see the description of the
same name of attribute for the default value):
- - Type bitmap related parameters: see class NSECBASE
- hashalg (8-bit int): The Hash Algorithm field. Note that
currently the only defined algorithm is SHA-1, for which a value
of 1 will be used, and it's the default. So this implementation
@@ -967,9 +989,6 @@ class NSEC3(NSECBASE):
- saltlen (int): The Salt Length field.
- salt (string): The Salt field. It is converted to a sequence of
ascii codes and its hexadecimal representation will be used.
- - hashlen (int): The Hash Length field.
- - hash (string): The Next Hashed Owner Name field. This parameter
- is interpreted as "salt".
'''
hashalg = 1 # SHA-1
@@ -978,15 +997,18 @@ class NSEC3(NSECBASE):
iterations = 1
saltlen = 5
salt = 's' * saltlen
- hashlen = 20
- hash = 'h' * hashlen
- def dump_fixedpart(self, f, bitmap_totallen):
+
+ def dump(self, f):
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
+ self.rdlen = 4 + 1 + len(self.salt)
self.dump_header(f, self.rdlen)
+ self._dump_params(f)
+
+ def _dump_params(self, f):
+ '''This method is intended to be shared with NSEC3 class.
+
+ '''
+
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),
@@ -997,6 +1019,29 @@ class NSEC3(NSECBASE):
f.write('%02x%s%s\n' % (self.saltlen,
' ' if len(self.salt) > 0 else '',
encode_string(self.salt)))
+
+class NSEC3(NSECBASE, NSEC3PARAM):
+ '''Implements rendering NSEC3 RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - Type bitmap related parameters: see class NSECBASE
+ - Hash parameter related parameters: see class NSEC3PARAM
+ - hashlen (int): The Hash Length field.
+ - hash (string): The Next Hashed Owner Name field. This parameter
+ is interpreted as "salt".
+ '''
+
+ 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
+ self.dump_header(f, self.rdlen)
+ self._dump_params(f)
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 '',
diff --git a/src/lib/util/python/wrapper_template.cc b/src/lib/util/python/wrapper_template.cc
index 1a67a88..780e695 100644
--- a/src/lib/util/python/wrapper_template.cc
+++ b/src/lib/util/python/wrapper_template.cc
@@ -33,14 +33,6 @@ using namespace isc::@MODULE@;
using namespace isc::@MODULE@::python;
//
-// 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
-
-//
// @CPPCLASS@
//
@@ -52,6 +44,7 @@ namespace {
// Shortcut type which would be convenient for adding class variables safely.
typedef CPPPyObjectContainer<s_ at CPPCLASS@, @CPPCLASS@> @CPPCLASS at Container;
+ at REMOVE_THIS_ON_RELEASE@
// This is a template of typical code logic of python class initialization
// with C++ backend. You'll need to adjust it according to details of the
// actual C++ class.
@@ -60,6 +53,7 @@ int
s_ at CPPCLASS@* self = static_cast<s_ at CPPCLASS@*>(po_self);
try {
if (PyArg_ParseTuple(args, "REPLACE ME")) {
+ @REMOVE_THIS_ON_RELEASE@
// YOU'LL NEED SOME VALIDATION, PREPARATION, ETC, HERE.
self->cppobj = new @CPPCLASS@(/*NECESSARY PARAMS*/);
return (0);
@@ -74,6 +68,7 @@ int
return (-1);
}
+ @REMOVE_THIS_ON_RELEASE@
// If we are here PyArg_ParseTuple() failed and TypeError should have
// been set. If the constructor is more complicated and the control
// could reach this point for other reasons, an appropriate Python
@@ -82,6 +77,7 @@ int
return (-1);
}
+ at REMOVE_THIS_ON_RELEASE@
// This is a template of typical code logic of python object destructor.
// In many cases you can use it without modification, but check that carefully.
void
@@ -92,6 +88,7 @@ void
Py_TYPE(self)->tp_free(self);
}
+ at REMOVE_THIS_ON_RELEASE@
// This should be able to be used without modification as long as the
// underlying C++ class has toText().
PyObject*
@@ -119,6 +116,7 @@ PyObject*
const_cast<char*>("")));
}
+ at REMOVE_THIS_ON_RELEASE@
// This is quite specific isc.dns. For other wrappers this should probably
// be removed.
PyObject* @CPPCLASS at _toWire(PyObject* self, PyObject* args) {
@@ -175,6 +173,8 @@ PyObject*
PyMethodDef @CPPCLASS at _methods[] = {
{ "to_text", @CPPCLASS at _toText, METH_NOARGS,
@CPPCLASS at _toText_doc },
+
+ @REMOVE_THIS_ON_RELEASE@
// This is quite specific isc.dns. For other wrappers this should probably
// be removed:
{ "to_wire", @CPPCLASS at _toWire, METH_VARARGS,
@@ -256,6 +256,7 @@ initModulePart_ at CPPCLASS@(PyObject* mod) {
}
Py_INCREF(&@cppclass at _type);
+ @REMOVE_THIS_ON_RELEASE@
// The following template is the typical procedure for installing class
// variables. If the class doesn't have a class variable, remove the
// entire try-catch clauses.
diff --git a/src/lib/util/pyunittests/Makefile.am b/src/lib/util/pyunittests/Makefile.am
index dd2d39a..93b0748 100644
--- a/src/lib/util/pyunittests/Makefile.am
+++ b/src/lib/util/pyunittests/Makefile.am
@@ -13,7 +13,7 @@ pyunittests_util_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
# Python prefers .so, while some OSes (specifically MacOS) use a different
# suffix for dynamic objects. -module is necessary to work this around.
-pyunittests_util_la_LDFLAGS += -module
+pyunittests_util_la_LDFLAGS += -module -avoid-version
pyunittests_util_la_LIBADD = $(top_builddir)/src/lib/util/libutil.la
pyunittests_util_la_LIBADD += $(PYTHON_LIB)
diff --git a/src/lib/util/tests/.gitignore b/src/lib/util/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/lib/util/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/lib/util/tests/buffer_unittest.cc b/src/lib/util/tests/buffer_unittest.cc
index 6615638..9d924b3 100644
--- a/src/lib/util/tests/buffer_unittest.cc
+++ b/src/lib/util/tests/buffer_unittest.cc
@@ -12,11 +12,15 @@
// 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 <util/buffer.h>
+#ifdef EXPECT_DEATH
+#include <util/unittests/resource.h>
+#endif /* EXPECT_DEATH */
-#include <gtest/gtest.h>
+#include <util/buffer.h>
using namespace isc;
@@ -182,7 +186,19 @@ TEST_F(BufferTest, outputBufferReadat) {
for (int i = 0; i < sizeof(testdata); i ++) {
EXPECT_EQ(testdata[i], obuffer[i]);
}
- EXPECT_THROW(obuffer[sizeof(testdata)], isc::util::InvalidBufferPosition);
+#ifdef EXPECT_DEATH
+ // We use assert now, so we check it dies
+ EXPECT_DEATH({
+ isc::util::unittests::dontCreateCoreDumps();
+
+ try {
+ obuffer[sizeof(testdata)];
+ } catch (...) {
+ // Prevent exceptions killing the application, we need
+ // to make sure it dies the real hard way
+ }
+ }, "");
+#endif
}
TEST_F(BufferTest, outputBufferClear) {
diff --git a/src/lib/util/unittests/Makefile.am b/src/lib/util/unittests/Makefile.am
index bbb0d49..2827471 100644
--- a/src/lib/util/unittests/Makefile.am
+++ b/src/lib/util/unittests/Makefile.am
@@ -6,6 +6,7 @@ libutil_unittests_la_SOURCES = fork.h fork.cc resolver.h
libutil_unittests_la_SOURCES += newhook.h newhook.cc
libutil_unittests_la_SOURCES += testdata.h testdata.cc
if HAVE_GTEST
+libutil_unittests_la_SOURCES += resource.h resource.cc
libutil_unittests_la_SOURCES += run_all.h run_all.cc
libutil_unittests_la_SOURCES += textdata.h
endif
diff --git a/src/lib/util/unittests/resource.cc b/src/lib/util/unittests/resource.cc
new file mode 100644
index 0000000..3e77e0d
--- /dev/null
+++ b/src/lib/util/unittests/resource.cc
@@ -0,0 +1,35 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "resource.h"
+
+#include <gtest/gtest.h>
+
+#include <sys/time.h>
+#include <sys/resource.h>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+void
+dontCreateCoreDumps() {
+ const rlimit core_limit = {0, 0};
+
+ EXPECT_EQ(setrlimit(RLIMIT_CORE, &core_limit), 0);
+}
+
+} // end of namespace unittests
+} // end of namespace util
+} // end of namespace isc
diff --git a/src/lib/util/unittests/resource.h b/src/lib/util/unittests/resource.h
new file mode 100644
index 0000000..6430ab2
--- /dev/null
+++ b/src/lib/util/unittests/resource.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __UTIL_UNITTESTS_RESOURCE_H
+#define __UTIL_UNITTESTS_RESOURCE_H 1
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// Don't create core dumps.
+///
+/// This function sets the core size to 0, inhibiting the creation of
+/// core dumps. It is meant to be used in testcases where EXPECT_DEATH
+/// is used, where processes abort (and create cores in the process).
+/// As a new process is forked to run EXPECT_DEATH tests, the rlimits of
+/// the parent process that runs the other tests should be unaffected.
+void dontCreateCoreDumps();
+
+} // end of namespace unittests
+} // end of namespace util
+} // end of namespace isc
+
+#endif /* __UTIL_UNITTESTS_RESOURCE_H */
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/xfr/tests/.gitignore b/src/lib/xfr/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/lib/xfr/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/tests/lettuce/.gitignore b/tests/lettuce/.gitignore
new file mode 100644
index 0000000..f41154c
--- /dev/null
+++ b/tests/lettuce/.gitignore
@@ -0,0 +1,2 @@
+/output/
+/setup_intree_bind10.sh
diff --git a/tests/lettuce/README b/tests/lettuce/README
index 21a57c7..94bf82b 100644
--- a/tests/lettuce/README
+++ b/tests/lettuce/README
@@ -6,7 +6,7 @@ these tests are specific for BIND10, but we are keeping in mind that RFC-related
tests could be separated, so that we can test other systems as well.
Prerequisites:
-- Installed version of BIND 10 (but see below how to run it from source tree)
+- BIND 10 must be compiled or installed
- dig
- lettuce (http://lettuce.it)
@@ -26,27 +26,23 @@ Port 47805 is used for cmdctl, and must also be available.
(note, we will need to extend this to a range, or if possible, we will need to
do some on-the-fly available port finding)
-The bind10 main program, bindctl, and dig must all be in the default search
-path of your environment, and BIND 10 must not be running if you use the
-installed version when you run the tests.
+You can run the lettuce tests with the provided run_lettuce.sh script.
-If you want to test an installed version of bind 10, just run 'lettuce' in
-this directory.
+By default it will use the build tree, but you can use an installed version
+of bind10 by passing -I as the first argument of run_lettuce.sh
-We have provided a script that sets up the shell environment to run the tests
-with the build tree version of bind. If your shell uses export to set
-environment variables, you can source the script setup_intree_bind10.sh, then
-run lettuce.
+The tool 'dig' must be in the default search path of your environment. If
+you specified -I, so must the main bind10 program, as well as bindctl.
Due to the default way lettuce prints its output, it is advisable to run it
in a terminal that is wide than the default. If you see a lot of lines twice
in different colors, the terminal is not wide enough.
If you just want to run one specific feature test, use
-lettuce features/<feature file>
+run_lettuce.sh [-I] features/<feature file>
To run a specific scenario from a feature, use
-lettuce features/<feature file> -s <scenario number>
+run_lettuce.sh [-I] features/<feature file> -s <scenario number>
We have set up the tests to assume that lettuce is run from this directory,
so even if you specify a specific feature file, you should do it from this
diff --git a/tests/lettuce/README.tutorial b/tests/lettuce/README.tutorial
index c7d3cd7..7d1c801 100644
--- a/tests/lettuce/README.tutorial
+++ b/tests/lettuce/README.tutorial
@@ -54,7 +54,7 @@ The one scenario we have has no steps, so if we run it we should
see something like:
-- output
-> lettuce
+> ./run_lettuce.sh
Feature: showing off BIND 10
This is to show BIND 10 running and that it answer queries
@@ -95,7 +95,7 @@ it to be started before we continue.
And let's run the tests again.
--
-> lettuce
+> ./run_lettuce.sh
Feature: showing off BIND 10
This is to show BIND 10 running and that it answer queries
diff --git a/tests/lettuce/configurations/.gitignore b/tests/lettuce/configurations/.gitignore
new file mode 100644
index 0000000..69d136f
--- /dev/null
+++ b/tests/lettuce/configurations/.gitignore
@@ -0,0 +1,2 @@
+/bindctl_commands.config
+/example.org.config
diff --git a/tests/lettuce/configurations/bindctl/bindctl.config.orig b/tests/lettuce/configurations/bindctl/bindctl.config.orig
new file mode 100644
index 0000000..bf623c5
--- /dev/null
+++ b/tests/lettuce/configurations/bindctl/bindctl.config.orig
@@ -0,0 +1,22 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/example.org.sqlite3",
+ "listen_on": [ {
+ "port": 47806,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "Boss": {
+ "components": {
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/bindctl_commands.config.orig b/tests/lettuce/configurations/bindctl_commands.config.orig
new file mode 100644
index 0000000..d74b96e
--- /dev/null
+++ b/tests/lettuce/configurations/bindctl_commands.config.orig
@@ -0,0 +1,34 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/example.org.sqlite3",
+ "listen_on": [ {
+ "port": 47806,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "StatsHttpd": {
+ "listen_on": [ {
+ "port": 47811,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "dispensable", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-stats": { "address": "Stats", "kind": "dispensable" },
+ "b10-stats-httpd": { "address": "StatsHttpd", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/default.config b/tests/lettuce/configurations/default.config
new file mode 100644
index 0000000..9e1d3d1
--- /dev/null
+++ b/tests/lettuce/configurations/default.config
@@ -0,0 +1,16 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "StatsHttpd": {
+ "listen_on": [ {
+ "port": 47811,
+ "address": "127.0.0.1"
+ } ]
+ }
+}
diff --git a/tests/lettuce/configurations/example.org.config.orig b/tests/lettuce/configurations/example.org.config.orig
index 642f2dd..fadb3e2 100644
--- a/tests/lettuce/configurations/example.org.config.orig
+++ b/tests/lettuce/configurations/example.org.config.orig
@@ -4,7 +4,7 @@
"loggers": [ {
"debuglevel": 99,
"severity": "DEBUG",
- "name": "auth"
+ "name": "*"
} ]
},
"Auth": {
@@ -13,5 +13,11 @@
"port": 47806,
"address": "127.0.0.1"
} ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
}
}
diff --git a/tests/lettuce/configurations/example.org.inmem.config b/tests/lettuce/configurations/example.org.inmem.config
new file mode 100644
index 0000000..6418c65
--- /dev/null
+++ b/tests/lettuce/configurations/example.org.inmem.config
@@ -0,0 +1,8 @@
+{"version": 2, "Logging": {"loggers": [{"severity": "DEBUG", "name": "*", "debuglevel": 99}]}, "Auth": {"database_file": "", "listen_on": [{"port": 47806, "address": "127.0.0.1"}], "datasources": [{"zones": [{"origin": "example.org", "file": "data/example.org"}], "type": "memory"}]},
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/example2.org.config b/tests/lettuce/configurations/example2.org.config
index 1a40d1b..25314dc 100644
--- a/tests/lettuce/configurations/example2.org.config
+++ b/tests/lettuce/configurations/example2.org.config
@@ -3,7 +3,7 @@
"Logging": {
"loggers": [ {
"severity": "DEBUG",
- "name": "auth",
+ "name": "*",
"debuglevel": 99
}
]
@@ -12,7 +12,13 @@
"database_file": "data/example.org.sqlite3",
"listen_on": [ {
"port": 47807,
- "address": "127.0.0.1"
+ "address": "::1"
} ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
}
}
diff --git a/tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf b/tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf
new file mode 100644
index 0000000..a104726
--- /dev/null
+++ b/tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf
@@ -0,0 +1,32 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "auth"
+ } ]
+ },
+ "Auth": {
+ "datasources": [ {
+ "type": "memory",
+ "zones": [ {
+ "origin": "example.org",
+ "file": "data/example.org.sqlite3",
+ "filetype": "sqlite3"
+ } ]
+ } ],
+ "listen_on": [ {
+ "port": 47806,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/ixfr-out/testset1-config.db b/tests/lettuce/configurations/ixfr-out/testset1-config.db
index c5fc165..1c1b990 100644
--- a/tests/lettuce/configurations/ixfr-out/testset1-config.db
+++ b/tests/lettuce/configurations/ixfr-out/testset1-config.db
@@ -1 +1,11 @@
-{"Xfrin": {"zones": [{"use_ixfr": true, "class": "IN", "name": "example.com.", "master_addr": "178.18.82.80"}]}, "version": 2, "Logging": {"loggers": [{"debuglevel": 99, "severity": "DEBUG", "output_options": [{"output": "stderr", "flush": true}], "name": "*"}]}, "Auth": {"database_file": "data/ixfr-out/zones.slite3", "listen_on": [{"port": 47806, "address": "::"}, {"port": 47806, "address": "0.0.0.0"}]}}
+{"Xfrin": {"zones": [{"use_ixfr": true, "class": "IN", "name": "example.com.", "master_addr": "178.18.82.80"}]}, "version": 2, "Logging": {"loggers": [{"debuglevel": 99, "severity": "DEBUG", "output_options": [{"output": "stderr", "flush": true}], "name": "*"}]}, "Auth": {"database_file": "data/ixfr-out/zones.sqlite3", "listen_on": [{"port": 47806, "address": "::"}, {"port": 47806, "address": "0.0.0.0"}]},
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/multi_instance/.gitignore b/tests/lettuce/configurations/multi_instance/.gitignore
new file mode 100644
index 0000000..9509290
--- /dev/null
+++ b/tests/lettuce/configurations/multi_instance/.gitignore
@@ -0,0 +1 @@
+/multi_auth.config
diff --git a/tests/lettuce/configurations/multi_instance/multi_auth.config.orig b/tests/lettuce/configurations/multi_instance/multi_auth.config.orig
new file mode 100644
index 0000000..35b8e72
--- /dev/null
+++ b/tests/lettuce/configurations/multi_instance/multi_auth.config.orig
@@ -0,0 +1,24 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/test_nonexistent_db.sqlite3",
+ "listen_on": [ {
+ "port": 47806,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth-2": {"kind": "dispensable", "special": "auth"},
+ "b10-auth": {"kind": "dispensable", "special": "auth"},
+ "b10-cmdctl": {"kind": "needed", "special": "cmdctl"}
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/no_db_file.config b/tests/lettuce/configurations/no_db_file.config
index f865354..fc0a25d 100644
--- a/tests/lettuce/configurations/no_db_file.config
+++ b/tests/lettuce/configurations/no_db_file.config
@@ -1,10 +1,24 @@
{
"version": 2,
+ "Logging": {
+ "loggers": [ {
+ "severity": "DEBUG",
+ "name": "*",
+ "debuglevel": 99
+ }
+ ]
+ },
"Auth": {
"database_file": "data/test_nonexistent_db.sqlite3",
"listen_on": [ {
"port": 47806,
"address": "127.0.0.1"
} ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
}
}
diff --git a/tests/lettuce/configurations/nsec3/nsec3_auth.config b/tests/lettuce/configurations/nsec3/nsec3_auth.config
new file mode 100644
index 0000000..94514c0
--- /dev/null
+++ b/tests/lettuce/configurations/nsec3/nsec3_auth.config
@@ -0,0 +1 @@
+{"version": 2, "Logging": {"loggers": [{"severity": "DEBUG", "name": "*", "debuglevel": 99}]}, "Auth": {"datasources": [{"zones": [{"origin": "example.", "file": "configurations/nsec3/rfc5155-example.zone.signed"}], "type": "memory"}], "listen_on": [{"port": 47806, "address": "0.0.0.0"}]}, "Boss": {"components": {"b10-auth": {"kind": "needed", "special": "auth"}, "b10-cmdctl": {"kind": "needed", "special": "cmdctl"}}}}
diff --git a/tests/lettuce/configurations/nsec3/rfc5155-example.zone.signed b/tests/lettuce/configurations/nsec3/rfc5155-example.zone.signed
new file mode 100644
index 0000000..4120224
--- /dev/null
+++ b/tests/lettuce/configurations/nsec3/rfc5155-example.zone.signed
@@ -0,0 +1,72 @@
+;; The example NSEC3-signed zone used in RFC5155.
+
+example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+example. 3600 IN NS ns1.example.
+example. 3600 IN NS ns2.example.
+example. 3600 IN RRSIG NS 7 1 3600 20150420235959 20051021000000 40430 example. PVOgtMK1HHeSTau+HwDWC8Ts+6C8qtqd4pQJqOtdEVgg+MA+ai4fWDEh u3qHJyLcQ9tbD2vvCnMXjtz6SyObxA==
+example. 3600 IN MX 1 xx.example.
+example. 3600 IN RRSIG MX 7 1 3600 20150420235959 20051021000000 40430 example. GgQ1A9xs47k42VPvpL/a1BWUz/6XsnHkjotw9So8MQtZtl2wJBsnOQsa oHrRCrRbyriEl/GZn9Mto/Kx+wBo+w==
+example. 3600 IN DNSKEY 256 3 7 AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LUsAD0QPWU+wzt89epO6tH zkMBVDkC7qphQO2hTY4hHn9npWFRw5BYubE=
+example. 3600 IN DNSKEY 257 3 7 AwEAAcUlFV1vhmqx6NSOUOq2R/dsR7Xm3upJj7IommWSpJABVfW8Q0rO vXdM6kzt+TAu92L9AbsUdblMFin8CVF3n4s=
+example. 3600 IN RRSIG DNSKEY 7 1 3600 20150420235959 20051021000000 12708 example. AuU4juU9RaxescSmStrQks3Gh9FblGBlVU31uzMZ/U/FpsUb8aC6QZS+ sTsJXnLnz7flGOsmMGQZf3bH+QsCtg==
+example. 3600 IN NSEC3PARAM 1 0 12 AABBCCDD
+example. 3600 IN RRSIG NSEC3PARAM 7 1 3600 20150420235959 20051021000000 40430 example. C1Gl8tPZNtnjlrYWDeeUV/sGLCyy/IHie2rerN05XSA3Pq0U3+4VvGWY WdUMfflOdxqnXHwJTLQsjlkynhG6Cg==
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN A 192.0.2.127
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. h6c++bzhRuWWt2bykN6mjaTNBcXNq5UuL5EdK+iDP4eY8I0kSiKaCjg3 tC1SQkeloMeub2GWk8p6xHMPZumXlw==
+a.example. 3600 IN NS ns1.a.example.
+a.example. 3600 IN NS ns2.a.example.
+a.example. 3600 IN DS 58470 5 1 3079F1593EBAD6DC121E202A8B766A6A4837206C
+a.example. 3600 IN RRSIG DS 7 2 3600 20150420235959 20051021000000 40430 example. XacFcQVHLVzdoc45EJhN616zQ4mEXtE8FzUhM2KWjfy1VfRKD9r1MeVG wwoukOKgJxBPFsWoo722vZ4UZ2dIdA==
+ns1.a.example. 3600 IN A 192.0.2.5
+ns2.a.example. 3600 IN A 192.0.2.6
+ai.example. 3600 IN A 192.0.2.9
+ai.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. hVe+wKYMlObTRPhX0NL67GxeZfdxqr/QeR6FtfdAj5+FgYxyzPEjIzvK Wy00hWIl6wD3Vws+rznEn8sQ64UdqA==
+ai.example. 3600 IN HINFO "KLH-10" "ITS"
+ai.example. 3600 IN RRSIG HINFO 7 2 3600 20150420235959 20051021000000 40430 example. Yi42uOq43eyO6qXHNvwwfFnIustWgV5urFcxenkLvs6pKRh00VBjODmf 3Z4nMO7IOl6nHSQ1v0wLHpEZG7Xj2w==
+ai.example. 3600 IN AAAA 2001:db8::f00:baa9
+ai.example. 3600 IN RRSIG AAAA 7 2 3600 20150420235959 20051021000000 40430 example. LcdxKaCB5bGZwPDg+3JJ4O02zoMBrjxqlf6WuaHQZZfTUpb9Nf2nxFGe 2XRPfR5tpJT6GdRGcHueLuXkMjBArQ==
+c.example. 3600 IN NS ns1.c.example.
+c.example. 3600 IN NS ns2.c.example.
+ns1.c.example. 3600 IN A 192.0.2.7
+ns2.c.example. 3600 IN A 192.0.2.8
+ns1.example. 3600 IN A 192.0.2.1
+ns1.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. bu6kx73n6XEunoVGuRfAgY7EF/AJqHy7hj0jkiqJjB0dOrx3wuz9SaBe GfqWIdn/uta3SavN4FRvZR9SCFHF5Q==
+ns2.example. 3600 IN A 192.0.2.2
+ns2.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. ktQ3TqE0CfRfki0Rb/Ip5BM0VnxelbuejCC4zpLbFKA/7eD7UNAwxMgx JPtbdST+syjYSJaj4IHfeX6n8vfoGA==
+*.w.example. 3600 IN MX 1 ai.example.
+*.w.example. 3600 IN RRSIG MX 7 2 3600 20150420235959 20051021000000 40430 example. CikebjQwGQPwijVcxgcZcSJKtfynugtlBiKb9FcBTrmOoyQ4InoWVudh CWsh/URX3lc4WRUMivEBP6+4KS3ldA==
+x.w.example. 3600 IN MX 1 xx.example.
+x.w.example. 3600 IN RRSIG MX 7 3 3600 20150420235959 20051021000000 40430 example. IrK3tq/tHFIBF0scHiE/1IwMAvckS/55hAVvQyxTFbkAdDloP3NbZzu+ yoSsr3b3OX6qbBpY7WCtwwekLKRAwQ==
+x.y.w.example. 3600 IN MX 1 xx.example.
+x.y.w.example. 3600 IN RRSIG MX 7 4 3600 20150420235959 20051021000000 40430 example. MqSt5HqJIN8+SLlzTOImrh5h9Xa6gDvAW/GnnbdPc6Z7nXvCpLPJj/5l Cwx3VuzVOjkbvXze8/8Ccl2Zn2hbug==
+xx.example. 3600 IN A 192.0.2.10
+xx.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. T35hBWEZ017VC5u2c4OriKyVn/pu+fVK4AlXYOxJ6iQylfV2HQIKjv6b 7DzINB3aF/wjJqgXpQvhq+Ac6+ZiFg==
+xx.example. 3600 IN HINFO "KLH-10" "TOPS-20"
+xx.example. 3600 IN RRSIG HINFO 7 2 3600 20150420235959 20051021000000 40430 example. KimG+rDd+7VA1zRsu0ITNAQUTRlpnsmqWrihFRnU+bRa93v2e5oFNFYC s3Rqgv62K93N7AhW6Jfqj/8NzWjvKg==
+xx.example. 3600 IN AAAA 2001:db8::f00:baaa
+xx.example. 3600 IN RRSIG AAAA 7 2 3600 20150420235959 20051021000000 40430 example. IXBcXORITNwd8h3gNwyxtYFvAupS/CYWufVeuBUX0O25ivBCULjZjpDx FSxfohb/KA7YRdxENzYfMItpILl/Xw==
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN NSEC3 1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR NS SOA MX RRSIG DNSKEY NSEC3PARAM
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKLIBHYH6blRxK9rC0bMJPw Q4mLIuw85H2EY762BOCXJZMnpuwhpA==
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN NSEC3 1 1 12 AABBCCDD 2VPTU5TIMAMQTTGL4LUU9KG21E0AOR3S A RRSIG
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OmBvJ1Vgg1hCKMXHFiNeIYHK9XVW0iLDLwJN4TFoNxZuP03gAXEI634Y wOc4YBNITrj413iqNI6mRk/r1dOSUw==
+2vptu5timamqttgl4luu9kg21e0aor3s.example. 3600 IN NSEC3 1 1 12 AABBCCDD 35MTHGPGCU1QG68FAB165KLNSNK3DPVL MX RRSIG
+2vptu5timamqttgl4luu9kg21e0aor3s.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. KL1V2oFYghNV0Hm7Tf2vpJjM6l+0g1JCcVYGVfI0lKrhPmTsOA96cLEA Cgo1x8I7kApJX+obTuktZ+sdsZPY1w==
+35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN NSEC3 1 1 12 AABBCCDD B4UM86EGHHDS6NEA196SMVMLO4ORS995 NS DS RRSIG
+35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQAynzo8EUWH+z6hEIBlUT PGj15eZll6VhQqgZXtAIR3chwgW+SA==
+b4um86eghhds6nea196smvmlo4ors995.example. 3600 IN NSEC3 1 1 12 AABBCCDD GJEQE526PLBF1G8MKLP59ENFD789NJGI MX RRSIG
+b4um86eghhds6nea196smvmlo4ors995.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. ZkPG3M32lmoHM6pa3D6gZFGB/rhL//Bs3Omh5u4m/CUiwtblEVOaAKKZ d7S959OeiX43aLX3pOv0TSTyiTxIZg==
+gjeqe526plbf1g8mklp59enfd789njgi.example. 3600 IN NSEC3 1 1 12 AABBCCDD JI6NEOAEPV8B5O6K4EV33ABHA8HT9FGC A HINFO AAAA RRSIG
+gjeqe526plbf1g8mklp59enfd789njgi.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. IVnezTJ9iqblFF97vPSmfXZ5Zozngx3KX3byLTZC4QBH2dFWhf6scrGF ZB980AfCxoD9qbbKDy+rdGIeRSVNyw==
+ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. 3600 IN NSEC3 1 1 12 AABBCCDD K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H
+ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. gPkFp1s2QDQ6wQzcg1uSebZ61W33rUBDcTj72F3kQ490fEdp7k1BUIfb cZtPbX3YCpE+sIt0MpzVSKfTwx4uYA==
+k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN NSEC3 1 1 12 AABBCCDD KOHAR7MBB8DC2CE8A9QVL8HON4K53UHI
+k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. FtXGbvF0+wf8iWkyo73enAuVx03klN+pILBKS6qCcftVtfH4yVzsEZqu J27NHR7ruxJWDNMtOtx7w9WfcIg62A==
+kohar7mbb8dc2ce8a9qvl8hon4k53uhi.example. 3600 IN NSEC3 1 1 12 AABBCCDD Q04JKCEVQVMU85R014C7DKBA38O0JI5R A RRSIG
+kohar7mbb8dc2ce8a9qvl8hon4k53uhi.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. VrDXs2uVW21N08SyQIz88zml+y4ZCInTwgDr6zz43yAg+LFERjOrj3Oj ct51ac7Dp4eZbf9FQJazmASFKGxGXg==
+q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN NSEC3 1 1 12 AABBCCDD R53BQ7CC2UVMUBFU5OCMM6PERS9TK9EN A RRSIG
+q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. hV5I89b+4FHJDATp09g4bbN0R1F845CaXpL3ZxlMKimoPAyqletMlEWw LfFia7sdpSzn+ZlNNlkxWcLsIlMmUg==
+r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN NSEC3 1 1 12 AABBCCDD T644EBQK9BIBCNA874GIVR6JOJ62MLHV MX RRSIG
+r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. aupviViruXs4bDg9rCbezzBMf9h1ZlDvbW/CZFKulIGXXLj8B/fsDJar XVDA9bnUoRhEbKp+HF1FWKW7RIJdtQ==
+t644ebqk9bibcna874givr6joj62mlhv.example. 3600 IN NSEC3 1 1 12 AABBCCDD 0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM A HINFO AAAA RRSIG
+t644ebqk9bibcna874givr6joj62mlhv.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. RAjGECB8P7O+F4Pa4Dx3tC0M+Z3KmlLKImcafb9XWwx+NWUNz7NBEDBQ HivIyKPVDkChcePIX1xPl1ATNa+8Dw==
diff --git a/tests/lettuce/configurations/resolver/.gitignore b/tests/lettuce/configurations/resolver/.gitignore
new file mode 100644
index 0000000..8d60553
--- /dev/null
+++ b/tests/lettuce/configurations/resolver/.gitignore
@@ -0,0 +1 @@
+/resolver_basic.config
diff --git a/tests/lettuce/configurations/resolver/resolver_basic.config.orig b/tests/lettuce/configurations/resolver/resolver_basic.config.orig
new file mode 100644
index 0000000..0adca9f
--- /dev/null
+++ b/tests/lettuce/configurations/resolver/resolver_basic.config.orig
@@ -0,0 +1 @@
+{"version": 2, "Logging": {"loggers": [{"severity": "DEBUG", "name": "*", "debuglevel": 99}]}, "Resolver": {"query_acl": [{"action": "REJECT", "from": "127.0.0.1"}], "listen_on": [{"port": 47806, "address": "127.0.0.1"}]}, "Boss": {"components": {"b10-resolver": {"kind": "needed"}, "b10-cmdctl": {"kind": "needed", "special": "cmdctl"}}}}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master.conf b/tests/lettuce/configurations/xfrin/retransfer_master.conf
index 95cd88e..eae47a6 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_master.conf
+++ b/tests/lettuce/configurations/xfrin/retransfer_master.conf
@@ -4,19 +4,27 @@
"loggers": [ {
"debuglevel": 99,
"severity": "DEBUG",
- "name": "auth"
+ "name": "*"
} ]
},
"Auth": {
"database_file": "data/example.org.sqlite3",
"listen_on": [ {
"port": 47807,
- "address": "127.0.0.1"
+ "address": "::1"
} ]
},
"Xfrout": {
"zone_config": [ {
"origin": "example.org"
} ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
}
}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave.conf b/tests/lettuce/configurations/xfrin/retransfer_slave.conf
index 51622cd..2296b8f 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_slave.conf
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave.conf
@@ -4,7 +4,7 @@
"loggers": [ {
"debuglevel": 99,
"severity": "DEBUG",
- "name": "auth"
+ "name": "*"
} ]
},
"Auth": {
@@ -13,5 +13,13 @@
"port": 47806,
"address": "127.0.0.1"
} ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
}
}
diff --git a/tests/lettuce/data/.gitignore b/tests/lettuce/data/.gitignore
new file mode 100644
index 0000000..8c54200
--- /dev/null
+++ b/tests/lettuce/data/.gitignore
@@ -0,0 +1,2 @@
+/inmem-xfrin.sqlite3
+/test_nonexistent_db.sqlite3
diff --git a/tests/lettuce/data/commands/bad_command b/tests/lettuce/data/commands/bad_command
new file mode 100644
index 0000000..95d1694
--- /dev/null
+++ b/tests/lettuce/data/commands/bad_command
@@ -0,0 +1,9 @@
+!echo shouldshow
+# just add something so the test can verify it's reverted
+config add /Boss/components b10-auth
+config set /Boss/components/b10-auth/kind needed
+config set /Boss/components/b10-auth/special auth
+bad command
+# this should not be reached
+!echo shouldnotshow
+config commit
diff --git a/tests/lettuce/data/commands/directives b/tests/lettuce/data/commands/directives
new file mode 100644
index 0000000..4fe10f5
--- /dev/null
+++ b/tests/lettuce/data/commands/directives
@@ -0,0 +1,19 @@
+# this is a comment: commentexample1
+!echo this is an echo: echoexample2
+!verbose on
+# this is a comment with verbose on: verbosecommentexample3
+!verbose off
+# this is a comment with verbose off again: commentexample4
+# empty lines and lines with only whitespace should be ignored
+
+
+
+
+
+# directives are case insensitive, and should handle whitespace
+!ECHO echoexample5
+!eChO echoexample6
+!Verbose ON
+# verbosecommentexample7
+!verBOSE off
+# commentexample8
diff --git a/tests/lettuce/data/commands/empty b/tests/lettuce/data/commands/empty
new file mode 100644
index 0000000..e69de29
diff --git a/tests/lettuce/data/commands/nested b/tests/lettuce/data/commands/nested
new file mode 100644
index 0000000..c153694
--- /dev/null
+++ b/tests/lettuce/data/commands/nested
@@ -0,0 +1,2 @@
+# include a different file
+execute file data/commands/nested1
diff --git a/tests/lettuce/data/commands/nested1 b/tests/lettuce/data/commands/nested1
new file mode 100644
index 0000000..8f984d5
--- /dev/null
+++ b/tests/lettuce/data/commands/nested1
@@ -0,0 +1,2 @@
+# this is included by nested
+!echo shouldshow
diff --git a/tests/lettuce/data/empty_db.sqlite3 b/tests/lettuce/data/empty_db.sqlite3
index f27a8b8..c434e30 100644
Binary files a/tests/lettuce/data/empty_db.sqlite3 and b/tests/lettuce/data/empty_db.sqlite3 differ
diff --git a/tests/lettuce/data/example.org b/tests/lettuce/data/example.org
new file mode 100644
index 0000000..20a93be
--- /dev/null
+++ b/tests/lettuce/data/example.org
@@ -0,0 +1,12 @@
+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 NS ns2.example.org.
+example.org. 3600 IN MX 10 mail.example.org.
+www.example.org. 3600 IN A 192.0.2.1
+mail.example.org. 3600 IN A 192.0.2.10
+sub.example.org. 3600 IN NS ns.sub.example.org.
+ns.sub.example.org. 3600 IN A 192.0.2.101
+dname.example.org. 3600 IN DNAME dname.example.info.
+dname2.foo.example.org. 3600 IN DNAME dname2.example.info.
+ns1.example.org. 3600 IN A 192.0.2.3
+ns2.example.org. 3600 IN A 192.0.2.4
diff --git a/tests/lettuce/data/example.org.sqlite3 b/tests/lettuce/data/example.org.sqlite3
index 070012f..427fa24 100644
Binary files a/tests/lettuce/data/example.org.sqlite3 and b/tests/lettuce/data/example.org.sqlite3 differ
diff --git a/tests/lettuce/data/ixfr-out/.gitignore b/tests/lettuce/data/ixfr-out/.gitignore
new file mode 100644
index 0000000..f8de78e
--- /dev/null
+++ b/tests/lettuce/data/ixfr-out/.gitignore
@@ -0,0 +1 @@
+/zones.sqlite3
diff --git a/tests/lettuce/data/ixfr-out/zones.slite3 b/tests/lettuce/data/ixfr-out/zones.slite3
deleted file mode 100644
index a2b2dbd..0000000
Binary files a/tests/lettuce/data/ixfr-out/zones.slite3 and /dev/null differ
diff --git a/tests/lettuce/data/ixfr-out/zones.sqlite3 b/tests/lettuce/data/ixfr-out/zones.sqlite3
new file mode 100644
index 0000000..311d335
Binary files /dev/null and b/tests/lettuce/data/ixfr-out/zones.sqlite3 differ
diff --git a/tests/lettuce/features/bindctl_commands.feature b/tests/lettuce/features/bindctl_commands.feature
new file mode 100644
index 0000000..1ab506d
--- /dev/null
+++ b/tests/lettuce/features/bindctl_commands.feature
@@ -0,0 +1,156 @@
+Feature: control with bindctl
+ Assorted tests using bindctl for the administration of BIND 10.
+
+
+ Scenario: Removing modules
+ # This test runs the original example configuration, which has
+ # a number of modules. It then removes all non-essential modules,
+ # and checks whether they do disappear from the list of running
+ # modules (note that it 'misuses' the help command for this,
+ # there is a Boss command 'show_processes' but it's output is
+ # currently less standardized than 'help')
+ Given I have bind10 running with configuration bindctl_commands.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message XFROUT_STARTED
+ And wait for bind10 stderr message STATS_STARTING
+ And wait for bind10 stderr message STATHTTPD_STARTED
+
+ Then remove bind10 configuration Boss/components/NOSUCHMODULE
+ last bindctl output should contain Error
+
+ bind10 module Xfrout should be running
+ bind10 module Stats should be running
+ bind10 module Zonemgr should be running
+ bind10 module Xfrin should be running
+ bind10 module Auth should be running
+ bind10 module StatsHttpd should be running
+ bind10 module Resolver should not be running
+
+ Then remove bind10 configuration Boss/components value b10-xfrout
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+ last bindctl output should not contain Error
+
+ # assuming it won't error for further modules (if it does, the final
+ # 'should not be running' tests would fail anyway)
+ Then remove bind10 configuration Boss/components value b10-stats-httpd
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+ last bindctl output should not contain Error
+
+ Then remove bind10 configuration Boss/components value b10-stats
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+ last bindctl output should not contain Error
+
+ Then remove bind10 configuration Boss/components value b10-zonemgr
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+ last bindctl output should not contain Error
+
+ Then remove bind10 configuration Boss/components value b10-xfrin
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+ last bindctl output should not contain Error
+
+ Then remove bind10 configuration Boss/components value b10-auth
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+ last bindctl output should not contain Error
+
+ # After these ^^^ have been stopped...
+ bind10 module Xfrout should not be running
+ bind10 module Zonemgr should not be running
+ bind10 module Xfrin should not be running
+ bind10 module Auth should not be running
+ bind10 module StatsHttpd should not be running
+ bind10 module Stats should not be running
+ bind10 module Resolver should not be running
+
+ Scenario: Executing scripts from files
+ # This test tests the 'execute' command, which reads and executes
+ # bindctl commands from a file
+ Given I have bind10 running with configuration bindctl/bindctl.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+
+ # first a few bad commands
+ When I send bind10 the command execute
+ last bindctl output should contain Error
+ When I send bind10 the command execute file
+ last bindctl output should contain Error
+ When I send bind10 the command execute file data/commands/nosuchfile
+ last bindctl output should contain Error
+
+ # empty list should be no-op
+ When I send bind10 the command execute file data/commands/empty
+ last bindctl output should not contain Error
+
+ # some tests of directives like !echo and !verbose
+ When I send bind10 the command execute file data/commands/directives
+ last bindctl output should not contain Error
+ last bindctl output should not contain commentexample1
+ last bindctl output should contain echoexample2
+ last bindctl output should contain verbosecommentexample3
+ last bindctl output should not contain commentexample4
+ last bindctl output should contain echoexample5
+ last bindctl output should contain echoexample6
+ last bindctl output should contain verbosecommentexample7
+ last bindctl output should not contain commentexample8
+
+ # bad_command contains a bad command, at which point execution should stop
+ When I send bind10 the command execute file data/commands/bad_command
+ last bindctl output should contain shouldshow
+ last bindctl output should contain Error
+ last bindctl output should not contain shouldnotshow
+ # This would fail if the entire list was passed, or the configuration
+ # was committed
+ send bind10 the command config show Boss/components
+ last bindctl output should not contain b10-auth
+
+ # nested_command contains another execute script
+ When I send bind10 the command execute file data/commands/nested
+ last bindctl output should contain shouldshow
+ last bindctl output should not contain Error
+
+ # show commands from a file
+ When I send bind10 the command execute file data/commands/bad_command show
+ last bindctl output should not contain Error
+ last bindctl output should contain shouldshow
+ last bindctl output should contain shouldnotshow
+
+ Scenario: Executing builting script init_authoritative_server
+ Given I have bind10 running with configuration bindctl/bindctl.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+
+ When I send bind10 the command execute init_authoritative_server show
+ # just test some parts of the output
+ last bindctl output should contain /Boss/components/b10-auth/special
+ last bindctl output should contain /Boss/components/b10-zonemgr/kind
+ last bindctl output should contain Please
+
+ # nothing should have been changed
+ When I send bind10 the command config diff
+ last bindctl output should contain {}
+
+ # ok now make sure modules aren't running, execute it, and make
+ # sure modules are running
+ bind10 module Auth should not be running
+ bind10 module Xfrout should not be running
+ bind10 module Xfrin should not be running
+ bind10 module Zonemgr should not be running
+
+ When I send bind10 the following commands:
+ """
+ execute init_authoritative_server
+ config commit
+ """
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message XFROUT_STARTED
+
+ last bindctl output should not contain Error
+ bind10 module Auth should be running
+ bind10 module Xfrout should be running
+ bind10 module Xfrin should be running
+ bind10 module Zonemgr should be running
diff --git a/tests/lettuce/features/default.feature b/tests/lettuce/features/default.feature
new file mode 100644
index 0000000..ce7ee1e
--- /dev/null
+++ b/tests/lettuce/features/default.feature
@@ -0,0 +1,21 @@
+Feature: default bind10 config
+ Tests for the default configuration of bind10.
+
+ Scenario: Check that only the default components are running
+ Given I have bind10 running with configuration default.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message STATS_STARTING
+
+ # These should be running
+ bind10 module Boss should be running
+ And bind10 module Logging should be running
+ And bind10 module Stats should be running
+
+ # These should not be running
+ bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Auth should not be running
+ And bind10 module StatsHttpd should not be running
diff --git a/tests/lettuce/features/example.feature b/tests/lettuce/features/example.feature
index d1ed6b3..ca5ffbf 100644
--- a/tests/lettuce/features/example.feature
+++ b/tests/lettuce/features/example.feature
@@ -8,6 +8,18 @@ Feature: Example feature
Scenario: A simple example
Given I have bind10 running with configuration example.org.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
A query for www.example.org should have rcode NOERROR
A query for www.doesnotexist.org should have rcode REFUSED
The SOA serial for example.org should be 1234
@@ -26,8 +38,18 @@ Feature: Example feature
# is actually a compound step consisting of the following two
# one to start the server
When I start bind10 with configuration no_db_file.config
- # And one to wait until it reports that b10-auth has started
- Then wait for bind10 auth to start
+
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
# This is a general step to stop a named process. By convention,
# the default name for any process is the same as the one we
@@ -50,6 +72,17 @@ Feature: Example feature
# This is a compound statement that starts and waits for the
# started message
Given I have bind10 running with configuration example.org.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
# Some simple queries that is not examined further
A query for www.example.com should have rcode REFUSED
@@ -113,8 +146,18 @@ Feature: Example feature
# the system
When I start bind10 with configuration example.org.config
- Then wait for bind10 auth to start
- Wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
A query for www.example.org should have rcode NOERROR
Wait for new bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
Then set bind10 configuration Auth/database_file to data/empty_db.sqlite3
@@ -128,15 +171,20 @@ Feature: Example feature
Scenario: two bind10 instances
# This is more a test of the test system, start 2 bind10's
When I start bind10 with configuration example.org.config as bind10_one
+ And wait for bind10_one stderr message BIND10_STARTED_CC
+ And wait for bind10_one stderr message CMDCTL_STARTED
+ And wait for bind10_one stderr message AUTH_SERVER_STARTED
+
And I start bind10 with configuration example2.org.config with cmdctl port 47804 as bind10_two
+ And wait for bind10_two stderr message BIND10_STARTED_CC
+ And wait for bind10_two stderr message CMDCTL_STARTED
+ And wait for bind10_two stderr message AUTH_SERVER_STARTED
- Then wait for bind10 auth of bind10_one to start
- Then wait for bind10 auth of bind10_two to start
A query for www.example.org to 127.0.0.1:47806 should have rcode NOERROR
- A query for www.example.org to 127.0.0.1:47807 should have rcode NOERROR
+ A query for www.example.org to [::1]:47807 should have rcode NOERROR
Then set bind10 configuration Auth/database_file to data/empty_db.sqlite3
And wait for bind10_one stderr message DATASRC_SQLITE_OPEN
A query for www.example.org to 127.0.0.1:47806 should have rcode REFUSED
- A query for www.example.org to 127.0.0.1:47807 should have rcode NOERROR
+ A query for www.example.org to [::1]:47807 should have rcode NOERROR
diff --git a/tests/lettuce/features/inmemory_over_sqlite3.feature b/tests/lettuce/features/inmemory_over_sqlite3.feature
new file mode 100644
index 0000000..85737e9
--- /dev/null
+++ b/tests/lettuce/features/inmemory_over_sqlite3.feature
@@ -0,0 +1,10 @@
+Feature: In-memory zone using SQLite3 backend
+ This feature tests the authoritative server configured with an in-memory
+ data source that uses the SQLite3 data source as the backend, and tests
+ scenarios that update the zone via incoming zone transfers.
+
+ Scenario: Load and response
+ Given I have bind10 running with configuration inmemory_over_sqlite3/secondary.conf
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ A query for www.example.org should have rcode NOERROR
+ The SOA serial for example.org should be 1234
diff --git a/tests/lettuce/features/ixfr_out_bind10.feature b/tests/lettuce/features/ixfr_out_bind10.feature
index e84ad8c..24a9299 100644
--- a/tests/lettuce/features/ixfr_out_bind10.feature
+++ b/tests/lettuce/features/ixfr_out_bind10.feature
@@ -31,7 +31,14 @@ Feature: IXFR out
Scenario: Test Set 1
Given I have bind10 running with configuration ixfr-out/testset1-config.db
- Then wait for bind10 xfrout to start
+
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFROUT_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
The SOA serial for example.com should be 22
#
@@ -146,7 +153,14 @@ Feature: IXFR out
Scenario: Test Set 2
Given I have bind10 running with configuration ixfr-out/testset1-config.db
- Then wait for bind10 xfrout to start
+
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFROUT_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
The SOA serial for example.com should be 22
#
diff --git a/tests/lettuce/features/multi_instance.feature b/tests/lettuce/features/multi_instance.feature
new file mode 100644
index 0000000..4ce135a
--- /dev/null
+++ b/tests/lettuce/features/multi_instance.feature
@@ -0,0 +1,59 @@
+Feature: Multiple instances
+ This feature tests whether multiple instances can be run, and whether
+ removing them does not affect the running of other instances
+
+ Scenario: Multiple instances of Auth
+ # Standard check to test (non-)existence of a file
+ # This file is actually automatically
+ The file data/test_nonexistent_db.sqlite3 should not exist
+
+ # This config should have two running instances
+ Given I have bind10 running with configuration multi_instance/multi_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+
+ # This is a hack. We should actually check if b10-auth and
+ # b10-auth-2 are started by name. But there's currently no way
+ # for a component to find out its name and log it.
+ And wait 2 times for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ # Now we use the first step again to see if the file has been created
+ The file data/test_nonexistent_db.sqlite3 should exist
+
+ A query for example.com should have rcode REFUSED
+
+ # this also checks whether the process is running
+ If I remember the pid of process b10-auth
+ And remember the pid of process b10-auth-2
+
+ When I remove bind10 configuration Boss/components value b10-auth-2
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+
+ Then the pid of process b10-auth should not have changed
+ And a query for example.com should have rcode REFUSED
+
+ When I send bind10 the following commands
+ """
+ config add Boss/components b10-auth-2
+ config set Boss/components/b10-auth-2/special auth
+ config set Boss/components/b10-auth-2/kind needed
+ config commit
+ """
+ And wait for new bind10 stderr message AUTH_SERVER_STARTED
+ And remember the pid of process b10-auth-2
+
+ Then the pid of process b10-auth should not have changed
+ A query for example.com should have rcode REFUSED
+
+ When I remove bind10 configuration Boss/components value b10-auth
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+ Then the pid of process b10-auth-2 should not have changed
+ A query for example.com should have rcode REFUSED
diff --git a/tests/lettuce/features/nsec3_auth.feature b/tests/lettuce/features/nsec3_auth.feature
new file mode 100644
index 0000000..4e5ed5b
--- /dev/null
+++ b/tests/lettuce/features/nsec3_auth.feature
@@ -0,0 +1,466 @@
+Feature: NSEC3 Authoritative service
+ This feature tests NSEC3 as defined in RFC5155, using the example
+ zone from appendix A and testing the example responses from appendix B.
+ Additional tests can be added as well.
+
+ # Response section data is taken directly from RFC5155
+ # It has been modified slightly; it has been 'flattened' (i.e. converted
+ # to 1-line RRs with TTL and class data), and whitespace has been added
+ # in the places where dig adds them too.
+ # Any other changes from the specific example data are added as inline
+ # comments.
+
+ Scenario: B.1. Name Error
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for a.c.x.w.example. should have rcode NXDOMAIN
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 8
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKLIBHYH6blRxK9rC0bMJPw Q4mLIuw85H2EY762BOCXJZMnpuwhpA==
+ b4um86eghhds6nea196smvmlo4ors995.example. 3600 IN NSEC3 1 1 12 aabbccdd gjeqe526plbf1g8mklp59enfd789njgi MX RRSIG
+ b4um86eghhds6nea196smvmlo4ors995.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. ZkPG3M32lmoHM6pa3D6gZFGB/rhL//Bs3Omh5u4m/CUiwtblEVOaAKKZ d7S959OeiX43aLX3pOv0TSTyiTxIZg==
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN NSEC3 1 1 12 aabbccdd b4um86eghhds6nea196smvmlo4ors995 NS DS RRSIG
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQAynzo8EUWH+z6hEIBlUT PGj15eZll6VhQqgZXtAIR3chwgW+SA==
+ """
+
+ Scenario: B.2. No Data Error
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for ns1.example. type MX should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 4
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ 2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN NSEC3 1 1 12 aabbccdd 2vptu5timamqttgl4luu9kg21e0aor3s A RRSIG
+ 2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OmBvJ1Vgg1hCKMXHFiNeIYHK9XVW0iLDLwJN4TFoNxZuP03gAXEI634Y wOc4YBNITrj413iqNI6mRk/r1dOSUw==
+ """
+
+ Scenario: B2.1. No Data Error, Empty Non-Terminal
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for y.w.example. should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 4
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. 3600 IN NSEC3 1 1 12 aabbccdd k8udemvp1j2f7eg6jebps17vp3n8i58h
+ ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. gPkFp1s2QDQ6wQzcg1uSebZ61W33rUBDcTj72F3kQ490fEdp7k1BUIfb cZtPbX3YCpE+sIt0MpzVSKfTwx4uYA==
+ """
+
+ Scenario: B.3. Referral to an Opt-Out Unsigned Zone
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for mc.c.example. type MX should have rcode NOERROR
+ The last query response should have flags qr rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 6
+ The last query response should have adcount 3
+ The authority section of the last query response should be
+ """
+ c.example. 3600 IN NS ns1.c.example.
+ c.example. 3600 IN NS ns2.c.example.
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN NSEC3 1 1 12 aabbccdd b4um86eghhds6nea196smvmlo4ors995 NS DS RRSIG
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQAynzo8EUWH+z6hEIBlUT PGj15eZll6VhQqgZXtAIR3chwgW+SA==
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKLIBHYH6blRxK9rC0bMJPw Q4mLIuw85H2EY762BOCXJZMnpuwhpA==
+ """
+ The additional section of the last query response should be
+ """
+ ns1.c.example. 3600 IN A 192.0.2.7
+ ns2.c.example. 3600 IN A 192.0.2.8
+ """
+
+ Scenario: B.4. Wildcard Expansion
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for a.z.w.example. type MX should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 2
+ The last query response should have nscount 5
+ The last query response should have adcount 9
+ The answer section of the last query response should be
+ """
+ a.z.w.example. 3600 IN MX 1 ai.example.
+ a.z.w.example. 3600 IN RRSIG MX 7 2 3600 20150420235959 20051021000000 40430 example. CikebjQwGQPwijVcxgcZcSJKtfynugtlBiKb9FcBTrmOoyQ4InoWVudh CWsh/URX3lc4WRUMivEBP6+4KS3ldA==
+ """
+ The authority section of the last query response should be
+ """
+ example. 3600 IN NS ns1.example.
+ example. 3600 IN NS ns2.example.
+ example. 3600 IN RRSIG NS 7 1 3600 20150420235959 20051021000000 40430 example. PVOgtMK1HHeSTau+HwDWC8Ts+6C8qtqd4pQJqOtdEVgg+MA+ai4fWDEh u3qHJyLcQ9tbD2vvCnMXjtz6SyObxA==
+ q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
+ q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. hV5I89b+4FHJDATp09g4bbN0R1F845CaXpL3ZxlMKimoPAyqletMlEWw LfFia7sdpSzn+ZlNNlkxWcLsIlMmUg==
+ """
+ # This is slightly different from the example in RFC5155; there are
+ # more RRs in the additional section.
+ The additional section of the last query response should be
+ """
+ ai.example. 3600 IN A 192.0.2.9
+ ai.example. 3600 IN AAAA 2001:db8::f00:baa9
+ ns1.example. 3600 IN A 192.0.2.1
+ ns2.example. 3600 IN A 192.0.2.2
+ ai.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. hVe+wKYMlObTRPhX0NL67GxeZfdxqr/QeR6FtfdAj5+FgYxyzPEjIzvK Wy00hWIl6wD3Vws+rznEn8sQ64UdqA==
+ ai.example. 3600 IN RRSIG AAAA 7 2 3600 20150420235959 20051021000000 40430 example. LcdxKaCB5bGZwPDg+3JJ4O02zoMBrjxqlf6WuaHQZZfTUpb9Nf2nxFGe 2XRPfR5tpJT6GdRGcHueLuXkMjBArQ==
+ ns1.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. bu6kx73n6XEunoVGuRfAgY7EF/AJqHy7hj0jkiqJjB0dOrx3wuz9SaBe GfqWIdn/uta3SavN4FRvZR9SCFHF5Q==
+ ns2.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. ktQ3TqE0CfRfki0Rb/Ip5BM0VnxelbuejCC4zpLbFKA/7eD7UNAwxMgx JPtbdST+syjYSJaj4IHfeX6n8vfoGA==
+ """
+
+ Scenario: B.5. Wildcard No Data Error
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for a.z.w.example. type AAAA should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 8
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN NSEC3 1 1 12 aabbccdd kohar7mbb8dc2ce8a9qvl8hon4k53uhi
+ k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. FtXGbvF0+wf8iWkyo73enAuVx03klN+pILBKS6qCcftVtfH4yVzsEZqu J27NHR7ruxJWDNMtOtx7w9WfcIg62A==
+ q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
+ q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. hV5I89b+4FHJDATp09g4bbN0R1F845CaXpL3ZxlMKimoPAyqletMlEWw LfFia7sdpSzn+ZlNNlkxWcLsIlMmUg==
+ r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN NSEC3 1 1 12 aabbccdd t644ebqk9bibcna874givr6joj62mlhv MX RRSIG
+ r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. aupviViruXs4bDg9rCbezzBMf9h1ZlDvbW/CZFKulIGXXLj8B/fsDJar XVDA9bnUoRhEbKp+HF1FWKW7RIJdtQ==
+ """
+
+ Scenario: B.6. DS Child Zone No Data Error
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for example. type DS should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 4
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKLIBHYH6blRxK9rC0bMJPw Q4mLIuw85H2EY762BOCXJZMnpuwhpA==
+ """
+
+ #
+ # Below are additional tests, not explicitely stated in RFC5155
+ #
+
+ Scenario: 7.2.2 other; Name Error where one NSEC3 covers multiple parts of proof (closest encloser)
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for b.x.w.example. should have rcode NXDOMAIN
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 6
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ b4um86eghhds6nea196smvmlo4ors995.example. 3600 IN NSEC3 1 1 12 aabbccdd gjeqe526plbf1g8mklp59enfd789njgi MX RRSIG
+ b4um86eghhds6nea196smvmlo4ors995.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. ZkPG3M32lmoHM6pa3D6gZFGB/rhL//Bs3Omh5u4m/CUiwtblEVOaAKKZ d7S959OeiX43aLX3pOv0TSTyiTxIZg==
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN NSEC3 1 1 12 aabbccdd b4um86eghhds6nea196smvmlo4ors995 NS DS RRSIG
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQAynzo8EUWH+z6hEIBlUT PGj15eZll6VhQqgZXtAIR3chwgW+SA==
+ """
+
+ Scenario: 7.2.2 other; Name Error where one NSEC3 covers multiple parts of proof (wildcard)
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for a.w.example. should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 6
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN NSEC3 1 1 12 AABBCCDD KOHAR7MBB8DC2CE8A9QVL8HON4K53UHI
+ k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. FtXGbvF0+wf8iWkyo73enAuVx03klN+pILBKS6qCcftVtfH4yVzsEZqu J27NHR7ruxJWDNMtOtx7w9WfcIg62A==
+ r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN NSEC3 1 1 12 AABBCCDD T644EBQK9BIBCNA874GIVR6JOJ62MLHV MX RRSIG
+ r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. aupviViruXs4bDg9rCbezzBMf9h1ZlDvbW/CZFKulIGXXLj8B/fsDJar XVDA9bnUoRhEbKp+HF1FWKW7RIJdtQ==
+ """
+
+ Scenario: Wildcard other: Wildcard name itself
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for *.w.example. type MX should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 2
+ The last query response should have nscount 3
+ The last query response should have adcount 9
+ The answer section of the last query response should be
+ """
+ *.w.example. 3600 IN MX 1 ai.example.
+ *.w.example. 3600 IN RRSIG MX 7 2 3600 20150420235959 20051021000000 40430 example. CikebjQwGQPwijVcxgcZcSJKtfynugtlBiKb9FcBTrmOoyQ4InoWVudh CWsh/URX3lc4WRUMivEBP6+4KS3ldA==
+ """
+ The authority section of the last query response should be
+ """
+ example. 3600 IN NS ns1.example.
+ example. 3600 IN NS ns2.example.
+ example. 3600 IN RRSIG NS 7 1 3600 20150420235959 20051021000000 40430 example. PVOgtMK1HHeSTau+HwDWC8Ts+6C8qtqd4pQJqOtdEVgg+MA+ai4fWDEh u3qHJyLcQ9tbD2vvCnMXjtz6SyObxA==
+ """
+ The additional section of the last query response should be
+ """
+ ai.example. 3600 IN A 192.0.2.9
+ ai.example. 3600 IN AAAA 2001:db8::f00:baa9
+ ns1.example. 3600 IN A 192.0.2.1
+ ns2.example. 3600 IN A 192.0.2.2
+ ai.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. hVe+wKYMlObTRPhX0NL67GxeZfdxqr/QeR6FtfdAj5+FgYxyzPEjIzvK Wy00hWIl6wD3Vws+rznEn8sQ64UdqA==
+ ai.example. 3600 IN RRSIG AAAA 7 2 3600 20150420235959 20051021000000 40430 example. LcdxKaCB5bGZwPDg+3JJ4O02zoMBrjxqlf6WuaHQZZfTUpb9Nf2nxFGe 2XRPfR5tpJT6GdRGcHueLuXkMjBArQ==
+ ns1.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. bu6kx73n6XEunoVGuRfAgY7EF/AJqHy7hj0jkiqJjB0dOrx3wuz9SaBe GfqWIdn/uta3SavN4FRvZR9SCFHF5Q==
+ ns2.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. ktQ3TqE0CfRfki0Rb/Ip5BM0VnxelbuejCC4zpLbFKA/7eD7UNAwxMgx JPtbdST+syjYSJaj4IHfeX6n8vfoGA==
+ """
+
+ Scenario: Wildcard other: Wildcard name itself nodata
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for *.w.example. type A should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 4
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN NSEC3 1 1 12 AABBCCDD T644EBQK9BIBCNA874GIVR6JOJ62MLHV MX RRSIG
+ r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. aupviViruXs4bDg9rCbezzBMf9h1ZlDvbW/CZFKulIGXXLj8B/fsDJar XVDA9bnUoRhEbKp+HF1FWKW7RIJdtQ==
+ """
+
+ Scenario: Direct query for NSEC3 record
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. type NSEC3 should have rcode NXDOMAIN
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 8
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN NSEC3 1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR NS SOA MX RRSIG DNSKEY NSEC3PARAM
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKLIBHYH6blRxK9rC0bMJPw Q4mLIuw85H2EY762BOCXJZMnpuwhpA==
+ q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN NSEC3 1 1 12 AABBCCDD R53BQ7CC2UVMUBFU5OCMM6PERS9TK9EN A RRSIG
+ q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. hV5I89b+4FHJDATp09g4bbN0R1F845CaXpL3ZxlMKimoPAyqletMlEWw LfFia7sdpSzn+ZlNNlkxWcLsIlMmUg==
+ gjeqe526plbf1g8mklp59enfd789njgi.example. 3600 IN NSEC3 1 1 12 AABBCCDD JI6NEOAEPV8B5O6K4EV33ABHA8HT9FGC A HINFO AAAA RRSIG
+ gjeqe526plbf1g8mklp59enfd789njgi.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. IVnezTJ9iqblFF97vPSmfXZ5Zozngx3KX3byLTZC4QBH2dFWhf6scrGF ZB980AfCxoD9qbbKDy+rdGIeRSVNyw==
+ """
+
+ Scenario: No data, type DS, in-zone
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for ai.example. type DS should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 4
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ gjeqe526plbf1g8mklp59enfd789njgi.example. 3600 IN NSEC3 1 1 12 AABBCCDD JI6NEOAEPV8B5O6K4EV33ABHA8HT9FGC A HINFO AAAA RRSIG
+ gjeqe526plbf1g8mklp59enfd789njgi.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. IVnezTJ9iqblFF97vPSmfXZ5Zozngx3KX3byLTZC4QBH2dFWhf6scrGF ZB980AfCxoD9qbbKDy+rdGIeRSVNyw==
+ """
+
+ Scenario: No data, type DS, optout delegation
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for c.example. type DS should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 6
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN NSEC3 1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR NS SOA MX RRSIG DNSKEY NSEC3PARAM
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKLIBHYH6blRxK9rC0bMJPw Q4mLIuw85H2EY762BOCXJZMnpuwhpA==
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN NSEC3 1 1 12 AABBCCDD B4UM86EGHHDS6NEA196SMVMLO4ORS995 NS DS RRSIG
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQAynzo8EUWH+z6hEIBlUT PGj15eZll6VhQqgZXtAIR3chwgW+SA==
+ """
diff --git a/tests/lettuce/features/queries.feature b/tests/lettuce/features/queries.feature
new file mode 100644
index 0000000..de6f0fa
--- /dev/null
+++ b/tests/lettuce/features/queries.feature
@@ -0,0 +1,124 @@
+Feature: Querying feature
+ This feature is a collection of non-specific querying tests;
+ for instance whether multiple queries in a row return consistent
+ answers.
+
+ Scenario: Repeated queries
+ Given I have bind10 running with configuration example.org.inmem.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A query for www.example.org should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have ancount 1
+ The last query response should have nscount 2
+ The last query response should have adcount 2
+
+ The answer section of the last query response should be
+ """
+ www.example.org. 3600 IN A 192.0.2.1
+ """
+ The authority section of the last query response should be
+ """
+ example.org. 3600 IN NS ns1.example.org.
+ example.org. 3600 IN NS ns2.example.org.
+ """
+ The additional section of the last query response should be
+ """
+ ns1.example.org. 3600 IN A 192.0.2.3
+ ns2.example.org. 3600 IN A 192.0.2.4
+ """
+
+ # Repeat of the above
+ A query for www.example.org should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have ancount 1
+ The last query response should have nscount 2
+ The last query response should have adcount 2
+
+ The answer section of the last query response should be
+ """
+ www.example.org. 3600 IN A 192.0.2.1
+ """
+ The authority section of the last query response should be
+ """
+ example.org. 3600 IN NS ns1.example.org.
+ example.org. 3600 IN NS ns2.example.org.
+ """
+ The additional section of the last query response should be
+ """
+ ns1.example.org. 3600 IN A 192.0.2.3
+ ns2.example.org. 3600 IN A 192.0.2.4
+ """
+
+ # And now query something completely different
+ A query for nosuchname.example.org should have rcode NXDOMAIN
+ The last query response should have flags qr aa rd
+ The last query response should have ancount 0
+ The last query response should have nscount 1
+ The last query response should have adcount 0
+ The authority section of the last query response should be
+ """
+ example.org. 3600 IN SOA ns1.example.org. admin.example.org. 1234 3600 1800 2419200 7200
+ """
+
+ Scenario: ANY query
+ Given I have bind10 running with configuration example.org.inmem.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A query for example.org type ANY should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have ancount 4
+ The last query response should have nscount 0
+ The last query response should have adcount 3
+ The answer section of the last query response should be
+ """
+ example.org. 3600 IN NS ns1.example.org.
+ example.org. 3600 IN NS ns2.example.org.
+ example.org. 3600 IN SOA ns1.example.org. admin.example.org. 1234 3600 1800 2419200 7200
+ example.org. 3600 IN MX 10 mail.example.org.
+ """
+ The additional section of the last query response should be
+ """
+ ns1.example.org. 3600 IN A 192.0.2.3
+ ns2.example.org. 3600 IN A 192.0.2.4
+ mail.example.org. 3600 IN A 192.0.2.10
+ """
+ Scenario: Delegation query for unsigned child zone
+ Given I have bind10 running with configuration example.org.inmem.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ A dnssec query for www.sub.example.org type AAAA should have rcode NOERROR
+ The last query response should have flags qr rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 1
+ The last query response should have adcount 2
+ The authority section of the last query response should be
+ """
+ sub.example.org. 3600 IN NS ns.sub.example.org.
+ """
+ The additional section of the last query response should be
+ """
+ ns.sub.example.org. 3600 IN A 192.0.2.101
+ """
diff --git a/tests/lettuce/features/resolver_basic.feature b/tests/lettuce/features/resolver_basic.feature
new file mode 100644
index 0000000..4092101
--- /dev/null
+++ b/tests/lettuce/features/resolver_basic.feature
@@ -0,0 +1,36 @@
+Feature: Basic Resolver
+ This feature set is just testing the execution of the b10-resolver
+ module. It sees whether it starts up, takes configuration, and
+ answers queries.
+
+ Scenario: Listen for and answer query
+ # This scenario starts a server that runs a real resolver.
+ # In order not to send out queries into the wild, we only
+ # query for something known to be hardcoded at this moment.
+ # NOTE: once real priming has been implemented, this test needs
+ # to be revised (as it would then leak, which is probably true
+ # for any resolver system test)
+ When I start bind10 with configuration resolver/resolver_basic.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message RESOLVER_STARTED
+
+ bind10 module Resolver should be running
+ And bind10 module Auth should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ # The ACL is set to reject any queries
+ A query for l.root-servers.net. should have rcode REFUSED
+
+ # Test whether acl ACCEPT works
+ When I set bind10 configuration Resolver/query_acl[0]/action to ACCEPT
+ # This address is currently hardcoded, so shouldn't cause outside traffic
+ A query for l.root-servers.net. should have rcode NOERROR
+
+ # Check whether setting the ACL to reject again works
+ When I set bind10 configuration Resolver/query_acl[0]/action to REJECT
+ A query for l.root-servers.net. should have rcode REFUSED
diff --git a/tests/lettuce/features/terrain/.gitignore b/tests/lettuce/features/terrain/.gitignore
new file mode 100644
index 0000000..0d20b64
--- /dev/null
+++ b/tests/lettuce/features/terrain/.gitignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/tests/lettuce/features/terrain/bind10_control.py b/tests/lettuce/features/terrain/bind10_control.py
index fdc419b..c56afb7 100644
--- a/tests/lettuce/features/terrain/bind10_control.py
+++ b/tests/lettuce/features/terrain/bind10_control.py
@@ -14,8 +14,18 @@
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
from lettuce import *
+import time
import subprocess
import re
+import json
+
+ at step('sleep for (\d+) seconds')
+def wait_seconds(step, seconds):
+ """Sleep for some seconds.
+ Parameters:
+ seconds number of seconds to sleep for.
+ """
+ time.sleep(float(seconds))
@step('start bind10(?: with configuration (\S+))?' +\
'(?: with cmdctl port (\d+))?' +\
@@ -99,21 +109,155 @@ def wait_for_xfrout(step, process_name):
def have_bind10_running(step, config_file, cmdctl_port, process_name):
"""
Compound convenience step for running bind10, which consists of
- start_bind10 and wait_for_auth.
+ start_bind10.
Currently only supports the 'with configuration' option.
"""
start_step = 'start bind10 with configuration ' + config_file
- wait_step = 'wait for bind10 auth to start'
if cmdctl_port is not None:
start_step += ' with cmdctl port ' + str(cmdctl_port)
if process_name is not None:
start_step += ' as ' + process_name
- wait_step = 'wait for bind10 auth of ' + process_name + ' to start'
step.given(start_step)
- step.given(wait_step)
+
+# function to send lines to bindctl, and store the result
+def run_bindctl(commands, cmdctl_port=None):
+ """Run bindctl.
+ Parameters:
+ commands: a sequence of strings which will be sent.
+ cmdctl_port: a port number on which cmdctl is listening, is converted
+ to string if necessary. If not provided, or None, defaults
+ to 47805
+
+ bindctl's stdout and stderr streams are stored (as one multiline string
+ in world.last_bindctl_stdout/stderr.
+ Fails if the return code is not 0
+ """
+ if cmdctl_port is None:
+ cmdctl_port = 47805
+ args = ['bindctl', '-p', str(cmdctl_port)]
+ bindctl = subprocess.Popen(args, 1, None, subprocess.PIPE,
+ subprocess.PIPE, None)
+ for line in commands:
+ bindctl.stdin.write(line + "\n")
+ (stdout, stderr) = bindctl.communicate()
+ result = bindctl.returncode
+ world.last_bindctl_stdout = stdout
+ world.last_bindctl_stderr = stderr
+ assert result == 0, "bindctl exit code: " + str(result) +\
+ "\nstdout:\n" + str(stdout) +\
+ "stderr:\n" + str(stderr)
+
+
+ at step('last bindctl( stderr)? output should( not)? contain (\S+)( exactly)?')
+def check_bindctl_output(step, stderr, notv, string, exactly):
+ """Checks the stdout (or stderr) stream of the last run of bindctl,
+ fails if the given string is not found in it (or fails if 'not' was
+ set and it is found
+ Parameters:
+ stderr ('stderr'): Check stderr instead of stdout output
+ notv ('not'): reverse the check (fail if string is found)
+ string ('contain <string>') string to look for
+ exactly ('exactly'): Make an exact match delimited by whitespace
+ """
+ if stderr is None:
+ output = world.last_bindctl_stdout
+ else:
+ output = world.last_bindctl_stderr
+ found = False
+ if exactly is None:
+ if string in output:
+ found = True
+ else:
+ if re.search(r'^\s+' + string + r'\s+', output, re.IGNORECASE | re.MULTILINE) is not None:
+ found = True
+ if notv is None:
+ assert found == True, "'" + string +\
+ "' was not found in bindctl output:\n" +\
+ output
+ else:
+ assert not found, "'" + string +\
+ "' was found in bindctl output:\n" +\
+ output
+
+def parse_bindctl_output_as_data_structure():
+ """Helper function for data-related command tests: evaluates the
+ last output of bindctl as a data structure that can then be
+ inspected.
+ If the bindctl output is not valid (json) data, this call will
+ fail with an assertion failure.
+ If it is valid, it is parsed and returned as whatever data
+ structure it represented.
+ """
+ # strip any extra output after a charater that commonly terminates a valid
+ # JSON expression, i.e., ']', '}' and '"'. (The extra output would
+ # contain 'Exit from bindctl' message, and depending on environment some
+ # other control-like characters...but why is this message even there?)
+ # Note that this filter is not perfect. For example, it cannot recognize
+ # a simple expression of true/false/null.
+ output = re.sub("(.*)([^]}\"]*$)", r"\1", world.last_bindctl_stdout)
+ try:
+ return json.loads(output)
+ except ValueError as ve:
+ assert False, "Last bindctl output does not appear to be a " +\
+ "parseable data structure: '" + output + "': " + str(ve)
+
+def find_process_pid(step, process_name):
+ """Helper function to request the running processes from Boss, and
+ return the pid of the process with the given process_name.
+ Fails with an assert if the response from boss is not valid JSON,
+ or if the process with the given name is not found.
+ """
+ # show_processes output is a list of lists, where the inner lists
+ # are of the form [ pid, "name" ]
+ # Not checking data form; errors will show anyway (if these turn
+ # out to be too vague, we can change this)
+ step.given('send bind10 the command Boss show_processes')
+ running_processes = parse_bindctl_output_as_data_structure()
+
+ for process in running_processes:
+ if process[1] == process_name:
+ return process[0]
+ assert False, "Process named " + process_name +\
+ " not found in output of Boss show_processes";
+
+ at step("remember the pid of process ([\S]+)")
+def remember_pid(step, process_name):
+ """Stores the PID of the process with the given name as returned by
+ Boss show_processes command.
+ Fails if the process with the given name does not appear to exist.
+ Stores the component_name->pid value in the dict world.process_pids.
+ This should only be used by the related step
+ 'the pid of process <name> should (not) have changed'
+ Arguments:
+ process name ('process <name>') the name of the component to store
+ the pid of.
+ """
+ if world.process_pids is None:
+ world.process_pids = {}
+ world.process_pids[process_name] = find_process_pid(step, process_name)
+
+ at step('pid of process ([\S]+) should not have changed')
+def check_pid(step, process_name):
+ """Checks the PID of the process with the given name as returned by
+ Boss show_processes command.
+ Fails if the process with the given name does not appear to exist.
+ Fails if the process with the given name exists, but has a different
+ pid than it had when the step 'remember the pid of process' was
+ called.
+ Fails if that step has not been called (since world.process_pids
+ does not exist).
+ """
+ assert world.process_pids is not None, "No process pids stored"
+ assert process_name in world.process_pids, "Process named " +\
+ process_name +\
+ " was not stored"
+ pid = find_process_pid(step, process_name)
+ assert world.process_pids[process_name] == pid,\
+ "Expected pid: " + str(world.process_pids[process_name]) +\
+ " Got pid: " + str(pid)
@step('set bind10 configuration (\S+) to (.*)(?: with cmdctl port (\d+))?')
-def set_config_command(step, name, value, cmdctl_port):
+def config_set_command(step, name, value, cmdctl_port):
"""
Run bindctl, set the given configuration to the given value, and commit it.
Parameters:
@@ -123,16 +267,44 @@ def set_config_command(step, name, value, cmdctl_port):
the command to. Defaults to 47805.
Fails if cmdctl does not exit with status code 0.
"""
- if cmdctl_port is None:
- cmdctl_port = '47805'
- args = ['bindctl', '-p', cmdctl_port]
- bindctl = subprocess.Popen(args, 1, None, subprocess.PIPE,
- subprocess.PIPE, None)
- bindctl.stdin.write("config set " + name + " " + value + "\n")
- bindctl.stdin.write("config commit\n")
- bindctl.stdin.write("quit\n")
- result = bindctl.wait()
- assert result == 0, "bindctl exit code: " + str(result)
+ commands = ["config set " + name + " " + value,
+ "config commit",
+ "quit"]
+ run_bindctl(commands, cmdctl_port)
+
+ at step('send bind10 the following commands(?: with cmdctl port (\d+))?')
+def send_multiple_commands(step, cmdctl_port):
+ """
+ Run bindctl, and send it the given multiline set of commands.
+ A quit command is always appended.
+ cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
+ the command to. Defaults to 47805.
+ Fails if cmdctl does not exit with status code 0.
+ """
+ commands = step.multiline.split("\n")
+ # Always add quit
+ commands.append("quit")
+ run_bindctl(commands, cmdctl_port)
+
+ at step('remove bind10 configuration (\S+)(?: value (\S+))?(?: with cmdctl port (\d+))?')
+def config_remove_command(step, name, value, cmdctl_port):
+ """
+ Run bindctl, remove the given configuration item, and commit it.
+ Parameters:
+ name ('configuration <name>'): Identifier of the configuration to remove
+ value ('value <value>'): if name is a named set, use value to identify
+ item to remove
+ cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
+ the command to. Defaults to 47805.
+ Fails if cmdctl does not exit with status code 0.
+ """
+ cmd = "config remove " + name
+ if value is not None:
+ cmd = cmd + " " + value
+ commands = [cmd,
+ "config commit",
+ "quit"]
+ run_bindctl(commands, cmdctl_port)
@step('send bind10 the command (.+)(?: with cmdctl port (\d+))?')
def send_command(step, command, cmdctl_port):
@@ -144,15 +316,21 @@ def send_command(step, command, cmdctl_port):
the command to. Defaults to 47805.
Fails if cmdctl does not exit with status code 0.
"""
- if cmdctl_port is None:
- cmdctl_port = '47805'
- args = ['bindctl', '-p', cmdctl_port]
- bindctl = subprocess.Popen(args, 1, None, subprocess.PIPE,
- subprocess.PIPE, None)
- bindctl.stdin.write(command + "\n")
- bindctl.stdin.write("quit\n")
- (stdout, stderr) = bindctl.communicate()
- result = bindctl.returncode
- assert result == 0, "bindctl exit code: " + str(result) +\
- "\nstdout:\n" + str(stdout) +\
- "stderr:\n" + str(stderr)
+ commands = [command,
+ "quit"]
+ run_bindctl(commands, cmdctl_port)
+
+ at step('bind10 module (\S+) should( not)? be running')
+def module_is_running(step, name, not_str):
+ """
+ Convenience step to check if a module is running; can only work with
+ default cmdctl port; sends a 'help' command with bindctl, then
+ checks if the output contains the given name.
+ Parameters:
+ name ('module <name>'): The name of the module (case sensitive!)
+ not ('not'): Reverse the check (fail if it is running)
+ """
+ if not_str is None:
+ not_str = ""
+ step.given('send bind10 the command help')
+ step.given('last bindctl output should' + not_str + ' contain ' + name + ' exactly')
diff --git a/tests/lettuce/features/terrain/querying.py b/tests/lettuce/features/terrain/querying.py
index b132512..a547014 100644
--- a/tests/lettuce/features/terrain/querying.py
+++ b/tests/lettuce/features/terrain/querying.py
@@ -41,9 +41,10 @@ import re
#
# The following attributes are 'parsed' from the response, all as strings,
# and end up as direct attributes of the QueryResult object:
-# opcode, rcode, id, flags, qdcount, ancount, nscount, adcount
-# (flags is one string with all flags, in the order they appear in the
-# response packet.)
+# opcode, rcode, id, flags, qdcount, ancount, nscount, adcount,
+# edns_version, edns_flags, and edns_udp_size
+# (flags and edns_flags are both one string with all flags, in the order
+# in which they appear in the response message.)
#
# this will set 'rcode' as the result code, we 'define' one additional
# rcode, "NO_ANSWER", if the dig process returned an error code itself
@@ -55,10 +56,12 @@ import re
# See server_from_sqlite3.feature for various examples to perform queries
class QueryResult(object):
status_re = re.compile("opcode: ([A-Z])+, status: ([A-Z]+), id: ([0-9]+)")
+ edns_re = re.compile("; EDNS: version: ([0-9]+), flags: ([a-z ]*); udp: ([0-9]+)")
flags_re = re.compile("flags: ([a-z ]+); QUERY: ([0-9]+), ANSWER: " +
"([0-9]+), AUTHORITY: ([0-9]+), ADDITIONAL: ([0-9]+)")
- def __init__(self, name, qtype, qclass, address, port):
+ def __init__(self, name, qtype, qclass, address, port,
+ additional_args=None):
"""
Constructor. This fires of a query using dig.
Parameters:
@@ -67,6 +70,7 @@ class QueryResult(object):
qclass: The RR class to query. Defaults to IN if it is None.
address: The IP adress to send the query to.
port: The port number to send the query to.
+ additional_args: List of additional arguments (e.g. '+dnssec').
All parameters must be either strings or have the correct string
representation.
Only one query attempt will be made.
@@ -78,6 +82,8 @@ class QueryResult(object):
if qclass is not None:
args.append('-c')
args.append(str(qclass))
+ if additional_args is not None:
+ args.extend(additional_args)
args.append(name)
dig_process = subprocess.Popen(args, 1, None, None, subprocess.PIPE,
None)
@@ -102,6 +108,8 @@ class QueryResult(object):
"""
if line == ";; ANSWER SECTION:\n":
self.line_handler = self.parse_answer
+ elif line == ";; OPT PSEUDOSECTION:\n":
+ self.line_handler = self.parse_opt
elif line == ";; AUTHORITY SECTION:\n":
self.line_handler = self.parse_authority
elif line == ";; ADDITIONAL SECTION:\n":
@@ -131,6 +139,19 @@ class QueryResult(object):
self.nscount = flags_match.group(4)
self.adcount = flags_match.group(5)
+ def parse_opt(self, line):
+ """
+ Parse the header lines of the query response.
+ Parameters:
+ line: The current line of the response.
+ """
+ if not self._check_next_header(line):
+ edns_match = self.edns_re.search(line)
+ if edns_match is not None:
+ self.edns_version = edns_match.group(1)
+ self.edns_flags = edns_match.group(2)
+ self.edns_udp_size = edns_match.group(3)
+
def parse_question(self, line):
"""
Parse the question section lines of the query response.
@@ -179,9 +200,10 @@ class QueryResult(object):
"""
pass
- at step('A query for ([\w.]+) (?:type ([A-Z0-9]+) )?(?:class ([A-Z]+) )?' +
- '(?:to ([^:]+)(?::([0-9]+))? )?should have rcode ([\w.]+)')
-def query(step, query_name, qtype, qclass, addr, port, rcode):
+ at step('A (dnssec )?query for ([\S]+) (?:type ([A-Z0-9]+) )?' +
+ '(?:class ([A-Z]+) )?(?:to ([^:]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))? )?' +
+ 'should have rcode ([\w.]+)')
+def query(step, dnssec, query_name, qtype, qclass, addr, port, rcode):
"""
Run a query, check the rcode of the response, and store the query
result in world.last_query_result.
@@ -201,9 +223,19 @@ def query(step, query_name, qtype, qclass, addr, port, rcode):
qclass = "IN"
if addr is None:
addr = "127.0.0.1"
+ addr = re.sub(r"\[(.+)\]", r"\1", addr) # convert [IPv6_addr] to IPv6_addr
if port is None:
port = 47806
- query_result = QueryResult(query_name, qtype, qclass, addr, port)
+ additional_arguments = []
+ if dnssec is not None:
+ additional_arguments.append("+dnssec")
+ else:
+ # some builds of dig add edns0 by default. This could muck up
+ # additional counts, so unless we need dnssec, explicitly
+ # disable edns0
+ additional_arguments.append("+noedns")
+ query_result = QueryResult(query_name, qtype, qclass, addr, port,
+ additional_arguments)
assert query_result.rcode == rcode,\
"Expected: " + rcode + ", got " + query_result.rcode
world.last_query_result = query_result
@@ -255,9 +287,15 @@ def check_last_query_section(step, section):
section ('<section> section'): The name of the section (QUESTION, ANSWER,
AUTHORITY or ADDITIONAL).
The expected response is taken from the multiline part of the step in the
- scenario. Differing whitespace is ignored, but currently the order is
- significant.
+ scenario. Differing whitespace is ignored, the order of the lines is
+ ignored, and the comparison is case insensitive.
Fails if they do not match.
+ WARNING: Case insensitivity is not strictly correct; for instance the
+ data of TXT RRs would be case sensitive. But most other output is, so
+ currently the checks are always case insensitive. Should we decide
+ these checks do need to be case sensitive, we can either remove it
+ or make it optional (for the former, we'll need to update a number of
+ tests).
"""
response_string = None
if section.lower() == 'question':
@@ -265,15 +303,32 @@ def check_last_query_section(step, section):
elif section.lower() == 'answer':
response_string = "\n".join(world.last_query_result.answer_section)
elif section.lower() == 'authority':
- response_string = "\n".join(world.last_query_result.answer_section)
+ response_string = "\n".join(world.last_query_result.authority_section)
elif section.lower() == 'additional':
- response_string = "\n".join(world.last_query_result.answer_section)
+ response_string = "\n".join(world.last_query_result.additional_section)
else:
assert False, "Unknown section " + section
+
+ # Now mangle the data for 'conformance'
+ # This could be done more efficiently, but is done one
+ # by one on a copy of the original data, so it is clear
+ # what is done. Final error output is currently still the
+ # original unchanged multiline strings
+
# replace whitespace of any length by one space
response_string = re.sub("[ \t]+", " ", response_string)
expect = re.sub("[ \t]+", " ", step.multiline)
+ # lowercase them
+ response_string = response_string.lower()
+ expect = expect.lower()
+ # sort them
+ response_string_parts = response_string.split("\n")
+ response_string_parts.sort()
+ response_string = "\n".join(response_string_parts)
+ expect_parts = expect.split("\n")
+ expect_parts.sort()
+ expect = "\n".join(expect_parts)
+
assert response_string.strip() == expect.strip(),\
"Got:\n'" + response_string + "'\nExpected:\n'" + step.multiline +"'"
-
-
+
diff --git a/tests/lettuce/features/terrain/steps.py b/tests/lettuce/features/terrain/steps.py
index 4b199d6..8df0bae 100644
--- a/tests/lettuce/features/terrain/steps.py
+++ b/tests/lettuce/features/terrain/steps.py
@@ -30,12 +30,13 @@ def stop_a_named_process(step, process_name):
"""
world.processes.stop_process(process_name)
- at step('wait for (new )?(\w+) stderr message (\w+)(?: not (\w+))?')
-def wait_for_message(step, new, process_name, message, not_message):
+ at step('wait (?:(\d+) times )?for (new )?(\w+) stderr message (\w+)(?: not (\w+))?')
+def wait_for_stderr_message(step, times, new, process_name, message, not_message):
"""
Block until the given message is printed to the given process's stderr
output.
Parameter:
+ times: Check for the string this many times.
new: (' new', optional): Only check the output printed since last time
this step was used for this process.
process_name ('<name> stderr'): Name of the process to check the output of.
@@ -46,16 +47,19 @@ def wait_for_message(step, new, process_name, message, not_message):
strings = [message]
if not_message is not None:
strings.append(not_message)
- (found, line) = world.processes.wait_for_stderr_str(process_name, strings, new)
+ if times is None:
+ times = 1
+ (found, line) = world.processes.wait_for_stderr_str(process_name, strings, new, int(times))
if not_message is not None:
assert found != not_message, line
- at step('wait for (new )?(\w+) stdout message (\w+)(?: not (\w+))?')
-def wait_for_message(step, process_name, message, not_message):
+ at step('wait (?:(\d+) times )?for (new )?(\w+) stdout message (\w+)(?: not (\w+))?')
+def wait_for_stdout_message(step, times, new, process_name, message, not_message):
"""
Block until the given message is printed to the given process's stdout
output.
Parameter:
+ times: Check for the string this many times.
new: (' new', optional): Only check the output printed since last time
this step was used for this process.
process_name ('<name> stderr'): Name of the process to check the output of.
@@ -66,7 +70,9 @@ def wait_for_message(step, process_name, message, not_message):
strings = [message]
if not_message is not None:
strings.append(not_message)
- (found, line) = world.processes.wait_for_stdout_str(process_name, strings, new)
+ if times is None:
+ times = 1
+ (found, line) = world.processes.wait_for_stdout_str(process_name, strings, new, int(times))
if not_message is not None:
assert found != not_message, line
diff --git a/tests/lettuce/features/terrain/terrain.py b/tests/lettuce/features/terrain/terrain.py
index d2ac03f..9a04bf5 100644
--- a/tests/lettuce/features/terrain/terrain.py
+++ b/tests/lettuce/features/terrain/terrain.py
@@ -28,6 +28,7 @@ import subprocess
import os.path
import shutil
import re
+import sys
import time
# In order to make sure we start all tests with a 'clean' environment,
@@ -42,7 +43,16 @@ import time
# The first element is the original, the second is the target that will be
# used by the tests that need them
copylist = [
-["configurations/example.org.config.orig", "configurations/example.org.config"]
+ ["configurations/bindctl_commands.config.orig",
+ "configurations/bindctl_commands.config"],
+ ["configurations/example.org.config.orig",
+ "configurations/example.org.config"],
+ ["configurations/bindctl/bindctl.config.orig",
+ "configurations/bindctl/bindctl.config"],
+ ["configurations/resolver/resolver_basic.config.orig",
+ "configurations/resolver/resolver_basic.config"],
+ ["configurations/multi_instance/multi_auth.config.orig",
+ "configurations/multi_instance/multi_auth.config"]
]
# This is a list of files that, if present, will be removed before a scenario
@@ -159,7 +169,7 @@ class RunningProcess:
os.remove(self.stderr_filename)
os.remove(self.stdout_filename)
- def _wait_for_output_str(self, filename, running_file, strings, only_new):
+ def _wait_for_output_str(self, filename, running_file, strings, only_new, matches = 1):
"""
Wait for a line of output in this process. This will (if only_new is
False) first check all previous output from the process, and if not
@@ -173,18 +183,22 @@ class RunningProcess:
strings: Array of strings to look for.
only_new: If true, only check output since last time this method was
called. If false, first check earlier output.
+ matches: Check for the string this many times.
Returns a tuple containing the matched string, and the complete line
it was found in.
Fails if none of the strings was read after 10 seconds
(OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
"""
+ match_count = 0
if not only_new:
full_file = open(filename, "r")
for line in full_file:
for string in strings:
if line.find(string) != -1:
- full_file.close()
- return (string, line)
+ match_count += 1
+ if match_count >= matches:
+ full_file.close()
+ return (string, line)
wait_count = 0
while wait_count < OUTPUT_WAIT_MAX_INTERVALS:
where = running_file.tell()
@@ -192,42 +206,46 @@ class RunningProcess:
if line:
for string in strings:
if line.find(string) != -1:
- return (string, line)
+ match_count += 1
+ if match_count >= matches:
+ return (string, line)
else:
wait_count += 1
time.sleep(OUTPUT_WAIT_INTERVAL)
running_file.seek(where)
assert False, "Timeout waiting for process output: " + str(strings)
- def wait_for_stderr_str(self, strings, only_new = True):
+ def wait_for_stderr_str(self, strings, only_new = True, matches = 1):
"""
Wait for one of the given strings in this process's stderr output.
Parameters:
strings: Array of strings to look for.
only_new: If true, only check output since last time this method was
called. If false, first check earlier output.
+ matches: Check for the string this many times.
Returns a tuple containing the matched string, and the complete line
it was found in.
Fails if none of the strings was read after 10 seconds
(OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
"""
return self._wait_for_output_str(self.stderr_filename, self.stderr,
- strings, only_new)
+ strings, only_new, matches)
- def wait_for_stdout_str(self, strings, only_new = True):
+ def wait_for_stdout_str(self, strings, only_new = True, matches = 1):
"""
Wait for one of the given strings in this process's stdout output.
Parameters:
strings: Array of strings to look for.
only_new: If true, only check output since last time this method was
called. If false, first check earlier output.
+ matches: Check for the string this many times.
Returns a tuple containing the matched string, and the complete line
it was found in.
Fails if none of the strings was read after 10 seconds
(OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
"""
return self._wait_for_output_str(self.stdout_filename, self.stdout,
- strings, only_new)
+ strings, only_new, matches)
# Container class for a number of running processes
# i.e. servers like bind10, etc
@@ -293,7 +311,7 @@ class RunningProcesses:
for process in self.processes.values():
process.remove_files_on_exit = False
- def wait_for_stderr_str(self, process_name, strings, only_new = True):
+ def wait_for_stderr_str(self, process_name, strings, only_new = True, matches = 1):
"""
Wait for one of the given strings in the given process's stderr output.
Parameters:
@@ -301,6 +319,7 @@ class RunningProcesses:
strings: Array of strings to look for.
only_new: If true, only check output since last time this method was
called. If false, first check earlier output.
+ matches: Check for the string this many times.
Returns the matched string.
Fails if none of the strings was read after 10 seconds
(OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
@@ -309,9 +328,10 @@ class RunningProcesses:
assert process_name in self.processes,\
"Process " + process_name + " unknown"
return self.processes[process_name].wait_for_stderr_str(strings,
- only_new)
+ only_new,
+ matches)
- def wait_for_stdout_str(self, process_name, strings, only_new = True):
+ def wait_for_stdout_str(self, process_name, strings, only_new = True, matches = 1):
"""
Wait for one of the given strings in the given process's stdout output.
Parameters:
@@ -319,6 +339,7 @@ class RunningProcesses:
strings: Array of strings to look for.
only_new: If true, only check output since last time this method was
called. If false, first check earlier output.
+ matches: Check for the string this many times.
Returns the matched string.
Fails if none of the strings was read after 10 seconds
(OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
@@ -327,7 +348,8 @@ class RunningProcesses:
assert process_name in self.processes,\
"Process " + process_name + " unknown"
return self.processes[process_name].wait_for_stdout_str(strings,
- only_new)
+ only_new,
+ matches)
@before.each_scenario
def initialize(scenario):
@@ -340,6 +362,10 @@ def initialize(scenario):
# Convenience variable to access the last query result from querying.py
world.last_query_result = None
+ # For slightly better errors, initialize a process_pids for the relevant
+ # steps
+ world.process_pids = None
+
# Some tests can modify the settings. If the tests fail half-way, or
# don't clean up, this can leave configurations or data in a bad state,
# so we copy them from originals before each scenario
@@ -360,4 +386,13 @@ def cleanup(scenario):
world.processes.keep_files()
# Stop any running processes we may have had around
world.processes.stop_all_processes()
-
+
+# Environment check
+# Checks if LETTUCE_SETUP_COMPLETED is set in the environment
+# If not, abort with an error to use the run-script
+if 'LETTUCE_SETUP_COMPLETED' not in os.environ:
+ print("Environment check failure; LETTUCE_SETUP_COMPLETED not set")
+ print("Please use the run_lettuce.sh script. If you want to test an")
+ print("installed version of bind10 with these tests, use")
+ print("run_lettuce.sh -I [lettuce arguments]")
+ sys.exit(1)
diff --git a/tests/lettuce/features/terrain/transfer.py b/tests/lettuce/features/terrain/transfer.py
index 305e677..4ba45b8 100644
--- a/tests/lettuce/features/terrain/transfer.py
+++ b/tests/lettuce/features/terrain/transfer.py
@@ -58,7 +58,7 @@ class TransferResult(object):
if len(line) > 0 and line[0] != ';':
self.records.append(line)
- at step('An AXFR transfer of ([\w.]+)(?: from ([^:]+)(?::([0-9]+))?)?')
+ at step('An AXFR transfer of ([\w.]+)(?: from ([^:]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))?)?')
def perform_axfr(step, zone_name, address, port):
"""
Perform an AXFR transfer, and store the result as an instance of
@@ -72,6 +72,8 @@ def perform_axfr(step, zone_name, address, port):
"""
if address is None:
address = "127.0.0.1"
+ # convert [IPv6_addr] to IPv6_addr:
+ address = re.sub(r"\[(.+)\]", r"\1", address)
if port is None:
port = 47806
args = [ 'dig', 'AXFR', '@' + str(address), '-p', str(port), zone_name ]
diff --git a/tests/lettuce/features/xfrin_bind10.feature b/tests/lettuce/features/xfrin_bind10.feature
index 70c3571..2a56356 100644
--- a/tests/lettuce/features/xfrin_bind10.feature
+++ b/tests/lettuce/features/xfrin_bind10.feature
@@ -3,9 +3,29 @@ Feature: Xfrin
Scenario: Retransfer command
Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ And wait for master stderr message BIND10_STARTED_CC
+ And wait for master stderr message CMDCTL_STARTED
+ And wait for master stderr message AUTH_SERVER_STARTED
+ And wait for master stderr message XFROUT_STARTED
+ And wait for master stderr message ZONEMGR_STARTED
+
And I have bind10 running with configuration xfrin/retransfer_slave.conf
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
A query for www.example.org should have rcode REFUSED
Wait for bind10 stderr message CMDCTL_STARTED
- When I send bind10 the command Xfrin retransfer example.org IN 127.0.0.1 47807
- Then wait for new bind10 stderr message XFRIN_XFR_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
+ When I send bind10 the command Xfrin retransfer example.org IN ::1 47807
+ Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
A query for www.example.org should have rcode NOERROR
+
+ # The transferred zone should have 11 non-NSEC3 RRs and 1 NSEC3 RR.
+ # The following check will get these by AXFR, so the total # of RRs
+ # should be 13, counting the duplicated SOA.
+ # At this point we can confirm both in and out of AXFR for a zone
+ # containing an NSEC3 RR.
+ When I do an AXFR transfer of example.org from ::1 47807
+ Then transfer result should have 13 rrs
diff --git a/tests/lettuce/run_lettuce.sh b/tests/lettuce/run_lettuce.sh
new file mode 100755
index 0000000..9580dce
--- /dev/null
+++ b/tests/lettuce/run_lettuce.sh
@@ -0,0 +1,25 @@
+#! /bin/sh
+
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+if [ "$1" = "-I" ]; then
+ shift
+ echo "$@"
+ LETTUCE_SETUP_COMPLETED=1 exec lettuce $@
+else
+ . ./setup_intree_bind10.sh
+ exec lettuce $@
+fi
diff --git a/tests/lettuce/setup_intree_bind10.sh.in b/tests/lettuce/setup_intree_bind10.sh.in
old mode 100755
new mode 100644
index b1f17bc..4ccf6ca
--- a/tests/lettuce/setup_intree_bind10.sh.in
+++ b/tests/lettuce/setup_intree_bind10.sh.in
@@ -23,7 +23,7 @@ BIND10_PATH=@abs_top_builddir@/src/bin/bind10
PATH=@abs_top_builddir@/src/bin/bind10:@abs_top_builddir@/src/bin/bindctl:@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:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:$PATH
export PATH
-PYTHONPATH=@abs_top_builddir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@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:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:@abs_top_builddir@/src/lib/python/isc/datasrc/.libs
+PYTHONPATH=@abs_top_builddir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@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:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:@abs_top_builddir@/src/lib/python/isc/datasrc/.libs:$PYTHONPATH
export PYTHONPATH
# If necessary (rare cases), explicitly specify paths to dynamic libraries
@@ -44,3 +44,5 @@ export B10_FROM_BUILD
BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
export BIND10_MSGQ_SOCKET_FILE
+
+export LETTUCE_SETUP_COMPLETED=1
diff --git a/tests/system/.gitignore b/tests/system/.gitignore
new file mode 100644
index 0000000..76f87fe
--- /dev/null
+++ b/tests/system/.gitignore
@@ -0,0 +1,2 @@
+/conf.sh
+/run.sh
diff --git a/tests/system/bindctl/nsx1/.gitignore b/tests/system/bindctl/nsx1/.gitignore
new file mode 100644
index 0000000..4a8ce05
--- /dev/null
+++ b/tests/system/bindctl/nsx1/.gitignore
@@ -0,0 +1,3 @@
+/b10-config.db.template
+/bind10.run
+/bindctl.out
diff --git a/tests/system/bindctl/nsx1/b10-config.db.template.in b/tests/system/bindctl/nsx1/b10-config.db.template.in
index 162329a..7a3647c 100644
--- a/tests/system/bindctl/nsx1/b10-config.db.template.in
+++ b/tests/system/bindctl/nsx1/b10-config.db.template.in
@@ -3,8 +3,5 @@
"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/tests.sh b/tests/system/bindctl/tests.sh
index 565b306..352642e 100755
--- a/tests/system/bindctl/tests.sh
+++ b/tests/system/bindctl/tests.sh
@@ -25,10 +25,25 @@ status=0
n=0
# TODO: consider consistency with statistics definition in auth.spec
-auth_queries_tcp="\<queries\.tcp\>"
-auth_queries_udp="\<queries\.udp\>"
+cnt_name1="\<queries\.tcp\>"
+cnt_name2="\<queries\.udp\>"
+cnt_name3="\<opcode\.query\>"
+cnt_value1=0
+cnt_value2=0
+cnt_value3=0
-echo "I:Checking b10-auth is working by default ($n)"
+echo "I:Checking b10-auth is disabled by default ($n)"
+$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A > /dev/null && status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Starting b10-auth and checking that it works ($n)"
+echo 'config add Boss/components b10-auth
+config set Boss/components/b10-auth { "special": "auth", "kind": "needed" }
+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
# 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
@@ -42,10 +57,14 @@ 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
+# the server should have received 1 UDP and 0 TCP queries (the server
+# startup script no longer sends any TCP queries)
+cnt_value1=`expr $cnt_value1 + 0`
+cnt_value2=`expr $cnt_value2 + 1`
+cnt_value3=`expr $cnt_value1 + $cnt_value2`
+grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
if [ $status != 0 ]; then echo "I:failed"; fi
n=`expr $n + 1`
@@ -56,7 +75,7 @@ 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
+$DIG +tcp +norec @10.53.0.1 -p 53210 ns.example.com. A > /dev/null && status=1
if [ $status != 0 ]; then echo "I:failed"; fi
n=`expr $n + 1`
@@ -68,6 +87,7 @@ 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
+# 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`
@@ -78,8 +98,12 @@ 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
+cnt_value1=0
+cnt_value2=1
+cnt_value3=`expr $cnt_value1 + $cnt_value2`
+grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
if [ $status != 0 ]; then echo "I:failed"; fi
n=`expr $n + 1`
@@ -102,10 +126,68 @@ 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
+cnt_value1=`expr $cnt_value1 + 0`
+cnt_value2=`expr $cnt_value2 + 1`
+cnt_value3=`expr $cnt_value1 + $cnt_value2`
+grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Starting more b10-auths and checking that ($n)"
+for i in 2 3
+do
+ echo 'config add Boss/components b10-auth-'$i'
+config set Boss/components/b10-auth-'$i' { "special": "auth", "kind": "needed" }
+config commit
+quit
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
+done
+$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 consistency after a pause ($n)"
+sleep 2
+cnt_value1=`expr $cnt_value1 + 0`
+cnt_value2=`expr $cnt_value2 + 1`
+cnt_value3=`expr $cnt_value1 + $cnt_value2`
+# Rechecking some times
+for i in 1 2 3 4
+do
+ echo 'Stats show
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
+ # The statistics counters should keep being consistent even while
+ # multiple b10-auths are running.
+ grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+ grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+ grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
+ if [ $status != 0 ]; then echo "I:failed "; break ; fi
+done
+n=`expr $n + 1`
+
+echo "I:Stopping extra b10-auths and checking that ($n)"
+for i in 3 2
+do
+ echo 'config remove Boss/components b10-auth-'$i'
+config commit
+quit
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
+done
+$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`
+
+# The statistics counters can not be rechecked here because the auth
+# instance seems to hang up after one of the multiple auth instances
+# was removed via bindctl. This reason seems to be the same reason as
+# #1703.
+
echo "I:exit status: $status"
exit $status
diff --git a/tests/system/glue/.gitignore b/tests/system/glue/.gitignore
new file mode 100644
index 0000000..87e08bf
--- /dev/null
+++ b/tests/system/glue/.gitignore
@@ -0,0 +1 @@
+/setup.sh
diff --git a/tests/system/glue/nsx1/.gitignore b/tests/system/glue/nsx1/.gitignore
new file mode 100644
index 0000000..c0750b3
--- /dev/null
+++ b/tests/system/glue/nsx1/.gitignore
@@ -0,0 +1,3 @@
+/b10-config.db
+/bind10.run
+/bindctl.out
diff --git a/tests/system/glue/nsx1/b10-config.db.in b/tests/system/glue/nsx1/b10-config.db.in
index acd040c..660183b 100644
--- a/tests/system/glue/nsx1/b10-config.db.in
+++ b/tests/system/glue/nsx1/b10-config.db.in
@@ -3,7 +3,14 @@
"listen_on": [{"address": "10.53.0.1", "port": 53210}],
"database_file": "@abs_builddir@/zone.sqlite3"
},
- "Xfrout": {
- "log_file": "@abs_builddir@/Xfrout.log"
+ "Boss": {
+ "components": {
+ "b10-auth": {"kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-stats": { "address": "Stats", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
}
}
diff --git a/tests/system/ixfr/.gitignore b/tests/system/ixfr/.gitignore
new file mode 100644
index 0000000..027d45e
--- /dev/null
+++ b/tests/system/ixfr/.gitignore
@@ -0,0 +1,8 @@
+/b10-config.db
+/common_tests.sh
+/db.example.n0
+/db.example.n2
+/db.example.n2.refresh
+/db.example.n4
+/db.example.n6
+/ixfr_init.sh
diff --git a/tests/system/ixfr/b10-config.db.in b/tests/system/ixfr/b10-config.db.in
index 946d80f..156c959 100644
--- a/tests/system/ixfr/b10-config.db.in
+++ b/tests/system/ixfr/b10-config.db.in
@@ -19,5 +19,15 @@
"name": "example.",
"class": "IN"
}]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": {"kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-stats": { "address": "Stats", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
}
}
diff --git a/tests/system/ixfr/in-1/.gitignore b/tests/system/ixfr/in-1/.gitignore
new file mode 100644
index 0000000..87e08bf
--- /dev/null
+++ b/tests/system/ixfr/in-1/.gitignore
@@ -0,0 +1 @@
+/setup.sh
diff --git a/tests/system/ixfr/in-2/.gitignore b/tests/system/ixfr/in-2/.gitignore
new file mode 100644
index 0000000..87e08bf
--- /dev/null
+++ b/tests/system/ixfr/in-2/.gitignore
@@ -0,0 +1 @@
+/setup.sh
diff --git a/tests/system/ixfr/in-2/ns1/.gitignore b/tests/system/ixfr/in-2/ns1/.gitignore
new file mode 100644
index 0000000..35ae1cb
--- /dev/null
+++ b/tests/system/ixfr/in-2/ns1/.gitignore
@@ -0,0 +1 @@
+/named.run
diff --git a/tests/system/ixfr/in-2/nsx2/.gitignore b/tests/system/ixfr/in-2/nsx2/.gitignore
new file mode 100644
index 0000000..d31eb18
--- /dev/null
+++ b/tests/system/ixfr/in-2/nsx2/.gitignore
@@ -0,0 +1 @@
+/bindctl.out
diff --git a/tests/system/ixfr/in-2/tests.sh b/tests/system/ixfr/in-2/tests.sh
index 7b1e2a8..3050713 100644
--- a/tests/system/ixfr/in-2/tests.sh
+++ b/tests/system/ixfr/in-2/tests.sh
@@ -54,7 +54,7 @@ then
exit 1
fi
-grep XFRIN_XFR_TRANSFER_SUCCESS nsx2/bind10.run | grep IXFR > /dev/null
+grep XFRIN_IXFR_TRANSFER_SUCCESS nsx2/bind10.run | grep IXFR > /dev/null
if [ $? -ne 0 ];
then
echo "R:$CLIENT_NAME FAIL no 'IXFR successful' message in the BIND 10 log"
diff --git a/tests/system/ixfr/in-3/.gitignore b/tests/system/ixfr/in-3/.gitignore
new file mode 100644
index 0000000..87e08bf
--- /dev/null
+++ b/tests/system/ixfr/in-3/.gitignore
@@ -0,0 +1 @@
+/setup.sh
diff --git a/tests/system/ixfr/in-4/.gitignore b/tests/system/ixfr/in-4/.gitignore
new file mode 100644
index 0000000..87e08bf
--- /dev/null
+++ b/tests/system/ixfr/in-4/.gitignore
@@ -0,0 +1 @@
+/setup.sh
diff --git a/tests/system/start.pl b/tests/system/start.pl
index daa4577..32284de 100755
--- a/tests/system/start.pl
+++ b/tests/system/start.pl
@@ -53,6 +53,8 @@ if ($server && !-d "$test/$server") {
my $topdir = abs_path("$test/..");
my $testdir = abs_path("$test");
my $RUN_BIND10 = $ENV{'RUN_BIND10'};
+my $RUN_BINDCTL = $ENV{'RUN_BINDCTL'};
+my $BINDCTL_CSV_DIR = $ENV{'BINDCTL_CSV_DIR'};
my $NAMED = $ENV{'BIND9_NAMED'};
my $LWRESD = $ENV{'LWRESD'};
my $DIG = $ENV{'DIG'};
@@ -211,14 +213,15 @@ sub verify_server {
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");
+ my $return = system("echo \"Stats show\" | $RUN_BINDCTL --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.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;
+ } else {
+ print "I:no response from $server. retrying.\n";
}
sleep 2;
}
diff --git a/tests/tools/badpacket/.gitignore b/tests/tools/badpacket/.gitignore
new file mode 100644
index 0000000..ad6c1e6
--- /dev/null
+++ b/tests/tools/badpacket/.gitignore
@@ -0,0 +1 @@
+/badpacket
diff --git a/tests/tools/badpacket/scan.cc b/tests/tools/badpacket/scan.cc
index a6e7229..1559a1f 100644
--- a/tests/tools/badpacket/scan.cc
+++ b/tests/tools/badpacket/scan.cc
@@ -61,7 +61,8 @@ Scan::scan(const CommandOptions& options) {
RRType::A()));
OutputBufferPtr msgbuf(new OutputBuffer(512));
- MessageRenderer renderer(*msgbuf);
+ MessageRenderer renderer;
+ renderer.setBuffer(msgbuf.get());
message.toWire(renderer);
iterateFlagsStart(msgbuf, options);
diff --git a/tests/tools/badpacket/tests/.gitignore b/tests/tools/badpacket/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/tests/tools/badpacket/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/tests/tools/perfdhcp/.gitignore b/tests/tools/perfdhcp/.gitignore
new file mode 100644
index 0000000..1a8375a
--- /dev/null
+++ b/tests/tools/perfdhcp/.gitignore
@@ -0,0 +1 @@
+/perfdhcp
diff --git a/tests/tools/perfdhcp/perfdhcp.c b/tests/tools/perfdhcp/perfdhcp.c
index 6e02b7e..3ab9a2e 100644
--- a/tests/tools/perfdhcp/perfdhcp.c
+++ b/tests/tools/perfdhcp/perfdhcp.c
@@ -66,6 +66,35 @@ main(const int argc, char* const argv[])
#include <time.h>
#include <unistd.h>
+#ifndef HAVE_PSELECT
+
+#include <assert.h>
+
+/* Platforms such as OpenBSD don't provide a pselect(), so we use our
+ own implementation for this testcase, which wraps around select() and
+ hence doesn't implement the high precision timer. This implementation
+ is fine for our purpose. */
+
+static int
+pselect (int nfds, fd_set *readfds, fd_set *writefds,
+ fd_set *exceptfds, const struct timespec *timeout,
+ const sigset_t *sigmask)
+{
+ struct timeval my_timeout;
+
+ /* Our particular usage of pselect() doesn't use these fields. */
+ assert(writefds == NULL);
+ assert(exceptfds == NULL);
+ assert(sigmask == NULL);
+
+ my_timeout.tv_sec = timeout->tv_sec;
+ my_timeout.tv_usec = timeout->tv_nsec / 1000;
+
+ return (select(nfds, readfds, writefds, exceptfds, &my_timeout));
+}
+
+#endif /* !HAVE_PSELECT */
+
/* DHCPv4 defines (to be moved/shared) */
#define DHCP_OFF_OPCODE 0
@@ -1207,6 +1236,13 @@ getsock6(void)
perror("socket");
exit(1);
}
+ ret = bind(sock,
+ (struct sockaddr *) &localaddr,
+ sizeof(struct sockaddr_in6));
+ if (ret < 0) {
+ perror("Failed to bind v6 socket to local-link address");
+ exit(1);
+ }
/* perform the multicast stuff when the destination is multicast */
if (IN6_IS_ADDR_MULTICAST(&s6->sin6_addr)) {
int hops = 1;
diff --git a/tools/git-obsolete-branch.py b/tools/git-obsolete-branch.py
new file mode 100755
index 0000000..70a7788
--- /dev/null
+++ b/tools/git-obsolete-branch.py
@@ -0,0 +1,198 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+#
+# This script lists obsolete (fully merged) branches. It is useful for periodic
+# maintenance of our GIT tree.
+#
+# It is good idea to use following command before running this script:
+#
+# git pull
+# git remote prune origin
+#
+# This script requires python 2.7 or 3.
+#
+# I have limited experience in Python. If things are done in a strange or
+# uncommon way, there are no obscure reasons to do it that way, just plain
+# lack of experience.
+#
+# tomek
+
+import string
+import subprocess
+import sys
+from optparse import OptionParser
+
+class Branch:
+ MERGED = 1
+ NOTMERGED = 2
+ name = None
+ status = NOTMERGED
+ last_commit = None
+
+
+def branch_list_get(verbose):
+ """ Generates a list of available remote branches and
+ checks their status (merged/unmerged). A branch is merged
+ if all changes on that branch are also on master. """
+
+ # call git branch -r (list of remote branches)
+ txt_list = subprocess.check_output(["git", "branch", "-r"])
+
+ txt_list = txt_list.split(b"\n")
+
+ # we will store list of suitable branches here
+ out = []
+ for branch in txt_list:
+ # skip empty lines
+ if len(branch) == 0:
+ continue
+
+ # skip branches that are aliases (something -> something_else)
+ if branch.find(b"->") != -1:
+ continue
+
+ # don't complain about master
+ if branch == b"origin/master":
+ continue
+
+ branch_info = Branch()
+
+ # get branch name
+ branch_info.name = branch.strip(b" ")
+ branch_info.name = branch_info.name.decode("utf-8")
+
+ # check if branch is merged or not
+ if verbose:
+ print("Checking branch %s" % branch_info.name)
+
+ # get a diff with changes that are on that branch only
+ # i.e. all unmerged code.
+ cmd = ["git", "diff", "master..." + branch_info.name ]
+ diff = subprocess.check_output(cmd)
+ if len(diff) == 0:
+ # No diff? Then all changes from that branch are on master as well.
+ branch_info.status = Branch.MERGED
+
+ # let's get the last contributor with extra formatting
+ # see man git-log and search for PRETTY FORMATS.
+ # %ai = date, %ae = author e-mail, %an = author name
+ cmd = [ "git" , "log", "-n", "1", "--pretty=\"%ai,%ae,%an\"",
+ branch_info.name ]
+ offender = subprocess.check_output(cmd)
+ offender = offender.strip(b"\n\"")
+
+ # comment out this 2 lines to disable obfuscation
+ offender = offender.replace(b"@", b"(at)")
+ # Obfuscating a dot does not work too well for folks that use
+ # initials
+ #offender = offender.replace(b".", b"(dot)")
+
+ branch_info.last_commit = offender.decode("utf-8")
+
+ else:
+ # diff is not empty, so there is something to merge
+ branch_info.status = Branch.NOTMERGED
+
+ out.append(branch_info)
+ return out
+
+def branch_print(branches, csv, print_merged, print_notmerged, print_stats):
+ """ prints out list of branches with specified details (using
+ human-readable (or CSV) format. It is possible to specify,
+ which branches should be printed (merged, notmerged) and
+ also print out summary statistics """
+
+ # counters used for statistics
+ merged = 0
+ notmerged = 0
+
+ # compact list of merged/notmerged branches
+ merged_str = ""
+ notmerged_str = ""
+ for branch in branches:
+ if branch.status == Branch.MERGED:
+ merged = merged + 1
+ if not print_merged:
+ continue
+ if csv:
+ print("%s,merged,%s" % (branch.name, branch.last_commit) )
+ else:
+ merged_str = merged_str + " " + branch.name
+ else:
+ # NOT MERGED
+ notmerged = notmerged + 1
+ if not print_notmerged:
+ continue
+ if csv:
+ print("%s,notmerged,%s" % (branch.name, branch.last_commit) )
+ else:
+ notmerged_str = notmerged_str + " " + branch.name
+
+ if not csv:
+ if print_merged:
+ print("Merged branches : %s" % (merged_str))
+ if print_notmerged:
+ print("NOT merged branches: %s" % (notmerged_str))
+
+ if print_stats:
+ print("#----------")
+ print("#Merged : %d" % merged)
+ print("#Not merged: %d" % notmerged)
+
+
+def parse_args(args=sys.argv[1:], Parser=OptionParser):
+
+ parser = Parser(description="This script prints out merged and/or unmerged"
+ " branches of a GIT tree.")
+
+ parser.add_option("-c", "--csv", action="store_true",
+ default=False, help="generates CSV output")
+ parser.add_option("-u", "--unmerged", action="store_true",
+ default=False, help="lists unmerged branches")
+ parser.add_option("-m", "--skip-merged", action="store_true",
+ default=False, help="omits listing merged branches")
+ parser.add_option("-s", "--stats", action="store_true",
+ default=False, help="prints also statistics")
+
+ (options, args) = parser.parse_args(args)
+
+ if args:
+ parser.print_help()
+ sys.exit(1)
+
+ return options
+
+def main():
+ usage = """%prog
+ Lists all obsolete (fully merged into master) branches.
+ """
+
+ options = parse_args()
+ csv = options.csv
+ merged = not options.skip_merged
+ unmerged = options.unmerged
+ stats = options.stats
+
+ if csv:
+ print("branch name,status,date,last commit(mail),last commit(name)")
+
+ branch_list = branch_list_get(not csv)
+
+ branch_print(branch_list, csv, merged, unmerged, stats)
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/system_messages.py b/tools/system_messages.py
index 7b0d60c..fd4c186 100644
--- a/tools/system_messages.py
+++ b/tools/system_messages.py
@@ -71,7 +71,7 @@ SEC_HEADER="""<?xml version="1.0" encoding="UTF-8"?>
<title>BIND 10 Messages Manual</title>
<copyright>
- <year>2011</year><holder>Internet Systems Consortium, Inc.</holder>
+ <year>2011-2012</year><holder>Internet Systems Consortium, Inc.</holder>
</copyright>
<abstract>
More information about the bind10-changes
mailing list