BIND 10 jreed-doxygen, updated. ba9ed9d67285288e425e9fe9ebddf99bcda69d8a [jreed-doxygen]Merge branch 'master' into jreed-doxygen
BIND 10 source code commits
bind10-changes at lists.isc.org
Mon Mar 14 15:47:43 UTC 2011
The branch, jreed-doxygen has been updated
via ba9ed9d67285288e425e9fe9ebddf99bcda69d8a (commit)
via 4049550671160e2bbc06ca7e65996d0b7e243971 (commit)
via 728f0af26952a8e684be96c73567defbe81052ab (commit)
via 9ea1252c6694bd933049e0629cae3d2d4a1cd47e (commit)
via f8fb852bc6aef292555063590c361f01cf29e5ca (commit)
via fdfe3422ffc3d7f6eb44114d02a0a3759d4dee7c (commit)
via 721a53160c15e8218f6798309befe940b9597ba0 (commit)
via 677c3d840612c58d1cd011adb678e464a904f959 (commit)
via 67bfb6c60061a86dd0420df77d5cb85af935c634 (commit)
via ec8977f28f7824c8c0b07180eda806be22110199 (commit)
via 2e697f95732f976f3ba51a671a0c23d2057f3162 (commit)
via e2c01932a3a51d44a76cdfe7c97bcc14bab8a1cc (commit)
via 19a98f3b7abb7ec47957f9143628174f61e3f590 (commit)
via 76022a7e9f3ff339f0f9f10049aa85e5784d72c5 (commit)
via c952ddadcd1c0339097009c7cc9015bf33fb81aa (commit)
via 454739d105be01d7b9711038ca68271d0f4fd02c (commit)
via 11889ab357ea40764683e4be03bcc27a4ab6bb04 (commit)
via 9efbe64fe3ff22bb5fba46de409ae058f199c8a7 (commit)
via 7e385db1cb3baae0bcc5461c17a07062816cc5a6 (commit)
via 3487cf43d093e5f3abc120fb416a73d8a0e6a878 (commit)
via 527718a5b0447981ab410c5379bb1a68ef4d9fa9 (commit)
via fef3266de36bdf6b6f349d3176ce37b6e29f0a7e (commit)
via db1c1a4eec4249d96c385c69a4fe4bc8c49b1b10 (commit)
via 385a9f81dd2d30a370b2f45a6ff0ec7d09dbea79 (commit)
via 420ec42bd913fb83da37b26b75faae49c7957c46 (commit)
via 1ecb969fc160370724527c3e2e1812463f2398ca (commit)
via 4c05048ba059b79efeab53498737abe94d37ee07 (commit)
via d6f579083977bb3a94ed1bf2ffcaa31da878e0bf (commit)
via cba9add1f69d78f1ace066e76136bcf6f0a0d3d9 (commit)
via f7d2e62980d949a74bcaf03f6079cb847d6f0f0f (commit)
via 0cd6c3812b7e7250be1381cf87140794c9fa3c42 (commit)
via bf0b8426e2d05c1fc30a2b29929531c430f8bcda (commit)
via a84eadd788ec9544c79b26a47f5ef34f6b34c359 (commit)
via d39787aa4305f91cfca004088c6f5ca2a3867622 (commit)
via ba92da6356b8d3a5b164102af9d8d22259efccc9 (commit)
via 7c38ee8fabb2f93c83495e45c58a37712da25e67 (commit)
via b1bc4d63d568d15f6b21a3c25a2848bea2553ff7 (commit)
via a0d91794e105108425429a6ab9eed196171955eb (commit)
via db57bdd2f3f73b7d4048fec614f0a3db73c8489a (commit)
via 08d197753711038ef73a50ebc4458df5a454f648 (commit)
via 87d58087b71b509d9798bd700b9ba968712e142d (commit)
via 827babb0d6caade8ca685e5f3e779c756f9ebe1a (commit)
via 290bbf2d7c93c202b22f1f4e2638ae01038bc983 (commit)
via 86d0ba9023b4f4a23cf203d3c9bf7c2f3668315e (commit)
via 1ac4ea1a6996dd39d15d4f6cbdf0a766e8d8d569 (commit)
via 002aa60f46e33079f4d9bc131115a127ea5a5130 (commit)
via 3a6535a46295133690647d8fb75819abb5107bac (commit)
via 58da05cd5f8709c8f4e1824c5479a0e3cd2b4371 (commit)
via bf8ba76aed284a9f1907b3edde1f546c9a4c0a3c (commit)
via 416fcf7eb6c7c0dc249bcc838d75504f070750a1 (commit)
via 64978870d1b6843e94268160f5e9c7b7dfbeb755 (commit)
via b4149e03fd8f722fe1f0fe567b34d530da005109 (commit)
via a447b8e583551311e487816e11a171fdb0e8951a (commit)
via e0863720a874d75923ea66adcfbf5b2948efb10a (commit)
via 03de120c1148775977a8cad2b0697e934e2a823a (commit)
via 241571157deb66f6766918b783379409e294e2f5 (commit)
via 65a77d8fde64d464c75917a1ab9b6b3f02640ca6 (commit)
via 9287cc8f31d6ed03cdcd95f6ca59b58ed6d695b6 (commit)
via 34eaa7c5e831bb170f0fe8792e40df88f66ed8b9 (commit)
via 0eefd8be74290fd9e72e3d25356c64b24c844816 (commit)
via 2be9594e5c6cac0e01a75a06d8df31d8cef98380 (commit)
via 17f4eab177a8d459024a5fcf2b43e6826c129891 (commit)
via de2d7124bd8a6bf3f2c0a6e661dc5b6b178765cc (commit)
via 5b825a61a8f083aa879da6aedc6717d42534d5b4 (commit)
via 1af89c43e91b613bf3d8ece42ec5d93c19a5b225 (commit)
via 933b35dcb9cec5113853c93994d69cac44a97f4b (commit)
via e1b0deba4ea46e097e15f40b9825f7abffa00596 (commit)
via 71bcc9a5c413276bd90276c24ee28d59478a138e (commit)
via 6ac000df85625f5921e8895a1aafff5e4be3ba9c (commit)
via 7fef6a88c5ab2cedc5f2fa0003ae1beb7a380b9c (commit)
via b0c4e1599b68046bbd72455ce90433285b226ffe (commit)
via 56f0f77fe500b6cb41cda2a950b4349f0778561e (commit)
via e49c4b5a038981cea80bf81613efa2c538c8b3e0 (commit)
via 5222b51a047d8f2352bc9f92fd022baf1681ed81 (commit)
via 5ecd1322b677fa9808125986a65b28d619c79120 (commit)
via 78828990b58d0b791f94ed2441bf1267f81d58d3 (commit)
via ccd3fea75828ec4e0e2b20d8bba1d7b103164141 (commit)
via a663b567f5a57c19c033de049219e4a5eb6d936b (commit)
via 4cf5938f86b3c1a54beac417f6723505183ddf11 (commit)
via 977a6a742c821ed49fc7d977f917b47f23563bdb (commit)
via 137a6934a14cf0c5b5c065e910b8b364beb0973f (commit)
via b23ef1cf58b0754e23e550362e7a979d5f8624ef (commit)
via bdc63200a7e2adcde2b0ce44aaeb8477fb059d17 (commit)
via 2495b96e3a3adeaaca091d7bf2533c1a58971ee5 (commit)
via eb6441aca464980d00e3ff827cbf4195c5a7afc5 (commit)
via 5e2e3fa2759475313d69d3f9cf1cd339f3db8bc7 (commit)
via 5ff855e14dca375c24404eebc4573b77dba915d3 (commit)
via 117fa08fc7f8b7462190b75f99d7604c49f3b7c6 (commit)
via 058f40e75eb59aea1d3e18ffcdfdacc8386aafa3 (commit)
via 0b42ead35810e82a97561d71309283950ca9d5a3 (commit)
via ed6b07b60ae503e97e284eff351386ba24377981 (commit)
via 19197b5bc9f2955bd6a8ca48a2d04472ed696e81 (commit)
via fa64874fe02ff6ab8a782e0ede0d70ff2c95783e (commit)
via 44bf7654cb3ce85fe63fa47c7b39f48bb041d4de (commit)
via 1c8557996bdd428c9a3bf0935c30bf42fb167f35 (commit)
via dfbeea4eb3f414dcb66d2447e59216c23dade515 (commit)
via 54e61304131965c4a1d88c9151f8697dcbb3ce12 (commit)
via b9d341ecd382b44d72888899e3559d7d41baa203 (commit)
via 242cb68113bd4d2c0a3ea3b61cc56e143a42b38e (commit)
via 3ffa099e0969468d0f16f00c503b8936b895a2d4 (commit)
via e0d5584bdb3ca05b078693787c35d8f8f9228040 (commit)
via 4596c143eadfb9d67c5395bb6fa367470976d0cb (commit)
via f3bd834c8e9334004994c33d0798ed6b61a7a99b (commit)
via 860a2965d04f80fd0e90d3c5100332a8bb2ae9d8 (commit)
via c4f4d32eed0f4c26e4968336179a1e749e925aa7 (commit)
via b2d210a1ed486dc8c1083deb6f1b0bf2802e8d57 (commit)
via 3407184a20c367a0de02b41befb4db16971c9589 (commit)
via 6c1f3af003f02eee8952da8463d05732e667c986 (commit)
via fa83bb99518bd8c4dede832769fe4f7a6405ea62 (commit)
via 0ca10e6357d7a41bc308f90183848ce41582adf8 (commit)
via 25a5f4ec755bc09b54410fcdff22691283147f32 (commit)
via 2275e0d7afa15b69bd7511616a6ebae0b9de0d22 (commit)
via 301da7d26d41e64d87c0cf72727f3347aa61fb40 (commit)
via 26582526e20e56ea4547a447cfa93e122af0f0e1 (commit)
via 6934390c92855d4006b727e1c8c1c89688afeb5f (commit)
via 333edc6244dafdab42258b84a46237c6dcf936a0 (commit)
via 8af2ccd45c7f070d2aa168763815fe8a82808e79 (commit)
via 19f9f10fa5594bfe311688823fba5dd149e76a59 (commit)
via 5851a6cc19356b49fcc2500f5d40e7861874561e (commit)
via 8be2bf19228473f5d54f5015e893b44791a5c0c2 (commit)
via edfcbcaccbf4b8098f5cfc1bd1943fe4542b3306 (commit)
via 7396247046ccc046d711265100aa8d46be057626 (commit)
via 9c3f27137967b5f92c430baab6ab442258d9d692 (commit)
via 56df4f9ab31b300b83c26fbeb1a7cf7951f8338e (commit)
via 2298392e3ce9b5a470f4c0e3b2a22ba571f9b8ab (commit)
via 309d02a51ee22ff4c0226784ecd7bb4394d19542 (commit)
via ddd7b7c3dfdebae68e3742e175e4931f0a5f6c5e (commit)
via a2609d0762b9dfdf2750a1bc44ea33525c634f5b (commit)
via de130e6d9b6dec3586126f660123a905cc9a284a (commit)
via 53b5297513ee2320556dead019f268e79b49f77a (commit)
via eeacd1d5adeb27ee536fb04296cc20ffa898cdab (commit)
via 33cf7e5a182bc98cd168928fa8b5c775581fe90a (commit)
via a9211d7973042503353e61f0e16ea092c0f621bf (commit)
via 48211526895ce56de771e65a7e6d23892c639660 (commit)
via 87829d23b5d698611c94ee92f758558d8ad95468 (commit)
via ff55e63140852f4c7cf8a0404dde36442c45ff4a (commit)
via b723719cdad0da4db9b2099596c09f407dbdb135 (commit)
via 996e2593c867478eec8f2b3700aa52776528394b (commit)
via 5cde27560e4b8e3cd098daae572ca4d0124358de (commit)
via 1f682b8172e368d7609aa2f066b778989a7fdf7e (commit)
via 87308eeddf767dea581b817d1d2ab2aaa4a99dd3 (commit)
via 1fd5547c170550880268ba0eb83a374231be348b (commit)
via f06ce638877acf6f8e1994962bf2dbfbab029edf (commit)
via c5ce94c579d2d022cc7a39f826599c12c193dc20 (commit)
via 6b5705bb7f6fac495f8b3e050ce9089997416ccb (commit)
via e9cd09d4a41cfb46af3a89e57f7d3184c602dc06 (commit)
via 8c136625d1e2556c7c8280917a1f18370794ce76 (commit)
via af0eb33e4d57c842f692b653d783e028250264dc (commit)
via 6ed6d55c1e9908ce49d4ba2584c9d647c23908ba (commit)
via ca06de9c9e5a017361041a7ce0db4bd37c27e0a7 (commit)
via 0e52d28e09894ae1cab993e9cf61a49d00f9be64 (commit)
via f9efa6910cdf152f850a76c039f597f516f0c2ce (commit)
via c0cec0792078fbdd5a6cc5cb19c4361a960d95cf (commit)
via 6ba7a9cf0d9bcaa52ab17652ef1e6e3ad78094a0 (commit)
via 8053d85447560fa3c64f276881496513c66f4ba0 (commit)
via e1db14abce5e64985340e4ebb9eb4e7bee56763e (commit)
via 6483bb374e7ac88e3b736f08bf11e292605aaeef (commit)
via a3d81c1479c88a8ba26941e351fce1b0b1e3a2ca (commit)
via 895e8df77e0d3f8957f3b518052ff874f67bb8b8 (commit)
via 5d246e92aef87505e9392274cacbabbd20478d3e (commit)
via cb13c22e8b2464d903d2254dec4d46c1103cf5ea (commit)
via 68c33a7725a2e41dab53b4d74e2aedec79090380 (commit)
via ae0b20c57c48c4932b9aff2146f5d76f9eff5a90 (commit)
via 0047c74393959975ffd9f75a687a60f1f1c42a9c (commit)
via 1d60afb59e9606f312caef352ecb2fe488c4e751 (commit)
via 9c22af762d0cb6cdcb0bcbcea2b302b1165a0f66 (commit)
via f9152fae80f751adc95c894b872204e98504dcb8 (commit)
via 40f87a0aa5e6b2e8f86346f181b5e1d785c36e67 (commit)
via 3d69f66a688c09a20083a52f2a64b9327ede70c6 (commit)
via 76843dc3d8538f932d58e55ab6189091cd709f48 (commit)
via 451fdd5e7209a31420d2a61df99d66d2abfe34ca (commit)
via 34066d5d8486b9d3dad08e0b13269db14a8d8aae (commit)
via 543f406610c4fc2c0236f8f9c7fdc37014937fb6 (commit)
via 42d9589eb5c3fb6dd9f1a73206510d01bb29bedc (commit)
via bb8d9d376a1e2caadfdc3631c221cb7ca118f60f (commit)
via 860be372d776c05c9535790257812dbde1b9f74f (commit)
via fa05a534849c2e3ba68d6fdc4c5ddc6531948fc3 (commit)
via b73ea473019bceeab7aab78ddc5d1c4a7710a8bd (commit)
via 70d71681b83dd3e0cb125e3f3e176d04e7461aae (commit)
via 21d48af75c1e756de9acea4c45dc35634c0475ac (commit)
via e55ec73b0bdcc2b7d6286c6a18886de194280947 (commit)
via 9ae710aa95a75d63e2a37ca7d79297f8ef308e6c (commit)
via cac094e7e14a483bbf84394adf55a86d70097188 (commit)
via 2f0de2e7a8d594fd40c4fb2449232bb5cd64efa9 (commit)
via dadeedb633df1f3793c32fa283c82f22fcdb7ff4 (commit)
via 5017cbe7d18c752b80e8e8289b9ab0315f5915d0 (commit)
via 0a9ce967a515894bd7c46cf86a6ff4bc3d856b3a (commit)
via 2e967b7775e023f77f082f49457342fff2c7be33 (commit)
via db2594e846ccf340ed9820f049b9839231a4832b (commit)
via b973f67520682b63ef38b1451d309be9f4f4b218 (commit)
via 0de367c06c8e4c730729de446cc5c54b7cee9ea4 (commit)
via a4a2b5aba9b2b16c4aa2cc8af01c19a4b9b61aea (commit)
via be8886364d3e7466a0a5007a75df797b6839004c (commit)
via 25747d9b65fccbe5e37b4c49377382381edf1e78 (commit)
via 77027490d53b644e157fce2df59c5dbd496d1e1a (commit)
via be701769ab3ba884c550d22455ba09af3b725a07 (commit)
via f4b20fc54b74d03279245942900806a5e7b57d39 (commit)
via 9186edcad7735f16c835bd845572e74f8069f2d3 (commit)
via 38f2d6e49c7f693c55e5d27b3b247a167895826c (commit)
via 120a7ea6efb5ba35008ed9b3502846f4b8fb2ed8 (commit)
via 682436e844799b2194eccc2ffdfb3fa3da527cea (commit)
via 8c7144c6aef7eb26215f045ee95551ed7a6fe307 (commit)
via a2dbc20364f13ddd393e51e711db7e5e3bd2551f (commit)
via 529119357642023c02dc40fd3c07bd2797062ba9 (commit)
via 28c720f2b0319ee8b2ee21cea1105e411a31360c (commit)
via d82bd9a601e95a301e268c21a8ddcfea560d38dc (commit)
via 3cb71c8c163525c612460eff4292adb997f8a797 (commit)
via 86355ec5ded2f1988dc466f8844360c78e8454a7 (commit)
via 2a0f21d3415558e8be812e74e554e11c6cbd6270 (commit)
via b13c2fd090114e2c0cfe164bae10cde145f509b4 (commit)
via 09191932190ac8a64a4b77def3877fc5801d8aeb (commit)
via f79cea1f5a7ce45498a7a94cb5ed9aae6dfe1a7f (commit)
via 9e7943a5c72c19247d6ae7e7c264ef37e11d3561 (commit)
via 96c47a07bb44b6667816e576d8907fc223d1d771 (commit)
via 6458b98ea487620fd4c47b0de5b5bc2e5fe97794 (commit)
via 910df2cc9298c1c7697f6f38c303b26169e62305 (commit)
via 2bd3a99bb2580fa3c783058f490eb1c8130c65ab (commit)
via 19603be4d345c061cb2345187cfed58a785bf03a (commit)
via ba727c17d3517232a3c40fa3a30c6924a30ed7dc (commit)
via cc7a7fb930a5300aae369665ddc882c8bbf73cb7 (commit)
via d3b169adf97f9c5a4990400e54562d644016fdaf (commit)
via 91193e42d664fbb494a15cdf5b01a0c5da19d0b7 (commit)
via ab86095a11e912123c40f6b41879dfa634e491a8 (commit)
via d1a73e27eb7d3c133e871809efeea5174b01a2ee (commit)
via 1022129fcea6743037ed7ae2b363b0af082afd30 (commit)
via 7de2a6f3f96d466ed24f7d8b24708a30ee679729 (commit)
via da0e644a1c4826da8587bee6fe1902a6f28e5931 (commit)
via 8f6657a13b4e6e140e3c5a52201fd4c998ee9560 (commit)
via 2e46317ec615067947983aa9ebcce371a6a8fd2f (commit)
via e5e62873bcc14a7aba87bf0bdc7d2d354aba331b (commit)
via a42df8e3040bc9cfa52aca8d45d09ef016810862 (commit)
via 5ac67ede03892a5eacf42ce3ace1e4e376164c9f (commit)
via a9e9a2b26fbe93fda5174e4482c9c13e05287539 (commit)
via f54d8505d8ac997fca63d2cd82485b19c248a804 (commit)
via 950fc0ab5f04dc55c41ad93179cc188f8695f335 (commit)
via b229aa1c060b0d3a21b3173b97c835ff10dc3b17 (commit)
via ba66913e80236f8b33afd055317fc0356580970e (commit)
via 4bdd50ce23081a45afbcb653a6b75a1b408c4e0b (commit)
via a232b085dfef35b196013423e25927cd6e230276 (commit)
via 4fa3cf5c5f623d24c357aac1d689a068528c73ce (commit)
via d3f8f8fce7957a2227dbc38d2faf58ca33f85fd1 (commit)
via 923a47079606a6ba9368d94007b3c24aaa4ca7e5 (commit)
via 6e0b7398570e219d417f991681f6d6428fb12a67 (commit)
via e8460092b6addf9ce69dddb49d80d91037178ba7 (commit)
via ddd04eed61f4945303400569629d66cf685833d2 (commit)
via add0f697aec63c9af1df661951bda2ae007c98bf (commit)
via 1a5be7480941be67947095ab12297d157f8fb572 (commit)
via 71d27e416b025bc77edd70f9e885fd804d043efd (commit)
via 89ad5194ebbbc808c7e5f317da47550e99efa9ea (commit)
via 9a75a9c73b0bb361f1952e92eb4b5a62694be4d6 (commit)
via 6fb5dd6bf0e9fcb0d00eb3f659b5b9c45340bb37 (commit)
via c3d81863bc201dc1fab9690565158b8f09d61061 (commit)
via 199599a979e331026ec5dc8ff07f1bb08f3228e9 (commit)
via d7ec12be1f1e87ca0cc4675165f126f8d010b6b3 (commit)
via 13204f9c20a03dcc041cd22fff22fb28af569eaf (commit)
via 7c9c7811d3d8642ae675073e0cf9e8b83df49c4c (commit)
via b7d8ac6afdd51bb18fcb0c1c0f7389d4975f7c54 (commit)
via 586df2b37d5f26ac1998d12c86f280b11bf077b7 (commit)
via 0ac0b4602fa30852b0d86cc3c0b4730deb1a58fe (commit)
via 40f74edaaf73a8a5a7798fd79646e2279b82b5cc (commit)
via 7c419681601df9c3a453f0e46756dd751344b1a8 (commit)
via 24ef7a16425e696fbb794605a89ff8d7bdd7172c (commit)
via 59455cd6b9de13d63c2b6cf17eb7e8c88c8a99cc (commit)
via 8a47d8d2b9123df707aebfa14141a5c11c5a6228 (commit)
via 6bdfcf31fed84b413714a0ef446578ebbb3002a9 (commit)
via f0dd8824dc9bb06420b960f2842902ca2d63fba3 (commit)
via 54f4650b7de4d275b29bc1d70b2cba98d59d305a (commit)
via af01e66fde8c0055319c47d775b0af1408a3e18a (commit)
via 33deff727f64139d15f45173679fd0e73531e7da (commit)
via 354ca192db138bdd942734ff985b6e21c8445a82 (commit)
via bb708b6586815ae9ac14d99b0d4bfd714f315273 (commit)
via d3120390ae945b88432c48aa82daf90ef67f6715 (commit)
via 27025d831a4d06672458004ec70b5c2cef73904d (commit)
via 34be7c60c8cd01a3693cdc35346ea8bed77a4c88 (commit)
via 008a3bf75b8aef0b813c5109fe8749f32ca6c7a9 (commit)
via cce105f44bf716ed738a1465e5412468d796aa13 (commit)
via 57b77aa7c09f568a9adf7f9ad475cc80f278fedc (commit)
via e5961a4ce06bb430469c457856f5392ca147d857 (commit)
via 14a51327ed408ed5a957147720f7ad3e3db6a95d (commit)
via 8f331c78a07959b413f4a00e3b3f7a935cd42b2f (commit)
via 60f301c235b9d83b7fde6f06abac18d0a1168260 (commit)
via 4a8925ec51c064a456795ba17042bdf30ff2b8ab (commit)
via d4af3712f3987069dffe6ed30919ce0b7bf84699 (commit)
via 0227b4b17e2bdbbf5472c145ce14f1fb08aa7791 (commit)
via 795a77a4651542f80ba8906d3f02b4987d414f79 (commit)
via 41a2bfb4045de0547186a8dca60c30314a3ae28c (commit)
via 8b41f77f0099ddc7ca7d34d39ad8c39bb1a8363c (commit)
via ea0d42e325de86353e17b29c26257333f0fe016e (commit)
via 34d182ec09a5674e87470772cce024d49043cbd9 (commit)
via f9e5f363f3abf3c08d65ff14421491b44ccbe8af (commit)
via e4ee8c985e9adf5f734f0f693b79fe459ef848db (commit)
via 9739cbce2eaffc7e80640db58a8513295cf684de (commit)
via 6df94e2db856c1adc020f658cc77da5edc967555 (commit)
via c6b5e0f57cb473f7e2731d8ca1d3619139263a12 (commit)
via 239114f5e894111f47dca785429a9180f2cb1598 (commit)
via 477b53cb86a431a7c38655db3972a554b412e700 (commit)
via 8ffbcda5bbf9729b9ab09d89531520c9f94618ee (commit)
via 77a02471ef75d226a6dbf7e962491fedfef6d6d9 (commit)
via 09b70f9b0f93da5c7034b02837987fc03a675472 (commit)
via de89048d524aef36e88ce770f3f953401e3a24a8 (commit)
via 94b5025336f509832addd712ed2f9f4fce48bfa4 (commit)
via 8578607b1c73c76e16a4eabd4ed45539260e0c74 (commit)
via 0503f4a60a6873d63bb1d9352c6b50a7a40130f9 (commit)
via 71c4e3cb8c0f6c4e51a8afd24928ee10710a49a2 (commit)
via bba7327c68c2242882dcaadbcae4b2db0aabedd3 (commit)
via 783c0a87b919e9d39ab271de6203bb4277758e2d (commit)
via f9be171869937e52f960568773c45cfda28baa3a (commit)
via 955a15527bec6ba0231f0bc377539a169fa34165 (commit)
via da6a33e379d7c8ad56087b9302d7dc5e6a6ed08d (commit)
via 094d2aa6c73201892be2362d6d43fdeb34a2650f (commit)
via d0181f27a73dc4b77bc9c6756cb6583226dafacc (commit)
via cf131c0c98c346b2f075b76c2fc92a658eb58f1f (commit)
via dee286dad1ba1b47d89659572fc2ce8fd1a94af6 (commit)
via d22c96099aacc094be00c1fe51433d4b39b9656f (commit)
via fcb8add55e0b6d5be9104e59e4f47a6d4d4ae9c5 (commit)
via ecbfb7cf929d62a018dd4cdc7a841add3d5a35ae (commit)
via 6689f461849841d7c5a724471107f758535c213e (commit)
via 004e382616150f8a2362e94d3458b59bb2710182 (commit)
via 6dad3efd9d023633d973d20b9102f2f8b10f3d17 (commit)
via 1355eed8148ae3199e5c047c004bc8ad839ad5f8 (commit)
via 5dc6a761125d48557a13a6354a6c373607fb2714 (commit)
via 76a5b79fe8b601f5c9aeafd68394628ac9552f16 (commit)
via 64dad23b34538632b6b2724c5d6c17b295baf9f5 (commit)
via 3ebbda21d35170b26d7db65686583617c8f0cc26 (commit)
via e7a46851671cd942d39e74d0456435401dc15881 (commit)
via ba870bd64170a8533f1bbabcd6df36edf31674c5 (commit)
via 1b7e11468e670385d56d05b97afac96c4da05b9a (commit)
via f3e39622ac840f6322cbc76277b2104ba4e8aee4 (commit)
via f9ed88b3ab1f080f2e96219a70c7f6ba9026d547 (commit)
via 04934bb05de6689d170ff4d52c2518301df0f960 (commit)
via 09ece8cdd41c0f025e8b897b4883885d88d4ba5d (commit)
via 296d6b7b9b3805b5bc71ece7e3f947604c641ffb (commit)
via 14b12e16f91cca0cd7c9053727832111538ecc85 (commit)
via 0e2743485d50f8987b9d1fe959b215e49039a965 (commit)
via e62c547688f1e4820fa8ba200149ea16960354b9 (commit)
via 319debfb957641f311102739a15059f8453c54ce (commit)
via 80a4c6e866a3f40462b26260c6de1ef05a334e53 (commit)
via 67ff28d5017ac0cb38b591de079809bfe765ddd1 (commit)
via 7e90e3729b54ca18425c3b5a5223230aa335efff (commit)
via bd8392ffd8873367f243121cf1e33eb7981b4f4d (commit)
via f8f81b7f1512760cb72592a79d2f49999f11be2c (commit)
via d6f67da406651c7cc013ce28c513c23ee43e2efc (commit)
via e6e3ba735b384ebe4ee1ccacb39af2100bdd5f19 (commit)
via 301cadd12c9f4aec34835ffde2abdc808ab28936 (commit)
via fa007383d4b4d71c5a4176c4bc6e11154d662d8d (commit)
via 3a9752bd2611ce206fe0526257876ba99a640e6b (commit)
via b6b79e9a9f444e2ab6b1a53fc1597c77cbcefd61 (commit)
via d58cbd26658afd381cb715d0ca976d11006a0445 (commit)
via a37dace36f7322764bb01cc790c3f36d524c0456 (commit)
via 9136696612968df28a925c910b54217ffe84d206 (commit)
via 236adec49e83cc6f5ce85c07766fe5f553e00812 (commit)
via c9a06623e3e6041342046d52f502ce5e478ef29c (commit)
via 85b6fa72d68d019149b8c751d495e34bbd4246a8 (commit)
via a2a0bf66d71c5b5adba4a1e0dca48496bfee0ce1 (commit)
via 004816211d6b0f438713c1a4e167be8ac25a356f (commit)
via e5fb3bc1ed5f3c0aec6eb40a16c63f3d0fc6a7b2 (commit)
via c78659bd80e45ddb44b3cafb4937aa7b4e5f1b14 (commit)
via 05f49a93714de9a09ad1c0a9d6e2a7365e890232 (commit)
via 3c06d0d6c0d002138ab3edbd9a6833a40de3f911 (commit)
via 64c233aa9b565df653928f79e987ff0ffacf68af (commit)
via f8e2229b901ea731ac1fd7e7bac0799b855cd88a (commit)
via ed89ba2b9f20a5aad5f213191f9b34981aa6a0b9 (commit)
via 8ad3c81393273b291cbe8e97f9ab7c260d8ca94f (commit)
via 5cfa9db0c1eea204a2f6161c4929da4d896afb41 (commit)
via cab140484a93d9fabc4c3a108ce4770e22b08dc4 (commit)
via 1c3d1954d25eed486f1fa97d4ae5493693b04945 (commit)
via 0fac71e31d16f1cfe127d8d0341842aad762a642 (commit)
via 6f031a09a248e7684723c000f3e8cc981dcdb349 (commit)
via b3d03749ba27521a93188402c18600360e44c497 (commit)
via 500a785136096ec3a7c5259ec348a80256aed494 (commit)
via 9c690982f24fef19c747a72f43c4298333a58f48 (commit)
via ce7bc899e76309c63b1ad270b9474fdc1407cf5e (commit)
via 96c902f2ffb43d75fbd138a6da15dbbc0d80a71f (commit)
via aa32fd56bf7b4eb3f1ee13b502f2724af8f7ac4d (commit)
via d0a29d0aa3931ad8091c24665ceeee95b94d58eb (commit)
via 6750a2bb8306b0c335f12f6d1bd97c1b0559bdd7 (commit)
via dab32ddc42b4707968fbc056261c50d4fa08e408 (commit)
via 73b2eaec32ef4dbfa93bb5009229e9ca261c3676 (commit)
via 72c4f5046949567927410bdb2ccb7ecb663d16dd (commit)
via 8f777e093c960f76ff489f12684005eb48580323 (commit)
via cb2cf2013b616b055904e301b3d5ff4c49475525 (commit)
via cbc9f118a77f69878f4cebf383db39d984201bae (commit)
via 689503220317cf2e86dae704a8e660ed646af67b (commit)
via 130ba31d0829fa47a5e245490a9ae172d1c91b7c (commit)
via ae9a05a4c5d73b3d32303b622c6b7d0c04d061d3 (commit)
via 57736fd520bda626de430e55d9f7f0b1c13e6353 (commit)
via 9b9f126d3e4adc4ef7e6b61945245499745996df (commit)
via b6bce27baf454101b3755d46877ac84c5eef20f2 (commit)
via 8661db658ebec93ba0e2e890ea7db2b9fa20df4d (commit)
via 3d61dceb1a9093ab110920fb436c3b2061f15857 (commit)
via 967fe3d95663240940e2c5e653fc3ecac7739037 (commit)
via 6bbb42b7fa668d7a8fcc3cf3809365179705a3a2 (commit)
via 1e3a2586abe597550611cca5697916f311ffd2a9 (commit)
via cad6c0a62b3c5cace703d9863d64dbdb1e047046 (commit)
via 949f642c90ab8ad7f0f02c35e383b9eb9e65792a (commit)
via f59af8201a12342a2779440a4c7afed1b51fef7f (commit)
via d7a9547198d61bc455d41ba45f6f14874f05c1d5 (commit)
via 9cf88a2d14abccc7b3ad6ffd8d299dfa464bee1c (commit)
via 17832da870f12a8bfb6ec4e8f91fda964c1afcbf (commit)
via cf0dac64cd9e7f132ca58f0df3609f00d2cc4a07 (commit)
via a9944090a851bd3be8f459c7185058394d31fd55 (commit)
via 4864018bdb5a6f3f9ea9d7a9cc608521655233dd (commit)
via 310d15bf23ac533d70a20c0a881fc285cb7af3f7 (commit)
via c2a34db25f4b0c903a025514cdff758e7ea55e42 (commit)
via ad418dc7853679f1d79c280af5993b82c43dc51a (commit)
via 98908690535371b9128a1263c7a4b59150609526 (commit)
via a01b61f9915856af9c915beca5061867804e5a86 (commit)
via 5ba0bcb7d12da8e28ca791fef5bdb82ee2619fa8 (commit)
via 866ae82be92451173b08ff1135c2ffe9ce820271 (commit)
via 634dd02746cd7d5cc6c702b9a3ea690825282c22 (commit)
via 24afa6a4a2bc6d4fb4151a866cb12ff5728db277 (commit)
via 00b804d7100f12f5ea261b4dccb01f1de4cd94cc (commit)
via 75cd27c3b2d1bde61ffef18bb53198cc459910ed (commit)
via b099d028354adfd16fe3dd838f1f36f024c79f40 (commit)
via ccdb524e2fa0b9d2ce5570b73023a3f127756c76 (commit)
via dd59f2ff605abcdb0a1e8b284c219b8d788c0585 (commit)
via 42cd95d56e1f9c71eeeb107284c4762cf1a9a726 (commit)
via 7ddfd9eca150efa2fed15114034e5297db765a53 (commit)
via 1888b383084177cbfab6c408e15565a3bec17ee9 (commit)
via 73837f513323e69175abfcfd04ca2f7b1fe22a4c (commit)
via 4f992e158c48eba97d4cfd497a1abba12336e38d (commit)
via 496a0353cded1f60342e7713c28fecdffcecbbbe (commit)
via 6c11d9c51b88e2a6d238eac951d1d0317978f636 (commit)
via cdd18357620129b8533e9619b30b37ecbc91ede0 (commit)
via 8aa3b2246ae095bbe7f855fd11656ae3bdb98986 (commit)
via adba8b7c9e699cf557646e942012567f8e5156c9 (commit)
via 541478374f183c2742a7f07e6558836a4dd09570 (commit)
via 17f237478961005707d649a661cc72a4a0d612d4 (commit)
via 12c23a667c1e9a4b92d1cd8fc07136faf4c41a8a (commit)
via dd61821a6c910d90c4a46024e303965048dde0aa (commit)
via 0e6a18e12f61cc482e07078776234f32605312e5 (commit)
via 756e56a8c683906b1b50bba5675c996fb1295820 (commit)
via f0324078a088345812c5397317b66445b02f82be (commit)
via e01aeb058be1d8bd7715ab603c097230cd18df5a (commit)
via 841627853b192480a8e663870ad3a1cb39bb724e (commit)
via bbb0031f3a987b3314e1b3e8db1c7ca155f9f480 (commit)
via 0cc21b601f0ef1e907deaf3dbf02813ec2e0f493 (commit)
via 26aae7d612bf0f01b22a7c4784fffb909d148970 (commit)
via afc4be09c37272fdadecbe8496b2579ef4cf3e42 (commit)
via 5266058997a5746c04db5a721ac60630bbbc2abd (commit)
via a30321812264615d0f64271ce6ba5e841f7977a5 (commit)
via 93b246f4689918c081d9ed889cea9140b75adc1f (commit)
via 5e357877317a7a70e9a66643a4f846c32f5dc2ae (commit)
via db9ebc20e4f4b401307d6237796e0252bfaa1c59 (commit)
via b9e1f48df9f398d7641f61317a7d969690af1ff2 (commit)
via d8989e0a05ae7a453a6768f2ff4019d41ad84aab (commit)
via a9ac4ee37388d93c0adceffa424599b5affc5c3a (commit)
via 297c145c01d18f73e2b453ef968426352815dda6 (commit)
via 1708a52ea3956e0e7027991f6cfeb6a1b9415b94 (commit)
via 47e53343554aa6b4323ab05799a6315fb1e42538 (commit)
via dfb68c22293f3e84aae8e81865525dd043f26e0d (commit)
via 72267db6056eca0fbc9ea9b656ffb42475e7480b (commit)
via a151c4a62009d73ab3f05607ccd12889de4b9718 (commit)
via 0d1efe0f87c1868ff5389c731dbfa7ead9416655 (commit)
via 0ae5d2d73aad78ee24e641870cec1ab06f65e3f6 (commit)
via 569afbc3c08776e04f1b755dce2ff5aa5c385647 (commit)
via 38778d44793426906ef7d0d25a70be4a36188765 (commit)
via 411b0f674614000d1f4606e0c225d078c1981628 (commit)
via 696cec5b0e99ebf65c3ea634abbc6114f1b92d8c (commit)
via 2c54e9c5f490b876eb59200cfcf9cc4623250bda (commit)
via e935dae553418e90ef4ac06d598da285e8b20bea (commit)
via 355e2efda4baba2827a9d95a648974ab3f5bcfa3 (commit)
via 9cdddf2713e4e4fe103cb3cdd6e9af44e827c5d8 (commit)
via 2cb4775feeb0be8174565e175c4f800899879c19 (commit)
via d74693d2cb99c839b5efdd7c1bc0188fe39bc86f (commit)
via e26d533eb62f553fcbae616d82e84e3784f750c6 (commit)
via c9eb48c9a8c49d99a31ce752b11ac507f3346356 (commit)
via 8697c90c350f8ec3c3c3677de0761230fedc8ef4 (commit)
via d619eab67a0c2255f249dfabd7c9db0e5d14b833 (commit)
via 6fbb0f720f6e6a5d58c0dfc6710f0e26e79816c4 (commit)
via 9cf25c82920546461f63a87359a9ef1aa00bfc78 (commit)
via 76e1d3b208772073e664437788865a5e3d067a68 (commit)
via d9fda268d2fcecbb69f06f3480bc9dcd058c582e (commit)
via e8cbf87a4e734023f9a79d131dbe1fde067fe0c3 (commit)
via 17a76d75e8ee9e88417058b220b01761c1fd370a (commit)
via af0e5cd93bebb27cb5c4457f7759d12c8bf953a6 (commit)
via 17f874bb877356f0eb3c777282e1a21172a241c9 (commit)
via 2d2093b3eda1d0d09ae852f2dc6feefc4886b71f (commit)
via 49e2c1ae796e46fca4f5c395a92387638bb828c6 (commit)
via a02e4f3441285f10958b72e2337aa1b6e290fb56 (commit)
via 16bbee505681b46ef60febb8067765704afdeb24 (commit)
via 53f4afe16013ef6f97556d3b49d1bc56443dc08c (commit)
via 180015eada7cdc67f8756a7425f9023d884b19df (commit)
via f9a11dcba2da26e3a27a4be00461aea07aa60d85 (commit)
via a43d8d0ef4cd44d5e0ab9fa9fc746e6d1ff353e0 (commit)
via 495087240c7b5736999e3edca697eb8509068ca3 (commit)
via f364f8feb190aa09fd0a65b35bdf05a3c7fe0932 (commit)
via 1b4b8193675f0ebc9b63480900257fa21e5cfa72 (commit)
via deedd6f7ff3339f822f9ed8de77a5a7164bda9a1 (commit)
via 214eb805737eed8035490dc8ada790bca5c14fb6 (commit)
via de81dbd26e42616b7c56718ffe1ffc7c6861b9e9 (commit)
via 406ff425ec228f5b99a61576d8a576fbc76b3122 (commit)
via ad91697c609b3ad79d8f2799a18ba63548a228a2 (commit)
via 2db67df75178a2bc407417540dde75e895eb619b (commit)
via 0da13ea71e97d62ee8cffeac9613a59f6dd07335 (commit)
via c95b25359f139a816f114378872bc08ab7e6aef2 (commit)
via 91a8eaa5b51c15b6d8f6cad3d75597678eeb03f1 (commit)
via 7f5705228eb95dba445d0d1d8297c6de0306360b (commit)
via 073d75e7ab64ff107198181cbc9ef4482e83b681 (commit)
via 4fd542a1c095efee2afb61c440784cd3984803cc (commit)
via 5400e06818b54c363c6006395ea51567caa651b3 (commit)
via 42e9021df5556117697f9228645a4ff3eee246ae (commit)
via 746980b33112b63147744796663389e5e1c3144c (commit)
via 47f69905aac05aa6f619cb143a81f206b396975a (commit)
via ca368d008315b135fa2ad3ff366aa5ae15eb4d12 (commit)
via c5dfa657c2431f3daea2dcc49741f96383f0d18d (commit)
via 1d018cd8a62fdf430f5322db023badc030cee6ee (commit)
via 7abd3c8866212a9c29920ee82be7390ab2499c43 (commit)
via 3c66bce7b2da45b00fa54358ef0728c0ab2e0a71 (commit)
via 0b1f8cb6dfa5481cb67e43ee8c2f24fac97ec95d (commit)
via 0c760d178eecf3162e848074e3fe631d690cc77a (commit)
via c393ec92641b6699822ebacd1c3f4c68496f3316 (commit)
via e7251d0c587ee8a794e590fdcdd4fa07f6a19c1c (commit)
via 26ab69e18bd0417086315c20192b8b93093bf6e3 (commit)
via a7cbba998123e58d89a9d0da2d77afa184ea357e (commit)
via a61a83e551549808ddc80fa99ad453932d001ff0 (commit)
via 13a3b9f237b1e29489804ccb5a25612cbea511f4 (commit)
via 0c25e92e3d59db2012909c9bb4c9663e220a9522 (commit)
via 3cddfb8bd95782eba2fa6c5935a9298500dcdd6a (commit)
via f059f96f5c390ffe7a3835ada1e4c6be4502985b (commit)
via b786dfa77ef76b5486367a40dfcc5f8b8681db52 (commit)
via 81c35ef7e5e01da333654443045de3dd2663482c (commit)
via 10b0c31c6762cfde1e466039d18e159a601f867f (commit)
via 691d3b625c7299fc9b7d58cb855615b9b11fe393 (commit)
via 8a2edd0d1909bfdb0f7ddbdfb953eac95c987f2f (commit)
via 3337b581fafb8c7a747badfabb84b2ad01007ae6 (commit)
via 7356673c98e3ca56e1ed4adecab4818c018ccf81 (commit)
via f16f9856058ea2dca3b0be5a964fc3926ee6f395 (commit)
via 3032690d227ee2fe8f660bdcd6c0782c6a635ef0 (commit)
via f167a263da41987a76b76e8b71acb9620a0c89c5 (commit)
via c334da7773375a87b5e7154daabf132f945f154a (commit)
via 7b51465130cede5f68994d4535b86ece6cc2f11d (commit)
via 36fe3ea99b743875476fa2b229a5f60502b745a4 (commit)
via c02f78e1a0757e0590e81f6486007deed34f2d4d (commit)
via fcf3cdd41a35c9c96e95272bc5d817b0457ab8dc (commit)
via bf5f7b5f56ed649f78206fa2c3b4f9acdee1e310 (commit)
via a463f3d1b7b87d98f8c84d9d93a9fbd0285c53b9 (commit)
via 5607fdf2add21db2a5e080fdc6b146dea58e15cc (commit)
via f3d4565f0bf0839c6b5d3cfd8ae8ee48dbc20732 (commit)
via 5cd467d3209b8665c21fad038e79c5063ca95798 (commit)
via 4d29fda48f7304d2c5bfbc246326850ca5970b0f (commit)
via 1ed67ffd6fc90c04a79056766b2658c01d30c548 (commit)
via 2ebf994dfcb704b90f9b1dcccbf56a4b38c84a36 (commit)
via 2ad85af5e37a9e393903a8c0b3589b9c086c16ec (commit)
via cd6beb6ccd33f33efbb6ac078a53fa9e0de7f3d0 (commit)
via d588ec8aafe64a586cc1bccab6be4a9fc50886d4 (commit)
via 0c303cb20840f80038f98f023ebd6903a452efdf (commit)
via f436c5c20d296c17f8afcea1e6ba6242a9d09929 (commit)
via dcfa4914665900dc475ac1dbe98336d8daab1c61 (commit)
via 389cf02a183ea2223d1c44f68fc2f764ca5d69ea (commit)
via b832c3c975bcae66717ce28e392a189887b6f08f (commit)
via f1885e6e7b4d2688be602b189eece51e4e649f42 (commit)
via 9bfbbabf77cb41babe425c9006c050edcd3cda7b (commit)
via 32ca390dad48d4d5c8bea98248bdfc62894f87ed (commit)
via 0afb9dde6cef8abfa38935e85030ab975002cf91 (commit)
via 3b18410e04cda0411a9e5699005a452d144793d7 (commit)
via 81d20bfeeab719136909382cc2d9bc1aba5f8070 (commit)
via b6d39e3da5f916c5c79ea49d7aebeca64a2c03b6 (commit)
via 0c97558ecf0c9c35d9ac86e214a0cbd1c10aaec7 (commit)
via 8cce1250c4b1a5303b515a75278103118cee83e0 (commit)
via 614ecf7cba243e94331171cc8d1761a99ebcab0f (commit)
via f7f035851da68ff926341eba4cad497ffa476115 (commit)
via 90980c5054e54aa637969ad59e2dc16b351d4488 (commit)
via 3715569dde8e4770696ba2552fb545d2cff81b87 (commit)
via c336287b3e5f3262b4f85ec45e7b38b893c0b2b8 (commit)
via bc1b548a3828f5cd722496dd8eb5c09fc1f95187 (commit)
via 725aa66bb4eea86f29f5f851b4c188b864881e81 (commit)
via f998d613872373c2fbf7088088d7464e79cc10c3 (commit)
via c5f4af2f749808f7e8b96dca09819f69bb094031 (commit)
via 92d766ce1407da1d1bed05e64c2b62b25af63303 (commit)
via 35d21690b35272d7046b54274ccbe87fd0279b30 (commit)
via 4aa5062b964d03a27adb9eef0d519aa26efa34a0 (commit)
via ab989937aef9282b9417a51ace071cdb199b290e (commit)
via bcac9ef1e7fb84fabbda7f41d360d4a437ae2102 (commit)
via 56baabf66b9a5224d0d2d545f65b82ab50ae781d (commit)
via dc5d961adfa0c6b05b911532535942c00eccda7d (commit)
via 1ab958cdc6f1ebdbf88f863a4a15d6a5783035bc (commit)
via 74e4f864d436d960d5feea540e5573cb21dbe39c (commit)
via bc3d1d70a2a074f3c3360ed75ea87a7fe81a4da6 (commit)
via 709abc271e802e1a27b6f74fe54f7f5da5a7161e (commit)
via 086b420bfba5b31f13e2828ca1be842a8faad4aa (commit)
via f33c6b79fa27d93bcf9be45aa6844e1c26f35a97 (commit)
via ef67acec41e9b83d4aacff8de12e6a3e37628227 (commit)
via 184c6c7068ec90f2a85a859bfa56d779a4294382 (commit)
via 7c7c776676860ba64571154faef440093799e996 (commit)
via e321d8b344152b558cb42856deb5e8798f153d1b (commit)
via 74d131f92fbcd1fe21335a743dde205063b63d00 (commit)
via 8ddc37ac3b6229f03a1fc122ea3c1c8906cbd75b (commit)
via cac61e91646c344359a5b767d2f2042d05cc1d74 (commit)
via fb4ca10d60279cc2d7da663b912dcb189849d0da (commit)
via 59dd3fb61b265455b544407fcd8976a04f152af3 (commit)
via 65ef7a150b4562f2e80222afd7fb010cbd55ec43 (commit)
via 026a4104701791649673a74591007e1d50167cbe (commit)
via 8a18de79026a2620f82662d308c4f87e9a54faa3 (commit)
via c1dd1aeae9aa2cb06b372d2002fa15d57aa7c552 (commit)
via a8746fb17e10db78330cf69b2850515020f5cd56 (commit)
via 42dbc3cabef1db3f4c9ce99bc0830ce34794a704 (commit)
via e7c61f1bb5cf375a1f005bb7d77080d2fcd8fb9e (commit)
via ac8359174eaeb1df18bddcb0a57807d49ff8ed53 (commit)
via c94995ad3292958582d1ad491fdfbaa76e43cad8 (commit)
via fca9cd2ad55dfdff80ed3106c4cb40bf4d508d26 (commit)
via db105a36beb8999996f9e4853a2cceadb074775f (commit)
via 42beef26ba126bab9740af0a809617fc3c1906c6 (commit)
via 3f791c7c437212e55981f044ef722a759f58a184 (commit)
via e64c9d4107c1d012b68239ecf4d828467f7eb6f7 (commit)
via ba8595ee4dbcc87deb5aca78359dd7a42e4a6f45 (commit)
via 0162b6441a567a0a687a726f651e23bb754bfd71 (commit)
via 74266da0141650d6ca337a66f266ba675ad2d357 (commit)
via bd58459c94e2074aecb39b02fa47a2b79e48cfb6 (commit)
via 238f276c9b5887fcf72e86011e610466ce163f5b (commit)
via 87c347e04505f141d624eb7be497181a37e346d0 (commit)
via 3a899ef4c5c11d4bdfd9f50ac4aaa1b276060a65 (commit)
via a99da0d489fbecc59b6d75a85e71e92ec7d173e3 (commit)
via a0e0f22323abff0332ac4dff60300874a3e2f1a0 (commit)
via 4205ee154f209d4493e2b7176b53c79d5dbaf193 (commit)
via 6e5884cfb1610b47339be5f2578e8e77840d3095 (commit)
via 4e8c15eae9ffb0d159f5d9f138455aa0213163fe (commit)
via 83d5d9a0bc2599bd9a643c2c6294f382638486b6 (commit)
via e75ba8ce2f5b2f9814741d0ea633f3542b458a74 (commit)
via f62f4f06cf94b4d979e5b036a41c06b4f53e5759 (commit)
via 39158d307dd4b4a74f89bb755d22d1edd3a04d80 (commit)
via 777f07c955102aaa875ed2567d788b4103f849e1 (commit)
via 2aff13f019888ae254733f2c16d9a8b995a48318 (commit)
via 0050a51bfb904132db32a2d4729064d8a35a5332 (commit)
via 77f677784ed005ff2cf6e5387d837897e3e3bdc4 (commit)
via 886b10843e5e8fe2fd7b04684a590affe1b812ab (commit)
via 0061de28095c4ef166de784c12366adabc8a6636 (commit)
via f64a1962849e0773c33f95b541daf18a95911265 (commit)
via 07e20270989ec37e95feb3a01fefeb5b80465bef (commit)
via a7bf808a220742d18fb1868241f1665f745ebdbc (commit)
via 8eafc24514f6d6cc1a8b71bef7358cb9bdff5a8c (commit)
via 25530c6ca2483cf92aab7f240c7fa70bd3c5b3a8 (commit)
via 33f0b8420f858e389d2fb0f27ddde1fa77287dc7 (commit)
via e8edbcf573f453fd1f72a3b899eaad9d195cc37c (commit)
via c31de72c76258d7444acc1f1ae1f30024b772af7 (commit)
via 14824b3a8199caabbf76a5c7b933715a0f0c4fec (commit)
via 38e8504574d599e1e6d139ebcfe5f905de678e2d (commit)
via 61e391f3bb0af427263c2374d613af3fd10a5fe4 (commit)
via 6c3a274305a7823a4060769ab21d55c5975615d6 (commit)
via 203152faad9ccda93c9f799ef48c47d22da1d3bf (commit)
via b6b9f884f331a4f8726a3cd6a0f3eed7870b7cf8 (commit)
via 5efcee702cfe268615733f30988abca319089531 (commit)
via 9abd2de988dbd33bac4149e0d2cb1e4fec55413e (commit)
via 5a7d082452848daa9dcb0b023f0829d457d8bcc0 (commit)
via cf32a344e6ed4eb7b5f2fcfb5c4d91a2d972d7d3 (commit)
via ff385d569e9a9b13c3c174b7d55f1916c75ce1df (commit)
via 7099d4df871fb6186a64157c13b92b2d8b56de0e (commit)
via 1b662b8dcab972fd07a7869b6b9108a8d128796f (commit)
via 330e485343c4f3054146046dac4b98c937c35269 (commit)
via 9851a189c6456b6ce5054cc4c9eea10a6866ce0f (commit)
via 4548257a1d70b64890433443d156d62a27fcc32a (commit)
via 06fdc8c5bff48e8cd0fa093dce018af40bdaa668 (commit)
via 359efa4b2178e6929e9226fdf1bce782fce0c002 (commit)
via 88effdc1b00be8636d40ef84f5556de60013347f (commit)
from 66174ced68123b63ec6d23a5f4ecd17302fd2e8b (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 ba9ed9d67285288e425e9fe9ebddf99bcda69d8a
Merge: 66174ced68123b63ec6d23a5f4ecd17302fd2e8b 4049550671160e2bbc06ca7e65996d0b7e243971
Author: Jeremy C. Reed <jreed at ISC.org>
Date: Mon Mar 14 10:47:19 2011 -0500
[jreed-doxygen]Merge branch 'master' into jreed-doxygen
Also manually fixed some conflicts.
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 223 ++++-
Makefile.am | 15 +-
README | 14 +-
configure.ac | 218 ++--
doc/Doxyfile | 2 +-
doc/guide/bind10-guide.html | 82 +-
doc/guide/bind10-guide.xml | 66 +-
ext/asio/asio/detail/epoll_reactor.hpp | 2 +-
ext/asio/asio/detail/kqueue_reactor.hpp | 2 +-
ext/asio/asio/detail/null_thread.hpp | 2 +-
.../internal => ext/coroutine}/coroutine.h | 0
src/bin/auth/Makefile.am | 4 +-
src/bin/auth/auth.spec.pre.in | 106 ++-
src/bin/auth/auth_srv.cc | 19 +
src/bin/auth/auth_srv.h | 14 +
src/bin/auth/b10-auth.8 | 62 +-
src/bin/auth/b10-auth.xml | 58 +-
src/bin/auth/benchmarks/Makefile.am | 2 +
src/bin/auth/benchmarks/query_bench.cc | 2 +-
src/bin/auth/config.cc | 61 +-
src/bin/auth/main.cc | 73 +-
src/bin/auth/query.cc | 59 +-
src/bin/auth/tests/Makefile.am | 2 +
src/bin/auth/tests/auth_srv_unittest.cc | 20 +-
src/bin/auth/tests/config_unittest.cc | 22 +-
src/bin/auth/tests/query_unittest.cc | 175 +++-
src/bin/bind10/Makefile.am | 3 +-
src/bin/bind10/bind10.8 | 37 +-
src/bin/bind10/bind10.py.in | 203 +++--
src/bin/bind10/bind10.xml | 43 +-
src/bin/bind10/tests/bind10_test.py | 315 -----
src/bin/bind10/tests/bind10_test.py.in | 463 ++++++++
src/bin/bindctl/Makefile.am | 11 +-
src/bin/bindctl/bindcmd.py | 260 +++--
src/bin/bindctl/bindctl-source.py.in | 129 ---
src/bin/bindctl/bindctl.xml | 21 +-
src/bin/bindctl/bindctl_main.py.in | 138 +++
src/bin/bindctl/cmdparse.py | 56 +-
src/bin/bindctl/moduleinfo.py | 68 +-
src/bin/bindctl/tests/Makefile.am | 4 +-
src/bin/bindctl/tests/bindctl_test.py | 152 +++-
src/bin/bindctl/tests/cmdparse_test.py | 88 ++
src/bin/cfgmgr/Makefile.am | 1 -
src/bin/cfgmgr/b10-cfgmgr.py.in | 14 +-
src/bin/cmdctl/Makefile.am | 3 +-
src/bin/host/host.cc | 7 +-
src/bin/msgq/Makefile.am | 1 -
src/bin/msgq/msgq.py.in | 2 +-
src/bin/msgq/tests/msgq_test.py | 2 +-
src/bin/resolver/Makefile.am | 7 +-
src/bin/resolver/b10-resolver.8 | 51 +-
src/bin/resolver/b10-resolver.xml | 58 +-
src/bin/resolver/main.cc | 54 +-
src/bin/resolver/resolver.cc | 204 ++--
src/bin/resolver/resolver.h | 36 +-
src/bin/resolver/resolver.spec.pre.in | 42 +-
src/bin/resolver/response_classifier.cc | 259 -----
src/bin/resolver/response_classifier.h | 138 ---
src/bin/resolver/response_scrubber.h | 2 +-
src/bin/resolver/tests/Makefile.am | 10 +-
src/bin/resolver/tests/resolver_config_unittest.cc | 116 +--
src/bin/resolver/tests/resolver_unittest.cc | 21 +
.../resolver/tests/response_classifier_unittest.cc | 494 --------
.../resolver/tests/response_scrubber_unittest.cc | 4 +-
src/bin/stats/Makefile.am | 3 +-
src/bin/usermgr/Makefile.am | 3 +-
src/bin/xfrin/Makefile.am | 3 +-
src/bin/xfrout/Makefile.am | 3 +-
src/bin/xfrout/tests/xfrout_test.py | 52 +-
src/bin/xfrout/xfrout.py.in | 192 ++--
src/bin/zonemgr/Makefile.am | 2 +-
src/cppcheck-suppress.lst | 15 +
src/lib/Makefile.am | 4 +-
src/lib/asiolink/Makefile.am | 42 +-
src/lib/asiolink/README | 81 ++-
src/lib/asiolink/asiodef.cc | 37 +
src/lib/asiolink/asiodef.h | 21 +
src/lib/asiolink/asiodef.msg | 56 +
src/lib/asiolink/asiolink.cc | 756 ------------
src/lib/asiolink/asiolink.h | 666 +-----------
src/lib/asiolink/asiolink_utilities.h | 61 +
src/lib/asiolink/dns_answer.h | 73 ++
src/lib/asiolink/dns_lookup.h | 82 ++
src/lib/asiolink/dns_server.h | 155 +++
src/lib/asiolink/dns_service.cc | 200 ++++
src/lib/asiolink/dns_service.h | 112 ++
src/lib/asiolink/dummy_io_cb.h | 59 +
src/lib/asiolink/internal/Makefile.am | 1 -
src/lib/asiolink/internal/tcpdns.h | 224 ----
src/lib/asiolink/internal/tests/Makefile.am | 37 -
src/lib/asiolink/internal/tests/run_unittests.cc | 21 -
src/lib/asiolink/internal/tests/udpdns_unittest.cc | 145 ---
src/lib/asiolink/internal/udpdns.h | 253 ----
src/lib/asiolink/interval_timer.cc | 136 +++
src/lib/asiolink/interval_timer.h | 133 +++
src/lib/asiolink/io_address.cc | 65 ++
src/lib/asiolink/io_address.h | 123 ++
src/lib/asiolink/io_asio_socket.h | 399 +++++++
src/lib/asiolink/io_endpoint.cc | 47 +
src/lib/asiolink/io_endpoint.h | 118 ++
src/lib/asiolink/io_error.h | 35 +
src/lib/asiolink/io_fetch.cc | 355 ++++++
src/lib/asiolink/io_fetch.h | 179 +++
src/lib/asiolink/io_message.h | 100 ++
src/lib/asiolink/io_service.cc | 98 ++
src/lib/asiolink/io_service.h | 77 ++
src/lib/asiolink/io_socket.cc | 67 ++
src/lib/asiolink/io_socket.h | 124 ++
src/lib/asiolink/ioaddress.cc | 62 -
src/lib/asiolink/ioaddress.h | 127 --
src/lib/asiolink/ioendpoint.cc | 43 -
src/lib/asiolink/ioendpoint.h | 122 --
src/lib/asiolink/iomessage.h | 103 --
src/lib/asiolink/iosocket.cc | 67 --
src/lib/asiolink/iosocket.h | 127 --
src/lib/asiolink/qid_gen.cc | 54 +
src/lib/asiolink/qid_gen.h | 85 ++
src/lib/asiolink/simple_callback.h | 71 ++
src/lib/asiolink/tcp_endpoint.h | 113 ++
src/lib/asiolink/tcp_server.cc | 229 ++++
src/lib/asiolink/tcp_server.h | 123 ++
src/lib/asiolink/tcp_socket.h | 416 +++++++
src/lib/asiolink/tcpdns.cc | 194 ----
src/lib/asiolink/tests/Makefile.am | 33 +-
src/lib/asiolink/tests/asiolink_unittest.cc | 1207 --------------------
.../asiolink/tests/asiolink_utilities_unittest.cc | 74 ++
src/lib/asiolink/tests/interval_timer_unittest.cc | 296 +++++
src/lib/asiolink/tests/io_address_unittest.cc | 63 +
src/lib/asiolink/tests/io_endpoint_unittest.cc | 68 ++
src/lib/asiolink/tests/io_fetch_unittest.cc | 608 ++++++++++
src/lib/asiolink/tests/io_service_unittest.cc | 116 ++
src/lib/asiolink/tests/io_socket_unittest.cc | 32 +
src/lib/asiolink/tests/qid_gen_unittest.cc | 59 +
src/lib/asiolink/tests/run_unittests.cc | 6 +-
src/lib/asiolink/tests/tcp_endpoint_unittest.cc | 55 +
src/lib/asiolink/tests/tcp_socket_unittest.cc | 515 +++++++++
src/lib/asiolink/tests/udp_endpoint_unittest.cc | 55 +
src/lib/asiolink/tests/udp_socket_unittest.cc | 333 ++++++
src/lib/asiolink/udp_endpoint.h | 113 ++
src/lib/asiolink/udp_server.cc | 322 ++++++
src/lib/asiolink/udp_server.h | 106 ++
src/lib/asiolink/udp_socket.h | 322 ++++++
src/lib/asiolink/udpdns.cc | 432 -------
src/lib/bench/tests/benchmark_unittest.cc | 19 +-
src/lib/bench/tests/loadquery_unittest.cc | 2 +-
src/lib/cache/Makefile.am | 34 +
src/lib/cache/TODO | 18 +
src/lib/cache/cache_entry_key.cc | 42 +
src/lib/cache/cache_entry_key.h | 54 +
src/lib/cache/local_zone_data.cc | 56 +
src/lib/cache/local_zone_data.h | 64 +
src/lib/cache/message_cache.cc | 115 ++
src/lib/cache/message_cache.h | 104 ++
src/lib/cache/message_entry.cc | 345 ++++++
src/lib/cache/message_entry.h | 203 ++++
src/lib/cache/message_utility.cc | 80 ++
src/lib/cache/message_utility.h | 66 ++
src/lib/cache/resolver_cache.cc | 252 ++++
src/lib/cache/resolver_cache.h | 337 ++++++
src/lib/cache/rrset_cache.cc | 102 ++
src/lib/cache/rrset_cache.h | 109 ++
src/lib/cache/rrset_copy.cc | 38 +
src/lib/cache/rrset_copy.h | 42 +
src/lib/cache/rrset_entry.cc | 66 ++
src/lib/cache/rrset_entry.h | 135 +++
src/lib/cache/tests/Makefile.am | 80 ++
src/lib/cache/tests/cache_test_messagefromfile.h | 39 +
src/lib/cache/tests/cache_test_sectioncount.h | 44 +
src/lib/cache/tests/local_zone_data_unittest.cc | 64 +
src/lib/cache/tests/message_cache_unittest.cc | 162 +++
src/lib/cache/tests/message_entry_unittest.cc | 309 +++++
src/lib/cache/tests/negative_cache_unittest.cc | 242 ++++
src/lib/cache/tests/resolver_cache_unittest.cc | 128 +++
src/lib/cache/tests/rrset_cache_unittest.cc | 127 ++
src/lib/cache/tests/rrset_entry_unittest.cc | 106 ++
src/lib/cache/tests/run_unittests.cc | 28 +
.../tests/testdata/message_cname_referral.wire | 56 +
.../tests/testdata/message_example_com_soa.wire | 57 +
.../tests/testdata/message_fromWire1 | 0
src/lib/cache/tests/testdata/message_fromWire2 | 22 +
src/lib/cache/tests/testdata/message_fromWire3 | 76 ++
src/lib/cache/tests/testdata/message_fromWire4 | 80 ++
src/lib/cache/tests/testdata/message_fromWire5 | 36 +
src/lib/cache/tests/testdata/message_fromWire6 | 40 +
src/lib/cache/tests/testdata/message_fromWire7 | 27 +
src/lib/cache/tests/testdata/message_fromWire8 | 23 +
src/lib/cache/tests/testdata/message_fromWire9 | 25 +
.../cache/tests/testdata/message_large_ttl.wire | 31 +
.../tests/testdata/message_nodata_with_soa.wire | 32 +
.../tests/testdata/message_nxdomain_cname.wire | 36 +
.../tests/testdata/message_nxdomain_large_ttl.wire | 25 +
.../tests/testdata/message_nxdomain_no_soa.wire | 26 +
.../tests/testdata/message_nxdomain_with_soa.wire | 55 +
src/lib/cache/tests/testdata/message_referral.wire | 36 +
src/lib/cc/tests/session_unittests.cc | 2 +-
src/lib/config/ccsession.cc | 4 +-
src/lib/config/module_spec.cc | 2 +-
src/lib/config/tests/ccsession_unittests.cc | 2 +-
src/lib/config/tests/module_spec_unittests.cc | 4 +-
src/lib/config/tests/testdata/spec22.spec | 4 +-
src/lib/datasrc/data_source.cc | 15 +-
src/lib/datasrc/memory_datasrc.cc | 244 ++++-
src/lib/datasrc/rbtree.h | 218 +++-
src/lib/datasrc/tests/Makefile.am | 1 +
src/lib/datasrc/tests/datasrc_unittest.cc | 168 ++-
src/lib/datasrc/tests/memory_datasrc_unittest.cc | 554 ++++++++--
src/lib/datasrc/tests/rbtree_unittest.cc | 221 ++++-
src/lib/datasrc/tests/test_datasrc.cc | 15 +-
src/lib/dns/Makefile.am | 3 +
src/lib/dns/dnssectime.cc | 153 ++-
src/lib/dns/dnssectime.h | 98 ++-
src/lib/dns/message.cc | 33 +-
src/lib/dns/message.h | 12 +
src/lib/dns/python/messagerenderer_python.cc | 45 +-
.../python/tests/messagerenderer_python_test.py | 28 +-
src/lib/dns/rdata/generic/detail/nsec_bitmap.cc | 78 ++
src/lib/dns/rdata/generic/detail/nsec_bitmap.h | 51 +
src/lib/dns/rdata/generic/nsec3_50.cc | 121 ++-
src/lib/dns/rdata/generic/nsec3_50.h | 3 +-
src/lib/dns/rdata/generic/nsec_47.cc | 43 +-
src/lib/dns/rdata/generic/rrsig_46.cc | 11 +-
src/lib/dns/rrset.h | 10 +-
src/lib/dns/tests/Makefile.am | 1 +
src/lib/dns/tests/dnssectime_unittest.cc | 147 ++-
src/lib/dns/tests/message_unittest.cc | 126 ++
src/lib/dns/tests/name_unittest.cc | 2 +-
src/lib/dns/tests/rdata_mx_unittest.cc | 5 +-
src/lib/dns/tests/rdata_nsec3_unittest.cc | 127 ++-
src/lib/dns/tests/rdata_nsec_unittest.cc | 41 +-
src/lib/dns/tests/rdata_nsecbitmap_unittest.cc | 103 ++
src/lib/dns/tests/testdata/Makefile.am | 25 +-
src/lib/dns/tests/testdata/gen-wiredata.py.in | 81 +-
src/lib/dns/tests/testdata/rdata_mx_toWire2 | 12 +
.../dns/tests/testdata/rdata_nsec3_fromWire1.spec | 7 +
.../dns/tests/testdata/rdata_nsec3_fromWire10.spec | 8 +
.../dns/tests/testdata/rdata_nsec3_fromWire11.spec | 8 +
.../dns/tests/testdata/rdata_nsec3_fromWire12.spec | 9 +
.../dns/tests/testdata/rdata_nsec3_fromWire13.spec | 9 +
.../dns/tests/testdata/rdata_nsec3_fromWire14.spec | 9 +
.../dns/tests/testdata/rdata_nsec3_fromWire15.spec | 10 +
src/lib/dns/tests/testdata/rdata_nsec3_fromWire2 | 12 -
.../dns/tests/testdata/rdata_nsec3_fromWire2.spec | 9 +
.../dns/tests/testdata/rdata_nsec3_fromWire4.spec | 9 +
.../dns/tests/testdata/rdata_nsec3_fromWire5.spec | 13 +
.../dns/tests/testdata/rdata_nsec3_fromWire6.spec | 11 +
.../dns/tests/testdata/rdata_nsec3_fromWire7.spec | 9 +
.../dns/tests/testdata/rdata_nsec3_fromWire8.spec | 9 +
.../dns/tests/testdata/rdata_nsec3_fromWire9.spec | 10 +
src/lib/log/Makefile.am | 14 +-
src/lib/log/README | 376 ++++++
src/lib/log/compiler/Makefile.am | 4 +-
src/lib/log/compiler/message.cc | 270 +++--
src/lib/log/dbglevels.h | 31 -
src/lib/log/debug_levels.h | 29 +
src/lib/log/documentation.txt | 371 ------
src/lib/log/filename.cc | 8 +-
src/lib/log/filename.h | 12 +-
src/lib/log/logger.cc | 316 ++----
src/lib/log/logger.h | 265 ++---
src/lib/log/logger_impl.cc | 221 ++++
src/lib/log/logger_impl.h | 267 +++++
src/lib/log/logger_impl_log4cxx.cc | 241 ++++
src/lib/log/logger_impl_log4cxx.h | 315 +++++
src/lib/log/logger_levels.h | 42 +
src/lib/log/logger_support.cc | 58 +-
src/lib/log/logger_support.h | 21 +-
src/lib/log/message_dictionary.cc | 51 +-
src/lib/log/message_dictionary.h | 70 +-
src/lib/log/message_exception.cc | 2 -
src/lib/log/message_exception.h | 4 +-
src/lib/log/message_initializer.cc | 20 +-
src/lib/log/message_initializer.h | 20 +-
src/lib/log/message_reader.cc | 91 ++-
src/lib/log/message_reader.h | 43 +-
src/lib/log/message_types.h | 13 +-
src/lib/log/messagedef.cc | 46 +-
src/lib/log/messagedef.h | 36 +-
src/lib/log/messagedef.mes | 61 +-
src/lib/log/root_logger_name.cc | 26 +-
src/lib/log/root_logger_name.h | 52 +-
src/lib/log/strutil.cc | 13 +-
src/lib/log/strutil.h | 10 +-
src/lib/log/tests/Makefile.am | 4 +-
src/lib/log/tests/filename_unittest.cc | 4 +-
src/lib/log/tests/localdef.mes | 23 -
src/lib/log/tests/logger_impl_log4cxx_unittest.cc | 91 ++
src/lib/log/tests/logger_support_test.cc | 45 +-
src/lib/log/tests/logger_unittest.cc | 202 ++--
src/lib/log/tests/message_dictionary_unittest.cc | 58 +-
src/lib/log/tests/message_initializer_unittest.cc | 18 +-
.../log/tests/message_initializer_unittest_2.cc | 4 +-
src/lib/log/tests/message_reader_unittest.cc | 62 +-
src/lib/log/tests/root_logger_name_unittest.cc | 12 +-
src/lib/log/tests/run_time_init_test.sh.in | 34 +-
src/lib/log/tests/run_unittests.cc | 4 +-
src/lib/log/tests/strutil_unittest.cc | 4 +-
src/lib/log/tests/xdebuglevel_unittest.cc | 6 +-
src/lib/log/xdebuglevel.cc | 28 +-
src/lib/log/xdebuglevel.h | 6 +-
src/lib/nsas/Makefile.am | 1 +
src/lib/nsas/address_entry.h | 4 +-
src/lib/nsas/asiolink.h | 37 -
src/lib/nsas/hash_table.h | 24 +-
src/lib/nsas/locks.h | 116 ++
src/lib/nsas/lru_list.h | 24 +-
src/lib/nsas/nameserver_address.cc | 4 +-
src/lib/nsas/nameserver_address_store.cc | 28 +-
src/lib/nsas/nameserver_address_store.h | 7 +
src/lib/nsas/nameserver_entry.cc | 11 +-
src/lib/nsas/nameserver_entry.h | 24 +-
src/lib/nsas/tests/Makefile.am | 1 +
src/lib/nsas/tests/address_entry_unittest.cc | 4 +-
src/lib/nsas/tests/nameserver_address_unittest.cc | 2 +-
src/lib/nsas/tests/nameserver_entry_unittest.cc | 4 +-
src/lib/nsas/tests/nsas_test.h | 1 +
src/lib/nsas/tests/zone_entry_unittest.cc | 32 +-
src/lib/nsas/zone_entry.cc | 23 +-
src/lib/nsas/zone_entry.h | 30 +-
src/lib/python/isc/cc/data.py | 10 +-
src/lib/python/isc/config/ccsession.py | 22 +-
src/lib/python/isc/config/config_data.py | 181 ++-
.../python/isc/config/tests/config_data_test.py | 76 +-
src/lib/python/isc/notify/tests/notify_out_test.py | 265 +++--
src/lib/python/isc/util/socketserver_mixin.py | 6 +-
.../isc/util/tests/socketserver_mixin_test.py | 2 +-
src/lib/python/isc/utils/Makefile.am | 5 -
src/lib/python/isc/utils/process.py | 37 -
src/lib/python/isc/utils/tests/Makefile.am | 16 -
src/lib/python/isc/utils/tests/process_test.py | 39 -
src/lib/resolve/Makefile.am | 14 +-
src/lib/resolve/recursive_query.cc | 757 ++++++++++++
src/lib/resolve/recursive_query.h | 133 +++
src/lib/resolve/resolve.cc | 78 ++
src/lib/resolve/resolve.h | 76 ++
src/lib/resolve/response_classifier.cc | 278 +++++
src/lib/resolve/response_classifier.h | 157 +++
src/lib/resolve/tests/Makefile.am | 11 +
src/lib/resolve/tests/recursive_query_unittest.cc | 861 ++++++++++++++
.../resolve/tests/recursive_query_unittest_2.cc | 656 +++++++++++
src/lib/resolve/tests/resolve_unittest.cc | 198 ++++
.../resolve/tests/response_classifier_unittest.cc | 554 +++++++++
src/lib/server_common/Makefile.am | 26 +
src/lib/server_common/portconfig.cc | 119 ++
src/lib/server_common/portconfig.h | 121 ++
src/lib/server_common/tests/Makefile.am | 42 +
src/lib/server_common/tests/portconfig_unittest.cc | 182 +++
src/lib/server_common/tests/run_unittests.cc | 26 +
src/lib/testutils/Makefile.am | 3 +
src/lib/testutils/portconfig.h | 189 +++
src/lib/testutils/srv_test.cc | 4 +-
tests/Makefile.am | 1 +
tests/system/Makefile.am | 16 +
tests/system/README | 63 +
tests/system/bindctl/clean.sh | 20 +
.../system/bindctl/nsx1/b10-config.db.template.in | 10 +
tests/system/bindctl/nsx1/example-normalized.db | 3 +
tests/system/bindctl/nsx1/root.db | 25 +
tests/system/bindctl/setup.sh | 26 +
tests/system/bindctl/tests.sh | 106 ++
tests/system/cleanall.sh | 33 +
tests/system/common/default_user.csv | 1 +
tests/system/conf.sh.in | 57 +
tests/system/glue/auth.good | 15 +
tests/system/glue/clean.sh | 23 +
tests/system/glue/example.good | 19 +
tests/system/glue/noglue.good | 14 +
tests/system/glue/nsx1/b10-config.db.in | 9 +
tests/system/glue/nsx1/com.db | 31 +
tests/system/glue/nsx1/net.db | 32 +
tests/system/glue/nsx1/root-servers.nil.db | 26 +
tests/system/glue/nsx1/root.db | 55 +
tests/system/glue/setup.sh.in | 25 +
tests/system/glue/test.good | 19 +
tests/system/glue/tests.sh | 66 ++
tests/system/ifconfig.sh | 226 ++++
tests/system/run.sh | 125 ++
tests/system/runall.sh | 44 +
tests/system/start.pl | 226 ++++
tests/system/stop.pl | 188 +++
tools/README | 5 +-
tools/import_boost.sh | 74 --
tools/tests_in_valgrind.sh | 75 ++
tools/valgrind_test_cleaner.pl | 64 +
383 files changed, 26027 insertions(+), 9478 deletions(-)
rename {src/lib/asiolink/internal => ext/coroutine}/coroutine.h (100%)
mode change 100644 => 100755 src/bin/bind10/bind10.py.in
delete mode 100644 src/bin/bind10/tests/bind10_test.py
create mode 100644 src/bin/bind10/tests/bind10_test.py.in
delete mode 100644 src/bin/bindctl/bindctl-source.py.in
create mode 100755 src/bin/bindctl/bindctl_main.py.in
create mode 100644 src/bin/bindctl/tests/cmdparse_test.py
delete mode 100644 src/bin/resolver/response_classifier.cc
delete mode 100644 src/bin/resolver/response_classifier.h
delete mode 100644 src/bin/resolver/tests/response_classifier_unittest.cc
create mode 100644 src/cppcheck-suppress.lst
create mode 100644 src/lib/asiolink/asiodef.cc
create mode 100644 src/lib/asiolink/asiodef.h
create mode 100644 src/lib/asiolink/asiodef.msg
delete mode 100644 src/lib/asiolink/asiolink.cc
create mode 100644 src/lib/asiolink/asiolink_utilities.h
create mode 100644 src/lib/asiolink/dns_answer.h
create mode 100644 src/lib/asiolink/dns_lookup.h
create mode 100644 src/lib/asiolink/dns_server.h
create mode 100644 src/lib/asiolink/dns_service.cc
create mode 100644 src/lib/asiolink/dns_service.h
create mode 100644 src/lib/asiolink/dummy_io_cb.h
delete mode 100644 src/lib/asiolink/internal/Makefile.am
delete mode 100644 src/lib/asiolink/internal/tcpdns.h
delete mode 100644 src/lib/asiolink/internal/tests/Makefile.am
delete mode 100644 src/lib/asiolink/internal/tests/run_unittests.cc
delete mode 100644 src/lib/asiolink/internal/tests/udpdns_unittest.cc
delete mode 100644 src/lib/asiolink/internal/udpdns.h
create mode 100644 src/lib/asiolink/interval_timer.cc
create mode 100644 src/lib/asiolink/interval_timer.h
create mode 100644 src/lib/asiolink/io_address.cc
create mode 100644 src/lib/asiolink/io_address.h
create mode 100644 src/lib/asiolink/io_asio_socket.h
create mode 100644 src/lib/asiolink/io_endpoint.cc
create mode 100644 src/lib/asiolink/io_endpoint.h
create mode 100644 src/lib/asiolink/io_error.h
create mode 100644 src/lib/asiolink/io_fetch.cc
create mode 100644 src/lib/asiolink/io_fetch.h
create mode 100644 src/lib/asiolink/io_message.h
create mode 100644 src/lib/asiolink/io_service.cc
create mode 100644 src/lib/asiolink/io_service.h
create mode 100644 src/lib/asiolink/io_socket.cc
create mode 100644 src/lib/asiolink/io_socket.h
delete mode 100644 src/lib/asiolink/ioaddress.cc
delete mode 100644 src/lib/asiolink/ioaddress.h
delete mode 100644 src/lib/asiolink/ioendpoint.cc
delete mode 100644 src/lib/asiolink/ioendpoint.h
delete mode 100644 src/lib/asiolink/iomessage.h
delete mode 100644 src/lib/asiolink/iosocket.cc
delete mode 100644 src/lib/asiolink/iosocket.h
create mode 100644 src/lib/asiolink/qid_gen.cc
create mode 100644 src/lib/asiolink/qid_gen.h
create mode 100644 src/lib/asiolink/simple_callback.h
create mode 100644 src/lib/asiolink/tcp_endpoint.h
create mode 100644 src/lib/asiolink/tcp_server.cc
create mode 100644 src/lib/asiolink/tcp_server.h
create mode 100644 src/lib/asiolink/tcp_socket.h
delete mode 100644 src/lib/asiolink/tcpdns.cc
delete mode 100644 src/lib/asiolink/tests/asiolink_unittest.cc
create mode 100644 src/lib/asiolink/tests/asiolink_utilities_unittest.cc
create mode 100644 src/lib/asiolink/tests/interval_timer_unittest.cc
create mode 100644 src/lib/asiolink/tests/io_address_unittest.cc
create mode 100644 src/lib/asiolink/tests/io_endpoint_unittest.cc
create mode 100644 src/lib/asiolink/tests/io_fetch_unittest.cc
create mode 100644 src/lib/asiolink/tests/io_service_unittest.cc
create mode 100644 src/lib/asiolink/tests/io_socket_unittest.cc
create mode 100644 src/lib/asiolink/tests/qid_gen_unittest.cc
create mode 100644 src/lib/asiolink/tests/tcp_endpoint_unittest.cc
create mode 100644 src/lib/asiolink/tests/tcp_socket_unittest.cc
create mode 100644 src/lib/asiolink/tests/udp_endpoint_unittest.cc
create mode 100644 src/lib/asiolink/tests/udp_socket_unittest.cc
create mode 100644 src/lib/asiolink/udp_endpoint.h
create mode 100644 src/lib/asiolink/udp_server.cc
create mode 100644 src/lib/asiolink/udp_server.h
create mode 100644 src/lib/asiolink/udp_socket.h
delete mode 100644 src/lib/asiolink/udpdns.cc
create mode 100644 src/lib/cache/Makefile.am
create mode 100644 src/lib/cache/TODO
create mode 100644 src/lib/cache/cache_entry_key.cc
create mode 100644 src/lib/cache/cache_entry_key.h
create mode 100644 src/lib/cache/local_zone_data.cc
create mode 100644 src/lib/cache/local_zone_data.h
create mode 100644 src/lib/cache/message_cache.cc
create mode 100644 src/lib/cache/message_cache.h
create mode 100644 src/lib/cache/message_entry.cc
create mode 100644 src/lib/cache/message_entry.h
create mode 100644 src/lib/cache/message_utility.cc
create mode 100644 src/lib/cache/message_utility.h
create mode 100644 src/lib/cache/resolver_cache.cc
create mode 100644 src/lib/cache/resolver_cache.h
create mode 100644 src/lib/cache/rrset_cache.cc
create mode 100644 src/lib/cache/rrset_cache.h
create mode 100644 src/lib/cache/rrset_copy.cc
create mode 100644 src/lib/cache/rrset_copy.h
create mode 100644 src/lib/cache/rrset_entry.cc
create mode 100644 src/lib/cache/rrset_entry.h
create mode 100644 src/lib/cache/tests/Makefile.am
create mode 100644 src/lib/cache/tests/cache_test_messagefromfile.h
create mode 100644 src/lib/cache/tests/cache_test_sectioncount.h
create mode 100644 src/lib/cache/tests/local_zone_data_unittest.cc
create mode 100644 src/lib/cache/tests/message_cache_unittest.cc
create mode 100644 src/lib/cache/tests/message_entry_unittest.cc
create mode 100644 src/lib/cache/tests/negative_cache_unittest.cc
create mode 100644 src/lib/cache/tests/resolver_cache_unittest.cc
create mode 100644 src/lib/cache/tests/rrset_cache_unittest.cc
create mode 100644 src/lib/cache/tests/rrset_entry_unittest.cc
create mode 100644 src/lib/cache/tests/run_unittests.cc
create mode 100644 src/lib/cache/tests/testdata/message_cname_referral.wire
create mode 100644 src/lib/cache/tests/testdata/message_example_com_soa.wire
copy src/lib/{dns => cache}/tests/testdata/message_fromWire1 (100%)
create mode 100644 src/lib/cache/tests/testdata/message_fromWire2
create mode 100644 src/lib/cache/tests/testdata/message_fromWire3
create mode 100644 src/lib/cache/tests/testdata/message_fromWire4
create mode 100644 src/lib/cache/tests/testdata/message_fromWire5
create mode 100644 src/lib/cache/tests/testdata/message_fromWire6
create mode 100644 src/lib/cache/tests/testdata/message_fromWire7
create mode 100644 src/lib/cache/tests/testdata/message_fromWire8
create mode 100644 src/lib/cache/tests/testdata/message_fromWire9
create mode 100644 src/lib/cache/tests/testdata/message_large_ttl.wire
create mode 100644 src/lib/cache/tests/testdata/message_nodata_with_soa.wire
create mode 100644 src/lib/cache/tests/testdata/message_nxdomain_cname.wire
create mode 100644 src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire
create mode 100644 src/lib/cache/tests/testdata/message_nxdomain_no_soa.wire
create mode 100644 src/lib/cache/tests/testdata/message_nxdomain_with_soa.wire
create mode 100644 src/lib/cache/tests/testdata/message_referral.wire
create mode 100644 src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
create mode 100644 src/lib/dns/rdata/generic/detail/nsec_bitmap.h
create mode 100644 src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
create mode 100644 src/lib/dns/tests/testdata/rdata_mx_toWire2
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire1.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.spec
delete mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire2
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.spec
create mode 100644 src/lib/log/README
delete mode 100644 src/lib/log/dbglevels.h
create mode 100644 src/lib/log/debug_levels.h
delete mode 100644 src/lib/log/documentation.txt
create mode 100644 src/lib/log/logger_impl.cc
create mode 100644 src/lib/log/logger_impl.h
create mode 100644 src/lib/log/logger_impl_log4cxx.cc
create mode 100644 src/lib/log/logger_impl_log4cxx.h
create mode 100644 src/lib/log/logger_levels.h
delete mode 100644 src/lib/log/tests/localdef.mes
create mode 100644 src/lib/log/tests/logger_impl_log4cxx_unittest.cc
create mode 100644 src/lib/nsas/locks.h
delete mode 100644 src/lib/python/isc/utils/Makefile.am
delete mode 100644 src/lib/python/isc/utils/__init__.py
delete mode 100644 src/lib/python/isc/utils/process.py
delete mode 100644 src/lib/python/isc/utils/tests/Makefile.am
delete mode 100644 src/lib/python/isc/utils/tests/process_test.py
create mode 100644 src/lib/resolve/recursive_query.cc
create mode 100644 src/lib/resolve/recursive_query.h
create mode 100644 src/lib/resolve/resolve.cc
create mode 100644 src/lib/resolve/response_classifier.cc
create mode 100644 src/lib/resolve/response_classifier.h
create mode 100644 src/lib/resolve/tests/recursive_query_unittest.cc
create mode 100644 src/lib/resolve/tests/recursive_query_unittest_2.cc
create mode 100644 src/lib/resolve/tests/resolve_unittest.cc
create mode 100644 src/lib/resolve/tests/response_classifier_unittest.cc
create mode 100644 src/lib/server_common/Makefile.am
create mode 100644 src/lib/server_common/portconfig.cc
create mode 100644 src/lib/server_common/portconfig.h
create mode 100644 src/lib/server_common/tests/Makefile.am
create mode 100644 src/lib/server_common/tests/portconfig_unittest.cc
create mode 100644 src/lib/server_common/tests/run_unittests.cc
create mode 100644 src/lib/testutils/portconfig.h
create mode 100644 tests/Makefile.am
create mode 100644 tests/system/Makefile.am
create mode 100644 tests/system/README
create mode 100755 tests/system/bindctl/clean.sh
create mode 100644 tests/system/bindctl/nsx1/b10-config.db.template.in
create mode 100644 tests/system/bindctl/nsx1/example-normalized.db
create mode 100644 tests/system/bindctl/nsx1/root.db
create mode 100755 tests/system/bindctl/setup.sh
create mode 100755 tests/system/bindctl/tests.sh
create mode 100755 tests/system/cleanall.sh
create mode 100644 tests/system/common/default_user.csv
create mode 100755 tests/system/conf.sh.in
create mode 100644 tests/system/glue/auth.good
create mode 100755 tests/system/glue/clean.sh
create mode 100644 tests/system/glue/example.good
create mode 100644 tests/system/glue/noglue.good
create mode 100644 tests/system/glue/nsx1/b10-config.db.in
create mode 100644 tests/system/glue/nsx1/com.db
create mode 100644 tests/system/glue/nsx1/net.db
create mode 100644 tests/system/glue/nsx1/root-servers.nil.db
create mode 100644 tests/system/glue/nsx1/root.db
create mode 100755 tests/system/glue/setup.sh.in
create mode 100644 tests/system/glue/test.good
create mode 100755 tests/system/glue/tests.sh
create mode 100755 tests/system/ifconfig.sh
create mode 100755 tests/system/run.sh
create mode 100755 tests/system/runall.sh
create mode 100755 tests/system/start.pl
create mode 100755 tests/system/stop.pl
delete mode 100755 tools/import_boost.sh
create mode 100755 tools/tests_in_valgrind.sh
create mode 100755 tools/valgrind_test_cleaner.pl
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index ae4e72a..4294904 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,13 +1,228 @@
+ 199. [func] ocean
+ Cache negative responses (NXDOMAIN/NODATA) from authoritative
+ server for recursive resolver.
+ (Trac #493, git f8fb852bc6aef292555063590c361f01cf29e5ca)
+
+ 198. [bug] jinmei
+ b10-auth, src/lib/datasrc: fixed a bug where hot spot cache failed
+ to reuse cached SOA for negative responses. Due to this bug
+ b10-auth returned SERVFAIL when it was expected to return a
+ negative response immediately after a specific SOA query for
+ the zone.
+ (Trac #626, git 721a53160c15e8218f6798309befe940b9597ba0)
+
+ 197. [bug] zhang likun
+ Remove expired message and rrset entries when looking up them
+ in cache, touch or remove the rrset entry in cache properly
+ when doing lookup or update.
+ (Trac #661, git 9efbe64fe3ff22bb5fba46de409ae058f199c8a7)
+
+ 196. [bug] jinmei
+ b10-auth, src/lib/datasrc: the backend of the in-memory data
+ source could not handle the root name. As a result b10-auth could
+ not work as a root server when using the in-memory data source.
+ (Trac #683, git 420ec42bd913fb83da37b26b75faae49c7957c46)
+
+ 195. [func] stephen
+ Resolver will now re-try a query over TCP if a response to a UDP
+ query has the TC bit set.
+ (Trac #499, git 4c05048ba059b79efeab53498737abe94d37ee07)
+
+ 194. [bug] vorner
+ Solved a 100% CPU usage problem after switching addresses in b10-auth
+ (and possibly, but unconfirmed, in b10-resolver). It was caused by
+ repeated reads/accepts on closed socket (the bug was in the code for a
+ long time, recent changes made it show).
+ (Trac #657, git e0863720a874d75923ea66adcfbf5b2948efb10a)
+
+ 193. [func]* jreed
+ Listen on the IPv6 (::) and IPv4 (0.0.0.0) wildcard addresses
+ for b10-auth. This returns to previous behavior prior to
+ change #184. Document the listen_on configuration in manual.
+ (Trac #649, git 65a77d8fde64d464c75917a1ab9b6b3f02640ca6)
+
+ 192. [func]* jreed
+ Listen on standard domain port 53 for b10-auth and
+ b10-resolver.
+ (Trac #617, #618, git 137a6934a14cf0c5b5c065e910b8b364beb0973f)
+
+ 191. [func] jinmei
+ Imported system test framework of BIND 9. It can be run by
+ 'make systest' at the top source directory. Notes: currently it
+ doesn't work when built in a separate tree. It also requires
+ perl, an inherited dependency from the original framework.
+ Also, mainly for the purpose of tests, a new option "--pid-file"
+ was added to BoB, with which the boss process will dump its PID
+ to the specified file.
+ (Trac #606, git 6ac000df85625f5921e8895a1aafff5e4be3ba9c)
+
+ 190. [func] jelte
+ Resolver now sets random qids on outgoing queries using
+ the boost::mt19937 prng.
+ (Trac #583, git 5222b51a047d8f2352bc9f92fd022baf1681ed81)
+
+ 189. [bug] jreed
+ Do not install the log message compiler.
+ (Trac #634, git eb6441aca464980d00e3ff827cbf4195c5a7afc5)
+
+ 188. [bug] zhang likun
+ Make the rrset trust level ranking algorithm used by
+ isc::cache::MessageEntry::getRRsetTrustLevel() follow RFC2181
+ section 5.4.1.
+ (Trac #595 git 19197b5bc9f2955bd6a8ca48a2d04472ed696e81)
+
+ 187. [bug] zhang likun
+ Fix the assert error in class isc::cache::RRsetCache by adding the
+ check for empty pointer and test case for it.
+ (Trac #638, git 54e61304131965c4a1d88c9151f8697dcbb3ce12)
+
+ 186. [bug] jelte
+ b10-resolver could stop with an assertion failure on certain kinds
+ of messages (there was a problem in error message creation). This
+ fixes that.
+ (Trac #607, git 25a5f4ec755bc09b54410fcdff22691283147f32)
+
+ 185. [bug] vorner
+ Tests use port from private range (53210), lowering chance of
+ a conflict with something else (eg. running bind 10).
+ (Trac #523, git 301da7d26d41e64d87c0cf72727f3347aa61fb40)
+
+ 184. [func]* vorner
+ Listening address and port configuration of b10-auth is the same as
+ for b10-resolver now. That means, it is configured through bindctl
+ at runtime, in the Auth/listen_on list, not through command line
+ arguments.
+ (Trac #575, #576, git f06ce638877acf6f8e1994962bf2dbfbab029edf)
+
+ 183. [bug] jerry
+ src/bin/xfrout: Enable parallel sessions between xfrout server and
+ muti-Auth. The session needs to be created only on the first time
+ or if an error occur.
+ (Trac #419, git 1d60afb59e9606f312caef352ecb2fe488c4e751)
+
+ 182. [func] jinmei
+ Support cppcheck for static code check on C++ code. If cppcheck
+ is available, 'make cppcheck' on the top source directory will run
+ the checker and should cleanly complete with an exit code of 0
+ (at least with cppcheck 1.47).
+ Note: the suppression list isn't included in the final
+ distributions. It should be created by hand or retrieved from
+ the git repository.
+ (Trac #613, git b973f67520682b63ef38b1451d309be9f4f4b218)
+
+ 181. [func] feng
+ Add stop interface into dns server, so we can stop each running
+ server individually. With it, user can reconfigure her running server
+ with different ip address or port.
+ (Trac #388, git 6df94e2db856c1adc020f658cc77da5edc967555)
+
+ 180. [build] jreed
+ Fix custom DESTDIR for make install. Patch from Jan Engelhardt.
+ (Trac #629, git 5ac67ede03892a5eacf42ce3ace1e4e376164c9f)
+
+bind10-devel-20110224 released on February 24, 2011
+
+ 179. [func] vorner
+ It is possible to start and stop resolver and authoritative
+ server without restart of the whole system. Change of the
+ configuration (Boss/start_auth and Boss/start_resolver) is
+ enough.
+ (Trac #565, git 0ac0b4602fa30852b0d86cc3c0b4730deb1a58fe)
+
+ 178. [func] jelte
+ Resolver now makes (limited) use of the cache
+ (Trac #491, git 8b41f77f0099ddc7ca7d34d39ad8c39bb1a8363c)
+
+ 177. [func] stephen
+ The upstream fetch code in asiolink is now protocol agnostic to
+ allow for the addition of fallback to TCP if a fetch response
+ indicates truncation.
+ (Trac #554, git 9739cbce2eaffc7e80640db58a8513295cf684de)
+
+ 176. [func] zhang likun
+ src/lib/cache: Rename one interface: from lookupClosestRRset()
+ to lookupDeepestNS(), and remove one parameter of it.
+ (Trac #492, git ecbfb7cf929d62a018dd4cdc7a841add3d5a35ae)
+
+ 175. [bug] jerry
+ src/bin/xfrout: Xfrout use the case-sensitive mode to compress
+ names in an AXFR massage.
+ (Trac #253, git 004e382616150f8a2362e94d3458b59bb2710182)
+
+ 174. [bug]* jinmei
+ src/lib/dns: revised dnssectime functions so that they don't rely
+ on the time_t type (whose size varies on different systems, which
+ can lead to subtle bugs like some form of "year 2038 problem").
+ Also handled 32-bit wrap around issues more explicitly, with more
+ detailed tests. The function API has been changed, but the effect
+ should be minimal because these functions are mostly private.
+ (Trac #61, git 09ece8cdd41c0f025e8b897b4883885d88d4ba5d)
+
+ 173. [bug] jerry
+ python/isc/notify: A notify_out test fails without network
+ connectivity, encapsulate the socket behavior using a mock
+ socket class to fix it.
+ (Trac #346, git 319debfb957641f311102739a15059f8453c54ce)
+
+ 172. [func] jelte
+ Improved the bindctl cli in various ways, mainly concerning
+ list and map item addressing, the correct display of actual values,
+ and internal help.
+ (Trac #384, git e5fb3bc1ed5f3c0aec6eb40a16c63f3d0fc6a7b2)
+
+ 171. [func] feng, jerry, jinmei, vorner
+ b10-auth, src/lib/datasrc: in memory data source now works as a
+ complete data source for authoritative DNS servers and b10-auth
+ uses it. It still misses major features, however, including
+ DNSSEC support and zone transfer.
+ (Last trac #553, but many more,
+ git 6f031a09a248e7684723c000f3e8cc981dcdb349)
+
+ 170. [bug] jinmei
+ Tightened validity checks in the NSEC3 constructors, both "from
+ "text" and "from wire". Specifically, wire data containing
+ invalid type bitmaps or invalid lengths of salt or hash is now
+ correctly rejected.
+ (Trac #117, git 9c690982f24fef19c747a72f43c4298333a58f48)
+
+ 169. [func] zhang likun, jelte
+ Added a basic implementation for a resolver cache (though not
+ used yet).
+ (Trac #449, git 8aa3b2246ae095bbe7f855fd11656ae3bdb98986)
+
+ 168. [bug] vorner
+ Boss no longer has the -f argument, which was undocumented and
+ stayed as a relict of previous versions, currently causing only
+ strange behaviour.
+ (Trac #572, git 17f237478961005707d649a661cc72a4a0d612d4)
+
+ 167. [bug] naokikambe
+ Fixed failure of termination of msgq_test.py with python3
+ coverage(3.3.1)
+ (Trac #573, git 0e6a18e12f61cc482e07078776234f32605312e5)
+
+ 166. [func] jelte
+ The resolver now sends back a SERVFAIL when there is a client
+ timeout (timeout_client config setting), but it will not stop
+ resolving (until there is a lookup timeout or a result).
+ (Trac #497 and #489, git af0e5cd93bebb27cb5c4457f7759d12c8bf953a6)
+
+ 165. [func] jelte
+ The resolver now handles CNAMEs, it will follow them, and include
+ them in the answer. The maximum length of CNAME chains that is
+ supported is 16.
+ (Trac #497, git af0e5cd93bebb27cb5c4457f7759d12c8bf953a6)
+
164. [bug] y-aharen
IntervalTimer: Modified the interface to accept interval in
milliseconds. It shortens the time of the tests of IntervalTimer.
(Trac #452, git c9f6acc81e24c4b8f0eb351123dc7b43f64e0914)
163. [func] vorner
- The pimpl design pattern is used in UDPServer, with a shared pointer. This
- makes it smaller to copy (which is done a lot as a sideeffect of being
- coroutine) and speeds applications of this class (notably b10-auth) up by
- around 10%.
+ The pimpl design pattern is used in UDPServer, with a shared
+ pointer. This makes it smaller to copy (which is done a lot as a
+ sideeffect of being coroutine) and speeds applications of this
+ class (notably b10-auth) up by around 10%.
(Trac #537, git 94cb95b1d508541201fc064302ba836164d3cbe6)
162. [func] stephen
diff --git a/Makefile.am b/Makefile.am
index 93a7498..e31a1a5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = doc src
+SUBDIRS = doc src tests
USE_LCOV=@USE_LCOV@
LCOV=@LCOV@
GENHTML=@GENHTML@
@@ -70,6 +70,18 @@ clean-coverage: clean-cpp-coverage clean-python-coverage
report-coverage: report-cpp-coverage report-python-coverage
+# for static C++ check using cppcheck (when available)
+cppcheck:
+ cppcheck --enable=all --suppressions src/cppcheck-suppress.lst \
+ --quiet --error-exitcode=1 \
+ --template '{file}:{line}: check_fail: {message} ({severity},{id})' \
+ src
+
+# system tests
+systest:
+ cd tests/system; \
+ sh $(abs_srcdir)/tests/system/runall.sh
+
#### include external sources in the distributed tarball:
EXTRA_DIST = ext/asio/README
EXTRA_DIST += ext/asio/asio/local/stream_protocol.hpp
@@ -282,3 +294,4 @@ EXTRA_DIST += ext/asio/asio/is_write_buffered.hpp
EXTRA_DIST += ext/asio/asio/buffered_read_stream_fwd.hpp
EXTRA_DIST += ext/asio/asio/socket_acceptor_service.hpp
EXTRA_DIST += ext/asio/asio.hpp
+EXTRA_DIST += ext/coroutine/coroutine.h
diff --git a/README b/README
index 9b258bc..b10d12e 100644
--- a/README
+++ b/README
@@ -14,12 +14,12 @@ five year plan are described here:
https://bind10.isc.org/wiki/Year2Milestones
This release includes the bind10 master process, b10-msgq message
-bus, b10-auth authoritative DNS server (with SQLite3 backend),
-b10-resolver forwarding DNS server, b10-cmdctl remote control daemon,
-b10-cfgmgr configuration manager, b10-xfrin AXFR inbound service,
-b10-xfrout outgoing AXFR service, b10-zonemgr secondary manager,
-b10-stats statistics collection and reporting daemon, and a new
-libdns++ library for C++ with a python wrapper.
+bus, b10-auth authoritative DNS server (with SQLite3 and in-memory
+backends), b10-resolver forwarding DNS server, b10-cmdctl remote
+control daemon, b10-cfgmgr configuration manager, b10-xfrin AXFR
+inbound service, b10-xfrout outgoing AXFR service, b10-zonemgr
+secondary manager, b10-stats statistics collection and reporting
+daemon, and a new libdns++ library for C++ with a python wrapper.
Documentation is included and also available via the BIND 10
website at http://bind10.isc.org/
@@ -164,8 +164,6 @@ source tree:
(Which will use the modules and configurations also from the source
tree.)
-The server will listen on port 5300 for DNS requests.
-
CONFIGURATION
Commands can be given through the bindctl tool.
diff --git a/configure.ac b/configure.ac
index 87d8473..acc7628 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.59])
-AC_INIT(bind10-devel, 20110120, bind10-dev at isc.org)
+AC_INIT(bind10-devel, 20110224, bind10-dev at isc.org)
AC_CONFIG_SRCDIR(README)
AM_INIT_AUTOMAKE
AC_CONFIG_HEADERS([config.h])
@@ -14,18 +14,22 @@ AC_PROG_CXX
#
# On FreeBSD (and probably some others), clang++ does not meet an autoconf
# assumption in identifying libtool configuration regarding shared library:
-# the configure script will execute "$CC -shared $CFLAGS -v -o" and expect
-# the output contains -Lxxx or -Ryyy. This is the case for g++, but not for
-# clang++, and, as a result, it will cause various errors in linking programs
-# or running them with a shared object (such as some of our python scripts).
+# the configure script will execute "$CC -shared $CFLAGS/$CXXFLAGS -v" and
+# expect the output contains -Lxxx or -Ryyy. This is the case for g++, but
+# not for clang++, and, as a result, it will cause various errors in linking
+# programs or running them with a shared object (such as some of our python
+# scripts).
# To work around this problem we define a temporary variable
# "CXX_LIBTOOL_LDFLAGS". It's expected to be defined as, e.g, "-L/usr/lib"
# to temporarily fake the output so that it will be compatible with that of
# g++.
CFLAGS_SAVED=$CFLAGS
+CXXFLAGS_SAVED=$CXXFLAGS
CFLAGS="$CFLAGS $CXX_LIBTOOL_LDFLAGS"
+CXXFLAGS="$CXXFLAGS $CXX_LIBTOOL_LDFLAGS"
AC_PROG_LIBTOOL
CFLAGS=$CFLAGS_SAVED
+CXXFLAGS=$CXXFLAGS_SAVED
# Use C++ language
AC_LANG([C++])
@@ -66,6 +70,11 @@ if test $enable_shared = no; then
AC_MSG_ERROR([BIND 10 requires shared libraries to be built])
fi
+AC_ARG_ENABLE(boost-threads,
+AC_HELP_STRING([--enable-boost-threads],
+ [use boost threads. Currently this only means using its locks instead of dummy locks, in the cache and NSAS]),
+ use_boost_threads=$enableval, use_boost_threads=no)
+
# allow configuring without setproctitle.
AC_ARG_ENABLE(setproctitle-check,
AC_HELP_STRING([--disable-setproctitle-check],
@@ -193,8 +202,9 @@ if test "$setproctitle_check" = "yes" ; then
AC_MSG_RESULT(ok)
else
AC_MSG_RESULT(missing)
- AC_MSG_ERROR([Missing setproctitle module. Either install it or provide --disable-setproctitle-check.
-In that case we will continue, but naming of python processes will not work.])
+ AC_MSG_WARN([Missing setproctitle python module.
+Use --disable-setproctitle-check to skip this check.
+In this case we will continue, but naming of python processes will not work.])
fi
fi
@@ -285,6 +295,7 @@ AC_SUBST(B10_CXXFLAGS)
AC_SEARCH_LIBS(inet_pton, [nsl])
AC_SEARCH_LIBS(recvfrom, [socket])
+AC_SEARCH_LIBS(nanosleep, [rt])
# Checks for header files.
@@ -363,57 +374,6 @@ if test "$lcov" != "no"; then
fi
AC_SUBST(USE_LCOV)
-# Configure log4cxx header and library path
-#
-# If explicitly specified, use it.
-
-AC_ARG_WITH([log4cxx],
- AC_HELP_STRING([--with-log4cxx=PATH],
- [specify directory where log4cxx is installed]),
- [
- log4cxx_include_path="${withval}/include";
- log4cxx_library_path="${withval}/lib"
- ])
-
-# This is an urgent fix to avoid regression due to log4cxx on some
-# platforms. It should be cleaned up with a better fix.
-if test "X$with_log4cxx" != "Xno"; then
-
-# If not specified, try some common paths. These default to
-# /usr/include and /usr/lib if not found
-
-if test -z "$with_log4cxx"; then
- log4cxxdirs="/usr/local /usr/pkg /opt /opt/local"
- for d in $log4cxxdirs
- do
- if test -d $d/include/log4cxx; then
- log4cxx_include_path=$d/include
- log4cxx_library_path=$d/lib
- break
- fi
- done
-fi
-
-CPPFLAGS_SAVES="$CPPFLAGS"
-if test "${log4cxx_include_path}" ; then
- LOG4CXX_INCLUDES="-I${log4cxx_include_path}"
- CPPFLAGS="$CPPFLAGS $LOG4CXX_INCLUDES"
-fi
-AC_CHECK_HEADER([log4cxx/logger.h],, AC_MSG_ERROR([Missing log4cxx header files.]))
-CPPFLAGS="$CPPFLAGS_SAVES"
-AC_SUBST(LOG4CXX_INCLUDES)
-
-LOG4CXX_LDFLAGS="-llog4cxx";
-if test "${log4cxx_library_path}"; then
- LOG4CXX_LDFLAGS="-L${log4cxx_library_path} -llog4cxx"
-fi
-AC_SUBST(LOG4CXX_LDFLAGS)
-
-# The following two lines are part of the urgent fix, and should be cleaned
-# up with a better fix.
-fi
-AM_CONDITIONAL(USE_LOG4CXX, test "X${with_log4cxx}" != "Xno")
-
#
# Configure Boost header path
#
@@ -443,62 +403,69 @@ AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync
CPPFLAGS="$CPPFLAGS_SAVES"
AC_SUBST(BOOST_INCLUDES)
-# 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])
+
+if test "${use_boost_threads}" = "yes" ; then
+ AC_DEFINE([USE_BOOST_THREADS], [], [Use boost threads])
+
+ # Using boost::mutex can result in requiring libboost_thread with older
+ # versions of Boost. We'd like to avoid relying on a compiled Boost library
+ # whenever possible, so we need to check for it step by step.
+ #
+ # NOTE: another fix of this problem is to simply require newer versions of
+ # boost. If we choose that solution we should simplify the following tricky
+ # checks accordingly and all Makefile.am's that refer to NEED_LIBBOOST_THREAD.
+ AC_MSG_CHECKING(for boost::mutex)
+ CPPFLAGS_SAVES="$CPPFLAGS"
+ LIBS_SAVES="$LIBS"
+ CPPFLAGS="$BOOST_INCLUDES $CPPFLAGS $MULTITHREADING_FLAG"
+ need_libboost_thread=0
+ need_sunpro_workaround=0
+ AC_TRY_LINK([
+ #include <boost/thread.hpp>
+ ],[
+ boost::mutex m;
+ ],
+ [ AC_MSG_RESULT(yes (without libboost_thread)) ],
+ # there is one specific problem with SunStudio 5.10
+ # where including boost/thread causes a compilation failure
+ # There is a workaround in boost but it checks the version not being 5.10
+ # This will probably be fixed in the future, in which case this
+ # is only a temporary workaround
+ [ AC_TRY_LINK([
+ #if defined(__SUNPRO_CC) && __SUNPRO_CC == 0x5100
+ #undef __SUNPRO_CC
+ #define __SUNPRO_CC 0x5090
+ #endif
+ #include <boost/thread.hpp>
+ ],[
+ boost::mutex m;
+ ],
+ [ AC_MSG_RESULT(yes (with SUNOS workaround))
+ need_sunpro_workaround=1 ],
+ [ LIBS=" $LIBS -lboost_thread"
+ AC_TRY_LINK([
+ #include <boost/thread.hpp>
+ ],[
+ boost::mutex m;
+ ],
+ [ AC_MSG_RESULT(yes (with libboost_thread))
+ need_libboost_thread=1 ],
+ [ AC_MSG_RESULT(no)
+ AC_MSG_ERROR([boost::mutex cannot be linked in this build environment.
+ Perhaps you are using an older version of Boost that requires libboost_thread for the mutex support, which does not appear to be available.
+ You may want to check the availability of the library or to upgrade Boost.])
+ ])])])
+ CPPFLAGS="$CPPFLAGS_SAVES"
+ LIBS="$LIBS_SAVES"
+ AM_CONDITIONAL(NEED_LIBBOOST_THREAD, test $need_libboost_thread = 1)
+ if test $need_sunpro_workaround = 1; then
+ AC_DEFINE([NEED_SUNPRO_WORKAROUND], [], [Need boost sunstudio workaround])
+ fi
+else
+ AM_CONDITIONAL(NEED_LIBBOOST_THREAD, test "${use_boost_threads}" = "yes")
fi
+
#
# Check availability of gtest, which will be used for unit tests.
#
@@ -573,6 +540,9 @@ AC_SUBST(MULTITHREADING_FLAG)
#
CPPFLAGS="$CPPFLAGS -I\$(top_srcdir)/ext/asio"
#
+# Use our 'coroutine' header from ext
+CPPFLAGS="$CPPFLAGS -I\$(top_srcdir)/ext/coroutine"
+#
# Disable threads: Currently we don't use them.
CPPFLAGS="$CPPFLAGS -DASIO_DISABLE_THREADS=1"
#
@@ -613,6 +583,12 @@ if test "X$ac_cv_have_devpoll" = "Xyes" -a "X$GXX" = "Xyes"; then
CPPFLAGS="$CPPFLAGS -DASIO_DISABLE_DEV_POLL=1"
fi
+#
+# Perl is optional; it is used only by some of the system test scripts.
+#
+AC_PATH_PROGS(PERL, perl5 perl)
+AC_SUBST(PERL)
+
AC_ARG_ENABLE(man, [AC_HELP_STRING([--enable-man],
[regenerate man pages [default=no]])], enable_man=yes, enable_man=no)
@@ -666,8 +642,6 @@ AC_CONFIG_FILES([Makefile
src/lib/Makefile
src/lib/asiolink/Makefile
src/lib/asiolink/tests/Makefile
- src/lib/asiolink/internal/Makefile
- src/lib/asiolink/internal/tests/Makefile
src/lib/bench/Makefile
src/lib/bench/example/Makefile
src/lib/bench/tests/Makefile
@@ -711,6 +685,12 @@ AC_CONFIG_FILES([Makefile
src/lib/testutils/testdata/Makefile
src/lib/nsas/Makefile
src/lib/nsas/tests/Makefile
+ src/lib/cache/Makefile
+ src/lib/cache/tests/Makefile
+ src/lib/server_common/Makefile
+ src/lib/server_common/tests/Makefile
+ tests/Makefile
+ tests/system/Makefile
])
AC_OUTPUT([doc/version.ent
src/bin/cfgmgr/b10-cfgmgr.py
@@ -740,9 +720,10 @@ AC_OUTPUT([doc/version.ent
src/bin/stats/tests/stats_test
src/bin/bind10/bind10.py
src/bin/bind10/tests/bind10_test
+ src/bin/bind10/tests/bind10_test.py
src/bin/bind10/run_bind10.sh
src/bin/bindctl/run_bindctl.sh
- src/bin/bindctl/bindctl-source.py
+ src/bin/bindctl/bindctl_main.py
src/bin/bindctl/tests/bindctl_test
src/bin/loadzone/run_loadzone.sh
src/bin/loadzone/tests/correct/correct_test.sh
@@ -767,6 +748,10 @@ AC_OUTPUT([doc/version.ent
src/lib/cc/session_config.h.pre
src/lib/cc/tests/session_unittests_config.h
src/lib/log/tests/run_time_init_test.sh
+ tests/system/conf.sh
+ tests/system/glue/setup.sh
+ tests/system/glue/nsx1/b10-config.db
+ tests/system/bindctl/nsx1/b10-config.db.template
], [
chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
chmod +x src/bin/xfrin/run_b10-xfrin.sh
@@ -791,6 +776,7 @@ AC_OUTPUT([doc/version.ent
chmod +x src/lib/dns/gen-rdatacode.py
chmod +x src/lib/dns/tests/testdata/gen-wiredata.py
chmod +x src/lib/log/tests/run_time_init_test.sh
+ chmod +x tests/system/conf.sh
])
AC_OUTPUT
@@ -818,8 +804,6 @@ dnl includes too
${PYTHON_LDFLAGS}
${PYTHON_LIB}
Boost: ${BOOST_INCLUDES}
- log4cxx: ${LOG4CXX_INCLUDES}
- ${LOG4CXX_LDFLAGS}
SQLite: $SQLITE_CFLAGS
$SQLITE_LIBS
diff --git a/doc/Doxyfile b/doc/Doxyfile
index 6ab2001..46aa178 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -568,7 +568,7 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
-INPUT = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/bin/resolver ../src/lib/bench ../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas ../src/lib/testutils
+INPUT = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/bin/resolver ../src/lib/bench ../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
diff --git a/doc/guide/bind10-guide.html b/doc/guide/bind10-guide.html
index 98c7e46..fe6bd93 100644
--- a/doc/guide/bind10-guide.html
+++ b/doc/guide/bind10-guide.html
@@ -1,23 +1,24 @@
-<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>BIND 10 Guide</title><link rel="stylesheet" href="./bind10-guide.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.75.2"><meta name="description" content="BIND 10 is a Domain Name System (DNS) suite managed by Internet Systems Consortium (ISC). It includes DNS libraries and modular components for controlling authoritative and recursive DNS servers. This is the reference guide for BIND 10 version 20110120. The most up-to-date version of this document, along with other documents for BIND 10, can be found at ."></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="book" title="BIND 10 Guide"><div class="titlepage"><div><div><h1 class="title"><a name="id1168230298903"></a>BIND 10 Guide</h1></div><div><h2 class="subtitle">Administrator Reference for BIND 10</h2></div><div><p class="releaseinfo">This is the referenc
e guide for BIND 10 version
- 20110120.</p></div><div><p class="copyright">Copyright © 2010 Internet Systems Consortium, Inc.</p></div><div><div class="abstract" title="Abstract"><p class="title"><b>Abstract</b></p><p>BIND 10 is a Domain Name System (DNS) suite managed by
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>BIND 10 Guide</title><link rel="stylesheet" href="./bind10-guide.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.75.2"><meta name="description" content="BIND 10 is a Domain Name System (DNS) suite managed by Internet Systems Consortium (ISC). It includes DNS libraries and modular components for controlling authoritative and recursive DNS servers. This is the reference guide for BIND 10 version 20110224. The most up-to-date version of this document, along with other documents for BIND 10, can be found at ."></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="book" title="BIND 10 Guide"><div class="titlepage"><div><div><h1 class="title"><a name="id1168230298903"></a>BIND 10 Guide</h1></div><div><h2 class="subtitle">Administrator Reference for BIND 10</h2></div><div><p class="releaseinfo">This is the referenc
e guide for BIND 10 version
+ 20110224.</p></div><div><p class="copyright">Copyright © 2010 Internet Systems Consortium, Inc.</p></div><div><div class="abstract" title="Abstract"><p class="title"><b>Abstract</b></p><p>BIND 10 is a Domain Name System (DNS) suite managed by
Internet Systems Consortium (ISC). It includes DNS libraries
and modular components for controlling authoritative and
recursive DNS servers.
</p><p>
- This is the reference guide for BIND 10 version 20110120.
+ This is the reference guide for BIND 10 version 20110224.
The most up-to-date version of this document, along with
- other documents for BIND 10, can be found at <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="#id1168230299042">Supported Platforms</a></span></dt><dt><span class="section"><a href="#id1168230299068">Required Software</a></span></dt><dt><span class="section"><a href="#starting_stopping">Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">Managing BIND 10</a></span></dt></dl></dd><dt><span class="chapter"><a href="#installation">2. Installation</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230284843">Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">Quick start</a></span></dt><dt><span class="section"><a href="#install">In
stallation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285029">Download Tar File</a></span></dt><dt><span class="section"><a href="#id1168230285048">Retrieve from Git</a></span></dt><dt><span class="section"><a href="#id1168230285109">Configure before the build</a></span></dt><dt><span class="section"><a href="#id1168230285206">Build</a></span></dt><dt><span class="section"><a href="#id1168230285222">Install</a></span></dt><dt><span class="section"><a href="#id1168230285245">Install Hierarchy</a></span></dt></dl></dd></dl></dd><dt><span class="chapter"><a href="#bind10">3. Starting BIND10 with <span class="command"><strong>bind10</strong></span></a></span></dt><dd><dl><dt><span class="section"><a href="#start">Starting BIND 10</a></span></dt></dl></dd><dt><span class="chapter"><a href="#msgq">4. Command channel</a></span></dt><dt><span class="chapter"><a href="#cfgmgr">5. Configuration manager</a></span></dt><dt><span class="chapter"><a hr
ef="#cmdctl">6. Remote control daemon</a></span></dt><dd><dl><dt><span class="section"><a href="#cmdctl.spec">Configuration specification for b10-cmdctl</a></span></dt></dl></dd><dt><span class="chapter"><a href="#bindctl">7. Control and configure user interface</a></span></dt><dt><span class="chapter"><a href="#authserver">8. Authoritative Server</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285821">Server Configurations</a></span></dt><dt><span class="section"><a href="#id1168230285886">Data Source Backends</a></span></dt><dt><span class="section"><a href="#id1168230285917">Loading Master Zones Files</a></span></dt></dl></dd><dt><span class="chapter"><a href="#xfrin">9. Incoming Zone Transfers</a></span></dt><dt><span class="chapter"><a href="#xfrout">10. Outbound Zone Transfers</a></span></dt><dt><span class="chapter"><a href="#zonemgr">11. Secondary Manager</a></span></dt><dt><span class="chapter"><a href="#resolverserver">12. Recursive Name Server<
/a></span></dt><dt><span class="chapter"><a href="#statistics">13. Statistics</a></span></dt></dl></div><div class="chapter" title="Chapter 1. Introduction"><div class="titlepage"><div><div><h2 class="title"><a name="intro"></a>Chapter 1. Introduction</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230299042">Supported Platforms</a></span></dt><dt><span class="section"><a href="#id1168230299068">Required Software</a></span></dt><dt><span class="section"><a href="#starting_stopping">Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">Managing BIND 10</a></span></dt></dl></div><p>
+ other documents for BIND 10, can be found at <a class="ulink" href="http://bind10.isc.org/docs" target="_top">http://bind10.isc.org/docs</a>. </p></div></div></div><hr></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="chapter"><a href="#intro">1. Introduction</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230299038">Supported Platforms</a></span></dt><dt><span class="section"><a href="#id1168230299065">Required Software</a></span></dt><dt><span class="section"><a href="#starting_stopping">Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">Managing BIND 10</a></span></dt></dl></dd><dt><span class="chapter"><a href="#installation">2. Installation</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230284842">Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">Quick start</a></span></dt><dt><span class="section"><a href="#install">In
stallation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285028">Download Tar File</a></span></dt><dt><span class="section"><a href="#id1168230285047">Retrieve from Git</a></span></dt><dt><span class="section"><a href="#id1168230285108">Configure before the build</a></span></dt><dt><span class="section"><a href="#id1168230285205">Build</a></span></dt><dt><span class="section"><a href="#id1168230285221">Install</a></span></dt><dt><span class="section"><a href="#id1168230285244">Install Hierarchy</a></span></dt></dl></dd></dl></dd><dt><span class="chapter"><a href="#bind10">3. Starting BIND10 with <span class="command"><strong>bind10</strong></span></a></span></dt><dd><dl><dt><span class="section"><a href="#start">Starting BIND 10</a></span></dt></dl></dd><dt><span class="chapter"><a href="#msgq">4. Command channel</a></span></dt><dt><span class="chapter"><a href="#cfgmgr">5. Configuration manager</a></span></dt><dt><span class="chapter"><a hr
ef="#cmdctl">6. Remote control daemon</a></span></dt><dd><dl><dt><span class="section"><a href="#cmdctl.spec">Configuration specification for b10-cmdctl</a></span></dt></dl></dd><dt><span class="chapter"><a href="#bindctl">7. Control and configure user interface</a></span></dt><dt><span class="chapter"><a href="#authserver">8. Authoritative Server</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285822">Server Configurations</a></span></dt><dt><span class="section"><a href="#id1168230285888">Data Source Backends</a></span></dt><dt><span class="section"><a href="#id1168230285918">Loading Master Zones Files</a></span></dt></dl></dd><dt><span class="chapter"><a href="#xfrin">9. Incoming Zone Transfers</a></span></dt><dt><span class="chapter"><a href="#xfrout">10. Outbound Zone Transfers</a></span></dt><dt><span class="chapter"><a href="#zonemgr">11. Secondary Manager</a></span></dt><dt><span class="chapter"><a href="#resolverserver">12. Recursive Name Server<
/a></span></dt><dt><span class="chapter"><a href="#statistics">13. Statistics</a></span></dt></dl></div><div class="chapter" title="Chapter 1. Introduction"><div class="titlepage"><div><div><h2 class="title"><a name="intro"></a>Chapter 1. Introduction</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230299038">Supported Platforms</a></span></dt><dt><span class="section"><a href="#id1168230299065">Required Software</a></span></dt><dt><span class="section"><a href="#starting_stopping">Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">Managing BIND 10</a></span></dt></dl></div><p>
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 20110120.
+ BIND 10 version 20110224.
</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 forwarding DNS server.
- </p></div><div class="section" title="Supported Platforms"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230299042"></a>Supported Platforms</h2></div></div></div><p>
+ authoritative DNS server and a caching recursive name server
+ which also provides forwarding.
+ </p></div><div class="section" title="Supported Platforms"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230299038"></a>Supported Platforms</h2></div></div></div><p>
BIND 10 builds have been tested on Debian GNU/Linux 5,
Ubuntu 9.10, NetBSD 5, Solaris 10, FreeBSD 7 and 8, and CentOS
Linux 5.3.
@@ -27,13 +28,11 @@
It is planned for BIND 10 to build, install and run on
Windows and standard Unix-type platforms.
- </p></div><div class="section" title="Required Software"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230299068"></a>Required Software</h2></div></div></div><p>
+ </p></div><div class="section" title="Required Software"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230299065"></a>Required Software</h2></div></div></div><p>
BIND 10 requires Python 3.1. Later versions may work, but Python
3.1 is the minimum version which will work.
</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
- For this development prototype release, the only supported
- data source backend is SQLite3. The authoritative server
- requires SQLite 3.3.9 or newer.
+ The authoritative server requires SQLite 3.3.9 or newer.
The <span class="command"><strong>b10-xfrin</strong></span>, <span class="command"><strong>b10-xfrout</strong></span>,
and <span class="command"><strong>b10-zonemgr</strong></span> modules require the
libpython3 library and the Python _sqlite3.so module.
@@ -133,7 +132,7 @@
and, of course, DNS. These include detailed developer
documentation and code examples.
- </p></div><div class="chapter" title="Chapter 2. Installation"><div class="titlepage"><div><div><h2 class="title"><a name="installation"></a>Chapter 2. Installation</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230284843">Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">Quick start</a></span></dt><dt><span class="section"><a href="#install">Installation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285029">Download Tar File</a></span></dt><dt><span class="section"><a href="#id1168230285048">Retrieve from Git</a></span></dt><dt><span class="section"><a href="#id1168230285109">Configure before the build</a></span></dt><dt><span class="section"><a href="#id1168230285206">Build</a></span></dt><dt><span class="section"><a href="#id1168230285222">Install</a></span></dt><dt><span class="section"><a href="#id1168230285245">Install Hierarchy<
/a></span></dt></dl></dd></dl></div><div class="section" title="Building Requirements"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230284843"></a>Building Requirements</h2></div></div></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
+ </p></div><div class="chapter" title="Chapter 2. Installation"><div class="titlepage"><div><div><h2 class="title"><a name="installation"></a>Chapter 2. Installation</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230284842">Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">Quick start</a></span></dt><dt><span class="section"><a href="#install">Installation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285028">Download Tar File</a></span></dt><dt><span class="section"><a href="#id1168230285047">Retrieve from Git</a></span></dt><dt><span class="section"><a href="#id1168230285108">Configure before the build</a></span></dt><dt><span class="section"><a href="#id1168230285205">Build</a></span></dt><dt><span class="section"><a href="#id1168230285221">Install</a></span></dt><dt><span class="section"><a href="#id1168230285244">Install Hierarchy<
/a></span></dt></dl></dd></dl></div><div class="section" title="Building Requirements"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230284842"></a>Building Requirements</h2></div></div></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
Some operating systems have split their distribution packages into
a run-time and a development package. You will need to install
the development package versions, which include header files and
@@ -193,14 +192,14 @@
the Git code revision control system or as a downloadable
tar file. It may also be available in pre-compiled ready-to-use
packages from operating system vendors.
- </p><div class="section" title="Download Tar File"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285029"></a>Download Tar File</h3></div></div></div><p>
+ </p><div class="section" title="Download Tar File"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285028"></a>Download Tar File</h3></div></div></div><p>
Downloading a release tar file is the recommended method to
obtain the source code.
</p><p>
The BIND 10 releases are available as tar file downloads from
<a class="ulink" href="ftp://ftp.isc.org/isc/bind10/" target="_top">ftp://ftp.isc.org/isc/bind10/</a>.
Periodic development snapshots may also be available.
- </p></div><div class="section" title="Retrieve from Git"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285048"></a>Retrieve from Git</h3></div></div></div><p>
+ </p></div><div class="section" title="Retrieve from Git"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285047"></a>Retrieve from Git</h3></div></div></div><p>
Downloading this "bleeding edge" code is recommended only for
developers or advanced users. Using development code in a production
environment is not recommended.
@@ -234,7 +233,7 @@
<span class="command"><strong>autoheader</strong></span>,
<span class="command"><strong>automake</strong></span>,
and related commands.
- </p></div><div class="section" title="Configure before the build"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285109"></a>Configure before the build</h3></div></div></div><p>
+ </p></div><div class="section" title="Configure before the build"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285108"></a>Configure before the build</h3></div></div></div><p>
BIND 10 uses the GNU Build System to discover build environment
details.
To generate the makefiles using the defaults, simply run:
@@ -265,16 +264,16 @@
</p><p>
If the configure fails, it may be due to missing or old
dependencies.
- </p></div><div class="section" title="Build"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285206"></a>Build</h3></div></div></div><p>
+ </p></div><div class="section" title="Build"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285205"></a>Build</h3></div></div></div><p>
After the configure step is complete, to build the executables
from the C++ code and prepare the Python scripts, run:
</p><pre class="screen">$ <strong class="userinput"><code>make</code></strong></pre><p>
- </p></div><div class="section" title="Install"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285222"></a>Install</h3></div></div></div><p>
+ </p></div><div class="section" title="Install"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285221"></a>Install</h3></div></div></div><p>
To install the BIND 10 executables, support files,
and documentation, run:
</p><pre class="screen">$ <strong class="userinput"><code>make install</code></strong></pre><p>
- </p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>The install step may require superuser privileges.</p></div></div><div class="section" title="Install Hierarchy"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285245"></a>Install Hierarchy</h3></div></div></div><p>
+ </p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>The install step may require superuser privileges.</p></div></div><div class="section" title="Install Hierarchy"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285244"></a>Install Hierarchy</h3></div></div></div><p>
The following is the layout of the complete BIND 10 installation:
</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem">
<code class="filename">bin/</code> —
@@ -465,6 +464,8 @@ accounts_file
</p><p>
The control commands are:
print_settings
+
+
shutdown
</p></div></div><div class="chapter" title="Chapter 7. Control and configure user interface"><div class="titlepage"><div><div><h2 class="title"><a name="bindctl"></a>Chapter 7. Control and configure user interface</h2></div></div></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
For this development prototype release, <span class="command"><strong>bindctl</strong></span>
@@ -489,7 +490,7 @@ 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="#id1168230285821">Server Configurations</a></span></dt><dt><span class="section"><a href="#id1168230285886">Data Source Backends</a></span></dt><dt><span class="section"><a href="#id1168230285917">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="#id1168230285822">Server Configurations</a></span></dt><dt><span class="section"><a href="#id1168230285888">Data Source Backends</a></span></dt><dt><span class="section"><a href="#id1168230285918">Loading Master Zones Files</a></span></dt></dl></div><p>
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
@@ -497,7 +498,7 @@ shutdown
</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
This development prototype release listens on all interfaces
and the non-standard port 5300.
- </p></div><div class="section" title="Server Configurations"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285821"></a>Server Configurations</h2></div></div></div><p>
+ </p></div><div class="section" title="Server Configurations"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285822"></a>Server Configurations</h2></div></div></div><p>
<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>.
@@ -517,11 +518,12 @@ This may be a temporary setting until then.
</p><div class="variablelist"><dl><dt><span class="term">shutdown</span></dt><dd>Stop the authoritative DNS server.
</dd></dl></div><p>
- </p></div><div class="section" title="Data Source Backends"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285886"></a>Data Source Backends</h2></div></div></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
+ </p></div><div class="section" title="Data Source Backends"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285888"></a>Data Source Backends</h2></div></div></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
For the development prototype release, <span class="command"><strong>b10-auth</strong></span>
- only supports the SQLite3 data source backend.
+ supports a SQLite3 data source backend and in-memory data source
+ backend.
Upcoming versions will be able to use multiple different
- data sources, such as MySQL, Berkeley DB, or in-memory DB.
+ data sources, such as MySQL and Berkeley DB.
</p></div><p>
By default, the SQLite3 backend uses the data file located at
<code class="filename">/usr/local/var/bind10-devel/zone.sqlite3</code>.
@@ -530,7 +532,7 @@ This may be a temporary setting until then.
The default is <code class="filename">/usr/local/var/</code>.)
This data file location may be changed by defining the
<span class="quote">“<span class="quote">database_file</span>”</span> configuration.
- </p></div><div class="section" title="Loading Master Zones Files"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285917"></a>Loading Master Zones Files</h2></div></div></div><p>
+ </p></div><div class="section" title="Loading Master Zones Files"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285918"></a>Loading Master Zones Files</h2></div></div></div><p>
RFC 1035 style DNS master zone files may imported
into a BIND 10 data source by using the
<span class="command"><strong>b10-loadzone</strong></span> utility.
@@ -610,11 +612,7 @@ This may be a temporary setting until then.
The <span class="command"><strong>b10-resolver</strong></span> process is started by
<span class="command"><strong>bind10</strong></span>.
- </p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
- The current version only provides a forwarding DNS server.
- It does not cache and does not iterate to find answers.
- It simply forwards the query on to another full resolver.
- </p></div><p>
+ </p><p>
The main <span class="command"><strong>bind10</strong></span> process can be configured
to select to run either the authoritative or resolver.
By default, it starts the authoritative service.
@@ -628,12 +626,20 @@ This may be a temporary setting until then.
> <strong class="userinput"><code>config commit</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>
- In the current version, the master <span class="command"><strong>bind10</strong></span>
- process must be stopped and restarted to start up the resolver.
- </p></div><p>
- Then the upstream address and port must be configured to
- forward queries to, such as:
+ </p><p>
+ The master <span class="command"><strong>bind10</strong></span> will stop and start
+ the desired services.
+ </p><p>
+ The resolver also needs to be configured to listen on an address
+ and port:
+
+ </p><pre class="screen">
+> <strong class="userinput"><code>config set Resolver/listen_on [{ "address": "127.0.0.1", "port": 53 }]</code></strong>
+> <strong class="userinput"><code>config commit</code></strong>
+</pre><p>
+ </p><p>
+ To enable forwarding, the upstream address and port must be
+ configured to forward queries to, such as:
</p><pre class="screen">
> <strong class="userinput"><code>config set Resolver/forward_addresses [{ "address": "<em class="replaceable"><code>192.168.1.1</code></em>", "port": 53 }]</code></strong>
@@ -643,11 +649,11 @@ This may be a temporary setting until then.
(Replace <em class="replaceable"><code>192.168.1.1</code></em> to point to your
full resolver.)
</p><p>
- The resolver also needs to be configured to listen on an address
- and port:
+ Normal iterative name service can be re-enabled by clearing the
+ forwarding address(es); for example:
</p><pre class="screen">
-> <strong class="userinput"><code>config set Resolver/listen_on [{ "address": "127.0.0.1", "port": 53 }]</code></strong>
+> <strong class="userinput"><code>config set Resolver/forward_addresses []</code></strong>
> <strong class="userinput"><code>config commit</code></strong>
</pre><p>
</p></div><div class="chapter" title="Chapter 13. Statistics"><div class="titlepage"><div><div><h2 class="title"><a name="statistics"></a>Chapter 13. Statistics</h2></div></div></div><p>
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 3670c46..bceb40c 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -52,7 +52,8 @@
<note>
<para>
BIND 10 provides a EDNS0- and DNSSEC-capable
- authoritative DNS server and a forwarding DNS server.
+ authoritative DNS server and a caching recursive name server
+ which also provides forwarding.
</para>
</note>
@@ -79,9 +80,7 @@
</para>
<note><para>
- For this development prototype release, the only supported
- data source backend is SQLite3. The authoritative server
- requires SQLite 3.3.9 or newer.
+ The authoritative server requires SQLite 3.3.9 or newer.
The <command>b10-xfrin</command>, <command>b10-xfrout</command>,
and <command>b10-zonemgr</command> modules require the
libpython3 library and the Python _sqlite3.so module.
@@ -337,14 +336,6 @@ var/
</simpara>
</note>
- <note>
- <simpara>
- The development prototype of the b10-auth server listens on
- 0.0.0.0 (all interfaces) port 5300. (This is not the standard
- domain service port.)
- </simpara>
- </note>
-
<para>
To quickly get started with BIND 10, follow these steps.
</para>
@@ -398,7 +389,7 @@ var/
<listitem>
<para>Test it; for example:
- <screen>$ <userinput>dig @127.0.0.1 -p 5300 -c CH -t TXT authors.bind</userinput></screen>
+ <screen>$ <userinput>dig @127.0.0.1 -c CH -t TXT authors.bind</userinput></screen>
</para>
</listitem>
@@ -1045,11 +1036,6 @@ TODO
process.
</para>
- <note><simpara>
- This development prototype release listens on all interfaces
- and the non-standard port 5300.
- </simpara></note>
-
<section>
<title>Server Configurations</title>
@@ -1108,9 +1094,10 @@ This may be a temporary setting until then.
<note><para>
For the development prototype release, <command>b10-auth</command>
- only supports the SQLite3 data source backend.
+ supports a SQLite3 data source backend and in-memory data source
+ backend.
Upcoming versions will be able to use multiple different
- data sources, such as MySQL, Berkeley DB, or in-memory DB.
+ data sources, such as MySQL and Berkeley DB.
</para></note>
@@ -1309,12 +1296,6 @@ what is XfroutClient xfr_client??
-->
</para>
- <note><simpara>
- The current version only provides a forwarding DNS server.
- It does not cache and does not iterate to find answers.
- It simply forwards the query on to another full resolver.
- </simpara></note>
-
<para>
The main <command>bind10</command> process can be configured
to select to run either the authoritative or resolver.
@@ -1331,15 +1312,26 @@ what is XfroutClient xfr_client??
</para>
-<!-- TODO: -->
- <note><simpara>
- In the current version, the master <command>bind10</command>
- process must be stopped and restarted to start up the resolver.
- </simpara></note>
+ <para>
+ The master <command>bind10</command> will stop and start
+ the desired services.
+ </para>
<para>
- Then the upstream address and port must be configured to
- forward queries to, such as:
+ The resolver also needs to be configured to listen on an address
+ and port:
+
+ <screen>
+> <userinput>config set Resolver/listen_on [{ "address": "127.0.0.1", "port": 53 }]</userinput>
+> <userinput>config commit</userinput>
+</screen>
+ </para>
+
+<!-- TODO: later the above will have some defaults -->
+
+ <para>
+ To enable forwarding, the upstream address and port must be
+ configured to forward queries to, such as:
<screen>
> <userinput>config set Resolver/forward_addresses [{ "address": "<replaceable>192.168.1.1</replaceable>", "port": 53 }]</userinput>
@@ -1351,17 +1343,15 @@ what is XfroutClient xfr_client??
</para>
<para>
- The resolver also needs to be configured to listen on an address
- and port:
+ Normal iterative name service can be re-enabled by clearing the
+ forwarding address(es); for example:
<screen>
-> <userinput>config set Resolver/listen_on [{ "address": "127.0.0.1", "port": 53 }]</userinput>
+> <userinput>config set Resolver/forward_addresses []</userinput>
> <userinput>config commit</userinput>
</screen>
</para>
-<!-- TODO: later the above will have some defaults -->
-
<!-- TODO: later try this
> config set Resolver/forward_addresses[0]/address "192.168.8.8"
diff --git a/ext/asio/asio/detail/epoll_reactor.hpp b/ext/asio/asio/detail/epoll_reactor.hpp
index 6367398..0a1eac0 100644
--- a/ext/asio/asio/detail/epoll_reactor.hpp
+++ b/ext/asio/asio/detail/epoll_reactor.hpp
@@ -207,7 +207,7 @@ public:
// Cancel all operations associated with the given descriptor. The
// handlers associated with the descriptor will be invoked with the
// operation_aborted error.
- void cancel_ops(socket_type descriptor, per_descriptor_data& descriptor_data)
+ void cancel_ops(socket_type, per_descriptor_data& descriptor_data)
{
mutex::scoped_lock descriptor_lock(descriptor_data->mutex_);
diff --git a/ext/asio/asio/detail/kqueue_reactor.hpp b/ext/asio/asio/detail/kqueue_reactor.hpp
index 1e118b3..bfa004d 100644
--- a/ext/asio/asio/detail/kqueue_reactor.hpp
+++ b/ext/asio/asio/detail/kqueue_reactor.hpp
@@ -205,7 +205,7 @@ public:
// Cancel all operations associated with the given descriptor. The
// handlers associated with the descriptor will be invoked with the
// operation_aborted error.
- void cancel_ops(socket_type descriptor, per_descriptor_data& descriptor_data)
+ void cancel_ops(socket_type , per_descriptor_data& descriptor_data)
{
mutex::scoped_lock descriptor_lock(descriptor_data->mutex_);
diff --git a/ext/asio/asio/detail/null_thread.hpp b/ext/asio/asio/detail/null_thread.hpp
index d96883f..ce3d470 100644
--- a/ext/asio/asio/detail/null_thread.hpp
+++ b/ext/asio/asio/detail/null_thread.hpp
@@ -40,7 +40,7 @@ class null_thread
public:
// Constructor.
template <typename Function>
- null_thread(Function f)
+ null_thread(Function )
{
asio::system_error e(
asio::error::operation_not_supported, "thread");
diff --git a/ext/coroutine/coroutine.h b/ext/coroutine/coroutine.h
new file mode 100644
index 0000000..985888b
--- /dev/null
+++ b/ext/coroutine/coroutine.h
@@ -0,0 +1,133 @@
+//
+// coroutine.h
+// ~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef COROUTINE_HPP
+#define COROUTINE_HPP
+
+
+// \brief Coroutine object
+//
+// A coroutine object maintains the state of a re-enterable routine. It
+// is assignable and copy-constructable, and can be used as a base class
+// for a class that uses it, or as a data member. The copy overhead is
+// a single int.
+//
+// A reenterable function contains a CORO_REENTER (coroutine) { ... }
+// block. Whenever an asychrnonous operation is initiated within the
+// routine, the function is provided as the handler object. (The simplest
+// way to do this is to have the reenterable function be the operator()
+// member for the coroutine object itself.) For example:
+//
+// CORO_YIELD socket->async_read_some(buffer, *this);
+//
+// The CORO_YIELD keyword updates the current status of the coroutine to
+// indicate the line number currently being executed. The
+// async_read_some() call is initiated, with a copy of the updated
+// corotutine as its handler object, and the current coroutine exits. When
+// the async_read_some() call finishes, the copied coroutine will be
+// called, and will resume processing exactly where the original one left
+// off--right after asynchronous call. This allows asynchronous I/O
+// routines to be written with a logical flow, step following step, rather
+// than as a linked chain of separate handler functions.
+//
+// When necessary, a coroutine can fork itself using the CORO_FORK keyword.
+// This updates the status of the coroutine and makes a copy. The copy can
+// then be called directly or posted to the ASIO service queue so that both
+// coroutines will continue forward, one "parent" and one "child". The
+// is_parent() and is_child() methods indicate which is which.
+//
+// The CORO_REENTER, CORO_YIELD and CORO_FORK keywords are implemented
+// via preprocessor macros. The CORO_REENTER block is actually a large,
+// complex switch statement. Because of this, inline variable declaration
+// is impossible within CORO_REENTER unless it is done in a subsidiary
+// scope--and if it is, that scope cannot contain CORO_YIELD or CORO_FORK
+// keywords.
+//
+// Because coroutines are frequently copied, it is best to minimize copy
+// overhead by limiting the size of data members in derived classes.
+//
+// It should be noted that when a coroutine falls out of scope its memory
+// is reclaimed, even though it may be scheduled to resume when an
+// asynchronous operation completes. Any shared_ptr<> objects declared in
+// the coroutine may be destroyed if their reference count drops to zero,
+// in which case the coroutine will have serious problems once it resumes.
+// One solution so this is to have the space that will be used by a
+// coroutine pre-allocated and stored on a free list; a new coroutine can
+// fetch the block of space off a free list, place a shared pointer to it
+// on an "in use" list, and carry on. The reference in the "in use" list
+// would prevent the data from being destroyed.
+class coroutine
+{
+public:
+ coroutine() : value_(0) {}
+ virtual ~coroutine() {}
+ bool is_child() const { return value_ < 0; }
+ bool is_parent() const { return !is_child(); }
+ bool is_complete() const { return value_ == -1; }
+ int get_value() const { return value_; }
+private:
+ friend class coroutine_ref;
+ int value_;
+};
+
+class coroutine_ref
+{
+public:
+ coroutine_ref(coroutine& c) : value_(c.value_), modified_(false) {}
+ coroutine_ref(coroutine* c) : value_(c->value_), modified_(false) {}
+ ~coroutine_ref() { if (!modified_) value_ = -1; }
+ operator int() const { return value_; }
+ int& operator=(int v) { modified_ = true; return value_ = v; }
+private:
+ void operator=(const coroutine_ref&);
+ int& value_;
+ bool modified_;
+};
+
+#define CORO_REENTER(c) \
+ switch (coroutine_ref _coro_value = c) \
+ case -1: if (_coro_value) \
+ { \
+ goto terminate_coroutine; \
+ terminate_coroutine: \
+ _coro_value = -1; \
+ goto bail_out_of_coroutine; \
+ bail_out_of_coroutine: \
+ break; \
+ } \
+ else case 0:
+
+#define CORO_YIELD \
+ for (_coro_value = __LINE__;;) \
+ if (_coro_value == 0) \
+ { \
+ case __LINE__: ; \
+ break; \
+ } \
+ else \
+ switch (_coro_value ? 0 : 1) \
+ for (;;) \
+ case -1: if (_coro_value) \
+ goto terminate_coroutine; \
+ else for (;;) \
+ case 1: if (_coro_value) \
+ goto bail_out_of_coroutine; \
+ else case 0:
+
+#define CORO_FORK \
+ for (_coro_value = -__LINE__;; _coro_value = __LINE__) \
+ if (_coro_value == __LINE__) \
+ { \
+ case -__LINE__: ; \
+ break; \
+ } \
+ else
+#endif // COROUTINE_HPP
+
diff --git a/src/bin/auth/Makefile.am b/src/bin/auth/Makefile.am
index e9097f2..cdfc55e 100644
--- a/src/bin/auth/Makefile.am
+++ b/src/bin/auth/Makefile.am
@@ -50,11 +50,13 @@ b10_auth_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
b10_auth_LDADD += $(top_builddir)/src/lib/cc/libcc.la
b10_auth_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
b10_auth_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+b10_auth_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
b10_auth_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
+b10_auth_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
b10_auth_LDADD += $(SQLITE_LIBS)
# TODO: config.h.in is wrong because doesn't honor pkgdatadir
# and can't use @datadir@ because doesn't expand default ${prefix}
-b10_authdir = $(DESTDIR)$(pkgdatadir)
+b10_authdir = $(pkgdatadir)
b10_auth_DATA = auth.spec
diff --git a/src/bin/auth/auth.spec.pre.in b/src/bin/auth/auth.spec.pre.in
index 8a77455..d88ffb5 100644
--- a/src/bin/auth/auth.spec.pre.in
+++ b/src/bin/auth/auth.spec.pre.in
@@ -12,51 +12,85 @@
"item_type": "list",
"item_optional": true,
"item_default": [],
- "list_item_spec": {
- "item_name": "list_element",
+ "list_item_spec":
+ { "item_name": "list_element",
"item_type": "map",
"item_optional": false,
"item_default": {},
- "map_item_spec": [
- { "item_name": "type",
- "item_type": "string",
- "item_optional": false,
- "item_default": ""
- },
- { "item_name": "class",
- "item_type": "string",
- "item_optional": false,
- "item_default": "IN"
- },
- { "item_name": "zones",
- "item_type": "list",
- "item_optional": false,
- "item_default": [],
- "list_item_spec": {
- "item_name": "list_element",
- "item_type": "map",
- "item_optional": true,
- "map_item_spec": [
- { "item_name": "origin",
- "item_type": "string",
- "item_optional": false,
- "item_default": ""
- },
- { "item_name": "file",
- "item_type": "string",
- "item_optional": false,
- "item_default": ""
- }
- ]
- }
- }
- ]
+ "map_item_spec": [
+ { "item_name": "type",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ { "item_name": "class",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "IN"
+ },
+ { "item_name": "zones",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ { "item_name": "list_element",
+ "item_type": "map",
+ "item_optional": true,
+ "item_default": { "origin": "", "file": "" },
+ "map_item_spec": [
+ { "item_name": "origin",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ { "item_name": "file",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }]
+ }
+ }]
}
},
{ "item_name": "statistics-interval",
"item_type": "integer",
"item_optional": true,
"item_default": 60
+ },
+ {
+ "item_name": "listen_on",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [
+ {
+ "address": "::",
+ "port": 53
+ },
+ {
+ "address": "0.0.0.0",
+ "port": 53
+ }
+ ],
+ "list_item_spec": {
+ "item_name": "address",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "address",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "::1"
+ },
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 53
+ }
+ ]
+ }
}
],
"commands": [
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index 045fe7f..f46752a 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -69,6 +69,7 @@ using namespace isc::data;
using namespace isc::config;
using namespace isc::xfr;
using namespace asiolink;
+using namespace isc::server_common::portconfig;
class AuthSrvImpl {
private:
@@ -109,6 +110,9 @@ public:
/// Query counters for statistics
AuthCounters counters_;
+
+ /// Addresses we listen on
+ AddressList listen_addresses_;
private:
std::string db_file_;
@@ -750,3 +754,18 @@ uint64_t
AuthSrv::getCounter(const AuthCounters::CounterType type) const {
return (impl_->counters_.getCounter(type));
}
+
+const AddressList&
+AuthSrv::getListenAddresses() const {
+ return (impl_->listen_addresses_);
+}
+
+void
+AuthSrv::setListenAddresses(const AddressList& addresses) {
+ installListenAddresses(addresses, impl_->listen_addresses_, *dnss_);
+}
+
+void
+AuthSrv::setDNSService(asiolink::DNSService& dnss) {
+ dnss_ = &dnss;
+}
diff --git a/src/bin/auth/auth_srv.h b/src/bin/auth/auth_srv.h
index 4772a02..8253c85 100644
--- a/src/bin/auth/auth_srv.h
+++ b/src/bin/auth/auth_srv.h
@@ -25,6 +25,7 @@
#include <config/ccsession.h>
#include <asiolink/asiolink.h>
+#include <server_common/portconfig.h>
#include <auth/statistics.h>
namespace isc {
@@ -353,11 +354,24 @@ public:
/// \return the value of the counter.
uint64_t getCounter(const AuthCounters::CounterType type) const;
+ /**
+ * \brief Set and get the addresses we listen on.
+ */
+ void setListenAddresses(const isc::server_common::portconfig::AddressList&
+ addreses);
+ const isc::server_common::portconfig::AddressList& getListenAddresses()
+ const;
+
+ /// \brief Assign an ASIO DNS Service queue to this Auth object
+ void setDNSService(asiolink::DNSService& dnss);
+
+
private:
AuthSrvImpl* impl_;
asiolink::SimpleCallback* checkin_;
asiolink::DNSLookup* dns_lookup_;
asiolink::DNSAnswer* dns_answer_;
+ asiolink::DNSService* dnss_;
};
#endif // __AUTH_SRV_H
diff --git a/src/bin/auth/b10-auth.8 b/src/bin/auth/b10-auth.8
index bae8e4a..0356683 100644
--- a/src/bin/auth/b10-auth.8
+++ b/src/bin/auth/b10-auth.8
@@ -2,12 +2,12 @@
.\" Title: b10-auth
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: January 19, 2011
+.\" Date: March 8, 2011
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-AUTH" "8" "January 19, 2011" "BIND10" "BIND10"
+.TH "B10\-AUTH" "8" "March 8, 2011" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -22,7 +22,7 @@
b10-auth \- Authoritative DNS server
.SH "SYNOPSIS"
.HP \w'\fBb10\-auth\fR\ 'u
-\fBb10\-auth\fR [\fB\-4\fR] [\fB\-6\fR] [\fB\-a\ \fR\fB\fIaddress\fR\fR] [\fB\-n\fR] [\fB\-p\ \fR\fB\fInumber\fR\fR] [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR]
+\fBb10\-auth\fR [\fB\-n\fR] [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR]
.SH "DESCRIPTION"
.PP
The
@@ -42,55 +42,11 @@ It receives its configurations from
.PP
The arguments are as follows:
.PP
-\fB\-4\fR
-.RS 4
-Enables IPv4 only mode\&. This switch may not be used with
-\fB\-6\fR
-nor
-\fB\-a\fR\&. By default, it listens on both IPv4 and IPv6 (if capable)\&.
-.RE
-.PP
-\fB\-6\fR
-.RS 4
-Enables IPv6 only mode\&. This switch may not be used with
-\fB\-4\fR
-nor
-\fB\-a\fR\&. By default, it listens on both IPv4 and IPv6 (if capable)\&.
-.RE
-.PP
-\fB\-a \fR\fB\fIaddress\fR\fR
-.RS 4
-The IPv4 or IPv6 address to listen on\&. This switch may not be used with
-\fB\-4\fR
-nor
-\fB\-6\fR\&. The default is to listen on all addresses\&. (This is a short term workaround\&. This argument may change\&.)
-.RE
-.PP
\fB\-n\fR
.RS 4
Do not cache answers in memory\&. The default is to use the cache for faster responses\&. The cache keeps the most recent 30,000 answers (positive and negative) in memory for 30 seconds (instead of querying the data source, such as SQLite3 database, each time)\&.
.RE
.PP
-\fB\-p \fR\fB\fInumber\fR\fR
-.RS 4
-The port number it listens on\&. The default is 5300\&.
-.if n \{\
-.sp
-.\}
-.RS 4
-.it 1 an-trap
-.nr an-no-space-flag 1
-.nr an-break-flag 1
-.br
-.ps +1
-\fBNote\fR
-.ps -1
-.br
-The Y1 prototype runs on all interfaces and on this nonstandard port\&.
-.sp .5v
-.RE
-.RE
-.PP
\fB\-u \fR\fB\fIusername\fR\fR
.RS 4
The user name of the
@@ -114,6 +70,18 @@ defines the path to the SQLite3 zone file when using the sqlite datasource\&. Th
/usr/local/var/bind10\-devel/zone\&.sqlite3\&.
.PP
+\fIlisten_on\fR
+is a list of addresses and ports for
+\fBb10\-auth\fR
+to listen on\&. The list items are the
+\fIaddress\fR
+string and
+\fIport\fR
+number\&. By default,
+\fBb10\-auth\fR
+listens on port 53 on the IPv6 (::) and IPv4 (0\&.0\&.0\&.0) wildcard addresses\&.
+.PP
+
\fIdatasources\fR
configures data sources\&. The list items include:
\fItype\fR
diff --git a/src/bin/auth/b10-auth.xml b/src/bin/auth/b10-auth.xml
index b22d24d..2b53394 100644
--- a/src/bin/auth/b10-auth.xml
+++ b/src/bin/auth/b10-auth.xml
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>January 19, 2011</date>
+ <date>March 8, 2011</date>
</refentryinfo>
<refmeta>
@@ -44,11 +44,7 @@
<refsynopsisdiv>
<cmdsynopsis>
<command>b10-auth</command>
- <arg><option>-4</option></arg>
- <arg><option>-6</option></arg>
- <arg><option>-a <replaceable>address</replaceable></option></arg>
<arg><option>-n</option></arg>
- <arg><option>-p <replaceable>number</replaceable></option></arg>
<arg><option>-u <replaceable>username</replaceable></option></arg>
<arg><option>-v</option></arg>
</cmdsynopsis>
@@ -85,39 +81,6 @@
<variablelist>
<varlistentry>
- <term><option>-4</option></term>
- <listitem><para>
- Enables IPv4 only mode.
- This switch may not be used with <option>-6</option> nor
- <option>-a</option>.
- By default, it listens on both IPv4 and IPv6 (if capable).
- </para></listitem>
- </varlistentry>
-
- <varlistentry>
- <term><option>-6</option></term>
- <listitem><para>
- Enables IPv6 only mode.
- This switch may not be used with <option>-4</option> nor
- <option>-a</option>.
- By default, it listens on both IPv4 and IPv6 (if capable).
- </para></listitem>
- </varlistentry>
-
- <varlistentry>
- <term><option>-a <replaceable>address</replaceable></option></term>
-
- <listitem>
- <para>The IPv4 or IPv6 address to listen on.
- This switch may not be used with <option>-4</option> nor
- <option>-6</option>.
- The default is to listen on all addresses.
- (This is a short term workaround. This argument may change.)
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
<term><option>-n</option></term>
<listitem><para>
Do not cache answers in memory.
@@ -130,16 +93,6 @@
</varlistentry>
<varlistentry>
- <term><option>-p <replaceable>number</replaceable></option></term>
- <listitem><para>
- The port number it listens on.
- The default is 5300.</para>
- <note><simpara>This prototype runs on all interfaces
- and on this nonstandard port.</simpara></note>
- </listitem>
- </varlistentry>
-
- <varlistentry>
<term><option>-u <replaceable>username</replaceable></option></term>
<listitem>
<para>
@@ -179,6 +132,15 @@
</para>
<para>
+ <varname>listen_on</varname> is a list of addresses and ports for
+ <command>b10-auth</command> to listen on.
+ The list items are the <varname>address</varname> string
+ and <varname>port</varname> number.
+ By default, <command>b10-auth</command> listens on port 53
+ on the IPv6 (::) and IPv4 (0.0.0.0) wildcard addresses.
+ </para>
+
+ <para>
<varname>datasources</varname> configures data sources.
The list items include:
<varname>type</varname> to optionally choose the data source type
diff --git a/src/bin/auth/benchmarks/Makefile.am b/src/bin/auth/benchmarks/Makefile.am
index 05ab754..3078dd5 100644
--- a/src/bin/auth/benchmarks/Makefile.am
+++ b/src/bin/auth/benchmarks/Makefile.am
@@ -21,5 +21,7 @@ query_bench_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
query_bench_LDADD += $(top_builddir)/src/lib/cc/libcc.la
query_bench_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
query_bench_LDADD += $(top_builddir)/src/lib/log/liblog.la
+query_bench_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
query_bench_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+query_bench_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
query_bench_LDADD += $(SQLITE_LIBS)
diff --git a/src/bin/auth/benchmarks/query_bench.cc b/src/bin/auth/benchmarks/query_bench.cc
index 7f643f3..5e69134 100644
--- a/src/bin/auth/benchmarks/query_bench.cc
+++ b/src/bin/auth/benchmarks/query_bench.cc
@@ -77,7 +77,7 @@ protected:
dummy_socket(IOSocket::getDummyUDPSocket()),
dummy_endpoint(IOEndpointPtr(IOEndpoint::create(IPPROTO_UDP,
IOAddress("192.0.2.1"),
- 5300)))
+ 53210)))
{}
public:
unsigned int run() {
diff --git a/src/bin/auth/config.cc b/src/bin/auth/config.cc
index 5befc6e..f289ca0 100644
--- a/src/bin/auth/config.cc
+++ b/src/bin/auth/config.cc
@@ -32,11 +32,14 @@
#include <auth/config.h>
#include <auth/common.h>
+#include <server_common/portconfig.h>
+
using namespace std;
using boost::shared_ptr;
using namespace isc::dns;
using namespace isc::data;
using namespace isc::datasrc;
+using namespace isc::server_common::portconfig;
namespace {
// Forward declaration
@@ -210,6 +213,60 @@ public:
}
};
+/**
+ * \brief Configuration parser for listen_on.
+ *
+ * It parses and sets the listening addresses of the server.
+ *
+ * It acts in unusual way. Since actually binding (changing) the sockets
+ * is an operation that is expected to throw often, it shouldn't happen
+ * in commit. Thefere we do it in build. But if the config is not committed
+ * then, we would have it wrong. So we store the old addresses and if
+ * commit is not called before destruction of the object, we return the
+ * old addresses (which is the same kind of dangerous operation, but it is
+ * expected that if we just managed to bind some and had the old ones binded
+ * before, it should work).
+ *
+ * We might do something better in future (like open only the ports that are
+ * extra, put them in in commit and close the old ones), but that's left out
+ * for now.
+ */
+class ListenAddressConfig : public AuthConfigParser {
+public:
+ ListenAddressConfig(AuthSrv& server) :
+ server_(server)
+ { }
+ ~ ListenAddressConfig() {
+ if (rollbackAddresses_.get() != NULL) {
+ server_.setListenAddresses(*rollbackAddresses_);
+ }
+ }
+private:
+ typedef auto_ptr<AddressList> AddrListPtr;
+public:
+ virtual void build(ConstElementPtr config) {
+ AddressList newAddresses = parseAddresses(config, "listen_on");
+ AddrListPtr old(new AddressList(server_.getListenAddresses()));
+ server_.setListenAddresses(newAddresses);
+ /*
+ * Set the rollback addresses only after successful setting of the
+ * new addresses, so we don't try to rollback if the setup is
+ * unsuccessful (the above can easily throw).
+ */
+ rollbackAddresses_ = old;
+ }
+ virtual void commit() {
+ rollbackAddresses_.release();
+ }
+private:
+ AuthSrv& server_;
+ /**
+ * This is the old address list, if we expect to roll back. When we commit,
+ * this is set to NULL.
+ */
+ AddrListPtr rollbackAddresses_;
+};
+
// This is a generalized version of create function that can create
// an AuthConfigParser object for "internal" use.
AuthConfigParser*
@@ -226,6 +283,8 @@ createAuthConfigParser(AuthSrv& server, const std::string& config_id,
return (new StatisticsIntervalConfig(server));
} else if (internal && config_id == "datasources/memory") {
return (new MemoryDatasourceConfig(server));
+ } else if (config_id == "listen_on") {
+ return (new ListenAddressConfig(server));
} else if (config_id == "_commit_throw") {
// This is for testing purpose only and should not appear in the
// actual configuration syntax. While this could crash the caller
@@ -271,7 +330,7 @@ configureAuthServer(AuthSrv& server, ConstElementPtr config_set) {
parsers.push_back(parser);
}
} catch (const AuthConfigError& ex) {
- throw ex; // simply rethrowing it
+ throw; // simply rethrowing it
} catch (const isc::Exception& ex) {
isc_throw(AuthConfigError, "Server configuration failed: " <<
ex.what());
diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc
index 10e1194..0701b94 100644
--- a/src/bin/auth/main.cc
+++ b/src/bin/auth/main.cc
@@ -42,6 +42,7 @@
#include <auth/change_user.h>
#include <auth/auth_srv.h>
#include <asiolink/asiolink.h>
+#include <log/dummylog.h>
using namespace std;
using namespace isc::data;
@@ -55,9 +56,6 @@ namespace {
bool verbose_mode = false;
-// Default port current 5300 for testing purposes
-const char* DNSPORT = "5300";
-
/* need global var for config/command handlers.
* todo: turn this around, and put handlers in the authserver
* class itself? */
@@ -76,13 +74,8 @@ my_command_handler(const string& command, ConstElementPtr args) {
void
usage() {
- cerr << "Usage: b10-auth [-a address] [-p port] [-u user] [-4|-6] [-nv]"
- << endl;
- cerr << "\t-a: specify the address to listen on (default: all) " << endl;
- cerr << "\t-p: specify the port to listen on (default: " << DNSPORT << ")"
+ cerr << "Usage: b10-auth [-u user] [-nv]"
<< endl;
- cerr << "\t-4: listen on all IPv4 addresses (incompatible with -a)" << endl;
- cerr << "\t-6: listen on all IPv6 addresses (incompatible with -a)" << 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;
@@ -93,38 +86,20 @@ usage() {
int
main(int argc, char* argv[]) {
int ch;
- const char* port = DNSPORT;
- const char* address = NULL;
const char* uid = NULL;
- bool use_ipv4 = true, use_ipv6 = true, cache = true;
+ bool cache = true;
- while ((ch = getopt(argc, argv, "46a:np:u:v")) != -1) {
+ while ((ch = getopt(argc, argv, ":nu:v")) != -1) {
switch (ch) {
- case '4':
- // Note that -4 means "ipv4 only", we need to set "use_ipv6" here,
- // not "use_ipv4". We could use something like "ipv4_only", but
- // we found the negatively named variable could confuse the code
- // logic.
- use_ipv6 = false;
- break;
- case '6':
- // The same note as -4 applies.
- use_ipv4 = false;
- break;
case 'n':
cache = false;
break;
- case 'a':
- address = optarg;
- break;
- case 'p':
- port = optarg;
- break;
case 'u':
uid = optarg;
break;
case 'v':
verbose_mode = true;
+ isc::log::denabled = true;
break;
case '?':
default:
@@ -136,18 +111,6 @@ main(int argc, char* argv[]) {
usage();
}
- if (!use_ipv4 && !use_ipv6) {
- cerr << "[b10-auth] Error: Cannot specify both -4 and -6 "
- << "at the same time" << endl;
- usage();
- }
-
- if ((!use_ipv4 || !use_ipv6) && address != NULL) {
- cerr << "[b10-auth] Error: Cannot specify -4 or -6 "
- << "at the same time as -a" << endl;
- usage();
- }
-
int ret = 0;
// XXX: we should eventually pass io_service here.
@@ -159,7 +122,13 @@ main(int argc, char* argv[]) {
ModuleCCSession* config_session = NULL;
string xfrout_socket_path;
if (getenv("B10_FROM_BUILD") != NULL) {
- xfrout_socket_path = string(getenv("B10_FROM_BUILD")) + "/auth_xfrout_conn";
+ if (getenv("B10_FROM_SOURCE_LOCALSTATEDIR")) {
+ xfrout_socket_path = string("B10_FROM_SOURCE_LOCALSTATEDIR") +
+ "/auth_xfrout_conn";
+ } else {
+ xfrout_socket_path = string(getenv("B10_FROM_BUILD")) +
+ "/auth_xfrout_conn";
+ }
} else {
xfrout_socket_path = UNIX_SOCKET_FILE;
}
@@ -182,21 +151,8 @@ main(int argc, char* argv[]) {
DNSLookup* lookup = auth_server->getDNSLookupProvider();
DNSAnswer* answer = auth_server->getDNSAnswerProvider();
- DNSService* dns_service;
- if (address != NULL) {
- // XXX: we can only specify at most one explicit address.
- // This also means the server cannot run in the dual address
- // family mode if explicit addresses need to be specified.
- // We don't bother to fix this problem, however. The -a option
- // is a short term workaround until we support dynamic listening
- // port allocation.
- dns_service = new DNSService(io_service, *port, *address,
- checkin, lookup, answer);
- } else {
- dns_service = new DNSService(io_service, *port, use_ipv4,
- use_ipv6, checkin, lookup,
- answer);
- }
+ DNSService dns_service(io_service, checkin, lookup, answer);
+ auth_server->setDNSService(dns_service);
cout << "[b10-auth] DNSServices created." << endl;
cc_session = new Session(io_service.get_io_service());
@@ -237,7 +193,6 @@ main(int argc, char* argv[]) {
cout << "[b10-auth] Server started." << endl;
io_service.run();
- delete dns_service;
} catch (const std::exception& ex) {
cerr << "[b10-auth] Server failed: " << ex.what() << endl;
ret = 1;
diff --git a/src/bin/auth/query.cc b/src/bin/auth/query.cc
index e85c31a..e936c97 100644
--- a/src/bin/auth/query.cc
+++ b/src/bin/auth/query.cc
@@ -141,13 +141,56 @@ Query::process() const {
// Found a zone which is the nearest ancestor to QNAME, set the AA bit
response_.setHeaderFlag(Message::HEADERFLAG_AA);
+ response_.setRcode(Rcode::NOERROR());
while (keep_doing) {
keep_doing = false;
std::auto_ptr<RRsetList> target(qtype_is_any ? new RRsetList : NULL);
- Zone::FindResult db_result =
- result.zone->find(qname_, qtype_, target.get());
+ const Zone::FindResult db_result(result.zone->find(qname_, qtype_,
+ target.get()));
switch (db_result.code) {
+ case Zone::DNAME: {
+ // First, put the dname into the answer
+ response_.addRRset(Message::SECTION_ANSWER,
+ boost::const_pointer_cast<RRset>(db_result.rrset));
+ /*
+ * Empty DNAME should never get in, as it is impossible to
+ * create one in master file.
+ *
+ * FIXME: Other way to prevent this should be done
+ */
+ assert(db_result.rrset->getRdataCount() > 0);
+ // Get the data of DNAME
+ const rdata::generic::DNAME& dname(
+ dynamic_cast<const rdata::generic::DNAME&>(
+ db_result.rrset->getRdataIterator()->getCurrent()));
+ // The yet unmatched prefix dname
+ const Name prefix(qname_.split(0, qname_.getLabelCount() -
+ db_result.rrset->getName().getLabelCount()));
+ // If we put it together, will it be too long?
+ // (The prefix contains trailing ., which will be removed
+ if (prefix.getLength() - Name::ROOT_NAME().getLength() +
+ dname.getDname().getLength() > Name::MAX_WIRE) {
+ /*
+ * In case the synthesized name is too long, section 4.1
+ * of RFC 2672 mandates we return YXDOMAIN.
+ */
+ response_.setRcode(Rcode::YXDOMAIN());
+ return;
+ }
+ // The new CNAME we are creating (it will be unsigned even
+ // with DNSSEC, the DNAME is signed and it can be validated
+ // by that)
+ RRsetPtr cname(new RRset(qname_, db_result.rrset->getClass(),
+ RRType::CNAME(), db_result.rrset->getTTL()));
+ // Construct the new target by replacing the end
+ cname->addRdata(rdata::generic::CNAME(qname_.split(0,
+ qname_.getLabelCount() -
+ db_result.rrset->getName().getLabelCount()).
+ concatenate(dname.getDname())));
+ response_.addRRset(Message::SECTION_ANSWER, cname);
+ break;
+ }
case Zone::CNAME:
/*
* We don't do chaining yet. Therefore handling a CNAME is
@@ -155,10 +198,13 @@ Query::process() const {
* what we expected. It means no exceptions in ANY or NS
* on the origin (though CNAME in origin is probably
* forbidden anyway).
+ *
+ * So, just put it there.
*/
- // No break; here, fall trough.
+ response_.addRRset(Message::SECTION_ANSWER,
+ boost::const_pointer_cast<RRset>(db_result.rrset));
+ break;
case Zone::SUCCESS:
- response_.setRcode(Rcode::NOERROR());
if (qtype_is_any) {
// If quety type is ANY, insert all RRs under the domain
// into answer section.
@@ -184,7 +230,6 @@ Query::process() const {
break;
case Zone::DELEGATION:
response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
- response_.setRcode(Rcode::NOERROR());
response_.addRRset(Message::SECTION_AUTHORITY,
boost::const_pointer_cast<RRset>(db_result.rrset));
getAdditional(*result.zone, *db_result.rrset);
@@ -196,12 +241,8 @@ Query::process() const {
break;
case Zone::NXRRSET:
// Just empty answer with SOA in authority section
- response_.setRcode(Rcode::NOERROR());
putSOA(*result.zone);
break;
- case Zone::DNAME:
- // TODO : replace qname, continue lookup
- break;
}
}
}
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index a1114e4..7d489a1 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -45,6 +45,8 @@ run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
run_unittests_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
+run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
+run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc
index 576799c..379342e 100644
--- a/src/bin/auth/tests/auth_srv_unittest.cc
+++ b/src/bin/auth/tests/auth_srv_unittest.cc
@@ -26,6 +26,8 @@
#include <dns/rrttl.h>
#include <dns/rdataclass.h>
+#include <server_common/portconfig.h>
+
#include <datasrc/memory_datasrc.h>
#include <auth/auth_srv.h>
#include <auth/common.h>
@@ -34,6 +36,7 @@
#include <dns/tests/unittest_util.h>
#include <testutils/dnsmessage_test.h>
#include <testutils/srv_test.h>
+#include <testutils/portconfig.h>
using namespace std;
using namespace isc::cc;
@@ -43,6 +46,7 @@ using namespace isc::data;
using namespace isc::xfr;
using namespace asiolink;
using namespace isc::testutils;
+using namespace isc::server_common::portconfig;
using isc::UnitTestUtil;
namespace {
@@ -55,7 +59,12 @@ const char* const BADCONFIG_TESTDB =
class AuthSrvTest : public SrvTestBase {
protected:
- AuthSrvTest() : server(true, xfrout), rrclass(RRClass::IN()) {
+ AuthSrvTest() :
+ dnss_(ios_, NULL, NULL, NULL),
+ server(true, xfrout),
+ rrclass(RRClass::IN())
+ {
+ server.setDNSService(dnss_);
server.setXfrinSession(¬ify_session);
server.setStatisticsSession(&statistics_session);
}
@@ -63,6 +72,8 @@ protected:
server.processMessage(*io_message, parse_message, response_obuffer,
&dnsserv);
}
+ IOService ios_;
+ DNSService dnss_;
MockSession statistics_session;
MockXfroutClient xfrout;
AuthSrv server;
@@ -633,7 +644,7 @@ TEST_F(AuthSrvTest, queryCounterUnexpected) {
// Modify the message.
delete io_message;
endpoint = IOEndpoint::create(IPPROTO_UDP,
- IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
+ IOAddress(DEFAULT_REMOTE_ADDRESS), 53210);
io_message = new IOMessage(request_renderer.getData(),
request_renderer.getLength(),
getDummyUnknownSocket(), *endpoint);
@@ -650,4 +661,9 @@ TEST_F(AuthSrvTest, stop) {
// If/when the interval timer has finer granularity we'll probably add
// our own tests here, so we keep this empty test case.
}
+
+TEST_F(AuthSrvTest, listenAddresses) {
+ isc::testutils::portconfig::listenAddresses(server);
+}
+
}
diff --git a/src/bin/auth/tests/config_unittest.cc b/src/bin/auth/tests/config_unittest.cc
index b8b379e..8cce0af 100644
--- a/src/bin/auth/tests/config_unittest.cc
+++ b/src/bin/auth/tests/config_unittest.cc
@@ -30,6 +30,7 @@
#include <auth/common.h>
#include <testutils/mockups.h>
+#include <testutils/portconfig.h>
using namespace isc::dns;
using namespace isc::data;
@@ -39,7 +40,15 @@ using namespace asiolink;
namespace {
class AuthConfigTest : public ::testing::Test {
protected:
- AuthConfigTest() : rrclass(RRClass::IN()), server(true, xfrout) {}
+ AuthConfigTest() :
+ dnss_(ios_, NULL, NULL, NULL),
+ rrclass(RRClass::IN()),
+ server(true, xfrout)
+ {
+ server.setDNSService(dnss_);
+ }
+ IOService ios_;
+ DNSService dnss_;
const RRClass rrclass;
MockXfroutClient xfrout;
AuthSrv server;
@@ -112,6 +121,17 @@ TEST_F(AuthConfigTest, exceptionFromCommit) {
FatalError);
}
+// Test invalid address configs are rejected
+TEST_F(AuthConfigTest, invalidListenAddressConfig) {
+ // This currently passes simply because the config doesn't know listen_on
+ isc::testutils::portconfig::invalidListenAddressConfig(server);
+}
+
+// Try setting addresses trough config
+TEST_F(AuthConfigTest, listenAddressConfig) {
+ isc::testutils::portconfig::listenAddressConfig(server);
+}
+
class MemoryDatasrcConfigTest : public AuthConfigTest {
protected:
MemoryDatasrcConfigTest() :
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 3bf4142..05dd748 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -75,6 +75,17 @@ const char* const cname_nxdom_txt =
// CNAME Leading out of zone
const char* const cname_out_txt =
"cnameout.example.com. 3600 IN CNAME www.example.org.\n";
+// The DNAME to do tests against
+const char* const dname_txt =
+ "dname.example.com. 3600 IN DNAME "
+ "somethinglong.dnametarget.example.com.\n";
+// Some data at the dname node (allowed by RFC 2672)
+const char* const dname_a_txt =
+ "dname.example.com. 3600 IN A 192.0.2.5\n";
+// This is not inside the zone, this is created at runtime
+const char* const synthetized_cname_txt =
+ "www.dname.example.com. 3600 IN CNAME "
+ "www.somethinglong.dnametarget.example.com.\n";
// The rest of data won't be referenced from the test cases.
const char* const other_zone_rrs =
"cnamemailer.example.com. 3600 IN CNAME www.example.com.\n"
@@ -88,13 +99,16 @@ const char* const other_zone_rrs =
// 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".
-// It doesn't handle empty non terminal nodes (if we need to test such cases
-// find() should have specialized code for it).
+// Another special name is "dname.example.com". Query names under this name
+// will result in DNAME.
+// This mock zone doesn't handle empty non terminal nodes (if we need to test
+// such cases find() should have specialized code for it).
class MockZone : public Zone {
public:
MockZone() :
origin_(Name("example.com")),
delegation_name_("delegation.example.com"),
+ dname_name_("dname.example.com"),
has_SOA_(true),
has_apex_NS_(true),
rrclass_(RRClass::IN())
@@ -102,7 +116,8 @@ public:
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 << other_zone_rrs;
+ cname_nxdom_txt << cname_out_txt << dname_txt << dname_a_txt <<
+ other_zone_rrs;
masterLoad(zone_stream, origin_, rrclass_,
boost::bind(&MockZone::loadRRset, this, _1));
@@ -131,14 +146,20 @@ private:
if (rrset->getName() == delegation_name_ &&
rrset->getType() == RRType::NS()) {
delegation_rrset_ = rrset;
+ } else if (rrset->getName() == dname_name_ &&
+ rrset->getType() == RRType::DNAME()) {
+ dname_rrset_ = rrset;
}
}
const Name origin_;
+ // Names where we delegate somewhere else
const Name delegation_name_;
+ const Name dname_name_;
bool has_SOA_;
bool has_apex_NS_;
ConstRRsetPtr delegation_rrset_;
+ ConstRRsetPtr dname_rrset_;
const RRClass rrclass_;
};
@@ -160,6 +181,10 @@ MockZone::find(const Name& name, const RRType& type,
name.compare(delegation_name_).getRelation() ==
NameComparisonResult::SUBDOMAIN)) {
return (FindResult(DELEGATION, delegation_rrset_));
+ // And under DNAME
+ } else if (name.compare(dname_name_).getRelation() ==
+ NameComparisonResult::SUBDOMAIN) {
+ return (FindResult(DNAME, dname_rrset_));
}
// normal cases. names are searched for only per exact-match basis
@@ -176,8 +201,7 @@ MockZone::find(const Name& name, const RRType& type,
// If not found but we have a target, fill it with all RRsets here
if (!found_domain->second.empty() && target != NULL) {
for (found_rrset = found_domain->second.begin();
- found_rrset != found_domain->second.end(); found_rrset++)
- {
+ found_rrset != found_domain->second.end(); ++found_rrset) {
// Insert RRs under the domain name into target
target->addRRset(
boost::const_pointer_cast<RRset>(found_rrset->second));
@@ -443,8 +467,8 @@ TEST_F(QueryTest, CNAME) {
Query(memory_datasrc, Name("cname.example.com"), RRType::A(),
response).process();
- responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
- cname_txt, zone_ns_txt, ns_addrs_txt);
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+ cname_txt, NULL, NULL);
}
TEST_F(QueryTest, explicitCNAME) {
@@ -465,8 +489,8 @@ TEST_F(QueryTest, CNAME_NX_RRSET) {
Query(memory_datasrc, Name("cname.example.com"), RRType::TXT(),
response).process();
- responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
- cname_txt, zone_ns_txt, ns_addrs_txt);
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+ cname_txt, NULL, NULL);
}
TEST_F(QueryTest, explicitCNAME_NX_RRSET) {
@@ -488,8 +512,8 @@ TEST_F(QueryTest, CNAME_NX_DOMAIN) {
Query(memory_datasrc, Name("cnamenxdom.example.com"), RRType::A(),
response).process();
- responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
- cname_nxdom_txt, zone_ns_txt, ns_addrs_txt);
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+ cname_nxdom_txt, NULL, NULL);
}
TEST_F(QueryTest, explicitCNAME_NX_DOMAIN) {
@@ -513,8 +537,8 @@ TEST_F(QueryTest, CNAME_OUT) {
Query(memory_datasrc, Name("cnameout.example.com"), RRType::A(),
response).process();
- responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
- cname_out_txt, zone_ns_txt, ns_addrs_txt);
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+ cname_out_txt, NULL, NULL);
}
TEST_F(QueryTest, explicitCNAME_OUT) {
@@ -526,4 +550,129 @@ TEST_F(QueryTest, explicitCNAME_OUT) {
cname_out_txt, zone_ns_txt, ns_addrs_txt);
}
+/*
+ * Test a query under a domain with DNAME. We should get a synthetized CNAME
+ * as well as the DNAME.
+ *
+ * TODO: Once we have CNAME chaining, check it works with synthetized CNAMEs
+ * as well. This includes tests pointing inside the zone, outside the zone,
+ * pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
+ */
+TEST_F(QueryTest, DNAME) {
+ Query(memory_datasrc, Name("www.dname.example.com"), RRType::A(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
+ (string(dname_txt) + synthetized_cname_txt).c_str(),
+ NULL, NULL);
+}
+
+/*
+ * Ask an ANY query below a DNAME. Should return the DNAME and synthetized
+ * CNAME.
+ *
+ * ANY is handled specially sometimes. We check it is not the case with
+ * DNAME.
+ */
+TEST_F(QueryTest, DNAME_ANY) {
+ Query(memory_datasrc, Name("www.dname.example.com"), RRType::ANY(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
+ (string(dname_txt) + synthetized_cname_txt).c_str(), NULL, NULL);
+}
+
+// Test when we ask for DNAME explicitly, it does no synthetizing.
+TEST_F(QueryTest, explicitDNAME) {
+ Query(memory_datasrc, Name("dname.example.com"), RRType::DNAME(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ dname_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+/*
+ * Request a RRset at the domain with DNAME. It should not synthetize
+ * the CNAME, it should return the RRset.
+ */
+TEST_F(QueryTest, DNAME_A) {
+ Query(memory_datasrc, Name("dname.example.com"), RRType::A(),
+ response).process();
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ dname_a_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+/*
+ * Request a RRset at the domain with DNAME that is not there (NXRRSET).
+ * It should not synthetize the CNAME.
+ */
+TEST_F(QueryTest, DNAME_NX_RRSET) {
+ EXPECT_NO_THROW(Query(memory_datasrc, Name("dname.example.com"),
+ RRType::TXT(), response).process());
+
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
+ NULL, soa_txt, NULL, mock_zone->getOrigin());
+}
+
+/*
+ * Constructing the CNAME will result in a name that is too long. This,
+ * however, should not throw (and crash the server), but respond with
+ * YXDOMAIN.
+ */
+TEST_F(QueryTest, LongDNAME) {
+ // A name that is as long as it can be
+ Name longname(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "dname.example.com.");
+ EXPECT_NO_THROW(Query(memory_datasrc, longname, RRType::A(),
+ response).process());
+
+ responseCheck(response, Rcode::YXDOMAIN(), AA_FLAG, 1, 0, 0,
+ dname_txt, NULL, NULL);
+}
+
+/*
+ * Constructing the CNAME will result in a name of maximal length.
+ * This tests that we don't reject valid one by some kind of off by
+ * one mistake.
+ */
+TEST_F(QueryTest, MaxLenDNAME) {
+ Name longname(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+ "dname.example.com.");
+ EXPECT_NO_THROW(Query(memory_datasrc, longname, RRType::A(),
+ response).process());
+
+ // Check the answer is OK
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
+ NULL, NULL, NULL);
+
+ // Check that the CNAME has the maximal length.
+ bool ok(false);
+ for (RRsetIterator i(response.beginSection(Message::SECTION_ANSWER));
+ i != response.endSection(Message::SECTION_ANSWER); ++ i) {
+ if ((*i)->getType() == RRType::CNAME()) {
+ ok = true;
+ RdataIteratorPtr ci((*i)->getRdataIterator());
+ ASSERT_FALSE(ci->isLast()) << "The CNAME is empty";
+ /*
+ * Does anybody have a clue why, if the Name::MAX_WIRE is put
+ * directly inside ASSERT_EQ, it fails to link and complains
+ * it is unresolved external?
+ */
+ const size_t max_len(Name::MAX_WIRE);
+ ASSERT_EQ(max_len, dynamic_cast<const rdata::generic::CNAME&>(
+ ci->getCurrent()).getCname().getLength());
+ }
+ }
+ EXPECT_TRUE(ok) << "The synthetized CNAME not found";
+}
+
}
diff --git a/src/bin/bind10/Makefile.am b/src/bin/bind10/Makefile.am
index 1445b95..254875f 100644
--- a/src/bin/bind10/Makefile.am
+++ b/src/bin/bind10/Makefile.am
@@ -5,7 +5,7 @@ CLEANFILES = bind10 bind10.pyc
pkglibexecdir = $(libexecdir)/@PACKAGE@
-bind10dir = $(DESTDIR)$(pkgdatadir)
+bind10dir = $(pkgdatadir)
bind10_DATA = bob.spec
EXTRA_DIST = bob.spec
@@ -19,7 +19,6 @@ bind10.8: bind10.xml
endif
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
bind10: bind10.py
$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
diff --git a/src/bin/bind10/bind10.8 b/src/bin/bind10/bind10.8
index a3ac653..a75136b 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: July 29, 2010
+.\" Date: February 22, 2011
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "BIND10" "8" "July 29, 2010" "BIND10" "BIND10"
+.TH "BIND10" "8" "February 22, 2011" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -22,7 +22,7 @@
bind10 \- BIND 10 boss process
.SH "SYNOPSIS"
.HP \w'\fBbind10\fR\ 'u
-\fBbind10\fR [\fB\-a\ \fR\fB\fIaddress\fR\fR] [\fB\-m\ \fR\fB\fIfile\fR\fR] [\fB\-n\fR] [\fB\-p\ \fR\fB\fInumber\fR\fR] [\fB\-u\ \fR\fB\fIuser\fR\fR] [\fB\-v\fR] [\fB\-\-address\ \fR\fB\fIaddress\fR\fR] [\fB\-\-msgq\-socket\-file\ \fR\fB\fIfile\fR\fR] [\fB\-\-no\-cache\fR] [\fB\-\-port\ \fR\fB\fInumber\fR\fR] [\fB\-\-user\ \fR\fB\fIuser\fR\fR] [\fB\-\-pretty\-name\ \fR\fB\fIname\fR\fR] [\fB\-\-verbose\fR]
+\fBbind10\fR [\fB\-m\ \fR\fB\fIfile\fR\fR] [\fB\-n\fR] [\fB\-u\ \fR\fB\fIuser\fR\fR] [\fB\-v\fR] [\fB\-\-msgq\-socket\-file\ \fR\fB\fIfile\fR\fR] [\fB\-\-no\-cache\fR] [\fB\-\-user\ \fR\fB\fIuser\fR\fR] [\fB\-\-pretty\-name\ \fR\fB\fIname\fR\fR] [\fB\-\-verbose\fR]
.SH "DESCRIPTION"
.PP
The
@@ -32,13 +32,6 @@ daemon starts up other BIND 10 required daemons\&. It handles restarting of exit
.PP
The arguments are as follows:
.PP
-\fB\-a\fR \fIaddress\fR, \fB\-\-address\fR \fIaddress\fR
-.RS 4
-The IPv4 or IPv6 address for the
-\fBb10-auth\fR(8)
-daemon to listen on\&. The default is to listen on all addresses\&. (This is a short term workaround\&. This argument may change\&.)
-.RE
-.PP
\fB\-m\fR \fIfile\fR, \fB\-\-msgq\-socket\-file\fR \fIfile\fR
.RS 4
The UNIX domain socket file for the
@@ -54,28 +47,6 @@ Disables the hot\-spot caching used by the
daemon\&.
.RE
.PP
-\fB\-p\fR \fInumber\fR, \fB\-\-port\fR \fInumber\fR
-.RS 4
-The port number for the
-\fBb10-auth\fR(8)
-daemon to listen on\&. The default is 5300\&.
-.if n \{\
-.sp
-.\}
-.RS 4
-.it 1 an-trap
-.nr an-no-space-flag 1
-.nr an-break-flag 1
-.br
-.ps +1
-\fBNote\fR
-.ps -1
-.br
-The Y1 prototype release uses a non\-default port for domain service\&.
-.sp .5v
-.RE
-.RE
-.PP
\fB\-u\fR \fIuser\fR, \fB\-\-user\fR \fIname\fR
.RS 4
The username for
@@ -125,5 +96,5 @@ The
daemon was initially designed by Shane Kerr of ISC\&.
.SH "COPYRIGHT"
.br
-Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")
+Copyright \(co 2011 Internet Systems Consortium, Inc. ("ISC")
.br
diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in
old mode 100644
new mode 100755
index 7594b77..ce6e523
--- a/src/bin/bind10/bind10.py.in
+++ b/src/bin/bind10/bind10.py.in
@@ -72,7 +72,7 @@ isc.util.process.rename(sys.argv[0])
# This is the version that gets displayed to the user.
# The VERSION string consists of the module name, the module version
# number, and the overall BIND 10 version number (set in configure.ac).
-VERSION = "bind10 20101129 (BIND 10 @PACKAGE_VERSION@)"
+VERSION = "bind10 20110223 (BIND 10 @PACKAGE_VERSION@)"
# This is for bind10.boottime of stats module
_BASETIME = time.gmtime()
@@ -194,9 +194,8 @@ class CChannelConnectError(Exception): pass
class BoB:
"""Boss of BIND class."""
- def __init__(self, msgq_socket_file=None, dns_port=5300, address=None,
- forward=None, nocache=False, verbose=False, setuid=None,
- username=None):
+ def __init__(self, msgq_socket_file=None, nocache=False, verbose=False,
+ setuid=None, username=None):
"""
Initialize the Boss of BIND. This is a singleton (only one can run).
@@ -204,34 +203,72 @@ class BoB:
msgq process listens on. If verbose is True, then the boss reports
what it is doing.
"""
- self.address = address
- self.dns_port = dns_port
- self.forward = forward
- if forward:
- self.resolver = True
- else:
- self.resolver = False
self.cc_session = None
self.ccs = None
self.cfg_start_auth = True
self.cfg_start_resolver = False
+ self.started_auth_family = False
+ self.started_resolver_family = False
self.curproc = None
self.dead_processes = {}
self.msgq_socket_file = msgq_socket_file
self.nocache = nocache
self.processes = {}
+ self.expected_shutdowns = {}
self.runnable = False
self.uid = setuid
self.username = username
self.verbose = verbose
def config_handler(self, new_config):
+ # If this is initial update, don't do anything now, leave it to startup
+ if not self.runnable:
+ return
+ # Now we declare few functions used only internally here. Besides the
+ # benefit of not polluting the name space, they are closures, so we
+ # don't need to pass some variables
+ def start_stop(name, started, start, stop):
+ if not'start_' + name in new_config:
+ return
+ if new_config['start_' + name]:
+ if not started:
+ if self.uid is not None:
+ sys.stderr.write("[bind10] Starting " + name + " as " +
+ "a user, not root. This might fail.\n")
+ start()
+ else:
+ stop()
+ # These four functions are passed to start_stop (smells like functional
+ # programming little bit)
+ def resolver_on():
+ self.start_resolver(self.c_channel_env)
+ self.started_resolver_family = True
+ def resolver_off():
+ self.stop_resolver()
+ self.started_resolver_family = False
+ def auth_on():
+ self.start_auth(self.c_channel_env)
+ self.start_xfrout(self.c_channel_env)
+ self.start_xfrin(self.c_channel_env)
+ self.start_zonemgr(self.c_channel_env)
+ self.started_auth_family = True
+ def auth_off():
+ self.stop_zonemgr()
+ self.stop_xfrin()
+ self.stop_xfrout()
+ self.stop_auth()
+ self.started_auth_family = False
+
+ # The real code of the config handler function follows here
if self.verbose:
sys.stdout.write("[bind10] Handling new configuration: " +
str(new_config) + "\n")
+ start_stop('resolver', self.started_resolver_family, resolver_on,
+ resolver_off)
+ start_stop('auth', self.started_auth_family, auth_on, auth_off)
+
answer = isc.config.ccsession.create_answer(0)
return answer
- # TODO
def command_handler(self, command, args):
if self.verbose:
@@ -320,8 +357,8 @@ class BoB:
sys.stdout.write("\n")
# The next few methods start the individual processes of BIND-10. They
- # are called via start_all_process(). If any fail, an exception is raised
- # which is caught by the caller of start_all_processes(); this kills
+ # are called via start_all_processes(). If any fail, an exception is
+ # raised which is caught by the caller of start_all_processes(); this kills
# processes started up to that point before terminating the program.
def start_msgq(self, c_channel_env):
@@ -422,27 +459,16 @@ class BoB:
"""
Start the Authoritative server
"""
- # XXX: this must be read from the configuration manager in the future
- if self.resolver:
- dns_prog = 'b10-resolver'
- else:
- dns_prog = 'b10-auth'
- dnsargs = [dns_prog]
- if not self.resolver:
- # The resolver uses configuration manager for these
- dnsargs += ['-p', str(self.dns_port)]
- if self.address:
- dnsargs += ['-a', str(self.address)]
- if self.nocache:
- dnsargs += ['-n']
+ authargs = ['b10-auth']
+ if self.nocache:
+ authargs += ['-n']
if self.uid:
- dnsargs += ['-u', str(self.uid)]
+ authargs += ['-u', str(self.uid)]
if self.verbose:
- dnsargs += ['-v']
+ authargs += ['-v']
# ... and start
- self.start_process("b10-auth", dnsargs, c_channel_env,
- self.dns_port, self.address)
+ self.start_process("b10-auth", authargs, c_channel_env)
def start_resolver(self, c_channel_env):
"""
@@ -477,11 +503,12 @@ class BoB:
# XXX: we hardcode port 8080
self.start_simple("b10-cmdctl", c_channel_env, 8080)
- def start_all_processes(self, c_channel_env):
+ def start_all_processes(self):
"""
Starts up all the processes. Any exception generated during the
starting of the processes is handled by the caller.
"""
+ c_channel_env = self.c_channel_env
self.start_msgq(c_channel_env)
self.start_cfgmgr(c_channel_env)
self.start_ccsession(c_channel_env)
@@ -498,6 +525,7 @@ class BoB:
# ... and resolver (if selected):
if self.cfg_start_resolver:
self.start_resolver(c_channel_env)
+ self.started_resolver_family = True
# Everything after the main components can run as non-root.
# TODO: this is only temporary - once the privileged socket creator is
@@ -511,6 +539,7 @@ class BoB:
self.start_xfrout(c_channel_env)
self.start_xfrin(c_channel_env)
self.start_zonemgr(c_channel_env)
+ self.started_auth_family = True
# ... and finally start the remaining processes
self.start_stats(c_channel_env)
@@ -541,7 +570,8 @@ class BoB:
# Start all processes. If any one fails to start, kill all started
# processes and exit with an error indication.
try:
- self.start_all_processes(c_channel_env)
+ self.c_channel_env = c_channel_env
+ self.start_all_processes()
except Exception as e:
self.kill_started_processes()
return "Unable to start " + self.curproc + ": " + str(e)
@@ -563,10 +593,35 @@ class BoB:
self.cc_session.group_sendmsg(cmd, "Zonemgr", "Zonemgr")
self.cc_session.group_sendmsg(cmd, "Stats", "Stats")
- def stop_process(self, process):
- """Stop the given process, friendly-like."""
- # XXX nothing yet
- pass
+ def stop_process(self, process, recipient):
+ """
+ Stop the given process, friendly-like. The process is the name it has
+ (in logs, etc), the recipient is the address on msgq.
+ """
+ if self.verbose:
+ sys.stdout.write("[bind10] Asking %s to terminate\n" % process)
+ # TODO: Some timeout to solve processes that don't want to die would
+ # help. We can even store it in the dict, it is used only as a set
+ self.expected_shutdowns[process] = 1
+ # Ask the process to die willingly
+ self.cc_session.group_sendmsg({'command': ['shutdown']}, recipient,
+ recipient)
+
+ # Series of stop_process wrappers
+ def stop_resolver(self):
+ self.stop_process('b10-resolver', 'Resolver')
+
+ def stop_auth(self):
+ self.stop_process('b10-auth', 'Auth')
+
+ def stop_xfrout(self):
+ self.stop_process('b10-xfrout', 'Xfrout')
+
+ def stop_xfrin(self):
+ self.stop_process('b10-xfrin', 'Xfrin')
+
+ def stop_zonemgr(self):
+ self.stop_process('b10-zonemgr', 'Zonemgr')
def shutdown(self):
"""Stop the BoB instance."""
@@ -672,6 +727,10 @@ class BoB:
still_dead = {}
now = time.time()
for proc_info in self.dead_processes.values():
+ if proc_info.name in self.expected_shutdowns:
+ # We don't restart, we wanted it to die
+ del self.expected_shutdowns[proc_info.name]
+ continue
restart_time = proc_info.restart_schedule.get_restart_time(now)
if restart_time > now:
if (next_restart is None) or (next_restart > restart_time):
@@ -722,34 +781,39 @@ def fatal_signal(signal_number, stack_frame):
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
boss_of_bind.runnable = False
-def check_port(option, opt_str, value, parser):
- """Function to insure that the port we are passed is actually
- a valid port number. Used by OptionParser() on startup."""
- try:
- if opt_str in ['-p', '--port']:
- parser.values.dns_port = isc.net.parse.port_parse(value)
- else:
- raise OptionValueError("Unknown option " + opt_str)
- except ValueError as e:
- raise OptionValueError(str(e))
-
-def check_addr(option, opt_str, value, parser):
- """Function to insure that the address we are passed is actually
- a valid address. Used by OptionParser() on startup."""
- try:
- if opt_str in ['-a', '--address']:
- parser.values.address = isc.net.parse.addr_parse(value)
- elif opt_str in ['-f', '--forward']:
- parser.values.forward = isc.net.parse.addr_parse(value)
- else:
- raise OptionValueError("Unknown option " + opt_str)
- except ValueError:
- raise OptionValueError("%s requires a valid IPv4 or IPv6 address" % opt_str)
-
def process_rename(option, opt_str, value, parser):
"""Function that renames the process if it is requested by a option."""
isc.util.process.rename(value)
+def dump_pid(pid_file):
+ """
+ Dump the PID of the current process to the specified file. If the given
+ file is None this function does nothing. If the file already exists,
+ the existing content will be removed. If a system error happens in
+ creating or writing to the file, the corresponding exception will be
+ propagated to the caller.
+ """
+ if pid_file is None:
+ return
+ f = open(pid_file, "w")
+ f.write('%d\n' % os.getpid())
+ f.close()
+
+def unlink_pid_file(pid_file):
+ """
+ Remove the given file, which is basically expected to be the PID file
+ created by dump_pid(). The specified may or may not exist; if it
+ doesn't this function does nothing. Other system level errors in removing
+ the file will be propagated as the corresponding exception.
+ """
+ if pid_file is None:
+ return
+ try:
+ os.unlink(pid_file)
+ except OSError as error:
+ if error.errno is not errno.ENOENT:
+ raise
+
def main():
global options
global boss_of_bind
@@ -758,20 +822,11 @@ def main():
# Parse any command-line options.
parser = OptionParser(version=VERSION)
- parser.add_option("-a", "--address", dest="address", type="string",
- action="callback", callback=check_addr, default=None,
- help="address the DNS server will use (default: listen on all addresses)")
- parser.add_option("-f", "--forward", dest="forward", type="string",
- action="callback", callback=check_addr, default=None,
- help="nameserver to which DNS queries should be forwarded")
parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
type="string", default=None,
help="UNIX domain socket file the b10-msgq daemon will use")
parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
default=False, help="disable hot-spot cache in authoritative DNS server")
- parser.add_option("-p", "--port", dest="dns_port", type="int",
- action="callback", callback=check_port, default=5300,
- help="port the DNS server will use (default 5300)")
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",
@@ -779,6 +834,9 @@ def main():
parser.add_option("--pretty-name", type="string", action="callback",
callback=process_rename,
help="Set the process name (displayed in ps, top, ...)")
+ parser.add_option("--pid-file", dest="pid_file", type="string",
+ default=None,
+ help="file to dump the PID of the BIND 10 process")
(options, args) = parser.parse_args()
if args:
parser.print_help()
@@ -832,14 +890,14 @@ def main():
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
# Go bob!
- boss_of_bind = BoB(options.msgq_socket_file, options.dns_port,
- options.address, options.forward, options.nocache,
+ boss_of_bind = BoB(options.msgq_socket_file, options.nocache,
options.verbose, setuid, username)
startup_result = boss_of_bind.startup()
if startup_result:
sys.stderr.write("[bind10] Error on startup: %s\n" % startup_result)
sys.exit(1)
sys.stdout.write("[bind10] BIND 10 started\n")
+ dump_pid(options.pid_file)
# send "bind10.boot_time" to b10-stats
time.sleep(1) # wait a second
@@ -893,6 +951,7 @@ def main():
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
boss_of_bind.shutdown()
sys.stdout.write("[bind10] BIND 10 exiting\n");
+ unlink_pid_file(options.pid_file)
sys.exit(0)
if __name__ == "__main__":
diff --git a/src/bin/bind10/bind10.xml b/src/bin/bind10/bind10.xml
index dfc8acf..f3964a6 100644
--- a/src/bin/bind10/bind10.xml
+++ b/src/bin/bind10/bind10.xml
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>July 29, 2010</date>
+ <date>February 22, 2011</date>
</refentryinfo>
<refmeta>
@@ -36,24 +36,20 @@
<docinfo>
<copyright>
- <year>2010</year>
+ <year>2011</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
<refsynopsisdiv>
<cmdsynopsis>
- <command>bind10</command>
- <arg><option>-a <replaceable>address</replaceable></option></arg>
+ <command>bind10</command>
<arg><option>-m <replaceable>file</replaceable></option></arg>
<arg><option>-n</option></arg>
- <arg><option>-p <replaceable>number</replaceable></option></arg>
<arg><option>-u <replaceable>user</replaceable></option></arg>
<arg><option>-v</option></arg>
- <arg><option>--address <replaceable>address</replaceable></option></arg>
<arg><option>--msgq-socket-file <replaceable>file</replaceable></option></arg>
<arg><option>--no-cache</option></arg>
- <arg><option>--port <replaceable>number</replaceable></option></arg>
<arg><option>--user <replaceable>user</replaceable></option></arg>
<arg><option>--pretty-name <replaceable>name</replaceable></option></arg>
<arg><option>--verbose</option></arg>
@@ -86,19 +82,6 @@
<variablelist>
<varlistentry>
- <term><option>-a</option> <replaceable>address</replaceable>, <option>--address</option> <replaceable>address</replaceable></term>
-
- <listitem>
- <para>The IPv4 or IPv6 address for the
- <citerefentry><refentrytitle>b10-auth</refentrytitle><manvolnum>8</manvolnum></citerefentry>
- daemon to listen on.
- The default is to listen on all addresses.
- (This is a short term workaround. This argument may change.)
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
<term><option>-m</option> <replaceable>file</replaceable>,
<option>--msgq-socket-file</option> <replaceable>file</replaceable></term>
@@ -123,20 +106,6 @@
</varlistentry>
<varlistentry>
- <term><option>-p</option> <replaceable>number</replaceable>, <option>--port</option> <replaceable>number</replaceable></term>
-
- <listitem>
- <para>The port number for the
- <citerefentry><refentrytitle>b10-auth</refentrytitle><manvolnum>8</manvolnum></citerefentry>
- daemon to listen on.
- The default is 5300.</para>
-<!-- TODO: -->
- <note><simpara>This prototype release uses a non-default
- port for domain service.</simpara></note>
- </listitem>
- </varlistentry>
-
- <varlistentry>
<term><option>-u</option> <replaceable>user</replaceable>, <option>--user</option> <replaceable>name</replaceable></term>
<listitem>
@@ -155,7 +124,11 @@
<para>The name this process should have in tools like
<command>ps</command> or <command>top</command>. This
is handy if you have multiple versions/installations
- of <command>bind10</command>.</para>
+ of <command>bind10</command>.
+<!-- TODO: only supported with setproctitle feature
+The default is the basename of ARG 0.
+-->
+</para>
</listitem>
</varlistentry>
diff --git a/src/bin/bind10/tests/bind10_test.py b/src/bin/bind10/tests/bind10_test.py
deleted file mode 100644
index 5b38913..0000000
--- a/src/bin/bind10/tests/bind10_test.py
+++ /dev/null
@@ -1,315 +0,0 @@
-from bind10 import ProcessInfo, BoB
-
-# XXX: environment tests are currently disabled, due to the preprocessor
-# setup that we have now complicating the environment
-
-import unittest
-import sys
-import os
-import signal
-import socket
-from isc.net.addr import IPAddr
-
-class TestProcessInfo(unittest.TestCase):
- def setUp(self):
- # redirect stdout to a pipe so we can check that our
- # process spawning is doing the right thing with stdout
- self.old_stdout = os.dup(sys.stdout.fileno())
- self.pipes = os.pipe()
- os.dup2(self.pipes[1], sys.stdout.fileno())
- os.close(self.pipes[1])
- # note that we use dup2() to restore the original stdout
- # to the main program ASAP in each test... this prevents
- # hangs reading from the child process (as the pipe is only
- # open in the child), and also insures nice pretty output
-
- def tearDown(self):
- # clean up our stdout munging
- os.dup2(self.old_stdout, sys.stdout.fileno())
- os.close(self.pipes[0])
-
- def test_init(self):
- pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
- os.dup2(self.old_stdout, sys.stdout.fileno())
- self.assertEqual(pi.name, 'Test Process')
- self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
-# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
-# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
- self.assertEqual(pi.dev_null_stdout, False)
- self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
- self.assertNotEqual(pi.process, None)
- self.assertTrue(type(pi.pid) is int)
-
-# def test_setting_env(self):
-# pi = ProcessInfo('Test Process', [ '/bin/true' ], env={'FOO': 'BAR'})
-# os.dup2(self.old_stdout, sys.stdout.fileno())
-# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
-# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'],
-# 'FOO': 'BAR' })
-
- def test_setting_null_stdout(self):
- pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ],
- dev_null_stdout=True)
- os.dup2(self.old_stdout, sys.stdout.fileno())
- self.assertEqual(pi.dev_null_stdout, True)
- self.assertEqual(os.read(self.pipes[0], 100), b"")
-
- def test_respawn(self):
- pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
- # wait for old process to work...
- self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
- # respawn it
- old_pid = pi.pid
- pi.respawn()
- os.dup2(self.old_stdout, sys.stdout.fileno())
- # make sure the new one started properly
- self.assertEqual(pi.name, 'Test Process')
- self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
-# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
-# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
- self.assertEqual(pi.dev_null_stdout, False)
- self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
- self.assertNotEqual(pi.process, None)
- self.assertTrue(type(pi.pid) is int)
- self.assertNotEqual(pi.pid, old_pid)
-
-class TestBoB(unittest.TestCase):
- def test_init(self):
- bob = BoB()
- self.assertEqual(bob.verbose, False)
- self.assertEqual(bob.msgq_socket_file, None)
- self.assertEqual(bob.dns_port, 5300)
- self.assertEqual(bob.address, None)
- self.assertEqual(bob.cc_session, None)
- self.assertEqual(bob.ccs, None)
- self.assertEqual(bob.processes, {})
- self.assertEqual(bob.dead_processes, {})
- self.assertEqual(bob.runnable, False)
- self.assertEqual(bob.uid, None)
- self.assertEqual(bob.username, None)
- self.assertEqual(bob.nocache, False)
- self.assertEqual(bob.cfg_start_auth, True)
- self.assertEqual(bob.cfg_start_resolver, False)
-
- def test_init_alternate_socket(self):
- bob = BoB("alt_socket_file")
- self.assertEqual(bob.verbose, False)
- self.assertEqual(bob.msgq_socket_file, "alt_socket_file")
- self.assertEqual(bob.address, None)
- self.assertEqual(bob.dns_port, 5300)
- self.assertEqual(bob.cc_session, None)
- self.assertEqual(bob.ccs, None)
- self.assertEqual(bob.processes, {})
- self.assertEqual(bob.dead_processes, {})
- self.assertEqual(bob.runnable, False)
- self.assertEqual(bob.uid, None)
- self.assertEqual(bob.username, None)
- self.assertEqual(bob.nocache, False)
- self.assertEqual(bob.cfg_start_auth, True)
- self.assertEqual(bob.cfg_start_resolver, False)
-
- def test_init_alternate_dns_port(self):
- bob = BoB(None, 9999)
- self.assertEqual(bob.verbose, False)
- self.assertEqual(bob.msgq_socket_file, None)
- self.assertEqual(bob.dns_port, 9999)
- self.assertEqual(bob.address, None)
- self.assertEqual(bob.cc_session, None)
- self.assertEqual(bob.ccs, None)
- self.assertEqual(bob.processes, {})
- self.assertEqual(bob.dead_processes, {})
- self.assertEqual(bob.runnable, False)
- self.assertEqual(bob.uid, None)
- self.assertEqual(bob.username, None)
- self.assertEqual(bob.nocache, False)
- self.assertEqual(bob.cfg_start_auth, True)
- self.assertEqual(bob.cfg_start_resolver, False)
-
- def test_init_alternate_address(self):
- bob = BoB(None, 1234, IPAddr('127.127.127.127'))
- self.assertEqual(bob.verbose, False)
- self.assertEqual(bob.msgq_socket_file, None)
- self.assertEqual(bob.dns_port, 1234)
- self.assertEqual(bob.address.addr, socket.inet_aton('127.127.127.127'))
- self.assertEqual(bob.cc_session, None)
- self.assertEqual(bob.ccs, None)
- self.assertEqual(bob.processes, {})
- self.assertEqual(bob.dead_processes, {})
- self.assertEqual(bob.runnable, False)
- self.assertEqual(bob.uid, None)
- self.assertEqual(bob.username, None)
- self.assertEqual(bob.nocache, False)
- self.assertEqual(bob.cfg_start_auth, True)
- self.assertEqual(bob.cfg_start_resolver, False)
-
-# Class for testing the Bob.start_all_processes() method call.
-#
-# Although testing that external processes start is outside the scope
-# of the unit test, by overriding the process start methods we can check
-# that the right processes are started depending on the configuration
-# options.
-class StartAllProcessesBob(BoB):
- def __init__(self):
- BoB.__init__(self)
-
-# Set flags as to which of the overridden methods has been run.
- self.msgq = False
- self.cfgmgr = False
- self.ccsession = False
- self.auth = False
- self.resolver = False
- self.xfrout = False
- self.xfrin = False
- self.zonemgr = False
- self.stats = False
- self.cmdctl = False
-
- def read_bind10_config(self):
- # Configuration options are set directly
- pass
-
- def start_msgq(self, c_channel_env):
- self.msgq = True
-
- def start_cfgmgr(self, c_channel_env):
- self.cfgmgr = True
-
- def start_ccsession(self, c_channel_env):
- self.ccsession = True
-
- def start_auth(self, c_channel_env):
- self.auth = True
-
- def start_resolver(self, c_channel_env):
- self.resolver = True
-
- def start_xfrout(self, c_channel_env):
- self.xfrout = True
-
- def start_xfrin(self, c_channel_env):
- self.xfrin = True
-
- def start_zonemgr(self, c_channel_env):
- self.zonemgr = True
-
- def start_stats(self, c_channel_env):
- self.stats = True
-
- def start_cmdctl(self, c_channel_env):
- self.cmdctl = True
-
-# Check that the start_all_processes method starts the right combination
-# of processes.
-class TestStartAllProcessesBob(unittest.TestCase):
- def check_preconditions(self, bob):
- self.assertEqual(bob.msgq, False)
- self.assertEqual(bob.cfgmgr, False)
- self.assertEqual(bob.ccsession, False)
- self.assertEqual(bob.auth, False)
- self.assertEqual(bob.resolver, False)
- self.assertEqual(bob.xfrout, False)
- self.assertEqual(bob.xfrin, False)
- self.assertEqual(bob.zonemgr, False)
- self.assertEqual(bob.stats, False)
- self.assertEqual(bob.cmdctl, False)
-
- # Checks the processes started when starting neither auth nor resolver
- # is specified.
- def test_start_none(self):
- # Created Bob and ensure initialization correct
- bob = StartAllProcessesBob()
- self.check_preconditions(bob)
-
- # Start processes and check what was started
- c_channel_env = {}
- bob.cfg_start_auth = False
- bob.cfg_start_resolver = False
-
- bob.start_all_processes(c_channel_env)
-
- self.assertEqual(bob.msgq, True)
- self.assertEqual(bob.cfgmgr, True)
- self.assertEqual(bob.ccsession, True)
- self.assertEqual(bob.auth, False)
- self.assertEqual(bob.resolver, False)
- self.assertEqual(bob.xfrout, False)
- self.assertEqual(bob.xfrin, False)
- self.assertEqual(bob.zonemgr, False)
- self.assertEqual(bob.stats, True)
- self.assertEqual(bob.cmdctl, True)
-
- # Checks the processes started when starting only the auth process
- def test_start_auth(self):
- # Created Bob and ensure initialization correct
- bob = StartAllProcessesBob()
- self.check_preconditions(bob)
-
- # Start processes and check what was started
- c_channel_env = {}
- bob.cfg_start_auth = True
- bob.cfg_start_resolver = False
-
- bob.start_all_processes(c_channel_env)
-
- self.assertEqual(bob.msgq, True)
- self.assertEqual(bob.cfgmgr, True)
- self.assertEqual(bob.ccsession, True)
- self.assertEqual(bob.auth, True)
- self.assertEqual(bob.resolver, False)
- self.assertEqual(bob.xfrout, True)
- self.assertEqual(bob.xfrin, True)
- self.assertEqual(bob.zonemgr, True)
- self.assertEqual(bob.stats, True)
- self.assertEqual(bob.cmdctl, True)
-
- # Checks the processes started when starting only the resolver process
- def test_start_resolver(self):
- # Created Bob and ensure initialization correct
- bob = StartAllProcessesBob()
- self.check_preconditions(bob)
-
- # Start processes and check what was started
- c_channel_env = {}
- bob.cfg_start_auth = False
- bob.cfg_start_resolver = True
-
- bob.start_all_processes(c_channel_env)
-
- self.assertEqual(bob.msgq, True)
- self.assertEqual(bob.cfgmgr, True)
- self.assertEqual(bob.ccsession, True)
- self.assertEqual(bob.auth, False)
- self.assertEqual(bob.resolver, True)
- self.assertEqual(bob.xfrout, False)
- self.assertEqual(bob.xfrin, False)
- self.assertEqual(bob.zonemgr, False)
- self.assertEqual(bob.stats, True)
- self.assertEqual(bob.cmdctl, True)
-
- # Checks the processes started when starting both auth and resolver process
- def test_start_both(self):
- # Created Bob and ensure initialization correct
- bob = StartAllProcessesBob()
- self.check_preconditions(bob)
-
- # Start processes and check what was started
- c_channel_env = {}
- bob.cfg_start_auth = True
- bob.cfg_start_resolver = True
-
- bob.start_all_processes(c_channel_env)
-
- self.assertEqual(bob.msgq, True)
- self.assertEqual(bob.cfgmgr, True)
- self.assertEqual(bob.ccsession, True)
- self.assertEqual(bob.auth, True)
- self.assertEqual(bob.resolver, True)
- self.assertEqual(bob.xfrout, True)
- self.assertEqual(bob.xfrin, True)
- self.assertEqual(bob.zonemgr, True)
- self.assertEqual(bob.stats, True)
- self.assertEqual(bob.cmdctl, True)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
new file mode 100644
index 0000000..0603443
--- /dev/null
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -0,0 +1,463 @@
+from bind10 import ProcessInfo, BoB, dump_pid, unlink_pid_file
+
+# XXX: environment tests are currently disabled, due to the preprocessor
+# setup that we have now complicating the environment
+
+import unittest
+import sys
+import os
+import signal
+import socket
+from isc.net.addr import IPAddr
+
+class TestProcessInfo(unittest.TestCase):
+ def setUp(self):
+ # redirect stdout to a pipe so we can check that our
+ # process spawning is doing the right thing with stdout
+ self.old_stdout = os.dup(sys.stdout.fileno())
+ self.pipes = os.pipe()
+ os.dup2(self.pipes[1], sys.stdout.fileno())
+ os.close(self.pipes[1])
+ # note that we use dup2() to restore the original stdout
+ # to the main program ASAP in each test... this prevents
+ # hangs reading from the child process (as the pipe is only
+ # open in the child), and also insures nice pretty output
+
+ def tearDown(self):
+ # clean up our stdout munging
+ os.dup2(self.old_stdout, sys.stdout.fileno())
+ os.close(self.pipes[0])
+
+ def test_init(self):
+ pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
+ os.dup2(self.old_stdout, sys.stdout.fileno())
+ self.assertEqual(pi.name, 'Test Process')
+ self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
+# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
+# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
+ self.assertEqual(pi.dev_null_stdout, False)
+ self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
+ self.assertNotEqual(pi.process, None)
+ self.assertTrue(type(pi.pid) is int)
+
+# def test_setting_env(self):
+# pi = ProcessInfo('Test Process', [ '/bin/true' ], env={'FOO': 'BAR'})
+# os.dup2(self.old_stdout, sys.stdout.fileno())
+# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
+# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'],
+# 'FOO': 'BAR' })
+
+ def test_setting_null_stdout(self):
+ pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ],
+ dev_null_stdout=True)
+ os.dup2(self.old_stdout, sys.stdout.fileno())
+ self.assertEqual(pi.dev_null_stdout, True)
+ self.assertEqual(os.read(self.pipes[0], 100), b"")
+
+ def test_respawn(self):
+ pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
+ # wait for old process to work...
+ self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
+ # respawn it
+ old_pid = pi.pid
+ pi.respawn()
+ os.dup2(self.old_stdout, sys.stdout.fileno())
+ # make sure the new one started properly
+ self.assertEqual(pi.name, 'Test Process')
+ self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
+# self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
+# 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
+ self.assertEqual(pi.dev_null_stdout, False)
+ self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
+ self.assertNotEqual(pi.process, None)
+ self.assertTrue(type(pi.pid) is int)
+ self.assertNotEqual(pi.pid, old_pid)
+
+class TestBoB(unittest.TestCase):
+ def test_init(self):
+ bob = BoB()
+ self.assertEqual(bob.verbose, False)
+ self.assertEqual(bob.msgq_socket_file, None)
+ self.assertEqual(bob.cc_session, None)
+ self.assertEqual(bob.ccs, None)
+ self.assertEqual(bob.processes, {})
+ self.assertEqual(bob.dead_processes, {})
+ self.assertEqual(bob.runnable, False)
+ self.assertEqual(bob.uid, None)
+ self.assertEqual(bob.username, None)
+ self.assertEqual(bob.nocache, False)
+ self.assertEqual(bob.cfg_start_auth, True)
+ self.assertEqual(bob.cfg_start_resolver, False)
+
+ def test_init_alternate_socket(self):
+ bob = BoB("alt_socket_file")
+ self.assertEqual(bob.verbose, False)
+ self.assertEqual(bob.msgq_socket_file, "alt_socket_file")
+ self.assertEqual(bob.cc_session, None)
+ self.assertEqual(bob.ccs, None)
+ self.assertEqual(bob.processes, {})
+ self.assertEqual(bob.dead_processes, {})
+ self.assertEqual(bob.runnable, False)
+ self.assertEqual(bob.uid, None)
+ self.assertEqual(bob.username, None)
+ self.assertEqual(bob.nocache, False)
+ self.assertEqual(bob.cfg_start_auth, True)
+ self.assertEqual(bob.cfg_start_resolver, False)
+
+# Class for testing the BoB start/stop components routines.
+#
+# Although testing that external processes start is outside the scope
+# of the unit test, by overriding the process start methods we can check
+# that the right processes are started depending on the configuration
+# options.
+class StartStopCheckBob(BoB):
+ def __init__(self):
+ BoB.__init__(self)
+
+# Set flags as to which of the overridden methods has been run.
+ self.msgq = False
+ self.cfgmgr = False
+ self.ccsession = False
+ self.auth = False
+ self.resolver = False
+ self.xfrout = False
+ self.xfrin = False
+ self.zonemgr = False
+ self.stats = False
+ self.cmdctl = False
+ self.c_channel_env = {}
+
+ def read_bind10_config(self):
+ # Configuration options are set directly
+ pass
+
+ def start_msgq(self, c_channel_env):
+ self.msgq = True
+
+ def start_cfgmgr(self, c_channel_env):
+ self.cfgmgr = True
+
+ def start_ccsession(self, c_channel_env):
+ self.ccsession = True
+
+ def start_auth(self, c_channel_env):
+ self.auth = True
+
+ def start_resolver(self, c_channel_env):
+ self.resolver = True
+
+ def start_xfrout(self, c_channel_env):
+ self.xfrout = True
+
+ def start_xfrin(self, c_channel_env):
+ self.xfrin = True
+
+ def start_zonemgr(self, c_channel_env):
+ self.zonemgr = True
+
+ def start_stats(self, c_channel_env):
+ self.stats = True
+
+ def start_cmdctl(self, c_channel_env):
+ self.cmdctl = True
+
+ # We don't really use all of these stop_ methods. But it might turn out
+ # someone would add some stop_ method to BoB and we want that one overriden
+ # in case he forgets to update the tests.
+ def stop_msgq(self):
+ self.msgq = False
+
+ def stop_cfgmgr(self):
+ self.cfgmgr = False
+
+ def stop_ccsession(self):
+ self.ccsession = False
+
+ def stop_auth(self):
+ self.auth = False
+
+ def stop_resolver(self):
+ self.resolver = False
+
+ def stop_xfrout(self):
+ self.xfrout = False
+
+ def stop_xfrin(self):
+ self.xfrin = False
+
+ def stop_zonemgr(self):
+ self.zonemgr = False
+
+ def stop_stats(self):
+ self.stats = False
+
+ def stop_cmdctl(self):
+ self.cmdctl = False
+
+class TestStartStopProcessesBob(unittest.TestCase):
+ """
+ Check that the start_all_processes method starts the right combination
+ of processes and that the right processes are started and stopped
+ according to changes in configuration.
+ """
+ def check_started(self, bob, core, auth, resolver):
+ """
+ Check that the right sets of services are started. The ones that
+ should be running are specified by the core, auth and resolver parameters
+ (they are groups of processes, eg. auth means b10-auth, -xfrout, -xfrin
+ and -zonemgr).
+ """
+ self.assertEqual(bob.msgq, core)
+ self.assertEqual(bob.cfgmgr, core)
+ self.assertEqual(bob.ccsession, core)
+ self.assertEqual(bob.auth, auth)
+ self.assertEqual(bob.resolver, resolver)
+ self.assertEqual(bob.xfrout, auth)
+ self.assertEqual(bob.xfrin, auth)
+ self.assertEqual(bob.zonemgr, auth)
+ self.assertEqual(bob.stats, core)
+ self.assertEqual(bob.cmdctl, core)
+
+ def check_preconditions(self, bob):
+ self.check_started(bob, False, False, False)
+
+ def check_started_none(self, bob):
+ """
+ Check that the situation is according to configuration where no servers
+ should be started. Some processes still need to be running.
+ """
+ self.check_started(bob, True, False, False)
+
+ def check_started_both(self, bob):
+ """
+ Check the situation is according to configuration where both servers
+ (auth and resolver) are enabled.
+ """
+ self.check_started(bob, True, True, True)
+
+ def check_started_auth(self, bob):
+ """
+ Check the set of processes needed to run auth only is started.
+ """
+ self.check_started(bob, True, True, False)
+
+ def check_started_resolver(self, bob):
+ """
+ Check the set of processes needed to run resolver only is started.
+ """
+ self.check_started(bob, True, False, True)
+
+ # Checks the processes started when starting neither auth nor resolver
+ # is specified.
+ def test_start_none(self):
+ # Create BoB and ensure correct initialization
+ bob = StartStopCheckBob()
+ self.check_preconditions(bob)
+
+ # Start processes and check what was started
+ bob.cfg_start_auth = False
+ bob.cfg_start_resolver = False
+
+ bob.start_all_processes()
+ self.check_started_none(bob)
+
+ # Checks the processes started when starting only the auth process
+ def test_start_auth(self):
+ # Create BoB and ensure correct initialization
+ bob = StartStopCheckBob()
+ self.check_preconditions(bob)
+
+ # Start processes and check what was started
+ bob.cfg_start_auth = True
+ bob.cfg_start_resolver = False
+
+ bob.start_all_processes()
+
+ self.check_started_auth(bob)
+
+ # Checks the processes started when starting only the resolver process
+ def test_start_resolver(self):
+ # Create BoB and ensure correct initialization
+ bob = StartStopCheckBob()
+ self.check_preconditions(bob)
+
+ # Start processes and check what was started
+ bob.cfg_start_auth = False
+ bob.cfg_start_resolver = True
+
+ bob.start_all_processes()
+
+ self.check_started_resolver(bob)
+
+ # Checks the processes started when starting both auth and resolver process
+ def test_start_both(self):
+ # Create BoB and ensure correct initialization
+ bob = StartStopCheckBob()
+ self.check_preconditions(bob)
+
+ # Start processes and check what was started
+ bob.cfg_start_auth = True
+ bob.cfg_start_resolver = True
+
+ bob.start_all_processes()
+
+ self.check_started_both(bob)
+
+ def test_config_start(self):
+ """
+ Test that the configuration starts and stops processes according
+ to configuration changes.
+ """
+
+ # Create BoB and ensure correct initialization
+ bob = StartStopCheckBob()
+ self.check_preconditions(bob)
+
+ # Start processes (nothing much should be started, as in
+ # test_start_none)
+ bob.cfg_start_auth = False
+ bob.cfg_start_resolver = False
+
+ bob.start_all_processes()
+ bob.runnable = True
+ self.check_started_none(bob)
+
+ # Enable both at once
+ bob.config_handler({'start_auth': True, 'start_resolver': True})
+ self.check_started_both(bob)
+
+ # Not touched by empty change
+ bob.config_handler({})
+ self.check_started_both(bob)
+
+ # Not touched by change to the same configuration
+ bob.config_handler({'start_auth': True, 'start_resolver': True})
+ self.check_started_both(bob)
+
+ # Turn them both off again
+ bob.config_handler({'start_auth': False, 'start_resolver': False})
+ self.check_started_none(bob)
+
+ # Not touched by empty change
+ bob.config_handler({})
+ self.check_started_none(bob)
+
+ # Not touched by change to the same configuration
+ bob.config_handler({'start_auth': False, 'start_resolver': False})
+ self.check_started_none(bob)
+
+ # Start and stop auth separately
+ bob.config_handler({'start_auth': True})
+ self.check_started_auth(bob)
+
+ bob.config_handler({'start_auth': False})
+ self.check_started_none(bob)
+
+ # Start and stop resolver separately
+ bob.config_handler({'start_resolver': True})
+ self.check_started_resolver(bob)
+
+ bob.config_handler({'start_resolver': False})
+ self.check_started_none(bob)
+
+ # Alternate
+ bob.config_handler({'start_auth': True})
+ self.check_started_auth(bob)
+
+ bob.config_handler({'start_auth': False, 'start_resolver': True})
+ self.check_started_resolver(bob)
+
+ bob.config_handler({'start_auth': True, 'start_resolver': False})
+ self.check_started_auth(bob)
+
+ def test_config_start_once(self):
+ """
+ Tests that a process is started only once.
+ """
+ # Create BoB and ensure correct initialization
+ bob = StartStopCheckBob()
+ self.check_preconditions(bob)
+
+ # Start processes (both)
+ bob.cfg_start_auth = True
+ bob.cfg_start_resolver = True
+
+ bob.start_all_processes()
+ bob.runnable = True
+ self.check_started_both(bob)
+
+ bob.start_auth = lambda: self.fail("Started auth again")
+ bob.start_xfrout = lambda: self.fail("Started xfrout again")
+ bob.start_xfrin = lambda: self.fail("Started xfrin again")
+ bob.start_zonemgr = lambda: self.fail("Started zonemgr again")
+ bob.start_resolver = lambda: self.fail("Started resolver again")
+
+ # Send again we want to start them. Should not do it, as they are.
+ bob.config_handler({'start_auth': True})
+ bob.config_handler({'start_resolver': True})
+
+ def test_config_not_started_early(self):
+ """
+ Test that processes are not started by the config handler before
+ startup.
+ """
+ bob = StartStopCheckBob()
+ self.check_preconditions(bob)
+
+ bob.start_auth = lambda: self.fail("Started auth again")
+ bob.start_xfrout = lambda: self.fail("Started xfrout again")
+ bob.start_xfrin = lambda: self.fail("Started xfrin again")
+ bob.start_zonemgr = lambda: self.fail("Started zonemgr again")
+ bob.start_resolver = lambda: self.fail("Started resolver again")
+
+ bob.config_handler({'start_auth': True, 'start_resolver': True})
+
+class TestPIDFile(unittest.TestCase):
+ def setUp(self):
+ self.pid_file = '@builddir@' + os.sep + 'bind10.pid'
+ if os.path.exists(self.pid_file):
+ os.unlink(self.pid_file)
+
+ def tearDown(self):
+ if os.path.exists(self.pid_file):
+ os.unlink(self.pid_file)
+
+ def check_pid_file(self):
+ # dump PID to the file, and confirm the content is correct
+ dump_pid(self.pid_file)
+ my_pid = os.getpid()
+ self.assertEqual(my_pid, int(open(self.pid_file, "r").read()))
+
+ def test_dump_pid(self):
+ self.check_pid_file()
+
+ # make sure any existing content will be removed
+ open(self.pid_file, "w").write('dummy data\n')
+ self.check_pid_file()
+
+ def test_unlink_pid_file_notexist(self):
+ dummy_data = 'dummy_data\n'
+ open(self.pid_file, "w").write(dummy_data)
+ unlink_pid_file("no_such_pid_file")
+ # the file specified for unlink_pid_file doesn't exist,
+ # and the original content of the file should be intact.
+ self.assertEqual(dummy_data, open(self.pid_file, "r").read())
+
+ def test_dump_pid_with_none(self):
+ # Check the behavior of dump_pid() and unlink_pid_file() with None.
+ # This should be no-op.
+ dump_pid(None)
+ self.assertFalse(os.path.exists(self.pid_file))
+
+ dummy_data = 'dummy_data\n'
+ open(self.pid_file, "w").write(dummy_data)
+ unlink_pid_file(None)
+ self.assertEqual(dummy_data, open(self.pid_file, "r").read())
+
+ def test_dump_pid_failure(self):
+ # the attempt to open file will fail, which should result in exception.
+ self.assertRaises(IOError, dump_pid,
+ 'nonexistent_dir' + os.sep + 'bind10.pid')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/bin/bindctl/Makefile.am b/src/bin/bindctl/Makefile.am
index da9b63d..2f412ec 100644
--- a/src/bin/bindctl/Makefile.am
+++ b/src/bin/bindctl/Makefile.am
@@ -5,12 +5,13 @@ man_MANS = bindctl.1
EXTRA_DIST = $(man_MANS) bindctl.xml
-python_PYTHON = __init__.py bindcmd.py cmdparse.py exception.py moduleinfo.py mycollections.py
+python_PYTHON = __init__.py bindcmd.py cmdparse.py exception.py moduleinfo.py \
+ mycollections.py
pythondir = $(pyexecdir)/bindctl
-bindctldir = $(DESTDIR)$(pkgdatadir)
+bindctldir = $(pkgdatadir)
-CLEANFILES = bindctl
+CLEANFILES = bindctl bindctl_main.pyc
if ENABLE_MAN
@@ -19,8 +20,8 @@ bindctl.1: bindctl.xml
endif
-bindctl: bindctl-source.py
+bindctl: bindctl_main.py
$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
-e "s|@@SYSCONFDIR@@|@sysconfdir@|" \
- -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" bindctl-source.py >$@
+ -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" bindctl_main.py >$@
chmod a+x $@
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index fb6a892..83dab25 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -51,7 +51,6 @@ except ImportError:
my_readline = sys.stdin.readline
CSV_FILE_NAME = 'default_user.csv'
-FAIL_TO_CONNECT_WITH_CMDCTL = "Fail to connect with b10-cmdctl module, is it running?"
CONFIG_MODULE_NAME = 'config'
CONST_BINDCTL_HELP = """
usage: <module name> <command name> [param1 = value1 [, param2 = value2]]
@@ -88,20 +87,29 @@ class ValidatedHTTPSConnection(http.client.HTTPSConnection):
class BindCmdInterpreter(Cmd):
"""simple bindctl example."""
- def __init__(self, server_port = 'localhost:8080', pem_file = None):
+ def __init__(self, server_port='localhost:8080', pem_file=None,
+ csv_file_dir=None):
Cmd.__init__(self)
self.location = ""
self.prompt_end = '> '
- self.prompt = self.prompt_end
+ if sys.stdin.isatty():
+ self.prompt = self.prompt_end
+ else:
+ self.prompt = ""
self.ruler = '-'
self.modules = OrderedDict()
- self.add_module_info(ModuleInfo("help", desc = "Get help for bindctl"))
+ self.add_module_info(ModuleInfo("help", desc = "Get help for bindctl."))
self.server_port = server_port
self.conn = ValidatedHTTPSConnection(self.server_port,
ca_certs=pem_file)
self.session_id = self._get_session_id()
self.config_data = None
-
+ if csv_file_dir is not None:
+ self.csv_file_dir = csv_file_dir
+ else:
+ self.csv_file_dir = pwd.getpwnam(getpass.getuser()).pw_dir + \
+ os.sep + '.bind10' + os.sep
+
def _get_session_id(self):
'''Generate one session id for the connection. '''
rand = os.urandom(16)
@@ -119,8 +127,8 @@ class BindCmdInterpreter(Cmd):
self.cmdloop()
except FailToLogin as err:
- print(err)
- print(FAIL_TO_CONNECT_WITH_CMDCTL)
+ # error already printed when this was raised, ignoring
+ pass
except KeyboardInterrupt:
print('\nExit from bindctl')
@@ -173,9 +181,7 @@ class BindCmdInterpreter(Cmd):
time, username and password saved in 'default_user.csv' will be
used first.
'''
- csv_file_dir = pwd.getpwnam(getpass.getuser()).pw_dir
- csv_file_dir += os.sep + '.bind10' + os.sep
- users = self._get_saved_user_info(csv_file_dir, CSV_FILE_NAME)
+ users = self._get_saved_user_info(self.csv_file_dir, CSV_FILE_NAME)
for row in users:
param = {'username': row[0], 'password' : row[1]}
try:
@@ -209,7 +215,8 @@ class BindCmdInterpreter(Cmd):
raise FailToLogin()
if response.status == http.client.OK:
- self._save_user_info(username, passwd, csv_file_dir, CSV_FILE_NAME)
+ self._save_user_info(username, passwd, self.csv_file_dir,
+ CSV_FILE_NAME)
return True
def _update_commands(self):
@@ -270,8 +277,10 @@ class BindCmdInterpreter(Cmd):
return line
def postcmd(self, stop, line):
- '''Update the prompt after every command'''
- self.prompt = self.location + self.prompt_end
+ '''Update the prompt after every command, but only if we
+ have a tty as output'''
+ if sys.stdin.isatty():
+ self.prompt = self.location + self.prompt_end
return stop
def _prepare_module_commands(self, module_spec):
@@ -375,7 +384,14 @@ class BindCmdInterpreter(Cmd):
if cmd.command == "help" or ("help" in cmd.params.keys()):
self._handle_help(cmd)
elif cmd.module == CONFIG_MODULE_NAME:
- self.apply_config_cmd(cmd)
+ try:
+ self.apply_config_cmd(cmd)
+ except isc.cc.data.DataTypeError as dte:
+ print("Error: " + str(dte))
+ except isc.cc.data.DataNotFoundError as dnfe:
+ print("Error: " + str(dnfe))
+ except KeyError as ke:
+ print("Error: missing " + str(ke))
else:
self.apply_cmd(cmd)
@@ -396,9 +412,24 @@ class BindCmdInterpreter(Cmd):
def do_help(self, name):
print(CONST_BINDCTL_HELP)
- for k in self.modules.keys():
- print("\t", self.modules[k])
-
+ for k in self.modules.values():
+ n = k.get_name()
+ if len(n) >= CONST_BINDCTL_HELP_INDENT_WIDTH:
+ print(" %s" % n)
+ print(textwrap.fill(k.get_desc(),
+ initial_indent=" ",
+ subsequent_indent=" " +
+ " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
+ width=70))
+ else:
+ print(textwrap.fill("%s%s%s" %
+ (k.get_name(),
+ " "*(CONST_BINDCTL_HELP_INDENT_WIDTH - len(k.get_name())),
+ k.get_desc()),
+ initial_indent=" ",
+ subsequent_indent=" " +
+ " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
+ width=70))
def onecmd(self, line):
if line == 'EOF' or line.lower() == "quit":
@@ -411,7 +442,19 @@ class BindCmdInterpreter(Cmd):
Cmd.onecmd(self, line)
def remove_prefix(self, list, prefix):
- return [(val[len(prefix):]) for val in list]
+ """Removes the prefix already entered, and all elements from the
+ list that don't match it"""
+ if prefix.startswith('/'):
+ prefix = prefix[1:]
+
+ new_list = []
+ for val in list:
+ if val.startswith(prefix):
+ new_val = val[len(prefix):]
+ if new_val.startswith("/"):
+ new_val = new_val[1:]
+ new_list.append(new_val)
+ return new_list
def complete(self, text, state):
if 0 == state:
@@ -502,8 +545,7 @@ class BindCmdInterpreter(Cmd):
self._validate_cmd(cmd)
self._handle_cmd(cmd)
except (IOError, http.client.HTTPException) as err:
- print('Error!', err)
- print(FAIL_TO_CONNECT_WITH_CMDCTL)
+ print('Error: ', err)
except BindCtlException as err:
print("Error! ", err)
self._print_correct_usage(err)
@@ -541,87 +583,115 @@ class BindCmdInterpreter(Cmd):
Raises a KeyError if the command was not complete
'''
identifier = self.location
- try:
- if 'identifier' in cmd.params:
- if not identifier.endswith("/"):
- identifier += "/"
- if cmd.params['identifier'].startswith("/"):
- identifier = cmd.params['identifier']
- else:
- identifier += cmd.params['identifier']
-
- # Check if the module is known; for unknown modules
- # we currently deny setting preferences, as we have
- # no way yet to determine if they are ok.
- module_name = identifier.split('/')[1]
- if self.config_data is None or \
- not self.config_data.have_specification(module_name):
- print("Error: Module '" + module_name + "' unknown or not running")
- return
+ if 'identifier' in cmd.params:
+ if not identifier.endswith("/"):
+ identifier += "/"
+ if cmd.params['identifier'].startswith("/"):
+ identifier = cmd.params['identifier']
+ else:
+ if cmd.params['identifier'].startswith('['):
+ identifier = identifier[:-1]
+ identifier += cmd.params['identifier']
+
+ # Check if the module is known; for unknown modules
+ # we currently deny setting preferences, as we have
+ # no way yet to determine if they are ok.
+ module_name = identifier.split('/')[1]
+ if module_name != "" and (self.config_data is None or \
+ not self.config_data.have_specification(module_name)):
+ print("Error: Module '" + module_name + "' unknown or not running")
+ return
- if cmd.command == "show":
- values = self.config_data.get_value_maps(identifier)
- for value_map in values:
- line = value_map['name']
- if value_map['type'] in [ 'module', 'map', 'list' ]:
- line += "/"
- else:
- line += ":\t" + json.dumps(value_map['value'])
- line += "\t" + value_map['type']
- line += "\t"
- if value_map['default']:
- line += "(default)"
- if value_map['modified']:
- line += "(modified)"
- print(line)
- elif cmd.command == "add":
- self.config_data.add_value(identifier, cmd.params['value'])
- elif cmd.command == "remove":
- if 'value' in cmd.params:
- self.config_data.remove_value(identifier, cmd.params['value'])
+ if cmd.command == "show":
+ # check if we have the 'all' argument
+ show_all = False
+ if 'argument' in cmd.params:
+ if cmd.params['argument'] == 'all':
+ show_all = True
+ elif 'identifier' not in cmd.params:
+ # no 'all', no identifier, assume this is the
+ #identifier
+ identifier += cmd.params['argument']
else:
- self.config_data.remove_value(identifier, None)
- elif cmd.command == "set":
- if 'identifier' not in cmd.params:
- print("Error: missing identifier or value")
+ print("Error: unknown argument " + cmd.params['argument'] + ", or multiple identifiers given")
+ return
+ values = self.config_data.get_value_maps(identifier, show_all)
+ for value_map in values:
+ line = value_map['name']
+ if value_map['type'] in [ 'module', 'map' ]:
+ line += "/"
+ elif value_map['type'] == 'list' \
+ and value_map['value'] != []:
+ # do not print content of non-empty lists if
+ # we have more data to show
+ line += "/"
else:
- parsed_value = None
- try:
- parsed_value = json.loads(cmd.params['value'])
- except Exception as exc:
- # ok could be an unquoted string, interpret as such
- parsed_value = cmd.params['value']
- self.config_data.set_value(identifier, parsed_value)
- elif cmd.command == "unset":
- self.config_data.unset(identifier)
- elif cmd.command == "revert":
- self.config_data.clear_local_changes()
- elif cmd.command == "commit":
- self.config_data.commit()
- elif cmd.command == "diff":
- print(self.config_data.get_local_changes());
- elif cmd.command == "go":
- self.go(identifier)
- except isc.cc.data.DataTypeError as dte:
- print("Error: " + str(dte))
- except isc.cc.data.DataNotFoundError as dnfe:
- print("Error: " + identifier + " not found")
- except KeyError as ke:
- print("Error: missing " + str(ke))
- raise ke
+ line += "\t" + json.dumps(value_map['value'])
+ line += "\t" + value_map['type']
+ line += "\t"
+ if value_map['default']:
+ line += "(default)"
+ if value_map['modified']:
+ line += "(modified)"
+ print(line)
+ elif cmd.command == "show_json":
+ if identifier == "":
+ print("Need at least the module to show the configuration in JSON format")
+ else:
+ data, default = self.config_data.get_value(identifier)
+ print(json.dumps(data))
+ elif cmd.command == "add":
+ if 'value' in cmd.params:
+ self.config_data.add_value(identifier, cmd.params['value'])
+ else:
+ self.config_data.add_value(identifier)
+ elif cmd.command == "remove":
+ if 'value' in cmd.params:
+ self.config_data.remove_value(identifier, cmd.params['value'])
+ else:
+ self.config_data.remove_value(identifier, None)
+ elif cmd.command == "set":
+ if 'identifier' not in cmd.params:
+ print("Error: missing identifier or value")
+ else:
+ parsed_value = None
+ try:
+ parsed_value = json.loads(cmd.params['value'])
+ except Exception as exc:
+ # ok could be an unquoted string, interpret as such
+ parsed_value = cmd.params['value']
+ self.config_data.set_value(identifier, parsed_value)
+ elif cmd.command == "unset":
+ self.config_data.unset(identifier)
+ elif cmd.command == "revert":
+ self.config_data.clear_local_changes()
+ elif cmd.command == "commit":
+ self.config_data.commit()
+ elif cmd.command == "diff":
+ print(self.config_data.get_local_changes());
+ elif cmd.command == "go":
+ self.go(identifier)
def go(self, identifier):
'''Handles the config go command, change the 'current' location
- within the configuration tree'''
- # this is just to see if it exists
- self.config_data.get_value(identifier)
- # some sanitizing
- identifier = identifier.replace("//", "/")
- if not identifier.startswith("/"):
- identifier = "/" + identifier
- if identifier.endswith("/"):
- identifier = identifier[:-1]
- self.location = identifier
+ within the configuration tree. '..' will be interpreted as
+ 'up one level'.'''
+ id_parts = isc.cc.data.split_identifier(identifier)
+
+ new_location = ""
+ for id_part in id_parts:
+ if (id_part == ".."):
+ # go 'up' one level
+ new_location, a, b = new_location.rpartition("/")
+ else:
+ new_location += "/" + id_part
+ # check if exists, if not, revert and error
+ v,d = self.config_data.get_value(new_location)
+ if v is None:
+ print("Error: " + identifier + " not found")
+ return
+
+ self.location = new_location
def apply_cmd(self, cmd):
'''Handles a general module command'''
diff --git a/src/bin/bindctl/bindctl-source.py.in b/src/bin/bindctl/bindctl-source.py.in
deleted file mode 100644
index 83059d2..0000000
--- a/src/bin/bindctl/bindctl-source.py.in
+++ /dev/null
@@ -1,129 +0,0 @@
-#!@PYTHON@
-
-# Copyright (C) 2009 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""This is the main calling class for the bindctl configuration and
- command tool. It sets up a command interpreter and runs that."""
-
-import sys; sys.path.append ('@@PYTHONPATH@@')
-
-from bindctl.moduleinfo import *
-from bindctl.bindcmd import *
-import pprint
-from optparse import OptionParser, OptionValueError
-import isc.util.process
-
-isc.util.process.rename()
-
-# This is the version that gets displayed to the user.
-# The VERSION string consists of the module name, the module version
-# number, and the overall BIND 10 version number (set in configure.ac).
-VERSION = "bindctl 20101201 (BIND 10 @PACKAGE_VERSION@)"
-
-def prepare_config_commands(tool):
- '''Prepare fixed commands for local configuration editing'''
- module = ModuleInfo(name = CONFIG_MODULE_NAME, desc = "Configuration commands")
- cmd = CommandInfo(name = "show", desc = "Show configuration")
- param = ParamInfo(name = "identifier", type = "string", optional=True)
- cmd.add_param(param)
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "add", desc = "Add entry to configuration list")
- param = ParamInfo(name = "identifier", type = "string", optional=True)
- cmd.add_param(param)
- param = ParamInfo(name = "value", type = "string", optional=False)
- cmd.add_param(param)
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list")
- param = ParamInfo(name = "identifier", type = "string", optional=True)
- cmd.add_param(param)
- param = ParamInfo(name = "value", type = "string", optional=True)
- cmd.add_param(param)
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "set", desc = "Set a configuration value")
- param = ParamInfo(name = "identifier", type = "string", optional=True)
- cmd.add_param(param)
- param = ParamInfo(name = "value", type = "string", optional=False)
- cmd.add_param(param)
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "unset", desc = "Unset a configuration value")
- param = ParamInfo(name = "identifier", type = "string", optional=False)
- cmd.add_param(param)
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "diff", desc = "Show all local changes")
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "revert", desc = "Revert all local changes")
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "commit", desc = "Commit all local changes")
- module.add_command(cmd)
-
- cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part")
- param = ParamInfo(name = "identifier", type="string", optional=False)
- cmd.add_param(param)
- module.add_command(cmd)
-
- tool.add_module_info(module)
-
-def check_port(option, opt_str, value, parser):
- if (value < 0) or (value > 65535):
- raise OptionValueError('%s requires a port number (0-65535)' % opt_str)
- parser.values.port = value
-
-def check_addr(option, opt_str, value, parser):
- ipstr = value
- ip_family = socket.AF_INET
- if (ipstr.find(':') != -1):
- ip_family = socket.AF_INET6
-
- try:
- socket.inet_pton(ip_family, ipstr)
- except:
- raise OptionValueError("%s invalid ip address" % ipstr)
-
- parser.values.addr = value
-
-def set_bindctl_options(parser):
- parser.add_option('-p', '--port', dest = 'port', type = 'int',
- action = 'callback', callback=check_port,
- default = '8080', help = 'port for cmdctl of bind10')
-
- parser.add_option('-a', '--address', dest = 'addr', type = 'string',
- action = 'callback', callback=check_addr,
- default = '127.0.0.1', help = 'IP address for cmdctl of bind10')
-
- parser.add_option('-c', '--certificate-chain', dest = 'cert_chain',
- type = 'string', action = 'store',
- help = 'PEM formatted server certificate validation chain file')
-
-if __name__ == '__main__':
- try:
- parser = OptionParser(version = VERSION)
- set_bindctl_options(parser)
- (options, args) = parser.parse_args()
- server_addr = options.addr + ':' + str(options.port)
- tool = BindCmdInterpreter(server_addr, pem_file=options.cert_chain)
- prepare_config_commands(tool)
- tool.run()
- except Exception as e:
- print(e, "\nFailed to connect with b10-cmdctl module, is it running?")
-
-
diff --git a/src/bin/bindctl/bindctl.xml b/src/bin/bindctl/bindctl.xml
index 98d65f9..eff1de2 100644
--- a/src/bin/bindctl/bindctl.xml
+++ b/src/bin/bindctl/bindctl.xml
@@ -51,6 +51,7 @@
<arg><option>--address <replaceable>address</replaceable></option></arg>
<arg><option>--help</option></arg>
<arg><option>--certificate-chain <replaceable>file</replaceable></option></arg>
+ <arg><option>--csv-file-dir<replaceable>file</replaceable></option></arg>
<arg><option>--port <replaceable>number</replaceable></option></arg>
<arg><option>--version</option></arg>
</cmdsynopsis>
@@ -110,6 +111,22 @@
</varlistentry>
<varlistentry>
+ <term>
+ <option>--csv-file-dir</option><replaceable>file</replaceable>
+ </term>
+
+ <listitem>
+ <para>
+ The directory name in which the user/password CSV file
+ is stored (see AUTHENTICATION).
+ By default this option doesn't have any value,
+ in which case the ".bind10" directory under the user's
+ home directory will be used.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>-h</option>,
<option>--help</option></term>
<listitem><para>
@@ -148,8 +165,10 @@
<para>
The tool will authenticate using a username and password.
On the first successful login, it will save the details to
- <filename>~/.bind10/default_user.csv</filename>
+ a comma-separated-value (CSV) file
which will be used for later uses of <command>bindctl</command>.
+ The file name is <filename>default_user.csv</filename>
+ located under the directory specified by the --csv-file-dir option.
</para>
<!-- TODO: mention HTTPS? -->
diff --git a/src/bin/bindctl/bindctl_main.py.in b/src/bin/bindctl/bindctl_main.py.in
new file mode 100755
index 0000000..01307e9
--- /dev/null
+++ b/src/bin/bindctl/bindctl_main.py.in
@@ -0,0 +1,138 @@
+#!@PYTHON@
+
+# Copyright (C) 2009 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""This is the main calling class for the bindctl configuration and
+ command tool. It sets up a command interpreter and runs that."""
+
+import sys; sys.path.append ('@@PYTHONPATH@@')
+
+from bindctl.moduleinfo import *
+from bindctl.bindcmd import *
+import pprint
+from optparse import OptionParser, OptionValueError
+import isc.util.process
+
+isc.util.process.rename()
+
+# This is the version that gets displayed to the user.
+# The VERSION string consists of the module name, the module version
+# number, and the overall BIND 10 version number (set in configure.ac).
+VERSION = "bindctl 20110217 (BIND 10 @PACKAGE_VERSION@)"
+
+DEFAULT_IDENTIFIER_DESC = "The identifier specifies the config item. Child elements are separated with the '/' character. List indices can be specified with '[i]', where i is an integer specifying the index, starting with 0. Examples: 'Boss/start_auth', 'Recurse/listen_on[0]/address'. If no identifier is given, shows the item at the current location."
+
+def prepare_config_commands(tool):
+ '''Prepare fixed commands for local configuration editing'''
+ module = ModuleInfo(name = CONFIG_MODULE_NAME, desc = "Configuration commands.")
+ cmd = CommandInfo(name = "show", desc = "Show configuration.")
+ param = ParamInfo(name = "argument", type = "string", optional=True, desc = "If you specify the argument 'all' (before the identifier), recursively show all child elements for the given identifier.")
+ cmd.add_param(param)
+ param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "show_json", desc = "Show full configuration in JSON format.")
+ param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "add", desc = "Add an entry to configuration list. If no value is given, a default value is added.")
+ param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ cmd.add_param(param)
+ param = ParamInfo(name = "value", type = "string", optional=True, desc = "Specifies a value to add to the list. It must be in correct JSON format and complete.")
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list.")
+ param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ cmd.add_param(param)
+ param = ParamInfo(name = "value", type = "string", optional=True, desc = "Specifies a value to remove from the list. It must be in correct JSON format and complete.")
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "set", desc = "Set a configuration value.")
+ param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+ cmd.add_param(param)
+ param = ParamInfo(name = "value", type = "string", optional=False, desc = "Specifies a value to set. It must be in correct JSON format and complete.")
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "unset", desc = "Unset a configuration value (i.e. revert to the default, if any).")
+ param = ParamInfo(name = "identifier", type = "string", optional=False, desc = DEFAULT_IDENTIFIER_DESC)
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "diff", desc = "Show all local changes that have not been committed.")
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "revert", desc = "Revert all local changes.")
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "commit", desc = "Commit all local changes.")
+ module.add_command(cmd)
+
+ cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part.")
+ param = ParamInfo(name = "identifier", type="string", optional=False, desc = DEFAULT_IDENTIFIER_DESC)
+ cmd.add_param(param)
+ module.add_command(cmd)
+
+ tool.add_module_info(module)
+
+def check_port(option, opt_str, value, parser):
+ if (value < 0) or (value > 65535):
+ raise OptionValueError('%s requires a port number (0-65535)' % opt_str)
+ parser.values.port = value
+
+def check_addr(option, opt_str, value, parser):
+ ipstr = value
+ ip_family = socket.AF_INET
+ if (ipstr.find(':') != -1):
+ ip_family = socket.AF_INET6
+
+ try:
+ socket.inet_pton(ip_family, ipstr)
+ except:
+ raise OptionValueError("%s invalid ip address" % ipstr)
+
+ parser.values.addr = value
+
+def set_bindctl_options(parser):
+ parser.add_option('-p', '--port', dest='port', type='int',
+ action='callback', callback=check_port,
+ default='8080', help='port for cmdctl of bind10')
+
+ parser.add_option('-a', '--address', dest='addr', type='string',
+ action='callback', callback=check_addr,
+ default='127.0.0.1', help='IP address for cmdctl of bind10')
+
+ parser.add_option('-c', '--certificate-chain', dest='cert_chain',
+ type='string', action='store',
+ help='PEM formatted server certificate validation chain file')
+
+ parser.add_option('--csv-file-dir', dest='csv_file_dir', type='string',
+ default=None, action='store',
+ help='Directory to store the password CSV file')
+
+if __name__ == '__main__':
+ parser = OptionParser(version = VERSION)
+ set_bindctl_options(parser)
+ (options, args) = parser.parse_args()
+ server_addr = options.addr + ':' + str(options.port)
+ tool = BindCmdInterpreter(server_addr, pem_file=options.cert_chain,
+ csv_file_dir=options.csv_file_dir)
+ prepare_config_commands(tool)
+ tool.run()
diff --git a/src/bin/bindctl/cmdparse.py b/src/bin/bindctl/cmdparse.py
index ab891d7..c624cba 100644
--- a/src/bin/bindctl/cmdparse.py
+++ b/src/bin/bindctl/cmdparse.py
@@ -33,6 +33,7 @@ param_value_str = "(?P<param_value>[^\'\" ][^, ]+)"
param_value_with_quota_str = "[\"\'](?P<param_value>.+?)(?<!\\\)[\"\']"
next_params_str = "(?P<blank>\s*)(?P<comma>,?)(?P<next_params>.*)$"
+
PARAM_WITH_QUOTA_PATTERN = re.compile(param_name_str +
param_value_with_quota_str +
next_params_str)
@@ -40,8 +41,58 @@ PARAM_PATTERN = re.compile(param_name_str + param_value_str + next_params_str)
# Used for module and command name
NAME_PATTERN = re.compile("^\s*(?P<name>[\w]+)(?P<blank>\s*)(?P<others>.*)$")
+# this removes all whitespace in the given string, except when
+# between " quotes
+_remove_unquoted_whitespace = \
+ lambda text:'"'.join( it if i%2 else ''.join(it.split())
+ for i,it in enumerate(text.split('"')) )
+
+
+def _remove_list_and_map_whitespace(text):
+ """Returns a string where the whitespace between matching [ and ]
+ is removed, unless quoted"""
+ # regular expression aren't really the right tool, since we may have
+ # nested structures
+ result = []
+ start_pos = 0
+ pos = 0
+ list_count = 0
+ map_count = 0
+ cur_start_list_pos = None
+ cur_start_map_pos = None
+ for i in text:
+ if i == '[' and map_count == 0:
+ if list_count == 0:
+ result.append(text[start_pos:pos + 1])
+ cur_start_list_pos = pos + 1
+ list_count = list_count + 1
+ elif i == ']' and map_count == 0:
+ if list_count > 0:
+ list_count = list_count - 1
+ if list_count == 0:
+ result.append(_remove_unquoted_whitespace(text[cur_start_list_pos:pos + 1]))
+ start_pos = pos + 1
+ if i == '{' and list_count == 0:
+ if map_count == 0:
+ result.append(text[start_pos:pos + 1])
+ cur_start_map_pos = pos + 1
+ map_count = map_count + 1
+ elif i == '}' and list_count == 0:
+ if map_count > 0:
+ map_count = map_count - 1
+ if map_count == 0:
+ result.append(_remove_unquoted_whitespace(text[cur_start_map_pos:pos + 1]))
+ start_pos = pos + 1
+
+
+ pos = pos + 1
+ if start_pos <= len(text):
+ result.append(text[start_pos:len(text)])
+ return "".join(result)
+
+
class BindCmdParse:
- """ This class will parse the command line usr input into three part
+ """ This class will parse the command line user input into three parts:
module name, command, parameters
the first two parts are strings and parameter is one hash,
parameters part is optional
@@ -86,9 +137,12 @@ class BindCmdParse:
self._parse_params(param_str)
+ def _remove_list_whitespace(self, text):
+ return ""
def _parse_params(self, param_text):
"""convert a=b,c=d into one hash """
+ param_text = _remove_list_and_map_whitespace(param_text)
# Check parameter name "help"
param = NAME_PATTERN.match(param_text)
diff --git a/src/bin/bindctl/moduleinfo.py b/src/bin/bindctl/moduleinfo.py
index 015ef16..6e41dce 100644
--- a/src/bin/bindctl/moduleinfo.py
+++ b/src/bin/bindctl/moduleinfo.py
@@ -16,6 +16,8 @@
"""This module holds classes representing modules, commands and
parameters for use in bindctl"""
+import textwrap
+
try:
from collections import OrderedDict
except ImportError:
@@ -30,6 +32,9 @@ MODULE_NODE_NAME = 'module'
COMMAND_NODE_NAME = 'command'
PARAM_NODE_NAME = 'param'
+# this is used to align the descriptions in help output
+CONST_BINDCTL_HELP_INDENT_WIDTH=12
+
class ParamInfo:
"""One parameter of one command.
@@ -52,6 +57,12 @@ class ParamInfo:
def __str__(self):
return str("\t%s <type: %s> \t(%s)" % (self.name, self.type, self.desc))
+ def get_name(self):
+ return "%s <type: %s>" % (self.name, self.type)
+
+ def get_desc(self):
+ return self.desc
+
class CommandInfo:
"""One command which is provided by one bind10 module, it has zero
or more parameters
@@ -63,13 +74,18 @@ class CommandInfo:
self.params = OrderedDict()
# Set default parameter "help"
self.add_param(ParamInfo("help",
- desc = "Get help for command",
+ desc = "Get help for command.",
optional = True))
def __str__(self):
return str("%s \t(%s)" % (self.name, self.desc))
-
+ def get_name(self):
+ return self.name
+
+ def get_desc(self):
+ return self.desc;
+
def add_param(self, paraminfo):
"""Add a ParamInfo object to this CommandInfo"""
self.params[paraminfo.name] = paraminfo
@@ -144,22 +160,30 @@ class CommandInfo:
del params["help"]
if len(params) == 0:
- print("\tNo parameters for the command")
+ print("No parameters for the command")
return
- print("\n\tMandatory parameters:")
+ print("\nMandatory parameters:")
mandatory_infos = []
for info in params.values():
if not info.is_optional:
- print("\t", info)
+ print(" %s" % info.get_name())
+ print(textwrap.fill(info.get_desc(),
+ initial_indent=" ",
+ subsequent_indent=" ",
+ width=70))
mandatory_infos.append(info)
optional_infos = [info for info in params.values()
if info not in mandatory_infos]
if len(optional_infos) > 0:
- print("\n\tOptional parameters:")
+ print("\nOptional parameters:")
for info in optional_infos:
- print("\t", info)
+ print(" %s" % info.get_name())
+ print(textwrap.fill(info.get_desc(),
+ initial_indent=" ",
+ subsequent_indent=" ",
+ width=70))
class ModuleInfo:
@@ -172,11 +196,17 @@ class ModuleInfo:
self.desc = desc
self.commands = OrderedDict()
self.add_command(CommandInfo(name = "help",
- desc = "Get help for module"))
+ desc = "Get help for module."))
def __str__(self):
return str("%s \t%s" % (self.name, self.desc))
-
+
+ def get_name(self):
+ return self.name
+
+ def get_desc(self):
+ return self.desc
+
def add_command(self, command_info):
"""Add a CommandInfo to this ModuleInfo."""
self.commands[command_info.name] = command_info
@@ -201,8 +231,24 @@ class ModuleInfo:
def module_help(self):
"""Prints the help info for this module to stdout"""
print("Module ", self, "\nAvailable commands:")
- for k in self.commands.keys():
- print("\t", self.commands[k])
+ for k in self.commands.values():
+ n = k.get_name()
+ if len(n) >= CONST_BINDCTL_HELP_INDENT_WIDTH:
+ print(" %s" % n)
+ print(textwrap.fill(k.get_desc(),
+ initial_indent=" ",
+ subsequent_indent=" " +
+ " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
+ width=70))
+ else:
+ print(textwrap.fill("%s%s%s" %
+ (k.get_name(),
+ " "*(CONST_BINDCTL_HELP_INDENT_WIDTH - len(k.get_name())),
+ k.get_desc()),
+ initial_indent=" ",
+ subsequent_indent=" " +
+ " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
+ width=70))
def command_help(self, command):
"""Prints the help info for the command with the given name.
diff --git a/src/bin/bindctl/tests/Makefile.am b/src/bin/bindctl/tests/Makefile.am
index 5f93644..d2bb90f 100644
--- a/src/bin/bindctl/tests/Makefile.am
+++ b/src/bin/bindctl/tests/Makefile.am
@@ -1,5 +1,5 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = bindctl_test.py
+PYTESTS = bindctl_test.py cmdparse_test.py
EXTRA_DIST = $(PYTESTS)
# test using command-line arguments, so use check-local target instead of TESTS
@@ -11,6 +11,6 @@ if ENABLE_PYTHON_COVERAGE
endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
- env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_srcdir)/src/bin \
+ env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/bindctl:$(abs_top_srcdir)/src/bin \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/bindctl/tests/bindctl_test.py b/src/bin/bindctl/tests/bindctl_test.py
index 653c908..dd492c1 100644
--- a/src/bin/bindctl/tests/bindctl_test.py
+++ b/src/bin/bindctl/tests/bindctl_test.py
@@ -17,6 +17,12 @@
import unittest
import isc.cc.data
import os
+import pwd
+import getpass
+from optparse import OptionParser
+from isc.config.config_data import ConfigData, MultiConfigData
+from isc.config.module_spec import ModuleSpec
+from bindctl_main import set_bindctl_options
from bindctl import cmdparse
from bindctl import bindcmd
from bindctl.moduleinfo import *
@@ -238,14 +244,97 @@ class TestNameSequence(unittest.TestCase):
assert self.random_names[i] == module_names[i+1]
i = i + 1
- def test_apply_cfg_command(self):
+# tine class to fake a UIModuleCCSession, but only the config data
+# parts for the next set of tests
+class FakeCCSession(MultiConfigData):
+ def __init__(self):
+ self._local_changes = {}
+ self._current_config = {}
+ self._specifications = {}
+ self.add_foo_spec()
+
+ def add_foo_spec(self):
+ spec = { "module_name": "foo",
+ "config_data": [
+ { "item_name": "an_int",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 1
+ },
+ { "item_name": "a_list",
+ "item_type": "list",
+ "item_optional": False,
+ "item_default": [],
+ "list_item_spec":
+ { "item_name": "a_string",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "bar"
+ }
+ }
+ ]
+ }
+ self.set_specification(ModuleSpec(spec))
+
+
+class TestConfigCommands(unittest.TestCase):
+ def setUp(self):
+ self.tool = bindcmd.BindCmdInterpreter()
+ mod_info = ModuleInfo(name = "foo")
+ self.tool.add_module_info(mod_info)
+ self.tool.config_data = FakeCCSession()
+
+ def test_apply_cfg_command_int(self):
self.tool.location = '/'
- cmd = cmdparse.BindCmdParse("config set identifier=\"foo/bar\" value=\"5\"")
+
+ self.assertEqual((1, MultiConfigData.DEFAULT),
+ self.tool.config_data.get_value("/foo/an_int"))
+
+ cmd = cmdparse.BindCmdParse("config set identifier=\"foo/an_int\" value=\"5\"")
self.tool.apply_config_cmd(cmd)
-
-class FakeBindCmdInterpreter(bindcmd.BindCmdInterpreter):
- def __init__(self):
- pass
+ self.assertEqual((5, MultiConfigData.LOCAL),
+ self.tool.config_data.get_value("/foo/an_int"))
+
+ # this should raise a NotFoundError
+ cmd = cmdparse.BindCmdParse("config set identifier=\"foo/bar\" value=\"[]\"")
+ self.assertRaises(isc.cc.data.DataNotFoundError, self.tool.apply_config_cmd, cmd)
+
+ # this should raise a TypeError
+ cmd = cmdparse.BindCmdParse("config set identifier=\"foo/an_int\" value=\"[]\"")
+ self.assertRaises(isc.cc.data.DataTypeError, self.tool.apply_config_cmd, cmd)
+
+ # this is a very specific one for use with a set of list tests
+ # to try out the flexibility of the parser (only in the next test)
+ def clt(self, full_cmd_string, item_value):
+ cmd = cmdparse.BindCmdParse(full_cmd_string)
+ self.tool.apply_config_cmd(cmd)
+ self.assertEqual(([item_value], MultiConfigData.LOCAL),
+ self.tool.config_data.get_value("/foo/a_list"))
+
+ def test_apply_cfg_command_list(self):
+ self.tool.location = '/'
+
+ self.assertEqual(([], MultiConfigData.DEFAULT),
+ self.tool.config_data.get_value("/foo/a_list"))
+
+ self.clt("config set identifier=\"foo/a_list\" value=[\"a\"]", "a")
+ self.clt("config set identifier=\"foo/a_list\" value =[\"b\"]", "b")
+ self.clt("config set identifier=\"foo/a_list\" value= [\"c\"]", "c")
+ self.clt("config set identifier=\"foo/a_list\" value = [\"d\"]", "d")
+ self.clt("config set identifier =\"foo/a_list\" value=[\"e\"]", "e")
+ self.clt("config set identifier= \"foo/a_list\" value=[\"f\"]", "f")
+ self.clt("config set identifier = \"foo/a_list\" value=[\"g\"]", "g")
+ self.clt("config set identifier = \"foo/a_list\" value = [\"h\"]", "h")
+ self.clt("config set identifier = \"foo/a_list\" value=[\"i\" ]", "i")
+ self.clt("config set identifier = \"foo/a_list\" value=[ \"j\"]", "j")
+ self.clt("config set identifier = \"foo/a_list\" value=[ \"k\" ]", "k")
+
+ # this should raise a TypeError
+ cmd = cmdparse.BindCmdParse("config set identifier=\"foo/a_list\" value=\"a\"")
+ self.assertRaises(isc.cc.data.DataTypeError, self.tool.apply_config_cmd, cmd)
+
+ cmd = cmdparse.BindCmdParse("config set identifier=\"foo/a_list\" value=[1]")
+ self.assertRaises(isc.cc.data.DataTypeError, self.tool.apply_config_cmd, cmd)
class TestBindCmdInterpreter(unittest.TestCase):
@@ -257,9 +346,22 @@ class TestBindCmdInterpreter(unittest.TestCase):
writer.writerow(['name2'])
csvfile.close()
+ def test_csv_file_dir(self):
+ # Checking default value
+ if "HOME" in os.environ:
+ home_dir = os.environ["HOME"]
+ else:
+ home_dir = pwd.getpwnam(getpass.getuser()).pw_dir
+ self.assertEqual(home_dir + os.sep + '.bind10' + os.sep,
+ bindcmd.BindCmdInterpreter().csv_file_dir)
+
+ new_csv_dir = '/something/different/'
+ custom_cmd = bindcmd.BindCmdInterpreter(csv_file_dir=new_csv_dir)
+ self.assertEqual(new_csv_dir, custom_cmd.csv_file_dir)
+
def test_get_saved_user_info(self):
- cmd = FakeBindCmdInterpreter()
- users = cmd._get_saved_user_info('/notexist', 'cvs_file.cvs')
+ cmd = bindcmd.BindCmdInterpreter()
+ users = cmd._get_saved_user_info('/notexist', 'csv_file.csv')
self.assertEqual([], users)
csvfilename = 'csv_file.csv'
@@ -268,6 +370,40 @@ class TestBindCmdInterpreter(unittest.TestCase):
self.assertEqual([], users)
os.remove(csvfilename)
+
+class TestCommandLineOptions(unittest.TestCase):
+ class FakeParserError(Exception):
+ """An exception thrown from FakeOptionParser on parser error.
+ """
+ pass
+
+ class FakeOptionParser(OptionParser):
+ """This fake class emulates the OptionParser class with customized
+ error handling for the convenient of tests.
+ """
+ def __init__(self):
+ OptionParser.__init__(self)
+
+ def error(self, msg):
+ raise TestCommandLineOptions.FakeParserError
+
+ def setUp(self):
+ self.parser = self.FakeOptionParser()
+ set_bindctl_options(self.parser)
+
+ def test_csv_file_dir(self):
+ # by default the option is "undefined"
+ (options, _) = self.parser.parse_args([])
+ self.assertEqual(None, options.csv_file_dir)
+
+ # specify the option, valid case.
+ (options, _) = self.parser.parse_args(['--csv-file-dir', 'some_dir'])
+ self.assertEqual('some_dir', options.csv_file_dir)
+
+ # missing option arg; should trigger parser error.
+ self.assertRaises(self.FakeParserError, self.parser.parse_args,
+ ['--csv-file-dir'])
+
if __name__== "__main__":
unittest.main()
diff --git a/src/bin/bindctl/tests/cmdparse_test.py b/src/bin/bindctl/tests/cmdparse_test.py
new file mode 100644
index 0000000..9150ed3
--- /dev/null
+++ b/src/bin/bindctl/tests/cmdparse_test.py
@@ -0,0 +1,88 @@
+# Copyright (C) 2009 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
+import unittest
+from bindctl import cmdparse
+
+class TestCmdParse(unittest.TestCase):
+
+ def test_remove_unquoted_whitespace(self):
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a"), "a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" a"), "a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a "), "a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" a "), "a")
+ self.assertNotEqual(cmdparse._remove_unquoted_whitespace("a"), "a ")
+ self.assertNotEqual(cmdparse._remove_unquoted_whitespace(" a"), " a")
+ self.assertNotEqual(cmdparse._remove_unquoted_whitespace("a "), "a ")
+ self.assertNotEqual(cmdparse._remove_unquoted_whitespace(" a "), " a ")
+ self.assertNotEqual(cmdparse._remove_unquoted_whitespace(" a "), "b")
+
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("\"abc\""), "\"abc\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" \"abc\""), "\"abc\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("\"abc\" "), "\"abc\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" \"abc\" "), "\"abc\"")
+
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("\" abc\""), "\" abc\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" \"a bc\""), "\"a bc\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("\"ab c\" "), "\"ab c\"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" \"abc \" "), "\"abc \"")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace(" \" a b c \" "), "\" a b c \"")
+
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a\" abc\"a"), "a\" abc\"a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a \"a bc\"a"), "a\"a bc\"a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a\"ab c\" a"), "a\"ab c\"a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a \"abc \" a"), "a\"abc \"a")
+ self.assertEqual(cmdparse._remove_unquoted_whitespace("a \" a b c \" a"), "a\" a b c \"a")
+
+ # short-hand function to make the set of tests more readable
+ def rws(self, a, b):
+ self.assertEqual(cmdparse._remove_list_and_map_whitespace(a), b)
+
+ def test_remove_list_whitespace(self):
+ self.rws("a", "a")
+ self.rws(" a ", " a ")
+ self.rws(" [a] ", " [a] ")
+ self.rws(" [ a] ", " [a] ")
+ self.rws(" [ a ] ", " [a] ")
+ self.rws(" [ a b c ] ", " [abc] ")
+ self.rws(" [ a \"b c\" ] ", " [a\"b c\"] ")
+ self.rws("a [ a \"b c\" ] a", "a [a\"b c\"] a")
+ self.rws("a] [ a \"b c\" ] a", "a] [a\"b c\"] a")
+ self.rws(" [ a [b c] ] ", " [a[bc]] ")
+ self.rws(" [ a b][ c d ] ", " [ab][cd] ")
+ self.rws(" [ a b] [ c d ] ", " [ab] [cd] ")
+
+ self.rws("a", "a")
+ self.rws(" a ", " a ")
+ self.rws(" {a} ", " {a} ")
+ self.rws(" { a} ", " {a} ")
+ self.rws(" { a } ", " {a} ")
+ self.rws(" { a b c } ", " {abc} ")
+ self.rws(" { a \"b c\" } ", " {a\"b c\"} ")
+ self.rws("a { a \"b c\" } a", "a {a\"b c\"} a")
+ self.rws("a} { a \"b c\" } a", "a} {a\"b c\"} a")
+ self.rws(" { a {b c} } ", " {a{bc}} ")
+ self.rws(" { a b}{ c d } ", " {ab}{cd} ")
+ self.rws(" { a b} { c d } ", " {ab} {cd} ")
+
+ self.rws(" [ a b]{ c d } ", " [ab]{cd} ")
+ self.rws(" [ a b{ c d }] ", " [ab{cd}] ")
+ self.rws(" [ a b{ \"c d\" }] ", " [ab{\"c d\"}] ")
+
+
+if __name__== "__main__":
+ unittest.main()
+
diff --git a/src/bin/cfgmgr/Makefile.am b/src/bin/cfgmgr/Makefile.am
index e092070..a41448b 100644
--- a/src/bin/cfgmgr/Makefile.am
+++ b/src/bin/cfgmgr/Makefile.am
@@ -19,7 +19,6 @@ b10-cfgmgr.8: b10-cfgmgr.xml
endif
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-cfgmgr: b10-cfgmgr.py
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" b10-cfgmgr.py >$@
diff --git a/src/bin/cfgmgr/b10-cfgmgr.py.in b/src/bin/cfgmgr/b10-cfgmgr.py.in
index 659426d..e37ec48 100755
--- a/src/bin/cfgmgr/b10-cfgmgr.py.in
+++ b/src/bin/cfgmgr/b10-cfgmgr.py.in
@@ -26,10 +26,18 @@ import os
isc.util.process.rename()
# If B10_FROM_SOURCE is set in the environment, we use data files
-# from a directory relative to that, otherwise we use the ones
-# installed on the system
+# from a directory relative to the value of that variable, or, if defined,
+# relative to the value of B10_FROM_SOURCE_LOCALSTATEDIR. Otherwise
+# we use the ones installed on the system.
+# B10_FROM_SOURCE_LOCALSTATEDIR is specifically intended to be used for
+# tests where we want to use variuos types of configuration within the test
+# environment. (We may want to make it even more generic so that the path is
+# passed from the boss process)
if "B10_FROM_SOURCE" in os.environ:
- DATA_PATH = os.environ["B10_FROM_SOURCE"]
+ if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
+ DATA_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
+ else:
+ DATA_PATH = os.environ["B10_FROM_SOURCE"]
else:
PREFIX = "@prefix@"
DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
diff --git a/src/bin/cmdctl/Makefile.am b/src/bin/cmdctl/Makefile.am
index 76572ab..04cf5e2 100644
--- a/src/bin/cmdctl/Makefile.am
+++ b/src/bin/cmdctl/Makefile.am
@@ -4,7 +4,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-cmdctl
-b10_cmdctldir = $(DESTDIR)$(pkgdatadir)
+b10_cmdctldir = $(pkgdatadir)
# NOTE: this will overwrite on install
# So these generic copies are placed in share/bind10 instead of to etc
@@ -33,7 +33,6 @@ endif
cmdctl.spec: cmdctl.spec.pre
$(SED) -e "s|@@SYSCONFDIR@@|$(sysconfdir)|" cmdctl.spec.pre >$@
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-cmdctl: cmdctl.py
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" cmdctl.py >$@
diff --git a/src/bin/host/host.cc b/src/bin/host/host.cc
index 477586d..c513b5a 100644
--- a/src/bin/host/host.cc
+++ b/src/bin/host/host.cc
@@ -70,12 +70,15 @@ host_lookup(const char* const name, const char* const type) {
msg.toWire(renderer);
struct addrinfo hints, *res;
- int e;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = 0; // not using AI_NUMERICHOST in case to bootstrap
- e = getaddrinfo(server, server_port, &hints, &res);
+ if (getaddrinfo(server, server_port, &hints, &res) != 0) {
+ cerr << "address/port conversion for " << server << ":"
+ << server_port << " failed" << endl;
+ return (1);
+ }
if (verbose) {
cout << "Trying \"" << name << "\"\n";
diff --git a/src/bin/msgq/Makefile.am b/src/bin/msgq/Makefile.am
index 9ed4717..61d4f23 100644
--- a/src/bin/msgq/Makefile.am
+++ b/src/bin/msgq/Makefile.am
@@ -16,7 +16,6 @@ b10-msgq.8: msgq.xml
endif
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-msgq: msgq.py
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" msgq.py >$@
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index 8a8362a..06fe840 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -40,7 +40,7 @@ isc.util.process.rename()
# This is the version that gets displayed to the user.
# The VERSION string consists of the module name, the module version
# number, and the overall BIND 10 version number (set in configure.ac).
-VERSION = "b10-msgq 20100818 (BIND 10 @PACKAGE_VERSION@)"
+VERSION = "b10-msgq 20110127 (BIND 10 @PACKAGE_VERSION@)"
class MsgQReceiveError(Exception): pass
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index efae151..59fcf41 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -132,7 +132,7 @@ class SendNonblock(unittest.TestCase):
task()
# If we got here, then everything worked well and in time
# In that case, we terminate successfully
- sys.exit()
+ sys.exit(0) # needs exit code
else:
(pid, status) = os.waitpid(task_pid, 0)
self.assertEqual(0, status,
diff --git a/src/bin/resolver/Makefile.am b/src/bin/resolver/Makefile.am
index 0200675..54e15bd 100644
--- a/src/bin/resolver/Makefile.am
+++ b/src/bin/resolver/Makefile.am
@@ -37,7 +37,6 @@ spec_config.h: spec_config.h.pre
BUILT_SOURCES = spec_config.h
pkglibexec_PROGRAMS = b10-resolver
b10_resolver_SOURCES = resolver.cc resolver.h
-b10_resolver_SOURCES += response_classifier.cc response_classifier.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
@@ -49,11 +48,15 @@ b10_resolver_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
b10_resolver_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
b10_resolver_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
b10_resolver_LDADD += $(top_builddir)/src/lib/log/liblog.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/cache/libcache.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/resolve/libresolve.la
b10_resolver_LDADD += $(top_builddir)/src/bin/auth/change_user.o
b10_resolver_LDFLAGS = -pthread
# TODO: config.h.in is wrong because doesn't honor pkgdatadir
# and can't use @datadir@ because doesn't expand default ${prefix}
-b10_resolverdir = $(DESTDIR)$(pkgdatadir)
+b10_resolverdir = $(pkgdatadir)
b10_resolver_DATA = resolver.spec
diff --git a/src/bin/resolver/b10-resolver.8 b/src/bin/resolver/b10-resolver.8
index 493538b..849092c 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: January 19, 2011
+.\" Date: February 17, 2011
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-RESOLVER" "8" "January 19, 2011" "BIND10" "BIND10"
+.TH "B10\-RESOLVER" "8" "February 17, 2011" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -39,23 +39,6 @@ will exit\&.
.PP
It also receives its configurations from
\fBb10-cfgmgr\fR(8)\&.
-.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
-This prototype version only supports forwarding\&. Future versions will introduce full recursion, cache, lookup of local authoritative data (as in
-\fBb10\-auth\fR), and DNSSEC validation\&.
-.sp .5v
-.RE
.SH "OPTIONS"
.PP
The arguments are as follows:
@@ -91,15 +74,34 @@ to listen on\&. The list items are the
\fIaddress\fR
string and
\fIport\fR
-number\&. The defaults are address ::1 port 5300 and address 127\&.0\&.0\&.1 port 5300\&.
+number\&. The defaults are address ::1 port 53 and address 127\&.0\&.0\&.1 port 53\&.
.PP
\fIretries\fR
-is the number of times to retry (resend query) after a timeout\&. The default is 0 (do not retry)\&.
+is the number of times to retry (resend query) after a query timeout (\fItimeout_query\fR)\&. The default is 3\&.
+.PP
+
+\fIroot_addresses\fR
+is a list of addresses and ports for
+\fBb10\-resolver\fR
+to use directly as root servers to start resolving\&. The list items are the
+\fIaddress\fR
+string and
+\fIport\fR
+number\&. If empty, a hardcoded address for F\-root (192\&.5\&.5\&.241) is used\&.
+.PP
+
+\fItimeout_client\fR
+is the number of milliseconds to wait before timing out the incoming client query\&. If set to \-1, this timeout is disabled\&. The default is 4000\&. After this timeout, a SERVFAIL is sent back to the client asking the question\&. (The lookup may continue after the timeout, but a later answer is not returned for the now\-past query\&.)
.PP
-\fItimeout\fR
-is the number of milliseconds to wait for answer\&. If set to \-1, the timeout is disabled\&. The default is 2000\&.
+\fItimeout_lookup\fR
+is the number of milliseconds before it stops trying the query\&. If set to \-1, this timeout is disabled\&. The default is 30000\&.
+.PP
+
+
+\fItimeout_query\fR
+is the number of milliseconds to wait before it retries a query\&. If set to \-1, this timeout is disabled\&. The default is 2000\&.
.PP
The configuration command is:
.PP
@@ -119,8 +121,7 @@ BIND 10 Guide\&.
.PP
The
\fBb10\-resolver\fR
-daemon was first coded in September 2010\&. The initial implementation only provided forwarding\&.
-
+daemon was first coded in September 2010\&. The initial implementation only provided forwarding\&. Iteration was introduced in January 2011\&.
.SH "COPYRIGHT"
.br
diff --git a/src/bin/resolver/b10-resolver.xml b/src/bin/resolver/b10-resolver.xml
index dc2b724..bdf4f8a 100644
--- a/src/bin/resolver/b10-resolver.xml
+++ b/src/bin/resolver/b10-resolver.xml
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>January 19, 2011</date>
+ <date>February 17, 2011</date>
</refentryinfo>
<refmeta>
@@ -69,11 +69,13 @@
<citerefentry><refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
</para>
+<!--
<note><para>
- This prototype version only supports forwarding. Future versions
- will introduce full recursion, cache, lookup of local authoritative
- data (as in <command>b10-auth</command>), and DNSSEC validation.
+ Future versions will introduce lookup of local authoritative
+ data (as in <command>b10-auth</command>) and DNSSEC validation.
</para></note>
+-->
+
</refsect1>
<refsect1>
@@ -128,7 +130,7 @@ port
-->
</para>
-<!-- trac386:
+<!-- trac384:
once that is merged you can for instance do 'config add Resolver/forward_addresses { "port": 123 } and it will fill in the rest (in this case ::1 for the address)
@@ -139,19 +141,50 @@ once that is merged you can for instance do 'config add Resolver/forward_address
<command>b10-resolver</command> to listen on.
The list items are the <varname>address</varname> string
and <varname>port</varname> number.
- The defaults are address ::1 port 5300 and
- address 127.0.0.1 port 5300.
+ The defaults are address ::1 port 53 and
+ address 127.0.0.1 port 53.
+<!-- TODO: but defaults are not used, Trac #518 -->
</para>
<para>
<varname>retries</varname> is the number of times to retry
- (resend query) after a timeout.
- The default is 0 (do not retry).
+ (resend query) after a query timeout
+ (<varname>timeout_query</varname>).
+ The default is 3.
+ </para>
+
+ <para>
+ <varname>root_addresses</varname> is a list of addresses and ports
+ for <command>b10-resolver</command> to use directly as
+ root servers to start resolving.
+ The list items are the <varname>address</varname> string
+ and <varname>port</varname> number.
+ If empty, a hardcoded address for F-root (192.5.5.241) is used.
+ </para>
+
+ <para>
+ <varname>timeout_client</varname> is the number of milliseconds
+ to wait before timing out the incoming client query.
+ If set to -1, this timeout is disabled.
+ The default is 4000.
+ After this timeout, a SERVFAIL is sent back to the client asking
+ the question.
+ (The lookup may continue after the timeout, but a later answer
+ is not returned for the now-past query.)
+ </para>
+
+ <para>
+ <varname>timeout_lookup</varname> is the number of milliseconds
+ before it stops trying the query.
+ If set to -1, this timeout is disabled.
+ The default is 30000.
</para>
<para>
- <varname>timeout</varname> is the number of milliseconds to
- wait for answer. If set to -1, the timeout is disabled.
+<!-- previous timeout was renamed to timeout_query -->
+ <varname>timeout_query</varname> is the number of milliseconds to
+ wait before it retries a query.
+ If set to -1, this timeout is disabled.
The default is 2000.
</para>
@@ -200,9 +233,8 @@ once that is merged you can for instance do 'config add Resolver/forward_address
<para>
The <command>b10-resolver</command> daemon was first coded in
September 2010. The initial implementation only provided
- forwarding.
+ forwarding. Iteration was introduced in January 2011.
<!-- TODO: document when caching was added -->
-<!-- TODO: document when iteration was added -->
<!-- TODO: document when validation was added -->
</para>
</refsect1>
diff --git a/src/bin/resolver/main.cc b/src/bin/resolver/main.cc
index 03f9ab7..bbb7d13 100644
--- a/src/bin/resolver/main.cc
+++ b/src/bin/resolver/main.cc
@@ -45,6 +45,9 @@
#include <resolver/spec_config.h>
#include <resolver/resolver.h>
+#include <cache/resolver_cache.h>
+#include <nsas/nameserver_address_store.h>
+
#include <log/dummylog.h>
using namespace std;
@@ -56,11 +59,10 @@ using namespace asiolink;
namespace {
-// Default port current 5300 for testing purposes
static const string PROGRAM = "Resolver";
IOService io_service;
-static Resolver *resolver;
+static boost::shared_ptr<Resolver> resolver;
ConstElementPtr
my_config_handler(ConstElementPtr new_config) {
@@ -136,15 +138,58 @@ main(int argc, char* argv[]) {
specfile = string(RESOLVER_SPECFILE_LOCATION);
}
- resolver = new Resolver();
+ resolver = boost::shared_ptr<Resolver>(new Resolver());
dlog("Server created.");
SimpleCallback* checkin = resolver->getCheckinProvider();
DNSLookup* lookup = resolver->getDNSLookupProvider();
DNSAnswer* answer = resolver->getDNSAnswerProvider();
+ isc::nsas::NameserverAddressStore nsas(resolver);
+ resolver->setNameserverAddressStore(nsas);
+
+ isc::cache::ResolverCache cache;
+ resolver->setCache(cache);
+
+ // TODO priming query, remove root from direct
+ // Fake a priming query result here (TODO2 how to flag non-expiry?)
+ // propagation to runningquery. And check for forwarder mode?
+ isc::dns::QuestionPtr root_question(new isc::dns::Question(
+ isc::dns::Name("."),
+ isc::dns::RRClass::IN(),
+ isc::dns::RRType::NS()));
+ isc::dns::RRsetPtr root_ns_rrset(new isc::dns::RRset(isc::dns::Name("."),
+ isc::dns::RRClass::IN(),
+ isc::dns::RRType::NS(),
+ isc::dns::RRTTL(8888)));
+ root_ns_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::NS(),
+ isc::dns::RRClass::IN(),
+ "l.root-servers.net."));
+ isc::dns::RRsetPtr root_a_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
+ isc::dns::RRClass::IN(),
+ isc::dns::RRType::A(),
+ isc::dns::RRTTL(8888)));
+ root_a_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::A(),
+ isc::dns::RRClass::IN(),
+ "199.7.83.42"));
+ isc::dns::RRsetPtr root_aaaa_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
+ isc::dns::RRClass::IN(),
+ isc::dns::RRType::AAAA(),
+ isc::dns::RRTTL(8888)));
+ root_aaaa_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::AAAA(),
+ isc::dns::RRClass::IN(),
+ "2001:500:3::42"));
+ isc::dns::MessagePtr priming_result(new isc::dns::Message(isc::dns::Message::RENDER));
+ priming_result->addQuestion(root_question);
+ priming_result->addRRset(isc::dns::Message::SECTION_ANSWER, root_ns_rrset);
+ priming_result->addRRset(isc::dns::Message::SECTION_ADDITIONAL, root_a_rrset);
+ priming_result->addRRset(isc::dns::Message::SECTION_ADDITIONAL, root_aaaa_rrset);
+ cache.update(*priming_result);
+ cache.update(root_ns_rrset);
+ cache.update(root_a_rrset);
+ cache.update(root_aaaa_rrset);
+
DNSService dns_service(io_service, checkin, lookup, answer);
-
resolver->setDNSService(dns_service);
dlog("IOService created.");
@@ -173,7 +218,6 @@ main(int argc, char* argv[]) {
delete config_session;
delete cc_session;
- delete resolver;
return (ret);
}
diff --git a/src/bin/resolver/resolver.cc b/src/bin/resolver/resolver.cc
index 9e9bbd4..7656e2b 100644
--- a/src/bin/resolver/resolver.cc
+++ b/src/bin/resolver/resolver.cc
@@ -21,7 +21,6 @@
#include <cassert>
#include <asiolink/asiolink.h>
-#include <asiolink/ioaddress.h>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
@@ -40,6 +39,9 @@
#include <dns/rrttl.h>
#include <dns/message.h>
#include <dns/messagerenderer.h>
+#include <server_common/portconfig.h>
+
+#include <resolve/recursive_query.h>
#include <log/dummylog.h>
@@ -53,8 +55,7 @@ using namespace isc::data;
using namespace isc::config;
using isc::log::dlog;
using namespace asiolink;
-
-typedef pair<string, uint16_t> addr_t;
+using namespace isc::server_common::portconfig;
class ResolverImpl {
private:
@@ -75,10 +76,15 @@ public:
queryShutdown();
}
- void querySetup(DNSService& dnss) {
+ void querySetup(DNSService& dnss,
+ isc::nsas::NameserverAddressStore& nsas,
+ isc::cache::ResolverCache& cache)
+ {
assert(!rec_query_); // queryShutdown must be called first
dlog("Query setup");
- rec_query_ = new RecursiveQuery(dnss, upstream_,
+ rec_query_ = new RecursiveQuery(dnss,
+ nsas, cache,
+ upstream_,
upstream_root_,
query_timeout_,
client_timeout_,
@@ -97,14 +103,14 @@ public:
}
}
- void setForwardAddresses(const vector<addr_t>& upstream,
+ void setForwardAddresses(const AddressList& upstream,
DNSService *dnss)
{
upstream_ = upstream;
if (dnss) {
if (!upstream_.empty()) {
dlog("Setting forward addresses:");
- BOOST_FOREACH(const addr_t& address, upstream) {
+ BOOST_FOREACH(const AddressPair& address, upstream) {
dlog(" " + address.first + ":" +
boost::lexical_cast<string>(address.second));
}
@@ -114,14 +120,14 @@ public:
}
}
- void setRootAddresses(const vector<addr_t>& upstream_root,
+ void setRootAddresses(const AddressList& upstream_root,
DNSService *dnss)
{
upstream_root_ = upstream_root;
if (dnss) {
if (!upstream_root_.empty()) {
dlog("Setting root addresses:");
- BOOST_FOREACH(const addr_t& address, upstream_root) {
+ BOOST_FOREACH(const AddressPair& address, upstream_root) {
dlog(" " + address.first + ":" +
boost::lexical_cast<string>(address.second));
}
@@ -130,7 +136,7 @@ public:
}
}
}
-
+
void resolve(const isc::dns::QuestionPtr& question,
const isc::resolve::ResolverInterface::CallbackPtr& callback);
@@ -145,11 +151,11 @@ public:
/// These members are public because Resolver accesses them directly.
ModuleCCSession* config_session_;
/// Addresses of the root nameserver(s)
- vector<addr_t> upstream_root_;
+ AddressList upstream_root_;
/// Addresses of the forward nameserver
- vector<addr_t> upstream_;
+ AddressList upstream_;
/// Addresses we listen on
- vector<addr_t> listen_;
+ AddressList listen_;
/// Timeout for outgoing queries in milliseconds
int query_timeout_;
@@ -183,9 +189,11 @@ public:
MessagePtr message_;
};
+
+// TODO: REMOVE, USE isc::resolve::MakeErrorMessage?
void
-makeErrorMessage(MessagePtr message, OutputBufferPtr buffer,
- const Rcode& rcode)
+makeErrorMessage(MessagePtr message, MessagePtr answer_message,
+ OutputBufferPtr buffer, const Rcode& rcode)
{
// extract the parameters that should be kept.
// XXX: with the current implementation, it's not easy to set EDNS0
@@ -196,6 +204,12 @@ makeErrorMessage(MessagePtr message, OutputBufferPtr buffer,
const Opcode& opcode = message->getOpcode();
vector<QuestionPtr> questions;
+ // answer_message is actually ignored right now,
+ // see the comment in #607
+ answer_message->setRcode(rcode);
+ answer_message->setOpcode(opcode);
+ answer_message->setQid(qid);
+
// If this is an error to a query or notify, we should also copy the
// question section.
if (opcode == Opcode::QUERY() || opcode == Opcode::NOTIFY()) {
@@ -257,25 +271,16 @@ public:
const qid_t qid = query_message->getQid();
const bool rd = query_message->getHeaderFlag(Message::HEADERFLAG_RD);
const bool cd = query_message->getHeaderFlag(Message::HEADERFLAG_CD);
- const Opcode& opcode = query_message->getOpcode();
-
- // Fill in the final details of the answer message
+
+ // The opcode and question section should have already been set,
+ // fill in the final details of the answer message
answer_message->setQid(qid);
- answer_message->setOpcode(opcode);
answer_message->setHeaderFlag(Message::HEADERFLAG_QR);
answer_message->setHeaderFlag(Message::HEADERFLAG_RA);
- if (rd) {
- answer_message->setHeaderFlag(Message::HEADERFLAG_RD);
- }
- if (cd) {
- answer_message->setHeaderFlag(Message::HEADERFLAG_CD);
- }
+ answer_message->setHeaderFlag(Message::HEADERFLAG_RD, rd);
+ answer_message->setHeaderFlag(Message::HEADERFLAG_CD, cd);
- vector<QuestionPtr> questions;
- questions.assign(query_message->beginQuestion(), query_message->endQuestion());
- for_each(questions.begin(), questions.end(), QuestionInserter(answer_message));
-
// Now we can clear the buffer and render the new message into it
buffer->clear();
MessageRenderer renderer(*buffer);
@@ -344,6 +349,19 @@ Resolver::setDNSService(asiolink::DNSService& dnss) {
}
void
+Resolver::setNameserverAddressStore(isc::nsas::NameserverAddressStore& nsas)
+{
+ nsas_ = &nsas;
+}
+
+void
+Resolver::setCache(isc::cache::ResolverCache& cache)
+{
+ cache_ = &cache;
+}
+
+
+void
Resolver::setConfigSession(ModuleCCSession* config_session) {
impl_->config_session_ = config_session;
}
@@ -393,12 +411,14 @@ Resolver::processMessage(const IOMessage& io_message,
} catch (const DNSProtocolError& error) {
dlog(string("returning ") + error.getRcode().toText() + ": " +
error.what());
- makeErrorMessage(query_message, buffer, error.getRcode());
+ makeErrorMessage(query_message, answer_message,
+ buffer, error.getRcode());
server->resume(true);
return;
} catch (const Exception& ex) {
dlog(string("returning SERVFAIL: ") + ex.what());
- makeErrorMessage(query_message, buffer, Rcode::SERVFAIL());
+ makeErrorMessage(query_message, answer_message,
+ buffer, Rcode::SERVFAIL());
server->resume(true);
return;
} // other exceptions will be handled at a higher layer.
@@ -408,28 +428,34 @@ Resolver::processMessage(const IOMessage& io_message,
// Perform further protocol-level validation.
bool sendAnswer = true;
if (query_message->getOpcode() == Opcode::NOTIFY()) {
- makeErrorMessage(query_message, buffer, Rcode::NOTAUTH());
+ makeErrorMessage(query_message, answer_message,
+ buffer, Rcode::NOTAUTH());
dlog("Notify arrived, but we are not authoritative");
} else if (query_message->getOpcode() != Opcode::QUERY()) {
dlog("Unsupported opcode (got: " + query_message->getOpcode().toText() +
", expected: " + Opcode::QUERY().toText());
- makeErrorMessage(query_message, buffer, Rcode::NOTIMP());
+ makeErrorMessage(query_message, answer_message,
+ buffer, Rcode::NOTIMP());
} else if (query_message->getRRCount(Message::SECTION_QUESTION) != 1) {
dlog("The query contained " +
boost::lexical_cast<string>(query_message->getRRCount(
Message::SECTION_QUESTION) + " questions, exactly one expected"));
- makeErrorMessage(query_message, buffer, Rcode::FORMERR());
+ makeErrorMessage(query_message, answer_message,
+ buffer, Rcode::FORMERR());
} else {
ConstQuestionPtr question = *query_message->beginQuestion();
const RRType &qtype = question->getType();
if (qtype == RRType::AXFR()) {
if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
- makeErrorMessage(query_message, buffer, Rcode::FORMERR());
+ makeErrorMessage(query_message, answer_message,
+ buffer, Rcode::FORMERR());
} else {
- makeErrorMessage(query_message, buffer, Rcode::NOTIMP());
+ makeErrorMessage(query_message, answer_message,
+ buffer, Rcode::NOTIMP());
}
} else if (qtype == RRType::IXFR()) {
- makeErrorMessage(query_message, buffer, Rcode::NOTIMP());
+ makeErrorMessage(query_message, answer_message,
+ buffer, Rcode::NOTIMP());
} else {
// The RecursiveQuery object will post the "resume" event to the
// DNSServer when an answer arrives, so we don't have to do it now.
@@ -461,46 +487,6 @@ ResolverImpl::processNormalQuery(const Question& question,
rec_query_->resolve(question, answer_message, buffer, server);
}
-namespace {
-
-vector<addr_t>
-parseAddresses(ConstElementPtr addresses) {
- vector<addr_t> result;
- if (addresses) {
- if (addresses->getType() == Element::list) {
- for (size_t i(0); i < addresses->size(); ++ i) {
- ConstElementPtr addrPair(addresses->get(i));
- ConstElementPtr addr(addrPair->get("address"));
- ConstElementPtr port(addrPair->get("port"));
- if (!addr || ! port) {
- isc_throw(BadValue, "Address must contain both the IP"
- "address and port");
- }
- try {
- IOAddress(addr->stringValue());
- if (port->intValue() < 0 ||
- port->intValue() > 0xffff) {
- isc_throw(BadValue, "Bad port value (" <<
- port->intValue() << ")");
- }
- result.push_back(addr_t(addr->stringValue(),
- port->intValue()));
- }
- catch (const TypeError &e) { // Better error message
- isc_throw(TypeError,
- "Address must be a string and port an integer");
- }
- }
- } else if (addresses->getType() != Element::null) {
- isc_throw(TypeError,
- "root_addresses, forward_addresses, and listen_on config element must be a list");
- }
- }
- return (result);
-}
-
-}
-
ConstElementPtr
Resolver::updateConfig(ConstElementPtr config) {
dlog("New config comes: " + config->toWire());
@@ -508,11 +494,14 @@ Resolver::updateConfig(ConstElementPtr config) {
try {
// Parse forward_addresses
ConstElementPtr rootAddressesE(config->get("root_addresses"));
- vector<addr_t> rootAddresses(parseAddresses(rootAddressesE));
+ AddressList rootAddresses(parseAddresses(rootAddressesE,
+ "root_addresses"));
ConstElementPtr forwardAddressesE(config->get("forward_addresses"));
- vector<addr_t> forwardAddresses(parseAddresses(forwardAddressesE));
+ AddressList forwardAddresses(parseAddresses(forwardAddressesE,
+ "forward_addresses"));
ConstElementPtr listenAddressesE(config->get("listen_on"));
- vector<addr_t> listenAddresses(parseAddresses(listenAddressesE));
+ AddressList listenAddresses(parseAddresses(listenAddressesE,
+ "listen_on"));
bool set_timeouts(false);
int qtimeout = impl_->query_timeout_;
int ctimeout = impl_->client_timeout_;
@@ -575,7 +564,7 @@ Resolver::updateConfig(ConstElementPtr config) {
if (need_query_restart) {
impl_->queryShutdown();
- impl_->querySetup(*dnss_);
+ impl_->querySetup(*dnss_, *nsas_, *cache_);
}
return (isc::config::createAnswer());
} catch (const isc::Exception& error) {
@@ -585,13 +574,13 @@ Resolver::updateConfig(ConstElementPtr config) {
}
void
-Resolver::setForwardAddresses(const vector<addr_t>& addresses)
+Resolver::setForwardAddresses(const AddressList& addresses)
{
impl_->setForwardAddresses(addresses, dnss_);
}
void
-Resolver::setRootAddresses(const vector<addr_t>& addresses)
+Resolver::setRootAddresses(const AddressList& addresses)
{
impl_->setRootAddresses(addresses, dnss_);
}
@@ -601,58 +590,19 @@ Resolver::isForwarding() const {
return (!impl_->upstream_.empty());
}
-vector<addr_t>
+AddressList
Resolver::getForwardAddresses() const {
return (impl_->upstream_);
}
-vector<addr_t>
+AddressList
Resolver::getRootAddresses() const {
return (impl_->upstream_root_);
}
-namespace {
-
-void
-setAddresses(DNSService *service, const vector<addr_t>& addresses) {
- service->clearServers();
- BOOST_FOREACH(const addr_t &address, addresses) {
- service->addServer(address.second, address.first);
- }
-}
-
-}
-
void
-Resolver::setListenAddresses(const vector<addr_t>& addresses) {
- try {
- dlog("Setting listen addresses:");
- BOOST_FOREACH(const addr_t& addr, addresses) {
- dlog(" " + addr.first + ":" +
- boost::lexical_cast<string>(addr.second));
- }
- setAddresses(dnss_, addresses);
- impl_->listen_ = addresses;
- }
- catch (const exception& e) {
- /*
- * We couldn't set it. So return it back. If that fails as well,
- * we have a problem.
- *
- * If that fails, bad luck, but we are useless anyway, so just die
- * and let boss start us again.
- */
- dlog(string("Unable to set new address: ") + e.what(),true);
- try {
- setAddresses(dnss_, impl_->listen_);
- }
- catch (const exception& e2) {
- dlog(string("Unable to recover from error;"),true);
- dlog(string("Rollback failed with: ") + e2.what(),true);
- abort();
- }
- throw e; // Let it fly a little bit further
- }
+Resolver::setListenAddresses(const AddressList& addresses) {
+ installListenAddresses(addresses, impl_->listen_, *dnss_);
}
void
@@ -688,7 +638,7 @@ Resolver::getRetries() const {
return impl_->retries_;
}
-vector<addr_t>
+AddressList
Resolver::getListenAddresses() const {
return (impl_->listen_);
}
diff --git a/src/bin/resolver/resolver.h b/src/bin/resolver/resolver.h
index 380f667..d851656 100644
--- a/src/bin/resolver/resolver.h
+++ b/src/bin/resolver/resolver.h
@@ -24,6 +24,9 @@
#include <asiolink/asiolink.h>
+#include <nsas/nameserver_address_store.h>
+#include <cache/resolver_cache.h>
+
#include <resolve/resolver_interface.h>
class ResolverImpl;
@@ -65,8 +68,10 @@ public:
/// send the reply.
///
/// \param io_message The raw message received
- /// \param query_message Pointer to the \c Message object TODO
- /// \param answer_message Pointer to the \c Message object TODO
+ /// \param query_message Pointer to the query Message object we
+ /// received from the client
+ /// \param answer_message Pointer to the anwer Message object we
+ /// shall return to the client
/// \param buffer Pointer to an \c OutputBuffer for the resposne
/// \param server Pointer to the \c DNSServer
void processMessage(const asiolink::IOMessage& io_message,
@@ -84,10 +89,26 @@ public:
/// \brief Assign an ASIO IO Service queue to this Resolver object
void setDNSService(asiolink::DNSService& dnss);
+
+ /// \brief Assign a NameserverAddressStore to this Resolver object
+ void setNameserverAddressStore(isc::nsas::NameserverAddressStore &nsas);
+
+ /// \brief Assign a cache to this Resolver object
+ void setCache(isc::cache::ResolverCache& cache);
/// \brief Return this object's ASIO IO Service queue
asiolink::DNSService& getDNSService() const { return (*dnss_); }
+ /// \brief Returns this object's NSAS
+ isc::nsas::NameserverAddressStore& getNameserverAddressStore() const {
+ return *nsas_;
+ };
+
+ /// \brief Returns this object's ResolverCache
+ isc::cache::ResolverCache& getResolverCache() const {
+ return *cache_;
+ };
+
/// \brief Return pointer to the DNS Lookup callback function
asiolink::DNSLookup* getDNSLookupProvider() { return (dns_lookup_); }
@@ -147,10 +168,11 @@ public:
* \short Set options related to timeouts.
*
* This sets the time of timeout and number of retries.
- * The value -1 disables timeouts.
- * \param query_timeout The time in milliseconds.TODO
- * \param client_timeout The time in milliseconds. TODO
- * \param lookup_timeout The time in milliseconds. TODO
+ * \param query_timeout The timeout we use for queries we send
+ * \param client_timeout The timeout at which point we send back a
+ * SERVFAIL (while continuing to resolve the query)
+ * \param lookup_timeout The timeout at which point we give up and
+ * stop.
* \param retries The number of retries (0 means try the first time only,
* do not retry).
*/
@@ -205,6 +227,8 @@ private:
asiolink::SimpleCallback* checkin_;
asiolink::DNSLookup* dns_lookup_;
asiolink::DNSAnswer* dns_answer_;
+ isc::nsas::NameserverAddressStore* nsas_;
+ isc::cache::ResolverCache* cache_;
};
#endif // __RESOLVER_H
diff --git a/src/bin/resolver/resolver.spec.pre.in b/src/bin/resolver/resolver.spec.pre.in
index a249009..9df1e75 100644
--- a/src/bin/resolver/resolver.spec.pre.in
+++ b/src/bin/resolver/resolver.spec.pre.in
@@ -6,48 +6,48 @@
{
"item_name": "timeout_query",
"item_type": "integer",
- "item_optional": False,
+ "item_optional": false,
"item_default": 2000
},
{
"item_name": "timeout_client",
"item_type": "integer",
- "item_optional": False,
+ "item_optional": false,
"item_default": 4000
},
{
"item_name": "timeout_lookup",
"item_type": "integer",
- "item_optional": False,
+ "item_optional": false,
"item_default": 30000
},
{
"item_name": "retries",
"item_type": "integer",
- "item_optional": False,
- "item_default": 0
+ "item_optional": false,
+ "item_default": 3
},
{
"item_name": "forward_addresses",
"item_type": "list",
- "item_optional": True,
+ "item_optional": true,
"item_default": [],
"list_item_spec" : {
"item_name": "address",
"item_type": "map",
- "item_optional": False,
+ "item_optional": false,
"item_default": {},
"map_item_spec": [
{
"item_name": "address",
"item_type": "string",
- "item_optional": False,
+ "item_optional": false,
"item_default": "::1"
},
{
"item_name": "port",
"item_type": "integer",
- "item_optional": False,
+ "item_optional": false,
"item_default": 53
}
]
@@ -56,24 +56,24 @@
{
"item_name": "root_addresses",
"item_type": "list",
- "item_optional": True,
+ "item_optional": true,
"item_default": [],
"list_item_spec" : {
"item_name": "address",
"item_type": "map",
- "item_optional": False,
+ "item_optional": false,
"item_default": {},
"map_item_spec": [
{
"item_name": "address",
"item_type": "string",
- "item_optional": False,
+ "item_optional": false,
"item_default": "::1"
},
{
"item_name": "port",
"item_type": "integer",
- "item_optional": False,
+ "item_optional": false,
"item_default": 53
}
]
@@ -82,34 +82,34 @@
{
"item_name": "listen_on",
"item_type": "list",
- "item_optional": False,
+ "item_optional": false,
"item_default": [
{
"address": "::1",
- "port": 5300
+ "port": 53
},
{
"address": "127.0.0.1",
- "port": 5300
- },
+ "port": 53
+ }
],
"list_item_spec": {
"item_name": "address",
"item_type": "map",
- "item_optional": False,
+ "item_optional": false,
"item_default": {},
"map_item_spec": [
{
"item_name": "address",
"item_type": "string",
- "item_optional": False,
+ "item_optional": false,
"item_default": "::1"
},
{
"item_name": "port",
"item_type": "integer",
- "item_optional": False,
- "item_default": 5300
+ "item_optional": false,
+ "item_default": 53
}
]
}
diff --git a/src/bin/resolver/response_classifier.cc b/src/bin/resolver/response_classifier.cc
deleted file mode 100644
index 2d21407..0000000
--- a/src/bin/resolver/response_classifier.cc
+++ /dev/null
@@ -1,259 +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.
-
-// $Id$
-
-#include <cstddef>
-#include <vector>
-
-#include <resolver/response_classifier.h>
-#include <dns/name.h>
-#include <dns/opcode.h>
-#include <dns/rcode.h>
-#include <dns/rrset.h>
-
-using namespace isc::dns;
-using namespace std;
-
-// Classify the response in the "message" object.
-
-ResponseClassifier::Category ResponseClassifier::classify(
- const Question& question, const MessagePtr& message, bool tcignore)
-{
- // Check header bits
- if (!message->getHeaderFlag(Message::HEADERFLAG_QR)) {
- return (NOTRESPONSE); // Query-response bit not set in the response
- }
-
- // We only recognise responses to queries here
- if (message->getOpcode() != Opcode::QUERY()) {
- return (OPCODE);
- }
-
- // Apparently have a response. There must be a single question in it...
- const vector<QuestionPtr> msgquestion(message->beginQuestion(),
- message->endQuestion());
- if (msgquestion.size() != 1) {
- return (NOTONEQUEST); // Not one question in response question section
- }
-
- // ... and the question should be equal to the question given.
- // XXX: This means that "question" may not be the question sent by the
- // client. In the case of a CNAME response, the qname of subsequent
- // questions needs to be altered.
- if (question != *(msgquestion[0])) {
- return (MISMATQUEST);
- }
-
- // Check for Rcode-related errors.
- const Rcode& rcode = message->getRcode();
- if (rcode != Rcode::NOERROR()) {
- if (rcode == Rcode::NXDOMAIN()) {
-
- // No such domain. According to RFC2308, the domain referred to by
- // the QNAME does not exist, although there may be a CNAME in the
- // answer section and there may be an SOA and/or NS RRs in the
- // authority section (ignoring any DNSSEC RRs for now).
- //
- // Note the "may". There may not be anything. Also, note that if
- // there is a CNAME in the answer section, the authoritative server
- // has verified that the name given in the CNAME's RDATA field does
- // not exist. And that if a CNAME is returned in the answer, then
- // the QNAME of the RRs in the authority section will refer to the
- // authority for the CNAME's RDATA and not to the original question.
- //
- // Without doing further classification, it is sufficient to say
- // that if an NXDOMAIN is received, there was no translation of the
- // QNAME available.
- return (NXDOMAIN); // Received NXDOMAIN from parent.
-
- } else {
-
- // Not NXDOMAIN but not NOERROR either. Must be an RCODE-related
- // error.
- return (RCODE);
- }
- }
-
- // All seems OK and we can start looking at the content. However, one
- // more header check remains - was the response truncated? If so, we'll
- // probably want to re-query over TCP. However, in some circumstances we
- // might want to go with what we have. So give the caller the option of
- // ignoring the TC bit.
- if (message->getHeaderFlag(Message::HEADERFLAG_TC) && (!tcignore)) {
- return (TRUNCATED);
- }
-
- // By the time we get here, we're assured that the packet format is correct.
- // We now need to decide as to whether it is an answer, a CNAME, or a
- // referral. For this, we need to inspect the contents of the answer
- // and authority sections.
- const vector<RRsetPtr> answer(
- message->beginSection(Message::SECTION_ANSWER),
- message->endSection(Message::SECTION_ANSWER)
- );
- const vector<RRsetPtr> authority(
- message->beginSection(Message::SECTION_AUTHORITY),
- message->endSection(Message::SECTION_AUTHORITY)
- );
-
- // If there is nothing in the answer section, it is a referral - unless
- // there is nothing in the authority section
- if (answer.empty()) {
- if (authority.empty()) {
- return (EMPTY);
- } else {
- return (REFERRAL);
- }
- }
-
- // Look at two cases - one RRset in the answer and multiple RRsets in
- // the answer.
- if (answer.size() == 1) {
-
- // Does the name and class of the answer match that of the question?
- if ((answer[0]->getName() == question.getName()) &&
- (answer[0]->getClass() == question.getClass())) {
-
- // It does. How about the type of the response? The response
- // is an answer if the type matches that of the question, or if the
- // question was for type ANY. It is a CNAME reply if the answer
- // type is CNAME. And it is an error for anything else.
- if ((answer[0]->getType() == question.getType()) ||
- (question.getType() == RRType::ANY())) {
- return (ANSWER);
- } else if (answer[0]->getType() == RRType::CNAME()) {
- return (CNAME);
- } else {
- return (INVTYPE);
- }
- }
- else {
-
- // Either the name and/or class of the reply don't match that of
- // the question.
- return (INVNAMCLASS);
- }
- }
-
- // There are multiple RRsets in the answer. They should all have the same
- // QCLASS, else there is some error in the response.
- for (int i = 1; i < answer.size(); ++i) {
- if (answer[0]->getClass() != answer[i]->getClass()) {
- return (MULTICLASS);
- }
- }
-
- // If the request type was ANY and they all have the same QNAME, we have
- // an answer. But if they don't have the same QNAME, we must have an error;
- // the only way we could get different QNAMES in an answer is if one were a
- // CNAME - in which case there should no other record types at that QNAME.
- if (question.getType() == RRType::ANY()) {
- bool all_same = true;
- for (int i = 1; (i < answer.size()) && all_same; ++i) {
- all_same = (answer[0]->getName() == answer[i]->getName());
- }
- if (all_same) {
- return (ANSWER);
- } else {
- return (EXTRADATA);
- }
- }
-
- // Multiple RRs in the answer, and not all the same QNAME. This
- // is either an answer, a CNAME (in either case, there could be multiple
- // CNAMEs in the chain) or an error.
- //
- // So we need to follow the CNAME chain to resolve this. For this to work:
- //
- // a) There must be one RR that matches the name, class and type of
- // the question, and this is a CNAME.
- // b) The CNAME chain is followed until the end of the chain does not
- // exist (answer is a CNAME) or it is not of type CNAME (ANSWER).
- //
- // In the latter case, if there are additional RRs, it must be an error.
-
- vector<RRsetPtr> ansrrset(answer);
- vector<int> present(ansrrset.size(), 1);
- return cnameChase(question.getName(), question.getType(), ansrrset, present,
- ansrrset.size());
-}
-
-// Search the CNAME chain.
-ResponseClassifier::Category ResponseClassifier::cnameChase(
- const Name& qname, const RRType& qtype, vector<RRsetPtr>& ansrrset,
- vector<int>& present, size_t size)
-{
- // Search through the vector of RRset pointers until we find one with the
- // right QNAME.
- for (int i = 0; i < ansrrset.size(); ++i) {
- if (present[i]) {
-
- // This entry has not been logically removed, so look at it.
- if (ansrrset[i]->getName() == qname) {
-
- // QNAME match. If this RRset is a CNAME, remove it from
- // further consideration. If nothing is left, the end of the
- // chain is a CNAME so this is a CNAME. Otherwise replace
- // the name with the RDATA of the CNAME and call ourself
- // recursively.
- if (ansrrset[i]->getType() == RRType::CNAME()) {
-
- // Don't consider it in the next iteration (although we
- // can still access it for now).
- present[i] = 0;
- --size;
- if (size == 0) {
- return (CNAME);
- }
- else {
- if (ansrrset[i]->getRdataCount() != 1) {
-
- // Multiple RDATA for a CNAME? This is invalid.
-
- return (NOTSINGLE);
- }
- RdataIteratorPtr it = ansrrset[i]->getRdataIterator();
- Name newname(it->getCurrent().toText());
-
- return cnameChase(newname, qtype, ansrrset, present,
- size);
- }
-
- } else {
-
- // We've got here because the element is not a CNAME. If
- // this is the last element and the type is the one we are
- // after, we've found the answer, or it is an error. If
- // there is more than one RRset left in the list we are
- // searching, we have extra data in the answer.
- if (ansrrset[i]->getType() == qtype) {
- if (size == 1) {
- return (ANSWERCNAME);
- } else {
- return (EXTRADATA);
- }
- }
- return (INVTYPE);
- }
- }
- }
- }
-
- // We get here if we've dropped off the end of the list without finding the
- // QNAME we are looking for. This means that the CNAME chain has ended
- // but there are additional RRsets in the data.
-
- return (EXTRADATA);
-}
diff --git a/src/bin/resolver/response_classifier.h b/src/bin/resolver/response_classifier.h
deleted file mode 100644
index 394d60f..0000000
--- a/src/bin/resolver/response_classifier.h
+++ /dev/null
@@ -1,138 +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.
-
-// $Id$
-
-#ifndef __RESPONSE_CLASSIFIER_H
-#define __RESPONSE_CLASSIFIER_H
-
-#include <cstddef>
-
-#include <dns/question.h>
-#include <dns/message.h>
-#include <dns/question.h>
-
-/// \brief Classify Server Response
-///
-/// This class is used in the recursive server. It is passed an answer received
-/// from an upstream server and categorises it.
-///
-/// TODO: It is unlikely that the code can be used in this form. Some adaption
-/// of it will be required to put it in the server.
-///
-/// TODO: The code here does not take into account any EDNS0 fields.
-
-class ResponseClassifier {
-public:
-
- /// \brief Category of Answer
- ///
- /// In the valid answers, not the distinction between REFERRAL and CNAME.
- /// A REFERRAL answer means that the answer section of the message is
- /// empty, but there is something in the authority section. A CNAME means
- /// that the answer section contains one or more CNAMES in a chain that
- /// do not end with a non-CNAME RRset.
- enum Category {
-
- // Codes indicating that a message is valid.
-
- ANSWER, ///< Response contains the answer
- ANSWERCNAME, ///< Response was a CNAME chain ending in an answer
- CNAME, ///< Response was a CNAME
- NXDOMAIN, ///< Response was an NXDOMAIN
- REFERRAL, ///< Response contains a referral
-
- // Codes indicating that a message is invalid. Note that the error()
- // method relies on these appearing after the "message valid" codes.
-
- EMPTY, ///< No answer or authority sections
- EXTRADATA, ///< Answer section contains more RRsets than needed
- INVNAMCLASS, ///< Invalid name or class in answer
- INVTYPE, ///< Name/class of answer correct, type is wrong
- MISMATQUEST, ///< Response question section != question
- MULTICLASS, ///< Multiple classes in multi-RR answer
- NOTONEQUEST, ///< Not one question in response question section
- NOTRESPONSE, ///< Response has the Query/Response bit clear
- NOTSINGLE, ///< CNAME has multiple RDATA elements.
- OPCODE, ///< Opcode field does not indicate a query
- RCODE, ///< RCODE indicated an error
- TRUNCATED ///< Response was truncated
- };
-
- /// \brief Check Error
- ///
- /// An inline routine to quickly classify whether the return category is
- /// an error or not. This makes use of internal knowledge of the order of
- /// codes in the Category enum.
- ///
- /// \param code Return category from classify()
- ///
- /// \return true if the category is an error, false if not.
- static bool error(Category code) {
- return (code > REFERRAL);
- }
-
- /// \brief Classify
- ///
- /// Classify the response in the "message" object.
- ///
- /// \param question Question that was sent to the server
- /// \param message Pointer to the associated response from the server.
- /// \param tcignore If set, the TC bit in a response packet is
- /// ignored. Otherwise the error code TRUNCATED will be returned. The
- /// only time this is likely to be used is in development where we are not
- /// going to fail over to TCP and will want to use what is returned, even
- /// if some of the response was lost.
- static Category classify(const isc::dns::Question& question,
- const isc::dns::MessagePtr& message, bool tcignore = false);
-
-private:
- /// \brief Follow CNAMEs
- ///
- /// Given a QNAME and an answer section that contains CNAMEs, assume that
- /// they form a CNAME chain and search through them. Possible outcomes
- /// are:
- ///
- /// a) All CNAMES and they form a chain. The result is a referral.
- /// b) All but one are CNAMES and they form a chain. The other is pointed
- /// to by the last element of the chain and is the correct QTYPE. The
- /// result is an answer.
- /// c) Having followed the CNAME chain as far as we can, there is one
- /// remaining RRset that is of the wrong type, or there are multiple
- /// RRsets remaining. return the EXTRADATA code.
- ///
- /// \param qname Question name we are searching for
- /// \param qtype Question type we are search for. (This is assumed not
- /// to be "ANY".)
- /// \param ansrrset Vector of RRsetPtr pointing to the RRsets we are
- /// considering.
- /// \param present Array of "int" the same size of ansrrset, with each
- /// element set to "1" to allow the corresponding element of ansrrset to
- /// be checked, and "0" to skip it. This might be premature optimisation,
- /// but the algorithm would otherwise involve duplicating the RRset
- /// vector then removing elements from random positions one by one. As
- /// each removal involves the destruction of an "xxxPtr" element (which
- /// presently is implemented by boost::shared_ptr), the overhad of memory
- /// management seemed high. This solution imposes some additional loop
- /// cycles, but that should be minimal compared with the overhead of the
- /// memory management.
- /// \param size Number of elements to check. See description of \c present
- /// for details.
- static Category cnameChase(const isc::dns::Name& qname,
- const isc::dns::RRType& qtype,
- std::vector<isc::dns::RRsetPtr>& ansrrset, std::vector<int>& present,
- size_t size);
-};
-
-#endif // __RESPONSE_CLASSIFIER_H
diff --git a/src/bin/resolver/response_scrubber.h b/src/bin/resolver/response_scrubber.h
index d82d65f..680aa5a 100644
--- a/src/bin/resolver/response_scrubber.h
+++ b/src/bin/resolver/response_scrubber.h
@@ -243,7 +243,7 @@
/// scrubbed.
#include <config.h>
-#include <asiolink/ioendpoint.h>
+#include <asiolink/io_endpoint.h>
#include <dns/message.h>
#include <dns/name.h>
diff --git a/src/bin/resolver/tests/Makefile.am b/src/bin/resolver/tests/Makefile.am
index 8bebff0..b85c223 100644
--- a/src/bin/resolver/tests/Makefile.am
+++ b/src/bin/resolver/tests/Makefile.am
@@ -19,11 +19,9 @@ TESTS += run_unittests
run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
run_unittests_SOURCES += ../resolver.h ../resolver.cc
-run_unittests_SOURCES += ../response_classifier.h ../response_classifier.cc
run_unittests_SOURCES += ../response_scrubber.h ../response_scrubber.cc
run_unittests_SOURCES += resolver_unittest.cc
run_unittests_SOURCES += resolver_config_unittest.cc
-run_unittests_SOURCES += response_classifier_unittest.cc
run_unittests_SOURCES += response_scrubber_unittest.cc
run_unittests_SOURCES += run_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
@@ -31,14 +29,18 @@ run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
run_unittests_LDADD = $(GTEST_LDADD)
run_unittests_LDADD += $(SQLITE_LIBS)
run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
-run_unittests_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
-run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
run_unittests_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
+run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cache/libcache.la
+run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
+run_unittests_LDADD += $(top_builddir)/src/lib/resolve/libresolve.la
# Note the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS
diff --git a/src/bin/resolver/tests/resolver_config_unittest.cc b/src/bin/resolver/tests/resolver_config_unittest.cc
index 916396a..1d6415b 100644
--- a/src/bin/resolver/tests/resolver_config_unittest.cc
+++ b/src/bin/resolver/tests/resolver_config_unittest.cc
@@ -24,6 +24,7 @@
#include <dns/tests/unittest_util.h>
#include <testutils/srv_test.h>
+#include <testutils/portconfig.h>
using namespace std;
using namespace isc::data;
@@ -42,7 +43,7 @@ class ResolverConfig : public ::testing::Test {
{
server.setDNSService(dnss);
}
- void invalidTest(const string &JOSN);
+ void invalidTest(const string &JSON, const string& name);
};
TEST_F(ResolverConfig, forwardAddresses) {
@@ -122,117 +123,48 @@ TEST_F(ResolverConfig, rootAddressConfig) {
}
void
-ResolverConfig::invalidTest(const string &JOSN) {
- ElementPtr config(Element::fromJSON(JOSN));
- EXPECT_FALSE(server.updateConfig(config)->equals(
- *isc::config::createAnswer())) << "Accepted config " << JOSN << endl;
+ResolverConfig::invalidTest(const string &JSON, const string& name) {
+ isc::testutils::portconfig::configRejected(server, JSON, name);
}
TEST_F(ResolverConfig, invalidForwardAddresses) {
// Try torturing it with some invalid inputs
invalidTest("{"
"\"forward_addresses\": \"error\""
- "}");
+ "}", "Invalid type");
invalidTest("{"
"\"forward_addresses\": [{}]"
- "}");
+ "}", "Empty element");
invalidTest("{"
"\"forward_addresses\": [{"
" \"port\": 1.5,"
" \"address\": \"192.0.2.1\""
- "}]}");
+ "}]}", "Float port");
invalidTest("{"
"\"forward_addresses\": [{"
" \"port\": -5,"
" \"address\": \"192.0.2.1\""
- "}]}");
+ "}]}", "Negative port");
invalidTest("{"
"\"forward_addresses\": [{"
" \"port\": 53,"
" \"address\": \"bad_address\""
- "}]}");
+ "}]}", "Bad address");
}
+// Try setting the addresses directly
TEST_F(ResolverConfig, listenAddresses) {
- // Default value should be fully recursive
- EXPECT_TRUE(server.getListenAddresses().empty());
-
- // Try putting there some addresses
- vector<pair<string, uint16_t> > addresses;
- addresses.push_back(pair<string, uint16_t>("127.0.0.1", 5321));
- addresses.push_back(pair<string, uint16_t>("::1", 5321));
- server.setListenAddresses(addresses);
- EXPECT_EQ(2, server.getListenAddresses().size());
- EXPECT_EQ("::1", server.getListenAddresses()[1].first);
-
- // Is it independent from what we do with the vector later?
- addresses.clear();
- EXPECT_EQ(2, server.getListenAddresses().size());
-
- // Did it return to fully recursive?
- server.setListenAddresses(addresses);
- EXPECT_TRUE(server.getListenAddresses().empty());
+ isc::testutils::portconfig::listenAddresses(server);
}
-TEST_F(ResolverConfig, DISABLED_listenAddressConfig) {
- // Try putting there some address
- ElementPtr config(Element::fromJSON("{"
- "\"listen_on\": ["
- " {"
- " \"address\": \"127.0.0.1\","
- " \"port\": 5321"
- " }"
- "]"
- "}"));
- ConstElementPtr result(server.updateConfig(config));
- EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
- ASSERT_EQ(1, server.getListenAddresses().size());
- EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first);
- EXPECT_EQ(5321, server.getListenAddresses()[0].second);
-
- // As this is example address, the machine should not have it on
- // any interface
- // FIXME: This test aborts, because it tries to rollback and
- // it is impossible, since the sockets are not closed.
- // Once #388 is solved, enable this test.
- config = Element::fromJSON("{"
- "\"listen_on\": ["
- " {"
- " \"address\": \"192.0.2.0\","
- " \"port\": 5321"
- " }"
- "]"
- "}");
- result = server.updateConfig(config);
- EXPECT_FALSE(result->equals(*isc::config::createAnswer()));
- ASSERT_EQ(1, server.getListenAddresses().size());
- EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first);
- EXPECT_EQ(5321, server.getListenAddresses()[0].second);
+// Try setting some addresses and a rollback
+TEST_F(ResolverConfig, listenAddressConfig) {
+ isc::testutils::portconfig::listenAddressConfig(server);
}
+// Try some invalid configs are rejected
TEST_F(ResolverConfig, invalidListenAddresses) {
- // Try torturing it with some invalid inputs
- invalidTest("{"
- "\"listen_on\": \"error\""
- "}");
- invalidTest("{"
- "\"listen_on\": [{}]"
- "}");
- invalidTest("{"
- "\"listen_on\": [{"
- " \"port\": 1.5,"
- " \"address\": \"192.0.2.1\""
- "}]}");
- invalidTest("{"
- "\"listen_on\": [{"
- " \"port\": -5,"
- " \"address\": \"192.0.2.1\""
- "}]}");
- invalidTest("{"
- "\"listen_on\": [{"
- " \"port\": 53,"
- " \"address\": \"bad_address\""
- "}]}");
+ isc::testutils::portconfig::invalidListenAddressConfig(server);
}
// Just test it sets and gets the values correctly
@@ -267,28 +199,28 @@ TEST_F(ResolverConfig, timeoutsConfig) {
TEST_F(ResolverConfig, invalidTimeoutsConfig) {
invalidTest("{"
"\"timeout_query\": \"error\""
- "}");
+ "}", "Wrong query element type");
invalidTest("{"
"\"timeout_query\": -2"
- "}");
+ "}", "Negative query timeout");
invalidTest("{"
"\"timeout_client\": \"error\""
- "}");
+ "}", "Wrong client element type");
invalidTest("{"
"\"timeout_client\": -2"
- "}");
+ "}", "Negative client timeout");
invalidTest("{"
"\"timeout_lookup\": \"error\""
- "}");
+ "}", "Wrong lookup element type");
invalidTest("{"
"\"timeout_lookup\": -2"
- "}");
+ "}", "Negative lookup timeout");
invalidTest("{"
"\"retries\": \"error\""
- "}");
+ "}", "Wrong retries element type");
invalidTest("{"
"\"retries\": -1"
- "}");
+ "}", "Negative number of retries");
}
}
diff --git a/src/bin/resolver/tests/resolver_unittest.cc b/src/bin/resolver/tests/resolver_unittest.cc
index a4f11f5..97edf12 100644
--- a/src/bin/resolver/tests/resolver_unittest.cc
+++ b/src/bin/resolver/tests/resolver_unittest.cc
@@ -96,6 +96,27 @@ TEST_F(ResolverTest, AXFRFail) {
QR_FLAG, 1, 0, 0, 0);
}
+TEST_F(ResolverTest, IXFRFail) {
+ UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+ Name("example.com"), RRClass::IN(),
+ RRType::IXFR());
+ createRequestPacket(request_message, IPPROTO_TCP);
+ // IXFR is not implemented and should always send NOTIMP.
+ server.processMessage(*io_message,
+ parse_message,
+ response_message,
+ response_obuffer,
+ &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ // the second check is what we'll need in the end (with the values
+ // from the first one), but right now the first one is for what
+ // will actually be returned to the client
+ headerCheck(*parse_message, default_qid, Rcode::NOTIMP(), opcode.getCode(),
+ QR_FLAG, 1, 0, 0, 0);
+ headerCheck(*response_message, default_qid, Rcode::NOTIMP(), opcode.getCode(),
+ 0, 0, 0, 0, 0);
+}
+
TEST_F(ResolverTest, notifyFail) {
// Notify should always return NOTAUTH
request_message.clear(Message::RENDER);
diff --git a/src/bin/resolver/tests/response_classifier_unittest.cc b/src/bin/resolver/tests/response_classifier_unittest.cc
deleted file mode 100644
index ee439b1..0000000
--- a/src/bin/resolver/tests/response_classifier_unittest.cc
+++ /dev/null
@@ -1,494 +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 <iostream>
-#include <gtest/gtest.h>
-
-#include <dns/tests/unittest_util.h>
-
-#include <resolver/response_classifier.h>
-
-#include <dns/name.h>
-#include <dns/opcode.h>
-#include <dns/question.h>
-#include <dns/rdata.h>
-#include <dns/rdataclass.h>
-#include <dns/rcode.h>
-#include <dns/rrclass.h>
-#include <dns/rrset.h>
-#include <dns/rrtype.h>
-#include <dns/rrttl.h>
-
-using namespace std;
-using namespace isc::dns;
-using namespace rdata;
-using namespace isc::dns::rdata::generic;
-using namespace isc::dns::rdata::in;
-
-namespace {
-class ResponseClassifierTest : public ::testing::Test {
-public:
- /// \brief Constructor
- ///
- /// The naming convention is:
- ///
- /// <category>_<class>_<type>_<name>
- ///
- /// <category> is "qu" (question), "rrs" (rrset),
- /// <qclass> is self-explanatory
- /// <qtype> is self-explanatory
- /// <name> is the first part of the domain name (all expected to be in
- /// example.com)
- ///
- /// Message variables
- ///
- /// msg_<qtype> Where <qtype> is the type of query. These are only used
- /// in the early tests where simple messages are required.
-
- ResponseClassifierTest() :
- msg_a(new Message(Message::RENDER)),
- msg_any(new Message(Message::RENDER)),
- qu_ch_a_www(Name("www.example.com"), RRClass::CH(), RRType::A()),
- qu_in_any_www(Name("www.example.com"), RRClass::IN(), RRType::ANY()),
- qu_in_a_www2(Name("www2.example.com"), RRClass::IN(), RRType::A()),
- qu_in_a_www(Name("www.example.com"), RRClass::IN(), RRType::A()),
- qu_in_cname_www1(Name("www1.example.com"), RRClass::IN(), RRType::A()),
- qu_in_ns_(Name("example.com"), RRClass::IN(), RRType::NS()),
- qu_in_txt_www(Name("www.example.com"), RRClass::IN(), RRType::TXT()),
- rrs_hs_txt_www(new RRset(Name("www.example.com"), RRClass::HS(),
- RRType::TXT(), RRTTL(300))),
- rrs_in_a_mail(new RRset(Name("mail.example.com"), RRClass::IN(),
- RRType::A(), RRTTL(300))),
- rrs_in_a_www(new RRset(Name("www.example.com"), RRClass::IN(),
- RRType::A(), RRTTL(300))),
- rrs_in_cname_www1(new RRset(Name("www1.example.com"), RRClass::IN(),
- RRType::CNAME(), RRTTL(300))),
- rrs_in_cname_www2(new RRset(Name("www2.example.com"), RRClass::IN(),
- RRType::CNAME(), RRTTL(300))),
- rrs_in_ns_(new RRset(Name("example.com"), RRClass::IN(),
- RRType::NS(), RRTTL(300))),
- rrs_in_txt_www(new RRset(Name("www.example.com"), RRClass::IN(),
- RRType::TXT(), RRTTL(300)))
- {
- // Set up the message to indicate a successful response to the question
- // "www.example.com A", but don't add in any response sections.
- msg_a->setHeaderFlag(Message::HEADERFLAG_QR);
- msg_a->setOpcode(Opcode::QUERY());
- msg_a->setRcode(Rcode::NOERROR());
- msg_a->addQuestion(qu_in_a_www);
-
- // ditto for the query "www.example.com ANY"
- msg_any->setHeaderFlag(Message::HEADERFLAG_QR);
- msg_any->setOpcode(Opcode::QUERY());
- msg_any->setRcode(Rcode::NOERROR());
- msg_any->addQuestion(qu_in_any_www);
-
- // The next set of assignments set up the following zone records
- //
- // example.com NS ns0.isc.org
- // NS ns0.example.org
- //
- // www.example.com A 1.2.3.4
- // TXT "An example text string"
- //
- // mail.example.com A 4.5.6.7
- //
- // www1.example.com CNAME www.example.com
- //
- // www2.example.com CNAME www1.example.com
-
- // Set up an imaginary NS RRset for an authority section
- rrs_in_ns_->addRdata(ConstRdataPtr(new NS(Name("ns0.isc.org"))));
- rrs_in_ns_->addRdata(ConstRdataPtr(new NS(Name("ns0.example.org"))));
-
- // Set up the records for the www host
- rrs_in_a_www->addRdata(ConstRdataPtr(new A("1.2.3.4")));
- rrs_in_txt_www->addRdata(ConstRdataPtr(
- new TXT("An example text string")));
-
- // ... for the mail host
- rrs_in_a_mail->addRdata(ConstRdataPtr(new A("5.6.7.8")));
-
- // ... the CNAME records
- rrs_in_cname_www1->addRdata(ConstRdataPtr(
- new CNAME("www.example.com")));
- rrs_in_cname_www2->addRdata(ConstRdataPtr(
- new CNAME("www1.example.com")));
- }
-
- MessagePtr msg_a; // Pointer to message in RENDER state
- MessagePtr msg_any; // Pointer to message in RENDER state
- Question qu_ch_a_www; // www.example.com CH A
- Question qu_in_any_www; // www.example.com IN ANY
- Question qu_in_a_www2; // www.example.com IN ANY
- Question qu_in_a_www; // www.example.com IN A
- Question qu_in_cname_www1; // www1.example.com IN CNAME
- Question qu_in_ns_; // example.com IN NS
- Question qu_in_txt_www; // www.example.com IN TXT
- RRsetPtr rrs_hs_txt_www; // www.example.com HS TXT
- RRsetPtr rrs_in_a_mail; // mail.example.com IN A
- RRsetPtr rrs_in_a_www; // www.example.com IN A
- RRsetPtr rrs_in_cname_www1; // www1.example.com IN CNAME
- RRsetPtr rrs_in_cname_www2; // www2.example.com IN CNAME
- RRsetPtr rrs_in_ns_; // example.com IN NS
- RRsetPtr rrs_in_txt_www; // www.example.com IN TXT
-};
-
-// Test that the error() function categorises the codes correctly.
-
-TEST_F(ResponseClassifierTest, StatusCodes) {
- EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::ANSWER));
- EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::ANSWERCNAME));
- EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::CNAME));
- EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::NXDOMAIN));
- EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::REFERRAL));
-
- EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::EMPTY));
- EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::EXTRADATA));
- EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::INVNAMCLASS));
- EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::INVTYPE));
- EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::MISMATQUEST));
- EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::MULTICLASS));
- EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::NOTONEQUEST));
- EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::NOTRESPONSE));
- EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::NOTSINGLE));
- EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::OPCODE));
- EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::RCODE));
- EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::TRUNCATED));
-}
-
-// Test that the system will reject a message which is a query.
-
-TEST_F(ResponseClassifierTest, Query) {
-
- // Set up message to indicate a query (QR flag = 0, one question). By
- // default the opcode will be 0 (query)
- msg_a->setHeaderFlag(Message::HEADERFLAG_QR, false);
-
- // Should be rejected as it is a query, not a response
- EXPECT_EQ(ResponseClassifier::NOTRESPONSE,
- ResponseClassifier::classify(qu_in_a_www, msg_a));
-}
-
-// Check that we get an OPCODE error on all but QUERY opcodes.
-
-TEST_F(ResponseClassifierTest, Opcode) {
-
- uint8_t query = static_cast<uint8_t>(Opcode::QUERY().getCode());
-
- for (uint8_t i = 0; i < (1 << 4); ++i) {
- msg_a->setOpcode(Opcode(i));
- if (i == query) {
- EXPECT_NE(ResponseClassifier::OPCODE,
- ResponseClassifier::classify(qu_in_a_www, msg_a));
- } else {
- EXPECT_EQ(ResponseClassifier::OPCODE,
- ResponseClassifier::classify(qu_in_a_www, msg_a));
- }
- }
-}
-
-// Test that the system will reject a response with anything other than one
-// question.
-
-TEST_F(ResponseClassifierTest, MultipleQuestions) {
-
- // Create a message object for this test that has no question section.
- MessagePtr message(new Message(Message::RENDER));
- message->setHeaderFlag(Message::HEADERFLAG_QR);
- message->setOpcode(Opcode::QUERY());
- message->setRcode(Rcode::NOERROR());
-
- // Zero questions
- EXPECT_EQ(ResponseClassifier::NOTONEQUEST,
- ResponseClassifier::classify(qu_in_a_www, message));
-
- // One question
- message->addQuestion(qu_in_a_www);
- EXPECT_NE(ResponseClassifier::NOTONEQUEST,
- ResponseClassifier::classify(qu_in_a_www, message));
-
- // Two questions
- message->addQuestion(qu_in_ns_);
- EXPECT_EQ(ResponseClassifier::NOTONEQUEST,
- ResponseClassifier::classify(qu_in_a_www, message));
-
- // And finish the check with three questions
- message->addQuestion(qu_in_txt_www);
- EXPECT_EQ(ResponseClassifier::NOTONEQUEST,
- ResponseClassifier::classify(qu_in_a_www, message));
-}
-
-// Test that the question in the question section in the message response
-// is equal to the question supplied.
-
-TEST_F(ResponseClassifierTest, SameQuestion) {
-
- EXPECT_EQ(ResponseClassifier::MISMATQUEST,
- ResponseClassifier::classify(qu_in_ns_, msg_a));
- EXPECT_NE(ResponseClassifier::MISMATQUEST,
- ResponseClassifier::classify(qu_in_a_www, msg_a));
-}
-
-// Should get an NXDOMAIN response only on an NXDOMAIN RCODE.
-
-TEST_F(ResponseClassifierTest, NXDOMAIN) {
-
- uint16_t nxdomain = static_cast<uint16_t>(Rcode::NXDOMAIN().getCode());
-
- for (uint8_t i = 0; i < (1 << 4); ++i) {
- msg_a->setRcode(Rcode(i));
- if (i == nxdomain) {
- EXPECT_EQ(ResponseClassifier::NXDOMAIN,
- ResponseClassifier::classify(qu_in_a_www, msg_a));
- } else {
- EXPECT_NE(ResponseClassifier::NXDOMAIN,
- ResponseClassifier::classify(qu_in_a_www, msg_a));
- }
- }
-}
-
-// Check that we get an RCODE error on all but NXDOMAIN and NOERROR responses.
-
-TEST_F(ResponseClassifierTest, RCODE) {
-
- uint16_t nxdomain = static_cast<uint16_t>(Rcode::NXDOMAIN().getCode());
- uint16_t noerror = static_cast<uint16_t>(Rcode::NOERROR().getCode());
-
- for (uint8_t i = 0; i < (1 << 4); ++i) {
- msg_a->setRcode(Rcode(i));
- if ((i == nxdomain) || (i == noerror)) {
- EXPECT_NE(ResponseClassifier::RCODE,
- ResponseClassifier::classify(qu_in_a_www, msg_a));
- } else {
- EXPECT_EQ(ResponseClassifier::RCODE,
- ResponseClassifier::classify(qu_in_a_www, msg_a));
- }
- }
-}
-
-// Test that the code will detect a truncated message. Even if nothing else
-// is wrong, we'll want to retry the query if we receive a truncated code.
-// However, we give the option to the user of the code aws to whether they
-// want to take into account the truncated bit.
-
-TEST_F(ResponseClassifierTest, Truncated) {
-
- // Don't expect the truncated code whatever option we ask for if the TC
- // bit is not set.
- msg_a->setHeaderFlag(Message::HEADERFLAG_TC, false);
- EXPECT_NE(ResponseClassifier::TRUNCATED,
- ResponseClassifier::classify(qu_in_a_www, msg_a, true));
- EXPECT_NE(ResponseClassifier::TRUNCATED,
- ResponseClassifier::classify(qu_in_a_www, msg_a, false));
-
- // Expect the truncated code if the TC bit is set, only if we don't ignore
- // it.
- msg_a->setHeaderFlag(Message::HEADERFLAG_TC, true);
- EXPECT_NE(ResponseClassifier::TRUNCATED,
- ResponseClassifier::classify(qu_in_a_www, msg_a, true));
- EXPECT_EQ(ResponseClassifier::TRUNCATED,
- ResponseClassifier::classify(qu_in_a_www, msg_a, false));
-}
-
-// Check for an empty packet (i.e. no error, but with the answer and additional
-// sections empty).
-
-TEST_F(ResponseClassifierTest, Empty) {
-
- EXPECT_EQ(ResponseClassifier::EMPTY,
- ResponseClassifier::classify(qu_in_a_www, msg_a));
-}
-
-// Anything where we have an empty answer section but something in the
-// authority section is a referral (if the status is NOERROR).
-
-TEST_F(ResponseClassifierTest, EmptyAnswerReferral) {
-
- msg_a->addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_);
- EXPECT_EQ(ResponseClassifier::REFERRAL,
- ResponseClassifier::classify(qu_in_a_www, msg_a));
-
-}
-
-// Check the case where we have a simple answer in the answer section. This
-// occurs when the QNAME/QTYPE/QCLASS matches one of the RRsets in the
-// answer section - expect when the QTYPE is ANY, in which case the match
-// must be on the QNAME/QCLASS alone.
-
-TEST_F(ResponseClassifierTest, SingleAnswer) {
-
- // Check a question that matches the answer
- msg_a->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
- EXPECT_EQ(ResponseClassifier::ANSWER,
- ResponseClassifier::classify(qu_in_a_www, msg_a));
-
- // Check an ANY question that matches the answer
- msg_any->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
- EXPECT_EQ(ResponseClassifier::ANSWER,
- ResponseClassifier::classify(qu_in_any_www, msg_any));
-
- // Check a CNAME response that matches the QNAME.
- MessagePtr message_a(new Message(Message::RENDER));
- message_a->setHeaderFlag(Message::HEADERFLAG_QR);
- message_a->setOpcode(Opcode::QUERY());
- message_a->setRcode(Rcode::NOERROR());
- message_a->addQuestion(qu_in_cname_www1);
- message_a->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www1);
- EXPECT_EQ(ResponseClassifier::CNAME,
- ResponseClassifier::classify(qu_in_cname_www1, message_a));
-
- // Check if the answer QNAME does not match the question
- // Q: www.example.com IN A
- // A: mail.example.com IN A
- MessagePtr message_b(new Message(Message::RENDER));
- message_b->setHeaderFlag(Message::HEADERFLAG_QR);
- message_b->setOpcode(Opcode::QUERY());
- message_b->setRcode(Rcode::NOERROR());
- message_b->addQuestion(qu_in_a_www);
- message_b->addRRset(Message::SECTION_ANSWER, rrs_in_a_mail);
- EXPECT_EQ(ResponseClassifier::INVNAMCLASS,
- ResponseClassifier::classify(qu_in_a_www, message_b));
-
- // Check if the answer class does not match the question
- // Q: www.example.com CH A
- // A: www.example.com IN A
- MessagePtr message_c(new Message(Message::RENDER));
- message_c->setHeaderFlag(Message::HEADERFLAG_QR);
- message_c->setOpcode(Opcode::QUERY());
- message_c->setRcode(Rcode::NOERROR());
- message_c->addQuestion(qu_ch_a_www);
- message_c->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
- EXPECT_EQ(ResponseClassifier::INVNAMCLASS,
- ResponseClassifier::classify(qu_ch_a_www, message_c));
-
- // Check if the answer type does not match the question
- // Q: www.example.com IN A
- // A: www.example.com IN TXT
- MessagePtr message_d(new Message(Message::RENDER));
- message_d->setHeaderFlag(Message::HEADERFLAG_QR);
- message_d->setOpcode(Opcode::QUERY());
- message_d->setRcode(Rcode::NOERROR());
- message_d->addQuestion(qu_in_a_www);
- message_d->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
- EXPECT_EQ(ResponseClassifier::INVTYPE,
- ResponseClassifier::classify(qu_in_a_www, message_d));
-}
-
-// Check what happens if we have multiple RRsets in the answer.
-
-TEST_F(ResponseClassifierTest, MultipleAnswerRRsets) {
-
- // All the same QNAME but different types is only valid on an ANY query.
- MessagePtr message_a(new Message(Message::RENDER));
- message_a->setHeaderFlag(Message::HEADERFLAG_QR);
- message_a->setOpcode(Opcode::QUERY());
- message_a->setRcode(Rcode::NOERROR());
- message_a->addQuestion(qu_in_any_www);
- message_a->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
- message_a->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
- EXPECT_EQ(ResponseClassifier::ANSWER,
- ResponseClassifier::classify(qu_in_any_www, message_a));
-
- // On another type of query, it results in an EXTRADATA error
- MessagePtr message_b(new Message(Message::RENDER));
- message_b->setHeaderFlag(Message::HEADERFLAG_QR);
- message_b->setOpcode(Opcode::QUERY());
- message_b->setRcode(Rcode::NOERROR());
- message_b->addQuestion(qu_in_a_www);
- message_b->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
- message_b->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
- EXPECT_EQ(ResponseClassifier::EXTRADATA,
- ResponseClassifier::classify(qu_in_a_www, message_b));
-
- // Same QNAME on an ANY query is not valid with mixed classes
- MessagePtr message_c(new Message(Message::RENDER));
- message_c->setHeaderFlag(Message::HEADERFLAG_QR);
- message_c->setOpcode(Opcode::QUERY());
- message_c->setRcode(Rcode::NOERROR());
- message_c->addQuestion(qu_in_any_www);
- message_c->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
- message_c->addRRset(Message::SECTION_ANSWER, rrs_hs_txt_www);
- EXPECT_EQ(ResponseClassifier::MULTICLASS,
- ResponseClassifier::classify(qu_in_any_www, message_c));
-
- // Mixed QNAME is not valid unless QNAME requested is a CNAME.
- MessagePtr message_d(new Message(Message::RENDER));
- message_d->setHeaderFlag(Message::HEADERFLAG_QR);
- message_d->setOpcode(Opcode::QUERY());
- message_d->setRcode(Rcode::NOERROR());
- message_d->addQuestion(qu_in_a_www);
- message_d->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
- message_d->addRRset(Message::SECTION_ANSWER, rrs_in_a_mail);
- EXPECT_EQ(ResponseClassifier::EXTRADATA,
- ResponseClassifier::classify(qu_in_a_www, message_d));
-
- // Mixed QNAME is not valid when the query is an ANY.
- MessagePtr message_e(new Message(Message::RENDER));
- message_e->setHeaderFlag(Message::HEADERFLAG_QR);
- message_e->setOpcode(Opcode::QUERY());
- message_e->setRcode(Rcode::NOERROR());
- message_e->addQuestion(qu_in_any_www);
- message_e->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
- message_e->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
- message_e->addRRset(Message::SECTION_ANSWER, rrs_in_a_mail);
- EXPECT_EQ(ResponseClassifier::EXTRADATA,
- ResponseClassifier::classify(qu_in_any_www, message_e));
-}
-
-// CNAME chain is CNAME if it terminates in a CNAME, answer if it
-// does not, and error if there are RRs left over.
-TEST_F(ResponseClassifierTest, CNAMEChain) {
-
- // Answer contains a single CNAME
- MessagePtr message_a(new Message(Message::RENDER));
- message_a->setHeaderFlag(Message::HEADERFLAG_QR);
- message_a->setOpcode(Opcode::QUERY());
- message_a->setRcode(Rcode::NOERROR());
- message_a->addQuestion(qu_in_a_www2);
- message_a->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www2);
- EXPECT_EQ(ResponseClassifier::CNAME,
- ResponseClassifier::classify(qu_in_a_www2, message_a));
-
- // Add a CNAME for www1, and it should still return a CNAME
- message_a->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www1);
- EXPECT_EQ(ResponseClassifier::CNAME,
- ResponseClassifier::classify(qu_in_a_www2, message_a));
-
- // Add the A record for www and it should be an answer
- message_a->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
- EXPECT_EQ(ResponseClassifier::ANSWERCNAME,
- ResponseClassifier::classify(qu_in_a_www2, message_a));
-
- // Adding an unrelated TXT record should result in EXTRADATA
- message_a->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
- EXPECT_EQ(ResponseClassifier::EXTRADATA,
- ResponseClassifier::classify(qu_in_a_www2, message_a));
-
- // Recreate the chain, but this time end with a TXT RR and not the A
- // record. This should return INVTYPE.
- MessagePtr message_b(new Message(Message::RENDER));
- message_b->setHeaderFlag(Message::HEADERFLAG_QR);
- message_b->setOpcode(Opcode::QUERY());
- message_b->setRcode(Rcode::NOERROR());
- message_b->addQuestion(qu_in_a_www2);
- message_b->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www2);
- message_b->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www1);
- message_b->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
-
- EXPECT_EQ(ResponseClassifier::INVTYPE,
- ResponseClassifier::classify(qu_in_a_www2, message_b));
-}
-
-} // Anonymous namespace
diff --git a/src/bin/resolver/tests/response_scrubber_unittest.cc b/src/bin/resolver/tests/response_scrubber_unittest.cc
index e561eea..1dc6639 100644
--- a/src/bin/resolver/tests/response_scrubber_unittest.cc
+++ b/src/bin/resolver/tests/response_scrubber_unittest.cc
@@ -21,8 +21,8 @@
#include <config.h>
-#include <asiolink/ioendpoint.h>
-#include <asiolink/ioaddress.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_address.h>
#include <netinet/in.h>
#include <dns/name.h>
diff --git a/src/bin/stats/Makefile.am b/src/bin/stats/Makefile.am
index b267479..b173813 100644
--- a/src/bin/stats/Makefile.am
+++ b/src/bin/stats/Makefile.am
@@ -5,7 +5,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-stats
noinst_SCRIPTS = b10-stats_stub
-b10_statsdir = $(DESTDIR)$(pkgdatadir)
+b10_statsdir = $(pkgdatadir)
b10_stats_DATA = stats.spec
CLEANFILES = stats.spec b10-stats stats.pyc stats.pyo b10-stats_stub stats_stub.pyc stats_stub.pyo
@@ -23,7 +23,6 @@ endif
stats.spec: stats.spec.pre
$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" stats.spec.pre >$@
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-stats: stats.py
$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
diff --git a/src/bin/usermgr/Makefile.am b/src/bin/usermgr/Makefile.am
index f830aad..7ebb1cd 100644
--- a/src/bin/usermgr/Makefile.am
+++ b/src/bin/usermgr/Makefile.am
@@ -1,6 +1,6 @@
sbin_SCRIPTS = b10-cmdctl-usermgr
-b10_cmdctl_usermgrdir = $(DESTDIR)$(pkgdatadir)
+b10_cmdctl_usermgrdir = $(pkgdatadir)
CLEANFILES= b10-cmdctl-usermgr
@@ -14,7 +14,6 @@ b10-cmdctl-usermgr.8: b10-cmdctl-usermgr.xml
endif
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-cmdctl-usermgr: b10-cmdctl-usermgr.py
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" b10-cmdctl-usermgr.py >$@
diff --git a/src/bin/xfrin/Makefile.am b/src/bin/xfrin/Makefile.am
index 7ed41e2..ee8505e 100644
--- a/src/bin/xfrin/Makefile.am
+++ b/src/bin/xfrin/Makefile.am
@@ -4,7 +4,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-xfrin
-b10_xfrindir = $(DESTDIR)$(pkgdatadir)
+b10_xfrindir = $(pkgdatadir)
b10_xfrin_DATA = xfrin.spec
CLEANFILES = b10-xfrin xfrin.pyc
@@ -20,7 +20,6 @@ b10-xfrin.8: b10-xfrin.xml
endif
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-xfrin: xfrin.py
$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
diff --git a/src/bin/xfrout/Makefile.am b/src/bin/xfrout/Makefile.am
index 4955b7a..d4f021e 100644
--- a/src/bin/xfrout/Makefile.am
+++ b/src/bin/xfrout/Makefile.am
@@ -4,7 +4,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-xfrout
-b10_xfroutdir = $(DESTDIR)$(pkgdatadir)
+b10_xfroutdir = $(pkgdatadir)
b10_xfrout_DATA = xfrout.spec
CLEANFILES= b10-xfrout xfrout.pyc xfrout.spec
@@ -23,7 +23,6 @@ endif
xfrout.spec: xfrout.spec.pre
$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" xfrout.spec.pre >$@
-# TODO: does this need $$(DESTDIR) also?
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-xfrout: xfrout.py
$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
diff --git a/src/bin/xfrout/tests/xfrout_test.py b/src/bin/xfrout/tests/xfrout_test.py
index 2fb4463..5aec072 100644
--- a/src/bin/xfrout/tests/xfrout_test.py
+++ b/src/bin/xfrout/tests/xfrout_test.py
@@ -85,23 +85,12 @@ class TestXfroutSession(unittest.TestCase):
return msg
def setUp(self):
- request = MySocket(socket.AF_INET,socket.SOCK_STREAM)
+ self.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
self.log = isc.log.NSLogger('xfrout', '', severity = 'critical', log_to_console = False )
- (self.write_sock, self.read_sock) = socket.socketpair()
- self.xfrsess = MyXfroutSession(request, None, None, self.log, self.read_sock)
- self.xfrsess.server = Dbserver()
+ self.xfrsess = MyXfroutSession(self.sock, None, Dbserver(), self.log)
self.mdata = bytes(b'\xd6=\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\xfc\x00\x01')
- self.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
self.soa_record = (4, 3, 'example.com.', 'com.example.', 3600, 'SOA', None, 'master.example.com. admin.example.com. 1234 3600 1800 2419200 7200')
- def test_receive_query_message(self):
- send_msg = b"\xd6=\x00\x00\x00\x01\x00"
- msg_len = struct.pack('H', socket.htons(len(send_msg)))
- self.write_sock.send(msg_len)
- self.write_sock.send(send_msg)
- recv_msg = self.xfrsess._receive_query_message(self.read_sock)
- self.assertEqual(recv_msg, send_msg)
-
def test_parse_query_message(self):
[get_rcode, get_msg] = self.xfrsess._parse_query_message(self.mdata)
self.assertEqual(get_rcode.to_text(), "NOERROR")
@@ -121,6 +110,29 @@ class TestXfroutSession(unittest.TestCase):
get_msg = self.sock.read_msg()
self.assertEqual(get_msg.get_rcode().to_text(), "NXDOMAIN")
+ def test_send_message(self):
+ msg = self.getmsg()
+ msg.make_response()
+ # soa record data with different cases
+ soa_record = (4, 3, 'Example.com.', 'com.Example.', 3600, 'SOA', None, 'master.Example.com. admin.exAmple.com. 1234 3600 1800 2419200 7200')
+ rrset_soa = self.xfrsess._create_rrset_from_db_record(soa_record)
+ msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
+ self.xfrsess._send_message(self.sock, msg)
+ send_out_data = self.sock.readsent()[2:]
+
+ # CASE_INSENSITIVE compression mode
+ render = MessageRenderer();
+ render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
+ msg.to_wire(render)
+ self.assertNotEqual(render.get_data(), send_out_data)
+
+ # CASE_SENSITIVE compression mode
+ render.clear()
+ render.set_compress_mode(MessageRenderer.CASE_SENSITIVE)
+ render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
+ msg.to_wire(render)
+ self.assertEqual(render.get_data(), send_out_data)
+
def test_clear_message(self):
msg = self.getmsg()
qid = msg.get_qid()
@@ -134,7 +146,6 @@ class TestXfroutSession(unittest.TestCase):
self.assertTrue(msg.get_header_flag(Message.HEADERFLAG_AA))
def test_reply_query_with_format_error(self):
-
msg = self.getmsg()
self.xfrsess._reply_query_with_format_error(msg, self.sock)
get_msg = self.sock.read_msg()
@@ -249,11 +260,11 @@ class TestXfroutSession(unittest.TestCase):
self.xfrsess._zone_has_soa = zone_empty
def false_func():
return False
- self.xfrsess.server.increase_transfers_counter = false_func
+ self.xfrsess._server.increase_transfers_counter = false_func
self.assertEqual(self.xfrsess._check_xfrout_available(True).to_text(), "REFUSED")
def true_func():
return True
- self.xfrsess.server.increase_transfers_counter = true_func
+ self.xfrsess._server.increase_transfers_counter = true_func
self.assertEqual(self.xfrsess._check_xfrout_available(True).to_text(), "NOERROR")
def test_dns_xfrout_start_formerror(self):
@@ -323,8 +334,17 @@ class MyUnixSockServer(UnixSockServer):
class TestUnixSockServer(unittest.TestCase):
def setUp(self):
+ self.write_sock, self.read_sock = socket.socketpair()
self.unix = MyUnixSockServer()
+ def test_receive_query_message(self):
+ send_msg = b"\xd6=\x00\x00\x00\x01\x00"
+ msg_len = struct.pack('H', socket.htons(len(send_msg)))
+ self.write_sock.send(msg_len)
+ self.write_sock.send(send_msg)
+ recv_msg = self.unix._receive_query_message(self.read_sock)
+ self.assertEqual(recv_msg, send_msg)
+
def test_updata_config_data(self):
self.unix.update_config_data({'transfers_out':10 })
self.assertEqual(self.unix._max_transfers_out, 10)
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index eb96b94..f420d4b 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -50,7 +50,11 @@ isc.util.process.rename()
if "B10_FROM_BUILD" in os.environ:
SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/xfrout"
AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth"
- UNIX_SOCKET_FILE= os.environ["B10_FROM_BUILD"] + "/auth_xfrout_conn"
+ if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
+ UNIX_SOCKET_FILE = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"] + \
+ "/auth_xfrout_conn"
+ else:
+ UNIX_SOCKET_FILE = os.environ["B10_FROM_BUILD"] + "/auth_xfrout_conn"
else:
PREFIX = "@prefix@"
DATAROOTDIR = "@datarootdir@"
@@ -73,75 +77,25 @@ def get_rrset_len(rrset):
return len(bytes)
-class XfroutSession(BaseRequestHandler):
- def __init__(self, request, client_address, server, log, sock):
+class XfroutSession():
+ def __init__(self, sock_fd, request_data, server, log):
# The initializer for the superclass may call functions
# that need _log to be set, so we set it first
+ self._sock_fd = sock_fd
+ self._request_data = request_data
+ self._server = server
self._log = log
- self._shutdown_sock = sock
- BaseRequestHandler.__init__(self, request, client_address, server)
+ self.handle()
def handle(self):
- '''Handle a request until shutdown or xfrout client is closed.'''
- # check self.server._shutdown_event to ensure the real shutdown comes.
- # Linux could trigger a spurious readable event on the _shutdown_sock
- # due to a bug, so we need perform a double check.
- while not self.server._shutdown_event.is_set(): # Check if xfrout is shutdown
- try:
- (rlist, wlist, xlist) = select.select([self._shutdown_sock, self.request], [], [])
- except select.error as e:
- if e.args[0] == errno.EINTR:
- (rlist, wlist, xlist) = ([], [], [])
- continue
- else:
- self._log.log_message("error", "Error with select(): %s" %e)
- break
- # self.server._shutdown_evnet will be set by now, if it is not a false
- # alarm
- if self._shutdown_sock in rlist:
- continue
-
- sock_fd = recv_fd(self.request.fileno())
-
- if sock_fd < 0:
- # This may happen when one xfrout process try to connect to
- # xfrout unix socket server, to check whether there is another
- # xfrout running.
- if sock_fd == XFR_FD_RECEIVE_FAIL:
- self._log.log_message("error", "Failed to receive the file descriptor for XFR connection")
- break
-
- # receive query msg
- msgdata = self._receive_query_message(self.request)
- if not msgdata:
- break
-
- try:
- self.dns_xfrout_start(sock_fd, msgdata)
- #TODO, avoid catching all exceptions
- except Exception as e:
- self._log.log_message("error", str(e))
-
- os.close(sock_fd)
-
- def _receive_query_message(self, sock):
- ''' receive query message from sock'''
- # receive data length
- data_len = sock.recv(2)
- if not data_len:
- return None
- msg_len = struct.unpack('!H', data_len)[0]
- # receive data
- recv_size = 0
- msgdata = b''
- while recv_size < msg_len:
- data = sock.recv(msg_len - recv_size)
- if not data:
- return None
- recv_size += len(data)
- msgdata += data
+ ''' Handle a xfrout query, send xfrout response '''
+ try:
+ self.dns_xfrout_start(self._sock_fd, self._request_data)
+ #TODO, avoid catching all exceptions
+ except Exception as e:
+ self._log.log_message("error", str(e))
- return msgdata
+ os.close(self._sock_fd)
def _parse_query_message(self, mdata):
''' parse query message to [socket,message]'''
@@ -170,6 +124,9 @@ class XfroutSession(BaseRequestHandler):
def _send_message(self, sock_fd, msg):
render = MessageRenderer()
+ # As defined in RFC5936 section3.4, perform case-preserving name
+ # compression for AXFR message.
+ render.set_compress_mode(MessageRenderer.CASE_SENSITIVE)
render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
msg.to_wire(render)
header_len = struct.pack('H', socket.htons(render.get_length()))
@@ -192,7 +149,6 @@ class XfroutSession(BaseRequestHandler):
msg.set_rcode(Rcode.FORMERR())
self._send_message(sock_fd, msg)
-
def _zone_has_soa(self, zone):
'''Judge if the zone has an SOA record.'''
# In some sense, the SOA defines a zone.
@@ -200,7 +156,7 @@ class XfroutSession(BaseRequestHandler):
# specific zone, we need to judge if the zone has an SOA record;
# if not, we consider the zone has incomplete data, so xfrout can't
# serve for it.
- if sqlite3_ds.get_zone_soa(zone, self.server.get_db_file()):
+ if sqlite3_ds.get_zone_soa(zone, self._server.get_db_file()):
return True
return False
@@ -212,7 +168,7 @@ class XfroutSession(BaseRequestHandler):
# authority for the specific zone.
# TODO: should get zone's configuration from cfgmgr or other place
# in future.
- return sqlite3_ds.zone_exist(zonename, self.server.get_db_file())
+ return sqlite3_ds.zone_exist(zonename, self._server.get_db_file())
def _check_xfrout_available(self, zone_name):
'''Check if xfr request can be responsed.
@@ -231,7 +187,7 @@ class XfroutSession(BaseRequestHandler):
return Rcode.SERVFAIL()
#TODO, check allow_transfer
- if not self.server.increase_transfers_counter():
+ if not self._server.increase_transfers_counter():
return Rcode.REFUSED()
return Rcode.NOERROR()
@@ -257,7 +213,7 @@ class XfroutSession(BaseRequestHandler):
except Exception as err:
self._log.log_message("error", str(err))
- self.server.decrease_transfers_counter()
+ self._server.decrease_transfers_counter()
return
@@ -304,14 +260,14 @@ class XfroutSession(BaseRequestHandler):
#TODO, there should be a better way to insert rrset.
msg.make_response()
msg.set_header_flag(Message.HEADERFLAG_AA)
- soa_record = sqlite3_ds.get_zone_soa(zone_name, self.server.get_db_file())
+ soa_record = sqlite3_ds.get_zone_soa(zone_name, self._server.get_db_file())
rrset_soa = self._create_rrset_from_db_record(soa_record)
msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
message_upper_len = get_rrset_len(rrset_soa)
- for rr_data in sqlite3_ds.get_zone_datas(zone_name, self.server.get_db_file()):
- if self.server._shutdown_event.is_set(): # Check if xfrout is shutdown
+ for rr_data in sqlite3_ds.get_zone_datas(zone_name, self._server.get_db_file()):
+ if self._server._shutdown_event.is_set(): # Check if xfrout is shutdown
self._log.log_message("info", "xfrout process is being shutdown")
return
@@ -353,9 +309,93 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
self.update_config_data(config_data)
self._cc = cc
- def finish_request(self, request, client_address):
+ def _receive_query_message(self, sock):
+ ''' receive request message from sock'''
+ # receive data length
+ data_len = sock.recv(2)
+ if not data_len:
+ return None
+ msg_len = struct.unpack('!H', data_len)[0]
+ # receive data
+ recv_size = 0
+ msgdata = b''
+ while recv_size < msg_len:
+ data = sock.recv(msg_len - recv_size)
+ if not data:
+ return None
+ recv_size += len(data)
+ msgdata += data
+
+ return msgdata
+
+ def handle_request(self):
+ ''' Enable server handle a request until shutdown or auth is closed.'''
+ try:
+ request, client_address = self.get_request()
+ except socket.error:
+ self._log.log_message("error", "Failed to fetch request")
+ return
+
+ # Check self._shutdown_event to ensure the real shutdown comes.
+ # Linux could trigger a spurious readable event on the _read_sock
+ # due to a bug, so we need perform a double check.
+ while not self._shutdown_event.is_set(): # Check if xfrout is shutdown
+ try:
+ (rlist, wlist, xlist) = select.select([self._read_sock, request], [], [])
+ except select.error as e:
+ if e.args[0] == errno.EINTR:
+ (rlist, wlist, xlist) = ([], [], [])
+ continue
+ else:
+ self._log.log_message("error", "Error with select(): %s" %e)
+ break
+
+ # self.server._shutdown_event will be set by now, if it is not a false
+ # alarm
+ if self._read_sock in rlist:
+ continue
+
+ try:
+ self.process_request(request)
+ except:
+ self._log.log_message("error", "Exception happened during processing of %s"
+ % str(client_address))
+ break
+
+ def _handle_request_noblock(self):
+ """Override the function _handle_request_noblock(), it creates a new
+ thread to handle requests for each auth"""
+ td = threading.Thread(target=self.handle_request)
+ td.setDaemon(True)
+ td.start()
+
+ def process_request(self, request):
+ """Receive socket fd and query message from auth, then
+ start a new thread to process the request."""
+ sock_fd = recv_fd(request.fileno())
+ if sock_fd < 0:
+ # This may happen when one xfrout process try to connect to
+ # xfrout unix socket server, to check whether there is another
+ # xfrout running.
+ if sock_fd == XFR_FD_RECEIVE_FAIL:
+ self._log.log_message("error", "Failed to receive the file descriptor for XFR connection")
+ return
+
+ # receive request msg
+ request_data = self._receive_query_message(request)
+ if not request_data:
+ return
+
+ t = threading.Thread(target = self.finish_request,
+ args = (sock_fd, request_data))
+ if self.daemon_threads:
+ t.daemon = True
+ t.start()
+
+
+ def finish_request(self, sock_fd, request_data):
'''Finish one request by instantiating RequestHandlerClass.'''
- self.RequestHandlerClass(request, client_address, self, self._log, self._read_sock)
+ self.RequestHandlerClass(sock_fd, request_data, self, self._log)
def _remove_unused_sock_file(self, sock_file):
'''Try to remove the socket file. If the file is being used
@@ -373,7 +413,7 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
try:
os.unlink(sock_file)
except OSError as err:
- self._log.log_message("error", '[b10-xfrout] Fail to remove file %s: %s\n' % (sock_file, err))
+ self._log.log_message("error", "[b10-xfrout] Fail to remove file %s: %s\n" % (sock_file, err))
sys.exit(0)
def _sock_file_in_use(self, sock_file):
@@ -394,7 +434,7 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
try:
os.unlink(self._sock_file)
except Exception as e:
- self._log.log_message("error", str(e))
+ self._log.log_message('error', str(e))
def update_config_data(self, new_config):
'''Apply the new config setting of xfrout module. '''
diff --git a/src/bin/zonemgr/Makefile.am b/src/bin/zonemgr/Makefile.am
index dd3e67a..410279a 100644
--- a/src/bin/zonemgr/Makefile.am
+++ b/src/bin/zonemgr/Makefile.am
@@ -4,7 +4,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-zonemgr
-b10_zonemgrdir = $(DESTDIR)$(pkgdatadir)
+b10_zonemgrdir = $(pkgdatadir)
b10_zonemgr_DATA = zonemgr.spec
CLEANFILES = b10-zonemgr zonemgr.pyc zonemgr.spec
diff --git a/src/cppcheck-suppress.lst b/src/cppcheck-suppress.lst
new file mode 100644
index 0000000..3e4dcd6
--- /dev/null
+++ b/src/cppcheck-suppress.lst
@@ -0,0 +1,15 @@
+// On some systems cppcheck produces false alarms about 'missing includes'.
+// the following two will suppress, depending on the cppcheck version
+debug
+missingInclude
+// This is a template, and should be excluded from the check
+unreadVariable:src/lib/dns/rdata/template.cc:59
+// These three trigger warnings due to the incomplete implementation. This is
+// our problem, but we need to suppress the warnings for now.
+functionConst:src/lib/cache/resolver_cache.h
+functionConst:src/lib/cache/message_cache.h
+functionConst:src/lib/cache/rrset_cache.h
+// Intentional self assignment tests. Suppress warning about them.
+selfAssignment:src/lib/dns/tests/name_unittest.cc:292
+selfAssignment:src/lib/dns/tests/rdata_unittest.cc:227
+selfAssignment:src/lib/dns/tests/tsigkey_unittest.cc:104
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index b7b80bf..8525b8d 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -1,2 +1,2 @@
-SUBDIRS = exceptions dns cc config datasrc python xfr bench log \
- resolve asiolink testutils nsas
+SUBDIRS = exceptions dns cc config python xfr bench log asiolink \
+ nsas cache resolve testutils datasrc server_common
diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am
index 99b3c66..2fda728 100644
--- a/src/lib/asiolink/Makefile.am
+++ b/src/lib/asiolink/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = . tests internal
+SUBDIRS = . tests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
@@ -12,24 +12,40 @@ CLEANFILES = *.gcno *.gcda
# have some code fragments that would hit gcc's unused-parameter warning,
# which would make the build fail with -Werror (our default setting).
lib_LTLIBRARIES = libasiolink.la
-libasiolink_la_SOURCES = asiolink.cc asiolink.h
-libasiolink_la_SOURCES += iosocket.cc iosocket.h
-libasiolink_la_SOURCES += iomessage.h
-libasiolink_la_SOURCES += ioaddress.cc ioaddress.h
-libasiolink_la_SOURCES += ioendpoint.cc ioendpoint.h
-libasiolink_la_SOURCES += udpdns.cc internal/udpdns.h
-libasiolink_la_SOURCES += tcpdns.cc internal/tcpdns.h
-libasiolink_la_SOURCES += internal/coroutine.h
+libasiolink_la_SOURCES = asiolink.h
+libasiolink_la_SOURCES += asiolink_utilities.h
+libasiolink_la_SOURCES += asiodef.cc asiodef.h
+libasiolink_la_SOURCES += dns_answer.h
+libasiolink_la_SOURCES += dns_lookup.h
+libasiolink_la_SOURCES += dns_server.h
+libasiolink_la_SOURCES += dns_service.cc dns_service.h
+libasiolink_la_SOURCES += dummy_io_cb.h
+libasiolink_la_SOURCES += interval_timer.cc interval_timer.h
+libasiolink_la_SOURCES += io_address.cc io_address.h
+libasiolink_la_SOURCES += io_asio_socket.h
+libasiolink_la_SOURCES += io_endpoint.cc io_endpoint.h
+libasiolink_la_SOURCES += io_error.h
+libasiolink_la_SOURCES += io_fetch.cc io_fetch.h
+libasiolink_la_SOURCES += io_message.h
+libasiolink_la_SOURCES += qid_gen.cc qid_gen.h
+libasiolink_la_SOURCES += io_service.h io_service.cc
+libasiolink_la_SOURCES += io_socket.h io_socket.cc
+libasiolink_la_SOURCES += simple_callback.h
+libasiolink_la_SOURCES += tcp_endpoint.h
+libasiolink_la_SOURCES += tcp_server.cc tcp_server.h
+libasiolink_la_SOURCES += tcp_socket.h
+libasiolink_la_SOURCES += udp_endpoint.h
+libasiolink_la_SOURCES += udp_server.cc udp_server.h
+libasiolink_la_SOURCES += udp_socket.h
+
+EXTRA_DIST = asiodef.msg
+
# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS)
libasiolink_la_CXXFLAGS = $(AM_CXXFLAGS)
-if USE_GXX
-libasiolink_la_CXXFLAGS += -Wno-unused-parameter
-endif
if USE_CLANGPP
# Same for clang++, but we need to turn off -Werror completely.
libasiolink_la_CXXFLAGS += -Wno-error
endif
libasiolink_la_CPPFLAGS = $(AM_CPPFLAGS)
libasiolink_la_LIBADD = $(top_builddir)/src/lib/log/liblog.la
-libasiolink_la_LIBADD += $(top_builddir)/src/lib/resolve/libresolve.la
diff --git a/src/lib/asiolink/README b/src/lib/asiolink/README
index b0f6a7d..6bd1a73 100644
--- a/src/lib/asiolink/README
+++ b/src/lib/asiolink/README
@@ -33,7 +33,7 @@ This is intended to simplify development a bit, since it allows the
routines to be written in a straightfowrard step-step-step fashion rather
than as a complex chain of separate handler functions.
-Coroutine objects (i.e., UDPServer, TCPServer and UDPQuery) are objects
+Coroutine objects (i.e., UDPServer, TCPServer and IOFetch) are objects
with reenterable operator() members. When an instance of one of these
classes is called as a function, it resumes at the position where it left
off. Thus, a UDPServer can issue an asynchronous I/O call and specify
@@ -101,3 +101,82 @@ when the answer has arrived. In simplified form, the DNSQuery routine is:
Currently, DNSQuery is only implemented for UDP queries. In future work
it will be necessary to write code to fall back to TCP when circumstances
require it.
+
+
+Upstream Fetches
+================
+Upstream fetches (queries by the resolver on behalf of a client) are made
+using a slightly-modified version of the pattern described above.
+
+Sockets
+-------
+First, it will be useful to understand the class hierarchy used in the
+fetch logic:
+
+ IOSocket
+ |
+ IOAsioSocket
+ |
+ +-----+-----+
+ | |
+UDPSocket TCPSocket
+
+IOSocket is a wrapper class for a socket and is used by the authoritative
+server code. It is an abstract base class, providing little more that the ability to hold the socket and to return the protocol in use.
+
+Built on this is IOAsioSocket, which adds the open, close, asyncSend and
+asyncReceive methods. This is a template class, which takes as template
+argument the class of the object that will be used as the callback when the
+asynchronous operation completes. This object can be of any type, but must
+include an operator() method with the signature:
+
+ operator()(asio::error_code ec, size_t length)
+
+... the two arguments being the status of the completed I/O operation and
+the number of bytes transferred. (In the case of the open method, the second
+argument will be zero.)
+
+Finally, the TCPSocket and UDPSocket classes provide the body of the
+asynchronous operations.
+
+Fetch Sequence
+--------------
+The fetch is implemented by the IOFetch class, which takes as argument the
+protocol to use. The sequence is:
+
+ REENTER:
+ render the question into a wire-format query packet
+ open() // Open socket and optionally connect
+ if (! synchronous) {
+ YIELD;
+ }
+ YIELD asyncSend(query) // Send query
+ do {
+ YIELD asyncReceive(response) // Read response
+ } while (! complete(response))
+ close() // Drop connection and close socket
+ server->resume
+
+The open() method opens a socket for use. On TCP, it also makes a
+connection to the remote end. So under UDP the operation will complete
+immediately, but under TCP it could take a long time. One solution would be
+for the open operation to post an event to the I/O queue; then both cases
+could be regarded as being equivalent, with the completion being signalled
+by the posting of the completion event. However UDP is the most common case
+and that would involve extra overhead. So the open() returns a status
+indicating whether the operation completed asynchronously. If it did, the
+code yields back to the coroutine; if not the yield is bypassed.
+
+The asynchronous send is straightforward, invoking the underlying ASIO
+function. (Note that the address/port is supplied to both the open() and
+asyncSend() methods - it is used by the TCPSocket in open() and by the
+UDPSocket in asyncSend().)
+
+The asyncReceive() method issues an asynchronous read and waits for completion.
+The fetch object keeps track of the amount of data received so far and when
+the receive completes it calls a method on the socket to determine if the
+entire message has been received. (This will always be the case for UDP. On
+TCP though, the message is preceded by a count field as several reads may be
+required to read all the data.) The fetch loops until all the data is read.
+
+Finally, the socket is closed and the server called to resume operation.
diff --git a/src/lib/asiolink/asiodef.cc b/src/lib/asiolink/asiodef.cc
new file mode 100644
index 0000000..94c71b5
--- /dev/null
+++ b/src/lib/asiolink/asiodef.cc
@@ -0,0 +1,37 @@
+// File created from asiodef.msg on Mon Feb 28 17:15:30 2011
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace asiolink {
+
+extern const isc::log::MessageID ASIO_FETCHCOMP = "FETCHCOMP";
+extern const isc::log::MessageID ASIO_FETCHSTOP = "FETCHSTOP";
+extern const isc::log::MessageID ASIO_OPENSOCK = "OPENSOCK";
+extern const isc::log::MessageID ASIO_RECVSOCK = "RECVSOCK";
+extern const isc::log::MessageID ASIO_RECVTMO = "RECVTMO";
+extern const isc::log::MessageID ASIO_SENDSOCK = "SENDSOCK";
+extern const isc::log::MessageID ASIO_UNKORIGIN = "UNKORIGIN";
+extern const isc::log::MessageID ASIO_UNKRESULT = "UNKRESULT";
+
+} // namespace asiolink
+
+namespace {
+
+const char* values[] = {
+ "FETCHCOMP", "upstream fetch to %s(%d) has now completed",
+ "FETCHSTOP", "upstream fetch to %s(%d) has been stopped",
+ "OPENSOCK", "error %d opening %s socket to %s(%d)",
+ "RECVSOCK", "error %d reading %s data from %s(%d)",
+ "RECVTMO", "receive timeout while waiting for data from %s(%d)",
+ "SENDSOCK", "error %d sending data using %s to %s(%d)",
+ "UNKORIGIN", "unknown origin for ASIO error code %d (protocol: %s, address %s)",
+ "UNKRESULT", "unknown result (%d) when IOFetch::stop() was executed for I/O to %s(%d)",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/asiolink/asiodef.h b/src/lib/asiolink/asiodef.h
new file mode 100644
index 0000000..ba77817
--- /dev/null
+++ b/src/lib/asiolink/asiodef.h
@@ -0,0 +1,21 @@
+// File created from asiodef.msg on Mon Feb 28 17:15:30 2011
+
+#ifndef __ASIODEF_H
+#define __ASIODEF_H
+
+#include <log/message_types.h>
+
+namespace asiolink {
+
+extern const isc::log::MessageID ASIO_FETCHCOMP;
+extern const isc::log::MessageID ASIO_FETCHSTOP;
+extern const isc::log::MessageID ASIO_OPENSOCK;
+extern const isc::log::MessageID ASIO_RECVSOCK;
+extern const isc::log::MessageID ASIO_RECVTMO;
+extern const isc::log::MessageID ASIO_SENDSOCK;
+extern const isc::log::MessageID ASIO_UNKORIGIN;
+extern const isc::log::MessageID ASIO_UNKRESULT;
+
+} // namespace asiolink
+
+#endif // __ASIODEF_H
diff --git a/src/lib/asiolink/asiodef.msg b/src/lib/asiolink/asiodef.msg
new file mode 100644
index 0000000..2fcadd1
--- /dev/null
+++ b/src/lib/asiolink/asiodef.msg
@@ -0,0 +1,56 @@
+# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$PREFIX ASIO_
+$NAMESPACE asiolink
+
+FETCHCOMP upstream fetch to %s(%d) has now completed
++ A debug message, this records the the upstream fetch (a query made by the
++ resolver on behalf of its client) to the specified address has completed.
+
+FETCHSTOP upstream fetch to %s(%d) has been stopped
++ An external component has requested the halting of an upstream fetch. This
++ is an allowed operation, and the message should only appear if debug is
++ enabled.
+
+OPENSOCK error %d opening %s socket to %s(%d)
++ The asynchronous I/O code encountered an error when trying to open a socket
++ of the specified protocol in order to send a message to the target address.
++ The the number of the system error that cause the problem is given in the
++ message.
+
+RECVSOCK error %d reading %s data from %s(%d)
++ The asynchronous I/O code encountered an error when trying read data from
++ the specified address on the given protocol. The the number of the system
++ error that cause the problem is given in the message.
+
+SENDSOCK error %d sending data using %s to %s(%d)
++ The asynchronous I/O code encountered an error when trying send data to
++ the specified address on the given protocol. The the number of the system
++ error that cause the problem is given in the message.
+
+RECVTMO receive timeout while waiting for data from %s(%d)
++ An upstream fetch from the specified address timed out. This may happen for
++ any number of reasons and is most probably a problem at the remote server
++ or a problem on the network. The message will only appear if debug is
++ enabled.
+
+UNKORIGIN unknown origin for ASIO error code %d (protocol: %s, address %s)
++ This message should not appear and indicates an internal error if it does.
++ Please enter a bug report.
+
+UNKRESULT unknown result (%d) when IOFetch::stop() was executed for I/O to %s(%d)
++ The termination method of the resolver's upstream fetch class was called with
++ an unknown result code (which is given in the message). This message should
++ not appear and may indicate an internal error. Please enter a bug report.
diff --git a/src/lib/asiolink/asiolink.cc b/src/lib/asiolink/asiolink.cc
deleted file mode 100644
index 6fe7666..0000000
--- a/src/lib/asiolink/asiolink.cc
+++ /dev/null
@@ -1,756 +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 <config.h>
-
-#include <cstdlib> // For rand(), temporary until better forwarding is done
-
-#include <unistd.h> // for some IPC/network system calls
-#include <sys/socket.h>
-#include <netinet/in.h>
-
-#include <vector>
-#include <asio.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/bind.hpp>
-#include <boost/date_time/posix_time/posix_time_types.hpp>
-
-#include <boost/shared_ptr.hpp>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-#include <dns/rcode.h>
-#include <dns/opcode.h>
-
-#include <asiolink/asiolink.h>
-#include <asiolink/internal/tcpdns.h>
-#include <asiolink/internal/udpdns.h>
-
-#include <resolve/resolve.h>
-
-#include <log/dummylog.h>
-
-using namespace asio;
-using asio::ip::udp;
-using asio::ip::tcp;
-
-using namespace std;
-using namespace isc::dns;
-using isc::log::dlog;
-using namespace boost;
-
-// Is this something we can use in libdns++?
-namespace {
- class SectionInserter {
- public:
- SectionInserter(MessagePtr message, const Message::Section sect) :
- message_(message), section_(sect)
- {}
- void operator()(const RRsetPtr rrset) {
- message_->addRRset(section_, rrset, true);
- }
- MessagePtr message_;
- const Message::Section section_;
- };
-
-
- /// \brief Copies the parts relevant for a DNS answer to the
- /// target message
- ///
- /// This adds all the RRsets in the answer, authority and
- /// additional sections to the target, as well as the response
- /// code
- void copyAnswerMessage(const Message& source, MessagePtr target) {
- target->setRcode(source.getRcode());
-
- for_each(source.beginSection(Message::SECTION_ANSWER),
- source.endSection(Message::SECTION_ANSWER),
- SectionInserter(target, Message::SECTION_ANSWER));
- for_each(source.beginSection(Message::SECTION_AUTHORITY),
- source.endSection(Message::SECTION_AUTHORITY),
- SectionInserter(target, Message::SECTION_AUTHORITY));
- for_each(source.beginSection(Message::SECTION_ADDITIONAL),
- source.endSection(Message::SECTION_ADDITIONAL),
- SectionInserter(target, Message::SECTION_ADDITIONAL));
- }
-}
-
-namespace asiolink {
-
-typedef pair<string, uint16_t> addr_t;
-
-class IOServiceImpl {
-private:
- IOServiceImpl(const IOService& source);
- IOServiceImpl& operator=(const IOService& source);
-public:
- /// \brief The constructor
- IOServiceImpl() :
- io_service_(),
- work_(io_service_)
- {};
- /// \brief The destructor.
- ~IOServiceImpl() {};
- //@}
-
- /// \brief Start the underlying event loop.
- ///
- /// This method does not return control to the caller until
- /// the \c stop() method is called via some handler.
- void run() { io_service_.run(); };
-
- /// \brief Run the underlying event loop for a single event.
- ///
- /// This method return control to the caller as soon as the
- /// first handler has completed. (If no handlers are ready when
- /// it is run, it will block until one is.)
- void run_one() { io_service_.run_one();} ;
-
- /// \brief Stop the underlying event loop.
- ///
- /// This will return the control to the caller of the \c run() method.
- void stop() { io_service_.stop();} ;
-
- /// \brief Return the native \c io_service object used in this wrapper.
- ///
- /// This is a short term work around to support other BIND 10 modules
- /// that share the same \c io_service with the authoritative server.
- /// It will eventually be removed once the wrapper interface is
- /// generalized.
- asio::io_service& get_io_service() { return io_service_; };
-private:
- asio::io_service io_service_;
- asio::io_service::work work_;
-};
-
-IOService::IOService() {
- io_impl_ = new IOServiceImpl();
-}
-
-IOService::~IOService() {
- delete io_impl_;
-}
-
-void
-IOService::run() {
- io_impl_->run();
-}
-
-void
-IOService::run_one() {
- io_impl_->run_one();
-}
-
-void
-IOService::stop() {
- io_impl_->stop();
-}
-
-asio::io_service&
-IOService::get_io_service() {
- return (io_impl_->get_io_service());
-}
-
-class DNSServiceImpl {
-public:
- DNSServiceImpl(IOService& io_service, const char& port,
- const ip::address* v4addr, const ip::address* v6addr,
- SimpleCallback* checkin, DNSLookup* lookup,
- DNSAnswer* answer);
-
- IOService& io_service_;
-
- typedef boost::shared_ptr<UDPServer> UDPServerPtr;
- typedef boost::shared_ptr<TCPServer> TCPServerPtr;
- typedef boost::shared_ptr<DNSServer> DNSServerPtr;
- vector<DNSServerPtr> servers_;
- SimpleCallback *checkin_;
- DNSLookup *lookup_;
- DNSAnswer *answer_;
-
- void addServer(uint16_t port, const ip::address& address) {
- try {
- dlog(std::string("Initialize TCP server at ") + address.to_string() + ":" + boost::lexical_cast<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<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 ip::address& address) {
- uint16_t portnum;
- try {
- // XXX: SunStudio with stlport4 doesn't reject some invalid
- // representation such as "-1" by lexical_cast<uint16_t>, so
- // we convert it into a signed integer of a larger size and perform
- // range check ourselves.
- const int32_t portnum32 = boost::lexical_cast<int32_t>(&port);
- if (portnum32 < 0 || portnum32 > 65535) {
- isc_throw(IOError, "Invalid port number '" << &port);
- }
- portnum = portnum32;
- } catch (const boost::bad_lexical_cast& ex) {
- isc_throw(IOError, "Invalid port number '" << &port << "': " <<
- ex.what());
- }
- addServer(portnum, address);
- }
-};
-
-DNSServiceImpl::DNSServiceImpl(IOService& io_service,
- const char& port,
- const ip::address* const v4addr,
- const 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 ip::address v4addr_any = ip::address(ip::address_v4::any());
- const ip::address* const v4addrp = use_ipv4 ? &v4addr_any : NULL;
- const ip::address v6addr_any = ip::address(ip::address_v6::any());
- const ip::address* const v6addrp = use_ipv6 ? &v6addr_any : NULL;
- impl_ = new DNSServiceImpl(io_service, port, v4addrp, v6addrp, checkin, lookup, answer);
-}
-
-DNSService::DNSService(IOService& io_service, SimpleCallback* checkin,
- DNSLookup* lookup, DNSAnswer *answer) :
- impl_(new DNSServiceImpl(io_service, *"0", NULL, NULL, checkin, lookup,
- answer)), io_service_(io_service)
-{
-}
-
-DNSService::~DNSService() {
- delete impl_;
-}
-
-namespace {
-
-typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
-
-}
-
-// Here we do not use the typedef above, as the SunStudio compiler
-// mishandles this in its name mangling, and wouldn't compile.
-// We can probably use a typedef, but need to move it to a central
-// location and use it consistently.
-RecursiveQuery::RecursiveQuery(DNSService& dns_service,
- const std::vector<std::pair<std::string, uint16_t> >& upstream,
- const std::vector<std::pair<std::string, uint16_t> >& upstream_root,
- int query_timeout, int client_timeout, int lookup_timeout,
- unsigned retries) :
- dns_service_(dns_service), upstream_(new AddressVector(upstream)),
- upstream_root_(new AddressVector(upstream_root)),
- query_timeout_(query_timeout), client_timeout_(client_timeout),
- lookup_timeout_(lookup_timeout), retries_(retries)
-{}
-
-namespace {
-
-ip::address
-convertAddr(const string& address) {
- error_code err;
- ip::address addr = ip::address::from_string(address, err);
- if (err) {
- isc_throw(IOError, "Invalid IP address '" << &address << "': "
- << err.message());
- }
- return (addr);
-}
-
-}
-
-void
-DNSService::addServer(const char& port, const string& address) {
- impl_->addServer(port, convertAddr(address));
-}
-
-void
-DNSService::addServer(uint16_t port, const string& address) {
- impl_->addServer(port, convertAddr(address));
-}
-
-void
-DNSService::clearServers() {
- // FIXME: This does not work, it does not close the socket.
- // How is it done?
- impl_->servers_.clear();
-}
-
-namespace {
-
-/*
- * This is a query in progress. When a new query is made, this one holds
- * the context information about it, like how many times we are allowed
- * to retry on failure, what to do when we succeed, etc.
- *
- * Used by RecursiveQuery::sendQuery.
- */
-class RunningQuery : public UDPQuery::Callback {
-private:
- // The io service to handle async calls
- asio::io_service& io_;
-
- // Info for (re)sending the query (the question and destination)
- Question question_;
-
- // This is where we build and store our final answer
- MessagePtr answer_message_;
-
- // currently we use upstream as the current list of NS records
- // we should differentiate between forwarding and resolving
- shared_ptr<AddressVector> upstream_;
-
- // root servers...just copied over to the zone_servers_
- shared_ptr<AddressVector> upstream_root_;
-
- // Buffer to store the result.
- OutputBufferPtr buffer_;
-
- // Server to notify when we succeed or fail
- //shared_ptr<DNSServer> server_;
- isc::resolve::ResolverInterface::CallbackPtr resolvercallback_;
-
- /*
- * TODO Do something more clever with timeouts. In the long term, some
- * computation of average RTT, increase with each retry, etc.
- */
- // Timeout information
- int query_timeout_;
- unsigned retries_;
-
- // normal query state
-
- // Not using NSAS at this moment, so we keep a list
- // of 'current' zone servers
- vector<addr_t> zone_servers_;
-
- // Update the question that will be sent to the server
- void setQuestion(const Question& new_question) {
- question_ = new_question;
- }
-
- deadline_timer client_timer;
- deadline_timer lookup_timer;
-
- size_t queries_out_;
-
- // If we timed out ourselves (lookup timeout), stop issuing queries
- bool done_;
-
- // (re)send the query to the server.
- void send() {
- const int uc = upstream_->size();
- const int zs = zone_servers_.size();
- buffer_->clear();
- if (uc > 0) {
- int serverIndex = rand() % uc;
- dlog("Sending upstream query (" + question_.toText() +
- ") to " + upstream_->at(serverIndex).first);
- UDPQuery query(io_, question_,
- upstream_->at(serverIndex).first,
- upstream_->at(serverIndex).second, buffer_, this,
- query_timeout_);
- ++queries_out_;
- io_.post(query);
- } else if (zs > 0) {
- int serverIndex = rand() % zs;
- dlog("Sending query to zone server (" + question_.toText() +
- ") to " + zone_servers_.at(serverIndex).first);
- UDPQuery query(io_, question_,
- zone_servers_.at(serverIndex).first,
- zone_servers_.at(serverIndex).second, buffer_, this,
- query_timeout_);
- ++queries_out_;
- io_.post(query);
- } else {
- dlog("Error, no upstream servers to send to.");
- }
- }
-
- // This function is called by operator() if there is an actual
- // answer from a server and we are in recursive mode
- // depending on the contents, we go on recursing or return
- //
- // Note that the footprint may change as this function may
- // need to append data to the answer we are building later.
- //
- // returns true if we are done
- // returns false if we are not done
- bool handleRecursiveAnswer(const Message& incoming) {
- if (incoming.getRRCount(Message::SECTION_ANSWER) > 0) {
- dlog("Got final result, copying answer.");
- copyAnswerMessage(incoming, answer_message_);
- return true;
- } else {
- dlog("Got delegation, continuing");
- // ok we need to do some more processing.
- // the ns list should contain all nameservers
- // while the additional may contain addresses for
- // them.
- // this needs to tie into NSAS of course
- // for this very first mockup, hope there is an
- // address in additional and just use that
-
- // send query to the addresses in the delegation
- bool found_ns_address = false;
- zone_servers_.clear();
-
- for (RRsetIterator rrsi = incoming.beginSection(Message::SECTION_ADDITIONAL);
- rrsi != incoming.endSection(Message::SECTION_ADDITIONAL) && !found_ns_address;
- rrsi++) {
- ConstRRsetPtr rrs = *rrsi;
- if (rrs->getType() == RRType::A()) {
- // found address
- RdataIteratorPtr rdi = rrs->getRdataIterator();
- // just use the first for now
- if (!rdi->isLast()) {
- std::string addr_str = rdi->getCurrent().toText();
- dlog("[XX] first address found: " + addr_str);
- // now we have one address, simply
- // resend that exact same query
- // to that address and yield, when it
- // returns, loop again.
-
- // should use NSAS
- zone_servers_.push_back(addr_t(addr_str, 53));
- found_ns_address = true;
- }
- }
- }
- if (found_ns_address) {
- // next resolver round
- send();
- return false;
- } else {
- dlog("[XX] no ready-made addresses in additional. need nsas.");
- // this will result in answering with the delegation. oh well
- copyAnswerMessage(incoming, answer_message_);
- return true;
- }
- }
- }
-
-
-public:
- RunningQuery(asio::io_service& io, const Question &question,
- MessagePtr answer_message, shared_ptr<AddressVector> upstream,
- shared_ptr<AddressVector> upstream_root,
- OutputBufferPtr buffer,
- isc::resolve::ResolverInterface::CallbackPtr cb,
- int query_timeout, int client_timeout, int lookup_timeout,
- unsigned retries) :
- io_(io),
- question_(question),
- answer_message_(answer_message),
- upstream_(upstream),
- upstream_root_(upstream_root),
- buffer_(buffer),
- resolvercallback_(cb),
- query_timeout_(query_timeout),
- retries_(retries),
- client_timer(io),
- lookup_timer(io),
- queries_out_(0),
- done_(false)
- {
- // Setup the timer to stop trying (lookup_timeout)
- if (lookup_timeout >= 0) {
- lookup_timer.expires_from_now(
- boost::posix_time::milliseconds(lookup_timeout));
- lookup_timer.async_wait(boost::bind(&RunningQuery::stop, this, false));
- }
-
- // Setup the timer to send an answer (client_timeout)
- if (client_timeout >= 0) {
- client_timer.expires_from_now(
- boost::posix_time::milliseconds(client_timeout));
- client_timer.async_wait(boost::bind(&RunningQuery::clientTimeout, this));
- }
-
- // should use NSAS for root servers
- // Adding root servers if not a forwarder
- if (upstream_->empty()) {
- if (upstream_root_->empty()) { //if no root ips given, use this
- zone_servers_.push_back(addr_t("192.5.5.241", 53));
- }
- else
- {
- //copy the list
- dlog("Size is " +
- boost::lexical_cast<string>(upstream_root_->size()) +
- "\n");
- //Use BOOST_FOREACH here? Is it faster?
- for(AddressVector::iterator it = upstream_root_->begin();
- it < upstream_root_->end(); it++) {
- zone_servers_.push_back(addr_t(it->first,it->second));
- dlog("Put " + zone_servers_.back().first + "into root list\n");
- }
- }
- }
-
- send();
- }
-
- virtual void clientTimeout() {
- // right now, just stop (should make SERVFAIL and send that
- // back, but not stop)
- stop(false);
- }
-
- virtual void stop(bool resume) {
- // if we cancel our timers, we will still get an event for
- // that, so we cannot delete ourselves just yet (those events
- // would be bound to a deleted object)
- // cancel them one by one, both cancels should get us back
- // here again.
- // same goes if we have an outstanding query (can't delete
- // until that one comes back to us)
- done_ = true;
- if (resume) {
- resolvercallback_->success(answer_message_);
- } else {
- resolvercallback_->failure();
- }
- if (lookup_timer.cancel() != 0) {
- return;
- }
- if (client_timer.cancel() != 0) {
- return;
- }
- if (queries_out_ > 0) {
- return;
- }
- delete this;
- }
-
- // This function is used as callback from DNSQuery.
- virtual void operator()(UDPQuery::Result result) {
- // XXX is this the place for TCP retry?
- --queries_out_;
- if (!done_ && result != UDPQuery::TIME_OUT) {
- // we got an answer
- Message incoming(Message::PARSE);
- InputBuffer ibuf(buffer_->getData(), buffer_->getLength());
- incoming.fromWire(ibuf);
-
- if (upstream_->size() == 0 &&
- incoming.getRcode() == Rcode::NOERROR()) {
- done_ = handleRecursiveAnswer(incoming);
- } else {
- copyAnswerMessage(incoming, answer_message_);
- done_ = true;
- }
-
- if (done_) {
- stop(true);
- }
- } else if (!done_ && retries_--) {
- // We timed out, but we have some retries, so send again
- dlog("Timeout, resending query");
- send();
- } else {
- // out of retries, give up for now
- stop(false);
- }
- }
-};
-
-}
-
-void
-RecursiveQuery::resolve(const isc::dns::QuestionPtr& question,
- const isc::resolve::ResolverInterface::CallbackPtr callback)
-{
- asio::io_service& io = dns_service_.get_io_service();
-
- MessagePtr answer_message(new Message(Message::RENDER));
- OutputBufferPtr buffer(new OutputBuffer(0));
-
- // It will delete itself when it is done
- new RunningQuery(io, *question, answer_message, upstream_,
- upstream_root_, buffer, callback, query_timeout_,
- client_timeout_, lookup_timeout_, retries_);
-}
-
-void
-RecursiveQuery::resolve(const Question& question,
- MessagePtr answer_message,
- OutputBufferPtr buffer,
- DNSServer* server)
-{
- // XXX: eventually we will need to be able to determine whether
- // the message should be sent via TCP or UDP, or sent initially via
- // UDP and then fall back to TCP on failure, but for the moment
- // we're only going to handle UDP.
- asio::io_service& io = dns_service_.get_io_service();
-
- isc::resolve::ResolverInterface::CallbackPtr crs(
- new isc::resolve::ResolverCallbackServer(server));
-
- // It will delete itself when it is done
- new RunningQuery(io, question, answer_message, upstream_, upstream_root_,
- buffer, crs, query_timeout_, client_timeout_,
- lookup_timeout_, retries_);
-}
-
-class IntervalTimerImpl {
-private:
- // prohibit copy
- IntervalTimerImpl(const IntervalTimerImpl& source);
- IntervalTimerImpl& operator=(const IntervalTimerImpl& source);
-public:
- IntervalTimerImpl(IOService& io_service);
- ~IntervalTimerImpl();
- void setup(const IntervalTimer::Callback& cbfunc, const long interval);
- void callback(const asio::error_code& error);
- void cancel() {
- timer_.cancel();
- interval_ = 0;
- }
- long getInterval() const { return (interval_); }
-private:
- // a function to update timer_ when it expires
- void update();
- // a function to call back when timer_ expires
- IntervalTimer::Callback cbfunc_;
- // interval in milliseconds
- long interval_;
- // asio timer
- asio::deadline_timer timer_;
-};
-
-IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
- interval_(0), timer_(io_service.get_io_service())
-{}
-
-IntervalTimerImpl::~IntervalTimerImpl()
-{}
-
-void
-IntervalTimerImpl::setup(const IntervalTimer::Callback& cbfunc,
- const long interval)
-{
- // Interval should not be less than or equal to 0.
- if (interval <= 0) {
- isc_throw(isc::BadValue, "Interval should not be less than or "
- "equal to 0");
- }
- // Call back function should not be empty.
- if (cbfunc.empty()) {
- isc_throw(isc::InvalidParameter, "Callback function is empty");
- }
- cbfunc_ = cbfunc;
- interval_ = interval;
- // Set initial expire time.
- // At this point the timer is not running yet and will not expire.
- // After calling IOService::run(), the timer will expire.
- update();
- return;
-}
-
-void
-IntervalTimerImpl::update() {
- if (interval_ == 0) {
- // timer has been canceled. Do nothing.
- return;
- }
- try {
- // Update expire time to (current time + interval_).
- timer_.expires_from_now(boost::posix_time::millisec(interval_));
- } catch (const asio::system_error& e) {
- isc_throw(isc::Unexpected, "Failed to update timer");
- }
- // Reset timer.
- timer_.async_wait(boost::bind(&IntervalTimerImpl::callback, this, _1));
-}
-
-void
-IntervalTimerImpl::callback(const asio::error_code& cancelled) {
- // Do not call cbfunc_ in case the timer was cancelled.
- // The timer will be canelled in the destructor of asio::deadline_timer.
- if (!cancelled) {
- cbfunc_();
- // Set next expire time.
- update();
- }
-}
-
-IntervalTimer::IntervalTimer(IOService& io_service) {
- impl_ = new IntervalTimerImpl(io_service);
-}
-
-IntervalTimer::~IntervalTimer() {
- delete impl_;
-}
-
-void
-IntervalTimer::setup(const Callback& cbfunc, const long interval) {
- return (impl_->setup(cbfunc, interval));
-}
-
-void
-IntervalTimer::cancel() {
- impl_->cancel();
-}
-
-long
-IntervalTimer::getInterval() const {
- return (impl_->getInterval());
-}
-
-}
diff --git a/src/lib/asiolink/asiolink.h b/src/lib/asiolink/asiolink.h
index 521aebf..6e8fe84 100644
--- a/src/lib/asiolink/asiolink.h
+++ b/src/lib/asiolink/asiolink.h
@@ -18,34 +18,20 @@
// IMPORTANT NOTE: only very few ASIO headers files can be included in
// this file. In particular, asio.hpp should never be included here.
// See the description of the namespace below.
-#include <unistd.h> // for some network system calls
-#include <asio/ip/address.hpp>
-#include <boost/shared_ptr.hpp>
-#include <boost/function.hpp>
-#include <functional>
-#include <string>
-#include <vector>
-#include <utility>
+#include <asiolink/io_service.h>
+#include <asiolink/dns_service.h>
+#include <asiolink/dns_server.h>
+#include <asiolink/dns_lookup.h>
+#include <asiolink/dns_answer.h>
+#include <asiolink/simple_callback.h>
+#include <asiolink/interval_timer.h>
-#include <dns/buffer.h>
-#include <dns/message.h>
-#include <dns/question.h>
-#include <dns/rcode.h>
-
-#include <exceptions/exceptions.h>
-
-#include <asiolink/ioaddress.h>
-#include <asiolink/ioendpoint.h>
-#include <asiolink/iomessage.h>
-#include <asiolink/iosocket.h>
-
-#include <resolve/resolver_interface.h>
-
-namespace asio {
-// forward declaration for IOService::get_io_service() below
-class io_service;
-}
+#include <asiolink/io_address.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_message.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/io_error.h>
/// \namespace asiolink
/// \brief A wrapper interface for the ASIO library.
@@ -97,632 +83,4 @@ class io_service;
/// the placeholder of callback handlers:
/// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html
-namespace asiolink {
-class DNSServiceImpl;
-struct IOServiceImpl;
-struct IntervalTimerImpl;
-
-
-/// \brief An exception that is thrown if an error occurs within the IO
-/// module. This is mainly intended to be a wrapper exception class for
-/// ASIO specific exceptions.
-class IOError : public isc::Exception {
-public:
- IOError(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-/// \brief Forward declarations for classes used below
-class SimpleCallback;
-class DNSLookup;
-class DNSAnswer;
-
-/// \brief The \c IOService class is a wrapper for the ASIO \c io_service
-/// class.
-///
-class IOService {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- IOService(const IOService& source);
- IOService& operator=(const IOService& source);
-public:
- /// \brief The constructor
- IOService();
- /// \brief The destructor.
- ~IOService();
- //@}
-
- /// \brief Start the underlying event loop.
- ///
- /// This method does not return control to the caller until
- /// the \c stop() method is called via some handler.
- void run();
-
- /// \brief Run the underlying event loop for a single event.
- ///
- /// This method return control to the caller as soon as the
- /// first handler has completed. (If no handlers are ready when
- /// it is run, it will block until one is.)
- void run_one();
-
- /// \brief Stop the underlying event loop.
- ///
- /// This will return the control to the caller of the \c run() method.
- void stop();
-
- /// \brief Return the native \c io_service object used in this wrapper.
- ///
- /// This is a short term work around to support other BIND 10 modules
- /// that share the same \c io_service with the authoritative server.
- /// It will eventually be removed once the wrapper interface is
- /// generalized.
- asio::io_service& get_io_service();
-
-private:
- IOServiceImpl* io_impl_;
-};
-
-///
-/// DNSService is the service that handles DNS queries and answers with
-/// a given IOService. This class is mainly intended to hold all the
-/// logic that is shared between the authoritative and the recursive
-/// server implementations. As such, it handles asio, including config
-/// updates (through the 'Checkinprovider'), and listening sockets.
-///
-class DNSService {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- DNSService(const DNSService& source);
- DNSService& operator=(const DNSService& source);
-
-public:
- /// \brief The constructor with a specific IP address and port on which
- /// the services listen on.
- ///
- /// \param io_service The IOService to work with
- /// \param port the port to listen on
- /// \param address the IP address to listen on
- /// \param checkin Provider for cc-channel events (see \c SimpleCallback)
- /// \param lookup The lookup provider (see \c DNSLookup)
- /// \param answer The answer provider (see \c DNSAnswer)
- DNSService(IOService& io_service, const char& port,
- const char& address, SimpleCallback* checkin,
- DNSLookup* lookup, DNSAnswer* answer);
- /// \brief The constructor with a specific port on which the services
- /// listen on.
- ///
- /// It effectively listens on "any" IPv4 and/or IPv6 addresses.
- /// IPv4/IPv6 services will be available if and only if \c use_ipv4
- /// or \c use_ipv6 is \c true, respectively.
- ///
- /// \param io_service The IOService to work with
- /// \param port the port to listen on
- /// \param use_ipv4 If true, listen on ipv4 'any'
- /// \param use_ipv6 If true, listen on ipv6 'any'
- /// \param checkin Provider for cc-channel events (see \c SimpleCallback)
- /// \param lookup The lookup provider (see \c DNSLookup)
- /// \param answer The answer provider (see \c DNSAnswer)
- DNSService(IOService& io_service, const char& port,
- const bool use_ipv4, const bool use_ipv6,
- SimpleCallback* checkin, DNSLookup* lookup,
- DNSAnswer* answer);
- /// \brief The constructor without any servers.
- ///
- /// Use addServer() to add some servers.
- DNSService(IOService& io_service, SimpleCallback* checkin,
- DNSLookup* lookup, DNSAnswer* answer);
- /// \brief The destructor.
- ~DNSService();
- //@}
-
- /// \brief Add another server to the service
- void addServer(uint16_t port, const std::string &address);
- void addServer(const char &port, const std::string &address);
- /// \brief Remove all servers from the service
- void clearServers();
-
- /// \brief Return the native \c io_service object used in this wrapper.
- ///
- /// This is a short term work around to support other BIND 10 modules
- /// that share the same \c io_service with the authoritative server.
- /// It will eventually be removed once the wrapper interface is
- /// generalized.
- asio::io_service& get_io_service() { return io_service_.get_io_service(); }
-private:
- DNSServiceImpl* impl_;
- IOService& io_service_;
-};
-
-/// \brief The \c DNSServer class is a wrapper (and base class) for
-/// classes which provide DNS server functionality.
-///
-/// The classes derived from this one, \c TCPServer and \c UDPServer,
-/// act as the interface layer between clients sending queries, and
-/// functions defined elsewhere that provide answers to those queries.
-/// Those functions are described in more detail below under
-/// \c SimpleCallback, \c DNSLookup, and \c DNSAnswer.
-///
-/// Notes to developers:
-/// When constructed, this class (and its derived classes) will have its
-/// "self_" member set to point to "this". Objects of this class (as
-/// instantiated through a base class) are sometimes passed by
-/// reference (as this superclass); calls to methods in the base
-/// class are then rerouted via this pointer to methods in the derived
-/// class. This allows code from outside asiolink, with no specific
-/// knowledge of \c TCPServer or \c UDPServer, to access their methods.
-///
-/// This class is both assignable and copy-constructable. Its subclasses
-/// use the "stackless coroutine" pattern, meaning that it will copy itself
-/// when "forking", and that instances will be posted as ASIO handler
-/// objects, which are always copied.
-///
-/// Because these objects are frequently copied, it is recommended
-/// that derived classes be kept small to reduce copy overhead.
-class DNSServer {
-protected:
- ///
- /// \name Constructors and destructors
- ///
- /// This is intentionally defined as \c protected, as this base class
- /// should never be instantiated except as part of a derived class.
- //@{
- DNSServer() : self_(this) {}
-public:
- /// \brief The destructor
- virtual ~DNSServer() {}
- //@}
-
- ///
- /// \name Class methods
- ///
- /// These methods all make their calls indirectly via the "self_"
- /// pointer, ensuring that the functions ultimately invoked will be
- /// the ones in the derived class. This makes it possible to pass
- /// instances of derived classes as references to this base class
- /// without losing access to derived class data.
- ///
- //@{
- /// \brief The funtion operator
- virtual void operator()(asio::error_code ec = asio::error_code(),
- size_t length = 0)
- {
- (*self_)(ec, length);
- }
-
- /// \brief Resume processing of the server coroutine after an
- /// asynchronous call (e.g., to the DNS Lookup provider) has completed.
- ///
- /// \param done If true, this signals the system there is an answer
- /// to return.
- virtual void resume(const bool done) { self_->resume(done); }
-
- /// \brief Indicate whether the server is able to send an answer
- /// to a query.
- ///
- /// This is presently used only for testing purposes.
- virtual bool hasAnswer() { return (self_->hasAnswer()); }
-
- /// \brief Returns the current value of the 'coroutine' object
- ///
- /// This is a temporary method, intended to be used for debugging
- /// purposes during development and removed later. It allows
- /// callers from outside the coroutine object to retrieve information
- /// about its current state.
- ///
- /// \return The value of the 'coroutine' object
- virtual int value() { return (self_->value()); }
-
- /// \brief Returns a pointer to a clone of this DNSServer object.
- ///
- /// When a \c DNSServer object is copied or assigned, the result will
- /// normally be another \c DNSServer object containing a copy
- /// of the original "self_" pointer. Calling clone() guarantees
- /// that the underlying object is also correctly copied.
- ///
- /// \return A deep copy of this DNSServer object
- virtual DNSServer* clone() { return (self_->clone()); }
- //@}
-
-protected:
- /// \brief Lookup handler object.
- ///
- /// This is a protected class; it can only be instantiated
- /// from within a derived class of \c DNSServer.
- ///
- /// A server object that has received a query creates an instance
- /// of this class and scheudles it on the ASIO service queue
- /// using asio::io_service::post(). When the handler executes, it
- /// calls the asyncLookup() method in the server object to start a
- /// DNS lookup. When the lookup is complete, the server object is
- /// scheduled to resume, again using io_service::post().
- ///
- /// Note that the calling object is copied into the handler object,
- /// not referenced. This is because, once the calling object yields
- /// control to the handler, it falls out of scope and may disappear
- template <typename T>
- class AsyncLookup {
- public:
- AsyncLookup(T& caller) : caller_(caller) {}
- void operator()() { caller_.asyncLookup(); }
- private:
- T caller_;
- };
-
- /// \brief Carries out a DNS lookup.
- ///
- /// This function calls the \c DNSLookup object specified by the
- /// DNS server when the \c IOService was created, passing along
- /// the details of the query and a pointer back to the current
- /// server object. It is called asynchronously via the AsyncLookup
- /// handler class.
- virtual void asyncLookup() { self_->asyncLookup(); }
-
-private:
- DNSServer* self_;
-};
-
-/// \brief The \c DNSLookup class is an abstract base class for a DNS
-/// Lookup provider function.
-///
-/// Specific derived class implementations are hidden within the
-/// implementation. Instances of the derived classes can be called
-/// as functions via the operator() interface. Pointers to these
-/// instances can then be provided to the \c IOService class
-/// via its constructor.
-///
-/// A DNS Lookup provider function obtains the data needed to answer
-/// a DNS query (e.g., from authoritative data source, cache, or upstream
-/// query). After it has run, the OutputBuffer object passed to it
-/// should contain the answer to the query, in an internal representation.
-class DNSLookup {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- DNSLookup(const DNSLookup& source);
- DNSLookup& operator=(const DNSLookup& source);
-protected:
- /// \brief The default constructor.
- ///
- /// This is intentionally defined as \c protected as this base class
- /// should never be instantiated (except as part of a derived class).
- DNSLookup() : self_(this) {}
-public:
- /// \brief The destructor
- virtual ~DNSLookup() {}
- //@}
- /// \brief The function operator
- ///
- /// This makes its call indirectly via the "self" pointer, ensuring
- /// that the function ultimately invoked will be the one in the derived
- /// class.
- ///
- /// \param io_message The event message to handle
- /// \param message The DNS MessagePtr that needs handling
- /// \param answer_message The DNS MessagePtr TODO
- /// \param buffer The final answer is put here
- /// \param server DNSServer object to use
- virtual void operator()(const IOMessage& io_message,
- isc::dns::MessagePtr message,
- isc::dns::MessagePtr answer_message,
- isc::dns::OutputBufferPtr buffer,
- DNSServer* server) const
- {
- (*self_)(io_message, message, answer_message, buffer, server);
- }
-private:
- DNSLookup* self_;
-};
-
-/// \brief The \c DNSAnswer class is an abstract base class for a DNS
-/// Answer provider function.
-///
-/// Specific derived class implementations are hidden within the
-/// implementation. Instances of the derived classes can be called
-/// as functions via the operator() interface. Pointers to these
-/// instances can then be provided to the \c IOService class
-/// via its constructor.
-///
-/// A DNS Answer provider function takes answer data that has been obtained
-/// from a DNS Lookup provider functon and readies it to be sent to the
-/// client. After it has run, the OutputBuffer object passed to it should
-/// contain the answer to the query rendered into wire format.
-class DNSAnswer {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- DNSAnswer(const DNSAnswer& source);
- DNSAnswer& operator=(const DNSAnswer& source);
-protected:
- /// \brief The default constructor.
- ///
- /// This is intentionally defined as \c protected as this base class
- /// should never be instantiated (except as part of a derived class).
- DNSAnswer() {}
-public:
- /// \brief The destructor
- virtual ~DNSAnswer() {}
- //@}
- /// \brief The function operator
- ///
- /// This makes its call indirectly via the "self" pointer, ensuring
- /// that the function ultimately invoked will be the one in the derived
- /// class.
- ///
- /// \param io_message The event message to handle
- /// \param message The DNS MessagePtr that needs handling
- /// \param answer_message The DNS MessagePtr TODO
- /// \param buffer The result is put here
- virtual void operator()(const IOMessage& io_message,
- isc::dns::MessagePtr message,
- isc::dns::MessagePtr answer_message,
- isc::dns::OutputBufferPtr buffer) const = 0;
-};
-
-/// \brief The \c SimpleCallback class is an abstract base class for a
-/// simple callback function with the signature:
-///
-/// void simpleCallback(const IOMessage& io_message) const;
-///
-/// Specific derived class implementations are hidden within the
-/// implementation. Instances of the derived classes can be called
-/// as functions via the operator() interface. Pointers to these
-/// instances can then be provided to the \c IOService class
-/// via its constructor.
-///
-/// The \c SimpleCallback is expected to be used for basic, generic
-/// tasks such as checking for configuration changes. It may also be
-/// used for testing purposes.
-class SimpleCallback {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- SimpleCallback(const SimpleCallback& source);
- SimpleCallback& operator=(const SimpleCallback& source);
-protected:
- /// \brief The default constructor.
- ///
- /// This is intentionally defined as \c protected as this base class
- /// should never be instantiated (except as part of a derived class).
- SimpleCallback() : self_(this) {}
-public:
- /// \brief The destructor
- virtual ~SimpleCallback() {}
- /// \brief The function operator
- //@}
- ///
- /// This makes its call indirectly via the "self" pointer, ensuring
- /// that the function ultimately invoked will be the one in the derived
- /// class.
- ///
- /// \param io_message The event message to handle
- virtual void operator()(const IOMessage& io_message) const {
- (*self_)(io_message);
- }
-private:
- SimpleCallback* self_;
-};
-
-/// \brief The \c RecursiveQuery class provides a layer of abstraction around
-/// the ASIO code that carries out an upstream query.
-///
-/// This design is very preliminary; currently it is only capable of
-/// handling simple forward requests to a single resolver.
-class RecursiveQuery {
- ///
- /// \name Constructors
- ///
- //@{
-public:
- /// \brief Constructor
- ///
- /// This is currently the only way to construct \c RecursiveQuery
- /// object. If the addresses of the forward nameservers is specified,
- /// and every upstream query will be sent to one random address, and
- /// the result sent back directly. If not, it will do full resolving.
- ///
- /// \param dns_service The DNS Service to perform the recursive
- /// query on.
- /// \param upstream Addresses and ports of the upstream servers
- /// to forward queries to.
- /// \param upstream_root Addresses and ports of the root servers
- /// to use when resolving.
- /// \param query_timeout How long to timeout the query, in ms
- /// -1 means never timeout (but do not use that).
- /// TODO: This should be computed somehow dynamically in future
- /// \param client_timeout TODO:
- /// \param lookup_timeout TODO:
- /// \param retries how many times we try again (0 means just send and
- /// and return if it returs).
- RecursiveQuery(DNSService& dns_service,
- const std::vector<std::pair<std::string, uint16_t> >&
- upstream,
- const std::vector<std::pair<std::string, uint16_t> >&
- upstream_root,
- int query_timeout = 2000,
- int client_timeout = 4000,
- int lookup_timeout = 30000,
- unsigned retries = 3);
- //@}
-
- /// \brief Initiate resolving
- ///
- /// When sendQuery() is called, a (set of) message(s) is sent
- /// asynchronously. If upstream servers are set, one is chosen
- /// and the response (if any) from that server will be returned.
- ///
- /// If not upstream is set, a root server is chosen from the
- /// root_servers, and the RunningQuery shall do a full resolve
- /// (i.e. if the answer is a delegation, it will be followed, etc.)
- /// until there is an answer or an error.
- ///
- /// When there is a response or an error and we give up, the given
- /// CallbackPtr object shall be called (with either success() or
- /// failure(). See ResolverInterface::Callback for more information.
- ///
- /// \param question The question being answered <qname/qclass/qtype>
- /// \param callback Callback object. See
- /// \c ResolverInterface::Callback for more information
- void resolve(const isc::dns::QuestionPtr& question,
- const isc::resolve::ResolverInterface::CallbackPtr callback);
-
-
- /// \brief Initiates resolving for the given question.
- ///
- /// This actually calls the previous sendQuery() with a default
- /// callback object, which calls resume() on the given DNSServer
- /// object.
- ///
- /// \param question The question being answered <qname/qclass/qtype>
- /// \param answer_message An output Message into which the final response will be copied
- /// \param buffer An output buffer into which the intermediate responses will be copied
- /// \param server A pointer to the \c DNSServer object handling the client
- void resolve(const isc::dns::Question& question,
- isc::dns::MessagePtr answer_message,
- isc::dns::OutputBufferPtr buffer,
- DNSServer* server);
-private:
- DNSService& dns_service_;
- boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
- upstream_;
- boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
- upstream_root_;
- int query_timeout_;
- int client_timeout_;
- int lookup_timeout_;
- unsigned retries_;
-};
-
-/// \brief The \c IntervalTimer class is a wrapper for the ASIO
-/// \c asio::deadline_timer class.
-///
-/// This class is implemented to use \c asio::deadline_timer as interval
-/// timer.
-///
-/// \c setup() sets a timer to expire on (now + interval) and a call back
-/// function.
-///
-/// \c IntervalTimerImpl::callback() is called by the timer when it expires.
-///
-/// The function calls the call back function set by \c setup() and updates
-/// the timer to expire in (now + interval) milliseconds.
-/// The type of call back function is \c void(void).
-///
-/// The call back function will not be called if the instance of this class is
-/// destroyed before the timer is expired.
-///
-/// Note: Destruction of an instance of this class while call back is pending
-/// causes throwing an exception from \c IOService.
-///
-/// Sample code:
-/// \code
-/// void function_to_call_back() {
-/// // this function will be called periodically
-/// }
-/// int interval_in_milliseconds = 1000;
-/// IOService io_service;
-///
-/// IntervalTimer intervalTimer(io_service);
-/// intervalTimer.setup(function_to_call_back, interval_in_milliseconds);
-/// io_service.run();
-/// \endcode
-class IntervalTimer {
-public:
- /// \name The type of timer callback function
- typedef boost::function<void()> Callback;
-
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- IntervalTimer(const IntervalTimer& source);
- IntervalTimer& operator=(const IntervalTimer& source);
-public:
- /// \brief The constructor with \c IOService.
- ///
- /// This constructor may throw a standard exception if
- /// memory allocation fails inside the method.
- /// This constructor may also throw \c asio::system_error.
- ///
- /// \param io_service A reference to an instance of IOService
- IntervalTimer(IOService& io_service);
-
- /// \brief The destructor.
- ///
- /// This destructor never throws an exception.
- ///
- /// On the destruction of this class the timer will be canceled
- /// inside \c asio::deadline_timer.
- ~IntervalTimer();
- //@}
-
- /// \brief Register timer callback function and interval.
- ///
- /// This function sets callback function and interval in milliseconds.
- /// Timer will actually start after calling \c IOService::run().
- ///
- /// \param cbfunc A reference to a function \c void(void) to call back
- /// when the timer is expired (should not be an empty functor)
- /// \param interval Interval in milliseconds (greater than 0)
- ///
- /// Note: IntervalTimer will not pass \c asio::error_code to
- /// call back function. In case the timer is cancelled, the function
- /// will not be called.
- ///
- /// \throw isc::InvalidParameter cbfunc is empty
- /// \throw isc::BadValue interval is less than or equal to 0
- /// \throw isc::Unexpected ASIO library error
- void setup(const Callback& cbfunc, const long interval);
-
- /// Cancel the timer.
- ///
- /// If the timer has been set up, this method cancels any asynchronous
- /// events waiting on the timer and stops the timer itself.
- /// If the timer has already been canceled, this method effectively does
- /// nothing.
- ///
- /// This method never throws an exception.
- void cancel();
-
- /// Return the timer interval.
- ///
- /// This method returns the timer interval in milliseconds if it's running;
- /// if the timer has been canceled it returns 0.
- ///
- /// This method never throws an exception.
- long getInterval() const;
-
-private:
- IntervalTimerImpl* impl_;
-};
-
-} // asiolink
#endif // __ASIOLINK_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/asiolink_utilities.h b/src/lib/asiolink/asiolink_utilities.h
new file mode 100644
index 0000000..659e6a0
--- /dev/null
+++ b/src/lib/asiolink/asiolink_utilities.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_UTILITIES_H
+#define __ASIOLINK_UTILITIES_H
+
+#include <cstddef>
+
+namespace asiolink {
+
+/// \brief Read Unsigned 16-Bit Integer from Buffer
+///
+/// This is essentially a copy of the isc::dns::InputBuffer::readUint16. It
+/// should really be moved into a separate library.
+///
+/// \param buffer Data buffer at least two bytes long of which the first two
+/// bytes are assumed to represent a 16-bit integer in network-byte
+/// order.
+///
+/// \return Value of 16-bit integer
+inline uint16_t
+readUint16(const void* buffer) {
+ const uint8_t* byte_buffer = static_cast<const uint8_t*>(buffer);
+
+ uint16_t result = (static_cast<uint16_t>(byte_buffer[0])) << 8;
+ result |= static_cast<uint16_t>(byte_buffer[1]);
+
+ return (result);
+}
+
+/// \brief Write Unisgned 16-Bit Integer to Buffer
+///
+/// This is essentially a copy of isc::dns::OutputBuffer::writeUint16. It
+/// should really be moved into a separate library.
+///
+/// \param value 16-bit value to convert
+/// \param buffer Data buffer at least two bytes long into which the 16-bit
+/// value is written in network-byte order.
+
+inline void
+writeUint16(uint16_t value, void* buffer) {
+ uint8_t* byte_buffer = static_cast<uint8_t*>(buffer);
+
+ byte_buffer[0] = static_cast<uint8_t>((value & 0xff00U) >> 8);
+ byte_buffer[1] = static_cast<uint8_t>(value & 0x00ffU);
+}
+
+} // namespace asiolink
+
+#endif // __ASIOLINK_UTILITIES_H
diff --git a/src/lib/asiolink/dns_answer.h b/src/lib/asiolink/dns_answer.h
new file mode 100644
index 0000000..84e1f6f
--- /dev/null
+++ b/src/lib/asiolink/dns_answer.h
@@ -0,0 +1,73 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_DNS_ANSWER_H
+#define __ASIOLINK_DNS_ANSWER_H 1
+
+#include <asiolink/io_message.h>
+
+namespace asiolink {
+
+/// \brief The \c DNSAnswer class is an abstract base class for a DNS
+/// Answer provider function.
+///
+/// Specific derived class implementations are hidden within the
+/// implementation. Instances of the derived classes can be called
+/// as functions via the operator() interface. Pointers to these
+/// instances can then be provided to the \c IOService class
+/// via its constructor.
+///
+/// A DNS Answer provider function takes answer data that has been obtained
+/// from a DNS Lookup provider functon and readies it to be sent to the
+/// client. After it has run, the OutputBuffer object passed to it should
+/// contain the answer to the query rendered into wire format.
+class DNSAnswer {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ DNSAnswer(const DNSAnswer& source);
+ DNSAnswer& operator=(const DNSAnswer& source);
+protected:
+ /// \brief The default constructor.
+ ///
+ /// This is intentionally defined as \c protected as this base class
+ /// should never be instantiated (except as part of a derived class).
+ DNSAnswer() {}
+public:
+ /// \brief The destructor
+ virtual ~DNSAnswer() {}
+ //@}
+ /// \brief The function operator
+ ///
+ /// This makes its call indirectly via the "self" pointer, ensuring
+ /// that the function ultimately invoked will be the one in the derived
+ /// class.
+ ///
+ /// \param io_message The event message to handle
+ /// \param query_message The DNS MessagePtr of the original query
+ /// \param answer_message The DNS MessagePtr of the answer we are
+ /// building
+ /// \param buffer Intermediate data results are put here
+ virtual void operator()(const IOMessage& io_message,
+ isc::dns::MessagePtr query_message,
+ isc::dns::MessagePtr answer_message,
+ isc::dns::OutputBufferPtr buffer) const = 0;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_DNS_ANSWER_H
diff --git a/src/lib/asiolink/dns_lookup.h b/src/lib/asiolink/dns_lookup.h
new file mode 100644
index 0000000..39b3709
--- /dev/null
+++ b/src/lib/asiolink/dns_lookup.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_DNS_LOOKUP_H
+#define __ASIOLINK_DNS_LOOKUP_H 1
+
+#include <asiolink/io_message.h>
+#include <asiolink/dns_server.h>
+#include <dns/buffer.h>
+#include <dns/message.h>
+
+namespace asiolink {
+
+/// \brief The \c DNSLookup class is an abstract base class for a DNS
+/// Lookup provider function.
+///
+/// Specific derived class implementations are hidden within the
+/// implementation. Instances of the derived classes can be called
+/// as functions via the operator() interface. Pointers to these
+/// instances can then be provided to the \c IOService class
+/// via its constructor.
+///
+/// A DNS Lookup provider function obtains the data needed to answer
+/// a DNS query (e.g., from authoritative data source, cache, or upstream
+/// query). After it has run, the OutputBuffer object passed to it
+/// should contain the answer to the query, in an internal representation.
+class DNSLookup {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ DNSLookup(const DNSLookup& source);
+ DNSLookup& operator=(const DNSLookup& source);
+protected:
+ /// \brief The default constructor.
+ ///
+ /// This is intentionally defined as \c protected as this base class
+ /// should never be instantiated (except as part of a derived class).
+ DNSLookup() : self_(this) {}
+public:
+ /// \brief The destructor
+ virtual ~DNSLookup() {}
+ //@}
+ /// \brief The function operator
+ ///
+ /// This makes its call indirectly via the "self" pointer, ensuring
+ /// that the function ultimately invoked will be the one in the derived
+ /// class.
+ ///
+ /// \param io_message The event message to handle
+ /// \param message The DNS MessagePtr that needs handling
+ /// \param answer_message The DNS MessagePtr TODO
+ /// \param buffer The final answer is put here
+ /// \param server DNSServer object to use
+ virtual void operator()(const IOMessage& io_message,
+ isc::dns::MessagePtr message,
+ isc::dns::MessagePtr answer_message,
+ isc::dns::OutputBufferPtr buffer,
+ DNSServer* server) const
+ {
+ (*self_)(io_message, message, answer_message, buffer, server);
+ }
+private:
+ DNSLookup* self_;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_DNS_LOOKUP_H
diff --git a/src/lib/asiolink/dns_server.h b/src/lib/asiolink/dns_server.h
new file mode 100644
index 0000000..f15f808
--- /dev/null
+++ b/src/lib/asiolink/dns_server.h
@@ -0,0 +1,155 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_DNS_SERVER_H
+#define __ASIOLINK_DNS_SERVER_H 1
+
+#include <asiolink/io_message.h>
+
+namespace asiolink {
+
+/// \brief The \c DNSServer class is a wrapper (and base class) for
+/// classes which provide DNS server functionality.
+///
+/// The classes derived from this one, \c TCPServer and \c UDPServer,
+/// act as the interface layer between clients sending queries, and
+/// functions defined elsewhere that provide answers to those queries.
+/// Those functions are described in more detail below under
+/// \c SimpleCallback, \c DNSLookup, and \c DNSAnswer.
+///
+/// Notes to developers:
+/// When constructed, this class (and its derived classes) will have its
+/// "self_" member set to point to "this". Objects of this class (as
+/// instantiated through a base class) are sometimes passed by
+/// reference (as this superclass); calls to methods in the base
+/// class are then rerouted via this pointer to methods in the derived
+/// class. This allows code from outside asiolink, with no specific
+/// knowledge of \c TCPServer or \c UDPServer, to access their methods.
+///
+/// This class is both assignable and copy-constructable. Its subclasses
+/// use the "stackless coroutine" pattern, meaning that it will copy itself
+/// when "forking", and that instances will be posted as ASIO handler
+/// objects, which are always copied.
+///
+/// Because these objects are frequently copied, it is recommended
+/// that derived classes be kept small to reduce copy overhead.
+class DNSServer {
+protected:
+ ///
+ /// \name Constructors and destructors
+ ///
+ /// This is intentionally defined as \c protected, as this base class
+ /// should never be instantiated except as part of a derived class.
+ //@{
+ DNSServer() : self_(this) {}
+public:
+ /// \brief The destructor
+ virtual ~DNSServer() {}
+ //@}
+
+ ///
+ /// \name Class methods
+ ///
+ /// These methods all make their calls indirectly via the "self_"
+ /// pointer, ensuring that the functions ultimately invoked will be
+ /// the ones in the derived class. This makes it possible to pass
+ /// instances of derived classes as references to this base class
+ /// without losing access to derived class data.
+ ///
+ //@{
+ /// \brief The funtion operator
+ virtual void operator()(asio::error_code ec = asio::error_code(),
+ size_t length = 0)
+ {
+ (*self_)(ec, length);
+ }
+
+ /// \brief Stop current running server
+ virtual void stop() { self_->stop();}
+
+ /// \brief Resume processing of the server coroutine after an
+ /// asynchronous call (e.g., to the DNS Lookup provider) has completed.
+ ///
+ /// \param done If true, this signals the system there is an answer
+ /// to return.
+ virtual void resume(const bool done) { self_->resume(done); }
+
+ /// \brief Indicate whether the server is able to send an answer
+ /// to a query.
+ ///
+ /// This is presently used only for testing purposes.
+ virtual bool hasAnswer() { return (self_->hasAnswer()); }
+
+ /// \brief Returns the current value of the 'coroutine' object
+ ///
+ /// This is a temporary method, intended to be used for debugging
+ /// purposes during development and removed later. It allows
+ /// callers from outside the coroutine object to retrieve information
+ /// about its current state.
+ ///
+ /// \return The value of the 'coroutine' object
+ virtual int value() { return (self_->value()); }
+
+ /// \brief Returns a pointer to a clone of this DNSServer object.
+ ///
+ /// When a \c DNSServer object is copied or assigned, the result will
+ /// normally be another \c DNSServer object containing a copy
+ /// of the original "self_" pointer. Calling clone() guarantees
+ /// that the underlying object is also correctly copied.
+ ///
+ /// \return A deep copy of this DNSServer object
+ virtual DNSServer* clone() { return (self_->clone()); }
+ //@}
+
+protected:
+ /// \brief Lookup handler object.
+ ///
+ /// This is a protected class; it can only be instantiated
+ /// from within a derived class of \c DNSServer.
+ ///
+ /// A server object that has received a query creates an instance
+ /// of this class and scheudles it on the ASIO service queue
+ /// using asio::io_service::post(). When the handler executes, it
+ /// calls the asyncLookup() method in the server object to start a
+ /// DNS lookup. When the lookup is complete, the server object is
+ /// scheduled to resume, again using io_service::post().
+ ///
+ /// Note that the calling object is copied into the handler object,
+ /// not referenced. This is because, once the calling object yields
+ /// control to the handler, it falls out of scope and may disappear
+ template <typename T>
+ class AsyncLookup {
+ public:
+ AsyncLookup(T& caller) : caller_(caller) {}
+ void operator()() { caller_.asyncLookup(); }
+ private:
+ T caller_;
+ };
+
+ /// \brief Carries out a DNS lookup.
+ ///
+ /// This function calls the \c DNSLookup object specified by the
+ /// DNS server when the \c IOService was created, passing along
+ /// the details of the query and a pointer back to the current
+ /// server object. It is called asynchronously via the AsyncLookup
+ /// handler class.
+ virtual void asyncLookup() { self_->asyncLookup(); }
+
+private:
+ DNSServer* self_;
+};
+
+
+} // asiolink
+#endif // __ASIOLINK_DNS_SERVER_H
diff --git a/src/lib/asiolink/dns_service.cc b/src/lib/asiolink/dns_service.cc
new file mode 100644
index 0000000..f17bb44
--- /dev/null
+++ b/src/lib/asiolink/dns_service.cc
@@ -0,0 +1,200 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+
+#include <boost/lexical_cast.hpp>
+
+#include <config.h>
+
+#include <log/dummylog.h>
+
+#include <asio.hpp>
+#include <asiolink/dns_service.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_server.h>
+#include <asiolink/udp_server.h>
+
+#include <log/dummylog.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/foreach.hpp>
+
+using isc::log::dlog;
+
+namespace asiolink {
+
+class SimpleCallback;
+class DNSLookup;
+class DNSAnswer;
+
+namespace {
+
+asio::ip::address
+convertAddr(const std::string& address) {
+ asio::error_code err;
+ asio::ip::address addr = asio::ip::address::from_string(address, err);
+ if (err) {
+ isc_throw(IOError, "Invalid IP address '" << &address << "': "
+ << err.message());
+ }
+ return (addr);
+}
+
+}
+
+
+class DNSServiceImpl {
+public:
+ DNSServiceImpl(IOService& io_service, const char& port,
+ const asio::ip::address* v4addr,
+ const asio::ip::address* v6addr,
+ SimpleCallback* checkin, DNSLookup* lookup,
+ DNSAnswer* answer);
+
+ IOService& io_service_;
+
+ typedef boost::shared_ptr<UDPServer> UDPServerPtr;
+ typedef boost::shared_ptr<TCPServer> TCPServerPtr;
+ typedef boost::shared_ptr<DNSServer> DNSServerPtr;
+ std::vector<DNSServerPtr> servers_;
+ SimpleCallback *checkin_;
+ DNSLookup *lookup_;
+ DNSAnswer *answer_;
+
+ void addServer(uint16_t port, const asio::ip::address& address) {
+ try {
+ dlog(std::string("Initialize TCP server at ") + address.to_string() + ":" + boost::lexical_cast<std::string>(port));
+ TCPServerPtr tcpServer(new TCPServer(io_service_.get_io_service(),
+ address, port, checkin_, lookup_, answer_));
+ (*tcpServer)();
+ servers_.push_back(tcpServer);
+ dlog(std::string("Initialize UDP server at ") + address.to_string() + ":" + boost::lexical_cast<std::string>(port));
+ UDPServerPtr udpServer(new UDPServer(io_service_.get_io_service(),
+ address, port, checkin_, lookup_, answer_));
+ (*udpServer)();
+ servers_.push_back(udpServer);
+ }
+ catch (const asio::system_error& err) {
+ // We need to catch and convert any ASIO level exceptions.
+ // This can happen for unavailable address, binding a privilege port
+ // without the privilege, etc.
+ isc_throw(IOError, "Failed to initialize network servers: " <<
+ err.what());
+ }
+ }
+ void addServer(const char& port, const asio::ip::address& address) {
+ uint16_t portnum;
+ try {
+ // XXX: SunStudio with stlport4 doesn't reject some invalid
+ // representation such as "-1" by lexical_cast<uint16_t>, so
+ // we convert it into a signed integer of a larger size and perform
+ // range check ourselves.
+ const int32_t portnum32 = boost::lexical_cast<int32_t>(&port);
+ if (portnum32 < 0 || portnum32 > 65535) {
+ isc_throw(IOError, "Invalid port number '" << &port);
+ }
+ portnum = portnum32;
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(IOError, "Invalid port number '" << &port << "': " <<
+ ex.what());
+ }
+ addServer(portnum, address);
+ }
+};
+
+DNSServiceImpl::DNSServiceImpl(IOService& io_service,
+ const char& port,
+ const asio::ip::address* const v4addr,
+ const asio::ip::address* const v6addr,
+ SimpleCallback* checkin,
+ DNSLookup* lookup,
+ DNSAnswer* answer) :
+ io_service_(io_service),
+ checkin_(checkin),
+ lookup_(lookup),
+ answer_(answer)
+{
+
+ if (v4addr) {
+ addServer(port, *v4addr);
+ }
+ if (v6addr) {
+ addServer(port, *v6addr);
+ }
+}
+
+DNSService::DNSService(IOService& io_service,
+ const char& port, const char& address,
+ SimpleCallback* checkin,
+ DNSLookup* lookup,
+ DNSAnswer* answer) :
+ impl_(new DNSServiceImpl(io_service, port, NULL, NULL, checkin, lookup,
+ answer)), io_service_(io_service)
+{
+ addServer(port, &address);
+}
+
+DNSService::DNSService(IOService& io_service,
+ const char& port,
+ const bool use_ipv4, const bool use_ipv6,
+ SimpleCallback* checkin,
+ DNSLookup* lookup,
+ DNSAnswer* answer) :
+ impl_(NULL), io_service_(io_service)
+{
+ const asio::ip::address v4addr_any =
+ asio::ip::address(asio::ip::address_v4::any());
+ const asio::ip::address* const v4addrp = use_ipv4 ? &v4addr_any : NULL;
+ const asio::ip::address v6addr_any =
+ asio::ip::address(asio::ip::address_v6::any());
+ const asio::ip::address* const v6addrp = use_ipv6 ? &v6addr_any : NULL;
+ impl_ = new DNSServiceImpl(io_service, port, v4addrp, v6addrp, checkin, lookup, answer);
+}
+
+DNSService::DNSService(IOService& io_service, SimpleCallback* checkin,
+ DNSLookup* lookup, DNSAnswer *answer) :
+ impl_(new DNSServiceImpl(io_service, *"0", NULL, NULL, checkin, lookup,
+ answer)), io_service_(io_service)
+{
+}
+
+DNSService::~DNSService() {
+ delete impl_;
+}
+
+void
+DNSService::addServer(const char& port, const std::string& address) {
+ impl_->addServer(port, convertAddr(address));
+}
+
+void
+DNSService::addServer(uint16_t port, const std::string& address) {
+ impl_->addServer(port, convertAddr(address));
+}
+
+void
+DNSService::clearServers() {
+ BOOST_FOREACH(const DNSServiceImpl::DNSServerPtr& s, impl_->servers_) {
+ s->stop();
+ }
+ impl_->servers_.clear();
+}
+
+
+
+} // namespace asiolink
diff --git a/src/lib/asiolink/dns_service.h b/src/lib/asiolink/dns_service.h
new file mode 100644
index 0000000..9a3fb4c
--- /dev/null
+++ b/src/lib/asiolink/dns_service.h
@@ -0,0 +1,112 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_DNS_SERVICE_H
+#define __ASIOLINK_DNS_SERVICE_H 1
+
+#include <resolve/resolver_interface.h>
+
+#include <asiolink/io_service.h>
+
+namespace asiolink {
+
+class SimpleCallback;
+class DNSLookup;
+class DNSAnswer;
+class DNSServiceImpl;
+
+/// \brief Handle DNS Queries
+///
+/// DNSService is the service that handles DNS queries and answers with
+/// a given IOService. This class is mainly intended to hold all the
+/// logic that is shared between the authoritative and the recursive
+/// server implementations. As such, it handles asio, including config
+/// updates (through the 'Checkinprovider'), and listening sockets.
+class DNSService {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ DNSService(const DNSService& source);
+ DNSService& operator=(const DNSService& source);
+
+public:
+ /// \brief The constructor with a specific IP address and port on which
+ /// the services listen on.
+ ///
+ /// \param io_service The IOService to work with
+ /// \param port the port to listen on
+ /// \param address the IP address to listen on
+ /// \param checkin Provider for cc-channel events (see \c SimpleCallback)
+ /// \param lookup The lookup provider (see \c DNSLookup)
+ /// \param answer The answer provider (see \c DNSAnswer)
+ DNSService(IOService& io_service, const char& port,
+ const char& address, SimpleCallback* checkin,
+ DNSLookup* lookup, DNSAnswer* answer);
+ /// \brief The constructor with a specific port on which the services
+ /// listen on.
+ ///
+ /// It effectively listens on "any" IPv4 and/or IPv6 addresses.
+ /// IPv4/IPv6 services will be available if and only if \c use_ipv4
+ /// or \c use_ipv6 is \c true, respectively.
+ ///
+ /// \param io_service The IOService to work with
+ /// \param port the port to listen on
+ /// \param use_ipv4 If true, listen on ipv4 'any'
+ /// \param use_ipv6 If true, listen on ipv6 'any'
+ /// \param checkin Provider for cc-channel events (see \c SimpleCallback)
+ /// \param lookup The lookup provider (see \c DNSLookup)
+ /// \param answer The answer provider (see \c DNSAnswer)
+ DNSService(IOService& io_service, const char& port,
+ const bool use_ipv4, const bool use_ipv6,
+ SimpleCallback* checkin, DNSLookup* lookup,
+ DNSAnswer* answer);
+ /// \brief The constructor without any servers.
+ ///
+ /// Use addServer() to add some servers.
+ DNSService(IOService& io_service, SimpleCallback* checkin,
+ DNSLookup* lookup, DNSAnswer* answer);
+ /// \brief The destructor.
+ ~DNSService();
+ //@}
+
+ /// \brief Add another server to the service
+ void addServer(uint16_t port, const std::string &address);
+ void addServer(const char &port, const std::string &address);
+ /// \brief Remove all servers from the service
+ void clearServers();
+
+ /// \brief Return the native \c io_service object used in this wrapper.
+ ///
+ /// This is a short term work around to support other BIND 10 modules
+ /// that share the same \c io_service with the authoritative server.
+ /// It will eventually be removed once the wrapper interface is
+ /// generalized.
+ asio::io_service& get_io_service() { return io_service_.get_io_service(); }
+
+ /// \brief Return the IO Service Object
+ ///
+ /// \return IOService object for this DNS service.
+ asiolink::IOService& getIOService() { return (io_service_);}
+
+private:
+ DNSServiceImpl* impl_;
+ IOService& io_service_;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_DNS_SERVICE_H
diff --git a/src/lib/asiolink/dummy_io_cb.h b/src/lib/asiolink/dummy_io_cb.h
new file mode 100644
index 0000000..0006b95
--- /dev/null
+++ b/src/lib/asiolink/dummy_io_cb.h
@@ -0,0 +1,59 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __DUMMY_IO_CB_H
+#define __DUMMY_IO_CB_H
+
+#include <iostream>
+
+#include <asio/error.hpp>
+#include <asio/error_code.hpp>
+
+namespace asiolink {
+
+/// \brief Asynchronous I/O Completion Callback
+///
+/// The two socket classes (UDPSocket and TCPSocket) require that the I/O
+/// completion callback function have an operator() method with the appropriate
+/// signature. The classes are templates, any class with that method and
+/// signature can be passed as the callback object - there is no need for a
+/// base class defining the interface. However, some users of the socket
+/// classes do not use the asynchronous I/O operations, yet have to supply a
+/// template parameter. This is the reason for this class - it is the dummy
+/// template parameter.
+
+class DummyIOCallback {
+public:
+
+ /// \brief Asynchronous I/O callback method
+ ///
+ /// \param error Unused
+ void operator()(asio::error_code)
+ {
+ // TODO: log an error if this method ever gets called.
+ }
+
+ /// \brief Asynchronous I/O callback method
+ ///
+ /// \param error Unused
+ /// \param length Unused
+ void operator()(asio::error_code, size_t)
+ {
+ // TODO: log an error if this method ever gets called.
+ }
+};
+
+} // namespace asiolink
+
+#endif // __DUMMY_IO_CB_H
diff --git a/src/lib/asiolink/internal/Makefile.am b/src/lib/asiolink/internal/Makefile.am
deleted file mode 100644
index 3c6155b..0000000
--- a/src/lib/asiolink/internal/Makefile.am
+++ /dev/null
@@ -1 +0,0 @@
-SUBDIRS = tests
diff --git a/src/lib/asiolink/internal/coroutine.h b/src/lib/asiolink/internal/coroutine.h
deleted file mode 100644
index 985888b..0000000
--- a/src/lib/asiolink/internal/coroutine.h
+++ /dev/null
@@ -1,133 +0,0 @@
-//
-// coroutine.h
-// ~~~~~~~~~~~
-//
-// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-
-#ifndef COROUTINE_HPP
-#define COROUTINE_HPP
-
-
-// \brief Coroutine object
-//
-// A coroutine object maintains the state of a re-enterable routine. It
-// is assignable and copy-constructable, and can be used as a base class
-// for a class that uses it, or as a data member. The copy overhead is
-// a single int.
-//
-// A reenterable function contains a CORO_REENTER (coroutine) { ... }
-// block. Whenever an asychrnonous operation is initiated within the
-// routine, the function is provided as the handler object. (The simplest
-// way to do this is to have the reenterable function be the operator()
-// member for the coroutine object itself.) For example:
-//
-// CORO_YIELD socket->async_read_some(buffer, *this);
-//
-// The CORO_YIELD keyword updates the current status of the coroutine to
-// indicate the line number currently being executed. The
-// async_read_some() call is initiated, with a copy of the updated
-// corotutine as its handler object, and the current coroutine exits. When
-// the async_read_some() call finishes, the copied coroutine will be
-// called, and will resume processing exactly where the original one left
-// off--right after asynchronous call. This allows asynchronous I/O
-// routines to be written with a logical flow, step following step, rather
-// than as a linked chain of separate handler functions.
-//
-// When necessary, a coroutine can fork itself using the CORO_FORK keyword.
-// This updates the status of the coroutine and makes a copy. The copy can
-// then be called directly or posted to the ASIO service queue so that both
-// coroutines will continue forward, one "parent" and one "child". The
-// is_parent() and is_child() methods indicate which is which.
-//
-// The CORO_REENTER, CORO_YIELD and CORO_FORK keywords are implemented
-// via preprocessor macros. The CORO_REENTER block is actually a large,
-// complex switch statement. Because of this, inline variable declaration
-// is impossible within CORO_REENTER unless it is done in a subsidiary
-// scope--and if it is, that scope cannot contain CORO_YIELD or CORO_FORK
-// keywords.
-//
-// Because coroutines are frequently copied, it is best to minimize copy
-// overhead by limiting the size of data members in derived classes.
-//
-// It should be noted that when a coroutine falls out of scope its memory
-// is reclaimed, even though it may be scheduled to resume when an
-// asynchronous operation completes. Any shared_ptr<> objects declared in
-// the coroutine may be destroyed if their reference count drops to zero,
-// in which case the coroutine will have serious problems once it resumes.
-// One solution so this is to have the space that will be used by a
-// coroutine pre-allocated and stored on a free list; a new coroutine can
-// fetch the block of space off a free list, place a shared pointer to it
-// on an "in use" list, and carry on. The reference in the "in use" list
-// would prevent the data from being destroyed.
-class coroutine
-{
-public:
- coroutine() : value_(0) {}
- virtual ~coroutine() {}
- bool is_child() const { return value_ < 0; }
- bool is_parent() const { return !is_child(); }
- bool is_complete() const { return value_ == -1; }
- int get_value() const { return value_; }
-private:
- friend class coroutine_ref;
- int value_;
-};
-
-class coroutine_ref
-{
-public:
- coroutine_ref(coroutine& c) : value_(c.value_), modified_(false) {}
- coroutine_ref(coroutine* c) : value_(c->value_), modified_(false) {}
- ~coroutine_ref() { if (!modified_) value_ = -1; }
- operator int() const { return value_; }
- int& operator=(int v) { modified_ = true; return value_ = v; }
-private:
- void operator=(const coroutine_ref&);
- int& value_;
- bool modified_;
-};
-
-#define CORO_REENTER(c) \
- switch (coroutine_ref _coro_value = c) \
- case -1: if (_coro_value) \
- { \
- goto terminate_coroutine; \
- terminate_coroutine: \
- _coro_value = -1; \
- goto bail_out_of_coroutine; \
- bail_out_of_coroutine: \
- break; \
- } \
- else case 0:
-
-#define CORO_YIELD \
- for (_coro_value = __LINE__;;) \
- if (_coro_value == 0) \
- { \
- case __LINE__: ; \
- break; \
- } \
- else \
- switch (_coro_value ? 0 : 1) \
- for (;;) \
- case -1: if (_coro_value) \
- goto terminate_coroutine; \
- else for (;;) \
- case 1: if (_coro_value) \
- goto bail_out_of_coroutine; \
- else case 0:
-
-#define CORO_FORK \
- for (_coro_value = -__LINE__;; _coro_value = __LINE__) \
- if (_coro_value == __LINE__) \
- { \
- case -__LINE__: ; \
- break; \
- } \
- else
-#endif // COROUTINE_HPP
-
diff --git a/src/lib/asiolink/internal/tcpdns.h b/src/lib/asiolink/internal/tcpdns.h
deleted file mode 100644
index a97ed17..0000000
--- a/src/lib/asiolink/internal/tcpdns.h
+++ /dev/null
@@ -1,224 +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 __TCPDNS_H
-#define __TCPDNS_H 1
-
-#include <config.h>
-
-
-#include <asio.hpp>
-#include <boost/shared_array.hpp>
-#include <boost/shared_ptr.hpp>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-
-#include <asiolink/asiolink.h>
-#include <asiolink/internal/coroutine.h>
-
-// This file contains TCP-specific implementations of generic classes
-// defined in asiolink.h. It is *not* intended to be part of the public
-// API.
-
-namespace asiolink {
-/// \brief The \c TCPEndpoint class is a concrete derived class of
-/// \c IOEndpoint that represents an endpoint of a TCP connection.
-///
-/// In the current implementation, an object of this class is always
-/// instantiated within the wrapper routines. Applications are expected to
-/// get access to the object via the abstract base class, \c IOEndpoint.
-/// This design may be changed when we generalize the wrapper interface.
-///
-/// Note: this implementation is optimized for the case where this object
-/// is created from an ASIO endpoint object in a receiving code path
-/// by avoiding to make a copy of the base endpoint. For TCP it may not be
-/// a big deal, but when we receive UDP packets at a high rate, the copy
-/// overhead might be significant.
-class TCPEndpoint : public IOEndpoint {
-public:
- ///
- /// \name Constructors and Destructor
- ///
- //@{
- /// \brief Constructor from a pair of address and port.
- ///
- /// \param address The IP address of the endpoint.
- /// \param port The TCP port number of the endpoint.
- TCPEndpoint(const IOAddress& address, const unsigned short port) :
- asio_endpoint_placeholder_(
- new asio::ip::tcp::endpoint(
- asio::ip::address::from_string(address.toText()), port)),
- asio_endpoint_(*asio_endpoint_placeholder_)
- {}
-
- /// \brief Constructor from an ASIO TCP endpoint.
- ///
- /// This constructor is designed to be an efficient wrapper for the
- /// corresponding ASIO class, \c tcp::endpoint.
- ///
- /// \param asio_endpoint The ASIO representation of the TCP endpoint.
- TCPEndpoint(const asio::ip::tcp::endpoint& asio_endpoint) :
- asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
- {}
-
- /// \brief The destructor.
- ~TCPEndpoint() { delete asio_endpoint_placeholder_; }
- //@}
-
- IOAddress getAddress() const {
- return (asio_endpoint_.address());
- }
-
- uint16_t getPort() const {
- return (asio_endpoint_.port());
- }
-
- short getProtocol() const {
- return (asio_endpoint_.protocol().protocol());
- }
-
- short getFamily() const {
- return (asio_endpoint_.protocol().family());
- }
-
- // This is not part of the exosed IOEndpoint API but allows
- // direct access to the ASIO implementation of the endpoint
- const asio::ip::tcp::endpoint& getASIOEndpoint() const {
- return (asio_endpoint_);
- }
-
-private:
- const asio::ip::tcp::endpoint* asio_endpoint_placeholder_;
- const asio::ip::tcp::endpoint& asio_endpoint_;
-};
-
-/// \brief The \c TCPSocket class is a concrete derived class of
-/// \c IOSocket that represents a TCP socket.
-///
-/// In the current implementation, an object of this class is always
-/// instantiated within the wrapper routines. Applications are expected to
-/// get access to the object via the abstract base class, \c IOSocket.
-/// This design may be changed when we generalize the wrapper interface.
-class TCPSocket : public IOSocket {
-private:
- TCPSocket(const TCPSocket& source);
- TCPSocket& operator=(const TCPSocket& source);
-public:
- /// \brief Constructor from an ASIO TCP socket.
- ///
- /// \param socket The ASIO representation of the TCP socket.
- TCPSocket(asio::ip::tcp::socket& socket) : socket_(socket) {}
-
- int getNative() const { return (socket_.native()); }
- int getProtocol() const { return (IPPROTO_TCP); }
-
-private:
- asio::ip::tcp::socket& socket_;
-};
-
-/// \brief A TCP-specific \c DNSServer object.
-///
-/// This class inherits from both \c DNSServer and from \c coroutine,
-/// defined in coroutine.h.
-class TCPServer : public virtual DNSServer, public virtual coroutine {
-public:
- explicit TCPServer(asio::io_service& io_service,
- const asio::ip::address& addr, const uint16_t port,
- const SimpleCallback* checkin = NULL,
- const DNSLookup* lookup = NULL,
- const DNSAnswer* answer = NULL);
-
- void operator()(asio::error_code ec = asio::error_code(),
- size_t length = 0);
- void asyncLookup();
- void resume(const bool done);
- bool hasAnswer() { return (done_); }
- int value() { return (get_value()); }
-
- DNSServer* clone() {
- TCPServer* s = new TCPServer(*this);
- return (s);
- }
-
-private:
- enum { MAX_LENGTH = 65535 };
- static const size_t TCP_MESSAGE_LENGTHSIZE = 2;
-
- // The ASIO service object
- asio::io_service& io_;
-
- // Class member variables which are dynamic, and changes to which
- // need to accessible from both sides of a coroutine fork or from
- // outside of the coroutine (i.e., from an asynchronous I/O call),
- // should be declared here as pointers and allocated in the
- // constructor or in the coroutine. This allows state information
- // to persist when an individual copy of the coroutine falls out
- // scope while waiting for an event, *so long as* there is another
- // object that is referencing the same data. As a side-benefit, using
- // pointers also reduces copy overhead for coroutine objects.
- //
- // Note: Currently these objects are allocated by "new" in the
- // constructor, or in the function operator while processing a query.
- // Repeated allocations from the heap for every incoming query is
- // clearly a performance issue; this must be optimized in the future.
- // The plan is to have a structure pre-allocate several "server state"
- // objects which can be pulled off a free list and placed on an in-use
- // list whenever a query comes in. This will serve the dual purpose
- // of improving performance and guaranteeing that state information
- // will *not* be destroyed when any one instance of the coroutine
- // falls out of scope while waiting for an event.
- //
- // An ASIO acceptor object to handle new connections. Created in
- // the constructor.
- boost::shared_ptr<asio::ip::tcp::acceptor> acceptor_;
-
- // Socket used to for listen for queries. Created in the
- // constructor and stored in a shared_ptr because socket objects
- // are not copyable.
- boost::shared_ptr<asio::ip::tcp::socket> socket_;
-
- // The buffer into which the response is written
- boost::shared_ptr<isc::dns::OutputBuffer> respbuf_;
-
- // \c IOMessage and \c Message objects to be passed to the
- // DNS lookup and answer providers
- boost::shared_ptr<asiolink::IOMessage> io_message_;
- isc::dns::MessagePtr query_message_;
- isc::dns::MessagePtr answer_message_;
-
- // The buffer into which the query packet is written
- boost::shared_array<char>data_;
-
- // State information that is entirely internal to a given instance
- // of the coroutine can be declared here.
- size_t bytes_;
- bool done_;
-
- // Callback functions provided by the caller
- const SimpleCallback* checkin_callback_;
- const DNSLookup* lookup_callback_;
- const DNSAnswer* answer_callback_;
-
- boost::shared_ptr<IOEndpoint> peer_;
- boost::shared_ptr<IOSocket> iosock_;
-};
-
-}
-
-#endif // __TCPDNS_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/internal/tests/Makefile.am b/src/lib/asiolink/internal/tests/Makefile.am
deleted file mode 100644
index 449cab7..0000000
--- a/src/lib/asiolink/internal/tests/Makefile.am
+++ /dev/null
@@ -1,37 +0,0 @@
-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
-endif
-
-CLEANFILES = *.gcno *.gcda
-
-TESTS =
-if HAVE_GTEST
-TESTS += run_unittests
-run_unittests_SOURCES = udpdns_unittest.cc
-run_unittests_SOURCES += run_unittests.cc
-run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
-run_unittests_LDADD = $(GTEST_LDADD)
-run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
-run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
-run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
-run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
-# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
-# B10_CXXFLAGS)
-run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
-if USE_GXX
-run_unittests_CXXFLAGS += -Wno-unused-parameter
-endif
-if USE_CLANGPP
-# We need to disable -Werror for any test that uses internal definitions of
-# ASIO when using clang++
-run_unittests_CXXFLAGS += -Wno-error
-endif
-endif
-
-noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/asiolink/internal/tests/run_unittests.cc b/src/lib/asiolink/internal/tests/run_unittests.cc
deleted file mode 100644
index 5f1195d..0000000
--- a/src/lib/asiolink/internal/tests/run_unittests.cc
+++ /dev/null
@@ -1,21 +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 <gtest/gtest.h>
-
-int
-main(int argc, char* argv[]) {
- ::testing::InitGoogleTest(&argc, argv);
- return (RUN_ALL_TESTS());
-}
diff --git a/src/lib/asiolink/internal/tests/udpdns_unittest.cc b/src/lib/asiolink/internal/tests/udpdns_unittest.cc
deleted file mode 100644
index 099071c..0000000
--- a/src/lib/asiolink/internal/tests/udpdns_unittest.cc
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (C) 2010 CZ.NIC
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <asio.hpp>
-#include <boost/bind.hpp>
-#include <cstdlib>
-
-#include <dns/question.h>
-
-#include <asiolink/internal/udpdns.h>
-
-using namespace asio;
-using namespace isc::dns;
-using asio::ip::udp;
-
-namespace {
-
-const asio::ip::address TEST_HOST(asio::ip::address::from_string("127.0.0.1"));
-const uint16_t TEST_PORT(5301);
-// FIXME Shouldn't we send something that is real message?
-const char TEST_DATA[] = "TEST DATA";
-
-// Test fixture for the asiolink::UDPQuery.
-class UDPQueryTest : public ::testing::Test,
- public asiolink::UDPQuery::Callback
-{
- public:
- // Expected result of the callback
- asiolink::UDPQuery::Result expected_;
- // Did the callback run already?
- bool run_;
- // We use an io_service to run the query
- io_service service_;
- // Something to ask
- Question question_;
- // Buffer where the UDPQuery will store response
- OutputBufferPtr buffer_;
- // The query we are testing
- asiolink::UDPQuery query_;
-
- UDPQueryTest() :
- run_(false),
- question_(Name("example.net"), RRClass::IN(), RRType::A()),
- buffer_(new OutputBuffer(512)),
- query_(service_, question_, asiolink::IOAddress(TEST_HOST),
- TEST_PORT, buffer_, this, 100)
- { }
-
- // This is the callback's (), so it can be called.
- void operator()(asiolink::UDPQuery::Result result) {
- // We check the query returns the correct result
- EXPECT_EQ(expected_, result);
- // Check it is called only once
- EXPECT_FALSE(run_);
- // And mark the callback was called
- run_ = true;
- }
- // A response handler, pretending to be remote DNS server
- void respond(udp::endpoint* remote, udp::socket* socket) {
- // Some data came, just send something back.
- socket->send_to(asio::buffer(TEST_DATA, sizeof TEST_DATA),
- *remote);
- socket->close();
- }
-};
-
-/*
- * Test that when we run the query and stop it after it was run,
- * it returns "stopped" correctly.
- *
- * That is why stop() is posted to the service_ as well instead
- * of calling it.
- */
-TEST_F(UDPQueryTest, stop) {
- expected_ = asiolink::UDPQuery::STOPPED;
- // Post the query
- service_.post(query_);
- // Post query_.stop() (yes, the boost::bind thing is just
- // query_.stop()).
- service_.post(boost::bind(&asiolink::UDPQuery::stop, query_,
- asiolink::UDPQuery::STOPPED));
- // Run both of them
- service_.run();
- EXPECT_TRUE(run_);
-}
-
-/*
- * Test that when we queue the query to service_ and call stop()
- * before it gets executed, it acts sanely as well (eg. has the
- * same result as running stop() after - calls the callback).
- */
-TEST_F(UDPQueryTest, prematureStop) {
- expected_ = asiolink::UDPQuery::STOPPED;
- // Stop before it is started
- query_.stop();
- service_.post(query_);
- service_.run();
- EXPECT_TRUE(run_);
-}
-
-/*
- * Test that it will timeout when no answer will arrive.
- */
-TEST_F(UDPQueryTest, timeout) {
- expected_ = asiolink::UDPQuery::TIME_OUT;
- service_.post(query_);
- service_.run();
- EXPECT_TRUE(run_);
-}
-
-/*
- * Test that it will succeed when we fake an answer and
- * stores the same data we send.
- *
- * This is done through a real socket on loopback address.
- */
-TEST_F(UDPQueryTest, receive) {
- expected_ = asiolink::UDPQuery::SUCCESS;
- udp::socket socket(service_, udp::v4());
- socket.set_option(socket_base::reuse_address(true));
- socket.bind(udp::endpoint(TEST_HOST, TEST_PORT));
- char inbuff[512];
- udp::endpoint remote;
- socket.async_receive_from(asio::buffer(inbuff, 512), remote, boost::bind(
- &UDPQueryTest::respond, this, &remote, &socket));
- service_.post(query_);
- service_.run();
- EXPECT_TRUE(run_);
- ASSERT_EQ(sizeof TEST_DATA, buffer_->getLength());
- EXPECT_EQ(0, memcmp(TEST_DATA, buffer_->getData(), sizeof TEST_DATA));
-}
-
-}
diff --git a/src/lib/asiolink/internal/udpdns.h b/src/lib/asiolink/internal/udpdns.h
deleted file mode 100644
index 5b67ca9..0000000
--- a/src/lib/asiolink/internal/udpdns.h
+++ /dev/null
@@ -1,253 +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 __UDPDNS_H
-#define __UDPDNS_H 1
-
-#include <config.h>
-
-#include <asio.hpp>
-#include <boost/shared_array.hpp>
-#include <boost/shared_ptr.hpp>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-#include <dns/messagerenderer.h>
-
-#include <asiolink/asiolink.h>
-#include <asiolink/internal/coroutine.h>
-
-// This file contains UDP-specific implementations of generic classes
-// defined in asiolink.h. It is *not* intended to be part of the public
-// API.
-
-namespace asiolink {
-/// \brief The \c UDPEndpoint class is a concrete derived class of
-/// \c IOEndpoint that represents an endpoint of a UDP packet.
-///
-/// Other notes about \c TCPEndpoint applies to this class, too.
-class UDPEndpoint : public IOEndpoint {
-public:
- ///
- /// \name Constructors and Destructor.
- ///
- //@{
- /// \brief Constructor from a pair of address and port.
- ///
- /// \param address The IP address of the endpoint.
- /// \param port The UDP port number of the endpoint.
- UDPEndpoint(const IOAddress& address, const unsigned short port) :
- asio_endpoint_placeholder_(
- new asio::ip::udp::endpoint(asio::ip::address::from_string(address.toText()),
- port)),
- asio_endpoint_(*asio_endpoint_placeholder_)
- {}
-
- /// \brief Constructor from an ASIO UDP endpoint.
- ///
- /// This constructor is designed to be an efficient wrapper for the
- /// corresponding ASIO class, \c udp::endpoint.
- ///
- /// \param asio_endpoint The ASIO representation of the UDP endpoint.
- UDPEndpoint(const asio::ip::udp::endpoint& asio_endpoint) :
- asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
- {}
-
- /// \brief The destructor.
- ~UDPEndpoint() { delete asio_endpoint_placeholder_; }
- //@}
-
- inline IOAddress getAddress() const {
- return (asio_endpoint_.address());
- }
-
- inline uint16_t getPort() const {
- return (asio_endpoint_.port());
- }
-
- inline short getProtocol() const {
- return (asio_endpoint_.protocol().protocol());
- }
-
- inline short getFamily() const {
- return (asio_endpoint_.protocol().family());
- }
-
- // This is not part of the exosed IOEndpoint API but allows
- // direct access to the ASIO implementation of the endpoint
- inline const asio::ip::udp::endpoint& getASIOEndpoint() const {
- return (asio_endpoint_);
- }
-
-private:
- const asio::ip::udp::endpoint* asio_endpoint_placeholder_;
- const asio::ip::udp::endpoint& asio_endpoint_;
-};
-
-/// \brief The \c UDPSocket class is a concrete derived class of
-/// \c IOSocket that represents a UDP socket.
-///
-/// Other notes about \c TCPSocket applies to this class, too.
-class UDPSocket : public IOSocket {
-private:
- UDPSocket(const UDPSocket& source);
- UDPSocket& operator=(const UDPSocket& source);
-public:
- /// \brief Constructor from an ASIO UDP socket.
- ///
- /// \param socket The ASIO representation of the UDP socket.
- UDPSocket(asio::ip::udp::socket& socket) : socket_(socket) {}
-
- virtual int getNative() const { return (socket_.native()); }
- virtual int getProtocol() const { return (IPPROTO_UDP); }
-
-private:
- asio::ip::udp::socket& socket_;
-};
-
-//
-// Asynchronous UDP server coroutine
-//
-///
-/// \brief This class implements the coroutine to handle UDP
-/// DNS query event. As such, it is both a \c DNSServer and
-/// a \c coroutine
-///
-class UDPServer : public virtual DNSServer, public virtual coroutine {
-public:
- /// \brief Constructor
- /// \param io_service the asio::io_service to work with
- /// \param addr the IP address to listen for queries on
- /// \param port the port to listen for queries on
- /// \param checkin the callbackprovider for non-DNS events
- /// \param lookup the callbackprovider for DNS lookup events
- /// \param answer the callbackprovider for DNS answer events
- explicit UDPServer(asio::io_service& io_service,
- const asio::ip::address& addr, const uint16_t port,
- SimpleCallback* checkin = NULL,
- DNSLookup* lookup = NULL,
- DNSAnswer* answer = NULL);
-
- /// \brief The function operator
- void operator()(asio::error_code ec = asio::error_code(),
- size_t length = 0);
-
- /// \brief Calls the lookup callback
- void asyncLookup();
-
- /// \brief Resume operation
- ///
- /// \param done Set this to true if the lookup action is done and
- /// we have an answer
- void resume(const bool done);
-
- /// \brief Check if we have an answer
- ///
- /// \return true if we have an answer
- bool hasAnswer();
-
- /// \brief Returns the coroutine state value
- ///
- /// \return the coroutine state value
- int value() { return (get_value()); }
-
- /// \brief Clones the object
- ///
- /// \return a newly allocated copy of this object
- DNSServer* clone() {
- UDPServer* s = new UDPServer(*this);
- return (s);
- }
-
-private:
- enum { MAX_LENGTH = 4096 };
-
- /**
- * \brief Internal state and data.
- *
- * We use the pimple design pattern, but not because we need to hide
- * internal data. This class and whole header is for private use anyway.
- * It turned out that UDPServer is copied a lot, because it is a coroutine.
- * This way the overhead of copying is lower, we copy only one shared
- * pointer instead of about 10 of them.
- */
- class Data;
- boost::shared_ptr<Data> data_;
-};
-
-//
-// Asynchronous UDP coroutine for upstream queries
-//
-class UDPQuery : public coroutine {
-public:
- // TODO Maybe this should be more generic than just for UDPQuery?
- ///
- /// \brief Result of the query
- ///
- /// This is related only to contacting the remote server. If the answer
- ///indicates error, it is still counted as SUCCESS here, if it comes back.
- ///
- enum Result {
- SUCCESS,
- TIME_OUT,
- STOPPED
- };
- /// Abstract callback for the UDPQuery.
- class Callback {
- public:
- virtual ~Callback() {}
-
- /// This will be called when the UDPQuery is completed
- virtual void operator()(Result result) = 0;
- };
- ///
- /// \brief Constructor.
- ///
- /// It creates the query.
- /// @param callback will be called when we terminate. It is your task to
- /// delete it if allocated on heap.
- ///@param timeout in ms.
- ///
- explicit UDPQuery(asio::io_service& io_service,
- const isc::dns::Question& q,
- const IOAddress& addr, uint16_t port,
- isc::dns::OutputBufferPtr buffer,
- Callback* callback, int timeout = -1);
- void operator()(asio::error_code ec = asio::error_code(),
- size_t length = 0);
- /// Terminate the query.
- void stop(Result reason = STOPPED);
-private:
- enum { MAX_LENGTH = 4096 };
-
- ///
- /// \short Private data
- ///
- /// They are not private because of stability of the
- /// interface (this is private class anyway), but because this class
- /// will be copyed often (it is used as a coroutine and passed as callback
- /// to many async_*() functions) and we want keep the same data. Some of
- /// the data is not copyable too.
- ///
- struct PrivateData;
- boost::shared_ptr<PrivateData> data_;
-};
-}
-
-
-#endif // __UDPDNS_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/interval_timer.cc b/src/lib/asiolink/interval_timer.cc
new file mode 100644
index 0000000..8efb102
--- /dev/null
+++ b/src/lib/asiolink/interval_timer.cc
@@ -0,0 +1,136 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <unistd.h> // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <boost/bind.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <asio.hpp>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+
+namespace asiolink {
+
+class IntervalTimerImpl {
+private:
+ // prohibit copy
+ IntervalTimerImpl(const IntervalTimerImpl& source);
+ IntervalTimerImpl& operator=(const IntervalTimerImpl& source);
+public:
+ IntervalTimerImpl(IOService& io_service);
+ ~IntervalTimerImpl();
+ void setup(const IntervalTimer::Callback& cbfunc, const long interval);
+ void callback(const asio::error_code& error);
+ void cancel() {
+ timer_.cancel();
+ interval_ = 0;
+ }
+ long getInterval() const { return (interval_); }
+private:
+ // a function to update timer_ when it expires
+ void update();
+ // a function to call back when timer_ expires
+ IntervalTimer::Callback cbfunc_;
+ // interval in milliseconds
+ long interval_;
+ // asio timer
+ asio::deadline_timer timer_;
+};
+
+IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
+ interval_(0), timer_(io_service.get_io_service())
+{}
+
+IntervalTimerImpl::~IntervalTimerImpl()
+{}
+
+void
+IntervalTimerImpl::setup(const IntervalTimer::Callback& cbfunc,
+ const long interval)
+{
+ // Interval should not be less than or equal to 0.
+ if (interval <= 0) {
+ isc_throw(isc::BadValue, "Interval should not be less than or "
+ "equal to 0");
+ }
+ // Call back function should not be empty.
+ if (cbfunc.empty()) {
+ isc_throw(isc::InvalidParameter, "Callback function is empty");
+ }
+ cbfunc_ = cbfunc;
+ interval_ = interval;
+ // Set initial expire time.
+ // At this point the timer is not running yet and will not expire.
+ // After calling IOService::run(), the timer will expire.
+ update();
+ return;
+}
+
+void
+IntervalTimerImpl::update() {
+ if (interval_ == 0) {
+ // timer has been canceled. Do nothing.
+ return;
+ }
+ try {
+ // Update expire time to (current time + interval_).
+ timer_.expires_from_now(boost::posix_time::millisec(interval_));
+ } catch (const asio::system_error& e) {
+ isc_throw(isc::Unexpected, "Failed to update timer");
+ }
+ // Reset timer.
+ timer_.async_wait(boost::bind(&IntervalTimerImpl::callback, this, _1));
+}
+
+void
+IntervalTimerImpl::callback(const asio::error_code& cancelled) {
+ // Do not call cbfunc_ in case the timer was cancelled.
+ // The timer will be canelled in the destructor of asio::deadline_timer.
+ if (!cancelled) {
+ cbfunc_();
+ // Set next expire time.
+ update();
+ }
+}
+
+IntervalTimer::IntervalTimer(IOService& io_service) {
+ impl_ = new IntervalTimerImpl(io_service);
+}
+
+IntervalTimer::~IntervalTimer() {
+ delete impl_;
+}
+
+void
+IntervalTimer::setup(const Callback& cbfunc, const long interval) {
+ return (impl_->setup(cbfunc, interval));
+}
+
+void
+IntervalTimer::cancel() {
+ impl_->cancel();
+}
+
+long
+IntervalTimer::getInterval() const {
+ return (impl_->getInterval());
+}
+
+}
diff --git a/src/lib/asiolink/interval_timer.h b/src/lib/asiolink/interval_timer.h
new file mode 100644
index 0000000..6c43327
--- /dev/null
+++ b/src/lib/asiolink/interval_timer.h
@@ -0,0 +1,133 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_INTERVAL_TIMER_H
+#define __ASIOLINK_INTERVAL_TIMER_H 1
+
+#include <boost/function.hpp>
+
+#include <asiolink/io_service.h>
+
+namespace asiolink {
+
+struct IntervalTimerImpl;
+
+/// \brief The \c IntervalTimer class is a wrapper for the ASIO
+/// \c asio::deadline_timer class.
+///
+/// This class is implemented to use \c asio::deadline_timer as interval
+/// timer.
+///
+/// \c setup() sets a timer to expire on (now + interval) and a call back
+/// function.
+///
+/// \c IntervalTimerImpl::callback() is called by the timer when it expires.
+///
+/// The function calls the call back function set by \c setup() and updates
+/// the timer to expire in (now + interval) milliseconds.
+/// The type of call back function is \c void(void).
+///
+/// The call back function will not be called if the instance of this class is
+/// destroyed before the timer is expired.
+///
+/// Note: Destruction of an instance of this class while call back is pending
+/// causes throwing an exception from \c IOService.
+///
+/// Sample code:
+/// \code
+/// void function_to_call_back() {
+/// // this function will be called periodically
+/// }
+/// int interval_in_milliseconds = 1000;
+/// IOService io_service;
+///
+/// IntervalTimer intervalTimer(io_service);
+/// intervalTimer.setup(function_to_call_back, interval_in_milliseconds);
+/// io_service.run();
+/// \endcode
+class IntervalTimer {
+public:
+ /// \name The type of timer callback function
+ typedef boost::function<void()> Callback;
+
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IntervalTimer(const IntervalTimer& source);
+ IntervalTimer& operator=(const IntervalTimer& source);
+public:
+ /// \brief The constructor with \c IOService.
+ ///
+ /// This constructor may throw a standard exception if
+ /// memory allocation fails inside the method.
+ /// This constructor may also throw \c asio::system_error.
+ ///
+ /// \param io_service A reference to an instance of IOService
+ IntervalTimer(IOService& io_service);
+
+ /// \brief The destructor.
+ ///
+ /// This destructor never throws an exception.
+ ///
+ /// On the destruction of this class the timer will be canceled
+ /// inside \c asio::deadline_timer.
+ ~IntervalTimer();
+ //@}
+
+ /// \brief Register timer callback function and interval.
+ ///
+ /// This function sets callback function and interval in milliseconds.
+ /// Timer will actually start after calling \c IOService::run().
+ ///
+ /// \param cbfunc A reference to a function \c void(void) to call back
+ /// when the timer is expired (should not be an empty functor)
+ /// \param interval Interval in milliseconds (greater than 0)
+ ///
+ /// Note: IntervalTimer will not pass \c asio::error_code to
+ /// call back function. In case the timer is cancelled, the function
+ /// will not be called.
+ ///
+ /// \throw isc::InvalidParameter cbfunc is empty
+ /// \throw isc::BadValue interval is less than or equal to 0
+ /// \throw isc::Unexpected ASIO library error
+ void setup(const Callback& cbfunc, const long interval);
+
+ /// Cancel the timer.
+ ///
+ /// If the timer has been set up, this method cancels any asynchronous
+ /// events waiting on the timer and stops the timer itself.
+ /// If the timer has already been canceled, this method effectively does
+ /// nothing.
+ ///
+ /// This method never throws an exception.
+ void cancel();
+
+ /// Return the timer interval.
+ ///
+ /// This method returns the timer interval in milliseconds if it's running;
+ /// if the timer has been canceled it returns 0.
+ ///
+ /// This method never throws an exception.
+ long getInterval() const;
+
+private:
+ IntervalTimerImpl* impl_;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_INTERVAL_TIMER_H
diff --git a/src/lib/asiolink/io_address.cc b/src/lib/asiolink/io_address.cc
new file mode 100644
index 0000000..70e8374
--- /dev/null
+++ b/src/lib/asiolink/io_address.cc
@@ -0,0 +1,65 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <unistd.h> // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <asio.hpp>
+
+#include <exceptions/exceptions.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+
+
+using namespace asio;
+using asio::ip::udp;
+using asio::ip::tcp;
+
+using namespace std;
+
+namespace asiolink {
+
+// XXX: we cannot simply construct the address in the initialization list,
+// because we'd like to throw our own exception on failure.
+IOAddress::IOAddress(const string& address_str) {
+ error_code err;
+ asio_address_ = ip::address::from_string(address_str, err);
+ if (err) {
+ isc_throw(IOError, "Failed to convert string to address '"
+ << address_str << "': " << err.message());
+ }
+}
+
+IOAddress::IOAddress(const ip::address& asio_address) :
+ asio_address_(asio_address)
+{}
+
+string
+IOAddress::toText() const {
+ return (asio_address_.to_string());
+}
+
+short
+IOAddress::getFamily() const {
+ if (asio_address_.is_v4()) {
+ return (AF_INET);
+ } else {
+ return (AF_INET6);
+ }
+}
+
+}
diff --git a/src/lib/asiolink/io_address.h b/src/lib/asiolink/io_address.h
new file mode 100644
index 0000000..53c1a7a
--- /dev/null
+++ b/src/lib/asiolink/io_address.h
@@ -0,0 +1,123 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __IO_ADDRESS_H
+#define __IO_ADDRESS_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h> // for some network system calls
+#include <asio/ip/address.hpp>
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+namespace asiolink {
+
+/// \brief The \c IOAddress class represents an IP addresses (version
+/// agnostic)
+///
+/// This class is a wrapper for the ASIO \c ip::address class.
+class IOAddress {
+public:
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// This class is copyable. We use default versions of copy constructor
+ /// and the assignment operator.
+ /// We use the default destructor.
+ //@{
+ /// \brief Constructor from string.
+ ///
+ /// This constructor converts a textual representation of IPv4 and IPv6
+ /// addresses into an IOAddress object.
+ /// If \c address_str is not a valid representation of any type of
+ /// address, an exception of class \c IOError will be thrown.
+ /// This constructor allocates memory for the object, and if that fails
+ /// a corresponding standard exception will be thrown.
+ ///
+ /// \param address_str Textual representation of address.
+ IOAddress(const std::string& address_str);
+
+ /// \brief Constructor from an ASIO \c ip::address object.
+ ///
+ /// This constructor is intended to be used within the wrapper
+ /// implementation; user applications of the wrapper API won't use it.
+ ///
+ /// This constructor never throws an exception.
+ ///
+ /// \param asio_address The ASIO \c ip::address to be converted.
+ IOAddress(const asio::ip::address& asio_address);
+ //@}
+
+ /// \brief Convert the address to a string.
+ ///
+ /// This method is basically expected to be exception free, but
+ /// generating the string will involve resource allocation,
+ /// and if it fails the corresponding standard exception will be thrown.
+ ///
+ /// \return A string representation of the address.
+ std::string toText() const;
+
+ /// \brief Returns the address family
+ ///
+ /// \return AF_INET for IPv4 or AF_INET6 for IPv6.
+ short getFamily() const;
+
+ /// \brief Compare addresses for equality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return true if addresses are equal, false if not.
+ bool equals(const IOAddress& other) const {
+ return (asio_address_ == other.asio_address_);
+ }
+
+ /// \brief Compare addresses for equality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return true if addresses are equal, false if not.
+ bool operator==(const IOAddress& other) const {
+ return equals(other);
+ }
+
+ // \brief Compare addresses for inequality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return false if addresses are equal, true if not.
+ bool nequals(const IOAddress& other) const {
+ return (!equals(other));
+ }
+
+ // \brief Compare addresses for inequality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return false if addresses are equal, true if not.
+ bool operator!=(const IOAddress& other) const {
+ return (nequals(other));
+ }
+
+
+private:
+ asio::ip::address asio_address_;
+};
+
+} // asiolink
+#endif // __IO_ADDRESS_H
diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h
new file mode 100644
index 0000000..ac793a6
--- /dev/null
+++ b/src/lib/asiolink/io_asio_socket.h
@@ -0,0 +1,399 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __IO_ASIO_SOCKET_H
+#define __IO_ASIO_SOCKET_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h> // for some network system calls
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+#include <coroutine.h>
+
+#include <dns/buffer.h>
+
+#include <asiolink/io_error.h>
+#include <asiolink/io_socket.h>
+
+
+namespace asiolink {
+
+/// \brief Socket not open
+///
+/// Thrown on an attempt to do read/write to a socket that is not open.
+class SocketNotOpen : public IOError {
+public:
+ SocketNotOpen(const char* file, size_t line, const char* what) :
+ IOError(file, line, what) {}
+};
+
+/// \brief Error setting socket options
+///
+/// Thrown if attempt to change socket options fails.
+class SocketSetError : public IOError {
+public:
+ SocketSetError(const char* file, size_t line, const char* what) :
+ IOError(file, line, what) {}
+};
+
+/// \brief Buffer overflow
+///
+/// Thrown if an attempt is made to receive into an area beyond the end of
+/// the receive data buffer.
+class BufferOverflow : public IOError {
+public:
+ BufferOverflow(const char* file, size_t line, const char* what) :
+ IOError(file, line, what) {}
+};
+
+/// Forward declaration of an IOEndpoint
+class IOEndpoint;
+
+
+/// \brief I/O Socket with asynchronous operations
+///
+/// This class is a wrapper for the ASIO socket classes such as
+/// \c ip::tcp::socket and \c ip::udp::socket.
+///
+/// This is the basic IOSocket with additional operations - open, send, receive
+/// and close. Depending on how the asiolink code develops, it may be a
+/// temporary class: its main use is to add the template parameter needed for
+/// the derived classes UDPSocket and TCPSocket but without changing the
+/// signature of the more basic IOSocket class.
+///
+/// We may revisit this decision when we generalize the wrapper and more
+/// modules use it. Also, at that point we may define a separate (visible)
+/// derived class for testing purposes rather than providing factory methods
+/// (i.e., getDummy variants below).
+///
+/// TODO: Check if IOAsioSocket class is still needed
+///
+/// \param C Template parameter identifying type of the callback object.
+
+template <typename C>
+class IOAsioSocket : public IOSocket {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IOAsioSocket(const IOAsioSocket<C>& source);
+ IOAsioSocket& operator=(const IOAsioSocket<C>& source);
+protected:
+ /// \brief The default constructor.
+ ///
+ /// This is intentionally defined as \c protected as this base class
+ /// should never be instantiated (except as part of a derived class).
+ IOAsioSocket() {}
+public:
+ /// The destructor.
+ virtual ~IOAsioSocket() {}
+ //@}
+
+ /// \brief Return the "native" representation of the socket.
+ ///
+ /// In practice, this is the file descriptor of the socket for UNIX-like
+ /// systems so the current implementation simply uses \c int as the type of
+ /// the return value. We may have to need revisit this decision later.
+ ///
+ /// In general, the application should avoid using this method; it
+ /// essentially discloses an implementation specific "handle" that can
+ /// change the internal state of the socket (consider what would happen if
+ /// the application closes it, for example). But we sometimes need to
+ /// perform very low-level operations that requires the native
+ /// representation. Passing the file descriptor to a different process is
+ /// one example. This method is provided as a necessary evil for such
+ /// limited purposes.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return The native representation of the socket. This is the socket
+ /// file descriptor for UNIX-like systems.
+ virtual int getNative() const = 0;
+
+ /// \brief Return the transport protocol of the socket.
+ ///
+ /// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
+ /// \c IPPROTO_TCP for TCP sockets.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return \c IPPROTO_UDP for UDP sockets, \c IPPROTO_TCP for TCP sockets
+ virtual int getProtocol() const = 0;
+
+ /// \brief Is Open() synchronous?
+ ///
+ /// On a TCP socket, an "open" operation is a call to the socket's "open()"
+ /// method followed by a connection to the remote system: it is an
+ /// asynchronous operation. On a UDP socket, it is just a call to "open()"
+ /// and completes synchronously.
+ ///
+ /// For TCP, signalling of the completion of the operation is done by
+ /// by calling the callback function in the normal way. This could be done
+ /// for UDP (by posting en event on the event queue); however, that will
+ /// incur additional overhead in the most common case. So we give the
+ /// caller the choice for calling this open() method synchronously or
+ /// asynchronously.
+ ///
+ /// Owing to the way that the stackless coroutines are implemented, we need
+ /// to know _before_ executing the "open" function whether or not it is
+ /// asynchronous. So this method is called to provide that information.
+ ///
+ /// (The reason there is a need to know is because the call to open() passes
+ /// in the state of the coroutine at the time the call is made. On an
+ /// asynchronous I/O, we need to set the state to point to the statement
+ /// after the call to open() _before_ we pass the corouine to the open()
+ /// call. Unfortunately, the macros that set the state of the coroutine
+ /// also yield control - which we don't want to do if the open is
+ /// synchronous. Hence we need to know before we make the call to open()
+ /// whether that call will complete asynchronously.)
+ virtual bool isOpenSynchronous() const = 0;
+
+ /// \brief Open AsioSocket
+ ///
+ /// Opens the socket for asynchronous I/O. The open will complete
+ /// synchronously on UCP or asynchronously on TCP (in which case a callback
+ /// will be queued).
+ ///
+ /// \param endpoint Pointer to the endpoint object. This is ignored for
+ /// a UDP socket (the target is specified in the send call), but
+ /// should be of type TCPEndpoint for a TCP connection.
+ /// \param callback I/O Completion callback, called when the operation has
+ /// completed, but only if the operation was asynchronous. (It is
+ /// ignored on a UDP socket.)
+ virtual void open(const IOEndpoint* endpoint, C& callback) = 0;
+
+ /// \brief Send Asynchronously
+ ///
+ /// This corresponds to async_send_to() for UDP sockets and async_send()
+ /// for TCP. In both cases an endpoint argument is supplied indicating the
+ /// target of the send - this is ignored for TCP.
+ ///
+ /// \param data Data to send
+ /// \param length Length of data to send
+ /// \param endpoint Target of the send
+ /// \param callback Callback object.
+ virtual void asyncSend(const void* data, size_t length,
+ const IOEndpoint* endpoint, C& callback) = 0;
+
+ /// \brief Receive Asynchronously
+ ///
+ /// This corresponds to async_receive_from() for UDP sockets and
+ /// async_receive() for TCP. In both cases, an endpoint argument is
+ /// supplied to receive the source of the communication. For TCP it will
+ /// be filled in with details of the connection.
+ ///
+ /// \param data Buffer to receive incoming message
+ /// \param length Length of the data buffer
+ /// \param offset Offset into buffer where data is to be put. Although the
+ /// offset could be implied by adjusting "data" and "length"
+ /// appropriately, using this argument allows data to be specified as
+ /// "const void*" - the overhead of converting it to a pointer to a
+ /// set of bytes is hidden away here.
+ /// \param endpoint Source of the communication
+ /// \param callback Callback object
+ virtual void asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback) = 0;
+
+ /// \brief Processes received data
+ ///
+ /// In the IOFetch code, data is received into a staging buffer before being
+ /// copied into the target buffer. (This is because (a) we don't know how
+ /// much data we will be receiving, so don't know how to size the output
+ /// buffer and (b) TCP data is preceded by a two-byte count field that needs
+ /// to be discarded before being returned to the user.)
+ ///
+ /// An additional consideration is that TCP data is not received in one
+ /// I/O - it may take a number of I/Os - each receiving any non-zero number
+ /// of bytes - to read the entire message.
+ ///
+ /// So the IOFetch code has to loop until it determines that all the data
+ /// has been read. This is where this method comes in. It has several
+ /// functions:
+ ///
+ /// - It checks if the received data is complete.
+ /// - If data is not complete, decides if the next set of data is to go into
+ /// the start of the staging buffer or at some offset into it. (This
+ /// simplifies the case we could have in a TCP receive where the two-byte
+ /// count field is received in one-byte chunks: we put off interpreting
+ /// the count until we have all of it. The alternative - copying the
+ /// data to the output buffer and interpreting the count from there -
+ /// would require moving the data in the output buffer by two bytes before
+ /// returning it to the caller.)
+ /// - Copies data from the staging buffer into the output buffer.
+ ///
+ /// This functionality mainly applies to TCP receives. For UDP, all the
+ /// data is received in one I/O, so this just copies the data into the
+ /// output buffer.
+ ///
+ /// \param staging Pointer to the start of the staging buffer.
+ /// \param length Amount of data in the staging buffer.
+ /// \param cumulative Amount of data received before the staging buffer is
+ /// processed (this includes the TCP count field if appropriate).
+ /// The value should be set to zero before the receive loop is
+ /// entered, and it will be updated by this method as required.
+ /// \param offset Offset into the staging buffer where the next read should
+ /// put the received data. It should be set to zero before the first
+ /// call and may be updated by this method.
+ /// \param expected Expected amount of data to be received. This is
+ /// really the TCP count field and is set to that value when enough
+ /// of a TCP message is received. It should be initialized to -1
+ /// before the first read is executed.
+ /// \param outbuff Output buffer. Data in the staging buffer may be copied
+ /// to this output buffer in the call.
+ ///
+ /// \return true if the receive is complete, false if another receive is
+ /// needed. This is always true for UDP, but for TCP involves
+ /// checking the amount of data received so far against the amount
+ /// expected (as indicated by the two-byte count field). If this
+ /// method returns false, another read should be queued and data
+ /// should be read into the staging buffer at offset given by the
+ /// "offset" parameter.
+ virtual bool processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::dns::OutputBufferPtr& outbuff) = 0;
+
+ /// \brief Cancel I/O On AsioSocket
+ virtual void cancel() = 0;
+
+ /// \brief Close socket
+ virtual void close() = 0;
+};
+
+
+#include "io_socket.h"
+
+/// \brief The \c DummyAsioSocket class is a concrete derived class of
+/// \c IOAsioSocket that is not associated with any real socket.
+///
+/// This main purpose of this class is tests, where it may be desirable to
+/// instantiate an \c IOAsioSocket object without involving system resource
+/// allocation such as real network sockets.
+///
+/// \param C Template parameter identifying type of the callback object.
+
+template <typename C>
+class DummyAsioSocket : public IOAsioSocket<C> {
+private:
+ DummyAsioSocket(const DummyAsioSocket<C>& source);
+ DummyAsioSocket& operator=(const DummyAsioSocket<C>& source);
+public:
+ /// \brief Constructor from the protocol number.
+ ///
+ /// The protocol must validly identify a standard network protocol.
+ /// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
+ ///
+ /// \param protocol The network protocol number for the socket.
+ DummyAsioSocket(const int protocol) : protocol_(protocol) {}
+
+ /// \brief A dummy derived method of \c IOAsioSocket::getNative().
+ ///
+ /// \return Always returns -1 as the object is not associated with a real
+ /// (native) socket.
+ virtual int getNative() const { return (-1); }
+
+ /// \brief A dummy derived method of \c IOAsioSocket::getProtocol().
+ ///
+ /// \return Protocol socket was created with
+ virtual int getProtocol() const { return (protocol_); }
+
+
+ /// \brief Is socket opening synchronous?
+ ///
+ /// \return true - it is for this class.
+ bool isOpenSynchronous() const {
+ return true;
+ }
+
+ /// \brief Open AsioSocket
+ ///
+ /// A call that is a no-op on UDP sockets, this opens a connection to the
+ /// system identified by the given endpoint.
+ ///
+ /// \param endpoint Unused
+ /// \param callback Unused.
+ ///false indicating that the operation completed synchronously.
+ virtual bool open(const IOEndpoint*, C&) {
+ return (false);
+ }
+
+ /// \brief Send Asynchronously
+ ///
+ /// Must be supplied as it is abstract in the base class.
+ ///
+ /// \param data Unused
+ /// \param length Unused
+ /// \param endpoint Unused
+ /// \param callback Unused
+ virtual void asyncSend(const void*, size_t, const IOEndpoint*, C&) {
+ }
+
+ /// \brief Receive Asynchronously
+ ///
+ /// Must be supplied as it is abstract in the base class.
+ ///
+ /// \param data Unused
+ /// \param length Unused
+ /// \param offset Unused
+ /// \param endpoint Unused
+ /// \param callback Unused
+ virtual void asyncReceive(void* data, size_t, size_t, IOEndpoint*, C&) {
+ }
+
+ /// \brief Checks if the data received is complete.
+ ///
+ /// \param staging Unused
+ /// \param length Unused
+ /// \param cumulative Unused
+ /// \param offset Unused.
+ /// \param expected Unused.
+ /// \param outbuff Unused.
+ ///
+ /// \return Always true
+ virtual bool receiveComplete(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::dns::OutputBufferPtr& outbuff)
+ {
+ return (true);
+ }
+
+
+ /// \brief Cancel I/O On AsioSocket
+ ///
+ /// Must be supplied as it is abstract in the base class.
+ virtual void cancel() {
+ }
+
+ /// \brief Close socket
+ ///
+ /// Must be supplied as it is abstract in the base class.
+ virtual void close() {
+ }
+
+private:
+ const int protocol_;
+};
+
+} // namespace asiolink
+
+#endif // __IO_ASIO_SOCKET_H
diff --git a/src/lib/asiolink/io_endpoint.cc b/src/lib/asiolink/io_endpoint.cc
new file mode 100644
index 0000000..97e9c91
--- /dev/null
+++ b/src/lib/asiolink/io_endpoint.cc
@@ -0,0 +1,47 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <unistd.h> // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <asio.hpp>
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/udp_endpoint.h>
+
+using namespace std;
+
+namespace asiolink {
+
+const IOEndpoint*
+IOEndpoint::create(const int protocol, const IOAddress& address,
+ const unsigned short port)
+{
+ if (protocol == IPPROTO_UDP) {
+ return (new UDPEndpoint(address, port));
+ } else if (protocol == IPPROTO_TCP) {
+ return (new TCPEndpoint(address, port));
+ }
+ isc_throw(IOError,
+ "IOEndpoint creation attempt for unsupported protocol: " <<
+ protocol);
+}
+
+}
diff --git a/src/lib/asiolink/io_endpoint.h b/src/lib/asiolink/io_endpoint.h
new file mode 100644
index 0000000..2ec4083
--- /dev/null
+++ b/src/lib/asiolink/io_endpoint.h
@@ -0,0 +1,118 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __IO_ENDPOINT_H
+#define __IO_ENDPOINT_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h> // for some network system calls
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+#include <asiolink/io_address.h>
+
+namespace asiolink {
+
+/// \brief The \c IOEndpoint class is an abstract base class to represent
+/// a communication endpoint.
+///
+/// This class is a wrapper for the ASIO endpoint classes such as
+/// \c ip::tcp::endpoint and \c ip::udp::endpoint.
+///
+/// Derived class implementations are completely hidden within the
+/// implementation. User applications only get access to concrete
+/// \c IOEndpoint objects via the abstract interfaces.
+class IOEndpoint {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IOEndpoint(const IOEndpoint& source);
+ IOEndpoint& operator=(const IOEndpoint& source);
+protected:
+ /// \brief The default constructor.
+ ///
+ /// This is intentionally defined as \c protected as this base class
+ /// should never be instantiated (except as part of a derived class).
+ IOEndpoint() {}
+public:
+ /// The destructor.
+ virtual ~IOEndpoint() {}
+ //@}
+
+ /// \brief Returns the address of the endpoint.
+ ///
+ /// This method returns an IOAddress object corresponding to \c this
+ /// endpoint.
+ ///
+ /// Note that the return value is a real object, not a reference or
+ /// a pointer.
+ ///
+ /// This is aligned with the interface of the ASIO counterpart:
+ /// the \c address() method of \c ip::xxx::endpoint classes returns
+ /// an \c ip::address object.
+ ///
+ /// This also means handling the address of an endpoint using this method
+ /// can be expensive. If the address information is necessary in a
+ /// performance sensitive context and there's a more efficient interface
+ /// for that purpose, it's probably better to avoid using this method.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return A copy of \c IOAddress object corresponding to the endpoint.
+ virtual IOAddress getAddress() const = 0;
+
+ /// \brief Returns the port of the endpoint.
+ virtual uint16_t getPort() const = 0;
+
+ /// \brief Returns the protocol number of the endpoint (TCP, UDP...)
+ virtual short getProtocol() const = 0;
+
+ /// \brief Returns the address family of the endpoint.
+ virtual short getFamily() const = 0;
+
+ /// \brief A polymorphic factory of endpoint from address and port.
+ ///
+ /// This method creates a new instance of (a derived class of)
+ /// \c IOEndpoint object that identifies the pair of given address
+ /// and port.
+ /// The appropriate derived class is chosen based on the specified
+ /// transport protocol. If the \c protocol doesn't specify a protocol
+ /// supported in this implementation, an exception of class \c IOError
+ /// will be thrown.
+ ///
+ /// Memory for the created object will be dynamically allocated. It's
+ /// the caller's responsibility to \c delete it later.
+ /// If resource allocation for the new object fails, a corresponding
+ /// standard exception will be thrown.
+ ///
+ /// \param protocol The transport protocol used for the endpoint.
+ /// Currently, only \c IPPROTO_UDP and \c IPPROTO_TCP can be specified.
+ /// \param address The (IP) address of the endpoint.
+ /// \param port The transport port number of the endpoint
+ /// \return A pointer to a newly created \c IOEndpoint object.
+ static const IOEndpoint* create(const int protocol,
+ const IOAddress& address,
+ const unsigned short port);
+};
+
+} // asiolink
+#endif // __IO_ENDPOINT_H
diff --git a/src/lib/asiolink/io_error.h b/src/lib/asiolink/io_error.h
new file mode 100644
index 0000000..2869e0b
--- /dev/null
+++ b/src/lib/asiolink/io_error.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+
+#ifndef __IO_ERROR_H
+#define __IO_ERROR_H
+
+#include <exceptions/exceptions.h>
+
+namespace asiolink {
+
+/// \brief An exception that is thrown if an error occurs within the IO
+/// module. This is mainly intended to be a wrapper exception class for
+/// ASIO specific exceptions.
+class IOError : public isc::Exception {
+public:
+ IOError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+} // asiolink
+
+#endif // __IO_ERROR_H
diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc
new file mode 100644
index 0000000..3ff44c0
--- /dev/null
+++ b/src/lib/asiolink/io_fetch.cc
@@ -0,0 +1,355 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <unistd.h> // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <log/logger.h>
+
+#include <asiolink/qid_gen.h>
+
+#include <asio.hpp>
+#include <asio/deadline_timer.hpp>
+
+#include <asiolink/asiodef.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_fetch.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+
+using namespace asio;
+using namespace isc::dns;
+using namespace isc::log;
+using namespace std;
+
+namespace asiolink {
+
+/// Use the ASIO logger
+
+isc::log::Logger logger("asiolink");
+
+/// \brief IOFetch Data
+///
+/// The data for IOFetch is held in a separate struct pointed to by a shared_ptr
+/// object. This is because the IOFetch object will be copied often (it is used
+/// as a coroutine and passed as callback to many async_*() functions) and we
+/// want keep the same data). Organising the data in this way keeps copying to
+/// a minimum.
+struct IOFetchData {
+
+ // The first two members are shared pointers to a base class because what is
+ // actually instantiated depends on whether the fetch is over UDP or TCP,
+ // which is not known until construction of the IOFetch. Use of a shared
+ // pointer here is merely to ensure deletion when the data object is deleted.
+ boost::scoped_ptr<IOAsioSocket<IOFetch> > socket;
+ ///< Socket to use for I/O
+ boost::scoped_ptr<IOEndpoint> remote; ///< Where the fetch was sent
+ isc::dns::Question question; ///< Question to be asked
+ isc::dns::OutputBufferPtr msgbuf; ///< Wire buffer for question
+ isc::dns::OutputBufferPtr received; ///< Received data put here
+ IOFetch::Callback* callback; ///< Called on I/O Completion
+ asio::deadline_timer timer; ///< Timer to measure timeouts
+ IOFetch::Protocol protocol; ///< Protocol being used
+ size_t cumulative; ///< Cumulative received amount
+ size_t expected; ///< Expected amount of data
+ size_t offset; ///< Offset to receive data
+ bool stopped; ///< Have we stopped running?
+ int timeout; ///< Timeout in ms
+
+ // In case we need to log an error, the origin of the last asynchronous
+ // I/O is recorded. To save time and simplify the code, this is recorded
+ // as the ID of the error message that would be generated if the I/O failed.
+ // This means that we must make sure that all possible "origins" take the
+ // same arguments in their message in the same order.
+ isc::log::MessageID origin; ///< Origin of last asynchronous I/O
+ uint8_t staging[IOFetch::STAGING_LENGTH];
+ ///< Temporary array for received data
+
+ /// \brief Constructor
+ ///
+ /// Just fills in the data members of the IOFetchData structure
+ ///
+ /// \param proto Either IOFetch::TCP or IOFetch::UDP.
+ /// \param service I/O Service object to handle the asynchronous
+ /// operations.
+ /// \param query DNS question to send to the upstream server.
+ /// \param address IP address of upstream server
+ /// \param port Port to use for the query
+ /// \param buff Output buffer into which the response (in wire format)
+ /// is written (if a response is received).
+ /// \param cb Callback object containing the callback to be called
+ /// when we terminate. The caller is responsible for managing this
+ /// object and deleting it if necessary.
+ /// \param wait Timeout for the fetch (in ms).
+ ///
+ /// TODO: May need to alter constructor (see comment 4 in Trac ticket #554)
+ IOFetchData(IOFetch::Protocol proto, IOService& service,
+ const isc::dns::Question& query, const IOAddress& address,
+ uint16_t port, isc::dns::OutputBufferPtr& buff, IOFetch::Callback* cb,
+ int wait)
+ :
+ socket((proto == IOFetch::UDP) ?
+ static_cast<IOAsioSocket<IOFetch>*>(
+ new UDPSocket<IOFetch>(service)) :
+ static_cast<IOAsioSocket<IOFetch>*>(
+ new TCPSocket<IOFetch>(service))
+ ),
+ remote((proto == IOFetch::UDP) ?
+ static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
+ static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
+ ),
+ question(query),
+ msgbuf(new isc::dns::OutputBuffer(512)),
+ received(buff),
+
+ callback(cb),
+ timer(service.get_io_service()),
+ protocol(proto),
+ cumulative(0),
+ expected(0),
+ offset(0),
+ stopped(false),
+ timeout(wait),
+ origin(ASIO_UNKORIGIN),
+ staging()
+ {}
+};
+
+/// IOFetch Constructor - just initialize the private data
+
+IOFetch::IOFetch(Protocol protocol, IOService& service,
+ const isc::dns::Question& question, const IOAddress& address, uint16_t port,
+ OutputBufferPtr& buff, Callback* cb, int wait)
+ :
+ data_(new IOFetchData(protocol, service, question, address,
+ port, buff, cb, wait))
+{
+}
+
+// Return protocol in use.
+
+IOFetch::Protocol
+IOFetch::getProtocol() const {
+ return (data_->protocol);
+}
+
+/// The function operator is implemented with the "stackless coroutine"
+/// pattern; see internal/coroutine.h for details.
+
+void
+IOFetch::operator()(asio::error_code ec, size_t length) {
+
+ if (data_->stopped) {
+ return;
+ } else if (ec) {
+ logIOFailure(ec);
+ return;
+ }
+
+ CORO_REENTER (this) {
+
+ /// Generate the upstream query and render it to wire format
+ /// This is done in a different scope to allow inline variable
+ /// declarations.
+ {
+ Message msg(Message::RENDER);
+ msg.setQid(QidGenerator::getInstance().generateQid());
+ msg.setOpcode(Opcode::QUERY());
+ msg.setRcode(Rcode::NOERROR());
+ msg.setHeaderFlag(Message::HEADERFLAG_RD);
+ msg.addQuestion(data_->question);
+ MessageRenderer renderer(*data_->msgbuf);
+ msg.toWire(renderer);
+ }
+
+ // If we timeout, we stop, which will can cancel outstanding I/Os and
+ // shutdown everything.
+ if (data_->timeout != -1) {
+ data_->timer.expires_from_now(boost::posix_time::milliseconds(
+ data_->timeout));
+ data_->timer.async_wait(boost::bind(&IOFetch::stop, *this,
+ TIME_OUT));
+ }
+
+ // Open a connection to the target system. For speed, if the operation
+ // is synchronous (i.e. UDP operation) we bypass the yield.
+ data_->origin = ASIO_OPENSOCK;
+ if (data_->socket->isOpenSynchronous()) {
+ data_->socket->open(data_->remote.get(), *this);
+ } else {
+ CORO_YIELD data_->socket->open(data_->remote.get(), *this);
+ }
+
+ // Begin an asynchronous send, and then yield. When the send completes,
+ // we will resume immediately after this point.
+ data_->origin = ASIO_SENDSOCK;
+ CORO_YIELD data_->socket->asyncSend(data_->msgbuf->getData(),
+ data_->msgbuf->getLength(), data_->remote.get(), *this);
+
+ // Now receive the response. Since TCP may not receive the entire
+ // message in one operation, we need to loop until we have received
+ // it. (This can't be done within the asyncReceive() method because
+ // each I/O operation will be done asynchronously and between each one
+ // we need to yield ... and we *really* don't want to set up another
+ // coroutine within that method.) So after each receive (and yield),
+ // we check if the operation is complete and if not, loop to read again.
+ //
+ // Another concession to TCP is that the amount of is contained in the
+ // first two bytes. This leads to two problems:
+ //
+ // a) We don't want those bytes in the return buffer.
+ // b) They may not both arrive in the first I/O.
+ //
+ // So... we need to loop until we have at least two bytes, then store
+ // the expected amount of data. Then we need to loop until we have
+ // received all the data before copying it back to the user's buffer.
+ // And we want to minimise the amount of copying...
+
+ data_->origin = ASIO_RECVSOCK;
+ data_->cumulative = 0; // No data yet received
+ data_->offset = 0; // First data into start of buffer
+ do {
+ CORO_YIELD data_->socket->asyncReceive(data_->staging,
+ static_cast<size_t>(STAGING_LENGTH),
+ data_->offset,
+ data_->remote.get(), *this);
+ } while (!data_->socket->processReceivedData(data_->staging, length,
+ data_->cumulative, data_->offset,
+ data_->expected, data_->received));
+
+ // Finished with this socket, so close it. This will not generate an
+ // I/O error, but reset the origin to unknown in case we change this.
+ data_->origin = ASIO_UNKORIGIN;
+ data_->socket->close();
+
+ /// We are done
+ stop(SUCCESS);
+ }
+}
+
+// Function that stops the coroutine sequence. It is called either when the
+// query finishes or when the timer times out. Either way, it sets the
+// "stopped_" flag and cancels anything that is in progress.
+//
+// As the function may be entered multiple times as things wind down, it checks
+// if the stopped_ flag is already set. If it is, the call is a no-op.
+
+void
+IOFetch::stop(Result result) {
+
+ if (!data_->stopped) {
+
+ // Mark the fetch as stopped to prevent other completion callbacks
+ // (invoked because of the calls to cancel()) from executing the
+ // cancel calls again.
+ //
+ // In a single threaded environment, the callbacks won't be invoked
+ // until this one completes. In a multi-threaded environment, they may
+ // well be, in which case the testing (and setting) of the stopped_
+ // variable should be done inside a mutex (and the stopped_ variable
+ // declared as "volatile").
+ //
+ // The numeric arguments indicate the debug level, with the lower
+ // numbers indicating the most important information. The relative
+ // values are somewhat arbitrary.
+ //
+ // Although Logger::debug checks the debug flag internally, doing it
+ // below before calling Logger::debug avoids the overhead of a string
+ // conversion in the common case when debug is not enabled.
+ //
+ // TODO: Update testing of stopped_ if threads are used.
+ data_->stopped = true;
+ switch (result) {
+ case TIME_OUT:
+ if (logger.isDebugEnabled(1)) {
+ logger.debug(20, ASIO_RECVTMO,
+ data_->remote->getAddress().toText().c_str(),
+ static_cast<int>(data_->remote->getPort()));
+ }
+ break;
+
+ case SUCCESS:
+ if (logger.isDebugEnabled(50)) {
+ logger.debug(30, ASIO_FETCHCOMP,
+ data_->remote->getAddress().toText().c_str(),
+ static_cast<int>(data_->remote->getPort()));
+ }
+ break;
+
+ case STOPPED:
+ // Fetch has been stopped for some other reason. This is
+ // allowed but as it is unusual it is logged, but with a lower
+ // debug level than a timeout (which is totally normal).
+ logger.debug(1, ASIO_FETCHSTOP,
+ data_->remote->getAddress().toText().c_str(),
+ static_cast<int>(data_->remote->getPort()));
+ break;
+
+ default:
+ logger.error(ASIO_UNKRESULT, static_cast<int>(result),
+ data_->remote->getAddress().toText().c_str(),
+ static_cast<int>(data_->remote->getPort()));
+ }
+
+ // Stop requested, cancel and I/O's on the socket and shut it down,
+ // and cancel the timer.
+ data_->socket->cancel();
+ data_->socket->close();
+
+ data_->timer.cancel();
+
+ // Execute the I/O completion callback (if present).
+ if (data_->callback) {
+ (*(data_->callback))(result);
+ }
+ }
+}
+
+// Log an error - called on I/O failure
+
+void IOFetch::logIOFailure(asio::error_code ec) {
+
+ // Should only get here with a known error code.
+ assert((data_->origin == ASIO_OPENSOCK) ||
+ (data_->origin == ASIO_SENDSOCK) ||
+ (data_->origin == ASIO_RECVSOCK) ||
+ (data_->origin == ASIO_UNKORIGIN));
+
+ static const char* PROTOCOL[2] = {"TCP", "UDP"};
+ logger.error(data_->origin,
+ ec.value(),
+ ((data_->remote->getProtocol() == IPPROTO_TCP) ?
+ PROTOCOL[0] : PROTOCOL[1]),
+ data_->remote->getAddress().toText().c_str(),
+ static_cast<int>(data_->remote->getPort()));
+}
+
+} // namespace asiolink
+
diff --git a/src/lib/asiolink/io_fetch.h b/src/lib/asiolink/io_fetch.h
new file mode 100644
index 0000000..0723777
--- /dev/null
+++ b/src/lib/asiolink/io_fetch.h
@@ -0,0 +1,179 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __IO_FETCH_H
+#define __IO_FETCH_H 1
+
+#include <config.h>
+
+#include <boost/shared_array.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+#include <coroutine.h>
+
+#include <asio/error_code.hpp>
+
+#include <dns/buffer.h>
+#include <dns/question.h>
+
+namespace asiolink {
+
+// Forward declarations
+class IOAddress;
+class IOFetchData;
+class IOService;
+
+/// \brief Upstream Fetch Processing
+///
+/// IOFetch is the class used to send upstream fetches and to handle responses.
+///
+/// \param E Endpoint type to use.
+
+class IOFetch : public coroutine {
+public:
+ /// \brief Protocol to use on the fetch
+ enum Protocol {
+ UDP = 0,
+ TCP = 1
+ };
+
+ /// \brief Origin of Asynchronous I/O Call
+ ///
+ /// Indicates what initiated an asynchronous I/O call and used in deciding
+ /// what error message to output if the I/O fails.
+ enum Origin {
+ NONE = 0, ///< No asynchronous call outstanding
+ OPEN = 1,
+ SEND = 2,
+ RECEIVE = 3,
+ CLOSE = 4
+ };
+
+ /// \brief Result of Upstream Fetch
+ ///
+ /// Note that this applies to the status of I/Os in the fetch - a fetch
+ /// that resulted in a packet being received from the server is a SUCCESS,
+ /// even if the contents of the packet indicate that some error occurred.
+ enum Result {
+ SUCCESS = 0, ///< Success, fetch completed
+ TIME_OUT = 1, ///< Failure, fetch timed out
+ STOPPED = 2, ///< Control code, fetch has been stopped
+ NOTSET = 3 ///< For testing, indicates value not set
+ };
+
+ // The next enum is a "trick" to allow constants to be defined in a class
+ // declaration.
+
+ /// \brief Integer Constants
+ enum {
+ STAGING_LENGTH = 8192 ///< Size of staging buffer
+ };
+
+ /// \brief I/O Fetch Callback
+ ///
+ /// Class of callback object for when the fetch itself has completed - an
+ /// object of this class is passed to the IOFetch constructor and its
+ /// operator() method called when the fetch completes.
+ ///
+ /// Note the difference between the two operator() methods:
+ /// - IOFetch::operator() callback is called when an asynchronous I/O has
+ /// completed.
+ /// - IOFetch::Callback::operator() is called when an upstream fetch - which
+ /// may have involved several asynchronous I/O operations - has completed.
+ ///
+ /// This is an abstract class.
+ class Callback {
+ public:
+ /// \brief Default Constructor
+ Callback()
+ {}
+
+ /// \brief Virtual Destructor
+ virtual ~Callback()
+ {}
+
+ /// \brief Callback method
+ ///
+ /// This is the method called when the fetch completes.
+ ///
+ /// \param result Result of the fetch
+ virtual void operator()(Result result) = 0;
+ };
+
+ /// \brief Constructor.
+ ///
+ /// Creates the object that will handle the upstream fetch.
+ ///
+ /// TODO: Need to randomise the source port
+ ///
+ /// \param protocol Fetch protocol, either IOFetch::TCP or IOFetch::UDP
+ /// \param service I/O Service object to handle the asynchronous
+ /// operations.
+ /// \param question DNS question to send to the upstream server.
+ /// \param buff Output buffer into which the response (in wire format)
+ /// is written (if a response is received).
+ /// \param cb Callback object containing the callback to be called
+ /// when we terminate. The caller is responsible for managing this
+ /// object and deleting it if necessary.
+ /// \param address IP address of upstream server
+ /// \param port Port to which to connect on the upstream server
+ /// (default = 53)
+ /// \param wait Timeout for the fetch (in ms). The default value of
+ /// -1 indicates no timeout.
+ IOFetch(Protocol protocol, IOService& service,
+ const isc::dns::Question& question, const IOAddress& address,
+ uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb,
+ int wait = -1);
+
+ /// \brief Return Current Protocol
+ ///
+ /// \return Protocol associated with this IOFetch object.
+ Protocol getProtocol() const;
+
+ /// \brief Coroutine entry point
+ ///
+ /// The operator() method is the method in which the coroutine code enters
+ /// this object when an operation has been completed.
+ ///
+ /// \param ec Error code, the result of the last asynchronous I/O operation.
+ /// \param length Amount of data received on the last asynchronous read
+ void operator()(asio::error_code ec = asio::error_code(), size_t length = 0);
+
+ /// \brief Terminate query
+ ///
+ /// This method can be called at any point. It terminates the current
+ /// query with the specified reason.
+ ///
+ /// \param reason Reason for terminating the query
+ void stop(Result reason = STOPPED);
+
+private:
+ /// \brief Log I/O Failure
+ ///
+ /// Records an I/O failure to the log file
+ ///
+ /// \param ec ASIO error code
+ void logIOFailure(asio::error_code ec);
+
+ // Member variables. All data is in a structure pointed to by a shared
+ // pointer. The IOFetch object is copied a number of times during its
+ // life, and only requiring a pointer to be copied reduces overhead.
+ boost::shared_ptr<IOFetchData> data_; ///< Private data
+
+};
+
+} // namespace asiolink
+
+#endif // __IO_FETCH_H
diff --git a/src/lib/asiolink/io_message.h b/src/lib/asiolink/io_message.h
new file mode 100644
index 0000000..e857bd9
--- /dev/null
+++ b/src/lib/asiolink/io_message.h
@@ -0,0 +1,100 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __IO_MESSAGE_H
+#define __IO_MESSAGE_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h> // for some network system calls
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_socket.h>
+
+namespace asiolink {
+
+/// \brief The \c IOMessage class encapsulates an incoming message received
+/// on a socket.
+///
+/// An \c IOMessage object represents a tuple of a chunk of data
+/// (a UDP packet or some segment of TCP stream), the socket over which the
+/// data is passed, the information about the other end point of the
+/// communication, and perhaps more.
+///
+/// The current design and interfaces of this class is tentative.
+/// It only provides a minimal level of support that is necessary for
+/// the current implementation of the authoritative server.
+/// A future version of this class will definitely support more.
+class IOMessage {
+ ///
+ /// \name Constructors and Destructor
+ ///
+
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IOMessage(const IOMessage& source);
+ IOMessage& operator=(const IOMessage& source);
+public:
+ /// \brief Constructor from message data
+ ///
+ /// This constructor needs to handle the ASIO \c ip::address class,
+ /// and is intended to be used within this wrapper implementation.
+ /// Once the \c IOMessage object is created, the application can
+ /// get access to the information via the wrapper interface such as
+ /// \c getRemoteAddress().
+ ///
+ /// This constructor never throws an exception.
+ ///
+ /// \param data A pointer to the message data.
+ /// \param data_size The size of the message data in bytes.
+ /// \param io_socket The socket over which the data is given.
+ /// \param remote_endpoint The other endpoint of the socket, that is,
+ /// the sender of the message.
+ IOMessage(const void* data, const size_t data_size,
+ const IOSocket& io_socket, const IOEndpoint& remote_endpoint) :
+ data_(data), data_size_(data_size), io_socket_(io_socket),
+ remote_endpoint_(remote_endpoint)
+ {}
+ //@}
+
+ /// \brief Returns a pointer to the received data.
+ const void* getData() const { return (data_); }
+
+ /// \brief Returns the size of the received data in bytes.
+ size_t getDataSize() const { return (data_size_); }
+
+ /// \brief Returns the socket on which the message arrives.
+ const IOSocket& getSocket() const { return (io_socket_); }
+
+ /// \brief Returns the endpoint that sends the message.
+ const IOEndpoint& getRemoteEndpoint() const { return (remote_endpoint_); }
+
+private:
+ const void* data_;
+ const size_t data_size_;
+ const IOSocket& io_socket_;
+ const IOEndpoint& remote_endpoint_;
+};
+
+
+} // asiolink
+#endif // __IO_MESSAGE_H
diff --git a/src/lib/asiolink/io_service.cc b/src/lib/asiolink/io_service.cc
new file mode 100644
index 0000000..55fc4b3
--- /dev/null
+++ b/src/lib/asiolink/io_service.cc
@@ -0,0 +1,98 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+
+#include <config.h>
+
+#include <asio.hpp>
+#include <asiolink/io_service.h>
+
+namespace asiolink {
+
+class IOServiceImpl {
+private:
+ IOServiceImpl(const IOService& source);
+ IOServiceImpl& operator=(const IOService& source);
+public:
+ /// \brief The constructor
+ IOServiceImpl() :
+ io_service_(),
+ work_(io_service_)
+ {};
+ /// \brief The destructor.
+ ~IOServiceImpl() {};
+ //@}
+
+ /// \brief Start the underlying event loop.
+ ///
+ /// This method does not return control to the caller until
+ /// the \c stop() method is called via some handler.
+ void run() { io_service_.run(); };
+
+ /// \brief Run the underlying event loop for a single event.
+ ///
+ /// This method return control to the caller as soon as the
+ /// first handler has completed. (If no handlers are ready when
+ /// it is run, it will block until one is.)
+ void run_one() { io_service_.run_one();} ;
+
+ /// \brief Stop the underlying event loop.
+ ///
+ /// This will return the control to the caller of the \c run() method.
+ void stop() { io_service_.stop();} ;
+
+ /// \brief Return the native \c io_service object used in this wrapper.
+ ///
+ /// This is a short term work around to support other BIND 10 modules
+ /// that share the same \c io_service with the authoritative server.
+ /// It will eventually be removed once the wrapper interface is
+ /// generalized.
+ asio::io_service& get_io_service() { return io_service_; };
+private:
+ asio::io_service io_service_;
+ asio::io_service::work work_;
+};
+
+IOService::IOService() {
+ io_impl_ = new IOServiceImpl();
+}
+
+IOService::~IOService() {
+ delete io_impl_;
+}
+
+void
+IOService::run() {
+ io_impl_->run();
+}
+
+void
+IOService::run_one() {
+ io_impl_->run_one();
+}
+
+void
+IOService::stop() {
+ io_impl_->stop();
+}
+
+asio::io_service&
+IOService::get_io_service() {
+ return (io_impl_->get_io_service());
+}
+
+} // namepsace asiolink
diff --git a/src/lib/asiolink/io_service.h b/src/lib/asiolink/io_service.h
new file mode 100644
index 0000000..66558b7
--- /dev/null
+++ b/src/lib/asiolink/io_service.h
@@ -0,0 +1,77 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_IO_SERVICE_H
+#define __ASIOLINK_IO_SERVICE_H 1
+
+namespace asio {
+ class io_service;
+}
+
+namespace asiolink {
+
+struct IOServiceImpl;
+
+/// \brief The \c IOService class is a wrapper for the ASIO \c io_service
+/// class.
+///
+class IOService {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IOService(const IOService& source);
+ IOService& operator=(const IOService& source);
+public:
+ /// \brief The constructor
+ IOService();
+ /// \brief The destructor.
+ ~IOService();
+ //@}
+
+ /// \brief Start the underlying event loop.
+ ///
+ /// This method does not return control to the caller until
+ /// the \c stop() method is called via some handler.
+ void run();
+
+ /// \brief Run the underlying event loop for a single event.
+ ///
+ /// This method return control to the caller as soon as the
+ /// first handler has completed. (If no handlers are ready when
+ /// it is run, it will block until one is.)
+ void run_one();
+
+ /// \brief Stop the underlying event loop.
+ ///
+ /// This will return the control to the caller of the \c run() method.
+ void stop();
+
+ /// \brief Return the native \c io_service object used in this wrapper.
+ ///
+ /// This is a short term work around to support other BIND 10 modules
+ /// that share the same \c io_service with the authoritative server.
+ /// It will eventually be removed once the wrapper interface is
+ /// generalized.
+ asio::io_service& get_io_service();
+
+private:
+ IOServiceImpl* io_impl_;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_IO_SERVICE_H
diff --git a/src/lib/asiolink/io_socket.cc b/src/lib/asiolink/io_socket.cc
new file mode 100644
index 0000000..fb325e9
--- /dev/null
+++ b/src/lib/asiolink/io_socket.cc
@@ -0,0 +1,67 @@
+// Copyright (C) 2010 CZ NIC
+// Copyed from other version of auth/asiolink.cc which is:
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "io_socket.h"
+
+#include <asio.hpp>
+
+using namespace asio;
+
+namespace asiolink {
+
+/// \brief The \c DummySocket class is a concrete derived class of
+/// \c IOSocket that is not associated with any real socket.
+///
+/// This main purpose of this class is tests, where it may be desirable to
+/// instantiate an \c IOSocket object without involving system resource
+/// allocation such as real network sockets.
+class DummySocket : public IOSocket {
+private:
+ DummySocket(const DummySocket& source);
+ DummySocket& operator=(const DummySocket& source);
+public:
+ /// \brief Constructor from the protocol number.
+ ///
+ /// The protocol must validly identify a standard network protocol.
+ /// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
+ ///
+ /// \param protocol The network protocol number for the socket.
+ DummySocket(const int protocol) : protocol_(protocol) {}
+
+ /// \brief A dummy derived method of \c IOSocket::getNative().
+ ///
+ /// This version of method always returns -1 as the object is not
+ /// associated with a real (native) socket.
+ virtual int getNative() const { return (-1); }
+
+ virtual int getProtocol() const { return (protocol_); }
+private:
+ const int protocol_;
+};
+
+IOSocket&
+IOSocket::getDummyUDPSocket() {
+ static DummySocket socket(IPPROTO_UDP);
+ return (socket);
+}
+
+IOSocket&
+IOSocket::getDummyTCPSocket() {
+ static DummySocket socket(IPPROTO_TCP);
+ return (socket);
+}
+
+}
diff --git a/src/lib/asiolink/io_socket.h b/src/lib/asiolink/io_socket.h
new file mode 100644
index 0000000..bebc8b6
--- /dev/null
+++ b/src/lib/asiolink/io_socket.h
@@ -0,0 +1,124 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __IO_SOCKET_H
+#define __IO_SOCKET_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h> // for some network system calls
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+namespace asiolink {
+
+/// \brief The \c IOSocket class is an abstract base class to represent
+/// various types of network sockets.
+///
+/// This class is a wrapper for the ASIO socket classes such as
+/// \c ip::tcp::socket and \c ip::udp::socket.
+///
+/// Derived class implementations are completely hidden within the
+/// implementation. User applications only get access to concrete
+/// \c IOSocket objects via the abstract interfaces.
+///
+/// We may revisit this decision when we generalize the wrapper and more
+/// modules use it. Also, at that point we may define a separate (visible)
+/// derived class for testing purposes rather than providing factory methods
+/// (i.e., getDummy variants below).
+class IOSocket {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IOSocket(const IOSocket& source);
+ IOSocket& operator=(const IOSocket& source);
+protected:
+ /// \brief The default constructor.
+ ///
+ /// This is intentionally defined as \c protected as this base class
+ /// should never be instantiated (except as part of a derived class).
+ IOSocket() {}
+public:
+ /// The destructor.
+ virtual ~IOSocket() {}
+ //@}
+
+ /// \brief Return the "native" representation of the socket.
+ ///
+ /// In practice, this is the file descriptor of the socket for
+ /// UNIX-like systems so the current implementation simply uses
+ /// \c int as the type of the return value.
+ /// We may have to need revisit this decision later.
+ ///
+ /// In general, the application should avoid using this method;
+ /// it essentially discloses an implementation specific "handle" that
+ /// can change the internal state of the socket (consider the
+ /// application closes it, for example).
+ /// But we sometimes need to perform very low-level operations that
+ /// requires the native representation. Passing the file descriptor
+ /// to a different process is one example.
+ /// This method is provided as a necessary evil for such limited purposes.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return The native representation of the socket. This is the socket
+ /// file descriptor for UNIX-like systems.
+ virtual int getNative() const = 0;
+
+ /// \brief Return the transport protocol of the socket.
+ ///
+ /// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
+ /// \c IPPROTO_TCP for TCP sockets.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return IPPROTO_UDP for UDP sockets
+ /// \return IPPROTO_TCP for TCP sockets
+ virtual int getProtocol() const = 0;
+
+ /// \brief Return a non-usable "dummy" UDP socket for testing.
+ ///
+ /// This is a class method that returns a "mock" of UDP socket.
+ /// This is not associated with any actual socket, and its only
+ /// responsibility is to return \c IPPROTO_UDP from \c getProtocol().
+ /// The only feasible usage of this socket is for testing so that
+ /// the test code can prepare some "UDP data" even without opening any
+ /// actual socket.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return A reference to an \c IOSocket object whose \c getProtocol()
+ /// returns \c IPPROTO_UDP.
+ static IOSocket& getDummyUDPSocket();
+
+ /// \brief Return a non-usable "dummy" TCP socket for testing.
+ ///
+ /// See \c getDummyUDPSocket(). This method is its TCP version.
+ ///
+ /// \return A reference to an \c IOSocket object whose \c getProtocol()
+ /// returns \c IPPROTO_TCP.
+ static IOSocket& getDummyTCPSocket();
+};
+
+} // namespace asiolink
+
+#endif // __IO_SOCKET_H
diff --git a/src/lib/asiolink/ioaddress.cc b/src/lib/asiolink/ioaddress.cc
deleted file mode 100644
index 990524a..0000000
--- a/src/lib/asiolink/ioaddress.cc
+++ /dev/null
@@ -1,62 +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 <config.h>
-
-#include <unistd.h> // for some IPC/network system calls
-#include <sys/socket.h>
-#include <netinet/in.h>
-
-#include <asio.hpp>
-
-#include <asiolink/asiolink.h>
-
-using namespace asio;
-using asio::ip::udp;
-using asio::ip::tcp;
-
-using namespace std;
-
-namespace asiolink {
-
-// XXX: we cannot simply construct the address in the initialization list,
-// because we'd like to throw our own exception on failure.
-IOAddress::IOAddress(const string& address_str) {
- error_code err;
- asio_address_ = ip::address::from_string(address_str, err);
- if (err) {
- isc_throw(IOError, "Failed to convert string to address '"
- << address_str << "': " << err.message());
- }
-}
-
-IOAddress::IOAddress(const ip::address& asio_address) :
- asio_address_(asio_address)
-{}
-
-string
-IOAddress::toText() const {
- return (asio_address_.to_string());
-}
-
-short
-IOAddress::getFamily() const {
- if (asio_address_.is_v4()) {
- return (AF_INET);
- } else {
- return (AF_INET6);
- }
-}
-
-}
diff --git a/src/lib/asiolink/ioaddress.h b/src/lib/asiolink/ioaddress.h
deleted file mode 100644
index 58042d7..0000000
--- a/src/lib/asiolink/ioaddress.h
+++ /dev/null
@@ -1,127 +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 __IOADDRESS_H
-#define __IOADDRESS_H 1
-
-// IMPORTANT NOTE: only very few ASIO headers files can be included in
-// this file. In particular, asio.hpp should never be included here.
-// See the description of the namespace below.
-#include <unistd.h> // for some network system calls
-#include <asio/ip/address.hpp>
-
-#include <functional>
-#include <string>
-
-#include <exceptions/exceptions.h>
-
-namespace asiolink {
-
-/// \brief The \c IOAddress class represents an IP addresses (version
-/// agnostic)
-///
-/// This class is a wrapper for the ASIO \c ip::address class.
-class IOAddress {
-public:
- ///
- /// \name Constructors and Destructor
- ///
- /// This class is copyable. We use default versions of copy constructor
- /// and the assignment operator.
- /// We use the default destructor.
- //@{
- /// \brief Constructor from string.
- ///
- /// This constructor converts a textual representation of IPv4 and IPv6
- /// addresses into an IOAddress object.
- /// If \c address_str is not a valid representation of any type of
- /// address, an exception of class \c IOError will be thrown.
- /// This constructor allocates memory for the object, and if that fails
- /// a corresponding standard exception will be thrown.
- ///
- /// \param address_str Textual representation of address.
- IOAddress(const std::string& address_str);
-
- /// \brief Constructor from an ASIO \c ip::address object.
- ///
- /// This constructor is intended to be used within the wrapper
- /// implementation; user applications of the wrapper API won't use it.
- ///
- /// This constructor never throws an exception.
- ///
- /// \param asio_address The ASIO \c ip::address to be converted.
- IOAddress(const asio::ip::address& asio_address);
- //@}
-
- /// \brief Convert the address to a string.
- ///
- /// This method is basically expected to be exception free, but
- /// generating the string will involve resource allocation,
- /// and if it fails the corresponding standard exception will be thrown.
- ///
- /// \return A string representation of the address.
- std::string toText() const;
-
- /// \brief Returns the address family
- ///
- /// \return AF_INET for IPv4 or AF_INET6 for IPv6.
- short getFamily() const;
-
- /// \brief Compare addresses for equality
- ///
- /// \param other Address to compare against.
- ///
- /// \return true if addresses are equal, false if not.
- bool equals(const IOAddress& other) const {
- return (asio_address_ == other.asio_address_);
- }
-
- /// \brief Compare addresses for equality
- ///
- /// \param other Address to compare against.
- ///
- /// \return true if addresses are equal, false if not.
- bool operator==(const IOAddress& other) const {
- return equals(other);
- }
-
- // \brief Compare addresses for inequality
- ///
- /// \param other Address to compare against.
- ///
- /// \return false if addresses are equal, true if not.
- bool nequals(const IOAddress& other) const {
- return (!equals(other));
- }
-
- // \brief Compare addresses for inequality
- ///
- /// \param other Address to compare against.
- ///
- /// \return false if addresses are equal, true if not.
- bool operator!=(const IOAddress& other) const {
- return (nequals(other));
- }
-
-
-private:
- asio::ip::address asio_address_;
-};
-
-} // asiolink
-#endif // __IOADDRESS_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/ioendpoint.cc b/src/lib/asiolink/ioendpoint.cc
deleted file mode 100644
index 2807f8d..0000000
--- a/src/lib/asiolink/ioendpoint.cc
+++ /dev/null
@@ -1,43 +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 <config.h>
-
-#include <unistd.h> // for some IPC/network system calls
-#include <sys/socket.h>
-#include <netinet/in.h>
-
-#include <asiolink/asiolink.h>
-#include <internal/tcpdns.h>
-#include <internal/udpdns.h>
-
-using namespace std;
-
-namespace asiolink {
-
-const IOEndpoint*
-IOEndpoint::create(const int protocol, const IOAddress& address,
- const unsigned short port)
-{
- if (protocol == IPPROTO_UDP) {
- return (new UDPEndpoint(address, port));
- } else if (protocol == IPPROTO_TCP) {
- return (new TCPEndpoint(address, port));
- }
- isc_throw(IOError,
- "IOEndpoint creation attempt for unsupported protocol: " <<
- protocol);
-}
-
-}
diff --git a/src/lib/asiolink/ioendpoint.h b/src/lib/asiolink/ioendpoint.h
deleted file mode 100644
index 82190a3..0000000
--- a/src/lib/asiolink/ioendpoint.h
+++ /dev/null
@@ -1,122 +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 __IOENDPOINT_H
-#define __IOENDPOINT_H 1
-
-// IMPORTANT NOTE: only very few ASIO headers files can be included in
-// this file. In particular, asio.hpp should never be included here.
-// See the description of the namespace below.
-#include <unistd.h> // for some network system calls
-
-#include <functional>
-#include <string>
-
-#include <exceptions/exceptions.h>
-#include <asiolink/ioaddress.h>
-
-namespace asiolink {
-
-/// \brief The \c IOEndpoint class is an abstract base class to represent
-/// a communication endpoint.
-///
-/// This class is a wrapper for the ASIO endpoint classes such as
-/// \c ip::tcp::endpoint and \c ip::udp::endpoint.
-///
-/// Derived class implementations are completely hidden within the
-/// implementation. User applications only get access to concrete
-/// \c IOEndpoint objects via the abstract interfaces.
-class IOEndpoint {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- IOEndpoint(const IOEndpoint& source);
- IOEndpoint& operator=(const IOEndpoint& source);
-protected:
- /// \brief The default constructor.
- ///
- /// This is intentionally defined as \c protected as this base class
- /// should never be instantiated (except as part of a derived class).
- IOEndpoint() {}
-public:
- /// The destructor.
- virtual ~IOEndpoint() {}
- //@}
-
- /// \brief Returns the address of the endpoint.
- ///
- /// This method returns an IOAddress object corresponding to \c this
- /// endpoint.
- ///
- /// Note that the return value is a real object, not a reference or
- /// a pointer.
- ///
- /// This is aligned with the interface of the ASIO counterpart:
- /// the \c address() method of \c ip::xxx::endpoint classes returns
- /// an \c ip::address object.
- ///
- /// This also means handling the address of an endpoint using this method
- /// can be expensive. If the address information is necessary in a
- /// performance sensitive context and there's a more efficient interface
- /// for that purpose, it's probably better to avoid using this method.
- ///
- /// This method never throws an exception.
- ///
- /// \return A copy of \c IOAddress object corresponding to the endpoint.
- virtual IOAddress getAddress() const = 0;
-
- /// \brief Returns the port of the endpoint.
- virtual uint16_t getPort() const = 0;
-
- /// \brief Returns the protocol number of the endpoint (TCP, UDP...)
- virtual short getProtocol() const = 0;
-
- /// \brief Returns the address family of the endpoint.
- virtual short getFamily() const = 0;
-
- /// \brief A polymorphic factory of endpoint from address and port.
- ///
- /// This method creates a new instance of (a derived class of)
- /// \c IOEndpoint object that identifies the pair of given address
- /// and port.
- /// The appropriate derived class is chosen based on the specified
- /// transport protocol. If the \c protocol doesn't specify a protocol
- /// supported in this implementation, an exception of class \c IOError
- /// will be thrown.
- ///
- /// Memory for the created object will be dynamically allocated. It's
- /// the caller's responsibility to \c delete it later.
- /// If resource allocation for the new object fails, a corresponding
- /// standard exception will be thrown.
- ///
- /// \param protocol The transport protocol used for the endpoint.
- /// Currently, only \c IPPROTO_UDP and \c IPPROTO_TCP can be specified.
- /// \param address The (IP) address of the endpoint.
- /// \param port The transport port number of the endpoint
- /// \return A pointer to a newly created \c IOEndpoint object.
- static const IOEndpoint* create(const int protocol,
- const IOAddress& address,
- const unsigned short port);
-};
-
-} // asiolink
-#endif // __IOENDPOINT_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/iomessage.h b/src/lib/asiolink/iomessage.h
deleted file mode 100644
index 27baa8a..0000000
--- a/src/lib/asiolink/iomessage.h
+++ /dev/null
@@ -1,103 +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 __IOMESSAGE_H
-#define __IOMESSAGE_H 1
-
-// IMPORTANT NOTE: only very few ASIO headers files can be included in
-// this file. In particular, asio.hpp should never be included here.
-// See the description of the namespace below.
-#include <unistd.h> // for some network system calls
-
-#include <functional>
-#include <string>
-
-#include <exceptions/exceptions.h>
-
-#include <asiolink/ioendpoint.h>
-#include <asiolink/iosocket.h>
-
-namespace asiolink {
-
-/// \brief The \c IOMessage class encapsulates an incoming message received
-/// on a socket.
-///
-/// An \c IOMessage object represents a tuple of a chunk of data
-/// (a UDP packet or some segment of TCP stream), the socket over which the
-/// data is passed, the information about the other end point of the
-/// communication, and perhaps more.
-///
-/// The current design and interfaces of this class is tentative.
-/// It only provides a minimal level of support that is necessary for
-/// the current implementation of the authoritative server.
-/// A future version of this class will definitely support more.
-class IOMessage {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- IOMessage(const IOMessage& source);
- IOMessage& operator=(const IOMessage& source);
-public:
- /// \brief Constructor from message data
- ///
- /// This constructor needs to handle the ASIO \c ip::address class,
- /// and is intended to be used within this wrapper implementation.
- /// Once the \c IOMessage object is created, the application can
- /// get access to the information via the wrapper interface such as
- /// \c getRemoteAddress().
- ///
- /// This constructor never throws an exception.
- ///
- /// \param data A pointer to the message data.
- /// \param data_size The size of the message data in bytes.
- /// \param io_socket The socket over which the data is given.
- /// \param remote_endpoint The other endpoint of the socket, that is,
- /// the sender of the message.
- IOMessage(const void* data, const size_t data_size,
- const IOSocket& io_socket, const IOEndpoint& remote_endpoint) :
- data_(data), data_size_(data_size), io_socket_(io_socket),
- remote_endpoint_(remote_endpoint)
- {}
- //@}
-
- /// \brief Returns a pointer to the received data.
- const void* getData() const { return (data_); }
-
- /// \brief Returns the size of the received data in bytes.
- size_t getDataSize() const { return (data_size_); }
-
- /// \brief Returns the socket on which the message arrives.
- const IOSocket& getSocket() const { return (io_socket_); }
-
- /// \brief Returns the endpoint that sends the message.
- const IOEndpoint& getRemoteEndpoint() const { return (remote_endpoint_); }
-
-private:
- const void* data_;
- const size_t data_size_;
- const IOSocket& io_socket_;
- const IOEndpoint& remote_endpoint_;
-};
-
-
-} // asiolink
-#endif // __IOMESSAGE_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/iosocket.cc b/src/lib/asiolink/iosocket.cc
deleted file mode 100644
index a3967d4..0000000
--- a/src/lib/asiolink/iosocket.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (C) 2010 CZ NIC
-// Copyed from other version of auth/asiolink.cc which is:
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER 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 "iosocket.h"
-
-#include <asio.hpp>
-
-using namespace asio;
-
-namespace asiolink {
-
-/// \brief The \c DummySocket class is a concrete derived class of
-/// \c IOSocket that is not associated with any real socket.
-///
-/// This main purpose of this class is tests, where it may be desirable to
-/// instantiate an \c IOSocket object without involving system resource
-/// allocation such as real network sockets.
-class DummySocket : public IOSocket {
-private:
- DummySocket(const DummySocket& source);
- DummySocket& operator=(const DummySocket& source);
-public:
- /// \brief Constructor from the protocol number.
- ///
- /// The protocol must validly identify a standard network protocol.
- /// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
- ///
- /// \param protocol The network protocol number for the socket.
- DummySocket(const int protocol) : protocol_(protocol) {}
-
- /// \brief A dummy derived method of \c IOSocket::getNative().
- ///
- /// This version of method always returns -1 as the object is not
- /// associated with a real (native) socket.
- virtual int getNative() const { return (-1); }
-
- virtual int getProtocol() const { return (protocol_); }
-private:
- const int protocol_;
-};
-
-IOSocket&
-IOSocket::getDummyUDPSocket() {
- static DummySocket socket(IPPROTO_UDP);
- return (socket);
-}
-
-IOSocket&
-IOSocket::getDummyTCPSocket() {
- static DummySocket socket(IPPROTO_TCP);
- return (socket);
-}
-
-}
diff --git a/src/lib/asiolink/iosocket.h b/src/lib/asiolink/iosocket.h
deleted file mode 100644
index df37d71..0000000
--- a/src/lib/asiolink/iosocket.h
+++ /dev/null
@@ -1,127 +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 __IOSOCKET_H
-#define __IOSOCKET_H 1
-
-// IMPORTANT NOTE: only very few ASIO headers files can be included in
-// this file. In particular, asio.hpp should never be included here.
-// See the description of the namespace below.
-#include <unistd.h> // for some network system calls
-
-#include <functional>
-#include <string>
-
-#include <exceptions/exceptions.h>
-
-namespace asiolink {
-
-/// \brief The \c IOSocket class is an abstract base class to represent
-/// various types of network sockets.
-///
-/// This class is a wrapper for the ASIO socket classes such as
-/// \c ip::tcp::socket and \c ip::udp::socket.
-///
-/// Derived class implementations are completely hidden within the
-/// implementation. User applications only get access to concrete
-/// \c IOSocket objects via the abstract interfaces.
-///
-/// We may revisit this decision when we generalize the wrapper and more
-/// modules use it. Also, at that point we may define a separate (visible)
-/// derived class for testing purposes rather than providing factory methods
-/// (i.e., getDummy variants below).
-class IOSocket {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private, making this class non-copyable.
- //@{
-private:
- IOSocket(const IOSocket& source);
- IOSocket& operator=(const IOSocket& source);
-protected:
- /// \brief The default constructor.
- ///
- /// This is intentionally defined as \c protected as this base class
- /// should never be instantiated (except as part of a derived class).
- IOSocket() {}
-public:
- /// The destructor.
- virtual ~IOSocket() {}
- //@}
-
- /// \brief Return the "native" representation of the socket.
- ///
- /// In practice, this is the file descriptor of the socket for
- /// UNIX-like systems so the current implementation simply uses
- /// \c int as the type of the return value.
- /// We may have to need revisit this decision later.
- ///
- /// In general, the application should avoid using this method;
- /// it essentially discloses an implementation specific "handle" that
- /// can change the internal state of the socket (consider the
- /// application closes it, for example).
- /// But we sometimes need to perform very low-level operations that
- /// requires the native representation. Passing the file descriptor
- /// to a different process is one example.
- /// This method is provided as a necessary evil for such limited purposes.
- ///
- /// This method never throws an exception.
- ///
- /// \return The native representation of the socket. This is the socket
- /// file descriptor for UNIX-like systems.
- virtual int getNative() const = 0;
-
- /// \brief Return the transport protocol of the socket.
- ///
- /// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
- /// \c IPPROTO_TCP for TCP sockets.
- ///
- /// This method never throws an exception.
- ///
- /// \return IPPROTO_UDP for UDP sockets
- /// \return IPPROTO_TCP for TCP sockets
- virtual int getProtocol() const = 0;
-
- /// \brief Return a non-usable "dummy" UDP socket for testing.
- ///
- /// This is a class method that returns a "mock" of UDP socket.
- /// This is not associated with any actual socket, and its only
- /// responsibility is to return \c IPPROTO_UDP from \c getProtocol().
- /// The only feasible usage of this socket is for testing so that
- /// the test code can prepare some "UDP data" even without opening any
- /// actual socket.
- ///
- /// This method never throws an exception.
- ///
- /// \return A reference to an \c IOSocket object whose \c getProtocol()
- /// returns \c IPPROTO_UDP.
- static IOSocket& getDummyUDPSocket();
-
- /// \brief Return a non-usable "dummy" TCP socket for testing.
- ///
- /// See \c getDummyUDPSocket(). This method is its TCP version.
- ///
- /// \return A reference to an \c IOSocket object whose \c getProtocol()
- /// returns \c IPPROTO_TCP.
- static IOSocket& getDummyTCPSocket();
-};
-
-} // asiolink
-#endif // __IOSOCKET_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/qid_gen.cc b/src/lib/asiolink/qid_gen.cc
new file mode 100644
index 0000000..4063b39
--- /dev/null
+++ b/src/lib/asiolink/qid_gen.cc
@@ -0,0 +1,54 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// qid_gen defines a generator for query id's
+//
+// We probably want to merge this with the weighted random in the nsas
+// (and other parts where we need randomness, perhaps another thing
+// for a general libutil?)
+
+#include <asiolink/qid_gen.h>
+
+#include <sys/time.h>
+
+namespace {
+ asiolink::QidGenerator qid_generator_instance;
+}
+
+namespace asiolink {
+
+QidGenerator&
+QidGenerator::getInstance() {
+ return (qid_generator_instance);
+}
+
+QidGenerator::QidGenerator() : dist_(0, 65535),
+ vgen_(generator_, dist_)
+{
+ seed();
+}
+
+void
+QidGenerator::seed() {
+ struct timeval tv;
+ gettimeofday(&tv, 0);
+ generator_.seed((tv.tv_sec * 1000000) + tv.tv_usec);
+}
+
+isc::dns::qid_t
+QidGenerator::generateQid() {
+ return (vgen_());
+}
+
+} // namespace asiolink
diff --git a/src/lib/asiolink/qid_gen.h b/src/lib/asiolink/qid_gen.h
new file mode 100644
index 0000000..a5caa17
--- /dev/null
+++ b/src/lib/asiolink/qid_gen.h
@@ -0,0 +1,85 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// qid_gen defines a generator for query id's
+//
+// We probably want to merge this with the weighted random in the nsas
+// (and other parts where we need randomness, perhaps another thing
+// for a general libutil?)
+
+#ifndef __QID_GEN_H
+#define __QID_GEN_H
+
+#include <dns/message.h>
+#include <boost/random/mersenne_twister.hpp>
+#include <boost/random/uniform_int.hpp>
+#include <boost/random/variate_generator.hpp>
+
+
+namespace asiolink {
+
+/// This class generates Qids for outgoing queries
+///
+/// It is implemented as a singleton; the public way to access it
+/// is to call getInstance()->generateQid().
+///
+/// It automatically seeds it with the current time when it is first
+/// used.
+class QidGenerator {
+public:
+ /// \brief Returns the singleton instance of the QidGenerator
+ ///
+ /// Returns a reference to the singleton instance of the generator
+ static QidGenerator& getInstance();
+
+ /// \brief Default constructor
+ ///
+ /// It is recommended that getInstance is used rather than creating
+ /// separate instances of this class.
+ ///
+ /// The constructor automatically seeds the generator with the
+ /// current time.
+ QidGenerator();
+
+ /// Generate a Qid
+ ///
+ /// \return A random Qid
+ isc::dns::qid_t generateQid();
+
+ /// \brief Seeds the QidGenerator (based on the current time)
+ ///
+ /// This is automatically called by the constructor
+ void seed();
+
+private:
+ // "Mersenne Twister: A 623-dimensionally equidistributed
+ // uniform pseudo-random number generator", Makoto Matsumoto and
+ // Takuji Nishimura, ACM Transactions on Modeling and Computer
+ // Simulation: Special Issue on Uniform Random Number Generation,
+ // Vol. 8, No. 1, January 1998, pp. 3-30.
+ //
+ // mt19937 is an implementation of one of the pseudo random
+ // generators described in this paper.
+ boost::mt19937 generator_;
+
+ // For qid's we want a uniform distribution
+ boost::uniform_int<> dist_;
+
+ boost::variate_generator<boost::mt19937&, boost::uniform_int<> > vgen_;
+};
+
+
+} // namespace asiolink
+
+#endif // __QID_GEN_H
diff --git a/src/lib/asiolink/simple_callback.h b/src/lib/asiolink/simple_callback.h
new file mode 100644
index 0000000..ab5deaf
--- /dev/null
+++ b/src/lib/asiolink/simple_callback.h
@@ -0,0 +1,71 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_SIMPLE_CALLBACK_H
+#define __ASIOLINK_SIMPLE_CALLBACK_H 1
+
+#include <asiolink/io_message.h>
+
+namespace asiolink {
+
+/// \brief The \c SimpleCallback class is an abstract base class for a
+/// simple callback function with the signature:
+///
+/// void simpleCallback(const IOMessage& io_message) const;
+///
+/// Specific derived class implementations are hidden within the
+/// implementation. Instances of the derived classes can be called
+/// as functions via the operator() interface. Pointers to these
+/// instances can then be provided to the \c IOService class
+/// via its constructor.
+///
+/// The \c SimpleCallback is expected to be used for basic, generic
+/// tasks such as checking for configuration changes. It may also be
+/// used for testing purposes.
+class SimpleCallback {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ SimpleCallback(const SimpleCallback& source);
+ SimpleCallback& operator=(const SimpleCallback& source);
+protected:
+ /// \brief The default constructor.
+ ///
+ /// This is intentionally defined as \c protected as this base class
+ /// should never be instantiated (except as part of a derived class).
+ SimpleCallback() : self_(this) {}
+public:
+ /// \brief The destructor
+ virtual ~SimpleCallback() {}
+ /// \brief The function operator
+ //@}
+ ///
+ /// This makes its call indirectly via the "self" pointer, ensuring
+ /// that the function ultimately invoked will be the one in the derived
+ /// class.
+ ///
+ /// \param io_message The event message to handle
+ virtual void operator()(const IOMessage& io_message) const {
+ (*self_)(io_message);
+ }
+private:
+ SimpleCallback* self_;
+};
+
+} // namespace asiolink
+#endif // __ASIOLINK_SIMPLE_CALLBACK_H
diff --git a/src/lib/asiolink/tcp_endpoint.h b/src/lib/asiolink/tcp_endpoint.h
new file mode 100644
index 0000000..158ca4a
--- /dev/null
+++ b/src/lib/asiolink/tcp_endpoint.h
@@ -0,0 +1,113 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __TCP_ENDPOINT_H
+#define __TCP_ENDPOINT_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_endpoint.h>
+
+namespace asiolink {
+
+/// \brief The \c TCPEndpoint class is a concrete derived class of
+/// \c IOEndpoint that represents an endpoint of a TCP packet.
+///
+/// Other notes about \c TCPEndpoint applies to this class, too.
+class TCPEndpoint : public IOEndpoint {
+public:
+ ///
+ /// \name Constructors and Destructor.
+ ///
+ //@{
+
+ /// \brief Default Constructor
+ ///
+ /// Creates an internal endpoint. This is expected to be set by some
+ /// external call.
+ TCPEndpoint() :
+ asio_endpoint_placeholder_(new asio::ip::tcp::endpoint()),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
+ /// \brief Constructor from a pair of address and port.
+ ///
+ /// \param address The IP address of the endpoint.
+ /// \param port The TCP port number of the endpoint.
+ TCPEndpoint(const IOAddress& address, const unsigned short port) :
+ asio_endpoint_placeholder_(
+ new asio::ip::tcp::endpoint(asio::ip::address::from_string(address.toText()),
+ port)),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
+ /// \brief Constructor from an ASIO TCP endpoint.
+ ///
+ /// This constructor is designed to be an efficient wrapper for the
+ /// corresponding ASIO class, \c tcp::endpoint.
+ ///
+ /// \param asio_endpoint The ASIO representation of the TCP endpoint.
+ TCPEndpoint(asio::ip::tcp::endpoint& asio_endpoint) :
+ asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
+ {}
+
+ /// \brief Constructor from an ASIO TCP endpoint.
+ ///
+ /// This constructor is designed to be an efficient wrapper for the
+ /// corresponding ASIO class, \c tcp::endpoint.
+ ///
+ /// \param asio_endpoint The ASIO representation of the TCP endpoint.
+ TCPEndpoint(const asio::ip::tcp::endpoint& asio_endpoint) :
+ asio_endpoint_placeholder_(new asio::ip::tcp::endpoint(asio_endpoint)),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
+ /// \brief The destructor.
+ virtual ~TCPEndpoint() { delete asio_endpoint_placeholder_; }
+ //@}
+
+ virtual IOAddress getAddress() const {
+ return (asio_endpoint_.address());
+ }
+
+ virtual uint16_t getPort() const {
+ return (asio_endpoint_.port());
+ }
+
+ virtual short getProtocol() const {
+ return (asio_endpoint_.protocol().protocol());
+ }
+
+ virtual short getFamily() const {
+ return (asio_endpoint_.protocol().family());
+ }
+
+ // This is not part of the exosed IOEndpoint API but allows
+ // direct access to the ASIO implementation of the endpoint
+ inline const asio::ip::tcp::endpoint& getASIOEndpoint() const {
+ return (asio_endpoint_);
+ }
+ inline asio::ip::tcp::endpoint& getASIOEndpoint() {
+ return (asio_endpoint_);
+ }
+
+private:
+ asio::ip::tcp::endpoint* asio_endpoint_placeholder_;
+ asio::ip::tcp::endpoint& asio_endpoint_;
+};
+
+} // namespace asiolink
+#endif // __TCP_ENDPOINT_H
diff --git a/src/lib/asiolink/tcp_server.cc b/src/lib/asiolink/tcp_server.cc
new file mode 100644
index 0000000..3e0cdb4
--- /dev/null
+++ b/src/lib/asiolink/tcp_server.cc
@@ -0,0 +1,229 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+#include <errno.h>
+
+#include <boost/shared_array.hpp>
+
+#include <log/dummylog.h>
+
+#include <asio.hpp>
+#include <asiolink/dummy_io_cb.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+#include <asiolink/tcp_server.h>
+
+
+using namespace asio;
+using asio::ip::udp;
+using asio::ip::tcp;
+
+using namespace std;
+using namespace isc::dns;
+
+namespace asiolink {
+
+/// The following functions implement the \c TCPServer class.
+///
+/// The constructor
+TCPServer::TCPServer(io_service& io_service,
+ const ip::address& addr, const uint16_t port,
+ const SimpleCallback* checkin,
+ const DNSLookup* lookup,
+ const DNSAnswer* answer) :
+ io_(io_service), done_(false), stopped_by_hand_(false),
+ checkin_callback_(checkin), lookup_callback_(lookup),
+ answer_callback_(answer)
+{
+ tcp::endpoint endpoint(addr, port);
+ acceptor_.reset(new tcp::acceptor(io_service));
+ acceptor_->open(endpoint.protocol());
+ // Set v6-only (we use a separate instantiation for v4,
+ // otherwise asio will bind to both v4 and v6
+ if (addr.is_v6()) {
+ acceptor_->set_option(ip::v6_only(true));
+ }
+ acceptor_->set_option(tcp::acceptor::reuse_address(true));
+ acceptor_->bind(endpoint);
+ acceptor_->listen();
+}
+
+void
+TCPServer::operator()(error_code ec, size_t length) {
+ /// Because the coroutine reentry block is implemented as
+ /// a switch statement, inline variable declarations are not
+ /// permitted. Certain variables used below can be declared here.
+
+ /// If user has stopped the server, we won't enter the
+ /// coroutine body, just return
+ if (stopped_by_hand_) {
+ return;
+ }
+
+ boost::array<const_buffer,2> bufs;
+ OutputBuffer lenbuf(TCP_MESSAGE_LENGTHSIZE);
+
+ CORO_REENTER (this) {
+ do {
+ /// Create a socket to listen for connections
+ socket_.reset(new tcp::socket(acceptor_->get_io_service()));
+
+ /// Wait for new connections. In the event of non-fatal error,
+ /// try again
+ do {
+ CORO_YIELD acceptor_->async_accept(*socket_, *this);
+ // Abort on fatal errors
+ // TODO: Log error?
+ if (ec) {
+ using namespace asio::error;
+ if (ec.value() != would_block && ec.value() != try_again &&
+ ec.value() != connection_aborted &&
+ ec.value() != interrupted) {
+ return;
+ }
+ }
+ } while (ec);
+
+ /// Fork the coroutine by creating a copy of this one and
+ /// scheduling it on the ASIO service queue. The parent
+ /// will continue listening for DNS connections while the
+ /// handles the one that has just arrived.
+ CORO_FORK io_.post(TCPServer(*this));
+ } while (is_parent());
+
+ /// Instantiate the data buffer that will be used by the
+ /// asynchronous read call.
+ data_.reset(new char[MAX_LENGTH]);
+
+ /// Read the message, in two parts. First, the message length:
+ CORO_YIELD async_read(*socket_, asio::buffer(data_.get(),
+ TCP_MESSAGE_LENGTHSIZE), *this);
+ if (ec) {
+ CORO_YIELD return;
+ }
+
+ /// Now read the message itself. (This is done in a different scope
+ /// to allow inline variable declarations.)
+ CORO_YIELD {
+ InputBuffer dnsbuffer(data_.get(), length);
+ uint16_t msglen = dnsbuffer.readUint16();
+ async_read(*socket_, asio::buffer(data_.get(), msglen), *this);
+ }
+
+ if (ec) {
+ CORO_YIELD return;
+ }
+
+ // Create an \c IOMessage object to store the query.
+ //
+ // (XXX: It would be good to write a factory function
+ // that would quickly generate an IOMessage object without
+ // all these calls to "new".)
+ peer_.reset(new TCPEndpoint(socket_->remote_endpoint()));
+
+ // The TCP socket class has been extended with asynchronous functions
+ // and takes as a template parameter a completion callback class. As
+ // TCPServer does not use these extended functions (only those defined
+ // in the IOSocket base class) - but needs a TCPSocket to get hold of
+ // the underlying Boost TCP socket - DummyIOCallback is used. This
+ // provides the appropriate operator() but is otherwise functionless.
+ iosock_.reset(new TCPSocket<DummyIOCallback>(*socket_));
+ io_message_.reset(new IOMessage(data_.get(), length, *iosock_, *peer_));
+ bytes_ = length;
+
+ // Perform any necessary operations prior to processing the incoming
+ // packet (e.g., checking for queued configuration messages).
+ //
+ // (XXX: it may be a performance issue to have this called for
+ // every single incoming packet; we may wish to throttle it somehow
+ // in the future.)
+ if (checkin_callback_ != NULL) {
+ (*checkin_callback_)(*io_message_);
+ }
+
+ // If we don't have a DNS Lookup provider, there's no point in
+ // continuing; we exit the coroutine permanently.
+ if (lookup_callback_ == NULL) {
+ CORO_YIELD return;
+ }
+
+ // Reset or instantiate objects that will be needed by the
+ // DNS lookup and the write call.
+ respbuf_.reset(new OutputBuffer(0));
+ query_message_.reset(new Message(Message::PARSE));
+ answer_message_.reset(new Message(Message::RENDER));
+
+ // Schedule a DNS lookup, and yield. When the lookup is
+ // finished, the coroutine will resume immediately after
+ // this point.
+ CORO_YIELD io_.post(AsyncLookup<TCPServer>(*this));
+
+ // The 'done_' flag indicates whether we have an answer
+ // to send back. If not, exit the coroutine permanently.
+ if (!done_) {
+ CORO_YIELD return;
+ }
+
+ // Call the DNS answer provider to render the answer into
+ // wire format
+ (*answer_callback_)(*io_message_, query_message_,
+ answer_message_, respbuf_);
+
+ // Set up the response, beginning with two length bytes.
+ lenbuf.writeUint16(respbuf_->getLength());
+ bufs[0] = buffer(lenbuf.getData(), lenbuf.getLength());
+ bufs[1] = buffer(respbuf_->getData(), respbuf_->getLength());
+
+ // Begin an asynchronous send, and then yield. When the
+ // send completes, we will resume immediately after this point
+ // (though we have nothing further to do, so the coroutine
+ // will simply exit at that time).
+ CORO_YIELD async_write(*socket_, bufs, *this);
+ }
+}
+
+/// Call the DNS lookup provider. (Expected to be called by the
+/// AsyncLookup<TCPServer> handler.)
+void
+TCPServer::asyncLookup() {
+ (*lookup_callback_)(*io_message_, query_message_,
+ answer_message_, respbuf_, this);
+}
+
+void TCPServer::stop() {
+ // server should not be stopped twice
+ if (stopped_by_hand_) {
+ return;
+ }
+
+ stopped_by_hand_ = true;
+ acceptor_->close();
+ socket_->close();
+}
+/// Post this coroutine on the ASIO service queue so that it will
+/// resume processing where it left off. The 'done' parameter indicates
+/// whether there is an answer to return to the client.
+void
+TCPServer::resume(const bool done) {
+ done_ = done;
+ io_.post(*this);
+}
+
+} // namespace asiolink
+
diff --git a/src/lib/asiolink/tcp_server.h b/src/lib/asiolink/tcp_server.h
new file mode 100644
index 0000000..9df335d
--- /dev/null
+++ b/src/lib/asiolink/tcp_server.h
@@ -0,0 +1,123 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __TCP_SERVER_H
+#define __TCP_SERVER_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <boost/shared_array.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <asiolink/asiolink.h>
+#include <coroutine.h>
+
+
+namespace asiolink {
+
+/// \brief A TCP-specific \c DNSServer object.
+///
+/// This class inherits from both \c DNSServer and from \c coroutine,
+/// defined in coroutine.h.
+class TCPServer : public virtual DNSServer, public virtual coroutine {
+public:
+ explicit TCPServer(asio::io_service& io_service,
+ const asio::ip::address& addr, const uint16_t port,
+ const SimpleCallback* checkin = NULL,
+ const DNSLookup* lookup = NULL,
+ const DNSAnswer* answer = NULL);
+
+ void operator()(asio::error_code ec = asio::error_code(),
+ size_t length = 0);
+ void asyncLookup();
+ void stop();
+ void resume(const bool done);
+ bool hasAnswer() { return (done_); }
+ int value() { return (get_value()); }
+
+ DNSServer* clone() {
+ TCPServer* s = new TCPServer(*this);
+ return (s);
+ }
+
+private:
+ enum { MAX_LENGTH = 65535 };
+ static const size_t TCP_MESSAGE_LENGTHSIZE = 2;
+
+ // The ASIO service object
+ asio::io_service& io_;
+
+ // Class member variables which are dynamic, and changes to which
+ // need to accessible from both sides of a coroutine fork or from
+ // outside of the coroutine (i.e., from an asynchronous I/O call),
+ // should be declared here as pointers and allocated in the
+ // constructor or in the coroutine. This allows state information
+ // to persist when an individual copy of the coroutine falls out
+ // scope while waiting for an event, *so long as* there is another
+ // object that is referencing the same data. As a side-benefit, using
+ // pointers also reduces copy overhead for coroutine objects.
+ //
+ // Note: Currently these objects are allocated by "new" in the
+ // constructor, or in the function operator while processing a query.
+ // Repeated allocations from the heap for every incoming query is
+ // clearly a performance issue; this must be optimized in the future.
+ // The plan is to have a structure pre-allocate several "server state"
+ // objects which can be pulled off a free list and placed on an in-use
+ // list whenever a query comes in. This will serve the dual purpose
+ // of improving performance and guaranteeing that state information
+ // will *not* be destroyed when any one instance of the coroutine
+ // falls out of scope while waiting for an event.
+ //
+ // An ASIO acceptor object to handle new connections. Created in
+ // the constructor.
+ boost::shared_ptr<asio::ip::tcp::acceptor> acceptor_;
+
+ // Socket used to for listen for queries. Created in the
+ // constructor and stored in a shared_ptr because socket objects
+ // are not copyable.
+ boost::shared_ptr<asio::ip::tcp::socket> socket_;
+
+ // The buffer into which the response is written
+ boost::shared_ptr<isc::dns::OutputBuffer> respbuf_;
+
+ // \c IOMessage and \c Message objects to be passed to the
+ // DNS lookup and answer providers
+ boost::shared_ptr<asiolink::IOMessage> io_message_;
+ isc::dns::MessagePtr query_message_;
+ isc::dns::MessagePtr answer_message_;
+
+ // The buffer into which the query packet is written
+ boost::shared_array<char>data_;
+
+ // State information that is entirely internal to a given instance
+ // of the coroutine can be declared here.
+ size_t bytes_;
+ bool done_;
+
+ // whether user has stopped the server
+ bool stopped_by_hand_;
+
+ // Callback functions provided by the caller
+ const SimpleCallback* checkin_callback_;
+ const DNSLookup* lookup_callback_;
+ const DNSAnswer* answer_callback_;
+
+ boost::shared_ptr<IOEndpoint> peer_;
+ boost::shared_ptr<IOSocket> iosock_;
+};
+
+} // namespace asiolink
+#endif // __TCP_SERVER_H
diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h
new file mode 100644
index 0000000..e6e0863
--- /dev/null
+++ b/src/lib/asiolink/tcp_socket.h
@@ -0,0 +1,416 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __TCP_SOCKET_H
+#define __TCP_SOCKET_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <log/dummylog.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+
+#include <boost/bind.hpp>
+#include <boost/numeric/conversion/cast.hpp>
+
+#include <config.h>
+
+#include <dns/buffer.h>
+
+#include <asiolink/asiolink_utilities.h>
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+
+namespace asiolink {
+
+/// \brief Buffer Too Large
+///
+/// Thrown on an attempt to send a buffer > 64k
+class BufferTooLarge : public IOError {
+public:
+ BufferTooLarge(const char* file, size_t line, const char* what) :
+ IOError(file, line, what) {}
+};
+
+/// \brief The \c TCPSocket class is a concrete derived class of \c IOAsioSocket
+/// that represents a TCP socket.
+///
+/// \param C Callback type
+template <typename C>
+class TCPSocket : public IOAsioSocket<C> {
+private:
+ /// \brief Class is non-copyable
+ TCPSocket(const TCPSocket&);
+ TCPSocket& operator=(const TCPSocket&);
+
+public:
+
+ /// \brief Constructor from an ASIO TCP socket.
+ ///
+ /// \param socket The ASIO representation of the TCP socket. It is assumed
+ /// that the caller will open and close the socket, so these
+ /// operations are a no-op for that socket.
+ TCPSocket(asio::ip::tcp::socket& socket);
+
+ /// \brief Constructor
+ ///
+ /// Used when the TCPSocket is being asked to manage its own internal
+ /// socket. In this case, the open() and close() methods are used.
+ ///
+ /// \param service I/O Service object used to manage the socket.
+ TCPSocket(IOService& service);
+
+ /// \brief Destructor
+ virtual ~TCPSocket();
+
+ /// \brief Return file descriptor of underlying socket
+ virtual int getNative() const {
+ return (socket_.native());
+ }
+
+ /// \brief Return protocol of socket
+ virtual int getProtocol() const {
+ return (IPPROTO_TCP);
+ }
+
+ /// \brief Is "open()" synchronous?
+ ///
+ /// Indicates that the opening of a TCP socket is asynchronous.
+ virtual bool isOpenSynchronous() const {
+ return (false);
+ }
+
+ /// \brief Open Socket
+ ///
+ /// Opens the TCP socket. This is an asynchronous operation, completion of
+ /// which will be signalled via a call to the callback function.
+ ///
+ /// \param endpoint Endpoint to which the socket will connect.
+ /// \param callback Callback object.
+ virtual void open(const IOEndpoint* endpoint, C& callback);
+
+ /// \brief Send Asynchronously
+ ///
+ /// Calls the underlying socket's async_send() method to send a packet of
+ /// data asynchronously to the remote endpoint. The callback will be called
+ /// on completion.
+ ///
+ /// \param data Data to send
+ /// \param length Length of data to send
+ /// \param endpoint Target of the send. (Unused for a TCP socket because
+ /// that was determined when the connection was opened.)
+ /// \param callback Callback object.
+ virtual void asyncSend(const void* data, size_t length,
+ const IOEndpoint* endpoint, C& callback);
+
+ /// \brief Receive Asynchronously
+ ///
+ /// Calls the underlying socket's async_receive() method to read a packet
+ /// of data from a remote endpoint. Arrival of the data is signalled via a
+ /// call to the callback function.
+ ///
+ /// \param data Buffer to receive incoming message
+ /// \param length Length of the data buffer
+ /// \param offset Offset into buffer where data is to be put
+ /// \param endpoint Source of the communication
+ /// \param callback Callback object
+ virtual void asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback);
+
+ /// \brief Process received data packet
+ ///
+ /// See the description of IOAsioSocket::receiveComplete for a complete
+ /// description of this method.
+ ///
+ /// \param staging Pointer to the start of the staging buffer.
+ /// \param length Amount of data in the staging buffer.
+ /// \param cumulative Amount of data received before the staging buffer is
+ /// processed.
+ /// \param offset Unused.
+ /// \param expected unused.
+ /// \param outbuff Output buffer. Data in the staging buffer is be copied
+ /// to this output buffer in the call.
+ ///
+ /// \return Always true
+ virtual bool processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::dns::OutputBufferPtr& outbuff);
+
+ /// \brief Cancel I/O On Socket
+ virtual void cancel();
+
+ /// \brief Close socket
+ virtual void close();
+
+
+private:
+ // Two variables to hold the socket - a socket and a pointer to it. This
+ // handles the case where a socket is passed to the TCPSocket on
+ // construction, or where it is asked to manage its own socket.
+ asio::ip::tcp::socket* socket_ptr_; ///< Pointer to own socket
+ asio::ip::tcp::socket& socket_; ///< Socket
+ bool isopen_; ///< true when socket is open
+
+ // TODO: Remove temporary buffer
+ // The current implementation copies the buffer passed to asyncSend() into
+ // a temporary buffer and precedes it with a two-byte count field. As
+ // ASIO should really be just about sending and receiving data, the TCP
+ // code should not do this. If the protocol using this requires a two-byte
+ // count, it should add it before calling this code. (This may be best
+ // achieved by altering isc::dns::buffer to have pairs of methods:
+ // getLength()/getTCPLength(), getData()/getTCPData(), with the getTCPXxx()
+ // methods taking into account a two-byte count field.)
+ //
+ // The option of sending the data in two operations, the count followed by
+ // the data was discounted as that would lead to two callbacks which would
+ // cause problems with the stackless coroutine code.
+ isc::dns::OutputBufferPtr send_buffer_; ///< Send buffer
+};
+
+// Constructor - caller manages socket
+
+template <typename C>
+TCPSocket<C>::TCPSocket(asio::ip::tcp::socket& socket) :
+ socket_ptr_(NULL), socket_(socket), isopen_(true), send_buffer_()
+{
+}
+
+// Constructor - create socket on the fly
+
+template <typename C>
+TCPSocket<C>::TCPSocket(IOService& service) :
+ socket_ptr_(new asio::ip::tcp::socket(service.get_io_service())),
+ socket_(*socket_ptr_), isopen_(false)
+{
+}
+
+// Destructor. Only delete the socket if we are managing it.
+
+template <typename C>
+TCPSocket<C>::~TCPSocket()
+{
+ delete socket_ptr_;
+}
+
+// Open the socket.
+
+template <typename C> void
+TCPSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
+
+ // Ignore opens on already-open socket. Don't throw a failure because
+ // of uncertainties as to what precedes whan when using asynchronous I/O.
+ // At also allows us a treat a passed-in socket as a self-managed socket.
+ if (!isopen_) {
+ if (endpoint->getFamily() == AF_INET) {
+ socket_.open(asio::ip::tcp::v4());
+ }
+ else {
+ socket_.open(asio::ip::tcp::v6());
+ }
+ isopen_ = true;
+
+ // Set options on the socket:
+
+ // Reuse address - allow the socket to bind to a port even if the port
+ // is in the TIMED_WAIT state.
+ socket_.set_option(asio::socket_base::reuse_address(true));
+ }
+
+ // Upconvert to a TCPEndpoint. We need to do this because although
+ // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not
+ // contain a method for getting at the underlying endpoint type - that is in
+ /// the derived class and the two classes differ on return type.
+ assert(endpoint->getProtocol() == IPPROTO_TCP);
+ const TCPEndpoint* tcp_endpoint =
+ static_cast<const TCPEndpoint*>(endpoint);
+
+ // Connect to the remote endpoint. On success, the handler will be
+ // called (with one argument - the length argument will default to
+ // zero).
+ socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback);
+}
+
+// Send a message. Should never do this if the socket is not open, so throw
+// an exception if this is the case.
+
+template <typename C> void
+TCPSocket<C>::asyncSend(const void* data, size_t length,
+ const IOEndpoint*, C& callback)
+{
+ if (isopen_) {
+
+ // Need to copy the data into a temporary buffer and precede it with
+ // a two-byte count field.
+ // TODO: arrange for the buffer passed to be preceded by the count
+ try {
+ // Ensure it fits into 16 bits
+ uint16_t count = boost::numeric_cast<uint16_t>(length);
+
+ // Copy data into a buffer preceded by the count field.
+ send_buffer_.reset(new isc::dns::OutputBuffer(length + 2));
+ send_buffer_->writeUint16(count);
+ send_buffer_->writeData(data, length);
+
+ // ... and send it
+ socket_.async_send(asio::buffer(send_buffer_->getData(),
+ send_buffer_->getLength()), callback);
+ } catch (boost::numeric::bad_numeric_cast& e) {
+ isc_throw(BufferTooLarge,
+ "attempt to send buffer larger than 64kB");
+ }
+
+ } else {
+ isc_throw(SocketNotOpen,
+ "attempt to send on a TCP socket that is not open");
+ }
+}
+
+// Receive a message. Note that the "offset" argument is used as an index
+// into the buffer in order to decide where to put the data. It is up to the
+// caller to initialize the data to zero
+template <typename C> void
+TCPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback)
+{
+ if (isopen_) {
+ // Upconvert to a TCPEndpoint. We need to do this because although
+ // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
+ // does not contain a method for getting at the underlying endpoint
+ // type - that is in the derived class and the two classes differ on
+ // return type.
+ assert(endpoint->getProtocol() == IPPROTO_TCP);
+ TCPEndpoint* tcp_endpoint = static_cast<TCPEndpoint*>(endpoint);
+
+ // Write the endpoint details from the communications link. Ideally
+ // we should make IOEndpoint assignable, but this runs in to all sorts
+ // of problems concerning the management of the underlying Boost
+ // endpoint (e.g. if it is not self-managed, is the copied one
+ // self-managed?) The most pragmatic solution is to let Boost take care
+ // of everything and copy details of the underlying endpoint.
+ tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint();
+
+ // Ensure we can write into the buffer and if so, set the pointer to
+ // where the data will be written.
+ if (offset >= length) {
+ isc_throw(BufferOverflow, "attempt to read into area beyond end of "
+ "TCP receive buffer");
+ }
+ void* buffer_start = static_cast<void*>(static_cast<uint8_t*>(data) + offset);
+
+ // ... and kick off the read.
+ socket_.async_receive(asio::buffer(buffer_start, length - offset), callback);
+
+ } else {
+ isc_throw(SocketNotOpen,
+ "attempt to receive from a TCP socket that is not open");
+ }
+}
+
+// Is the receive complete?
+
+template <typename C> bool
+TCPSocket<C>::processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::dns::OutputBufferPtr& outbuff)
+{
+ // Point to the data in the staging buffer and note how much there is.
+ const uint8_t* data = static_cast<const uint8_t*>(staging);
+ size_t data_length = length;
+
+ // Is the number is "expected" valid? It won't be unless we have received
+ // at least two bytes of data in total for this set of receives.
+ if (cumulative < 2) {
+
+ // "expected" is not valid. Did this read give us enough data to
+ // work it out?
+ cumulative += length;
+ if (cumulative < 2) {
+
+ // Nope, still not valid. This must have been the first packet and
+ // was only one byte long. Tell the fetch code to read the next
+ // packet into the staging buffer beyond the data that is already
+ // there so that the next time we are called we have a complete
+ // TCP count.
+ offset = cumulative;
+ return (false);
+ }
+
+ // Have enough data to interpret the packet count, so do so now.
+ expected = readUint16(data);
+
+ // We have two bytes less of data to process. Point to the start of the
+ // data and adjust the packet size. Note that at this point,
+ // "cumulative" is the true amount of data in the staging buffer, not
+ // "length".
+ data += 2;
+ data_length = cumulative - 2;
+ } else {
+
+ // Update total amount of data received.
+ cumulative += length;
+ }
+
+ // Regardless of anything else, the next read goes into the start of the
+ // staging buffer.
+ offset = 0;
+
+ // Work out how much data we still have to put in the output buffer. (This
+ // could be zero if we have just interpreted the TCP count and that was
+ // set to zero.)
+ if (expected >= outbuff->getLength()) {
+
+ // Still need data in the output packet. Copy what we can from the
+ // staging buffer to the output buffer.
+ size_t copy_amount = std::min(expected - outbuff->getLength(), data_length);
+ outbuff->writeData(data, copy_amount);
+ }
+
+ // We can now say if we have all the data.
+ return (expected == outbuff->getLength());
+}
+
+// Cancel I/O on the socket. No-op if the socket is not open.
+
+template <typename C> void
+TCPSocket<C>::cancel() {
+ if (isopen_) {
+ socket_.cancel();
+ }
+}
+
+// Close the socket down. Can only do this if the socket is open and we are
+// managing it ourself.
+
+template <typename C> void
+TCPSocket<C>::close() {
+ if (isopen_ && socket_ptr_) {
+ socket_.close();
+ isopen_ = false;
+ }
+}
+
+} // namespace asiolink
+
+#endif // __TCP_SOCKET_H
diff --git a/src/lib/asiolink/tcpdns.cc b/src/lib/asiolink/tcpdns.cc
deleted file mode 100644
index c00b87a..0000000
--- a/src/lib/asiolink/tcpdns.cc
+++ /dev/null
@@ -1,194 +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 <config.h>
-
-#include <unistd.h> // for some IPC/network system calls
-#include <sys/socket.h>
-#include <netinet/in.h>
-
-#include <asio.hpp>
-#include <asio/ip/address.hpp>
-
-#include <boost/array.hpp>
-#include <boost/shared_ptr.hpp>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-
-#include <asiolink.h>
-#include <internal/coroutine.h>
-#include <internal/tcpdns.h>
-
-using namespace asio;
-using asio::ip::udp;
-using asio::ip::tcp;
-
-using namespace std;
-using namespace isc::dns;
-
-namespace asiolink {
-/// The following functions implement the \c UDPServer class.
-///
-/// The constructor
-TCPServer::TCPServer(io_service& io_service,
- const ip::address& addr, const uint16_t port,
- const SimpleCallback* checkin,
- const DNSLookup* lookup,
- const DNSAnswer* answer) :
- io_(io_service), done_(false),
- checkin_callback_(checkin), lookup_callback_(lookup),
- answer_callback_(answer)
-{
- tcp::endpoint endpoint(addr, port);
- acceptor_.reset(new tcp::acceptor(io_service));
- acceptor_->open(endpoint.protocol());
- // Set v6-only (we use a separate instantiation for v4,
- // otherwise asio will bind to both v4 and v6
- if (addr.is_v6()) {
- acceptor_->set_option(ip::v6_only(true));
- }
- acceptor_->set_option(tcp::acceptor::reuse_address(true));
- acceptor_->bind(endpoint);
- acceptor_->listen();
-}
-
-void
-TCPServer::operator()(error_code ec, size_t length) {
- /// Because the coroutine reeentry block is implemented as
- /// a switch statement, inline variable declarations are not
- /// permitted. Certain variables used below can be declared here.
- boost::array<const_buffer,2> bufs;
- OutputBuffer lenbuf(TCP_MESSAGE_LENGTHSIZE);
-
- CORO_REENTER (this) {
- do {
- /// Create a socket to listen for connections
- socket_.reset(new tcp::socket(acceptor_->get_io_service()));
-
- /// Wait for new connections. In the event of error,
- /// try again
- do {
- CORO_YIELD acceptor_->async_accept(*socket_, *this);
- } while (!ec);
-
- /// Fork the coroutine by creating a copy of this one and
- /// scheduling it on the ASIO service queue. The parent
- /// will continue listening for DNS connections while the
- /// handles the one that has just arrived.
- CORO_FORK io_.post(TCPServer(*this));
- } while (is_parent());
-
- /// Instantiate the data buffer that will be used by the
- /// asynchronous read call.
- data_.reset(new char[MAX_LENGTH]);
-
- /// Read the message, in two parts. First, the message length:
- CORO_YIELD async_read(*socket_, asio::buffer(data_.get(),
- TCP_MESSAGE_LENGTHSIZE), *this);
- if (ec) {
- CORO_YIELD return;
- }
-
- /// Now read the message itself. (This is done in a different scope
- /// to allow inline variable declarations.)
- CORO_YIELD {
- InputBuffer dnsbuffer((const void *) data_.get(), length);
- uint16_t msglen = dnsbuffer.readUint16();
- async_read(*socket_, asio::buffer(data_.get(), msglen), *this);
- }
-
- if (ec) {
- CORO_YIELD return;
- }
-
- // Create an \c IOMessage object to store the query.
- //
- // (XXX: It would be good to write a factory function
- // that would quickly generate an IOMessage object without
- // all these calls to "new".)
- peer_.reset(new TCPEndpoint(socket_->remote_endpoint()));
- iosock_.reset(new TCPSocket(*socket_));
- io_message_.reset(new IOMessage(data_.get(), length, *iosock_, *peer_));
- bytes_ = length;
-
- // Perform any necessary operations prior to processing the incoming
- // packet (e.g., checking for queued configuration messages).
- //
- // (XXX: it may be a performance issue to have this called for
- // every single incoming packet; we may wish to throttle it somehow
- // in the future.)
- if (checkin_callback_ != NULL) {
- (*checkin_callback_)(*io_message_);
- }
-
- // If we don't have a DNS Lookup provider, there's no point in
- // continuing; we exit the coroutine permanently.
- if (lookup_callback_ == NULL) {
- CORO_YIELD return;
- }
-
- // Reset or instantiate objects that will be needed by the
- // DNS lookup and the write call.
- respbuf_.reset(new OutputBuffer(0));
- query_message_.reset(new Message(Message::PARSE));
- answer_message_.reset(new Message(Message::RENDER));
-
- // Schedule a DNS lookup, and yield. When the lookup is
- // finished, the coroutine will resume immediately after
- // this point.
- CORO_YIELD io_.post(AsyncLookup<TCPServer>(*this));
-
- // The 'done_' flag indicates whether we have an answer
- // to send back. If not, exit the coroutine permanently.
- if (!done_) {
- CORO_YIELD return;
- }
-
- // Call the DNS answer provider to render the answer into
- // wire format
- (*answer_callback_)(*io_message_, query_message_,
- answer_message_, respbuf_);
-
- // Set up the response, beginning with two length bytes.
- lenbuf.writeUint16(respbuf_->getLength());
- bufs[0] = buffer(lenbuf.getData(), lenbuf.getLength());
- bufs[1] = buffer(respbuf_->getData(), respbuf_->getLength());
-
- // Begin an asynchronous send, and then yield. When the
- // send completes, we will resume immediately after this point
- // (though we have nothing further to do, so the coroutine
- // will simply exit at that time).
- CORO_YIELD async_write(*socket_, bufs, *this);
- }
-}
-
-/// Call the DNS lookup provider. (Expected to be called by the
-/// AsyncLookup<TCPServer> handler.)
-void
-TCPServer::asyncLookup() {
- (*lookup_callback_)(*io_message_, query_message_,
- answer_message_, respbuf_, this);
-}
-
-/// Post this coroutine on the ASIO service queue so that it will
-/// resume processing where it left off. The 'done' parameter indicates
-/// whether there is an answer to return to the client.
-void
-TCPServer::resume(const bool done) {
- done_ = done;
- io_.post(*this);
-}
-
-}
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index e301fb2..000e6f2 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -15,24 +15,43 @@ CLEANFILES = *.gcno *.gcda
TESTS =
if HAVE_GTEST
TESTS += run_unittests
-run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.h
run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
-run_unittests_SOURCES += asiolink_unittest.cc
-run_unittests_SOURCES += run_unittests.cc
+run_unittests_SOURCES += asiolink_utilities_unittest.cc
+run_unittests_SOURCES += io_address_unittest.cc
+run_unittests_SOURCES += io_endpoint_unittest.cc
+run_unittests_SOURCES += io_fetch_unittest.cc
+run_unittests_SOURCES += io_socket_unittest.cc
+run_unittests_SOURCES += io_service_unittest.cc
+run_unittests_SOURCES += interval_timer_unittest.cc
+run_unittests_SOURCES += tcp_endpoint_unittest.cc
+run_unittests_SOURCES += tcp_socket_unittest.cc
+run_unittests_SOURCES += udp_endpoint_unittest.cc
+run_unittests_SOURCES += udp_socket_unittest.cc
+run_unittests_SOURCES += qid_gen_unittest.cc
+
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(LOG4CXX_LDFLAGS)
-run_unittests_LDADD = $(GTEST_LDADD)
+
+run_unittests_LDADD = $(GTEST_LDADD)
run_unittests_LDADD += $(SQLITE_LIBS)
-run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
-run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS)
run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
if USE_GXX
run_unittests_CXXFLAGS += -Wno-unused-parameter
endif
+if USE_CLANGPP
+# Same for clang++, but we need to turn off -Werror completely.
+run_unittests_CXXFLAGS += -Wno-error
+endif
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/asiolink/tests/asiolink_unittest.cc b/src/lib/asiolink/tests/asiolink_unittest.cc
deleted file mode 100644
index 2044d50..0000000
--- a/src/lib/asiolink/tests/asiolink_unittest.cc
+++ /dev/null
@@ -1,1207 +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 <config.h>
-
-#include <sys/socket.h>
-#include <sys/time.h>
-
-#include <string.h>
-
-#include <boost/lexical_cast.hpp>
-#include <boost/bind.hpp>
-#include <boost/date_time/posix_time/posix_time_types.hpp>
-
-#include <gtest/gtest.h>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/tests/unittest_util.h>
-#include <dns/rcode.h>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-
-// IMPORTANT: We shouldn't directly use ASIO definitions in this test.
-// In particular, we must not include asio.hpp in this file.
-// The asiolink module is primarily intended to be a wrapper that hide the
-// details of the underlying implementations. We need to test the wrapper
-// level behaviors. In addition, some compilers reject to compile this file
-// if we include asio.hpp unless we specify a special compiler option.
-// If we need to test something at the level of underlying ASIO and need
-// their definition, that test should go to asiolink/internal/tests.
-#include <asiolink/asiolink.h>
-#include <asiolink/iosocket.h>
-
-using isc::UnitTestUtil;
-using namespace std;
-using namespace asiolink;
-using namespace isc::dns;
-
-namespace {
-const char* const TEST_SERVER_PORT = "53535";
-const char* const TEST_CLIENT_PORT = "53536";
-const char* const TEST_IPV6_ADDR = "::1";
-const char* const TEST_IPV4_ADDR = "127.0.0.1";
-// This data is intended to be valid as a DNS/TCP-like message: the first
-// two octets encode the length of the rest of the data. This is crucial
-// for the tests below.
-const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
-// TODO: Consider this margin
-const boost::posix_time::time_duration TIMER_MARGIN_MSEC =
- boost::posix_time::milliseconds(50);
-
-TEST(IOAddressTest, fromText) {
- IOAddress io_address_v4("192.0.2.1");
- EXPECT_EQ("192.0.2.1", io_address_v4.toText());
-
- IOAddress io_address_v6("2001:db8::1234");
- EXPECT_EQ("2001:db8::1234", io_address_v6.toText());
-
- // bogus IPv4 address-like input
- EXPECT_THROW(IOAddress("192.0.2.2.1"), IOError);
-
- // bogus IPv4 address-like input: out-of-range octet
- EXPECT_THROW(IOAddress("192.0.2.300"), IOError);
-
- // bogus IPv6 address-like input
- EXPECT_THROW(IOAddress("2001:db8:::1234"), IOError);
-
- // bogus IPv6 address-like input
- EXPECT_THROW(IOAddress("2001:db8::efgh"), IOError);
-}
-
-TEST(IOAddressTest, Equality) {
- EXPECT_TRUE(IOAddress("192.0.2.1") == IOAddress("192.0.2.1"));
- EXPECT_FALSE(IOAddress("192.0.2.1") != IOAddress("192.0.2.1"));
-
- EXPECT_TRUE(IOAddress("192.0.2.1") != IOAddress("192.0.2.2"));
- EXPECT_FALSE(IOAddress("192.0.2.1") == IOAddress("192.0.2.2"));
-
- EXPECT_TRUE(IOAddress("2001:db8::12") == IOAddress("2001:0DB8:0:0::0012"));
- EXPECT_FALSE(IOAddress("2001:db8::12") != IOAddress("2001:0DB8:0:0::0012"));
-
- EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("2001:db8::1235"));
- EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("2001:db8::1235"));
-
- EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("192.0.2.3"));
- EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("192.0.2.3"));
-}
-
-TEST(IOEndpointTest, createUDPv4) {
- const IOEndpoint* ep;
- ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5300);
- EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
- EXPECT_EQ(5300, ep->getPort());
- EXPECT_EQ(AF_INET, ep->getFamily());
- EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
- EXPECT_EQ(IPPROTO_UDP, ep->getProtocol());
-}
-
-TEST(IOEndpointTest, createTCPv4) {
- const IOEndpoint* ep;
- ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5301);
- EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
- EXPECT_EQ(5301, ep->getPort());
- EXPECT_EQ(AF_INET, ep->getFamily());
- EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
- EXPECT_EQ(IPPROTO_TCP, ep->getProtocol());
-}
-
-TEST(IOEndpointTest, createUDPv6) {
- const IOEndpoint* ep;
- ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5302);
- EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
- EXPECT_EQ(5302, ep->getPort());
- EXPECT_EQ(AF_INET6, ep->getFamily());
- EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
- EXPECT_EQ(IPPROTO_UDP, ep->getProtocol());
-}
-
-TEST(IOEndpointTest, createTCPv6) {
- const IOEndpoint* ep;
- ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5303);
- EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
- EXPECT_EQ(5303, ep->getPort());
- EXPECT_EQ(AF_INET6, ep->getFamily());
- EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
- EXPECT_EQ(IPPROTO_TCP, ep->getProtocol());
-}
-
-TEST(IOEndpointTest, createIPProto) {
- EXPECT_THROW(IOEndpoint::create(IPPROTO_IP, IOAddress("192.0.2.1"),
- 5300)->getAddress().toText(),
- IOError);
-}
-
-TEST(IOSocketTest, dummySockets) {
- EXPECT_EQ(IPPROTO_UDP, IOSocket::getDummyUDPSocket().getProtocol());
- EXPECT_EQ(IPPROTO_TCP, IOSocket::getDummyTCPSocket().getProtocol());
- EXPECT_EQ(-1, IOSocket::getDummyUDPSocket().getNative());
- EXPECT_EQ(-1, IOSocket::getDummyTCPSocket().getNative());
-}
-
-TEST(IOServiceTest, badPort) {
- IOService io_service;
- EXPECT_THROW(DNSService(io_service, *"65536", true, false, NULL, NULL, NULL), IOError);
- EXPECT_THROW(DNSService(io_service, *"5300.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;
-}
-
-// This function returns an addrinfo structure for use by tests, using
-// different addresses and ports depending on whether we're testing
-// IPv4 or v6, TCP or UDP, and client or server operation.
-struct addrinfo*
-resolveAddress(const int family, const int protocol, const bool client) {
- const char* const addr = (family == AF_INET6) ?
- TEST_IPV6_ADDR : TEST_IPV4_ADDR;
- const char* const port = client ? TEST_CLIENT_PORT : TEST_SERVER_PORT;
-
- struct addrinfo hints;
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = family;
- hints.ai_socktype = (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
- hints.ai_protocol = protocol;
- hints.ai_flags = AI_NUMERICSERV;
-
- struct addrinfo* res;
- const int error = getaddrinfo(addr, port, &hints, &res);
- if (error != 0) {
- isc_throw(IOError, "getaddrinfo failed: " << gai_strerror(error));
- }
-
- return (res);
-}
-
-// This fixture is a framework for various types of network operations
-// using the ASIO interfaces. Each test case creates an IOService object,
-// opens a local "client" socket for testing, sends data via the local socket
-// to the service that would run in the IOService object.
-// A mock callback function (an ASIOCallBack object) is registered with the
-// IOService object, so the test code should be able to examine the data
-// received on the server side. It then checks the received data matches
-// expected parameters.
-// If initialization parameters of the IOService should be modified, the test
-// case can do it using the setDNSService() method.
-// Note: the set of tests in ASIOLinkTest use actual network services and may
-// involve undesirable side effects such as blocking.
-class ASIOLinkTest : public ::testing::Test {
-protected:
- ASIOLinkTest();
- ~ASIOLinkTest() {
- if (res_ != NULL) {
- freeaddrinfo(res_);
- }
- if (sock_ != -1) {
- close(sock_);
- }
- delete dns_service_;
- delete callback_;
- delete io_service_;
- }
-
- // Send a test UDP packet to a mock server
- void sendUDP(const int family) {
- res_ = resolveAddress(family, IPPROTO_UDP, false);
-
- sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
- const int cc = sendto(sock_, test_data, sizeof(test_data), 0,
- res_->ai_addr, res_->ai_addrlen);
- if (cc != sizeof(test_data)) {
- isc_throw(IOError, "unexpected sendto result: " << cc);
- }
- io_service_->run();
- }
-
- // Send a test TCP packet to a mock server
- void sendTCP(const int family) {
- res_ = resolveAddress(family, IPPROTO_TCP, false);
-
- sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
- if (connect(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
- isc_throw(IOError, "failed to connect to the test server");
- }
- const int cc = send(sock_, test_data, sizeof(test_data), 0);
- if (cc != sizeof(test_data)) {
- isc_throw(IOError, "unexpected send result: " << cc);
- }
- io_service_->run();
- }
-
- // Receive a UDP packet from a mock server; used for testing
- // recursive lookup. The caller must place a RecursiveQuery
- // on the IO Service queue before running this routine.
- void recvUDP(const int family, void* buffer, size_t& size) {
- res_ = resolveAddress(family, IPPROTO_UDP, true);
-
- sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
-
- if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
- isc_throw(IOError, "bind failed: " << strerror(errno));
- }
-
- // The IO service queue should have a RecursiveQuery object scheduled
- // to run at this point. This call will cause it to begin an
- // async send, then return.
- io_service_->run_one();
-
- // ... and this one will block until the send has completed
- io_service_->run_one();
-
- // Now we attempt to recv() whatever was sent.
- // XXX: there's no guarantee the receiving socket can immediately get
- // the packet. Normally we can perform blocking recv to wait for it,
- // but in theory it's even possible that the packet is lost.
- // In order to prevent the test from hanging in such a worst case
- // we add an ad hoc timeout.
- const struct timeval timeo = { 10, 0 };
- int recv_options = 0;
- if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo,
- sizeof(timeo))) {
- if (errno == ENOPROTOOPT) {
- // Workaround for Solaris: it doesn't accept SO_RCVTIMEO
- // with the error of ENOPROTOOPT. Since this is a workaround
- // for rare error cases anyway, we simply switch to the
- // "don't wait" mode. If we still find an error in recv()
- // can happen often we'll consider a more complete solution.
- recv_options = MSG_DONTWAIT;
- } else {
- isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
- }
- }
- const int ret = recv(sock_, buffer, size, recv_options);
- if (ret < 0) {
- isc_throw(IOError, "recvfrom failed: " << strerror(errno));
- }
-
- // Pass the message size back via the size parameter
- size = ret;
- }
-
-
- // Set up an IO Service queue using the specified address
- void setDNSService(const char& address) {
- delete dns_service_;
- dns_service_ = NULL;
- delete io_service_;
- io_service_ = new IOService();
- callback_ = new ASIOCallBack(this);
- dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, address, callback_, NULL, NULL);
- }
-
- // Set up an IO Service queue using the "any" address, on IPv4 if
- // 'use_ipv4' is true and on IPv6 if 'use_ipv6' is true.
- void setDNSService(const bool use_ipv4, const bool use_ipv6) {
- delete dns_service_;
- dns_service_ = NULL;
- delete io_service_;
- io_service_ = new IOService();
- callback_ = new ASIOCallBack(this);
- dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, use_ipv4, use_ipv6, callback_,
- NULL, NULL);
- }
-
- // Set up empty DNS Service
- // Set up an IO Service queue without any addresses
- void setDNSService() {
- delete dns_service_;
- dns_service_ = NULL;
- delete io_service_;
- io_service_ = new IOService();
- callback_ = new ASIOCallBack(this);
- dns_service_ = new DNSService(*io_service_, callback_, NULL, NULL);
- }
-
- // Run a simple server test, on either IPv4 or IPv6, and over either
- // UDP or TCP. Calls the sendUDP() or sendTCP() methods, which will
- // start the IO Service queue. The UDPServer or TCPServer that was
- // created by setIOService() will receive the test packet and issue a
- // callback, which enables us to check that the data it received
- // matches what we sent.
- void doTest(const int family, const int protocol) {
- if (protocol == IPPROTO_UDP) {
- sendUDP(family);
- } else {
- sendTCP(family);
- }
-
- // There doesn't seem to be an effective test for the validity of
- // 'native'.
- // One thing we are sure is it must be different from our local socket.
- EXPECT_NE(sock_, callback_native_);
- EXPECT_EQ(protocol, callback_protocol_);
- EXPECT_EQ(family == AF_INET6 ? TEST_IPV6_ADDR : TEST_IPV4_ADDR,
- callback_address_);
-
- const uint8_t* expected_data =
- protocol == IPPROTO_UDP ? test_data : test_data + 2;
- const size_t expected_datasize =
- protocol == IPPROTO_UDP ? sizeof(test_data) :
- sizeof(test_data) - 2;
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &callback_data_[0],
- callback_data_.size(),
- expected_data, expected_datasize);
- }
-
-protected:
- // This is a nonfunctional mockup of a DNSServer object. Its purpose
- // is to resume after a recursive query or other asynchronous call
- // has completed.
- class MockServer : public DNSServer {
- public:
- explicit MockServer(IOService& io_service,
- SimpleCallback* checkin = NULL,
- DNSLookup* lookup = NULL,
- DNSAnswer* answer = NULL) :
- io_(io_service),
- message_(new Message(Message::PARSE)),
- answer_message_(new Message(Message::RENDER)),
- respbuf_(new OutputBuffer(0)),
- checkin_(checkin), lookup_(lookup), answer_(answer)
- {}
-
- void operator()(asio::error_code ec = asio::error_code(),
- size_t length = 0)
- {}
-
- void resume(const bool) {
- // should never be called in our tests
- }
-
- DNSServer* clone() {
- MockServer* s = new MockServer(*this);
- return (s);
- }
-
- inline void asyncLookup() {
- if (lookup_) {
- (*lookup_)(*io_message_, message_, answer_message_,
- respbuf_, this);
- }
- }
-
- protected:
- IOService& io_;
- bool done_;
-
- private:
- // Currently unused; these will be used for testing
- // asynchronous lookup calls via the asyncLookup() method
- boost::shared_ptr<asiolink::IOMessage> io_message_;
- isc::dns::MessagePtr message_;
- isc::dns::MessagePtr answer_message_;
- isc::dns::OutputBufferPtr respbuf_;
-
- // Callback functions provided by the caller
- const SimpleCallback* checkin_;
- const DNSLookup* lookup_;
- const DNSAnswer* answer_;
- };
-
- // This version of mock server just stops the io_service when it is resumed
- class MockServerStop : public MockServer {
- public:
- explicit MockServerStop(IOService& io_service, bool* done) :
- MockServer(io_service),
- done_(done)
- {}
-
- void resume(const bool done) {
- *done_ = done;
- io_.stop();
- }
-
- DNSServer* clone() {
- return (new MockServerStop(*this));
- }
- private:
- bool* done_;
- };
-
-private:
- class ASIOCallBack : public SimpleCallback {
- public:
- ASIOCallBack(ASIOLinkTest* test_obj) : test_obj_(test_obj) {}
- void operator()(const IOMessage& io_message) const {
- test_obj_->callBack(io_message);
- }
- private:
- ASIOLinkTest* test_obj_;
- };
- void callBack(const IOMessage& io_message) {
- callback_protocol_ = io_message.getSocket().getProtocol();
- callback_native_ = io_message.getSocket().getNative();
- callback_address_ =
- io_message.getRemoteEndpoint().getAddress().toText();
- callback_data_.assign(
- static_cast<const uint8_t*>(io_message.getData()),
- static_cast<const uint8_t*>(io_message.getData()) +
- io_message.getDataSize());
- io_service_->stop();
- }
-protected:
- // We use a pointer for io_service_, because for some tests we
- // need to recreate a new one within one onstance of this class
- IOService* io_service_;
- DNSService* dns_service_;
- ASIOCallBack* callback_;
- int callback_protocol_;
- int callback_native_;
- string callback_address_;
- vector<uint8_t> callback_data_;
- int sock_;
- struct addrinfo* res_;
-};
-
-ASIOLinkTest::ASIOLinkTest() :
- dns_service_(NULL), callback_(NULL), sock_(-1), res_(NULL)
-{
- io_service_ = new IOService();
- setDNSService(true, true);
-}
-
-TEST_F(ASIOLinkTest, v6UDPSend) {
- doTest(AF_INET6, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v6TCPSend) {
- doTest(AF_INET6, IPPROTO_TCP);
-}
-
-TEST_F(ASIOLinkTest, v4UDPSend) {
- doTest(AF_INET, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v4TCPSend) {
- doTest(AF_INET, IPPROTO_TCP);
-}
-
-TEST_F(ASIOLinkTest, v6UDPSendSpecific) {
- // Explicitly set a specific address to be bound to the socket.
- // The subsequent test does not directly ensures the underlying socket
- // is bound to the expected address, but the success of the tests should
- // reasonably suggest it works as intended.
- // Specifying an address also implicitly means the service runs in a
- // single address-family mode. In tests using TCP we can confirm that
- // by trying to make a connection and seeing a failure. In UDP, it'd be
- // more complicated because we need to use a connected socket and catch
- // an error on a subsequent read operation. We could do it, but for
- // simplicity we only tests the easier cases for now.
-
- setDNSService(*TEST_IPV6_ADDR);
- doTest(AF_INET6, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v6TCPSendSpecific) {
- setDNSService(*TEST_IPV6_ADDR);
- doTest(AF_INET6, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(ASIOLinkTest, v4UDPSendSpecific) {
- setDNSService(*TEST_IPV4_ADDR);
- doTest(AF_INET, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v4TCPSendSpecific) {
- setDNSService(*TEST_IPV4_ADDR);
- doTest(AF_INET, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-TEST_F(ASIOLinkTest, v6AddServer) {
- setDNSService();
- dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV6_ADDR);
- doTest(AF_INET6, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(ASIOLinkTest, v4AddServer) {
- setDNSService();
- dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV4_ADDR);
- doTest(AF_INET, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-TEST_F(ASIOLinkTest, DISABLED_clearServers) {
- // FIXME: Enable when clearServers actually close the sockets
- // See #388
- setDNSService();
- dns_service_->clearServers();
-
- EXPECT_THROW(sendTCP(AF_INET), IOError);
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-TEST_F(ASIOLinkTest, v6TCPOnly) {
- // Open only IPv6 TCP socket. A subsequent attempt of establishing an
- // IPv4/TCP connection should fail. See above for why we only test this
- // for TCP.
- setDNSService(false, true);
- EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(ASIOLinkTest, v4TCPOnly) {
- setDNSService(true, false);
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-vector<pair<string, uint16_t> >
-singleAddress(const string &address, uint16_t port) {
- vector<pair<string, uint16_t> > result;
- result.push_back(pair<string, uint16_t>(address, port));
- return (result);
-}
-
-TEST_F(ASIOLinkTest, recursiveSetupV4) {
- setDNSService(true, false);
- uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port)));
-}
-
-TEST_F(ASIOLinkTest, recursiveSetupV6) {
- setDNSService(false, true);
- uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
- singleAddress(TEST_IPV6_ADDR, port),
- singleAddress(TEST_IPV6_ADDR,port)));
-}
-
-// XXX:
-// This is very inadequate unit testing. It should be generalized into
-// a routine that can do this with variable address family, address, and
-// port, and with the various callbacks defined in such a way as to ensure
-// full code coverage including error cases.
-TEST_F(ASIOLinkTest, forwarderSend) {
- setDNSService(true, false);
-
- // Note: We use the test prot plus one to ensure we aren't binding
- // to the same port as the actual server
- uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
-
- MockServer server(*io_service_);
- RecursiveQuery rq(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port));
-
- Question q(Name("example.com"), RRClass::IN(), RRType::TXT());
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
- rq.resolve(q, answer, buffer, &server);
-
- char data[4096];
- size_t size = sizeof(data);
- ASSERT_NO_THROW(recvUDP(AF_INET, data, size));
-
- Message m(Message::PARSE);
- InputBuffer ibuf(data, size);
-
- // Make sure we can parse the message that was sent
- EXPECT_NO_THROW(m.parseHeader(ibuf));
- EXPECT_NO_THROW(m.fromWire(ibuf));
-
- // Check that the question sent matches the one we wanted
- QuestionPtr q2 = *m.beginQuestion();
- EXPECT_EQ(q.getName(), q2->getName());
- EXPECT_EQ(q.getType(), q2->getType());
- EXPECT_EQ(q.getClass(), q2->getClass());
-}
-
-int
-createTestSocket()
-{
- struct addrinfo* res_ = resolveAddress(AF_INET, IPPROTO_UDP, true);
- int sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
- if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
- isc_throw(IOError, "failed to bind test socket");
- }
- return sock_;
-}
-
-int
-setSocketTimeout(int sock_, size_t tv_sec, size_t tv_usec) {
- const struct timeval timeo = { tv_sec, tv_usec };
- int recv_options = 0;
- if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo))) {
- if (errno == ENOPROTOOPT) { // see ASIOLinkTest::recvUDP()
- recv_options = MSG_DONTWAIT;
- } else {
- isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
- }
- }
- return recv_options;
-}
-
-// try to read from the socket max time
-// *num is incremented for every succesfull read
-// returns true if it can read max times, false otherwise
-bool tryRead(int sock_, int recv_options, size_t max, int* num) {
- size_t i = 0;
- do {
- char inbuff[512];
- if (recv(sock_, inbuff, sizeof(inbuff), recv_options) < 0) {
- return false;
- } else {
- ++i;
- ++*num;
- }
- } while (i < max);
- return true;
-}
-
-
-// Test it tries the correct amount of times before giving up
-TEST_F(ASIOLinkTest, forwardQueryTimeout) {
- // Prepare the service (we do not use the common setup, we do not answer
- setDNSService();
-
- // Prepare the socket
- sock_ = createTestSocket();
-
- // Prepare the server
- bool done(true);
- MockServerStop server(*io_service_, &done);
-
- // Do the answer
- const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- RecursiveQuery query(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port),
- 10, 4000, 3000, 2);
- Question question(Name("example.net"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
- query.resolve(question, answer, buffer, &server);
-
- // Run the test
- io_service_->run();
-
- // Read up to 3 packets. Use some ad hoc timeout to prevent an infinite
- // block (see also recvUDP()).
- int recv_options = setSocketTimeout(sock_, 10, 0);
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 3, &num);
-
- // The query should fail
- EXPECT_FALSE(done);
- EXPECT_EQ(3, num);
- EXPECT_TRUE(read_success);
-}
-
-// If we set client timeout to lower than querytimeout, we should
-// get a failure answer, but still see retries
-// (no actual answer is given here yet)
-TEST_F(ASIOLinkTest, forwardClientTimeout) {
- // Prepare the service (we do not use the common setup, we do not answer
- setDNSService();
-
- sock_ = createTestSocket();
-
- // Prepare the server
- bool done(true);
- MockServerStop server(*io_service_, &done);
-
- MessagePtr answer(new Message(Message::RENDER));
-
- // Do the answer
- const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- // Set it up to retry twice before client timeout fires
- // Since the lookup timer has not fired, it should retry
- // a third time
- RecursiveQuery query(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port),
- 50, 120, 1000, 3);
- Question question(Name("example.net"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- query.resolve(question, answer, buffer, &server);
-
- // Run the test
- io_service_->run();
-
- // we know it'll fail, so make it a shorter timeout
- int recv_options = setSocketTimeout(sock_, 1, 0);
-
- // Try to read 5 times, should stop after 3 reads
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 5, &num);
-
- // The query should fail (for resolver it should send back servfail,
- // but currently, and perhaps for forwarder in general, the effect
- // will be the same as on a lookup timeout, i.e. no answer is sent
- // back)
- EXPECT_FALSE(done);
- EXPECT_EQ(3, num);
- EXPECT_FALSE(read_success);
-}
-
-// If we set lookup timeout to lower than querytimeout*retries, we should
-// fail before the full amount of retries
-TEST_F(ASIOLinkTest, forwardLookupTimeout) {
- // Prepare the service (we do not use the common setup, we do not answer
- setDNSService();
-
- // Prepare the socket
- sock_ = createTestSocket();
-
- // Prepare the server
- bool done(true);
- MockServerStop server(*io_service_, &done);
-
- MessagePtr answer(new Message(Message::RENDER));
-
- // Do the answer
- const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- // Set up the test so that it will retry 5 times, but the lookup
- // timeout will fire after only 3 normal timeouts
- RecursiveQuery query(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port),
- 50, 4000, 120, 5);
- Question question(Name("example.net"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- query.resolve(question, answer, buffer, &server);
-
- // Run the test
- io_service_->run();
-
- int recv_options = setSocketTimeout(sock_, 1, 0);
-
- // Try to read 5 times, should stop after 3 reads
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 5, &num);
-
- // The query should fail
- EXPECT_FALSE(done);
- EXPECT_EQ(3, num);
- EXPECT_FALSE(read_success);
-}
-
-// as mentioned above, we need a more better framework for this,
-// in addition to that, this sends out queries into the world
-// (which we should catch somehow and fake replies for)
-// for the skeleton code, it shouldn't be too much of a problem
-// Ok so even we don't all have access to the DNS world right now,
-// so disabling these tests too.
-TEST_F(ASIOLinkTest, DISABLED_recursiveSendOk) {
- setDNSService(true, false);
- bool done;
-
- MockServerStop server(*io_service_, &done);
- vector<pair<string, uint16_t> > empty_vector;
- RecursiveQuery rq(*dns_service_, empty_vector, empty_vector, 10000, 0);
-
- Question q(Name("www.isc.org"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
- rq.resolve(q, answer, buffer, &server);
- io_service_->run();
-
- // Check that the answer we got matches the one we wanted
- EXPECT_EQ(Rcode::NOERROR(), answer->getRcode());
- ASSERT_EQ(1, answer->getRRCount(Message::SECTION_ANSWER));
- RRsetPtr a = *answer->beginSection(Message::SECTION_ANSWER);
- EXPECT_EQ(q.getName(), a->getName());
- EXPECT_EQ(q.getType(), a->getType());
- EXPECT_EQ(q.getClass(), a->getClass());
- EXPECT_EQ(1, a->getRdataCount());
-}
-
-// see comments at previous test
-TEST_F(ASIOLinkTest, DISABLED_recursiveSendNXDOMAIN) {
- setDNSService(true, false);
- bool done;
-
- MockServerStop server(*io_service_, &done);
- vector<pair<string, uint16_t> > empty_vector;
- RecursiveQuery rq(*dns_service_, empty_vector, empty_vector, 10000, 0);
-
- Question q(Name("wwwdoesnotexist.isc.org"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
- rq.resolve(q, answer, buffer, &server);
- io_service_->run();
-
- // Check that the answer we got matches the one we wanted
- EXPECT_EQ(Rcode::NXDOMAIN(), answer->getRcode());
- EXPECT_EQ(0, answer->getRRCount(Message::SECTION_ANSWER));
-}
-
-
-
-// This fixture is for testing IntervalTimer. Some callback functors are
-// registered as callback function of the timer to test if they are called
-// or not.
-class IntervalTimerTest : public ::testing::Test {
-protected:
- IntervalTimerTest() : io_service_() {}
- ~IntervalTimerTest() {}
- class TimerCallBack : public std::unary_function<void, void> {
- public:
- TimerCallBack(IntervalTimerTest* test_obj) : test_obj_(test_obj) {}
- void operator()() const {
- test_obj_->timer_called_ = true;
- test_obj_->io_service_.stop();
- return;
- }
- private:
- IntervalTimerTest* test_obj_;
- };
- class TimerCallBackCounter : public std::unary_function<void, void> {
- public:
- TimerCallBackCounter(IntervalTimerTest* test_obj) : test_obj_(test_obj) {
- counter_ = 0;
- }
- void operator()() {
- ++counter_;
- return;
- }
- int counter_;
- private:
- IntervalTimerTest* test_obj_;
- };
- class TimerCallBackCancelDeleter : public std::unary_function<void, void> {
- public:
- TimerCallBackCancelDeleter(IntervalTimerTest* test_obj,
- IntervalTimer* timer,
- TimerCallBackCounter& counter)
- : test_obj_(test_obj), timer_(timer), counter_(counter), count_(0)
- {}
- void operator()() {
- ++count_;
- if (count_ == 1) {
- // First time of call back.
- // Store the value of counter_.counter_.
- prev_counter_ = counter_.counter_;
- delete timer_;
- } else if (count_ == 2) {
- // Second time of call back.
- // Stop io_service to stop all timers.
- test_obj_->io_service_.stop();
- // Compare the value of counter_.counter_ with stored one.
- // If TimerCallBackCounter was not called (expected behavior),
- // they are same.
- if (counter_.counter_ == prev_counter_) {
- test_obj_->timer_cancel_success_ = true;
- }
- }
- return;
- }
- private:
- IntervalTimerTest* test_obj_;
- IntervalTimer* timer_;
- TimerCallBackCounter& counter_;
- int count_;
- int prev_counter_;
- };
- class TimerCallBackCanceller {
- public:
- TimerCallBackCanceller(unsigned int& counter, IntervalTimer& itimer) :
- counter_(counter), itimer_(itimer)
- {}
- void operator()() {
- ++counter_;
- itimer_.cancel();
- }
- private:
- unsigned int& counter_;
- IntervalTimer& itimer_;
- };
- class TimerCallBackOverwriter : public std::unary_function<void, void> {
- public:
- TimerCallBackOverwriter(IntervalTimerTest* test_obj,
- IntervalTimer& timer)
- : test_obj_(test_obj), timer_(timer), count_(0)
- {}
- void operator()() {
- ++count_;
- if (count_ == 1) {
- // First time of call back.
- // Call setup() to update callback function to TimerCallBack.
- test_obj_->timer_called_ = false;
- timer_.setup(TimerCallBack(test_obj_), 100);
- } else if (count_ == 2) {
- // Second time of call back.
- // If it reaches here, re-setup() is failed (unexpected).
- // We should stop here.
- test_obj_->io_service_.stop();
- }
- return;
- }
- private:
- IntervalTimerTest* test_obj_;
- IntervalTimer& timer_;
- int count_;
- };
-protected:
- IOService io_service_;
- bool timer_called_;
- bool timer_cancel_success_;
-};
-
-TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) {
- // Create asio_link::IntervalTimer and setup.
- IntervalTimer itimer(io_service_);
- // expect throw if call back function is empty
- EXPECT_THROW(itimer.setup(IntervalTimer::Callback(), 1),
- isc::InvalidParameter);
- // expect throw if interval is not greater than 0
- EXPECT_THROW(itimer.setup(TimerCallBack(this), 0), isc::BadValue);
- EXPECT_THROW(itimer.setup(TimerCallBack(this), -1), isc::BadValue);
-}
-
-TEST_F(IntervalTimerTest, startIntervalTimer) {
- // Create asio_link::IntervalTimer and setup.
- // Then run IOService and test if the callback function is called.
- IntervalTimer itimer(io_service_);
- timer_called_ = false;
- // store start time
- boost::posix_time::ptime start;
- start = boost::posix_time::microsec_clock::universal_time();
- // setup timer
- itimer.setup(TimerCallBack(this), 100);
- EXPECT_EQ(100, itimer.getInterval());
- io_service_.run();
- // reaches here after timer expired
- // delta: difference between elapsed time and 100 milliseconds.
- boost::posix_time::time_duration delta =
- (boost::posix_time::microsec_clock::universal_time() - start)
- - boost::posix_time::millisec(100);
- if (delta.is_negative()) {
- delta.invert_sign();
- }
- // expect TimerCallBack is called; timer_called_ is true
- EXPECT_TRUE(timer_called_);
- // expect interval is 100 milliseconds +/- TIMER_MARGIN_MSEC.
- EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
-}
-
-TEST_F(IntervalTimerTest, destructIntervalTimer) {
- // This code isn't exception safe, but we'd rather keep the code
- // simpler and more readable as this is only for tests and if it throws
- // the program would immediately terminate anyway.
-
- // The call back function will not be called after the timer is
- // destroyed.
- //
- // There are two timers:
- // itimer_counter (A)
- // (Calls TimerCallBackCounter)
- // - increments internal counter in callback function
- // itimer_canceller (B)
- // (Calls TimerCallBackCancelDeleter)
- // - first time of callback, it stores the counter value of
- // callback_canceller and destroys itimer_counter
- // - second time of callback, it compares the counter value of
- // callback_canceller with stored value
- // if they are same the timer was not called; expected result
- // if they are different the timer was called after destroyed
- //
- // 0 100 200 300 400 500 600 (ms)
- // (A) i--------+----x
- // ^
- // |destroy itimer_counter
- // (B) i-------------+--------------s
- // ^stop io_service
- // and check if itimer_counter have been
- // stopped
-
- // itimer_counter will be deleted in TimerCallBackCancelDeleter
- IntervalTimer* itimer_counter = new IntervalTimer(io_service_);
- IntervalTimer itimer_canceller(io_service_);
- timer_cancel_success_ = false;
- TimerCallBackCounter callback_canceller(this);
- itimer_counter->setup(callback_canceller, 200);
- itimer_canceller.setup(
- TimerCallBackCancelDeleter(this, itimer_counter, callback_canceller),
- 300);
- io_service_.run();
- EXPECT_TRUE(timer_cancel_success_);
-}
-
-TEST_F(IntervalTimerTest, cancel) {
- // Similar to destructIntervalTimer test, but the first timer explicitly
- // cancels itself on first callback.
- IntervalTimer itimer_counter(io_service_);
- IntervalTimer itimer_watcher(io_service_);
- unsigned int counter = 0;
- itimer_counter.setup(TimerCallBackCanceller(counter, itimer_counter), 100);
- itimer_watcher.setup(TimerCallBack(this), 200);
- io_service_.run();
- EXPECT_EQ(1, counter);
- EXPECT_EQ(0, itimer_counter.getInterval());
-
- // canceling an already canceled timer shouldn't cause any surprise.
- EXPECT_NO_THROW(itimer_counter.cancel());
-}
-
-TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
- // Calling setup() multiple times updates call back function and interval.
- //
- // There are two timers:
- // itimer (A)
- // (Calls TimerCallBackCounter / TimerCallBack)
- // - increments internal counter in callback function
- // (TimerCallBackCounter)
- // interval: 300 milliseconds
- // - io_service_.stop() (TimerCallBack)
- // interval: 100 milliseconds
- // itimer_overwriter (B)
- // (Calls TimerCallBackOverwriter)
- // - first time of callback, it calls setup() to change call back
- // function to TimerCallBack and interval of itimer to 100
- // milliseconds
- // after 300 + 100 milliseconds from the beginning of this test,
- // TimerCallBack() will be called and io_service_ stops.
- // - second time of callback, it means the test fails.
- //
- // 0 100 200 300 400 500 600 700 800 (ms)
- // (A) i-------------+----C----s
- // ^ ^stop io_service
- // |change call back function
- // (B) i------------------+-------------------S
- // ^(stop io_service on fail)
- //
-
- IntervalTimer itimer(io_service_);
- IntervalTimer itimer_overwriter(io_service_);
- // store start time
- boost::posix_time::ptime start;
- start = boost::posix_time::microsec_clock::universal_time();
- itimer.setup(TimerCallBackCounter(this), 300);
- itimer_overwriter.setup(TimerCallBackOverwriter(this, itimer), 400);
- io_service_.run();
- // reaches here after timer expired
- // if interval is updated, it takes
- // 400 milliseconds for TimerCallBackOverwriter
- // + 100 milliseconds for TimerCallBack (stop)
- // = 500 milliseconds.
- // otherwise (test fails), it takes
- // 400 milliseconds for TimerCallBackOverwriter
- // + 400 milliseconds for TimerCallBackOverwriter (stop)
- // = 800 milliseconds.
- // delta: difference between elapsed time and 400 + 100 milliseconds
- boost::posix_time::time_duration delta =
- (boost::posix_time::microsec_clock::universal_time() - start)
- - boost::posix_time::millisec(400 + 100);
- if (delta.is_negative()) {
- delta.invert_sign();
- }
- // expect callback function is updated: TimerCallBack is called
- EXPECT_TRUE(timer_called_);
- // expect interval is updated
- EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
-}
-
-}
diff --git a/src/lib/asiolink/tests/asiolink_utilities_unittest.cc b/src/lib/asiolink/tests/asiolink_utilities_unittest.cc
new file mode 100644
index 0000000..51f565f
--- /dev/null
+++ b/src/lib/asiolink/tests/asiolink_utilities_unittest.cc
@@ -0,0 +1,74 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// \brief Test of asiolink utilties
+///
+/// Tests the fuctionality of the asiolink utilities code by comparing them
+/// with the equivalent methods in isc::dns::[Input/Output]Buffer.
+
+#include <cstddef>
+
+#include <gtest/gtest.h>
+
+#include <dns/buffer.h>
+#include <asiolink/asiolink_utilities.h>
+
+using namespace asiolink;
+using namespace isc::dns;
+
+TEST(asioutil, readUint16) {
+
+ // Reference buffer
+ uint8_t data[2];
+ isc::dns::InputBuffer buffer(data, sizeof(data));
+
+ // Avoid possible compiler warnings by only setting uint8_t variables to
+ // uint8_t values.
+ uint8_t i8 = 0;
+ uint8_t j8 = 0;
+ for (int i = 0; i < (2 << 8); ++i, ++i8) {
+ for (int j = 0; j < (2 << 8); ++j, ++j8) {
+ data[0] = i8;
+ data[1] = j8;
+ buffer.setPosition(0);
+ EXPECT_EQ(buffer.readUint16(), readUint16(data));
+ }
+ }
+}
+
+
+TEST(asioutil, writeUint16) {
+
+ // Reference buffer
+ isc::dns::OutputBuffer buffer(2);
+ uint8_t test[2];
+
+ // Avoid possible compiler warnings by only setting uint16_t variables to
+ // uint16_t values.
+ uint16_t i16 = 0;
+ for (uint32_t i = 0; i < (2 << 16); ++i, ++i16) {
+
+ // Write the reference data
+ buffer.clear();
+ buffer.writeUint16(i16);
+
+ // ... and the test data
+ writeUint16(i16, test);
+
+ // ... and compare
+ const uint8_t* ref = static_cast<const uint8_t*>(buffer.getData());
+ EXPECT_EQ(ref[0], test[0]);
+ EXPECT_EQ(ref[1], test[1]);
+ }
+}
diff --git a/src/lib/asiolink/tests/interval_timer_unittest.cc b/src/lib/asiolink/tests/interval_timer_unittest.cc
new file mode 100644
index 0000000..7e0e7bc
--- /dev/null
+++ b/src/lib/asiolink/tests/interval_timer_unittest.cc
@@ -0,0 +1,296 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <gtest/gtest.h>
+
+#include <asio.hpp>
+#include <asiolink/asiolink.h>
+
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+namespace {
+// TODO: Consider this margin
+const boost::posix_time::time_duration TIMER_MARGIN_MSEC =
+ boost::posix_time::milliseconds(50);
+}
+
+using namespace asiolink;
+
+// This fixture is for testing IntervalTimer. Some callback functors are
+// registered as callback function of the timer to test if they are called
+// or not.
+class IntervalTimerTest : public ::testing::Test {
+protected:
+ IntervalTimerTest() :
+ io_service_(), timer_called_(false), timer_cancel_success_(false)
+ {}
+ ~IntervalTimerTest() {}
+ class TimerCallBack : public std::unary_function<void, void> {
+ public:
+ TimerCallBack(IntervalTimerTest* test_obj) : test_obj_(test_obj) {}
+ void operator()() const {
+ test_obj_->timer_called_ = true;
+ test_obj_->io_service_.stop();
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ };
+ class TimerCallBackCounter : public std::unary_function<void, void> {
+ public:
+ TimerCallBackCounter(IntervalTimerTest* test_obj) : test_obj_(test_obj) {
+ counter_ = 0;
+ }
+ void operator()() {
+ ++counter_;
+ return;
+ }
+ int counter_;
+ private:
+ IntervalTimerTest* test_obj_;
+ };
+ class TimerCallBackCancelDeleter : public std::unary_function<void, void> {
+ public:
+ TimerCallBackCancelDeleter(IntervalTimerTest* test_obj,
+ IntervalTimer* timer,
+ TimerCallBackCounter& counter)
+ : test_obj_(test_obj), timer_(timer), counter_(counter), count_(0),
+ prev_counter_(-1)
+ {}
+ void operator()() {
+ ++count_;
+ if (count_ == 1) {
+ // First time of call back.
+ // Store the value of counter_.counter_.
+ prev_counter_ = counter_.counter_;
+ delete timer_;
+ } else if (count_ == 2) {
+ // Second time of call back.
+ // Stop io_service to stop all timers.
+ test_obj_->io_service_.stop();
+ // Compare the value of counter_.counter_ with stored one.
+ // If TimerCallBackCounter was not called (expected behavior),
+ // they are same.
+ if (counter_.counter_ == prev_counter_) {
+ test_obj_->timer_cancel_success_ = true;
+ }
+ }
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ IntervalTimer* timer_;
+ TimerCallBackCounter& counter_;
+ int count_;
+ int prev_counter_;
+ };
+ class TimerCallBackCanceller {
+ public:
+ TimerCallBackCanceller(unsigned int& counter, IntervalTimer& itimer) :
+ counter_(counter), itimer_(itimer)
+ {}
+ void operator()() {
+ ++counter_;
+ itimer_.cancel();
+ }
+ private:
+ unsigned int& counter_;
+ IntervalTimer& itimer_;
+ };
+ class TimerCallBackOverwriter : public std::unary_function<void, void> {
+ public:
+ TimerCallBackOverwriter(IntervalTimerTest* test_obj,
+ IntervalTimer& timer)
+ : test_obj_(test_obj), timer_(timer), count_(0)
+ {}
+ void operator()() {
+ ++count_;
+ if (count_ == 1) {
+ // First time of call back.
+ // Call setup() to update callback function to TimerCallBack.
+ test_obj_->timer_called_ = false;
+ timer_.setup(TimerCallBack(test_obj_), 100);
+ } else if (count_ == 2) {
+ // Second time of call back.
+ // If it reaches here, re-setup() is failed (unexpected).
+ // We should stop here.
+ test_obj_->io_service_.stop();
+ }
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ IntervalTimer& timer_;
+ int count_;
+ };
+protected:
+ IOService io_service_;
+ bool timer_called_;
+ bool timer_cancel_success_;
+};
+
+TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) {
+ // Create asio_link::IntervalTimer and setup.
+ IntervalTimer itimer(io_service_);
+ // expect throw if call back function is empty
+ EXPECT_THROW(itimer.setup(IntervalTimer::Callback(), 1),
+ isc::InvalidParameter);
+ // expect throw if interval is not greater than 0
+ EXPECT_THROW(itimer.setup(TimerCallBack(this), 0), isc::BadValue);
+ EXPECT_THROW(itimer.setup(TimerCallBack(this), -1), isc::BadValue);
+}
+
+TEST_F(IntervalTimerTest, startIntervalTimer) {
+ // Create asio_link::IntervalTimer and setup.
+ // Then run IOService and test if the callback function is called.
+ IntervalTimer itimer(io_service_);
+ timer_called_ = false;
+ // store start time
+ boost::posix_time::ptime start;
+ start = boost::posix_time::microsec_clock::universal_time();
+ // setup timer
+ itimer.setup(TimerCallBack(this), 100);
+ EXPECT_EQ(100, itimer.getInterval());
+ io_service_.run();
+ // reaches here after timer expired
+ // delta: difference between elapsed time and 100 milliseconds.
+ boost::posix_time::time_duration delta =
+ (boost::posix_time::microsec_clock::universal_time() - start)
+ - boost::posix_time::millisec(100);
+ if (delta.is_negative()) {
+ delta.invert_sign();
+ }
+ // expect TimerCallBack is called; timer_called_ is true
+ EXPECT_TRUE(timer_called_);
+ // expect interval is 100 milliseconds +/- TIMER_MARGIN_MSEC.
+ EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+}
+
+TEST_F(IntervalTimerTest, destructIntervalTimer) {
+ // This code isn't exception safe, but we'd rather keep the code
+ // simpler and more readable as this is only for tests and if it throws
+ // the program would immediately terminate anyway.
+
+ // The call back function will not be called after the timer is
+ // destroyed.
+ //
+ // There are two timers:
+ // itimer_counter (A)
+ // (Calls TimerCallBackCounter)
+ // - increments internal counter in callback function
+ // itimer_canceller (B)
+ // (Calls TimerCallBackCancelDeleter)
+ // - first time of callback, it stores the counter value of
+ // callback_canceller and destroys itimer_counter
+ // - second time of callback, it compares the counter value of
+ // callback_canceller with stored value
+ // if they are same the timer was not called; expected result
+ // if they are different the timer was called after destroyed
+ //
+ // 0 100 200 300 400 500 600 (ms)
+ // (A) i--------+----x
+ // ^
+ // |destroy itimer_counter
+ // (B) i-------------+--------------s
+ // ^stop io_service
+ // and check if itimer_counter have been
+ // stopped
+
+ // itimer_counter will be deleted in TimerCallBackCancelDeleter
+ IntervalTimer* itimer_counter = new IntervalTimer(io_service_);
+ IntervalTimer itimer_canceller(io_service_);
+ timer_cancel_success_ = false;
+ TimerCallBackCounter callback_canceller(this);
+ itimer_counter->setup(callback_canceller, 200);
+ itimer_canceller.setup(
+ TimerCallBackCancelDeleter(this, itimer_counter, callback_canceller),
+ 300);
+ io_service_.run();
+ EXPECT_TRUE(timer_cancel_success_);
+}
+
+TEST_F(IntervalTimerTest, cancel) {
+ // Similar to destructIntervalTimer test, but the first timer explicitly
+ // cancels itself on first callback.
+ IntervalTimer itimer_counter(io_service_);
+ IntervalTimer itimer_watcher(io_service_);
+ unsigned int counter = 0;
+ itimer_counter.setup(TimerCallBackCanceller(counter, itimer_counter), 100);
+ itimer_watcher.setup(TimerCallBack(this), 200);
+ io_service_.run();
+ EXPECT_EQ(1, counter);
+ EXPECT_EQ(0, itimer_counter.getInterval());
+
+ // canceling an already canceled timer shouldn't cause any surprise.
+ EXPECT_NO_THROW(itimer_counter.cancel());
+}
+
+TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
+ // Calling setup() multiple times updates call back function and interval.
+ //
+ // There are two timers:
+ // itimer (A)
+ // (Calls TimerCallBackCounter / TimerCallBack)
+ // - increments internal counter in callback function
+ // (TimerCallBackCounter)
+ // interval: 300 milliseconds
+ // - io_service_.stop() (TimerCallBack)
+ // interval: 100 milliseconds
+ // itimer_overwriter (B)
+ // (Calls TimerCallBackOverwriter)
+ // - first time of callback, it calls setup() to change call back
+ // function to TimerCallBack and interval of itimer to 100
+ // milliseconds
+ // after 300 + 100 milliseconds from the beginning of this test,
+ // TimerCallBack() will be called and io_service_ stops.
+ // - second time of callback, it means the test fails.
+ //
+ // 0 100 200 300 400 500 600 700 800 (ms)
+ // (A) i-------------+----C----s
+ // ^ ^stop io_service
+ // |change call back function
+ // (B) i------------------+-------------------S
+ // ^(stop io_service on fail)
+ //
+
+ IntervalTimer itimer(io_service_);
+ IntervalTimer itimer_overwriter(io_service_);
+ // store start time
+ boost::posix_time::ptime start;
+ start = boost::posix_time::microsec_clock::universal_time();
+ itimer.setup(TimerCallBackCounter(this), 300);
+ itimer_overwriter.setup(TimerCallBackOverwriter(this, itimer), 400);
+ io_service_.run();
+ // reaches here after timer expired
+ // if interval is updated, it takes
+ // 400 milliseconds for TimerCallBackOverwriter
+ // + 100 milliseconds for TimerCallBack (stop)
+ // = 500 milliseconds.
+ // otherwise (test fails), it takes
+ // 400 milliseconds for TimerCallBackOverwriter
+ // + 400 milliseconds for TimerCallBackOverwriter (stop)
+ // = 800 milliseconds.
+ // delta: difference between elapsed time and 400 + 100 milliseconds
+ boost::posix_time::time_duration delta =
+ (boost::posix_time::microsec_clock::universal_time() - start)
+ - boost::posix_time::millisec(400 + 100);
+ if (delta.is_negative()) {
+ delta.invert_sign();
+ }
+ // expect callback function is updated: TimerCallBack is called
+ EXPECT_TRUE(timer_called_);
+ // expect interval is updated
+ EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+}
diff --git a/src/lib/asiolink/tests/io_address_unittest.cc b/src/lib/asiolink/tests/io_address_unittest.cc
new file mode 100644
index 0000000..894f143
--- /dev/null
+++ b/src/lib/asiolink/tests/io_address_unittest.cc
@@ -0,0 +1,63 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <gtest/gtest.h>
+
+#include <asiolink/io_error.h>
+#include <asiolink/io_address.h>
+
+using namespace asiolink;
+
+TEST(IOAddressTest, fromText) {
+ IOAddress io_address_v4("192.0.2.1");
+ EXPECT_EQ("192.0.2.1", io_address_v4.toText());
+
+ IOAddress io_address_v6("2001:db8::1234");
+ EXPECT_EQ("2001:db8::1234", io_address_v6.toText());
+
+ // bogus IPv4 address-like input
+ EXPECT_THROW(IOAddress("192.0.2.2.1"), IOError);
+
+ // bogus IPv4 address-like input: out-of-range octet
+ EXPECT_THROW(IOAddress("192.0.2.300"), IOError);
+
+ // bogus IPv6 address-like input
+ EXPECT_THROW(IOAddress("2001:db8:::1234"), IOError);
+
+ // bogus IPv6 address-like input
+ EXPECT_THROW(IOAddress("2001:db8::efgh"), IOError);
+}
+
+TEST(IOAddressTest, Equality) {
+ EXPECT_TRUE(IOAddress("192.0.2.1") == IOAddress("192.0.2.1"));
+ EXPECT_FALSE(IOAddress("192.0.2.1") != IOAddress("192.0.2.1"));
+
+ EXPECT_TRUE(IOAddress("192.0.2.1") != IOAddress("192.0.2.2"));
+ EXPECT_FALSE(IOAddress("192.0.2.1") == IOAddress("192.0.2.2"));
+
+ EXPECT_TRUE(IOAddress("2001:db8::12") == IOAddress("2001:0DB8:0:0::0012"));
+ EXPECT_FALSE(IOAddress("2001:db8::12") != IOAddress("2001:0DB8:0:0::0012"));
+
+ EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("2001:db8::1235"));
+ EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("2001:db8::1235"));
+
+ EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("192.0.2.3"));
+ EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("192.0.2.3"));
+}
+
+TEST(IOAddressTest, Family) {
+ EXPECT_EQ(AF_INET, IOAddress("192.0.2.1").getFamily());
+ EXPECT_EQ(AF_INET6, IOAddress("2001:0DB8:0:0::0012").getFamily());
+}
diff --git a/src/lib/asiolink/tests/io_endpoint_unittest.cc b/src/lib/asiolink/tests/io_endpoint_unittest.cc
new file mode 100644
index 0000000..6101473
--- /dev/null
+++ b/src/lib/asiolink/tests/io_endpoint_unittest.cc
@@ -0,0 +1,68 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <gtest/gtest.h>
+
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_error.h>
+
+using namespace asiolink;
+
+TEST(IOEndpointTest, createUDPv4) {
+ const IOEndpoint* ep;
+ ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 53210);
+ EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
+ EXPECT_EQ(53210, ep->getPort());
+ EXPECT_EQ(AF_INET, ep->getFamily());
+ EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
+ EXPECT_EQ(IPPROTO_UDP, ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createTCPv4) {
+ const IOEndpoint* ep;
+ ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5301);
+ EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
+ EXPECT_EQ(5301, ep->getPort());
+ EXPECT_EQ(AF_INET, ep->getFamily());
+ EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
+ EXPECT_EQ(IPPROTO_TCP, ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createUDPv6) {
+ const IOEndpoint* ep;
+ ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5302);
+ EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
+ EXPECT_EQ(5302, ep->getPort());
+ EXPECT_EQ(AF_INET6, ep->getFamily());
+ EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
+ EXPECT_EQ(IPPROTO_UDP, ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createTCPv6) {
+ const IOEndpoint* ep;
+ ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5303);
+ EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
+ EXPECT_EQ(5303, ep->getPort());
+ EXPECT_EQ(AF_INET6, ep->getFamily());
+ EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
+ EXPECT_EQ(IPPROTO_TCP, ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createIPProto) {
+ EXPECT_THROW(IOEndpoint::create(IPPROTO_IP, IOAddress("192.0.2.1"),
+ 53210)->getAddress().toText(),
+ IOError);
+}
+
diff --git a/src/lib/asiolink/tests/io_fetch_unittest.cc b/src/lib/asiolink/tests/io_fetch_unittest.cc
new file mode 100644
index 0000000..901df45
--- /dev/null
+++ b/src/lib/asiolink/tests/io_fetch_unittest.cc
@@ -0,0 +1,608 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <algorithm>
+#include <cstdlib>
+#include <string>
+#include <iostream>
+#include <iomanip>
+#include <iterator>
+#include <vector>
+
+#include <gtest/gtest.h>
+#include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+#include <asio.hpp>
+
+#include <dns/buffer.h>
+#include <dns/question.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/opcode.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+
+#include <asiolink/asiolink_utilities.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_fetch.h>
+#include <asiolink/io_service.h>
+
+using namespace asio;
+using namespace isc::dns;
+using namespace asio::ip;
+using namespace std;
+
+namespace asiolink {
+
+const asio::ip::address TEST_HOST(asio::ip::address::from_string("127.0.0.1"));
+const uint16_t TEST_PORT(5301);
+const int SEND_INTERVAL = 250; // Interval in ms between TCP sends
+const size_t MAX_SIZE = 64 * 1024; // Should be able to take 64kB
+
+// The tests are complex, so debug output has been left in (although disabled).
+// Set this to true to enable it.
+const bool DEBUG = false;
+
+/// \brief Test fixture for the asiolink::IOFetch.
+class IOFetchTest : public virtual ::testing::Test, public virtual IOFetch::Callback
+{
+public:
+ IOService service_; ///< Service to run the query
+ IOFetch::Result expected_; ///< Expected result of the callback
+ bool run_; ///< Did the callback run already?
+ Question question_; ///< What to ask
+ OutputBufferPtr result_buff_; ///< Buffer to hold result of fetch
+ OutputBufferPtr msgbuf_; ///< Buffer corresponding to known question
+ IOFetch udp_fetch_; ///< For UDP query test
+ IOFetch tcp_fetch_; ///< For TCP query test
+ IOFetch::Protocol protocol_; ///< Protocol being tested
+ size_t cumulative_; ///< Cumulative data received by "server".
+ deadline_timer timer_; ///< Timer to measure timeouts
+
+ // The next member is the buffer in which the "server" (implemented by the
+ // response handler methods in this class) receives the question sent by the
+ // fetch object.
+ uint8_t receive_buffer_[MAX_SIZE]; ///< Server receive buffer
+ vector<uint8_t> send_buffer_; ///< Server send buffer
+ uint16_t send_cumulative_; ///< Data sent so far
+
+ // Other data.
+ string return_data_; ///< Data returned by server
+ string test_data_; ///< Large string - here for convenience
+ bool debug_; ///< true to enable debug output
+ size_t tcp_send_size_; ///< Max size of TCP send
+
+ /// \brief Constructor
+ IOFetchTest() :
+ service_(),
+ expected_(IOFetch::NOTSET),
+ run_(false),
+ question_(Name("example.net"), RRClass::IN(), RRType::A()),
+ result_buff_(new OutputBuffer(512)),
+ msgbuf_(new OutputBuffer(512)),
+ udp_fetch_(IOFetch::UDP, service_, question_, IOAddress(TEST_HOST),
+ TEST_PORT, result_buff_, this, 100),
+ tcp_fetch_(IOFetch::TCP, service_, question_, IOAddress(TEST_HOST),
+ TEST_PORT, result_buff_, this, (16 * SEND_INTERVAL)),
+ // Timeout interval chosen to ensure no timeout
+ protocol_(IOFetch::TCP), // for initialization - will be changed
+ cumulative_(0),
+ timer_(service_.get_io_service()),
+ receive_buffer_(),
+ send_buffer_(),
+ send_cumulative_(0),
+ return_data_(""),
+ test_data_(""),
+ debug_(DEBUG),
+ tcp_send_size_(0)
+ {
+ // Construct the data buffer for question we expect to receive.
+ Message msg(Message::RENDER);
+ msg.setQid(0);
+ msg.setOpcode(Opcode::QUERY());
+ msg.setRcode(Rcode::NOERROR());
+ msg.setHeaderFlag(Message::HEADERFLAG_RD);
+ msg.addQuestion(question_);
+ MessageRenderer renderer(*msgbuf_);
+ msg.toWire(renderer);
+
+ // Initialize the test data to be returned: tests will return a
+ // substring of this data. (It's convenient to have this as a member of
+ // the class.)
+ //
+ // We could initialize the data with a single character, but as an added
+ // check we'll make ssre that it has some structure.
+
+ test_data_.clear();
+ test_data_.reserve(MAX_SIZE);
+ while (test_data_.size() < MAX_SIZE) {
+ test_data_ += "A message to be returned to the client that has "
+ "some sort of structure.";
+ }
+ }
+
+ /// \brief UDP Response handler (the "remote UDP DNS server")
+ ///
+ /// When IOFetch is sending data, this response handler emulates the remote
+ /// DNS server. It checks that the data sent by the IOFetch object is what
+ /// was expected to have been sent, then sends back a known buffer of data.
+ ///
+ /// \param remote Endpoint to which to send the answer
+ /// \param socket Socket to use to send the answer
+ /// \param ec ASIO error code, completion code of asynchronous I/O issued
+ /// by the "server" to receive data.
+ /// \param length Amount of data received.
+ void udpReceiveHandler(udp::endpoint* remote, udp::socket* socket,
+ error_code ec = error_code(), size_t length = 0) {
+ if (debug_) {
+ cout << "udpReceiveHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+
+ // The QID in the incoming data is random so set it to 0 for the
+ // data comparison check. (It is set to 0 in the buffer containing
+ // the expected data.)
+ receive_buffer_[0] = receive_buffer_[1] = 0;
+
+ // Check that length of the received data and the expected data are
+ // identical, then check that the data is identical as well.
+ EXPECT_EQ(msgbuf_->getLength(), length);
+ EXPECT_TRUE(equal(receive_buffer_, (receive_buffer_ + length - 1),
+ static_cast<const uint8_t*>(msgbuf_->getData())));
+
+ // Return a message back to the IOFetch object.
+ socket->send_to(asio::buffer(return_data_.c_str(), return_data_.size()),
+ *remote);
+ if (debug_) {
+ cout << "udpReceiveHandler(): returned " << return_data_.size() <<
+ " bytes to the client" << endl;
+ }
+ }
+
+ /// \brief Completion Handler for accepting TCP data
+ ///
+ /// Called when the remote system connects to the "server". It issues
+ /// an asynchronous read on the socket to read data.
+ ///
+ /// \param socket Socket on which data will be received
+ /// \param ec Boost error code, value should be zero.
+ void tcpAcceptHandler(tcp::socket* socket, error_code ec = error_code())
+ {
+ if (debug_) {
+ cout << "tcpAcceptHandler(): error = " << ec.value() << endl;
+ }
+
+ // Expect that the accept completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // Work out the maximum size of data we can send over it when we
+ // respond, then subtract 1kB or so for safety.
+ tcp::socket::send_buffer_size send_size;
+ socket->get_option(send_size);
+ if (send_size.value() < (2 * 1024)) {
+ FAIL() << "TCP send size is less than 2kB";
+ } else {
+ tcp_send_size_ = send_size.value() - 1024;
+ if (debug_) {
+ cout << "tcpacceptHandler(): will use send size = " << tcp_send_size_ << endl;
+ }
+ }
+
+ // Initiate a read on the socket.
+ cumulative_ = 0;
+ socket->async_receive(asio::buffer(receive_buffer_, sizeof(receive_buffer_)),
+ boost::bind(&IOFetchTest::tcpReceiveHandler, this, socket, _1, _2));
+ }
+
+ /// \brief Completion handler for receiving TCP data
+ ///
+ /// When IOFetch is sending data, this response handler emulates the remote
+ /// DNS server. It that all the data sent by the IOFetch object has been
+ /// received, issuing another read if not. If the data is complete, it is
+ /// compared to what is expected and a reply sent back to the IOFetch.
+ ///
+ /// \param socket Socket to use to send the answer
+ /// \param ec ASIO error code, completion code of asynchronous I/O issued
+ /// by the "server" to receive data.
+ /// \param length Amount of data received.
+ void tcpReceiveHandler(tcp::socket* socket, error_code ec = error_code(),
+ size_t length = 0)
+ {
+ if (debug_) {
+ cout << "tcpReceiveHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+ // Expect that the receive completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // If we haven't received all the data, issue another read.
+ cumulative_ += length;
+ bool complete = false;
+ if (cumulative_ > 2) {
+ uint16_t dns_length = readUint16(receive_buffer_);
+ complete = ((dns_length + 2) == cumulative_);
+ }
+
+ if (!complete) {
+ socket->async_receive(asio::buffer((receive_buffer_ + cumulative_),
+ (sizeof(receive_buffer_) - cumulative_)),
+ boost::bind(&IOFetchTest::tcpReceiveHandler, this, socket, _1, _2));
+ return;
+ }
+
+ // Check that length of the DNS message received is that expected, then
+ // compare buffers, zeroing the QID in the received buffer to match
+ // that set in our expected question. Note that due to the length
+ // field the QID in the received buffer is in the third and fourth
+ // bytes.
+ EXPECT_EQ(msgbuf_->getLength() + 2, cumulative_);
+ receive_buffer_[2] = receive_buffer_[3] = 0;
+ EXPECT_TRUE(equal((receive_buffer_ + 2), (receive_buffer_ + cumulative_ - 2),
+ static_cast<const uint8_t*>(msgbuf_->getData())));
+
+ // ... and return a message back. This has to be preceded by a two-byte
+ // count field.
+ send_buffer_.clear();
+ send_buffer_.push_back(0);
+ send_buffer_.push_back(0);
+ writeUint16(return_data_.size(), &send_buffer_[0]);
+ copy(return_data_.begin(), return_data_.end(), back_inserter(send_buffer_));
+
+ // Send the data. This is done in multiple writes with a delay between
+ // each to check that the reassembly of TCP packets from fragments works.
+ send_cumulative_ = 0;
+ tcpSendData(socket);
+ }
+
+ /// \brief Sent Data Over TCP
+ ///
+ /// Send the TCP data back to the IOFetch object. The data is sent in
+ /// three chunks - two of 16 bytes and the remainder, with a 250ms gap
+ /// between each. (Amounts of data smaller than one 32 bytes are sent in
+ /// one or two packets.)
+ ///
+ /// \param socket Socket over which send should take place
+ void tcpSendData(tcp::socket* socket) {
+ if (debug_) {
+ cout << "tcpSendData()" << endl;
+ }
+
+ // Decide what to send based on the cumulative count. At most we'll do
+ // two chunks of 16 bytes (with a 250ms gap between) and then the
+ // remainder.
+ uint8_t* send_ptr = &send_buffer_[send_cumulative_];
+ // Pointer to data to send
+ size_t amount = 16; // Amount of data to send
+ if (send_cumulative_ < (2 * amount)) {
+
+ // First or second time through, send at most 16 bytes
+ amount = min(amount, (send_buffer_.size() - send_cumulative_));
+
+ } else {
+
+ // For all subsequent times, send the remainder, maximised to
+ // whatever we have chosen for the maximum send size.
+ amount = min(tcp_send_size_,
+ (send_buffer_.size() - send_cumulative_));
+ }
+ if (debug_) {
+ cout << "tcpSendData(): sending " << amount << " bytes" << endl;
+ }
+
+
+ // ... and send it. The amount sent is also passed as the first
+ // argument of the send callback, as a check.
+ socket->async_send(asio::buffer(send_ptr, amount),
+ boost::bind(&IOFetchTest::tcpSendHandler, this,
+ amount, socket, _1, _2));
+ }
+
+ /// \brief Completion Handler for Sending TCP data
+ ///
+ /// Called when the asynchronous send of data back to the IOFetch object
+ /// by the TCP "server" in this class has completed. (This send has to
+ /// be asynchronous because control needs to return to the caller in order
+ /// for the IOService "run()" method to be called to run the handlers.)
+ ///
+ /// If not all the data has been sent, a short delay is instigated (during
+ /// which control returns to the IOService). This should force the queued
+ /// data to actually be sent and the IOFetch receive handler to be triggered.
+ /// In this way, the ability of IOFetch to handle fragmented TCP packets
+ /// should be checked.
+ ///
+ /// \param expected Number of bytes that were expected to have been sent.
+ /// \param socket Socket over which the send took place. Only used to
+ /// pass back to the send method.
+ /// \param ec Boost error code, value should be zero.
+ /// \param length Number of bytes sent.
+ void tcpSendHandler(size_t expected, tcp::socket* socket,
+ error_code ec = error_code(), size_t length = 0)
+ {
+ if (debug_) {
+ cout << "tcpSendHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+
+ EXPECT_EQ(0, ec.value()); // Expect no error
+ EXPECT_EQ(expected, length); // And that amount sent is as expected
+
+ // Do we need to send more?
+ send_cumulative_ += length;
+ if (send_cumulative_ < send_buffer_.size()) {
+
+ // Yes - set up a timer: the callback handler for the timer is
+ // tcpSendData, which will then send the next chunk. We pass the
+ // socket over which data should be sent as an argument to that
+ // function.
+ timer_.expires_from_now(boost::posix_time::milliseconds(SEND_INTERVAL));
+ timer_.async_wait(boost::bind(&IOFetchTest::tcpSendData, this,
+ socket));
+ }
+ }
+
+ /// \brief Fetch completion callback
+ ///
+ /// This is the callback's operator() method which is called when the fetch
+ /// is complete. It checks that the data received is the wire format of the
+ /// data sent back by the server.
+ ///
+ /// \param result Result indicated by the callback
+ void operator()(IOFetch::Result result) {
+ if (debug_) {
+ cout << "operator()(): result = " << result << endl;
+ }
+
+ EXPECT_EQ(expected_, result); // Check correct result returned
+ EXPECT_FALSE(run_); // Check it is run only once
+ run_ = true; // Note success
+
+ // If the expected result for SUCCESS, then this should have been called
+ // when one of the "servers" in this class has sent back return_data_.
+ // Check the data is as expected/
+ if (expected_ == IOFetch::SUCCESS) {
+ EXPECT_EQ(return_data_.size(), result_buff_->getLength());
+
+ const uint8_t* start = static_cast<const uint8_t*>(result_buff_->getData());
+ EXPECT_TRUE(equal(return_data_.begin(), return_data_.end(), start));
+ }
+
+ // ... and cause the run loop to exit.
+ service_.stop();
+ }
+
+ // The next set of methods are the tests themselves. A number of the TCP
+ // and UDP tests are very similar.
+
+ /// \brief Check for stop()
+ ///
+ /// Test that when we run the query and stop it after it was run, it returns
+ /// "stopped" correctly. (That is why stop() is posted to the service_ as
+ /// well instead of calling it.)
+ ///
+ /// \param protocol Test protocol
+ /// \param fetch Fetch object being tested
+ void stopTest(IOFetch::Protocol protocol, IOFetch& fetch) {
+ protocol_ = protocol;
+ expected_ = IOFetch::STOPPED;
+
+ // Post the query
+ service_.get_io_service().post(fetch);
+
+ // Post query_.stop() (yes, the boost::bind thing is just
+ // query_.stop()).
+ service_.get_io_service().post(
+ boost::bind(&IOFetch::stop, fetch, IOFetch::STOPPED));
+
+ // Run both of them. run() returns when everything in the I/O service
+ // queue has completed.
+ service_.run();
+ EXPECT_TRUE(run_);
+ }
+
+ /// \brief Premature stop test
+ ///
+ /// Test that when we queue the query to service_ and call stop() before it
+ /// gets executed, it acts sanely as well (eg. has the same result as
+ /// running stop() after - calls the callback).
+ ///
+ /// \param protocol Test protocol
+ /// \param fetch Fetch object being tested
+ void prematureStopTest(IOFetch::Protocol protocol, IOFetch& fetch) {
+ protocol_ = protocol;
+ expected_ = IOFetch::STOPPED;
+
+ // Stop before it is started
+ fetch.stop();
+ service_.get_io_service().post(fetch);
+
+ service_.run();
+ EXPECT_TRUE(run_);
+ }
+
+ /// \brief Timeout test
+ ///
+ /// Test that fetch times out when no answer arrives.
+ ///
+ /// \param protocol Test protocol
+ /// \param fetch Fetch object being tested
+ void timeoutTest(IOFetch::Protocol protocol, IOFetch& fetch) {
+ protocol_ = protocol;
+ expected_ = IOFetch::TIME_OUT;
+
+ service_.get_io_service().post(fetch);
+ service_.run();
+ EXPECT_TRUE(run_);
+ }
+
+ /// \brief Send/Receive Test
+ ///
+ /// Send a query to the server then receives a response.
+ ///
+ /// \param Test data to return to client
+ void tcpSendReturnTest(const std::string& return_data) {
+ if (debug_) {
+ cout << "tcpSendReturnTest(): data size = " << return_data.size() << endl;
+ }
+ return_data_ = return_data;
+ protocol_ = IOFetch::TCP;
+ expected_ = IOFetch::SUCCESS;
+
+ // Socket into which the connection will be accepted.
+ tcp::socket socket(service_.get_io_service());
+
+ // Acceptor object - called when the connection is made, the handler
+ // will initiate a read on the socket.
+ tcp::acceptor acceptor(service_.get_io_service(),
+ tcp::endpoint(tcp::v4(), TEST_PORT));
+ acceptor.async_accept(socket,
+ boost::bind(&IOFetchTest::tcpAcceptHandler, this, &socket, _1));
+
+ // Post the TCP fetch object to send the query and receive the response.
+ service_.get_io_service().post(tcp_fetch_);
+
+ // ... and execute all the callbacks. This exits when the fetch
+ // completes.
+ service_.run();
+ EXPECT_TRUE(run_); // Make sure the callback did execute
+
+ // Tidy up
+ socket.close();
+ }
+};
+
+// Check the protocol
+TEST_F(IOFetchTest, Protocol) {
+ EXPECT_EQ(IOFetch::UDP, udp_fetch_.getProtocol());
+ EXPECT_EQ(IOFetch::TCP, tcp_fetch_.getProtocol());
+}
+
+// UDP Stop test - see IOFetchTest::stopTest() header.
+TEST_F(IOFetchTest, UdpStop) {
+ stopTest(IOFetch::UDP, udp_fetch_);
+}
+
+// UDP premature stop test - see IOFetchTest::prematureStopTest() header.
+TEST_F(IOFetchTest, UdpPrematureStop) {
+ prematureStopTest(IOFetch::UDP, udp_fetch_);
+}
+
+// UDP premature stop test - see IOFetchTest::timeoutTest() header.
+TEST_F(IOFetchTest, UdpTimeout) {
+ timeoutTest(IOFetch::UDP, udp_fetch_);
+}
+
+// UDP SendReceive test. Set up a UDP server then ports a UDP fetch object.
+// This will send question_ to the server and receive the answer back from it.
+TEST_F(IOFetchTest, UdpSendReceive) {
+ protocol_ = IOFetch::UDP;
+ expected_ = IOFetch::SUCCESS;
+
+ // Set up the server.
+ udp::socket socket(service_.get_io_service(), udp::v4());
+ socket.set_option(socket_base::reuse_address(true));
+ socket.bind(udp::endpoint(TEST_HOST, TEST_PORT));
+ return_data_ = "Message returned to the client";
+
+ udp::endpoint remote;
+ socket.async_receive_from(asio::buffer(receive_buffer_, sizeof(receive_buffer_)),
+ remote,
+ boost::bind(&IOFetchTest::udpReceiveHandler, this, &remote, &socket,
+ _1, _2));
+ service_.get_io_service().post(udp_fetch_);
+ if (debug_) {
+ cout << "udpSendReceive: async_receive_from posted, waiting for callback" <<
+ endl;
+ }
+ service_.run();
+
+ socket.close();
+
+ EXPECT_TRUE(run_);;
+}
+
+// Do the same tests for TCP transport
+
+TEST_F(IOFetchTest, TcpStop) {
+ stopTest(IOFetch::TCP, tcp_fetch_);
+}
+
+TEST_F(IOFetchTest, TcpPrematureStop) {
+ prematureStopTest(IOFetch::TCP, tcp_fetch_);
+}
+
+TEST_F(IOFetchTest, TcpTimeout) {
+ timeoutTest(IOFetch::TCP, tcp_fetch_);
+}
+
+// Test with values at or near 0, then at or near the chunk size (16 and 32
+// bytes, the sizes of the first two packets) then up to 65535. These are done
+// in separate tests because in practice a new IOFetch is created for each
+// query/response exchange and we don't want to confuse matters in the test
+// by running the test with an IOFetch that has already done one exchange.
+
+TEST_F(IOFetchTest, TcpSendReceive0) {
+ tcpSendReturnTest(test_data_.substr(0, 0));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive1) {
+ tcpSendReturnTest(test_data_.substr(0, 1));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive15) {
+ tcpSendReturnTest(test_data_.substr(0, 15));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive16) {
+ tcpSendReturnTest(test_data_.substr(0, 16));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive17) {
+ tcpSendReturnTest(test_data_.substr(0, 17));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive31) {
+ tcpSendReturnTest(test_data_.substr(0, 31));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive32) {
+ tcpSendReturnTest(test_data_.substr(0, 32));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive33) {
+ tcpSendReturnTest(test_data_.substr(0, 33));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive4096) {
+ tcpSendReturnTest(test_data_.substr(0, 4096));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive8192) {
+ tcpSendReturnTest(test_data_.substr(0, 8192));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive16384) {
+ tcpSendReturnTest(test_data_.substr(0, 16384));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive32768) {
+ tcpSendReturnTest(test_data_.substr(0, 32768));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive65535) {
+ tcpSendReturnTest(test_data_.substr(0, 65535));
+}
+
+} // namespace asiolink
diff --git a/src/lib/asiolink/tests/io_service_unittest.cc b/src/lib/asiolink/tests/io_service_unittest.cc
new file mode 100644
index 0000000..779d03e
--- /dev/null
+++ b/src/lib/asiolink/tests/io_service_unittest.cc
@@ -0,0 +1,116 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <gtest/gtest.h>
+
+#include <asio.hpp>
+#include <asiolink/asiolink.h>
+
+using namespace asiolink;
+
+const char* const TEST_SERVER_PORT = "53535";
+const char* const TEST_CLIENT_PORT = "53536";
+const char* const TEST_IPV6_ADDR = "::1";
+const char* const TEST_IPV4_ADDR = "127.0.0.1";
+
+TEST(IOServiceTest, badPort) {
+ IOService io_service;
+ EXPECT_THROW(DNSService(io_service, *"65536", true, false, NULL, NULL, NULL), IOError);
+ EXPECT_THROW(DNSService(io_service, *"53210.0", true, false, NULL, NULL, NULL), IOError);
+ EXPECT_THROW(DNSService(io_service, *"-1", true, false, NULL, NULL, NULL), IOError);
+ EXPECT_THROW(DNSService(io_service, *"domain", true, false, NULL, NULL, NULL), IOError);
+}
+
+TEST(IOServiceTest, badAddress) {
+ IOService io_service;
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.1.1", NULL, NULL, NULL), IOError);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"2001:db8:::1", NULL, NULL, NULL), IOError);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"localhost", NULL, NULL, NULL), IOError);
+}
+
+TEST(IOServiceTest, unavailableAddress) {
+ IOService io_service;
+ // These addresses should generally be unavailable as a valid local
+ // address, although there's no guarantee in theory.
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.0", NULL, NULL, NULL), IOError);
+
+ // Some OSes would simply reject binding attempt for an AF_INET6 socket
+ // to an IPv4-mapped IPv6 address. Even if those that allow it, since
+ // the corresponding IPv4 address is the same as the one used in the
+ // AF_INET socket case above, it should at least show the same result
+ // as the previous one.
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:192.0.2.0", NULL, NULL, NULL), IOError);
+}
+
+TEST(IOServiceTest, duplicateBind_v6) {
+ // In each sub test case, second attempt should fail due to duplicate bind
+ IOService io_service;
+
+ // IPv6, "any" address
+ DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL), IOError);
+ delete dns_service;
+
+}
+
+TEST(IOServiceTest, duplicateBind_v6_address) {
+ // In each sub test case, second attempt should fail due to duplicate bind
+ IOService io_service;
+
+ // IPv6, specific address
+ DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL), IOError);
+ delete dns_service;
+
+}
+
+TEST(IOServiceTest, duplicateBind_v4) {
+ // In each sub test case, second attempt should fail due to duplicate bind
+ IOService io_service;
+
+ // IPv4, "any" address
+ DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL), IOError);
+ delete dns_service;
+
+}
+
+TEST(IOServiceTest, duplicateBind_v4_address) {
+ // In each sub test case, second attempt should fail due to duplicate bind
+ IOService io_service;
+
+ // IPv4, specific address
+ DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL), IOError);
+ delete dns_service;
+}
+
+// Disabled because IPv4-mapped addresses don't seem to be working with
+// the IOService constructor
+TEST(IOServiceTest, DISABLED_IPv4MappedDuplicateBind) {
+ IOService io_service;
+ // Duplicate bind on IPv4-mapped IPv6 address
+ DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL), IOError);
+ delete dns_service;
+
+ // XXX:
+ // Currently, this throws an "invalid argument" exception. I have
+ // not been able to get IPv4-mapped addresses to work.
+ dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL), IOError);
+ delete dns_service;
+}
+
diff --git a/src/lib/asiolink/tests/io_socket_unittest.cc b/src/lib/asiolink/tests/io_socket_unittest.cc
new file mode 100644
index 0000000..6538550
--- /dev/null
+++ b/src/lib/asiolink/tests/io_socket_unittest.cc
@@ -0,0 +1,32 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <gtest/gtest.h>
+
+#include <netinet/in.h>
+
+#include <asio.hpp>
+#include <asiolink/io_socket.h>
+
+using namespace asiolink;
+
+TEST(IOSocketTest, dummySockets) {
+ EXPECT_EQ(IPPROTO_UDP, IOSocket::getDummyUDPSocket().getProtocol());
+ EXPECT_EQ(IPPROTO_TCP, IOSocket::getDummyTCPSocket().getProtocol());
+ EXPECT_EQ(-1, IOSocket::getDummyUDPSocket().getNative());
+ EXPECT_EQ(-1, IOSocket::getDummyTCPSocket().getNative());
+}
+
+
diff --git a/src/lib/asiolink/tests/qid_gen_unittest.cc b/src/lib/asiolink/tests/qid_gen_unittest.cc
new file mode 100644
index 0000000..3ad8a03
--- /dev/null
+++ b/src/lib/asiolink/tests/qid_gen_unittest.cc
@@ -0,0 +1,59 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+
+/// \brief Test of QidGenerator
+///
+
+#include <gtest/gtest.h>
+
+#include <asiolink/qid_gen.h>
+#include <dns/message.h>
+
+// Tests the operation of the Qid generator
+
+// Check that getInstance returns a singleton
+TEST(QidGenerator, singleton) {
+ asiolink::QidGenerator& g1 = asiolink::QidGenerator::getInstance();
+ asiolink::QidGenerator& g2 = asiolink::QidGenerator::getInstance();
+
+ EXPECT_TRUE(&g1 == &g2);
+}
+
+TEST(QidGenerator, generate) {
+ // We'll assume that boost's generator is 'good enough', and won't
+ // do full statistical checking here. Let's just call it the xkcd
+ // test (http://xkcd.com/221/), and check if three consecutive
+ // generates are not all the same.
+ isc::dns::qid_t one, two, three;
+ asiolink::QidGenerator& gen = asiolink::QidGenerator::getInstance();
+ one = gen.generateQid();
+ two = gen.generateQid();
+ three = gen.generateQid();
+ ASSERT_FALSE((one == two) && (one == three));
+}
diff --git a/src/lib/asiolink/tests/run_unittests.cc b/src/lib/asiolink/tests/run_unittests.cc
index b481784..c285f9e 100644
--- a/src/lib/asiolink/tests/run_unittests.cc
+++ b/src/lib/asiolink/tests/run_unittests.cc
@@ -14,13 +14,15 @@
#include <gtest/gtest.h>
+#include <log/root_logger_name.h>
#include <dns/tests/unittest_util.h>
int
main(int argc, char* argv[])
{
- ::testing::InitGoogleTest(&argc, argv);
- isc::UnitTestUtil::addDataPath(TEST_DATA_DIR);
+ ::testing::InitGoogleTest(&argc, argv); // Initialize Google test
+ isc::log::setRootLoggerName("unittest"); // Set a root logger name
+ isc::UnitTestUtil::addDataPath(TEST_DATA_DIR); // Add location of test data
return (RUN_ALL_TESTS());
}
diff --git a/src/lib/asiolink/tests/tcp_endpoint_unittest.cc b/src/lib/asiolink/tests/tcp_endpoint_unittest.cc
new file mode 100644
index 0000000..3787e1c
--- /dev/null
+++ b/src/lib/asiolink/tests/tcp_endpoint_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <asio.hpp>
+#include <asiolink/io_address.h>
+#include <asiolink/tcp_endpoint.h>
+
+using namespace asiolink;
+using namespace std;
+
+// This test checks that the endpoint can manage its own internal
+// asio::ip::tcp::endpoint object.
+
+TEST(TCPEndpointTest, v4Address) {
+ const string test_address("192.0.2.1");
+ const unsigned short test_port = 5301;
+
+ IOAddress address(test_address);
+ TCPEndpoint endpoint(address, test_port);
+
+ EXPECT_TRUE(address == endpoint.getAddress());
+ EXPECT_EQ(test_port, endpoint.getPort());
+ EXPECT_EQ(IPPROTO_TCP, endpoint.getProtocol());
+ EXPECT_EQ(AF_INET, endpoint.getFamily());
+}
+
+TEST(TCPEndpointTest, v6Address) {
+ const string test_address("2001:db8::1235");
+ const unsigned short test_port = 5302;
+
+ IOAddress address(test_address);
+ TCPEndpoint endpoint(address, test_port);
+
+ EXPECT_TRUE(address == endpoint.getAddress());
+ EXPECT_EQ(test_port, endpoint.getPort());
+ EXPECT_EQ(IPPROTO_TCP, endpoint.getProtocol());
+ EXPECT_EQ(AF_INET6, endpoint.getFamily());
+}
diff --git a/src/lib/asiolink/tests/tcp_socket_unittest.cc b/src/lib/asiolink/tests/tcp_socket_unittest.cc
new file mode 100644
index 0000000..f0a45ee
--- /dev/null
+++ b/src/lib/asiolink/tests/tcp_socket_unittest.cc
@@ -0,0 +1,515 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// \brief Test of TCPSocket
+///
+/// Tests the fuctionality of a TCPSocket by working through an open-send-
+/// receive-close sequence and checking that the asynchronous notifications
+/// work.
+
+#include <string>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <cstddef>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <dns/buffer.h>
+
+#include <asio.hpp>
+
+#include <asiolink/asiolink_utilities.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+
+using namespace asio;
+using namespace asio::ip;
+using namespace asiolink;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+
+const char SERVER_ADDRESS[] = "127.0.0.1";
+const unsigned short SERVER_PORT = 5303;
+
+// TODO: Shouldn't we send something that is real message?
+const char OUTBOUND_DATA[] = "Data sent from client to server";
+const char INBOUND_DATA[] = "Returned data from server to client";
+}
+
+/// An instance of this object is passed to the asynchronous I/O functions
+/// and the operator() method is called when when an asynchronous I/O completes.
+/// The arguments to the completion callback are stored for later retrieval.
+class TCPCallback {
+public:
+ /// \brief Operations the server is doing
+ enum Operation {
+ ACCEPT = 0, ///< accept() was issued
+ OPEN = 1, /// Client connected to server
+ READ = 2, ///< Asynchronous read completed
+ WRITE = 3, ///< Asynchronous write completed
+ NONE = 4 ///< "Not set" state
+ };
+
+ /// \brief Minimim size of buffers
+ enum {
+ MIN_SIZE = (64 * 1024 + 2) ///< 64kB + two bytes for a count
+ };
+
+ struct PrivateData {
+ PrivateData() :
+ error_code_(), length_(0), cumulative_(0), expected_(0), offset_(0),
+ name_(""), queued_(NONE), called_(NONE)
+ {}
+
+ asio::error_code error_code_; ///< Completion error code
+ size_t length_; ///< Bytes transferred in this I/O
+ size_t cumulative_; ///< Cumulative bytes transferred
+ size_t expected_; ///< Expected amount of data
+ size_t offset_; ///< Where to put data in buffer
+ std::string name_; ///< Which of the objects this is
+ Operation queued_; ///< Queued operation
+ Operation called_; ///< Which callback called
+ uint8_t data_[MIN_SIZE]; ///< Receive buffer
+
+ };
+
+ /// \brief Constructor
+ ///
+ /// Constructs the object. It also creates the data member pointed to by
+ /// a shared pointer. When used as a callback object, this is copied as it
+ /// is passed into the asynchronous function. This means that there are two
+ /// objects and inspecting the one we passed in does not tell us anything.
+ ///
+ /// Therefore we use a boost::shared_ptr. When the object is copied, the
+ /// shared pointer is copied, which leaves both objects pointing to the same
+ /// data.
+ ///
+ /// \param which Which of the two callback objects this is
+ TCPCallback(std::string which) : ptr_(new PrivateData())
+ {
+ ptr_->name_ = which;
+ }
+
+ /// \brief Destructor
+ ///
+ /// No code needed, destroying the shared pointer destroys the private data.
+ virtual ~TCPCallback()
+ {}
+
+ /// \brief Client Callback Function
+ ///
+ /// Called when an asynchronous operation is completed by the client, this
+ /// stores the origin of the operation in the client_called_ data member.
+ ///
+ /// \param ec I/O completion error code passed to callback function.
+ /// \param length Number of bytes transferred
+ void operator()(asio::error_code ec = asio::error_code(),
+ size_t length = 0)
+ {
+ setCode(ec.value());
+ ptr_->called_ = ptr_->queued_;
+ ptr_->length_ = length;
+ }
+
+ /// \brief Get I/O completion error code
+ int getCode() {
+ return (ptr_->error_code_.value());
+ }
+
+ /// \brief Set I/O completion code
+ ///
+ /// \param code New value of completion code
+ void setCode(int code) {
+ ptr_->error_code_ = asio::error_code(code, asio::error_code().category());
+ }
+
+ /// \brief Get number of bytes transferred in I/O
+ size_t& length() {
+ return (ptr_->length_);
+ }
+
+ /// \brief Get cumulative number of bytes transferred in I/O
+ size_t& cumulative() {
+ return (ptr_->cumulative_);
+ }
+
+ /// \brief Get expected amount of data
+ size_t& expected() {
+ return (ptr_->expected_);
+ }
+
+ /// \brief Get offset intodData
+ size_t& offset() {
+ return (ptr_->offset_);
+ }
+
+ /// \brief Get data member
+ uint8_t* data() {
+ return (ptr_->data_);
+ }
+
+ /// \brief Get flag to say what was queued
+ Operation& queued() {
+ return (ptr_->queued_);
+ }
+
+ /// \brief Get flag to say when callback was called
+ Operation& called() {
+ return (ptr_->called_);
+ }
+
+ /// \brief Return instance of callback name
+ std::string& name() {
+ return (ptr_->name_);
+ }
+
+private:
+ boost::shared_ptr<PrivateData> ptr_; ///< Pointer to private data
+};
+
+
+// Read Server Data
+//
+// Called in the part of the test that has the client send a message to the
+// server, this loops until all the data has been read (synchronously) by the
+// server.
+//
+// "All the data read" means that the server has received a message that is
+// preceded by a two-byte count field and that the total amount of data received
+// from the remote end is equal to the value in the count field plus two bytes
+// for the count field itself.
+//
+// \param socket Socket on which the server is reading data
+// \param server_cb Structure in which server data is held.
+void
+serverRead(tcp::socket& socket, TCPCallback& server_cb) {
+
+ // As we may need to read multiple times, keep a count of the cumulative
+ // amount of data read and do successive reads into the appropriate part
+ // of the buffer.
+ //
+ // Note that there are no checks for buffer overflow - this is a test
+ // program and we have sized the buffer to be large enough for the test.
+ server_cb.cumulative() = 0;
+
+ bool complete = false;
+ while (!complete) {
+
+ // Read block of data and update cumulative amount of data received.
+ server_cb.length() = socket.receive(
+ asio::buffer(server_cb.data() + server_cb.cumulative(),
+ TCPCallback::MIN_SIZE - server_cb.cumulative()));
+ server_cb.cumulative() += server_cb.length();
+
+ // If we have read at least two bytes, we can work out how much we
+ // should be reading.
+ if (server_cb.cumulative() >= 2) {
+ server_cb.expected() = readUint16(server_cb.data());
+ if ((server_cb.expected() + 2) == server_cb.cumulative()) {
+
+ // Amount of data read from socket equals the size of the
+ // message (as indicated in the first two bytes of the message)
+ // plus the size of the count field. Therefore we have received
+ // all the data.
+ complete = true;
+ }
+ }
+ }
+}
+
+// Receive complete method should return true only if the count in the first
+// two bytes is equal to the size of the rest if the buffer.
+
+TEST(TCPSocket, processReceivedData) {
+ const uint16_t PACKET_SIZE = 16382; // Amount of "real" data in the buffer
+
+ IOService service; // Used to instantiate socket
+ TCPSocket<TCPCallback> test(service); // Socket under test
+ uint8_t inbuff[PACKET_SIZE + 2]; // Buffer to check
+ OutputBufferPtr outbuff(new OutputBuffer(16));
+ // Where data is put
+ size_t expected; // Expected amount of data
+ size_t offset; // Where to put next data
+ size_t cumulative; // Cumulative data received
+
+ // Set some dummy values in the buffer to check
+ for (size_t i = 0; i < sizeof(inbuff); ++i) {
+ inbuff[i] = i % 256;
+ }
+
+ // Check that the method will handle various receive sizes.
+ writeUint16(PACKET_SIZE, inbuff);
+
+ cumulative = 0;
+ offset = 0;
+ expected = 0;
+ outbuff->clear();
+ bool complete = test.processReceivedData(inbuff, 1, cumulative, offset,
+ expected, outbuff);
+ EXPECT_FALSE(complete);
+ EXPECT_EQ(1, cumulative);
+ EXPECT_EQ(1, offset);
+ EXPECT_EQ(0, expected);
+ EXPECT_EQ(0, outbuff->getLength());
+
+ // Now pretend that we've received one more byte.
+ complete = test.processReceivedData(inbuff, 1, cumulative, offset, expected,
+ outbuff);
+ EXPECT_FALSE(complete);
+ EXPECT_EQ(2, cumulative);
+ EXPECT_EQ(0, offset);
+ EXPECT_EQ(PACKET_SIZE, expected);
+ EXPECT_EQ(0, outbuff->getLength());
+
+ // Add another two bytes. However, this time note that we have to offset
+ // in the input buffer because it is expected that the next chunk of data
+ // from the connection will be read into the start of the buffer.
+ complete = test.processReceivedData(inbuff + cumulative, 2, cumulative,
+ offset, expected, outbuff);
+ EXPECT_FALSE(complete);
+ EXPECT_EQ(4, cumulative);
+ EXPECT_EQ(0, offset);
+ EXPECT_EQ(PACKET_SIZE, expected);
+ EXPECT_EQ(2, outbuff->getLength());
+
+ const uint8_t* dataptr = static_cast<const uint8_t*>(outbuff->getData());
+ EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr));
+
+ // And add the remaining data. Remember that "inbuff" is "PACKET_SIZE + 2"
+ // long.
+ complete = test.processReceivedData(inbuff + cumulative,
+ PACKET_SIZE + 2 - cumulative,
+ cumulative, offset, expected, outbuff);
+ EXPECT_TRUE(complete);
+ EXPECT_EQ(PACKET_SIZE + 2, cumulative);
+ EXPECT_EQ(0, offset);
+ EXPECT_EQ(PACKET_SIZE, expected);
+ EXPECT_EQ(PACKET_SIZE, outbuff->getLength());
+ dataptr = static_cast<const uint8_t*>(outbuff->getData());
+ EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr));
+}
+
+// TODO: Need to add a test to check the cancel() method
+
+// Tests the operation of a TCPSocket by opening it, sending an asynchronous
+// message to a server, receiving an asynchronous message from the server and
+// closing.
+TEST(TCPSocket, SequenceTest) {
+
+ // Common objects.
+ IOService service; // Service object for async control
+
+ // The client - the TCPSocket being tested
+ TCPSocket<TCPCallback> client(service);// Socket under test
+ TCPCallback client_cb("Client"); // Async I/O callback function
+ TCPEndpoint client_remote_endpoint; // Where client receives message from
+ OutputBufferPtr client_buffer(new OutputBuffer(128));
+ // Received data is put here
+
+ // The server - with which the client communicates.
+ IOAddress server_address(SERVER_ADDRESS);
+ // Address of target server
+ TCPCallback server_cb("Server"); // Server callback
+ TCPEndpoint server_endpoint(server_address, SERVER_PORT);
+ // Endpoint describing server
+ TCPEndpoint server_remote_endpoint; // Address where server received message from
+ tcp::socket server_socket(service.get_io_service());
+ // Socket used for server
+
+ // Step 1. Create the connection between the client and the server. Set
+ // up the server to accept incoming connections and have the client open
+ // a channel to it.
+
+ // Set up server - open socket and queue an accept.
+ server_cb.queued() = TCPCallback::ACCEPT;
+ server_cb.called() = TCPCallback::NONE;
+ server_cb.setCode(42); // Some error
+ tcp::acceptor acceptor(service.get_io_service(),
+ tcp::endpoint(tcp::v4(), SERVER_PORT));
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ acceptor.async_accept(server_socket, server_cb);
+
+ // Set up client - connect to the server.
+ client_cb.queued() = TCPCallback::OPEN;
+ client_cb.called() = TCPCallback::NONE;
+ client_cb.setCode(43); // Some error
+ EXPECT_FALSE(client.isOpenSynchronous());
+ client.open(&server_endpoint, client_cb);
+
+ // Run the open and the accept callback and check that they ran.
+ service.run_one();
+ service.run_one();
+
+ EXPECT_EQ(TCPCallback::ACCEPT, server_cb.called());
+ EXPECT_EQ(0, server_cb.getCode());
+
+ EXPECT_EQ(TCPCallback::OPEN, client_cb.called());
+ EXPECT_EQ(0, client_cb.getCode());
+
+ // Step 2. Get the client to write to the server asynchronously. The
+ // server will loop reading the data synchronously.
+
+ // Write asynchronously to the server.
+ client_cb.called() = TCPCallback::NONE;
+ client_cb.queued() = TCPCallback::WRITE;
+ client_cb.setCode(143); // Arbitrary number
+ client_cb.length() = 0;
+ client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb);
+
+ // Wait for the client callback to complete. (Must do this first on
+ // Solaris: if we do the synchronous read first, the test hangs.)
+ service.run_one();
+
+ // Synchronously read the data from the server.;
+ serverRead(server_socket, server_cb);
+
+ // Check the client state
+ EXPECT_EQ(TCPCallback::WRITE, client_cb.called());
+ EXPECT_EQ(0, client_cb.getCode());
+ EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, client_cb.length());
+
+ // ... and check what the server received.
+ EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, server_cb.cumulative());
+ EXPECT_TRUE(equal(OUTBOUND_DATA,
+ (OUTBOUND_DATA + (sizeof(OUTBOUND_DATA) - 1)),
+ (server_cb.data() + 2)));
+
+ // Step 3. Get the server to write all the data asynchronously and have the
+ // client loop (asynchronously) reading the data. Note that we copy the
+ // data into the server's internal buffer in order to precede it with a two-
+ // byte count field.
+
+ // Have the server write asynchronously to the client.
+ server_cb.called() = TCPCallback::NONE;
+ server_cb.queued() = TCPCallback::WRITE;
+ server_cb.length() = 0;
+ server_cb.cumulative() = 0;
+
+ writeUint16(sizeof(INBOUND_DATA), server_cb.data());
+ copy(INBOUND_DATA, (INBOUND_DATA + sizeof(INBOUND_DATA) - 1),
+ (server_cb.data() + 2));
+ server_socket.async_send(asio::buffer(server_cb.data(),
+ (sizeof(INBOUND_DATA) + 2)),
+ server_cb);
+
+ // Have the client read asynchronously.
+ client_cb.called() = TCPCallback::NONE;
+ client_cb.queued() = TCPCallback::READ;
+ client_cb.length() = 0;
+ client_cb.cumulative() = 0;
+ client_cb.expected() = 0;
+ client_cb.offset() = 0;
+
+ client.asyncReceive(client_cb.data(), TCPCallback::MIN_SIZE,
+ client_cb.offset(), &client_remote_endpoint,
+ client_cb);
+
+ // Run the callbacks. Several options are possible depending on how ASIO
+ // is implemented and whether the message gets fragmented:
+ //
+ // 1) The send handler may complete immediately, regardess of whether the
+ // data has been read by the client. (This is the most likely.)
+ // 2) The send handler may only run after all the data has been read by
+ // the client. (This could happen if the client's TCP buffers were too
+ // small so the data was not transferred to the "remote" system until the
+ // remote buffer has been emptied one or more times.)
+ // 3) The client handler may be run a number of times to handle the message
+ // fragments and the server handler may run between calls of the client
+ // handler.
+ //
+ // So loop, running one handler at a time until we are certain that all the
+ // handlers have run.
+
+ bool server_complete = false;
+ bool client_complete = false;
+ while (!server_complete || !client_complete) {
+ service.run_one();
+
+ // Has the server run?
+ if (!server_complete) {
+ if (server_cb.called() == server_cb.queued()) {
+
+ // Yes. Check that the send completed successfully and that
+ // all the data that was expected to have been sent was in fact
+ // sent.
+ EXPECT_EQ(0, server_cb.getCode());
+ EXPECT_EQ((sizeof(INBOUND_DATA) + 2), server_cb.length());
+ server_complete = true;
+ continue;
+ }
+ }
+
+ if (!client_complete) {
+
+ // Client callback must have run. Check that it ran OK.
+ EXPECT_EQ(TCPCallback::READ, client_cb.called());
+ EXPECT_EQ(0, client_cb.getCode());
+
+ // Check if we need to queue another read, copying the data into
+ // the output buffer as we do so.
+ client_complete = client.processReceivedData(client_cb.data(),
+ client_cb.length(),
+ client_cb.cumulative(),
+ client_cb.offset(),
+ client_cb.expected(),
+ client_buffer);
+
+ // If the data is not complete, queue another read.
+ if (! client_complete) {
+ client_cb.called() = TCPCallback::NONE;
+ client_cb.queued() = TCPCallback::READ;
+ client_cb.length() = 0;
+ client.asyncReceive(client_cb.data(), TCPCallback::MIN_SIZE ,
+ client_cb.offset(), &client_remote_endpoint,
+ client_cb);
+ }
+ }
+ }
+
+ // Both the send and the receive have completed. Check that the received
+ // is what was sent.
+
+ // Check the client state
+ EXPECT_EQ(TCPCallback::READ, client_cb.called());
+ EXPECT_EQ(0, client_cb.getCode());
+ EXPECT_EQ(sizeof(INBOUND_DATA) + 2, client_cb.cumulative());
+ EXPECT_EQ(sizeof(INBOUND_DATA), client_buffer->getLength());
+
+ // ... and check what the server sent.
+ EXPECT_EQ(TCPCallback::WRITE, server_cb.called());
+ EXPECT_EQ(0, server_cb.getCode());
+ EXPECT_EQ(sizeof(INBOUND_DATA) + 2, server_cb.length());
+
+ // ... and that what was sent is what was received.
+ const uint8_t* received = static_cast<const uint8_t*>(client_buffer->getData());
+ EXPECT_TRUE(equal(INBOUND_DATA, (INBOUND_DATA + (sizeof(INBOUND_DATA) - 1)),
+ received));
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.close());
+ EXPECT_NO_THROW(server_socket.close());
+}
diff --git a/src/lib/asiolink/tests/udp_endpoint_unittest.cc b/src/lib/asiolink/tests/udp_endpoint_unittest.cc
new file mode 100644
index 0000000..18135ec
--- /dev/null
+++ b/src/lib/asiolink/tests/udp_endpoint_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <asio.hpp>
+#include <asiolink/io_address.h>
+#include <asiolink/udp_endpoint.h>
+
+using namespace asiolink;
+using namespace std;
+
+// This test checks that the endpoint can manage its own internal
+// asio::ip::udp::endpoint object.
+
+TEST(UDPEndpointTest, v4Address) {
+ const string test_address("192.0.2.1");
+ const unsigned short test_port = 5301;
+
+ IOAddress address(test_address);
+ UDPEndpoint endpoint(address, test_port);
+
+ EXPECT_TRUE(address == endpoint.getAddress());
+ EXPECT_EQ(test_port, endpoint.getPort());
+ EXPECT_EQ(IPPROTO_UDP, endpoint.getProtocol());
+ EXPECT_EQ(AF_INET, endpoint.getFamily());
+}
+
+TEST(UDPEndpointTest, v6Address) {
+ const string test_address("2001:db8::1235");
+ const unsigned short test_port = 5302;
+
+ IOAddress address(test_address);
+ UDPEndpoint endpoint(address, test_port);
+
+ EXPECT_TRUE(address == endpoint.getAddress());
+ EXPECT_EQ(test_port, endpoint.getPort());
+ EXPECT_EQ(IPPROTO_UDP, endpoint.getProtocol());
+ EXPECT_EQ(AF_INET6, endpoint.getFamily());
+}
diff --git a/src/lib/asiolink/tests/udp_socket_unittest.cc b/src/lib/asiolink/tests/udp_socket_unittest.cc
new file mode 100644
index 0000000..8563d22
--- /dev/null
+++ b/src/lib/asiolink/tests/udp_socket_unittest.cc
@@ -0,0 +1,333 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// \brief Test of UDPSocket
+///
+/// Tests the fuctionality of a UDPSocket by working through an open-send-
+/// receive-close sequence and checking that the asynchronous notifications
+/// work.
+
+#include <string>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <cstddef>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <dns/buffer.h>
+
+#include <asio.hpp>
+
+#include <asiolink/asiolink_utilities.h>
+#include <asiolink/io_service.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+
+using namespace asio;
+using namespace asiolink;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+
+const char SERVER_ADDRESS[] = "127.0.0.1";
+const unsigned short SERVER_PORT = 5301;
+
+// TODO: Shouldn't we send something that is real message?
+const char OUTBOUND_DATA[] = "Data sent from client to server";
+const char INBOUND_DATA[] = "Returned data from server to client";
+}
+
+///
+/// An instance of this object is passed to the asynchronous I/O functions
+/// and the operator() method is called when when an asynchronous I/O
+/// completes. The arguments to the completion callback are stored for later
+/// retrieval.
+class UDPCallback {
+public:
+
+ struct PrivateData {
+ PrivateData() :
+ error_code_(), length_(0), called_(false), name_("")
+ {}
+
+ asio::error_code error_code_; ///< Completion error code
+ size_t length_; ///< Number of bytes transferred
+ bool called_; ///< Set true when callback called
+ std::string name_; ///< Which of the objects this is
+ };
+
+ /// \brief Constructor
+ ///
+ /// Constructs the object. It also creates the data member pointed to by
+ /// a shared pointer. When used as a callback object, this is copied as it
+ /// is passed into the asynchronous function. This means that there are two
+ /// objects and inspecting the one we passed in does not tell us anything.
+ ///
+ /// Therefore we use a boost::shared_ptr. When the object is copied, the
+ /// shared pointer is copied, which leaves both objects pointing to the same
+ /// data.
+ ///
+ /// \param which Which of the two callback objects this is
+ UDPCallback(std::string which) : ptr_(new PrivateData())
+ {
+ setName(which);
+ }
+
+ /// \brief Destructor
+ ///
+ /// No code needed, destroying the shared pointer destroys the private data.
+ virtual ~UDPCallback()
+ {}
+
+ /// \brief Callback Function
+ ///
+ /// Called when an asynchronous I/O completes, this stores the
+ /// completion error code and the number of bytes transferred.
+ ///
+ /// \param ec I/O completion error code passed to callback function.
+ /// \param length Number of bytes transferred
+ virtual void operator()(asio::error_code ec, size_t length = 0) {
+ ptr_->error_code_ = ec;
+ setLength(length);
+ setCalled(true);
+ }
+
+ /// \brief Get I/O completion error code
+ int getCode() {
+ return (ptr_->error_code_.value());
+ }
+
+ /// \brief Set I/O completion code
+ ///
+ /// \param code New value of completion code
+ void setCode(int code) {
+ ptr_->error_code_ = asio::error_code(code, asio::error_code().category());
+ }
+
+ /// \brief Get number of bytes transferred in I/O
+ size_t getLength() const {
+ return (ptr_->length_);
+ }
+
+ /// \brief Set number of bytes transferred in I/O
+ ///
+ /// \param length New value of length parameter
+ void setLength(size_t length) {
+ ptr_->length_ = length;
+ }
+
+ /// \brief Get flag to say when callback was called
+ bool getCalled() const {
+ return (ptr_->called_);
+ }
+
+ /// \brief Set flag to say when callback was called
+ ///
+ /// \param called New value of called parameter
+ void setCalled(bool called) {
+ ptr_->called_ = called;
+ }
+
+ /// \brief Return instance of callback name
+ std::string getName() const {
+ return (ptr_->name_);
+ }
+
+ /// \brief Set callback name
+ ///
+ /// \param name New value of the callback name
+ void setName(const std::string& name) {
+ ptr_->name_ = name;
+ }
+
+private:
+ boost::shared_ptr<PrivateData> ptr_; ///< Pointer to private data
+};
+
+// Receive complete method should return true regardless of what is in the first
+// two bytes of a buffer.
+
+TEST(UDPSocket, processReceivedData) {
+ IOService service; // Used to instantiate socket
+ UDPSocket<UDPCallback> test(service); // Socket under test
+ uint8_t inbuff[32]; // Buffer to check
+ OutputBufferPtr outbuff(new OutputBuffer(16));
+ // Where data is put
+ size_t expected; // Expected amount of data
+ size_t offset; // Where to put next data
+ size_t cumulative; // Cumulative data received
+
+ // Set some dummy values in the buffer to check
+ for (uint8_t i = 0; i < sizeof(inbuff); ++i) {
+ inbuff[i] = i;
+ }
+
+ // Expect that the value is true whatever number is written in the first
+ // two bytes of the buffer.
+ uint16_t count = 0;
+ for (uint32_t i = 0; i < (2 << 16); ++i, ++count) {
+ writeUint16(count, inbuff);
+
+ // Set some random values
+ cumulative = 5;
+ offset = 10;
+ expected = 15;
+ outbuff->clear();
+
+ bool completed = test.processReceivedData(inbuff, sizeof(inbuff),
+ cumulative, offset, expected,
+ outbuff);
+ EXPECT_TRUE(completed);
+ EXPECT_EQ(sizeof(inbuff), cumulative);
+ EXPECT_EQ(0, offset);
+ EXPECT_EQ(sizeof(inbuff), expected);
+
+ const uint8_t* dataptr = static_cast<const uint8_t*>(outbuff->getData());
+ EXPECT_TRUE(equal(inbuff, inbuff + sizeof(inbuff) - 1, dataptr));
+ }
+}
+
+// TODO: Need to add a test to check the cancel() method
+
+// Tests the operation of a UDPSocket by opening it, sending an asynchronous
+// message to a server, receiving an asynchronous message from the server and
+// closing.
+TEST(UDPSocket, SequenceTest) {
+
+ // Common objects.
+ IOService service; // Service object for async control
+
+ // Server
+ IOAddress server_address(SERVER_ADDRESS); // Address of target server
+ UDPCallback server_cb("Server"); // Server callback
+ UDPEndpoint server_endpoint( // Endpoint describing server
+ server_address, SERVER_PORT);
+ UDPEndpoint server_remote_endpoint; // Address where server received message from
+
+ // The client - the UDPSocket being tested
+ UDPSocket<UDPCallback> client(service);// Socket under test
+ UDPCallback client_cb("Client"); // Async I/O callback function
+ UDPEndpoint client_remote_endpoint; // Where client receives message from
+ size_t client_cumulative = 0; // Cumulative data received
+ size_t client_offset = 0; // Offset into buffer where data is put
+ size_t client_expected = 0; // Expected amount of data
+ OutputBufferPtr client_buffer(new OutputBuffer(16));
+ // Where data is put
+
+ // The server - with which the client communicates. For convenience, we
+ // use the same io_service, and use the endpoint object created for
+ // the client to send to as the endpoint object in the constructor.
+ asio::ip::udp::socket server(service.get_io_service(),
+ server_endpoint.getASIOEndpoint());
+ server.set_option(socket_base::reuse_address(true));
+
+ // Assertion to ensure that the server buffer is large enough
+ char data[UDPSocket<UDPCallback>::MIN_SIZE];
+ ASSERT_GT(sizeof(data), sizeof(OUTBOUND_DATA));
+
+ // Open the client socket - the operation should be synchronous
+ EXPECT_TRUE(client.isOpenSynchronous());
+ client.open(&server_endpoint, client_cb);
+
+ // Issue read on the server. Completion callback should not have run.
+ server_cb.setCalled(false);
+ server_cb.setCode(42); // Answer to Life, the Universe and Everything!
+ server.async_receive_from(buffer(data, sizeof(data)),
+ server_remote_endpoint.getASIOEndpoint(), server_cb);
+ EXPECT_FALSE(server_cb.getCalled());
+
+ // Write something to the server using the client - the callback should not
+ // be called until we call the io_service.run() method.
+ client_cb.setCalled(false);
+ client_cb.setCode(7); // Arbitrary number
+ client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb);
+ EXPECT_FALSE(client_cb.getCalled());
+
+ // Execute the two callbacks.
+ service.run_one();
+ service.run_one();
+
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_EQ(0, client_cb.getCode());
+ EXPECT_EQ(sizeof(OUTBOUND_DATA), client_cb.getLength());
+
+ EXPECT_TRUE(server_cb.getCalled());
+ EXPECT_EQ(0, server_cb.getCode());
+ EXPECT_EQ(sizeof(OUTBOUND_DATA), server_cb.getLength());
+
+ EXPECT_TRUE(equal(&data[0], &data[server_cb.getLength() - 1], OUTBOUND_DATA));
+
+ // Now return data from the server to the client. Issue the read on the
+ // client.
+ client_cb.setLength(12345); // Arbitrary number
+ client_cb.setCalled(false);
+ client_cb.setCode(32); // Arbitrary number
+ client.asyncReceive(data, sizeof(data), client_cumulative,
+ &client_remote_endpoint, client_cb);
+
+ // Issue the write on the server side to the source of the data it received.
+ server_cb.setLength(22345); // Arbitrary number
+ server_cb.setCalled(false);
+ server_cb.setCode(232); // Arbitrary number
+ server.async_send_to(buffer(INBOUND_DATA, sizeof(INBOUND_DATA)),
+ server_remote_endpoint.getASIOEndpoint(), server_cb);
+
+ // Expect two callbacks to run.
+ service.run_one();
+ service.run_one();
+
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_EQ(0, client_cb.getCode());
+ EXPECT_EQ(sizeof(INBOUND_DATA), client_cb.getLength());
+
+ EXPECT_TRUE(server_cb.getCalled());
+ EXPECT_EQ(0, server_cb.getCode());
+ EXPECT_EQ(sizeof(INBOUND_DATA), server_cb.getLength());
+
+ EXPECT_TRUE(equal(&data[0], &data[server_cb.getLength() - 1], INBOUND_DATA));
+
+ // Check that the address/port received by the client corresponds to the
+ // address and port the server is listening on.
+ EXPECT_TRUE(server_address == client_remote_endpoint.getAddress());
+ EXPECT_EQ(SERVER_PORT, client_remote_endpoint.getPort());
+
+ // Check that the receive received a complete buffer's worth of data.
+ EXPECT_TRUE(client.processReceivedData(&data[0], client_cb.getLength(),
+ client_cumulative, client_offset,
+ client_expected, client_buffer));
+
+ EXPECT_EQ(client_cb.getLength(), client_cumulative);
+ EXPECT_EQ(0, client_offset);
+ EXPECT_EQ(client_cb.getLength(), client_expected);
+ EXPECT_EQ(client_cb.getLength(), client_buffer->getLength());
+
+ // ...and check that the data was copied to the output client buffer.
+ const char* client_char_data = static_cast<const char*>(client_buffer->getData());
+ EXPECT_TRUE(equal(&data[0], &data[client_cb.getLength() - 1], client_char_data));
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.close());
+ EXPECT_NO_THROW(server.close());
+}
diff --git a/src/lib/asiolink/udp_endpoint.h b/src/lib/asiolink/udp_endpoint.h
new file mode 100644
index 0000000..99dc27f
--- /dev/null
+++ b/src/lib/asiolink/udp_endpoint.h
@@ -0,0 +1,113 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __UDP_ENDPOINT_H
+#define __UDP_ENDPOINT_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_endpoint.h>
+
+namespace asiolink {
+
+/// \brief The \c UDPEndpoint class is a concrete derived class of
+/// \c IOEndpoint that represents an endpoint of a UDP packet.
+///
+/// Other notes about \c TCPEndpoint applies to this class, too.
+class UDPEndpoint : public IOEndpoint {
+public:
+ ///
+ /// \name Constructors and Destructor.
+ ///
+ //@{
+
+ /// \brief Default Constructor
+ ///
+ /// Creates an internal endpoint. This is expected to be set by some
+ /// external call.
+ UDPEndpoint() :
+ asio_endpoint_placeholder_(new asio::ip::udp::endpoint()),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
+ /// \brief Constructor from a pair of address and port.
+ ///
+ /// \param address The IP address of the endpoint.
+ /// \param port The UDP port number of the endpoint.
+ UDPEndpoint(const IOAddress& address, const unsigned short port) :
+ asio_endpoint_placeholder_(
+ new asio::ip::udp::endpoint(asio::ip::address::from_string(address.toText()),
+ port)),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
+ /// \brief Constructor from an ASIO UDP endpoint.
+ ///
+ /// This constructor is designed to be an efficient wrapper for the
+ /// corresponding ASIO class, \c udp::endpoint.
+ ///
+ /// \param asio_endpoint The ASIO representation of the UDP endpoint.
+ UDPEndpoint(asio::ip::udp::endpoint& asio_endpoint) :
+ asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
+ {}
+
+ /// \brief Constructor from an ASIO UDP endpoint.
+ ///
+ /// This constructor is designed to be an efficient wrapper for the
+ /// corresponding ASIO class, \c udp::endpoint.
+ ///
+ /// \param asio_endpoint The ASIO representation of the TCP endpoint.
+ UDPEndpoint(const asio::ip::udp::endpoint& asio_endpoint) :
+ asio_endpoint_placeholder_(new asio::ip::udp::endpoint(asio_endpoint)),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
+ /// \brief The destructor.
+ virtual ~UDPEndpoint() { delete asio_endpoint_placeholder_; }
+ //@}
+
+ virtual IOAddress getAddress() const {
+ return (asio_endpoint_.address());
+ }
+
+ virtual uint16_t getPort() const {
+ return (asio_endpoint_.port());
+ }
+
+ virtual short getProtocol() const {
+ return (asio_endpoint_.protocol().protocol());
+ }
+
+ virtual short getFamily() const {
+ return (asio_endpoint_.protocol().family());
+ }
+
+ // This is not part of the exosed IOEndpoint API but allows
+ // direct access to the ASIO implementation of the endpoint
+ inline const asio::ip::udp::endpoint& getASIOEndpoint() const {
+ return (asio_endpoint_);
+ }
+ inline asio::ip::udp::endpoint& getASIOEndpoint() {
+ return (asio_endpoint_);
+ }
+
+private:
+ asio::ip::udp::endpoint* asio_endpoint_placeholder_;
+ asio::ip::udp::endpoint& asio_endpoint_;
+};
+
+} // namespace asiolink
+#endif // __UDP_ENDPOINT_H
diff --git a/src/lib/asiolink/udp_server.cc b/src/lib/asiolink/udp_server.cc
new file mode 100644
index 0000000..063926e
--- /dev/null
+++ b/src/lib/asiolink/udp_server.cc
@@ -0,0 +1,322 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+#include <errno.h>
+
+#include <boost/shared_array.hpp>
+
+#include <config.h>
+
+#include <log/dummylog.h>
+
+#include <asio.hpp>
+#include <asiolink/dummy_io_cb.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_server.h>
+#include <asiolink/udp_socket.h>
+
+#include <dns/opcode.h>
+
+using namespace asio;
+using asio::ip::udp;
+using isc::log::dlog;
+
+using namespace std;
+using namespace isc::dns;
+
+namespace asiolink {
+
+/*
+ * Some of the member variables here are shared_ptrs and some are
+ * auto_ptrs. There will be one instance of Data for the lifetime
+ * of packet. The variables that are state only for a single packet
+ * use auto_ptr, as it is more lightweight. In the case of shared
+ * configuration (eg. the callbacks, socket), we use shared_ptrs.
+ */
+struct UDPServer::Data {
+ /*
+ * Constructor from parameters passed to UDPServer constructor.
+ * This instance will not be used to retrieve and answer the actual
+ * query, it will only hold parameters until we wait for the
+ * first packet. But we do initialize the socket in here.
+ */
+ Data(io_service& io_service, const ip::address& addr, const uint16_t port,
+ SimpleCallback* checkin, DNSLookup* lookup, DNSAnswer* answer) :
+ io_(io_service), done_(false), stopped_by_hand_(false),
+ checkin_callback_(checkin),lookup_callback_(lookup),
+ answer_callback_(answer)
+ {
+ // We must use different instantiations for v4 and v6;
+ // otherwise ASIO will bind to both
+ udp proto = addr.is_v4() ? udp::v4() : udp::v6();
+ socket_.reset(new udp::socket(io_service, proto));
+ socket_->set_option(socket_base::reuse_address(true));
+ if (addr.is_v6()) {
+ socket_->set_option(asio::ip::v6_only(true));
+ }
+ socket_->bind(udp::endpoint(addr, port));
+ }
+
+ /*
+ * Copy constructor. Default one would probably do, but it is unnecessary
+ * to copy many of the member variables every time we fork to handle
+ * another packet.
+ *
+ * We also allocate data for receiving the packet here.
+ */
+ Data(const Data& other) :
+ io_(other.io_), socket_(other.socket_), done_(false),
+ stopped_by_hand_(false),
+ checkin_callback_(other.checkin_callback_),
+ lookup_callback_(other.lookup_callback_),
+ answer_callback_(other.answer_callback_)
+ {
+ // Instantiate the data buffer and endpoint that will
+ // be used by the asynchronous receive call.
+ data_.reset(new char[MAX_LENGTH]);
+ sender_.reset(new udp::endpoint());
+ }
+
+ // The ASIO service object
+ asio::io_service& io_;
+
+ // Class member variables which are dynamic, and changes to which
+ // need to accessible from both sides of a coroutine fork or from
+ // outside of the coroutine (i.e., from an asynchronous I/O call),
+ // should be declared here as pointers and allocated in the
+ // constructor or in the coroutine. This allows state information
+ // to persist when an individual copy of the coroutine falls out
+ // scope while waiting for an event, *so long as* there is another
+ // object that is referencing the same data. As a side-benefit, using
+ // pointers also reduces copy overhead for coroutine objects.
+ //
+ // Note: Currently these objects are allocated by "new" in the
+ // constructor, or in the function operator while processing a query.
+ // Repeated allocations from the heap for every incoming query is
+ // clearly a performance issue; this must be optimized in the future.
+ // The plan is to have a structure pre-allocate several "Data"
+ // objects which can be pulled off a free list and placed on an in-use
+ // list whenever a query comes in. This will serve the dual purpose
+ // of improving performance and guaranteeing that state information
+ // will *not* be destroyed when any one instance of the coroutine
+ // falls out of scope while waiting for an event.
+ //
+ // Socket used to for listen for queries. Created in the
+ // constructor and stored in a shared_ptr because socket objects
+ // are not copyable.
+ boost::shared_ptr<asio::ip::udp::socket> socket_;
+
+ // The ASIO-internal endpoint object representing the client
+ std::auto_ptr<asio::ip::udp::endpoint> sender_;
+
+ // \c IOMessage and \c Message objects to be passed to the
+ // DNS lookup and answer providers
+ std::auto_ptr<asiolink::IOMessage> io_message_;
+
+ // The original query as sent by the client
+ isc::dns::MessagePtr query_message_;
+
+ // The response message we are building
+ isc::dns::MessagePtr answer_message_;
+
+ // The buffer into which the response is written
+ isc::dns::OutputBufferPtr respbuf_;
+
+ // The buffer into which the query packet is written
+ boost::shared_array<char> data_;
+
+ // State information that is entirely internal to a given instance
+ // of the coroutine can be declared here.
+ size_t bytes_;
+ bool done_;
+
+ //whether user explicitly stop the server
+ bool stopped_by_hand_;
+
+ // Callback functions provided by the caller
+ const SimpleCallback* checkin_callback_;
+ const DNSLookup* lookup_callback_;
+ const DNSAnswer* answer_callback_;
+
+ std::auto_ptr<IOEndpoint> peer_;
+ std::auto_ptr<IOSocket> iosock_;
+};
+
+/// The following functions implement the \c UDPServer class.
+///
+/// The constructor. It just creates new internal state object
+/// and lets it handle the initialization.
+UDPServer::UDPServer(io_service& io_service, const ip::address& addr,
+ const uint16_t port, SimpleCallback* checkin, DNSLookup* lookup,
+ DNSAnswer* answer) :
+ data_(new Data(io_service, addr, port, checkin, lookup, answer))
+{ }
+
+/// The function operator is implemented with the "stackless coroutine"
+/// pattern; see internal/coroutine.h for details.
+void
+UDPServer::operator()(error_code ec, size_t length) {
+ /// Because the coroutine reentry block is implemented as
+ /// a switch statement, inline variable declarations are not
+ /// permitted. Certain variables used below can be declared here.
+
+ /// if user stopped the server, we won't enter the coroutine body
+ /// just return
+ if (data_->stopped_by_hand_) {
+ return;
+ }
+
+ CORO_REENTER (this) {
+ do {
+ /*
+ * This is preparation for receiving a packet. We get a new
+ * state object for the lifetime of the next packet to come.
+ * It allocates the buffers to receive data into.
+ */
+ data_.reset(new Data(*data_));
+
+ do {
+ // Begin an asynchronous receive, then yield.
+ // When the receive event is posted, the coroutine
+ // will resume immediately after this point.
+ CORO_YIELD data_->socket_->async_receive_from(
+ buffer(data_->data_.get(), MAX_LENGTH), *data_->sender_,
+ *this);
+ // Abort on fatal errors
+ if (ec) {
+ using namespace asio::error;
+ if (ec.value() != would_block && ec.value() != try_again &&
+ ec.value() != interrupted) {
+ return;
+ }
+ }
+ } while (ec || length == 0);
+
+ data_->bytes_ = length;
+
+ /*
+ * We fork the coroutine now. One (the child) will keep
+ * the current state and handle the packet, then die and
+ * drop ownership of the state. The other (parent) will just
+ * go into the loop again and replace the current state with
+ * a new one for a new object.
+ *
+ * Actually, both of the coroutines will be a copy of this
+ * one, but that's just internal implementation detail.
+ */
+ CORO_FORK data_->io_.post(UDPServer(*this));
+ } while (is_parent());
+
+ // Create an \c IOMessage object to store the query.
+ //
+ // (XXX: It would be good to write a factory function
+ // that would quickly generate an IOMessage object without
+ // all these calls to "new".)
+ data_->peer_.reset(new UDPEndpoint(*data_->sender_));
+
+ // The UDP socket class has been extended with asynchronous functions
+ // and takes as a template parameter a completion callback class. As
+ // UDPServer does not use these extended functions (only those defined
+ // in the IOSocket base class) - but needs a UDPSocket to get hold of
+ // the underlying Boost UDP socket - DummyIOCallback is used. This
+ // provides the appropriate operator() but is otherwise functionless.
+ data_->iosock_.reset(
+ new UDPSocket<DummyIOCallback>(*data_->socket_));
+
+ data_->io_message_.reset(new IOMessage(data_->data_.get(),
+ data_->bytes_, *data_->iosock_, *data_->peer_));
+
+ // Perform any necessary operations prior to processing an incoming
+ // query (e.g., checking for queued configuration messages).
+ //
+ // (XXX: it may be a performance issue to check in for every single
+ // incoming query; we may wish to throttle this in the future.)
+ if (data_->checkin_callback_ != NULL) {
+ (*data_->checkin_callback_)(*data_->io_message_);
+ }
+
+ // If we don't have a DNS Lookup provider, there's no point in
+ // continuing; we exit the coroutine permanently.
+ if (data_->lookup_callback_ == NULL) {
+ CORO_YIELD return;
+ }
+
+ // Instantiate objects that will be needed by the
+ // asynchronous DNS lookup and/or by the send call.
+ data_->respbuf_.reset(new OutputBuffer(0));
+ data_->query_message_.reset(new Message(Message::PARSE));
+ data_->answer_message_.reset(new Message(Message::RENDER));
+
+ // Schedule a DNS lookup, and yield. When the lookup is
+ // finished, the coroutine will resume immediately after
+ // this point.
+ CORO_YIELD data_->io_.post(AsyncLookup<UDPServer>(*this));
+
+ // The 'done_' flag indicates whether we have an answer
+ // to send back. If not, exit the coroutine permanently.
+ if (!data_->done_) {
+ CORO_YIELD return;
+ }
+
+ // Call the DNS answer provider to render the answer into
+ // wire format
+ (*data_->answer_callback_)(*data_->io_message_, data_->query_message_,
+ data_->answer_message_, data_->respbuf_);
+
+ // Begin an asynchronous send, and then yield. When the
+ // send completes, we will resume immediately after this point
+ // (though we have nothing further to do, so the coroutine
+ // will simply exit at that time).
+ CORO_YIELD data_->socket_->async_send_to(
+ buffer(data_->respbuf_->getData(), data_->respbuf_->getLength()),
+ *data_->sender_, *this);
+ }
+}
+
+/// Call the DNS lookup provider. (Expected to be called by the
+/// AsyncLookup<UDPServer> handler.)
+void
+UDPServer::asyncLookup() {
+ (*data_->lookup_callback_)(*data_->io_message_,
+ data_->query_message_, data_->answer_message_, data_->respbuf_, this);
+}
+
+/// Stop the UDPServer
+void
+UDPServer::stop() {
+ //server should not be stopped twice
+ if (data_->stopped_by_hand_)
+ return;
+ data_->stopped_by_hand_ = true;
+ data_->socket_->close();
+}
+
+/// Post this coroutine on the ASIO service queue so that it will
+/// resume processing where it left off. The 'done' parameter indicates
+/// whether there is an answer to return to the client.
+void
+UDPServer::resume(const bool done) {
+ data_->done_ = done;
+ data_->io_.post(*this);
+}
+
+bool
+UDPServer::hasAnswer() {
+ return (data_->done_);
+}
+
+} // namespace asiolink
diff --git a/src/lib/asiolink/udp_server.h b/src/lib/asiolink/udp_server.h
new file mode 100644
index 0000000..1d37471
--- /dev/null
+++ b/src/lib/asiolink/udp_server.h
@@ -0,0 +1,106 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __UDP_SERVER_H
+#define __UDP_SERVER_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/dns_server.h>
+#include <asiolink/simple_callback.h>
+#include <asiolink/dns_lookup.h>
+#include <asiolink/dns_answer.h>
+
+#include <coroutine.h>
+
+namespace asiolink {
+
+//
+// Asynchronous UDP server coroutine
+//
+///
+/// \brief This class implements the coroutine to handle UDP
+/// DNS query event. As such, it is both a \c DNSServer and
+/// a \c coroutine
+///
+class UDPServer : public virtual DNSServer, public virtual coroutine {
+public:
+ /// \brief Constructor
+ /// \param io_service the asio::io_service to work with
+ /// \param addr the IP address to listen for queries on
+ /// \param port the port to listen for queries on
+ /// \param checkin the callbackprovider for non-DNS events
+ /// \param lookup the callbackprovider for DNS lookup events
+ /// \param answer the callbackprovider for DNS answer events
+ explicit UDPServer(asio::io_service& io_service,
+ const asio::ip::address& addr, const uint16_t port,
+ SimpleCallback* checkin = NULL,
+ DNSLookup* lookup = NULL,
+ DNSAnswer* answer = NULL);
+
+ /// \brief The function operator
+ void operator()(asio::error_code ec = asio::error_code(),
+ size_t length = 0);
+
+ /// \brief Calls the lookup callback
+ void asyncLookup();
+
+ /// \brief Stop the running server
+ /// \note once the server stopped, it can't restart
+ void stop();
+
+ /// \brief Resume operation
+ ///
+ /// \param done Set this to true if the lookup action is done and
+ /// we have an answer
+ void resume(const bool done);
+
+ /// \brief Check if we have an answer
+ ///
+ /// \return true if we have an answer
+ bool hasAnswer();
+
+ /// \brief Returns the coroutine state value
+ ///
+ /// \return the coroutine state value
+ int value() { return (get_value()); }
+
+ /// \brief Clones the object
+ ///
+ /// \return a newly allocated copy of this object
+ DNSServer* clone() {
+ UDPServer* s = new UDPServer(*this);
+ return (s);
+ }
+
+private:
+ enum { MAX_LENGTH = 4096 };
+
+ /**
+ * \brief Internal state and data.
+ *
+ * We use the pimple design pattern, but not because we need to hide
+ * internal data. This class and whole header is for private use anyway.
+ * It turned out that UDPServer is copied a lot, because it is a coroutine.
+ * This way the overhead of copying is lower, we copy only one shared
+ * pointer instead of about 10 of them.
+ */
+ class Data;
+ boost::shared_ptr<Data> data_;
+};
+
+} // namespace asiolink
+#endif // __UDP_SERVER_H
diff --git a/src/lib/asiolink/udp_socket.h b/src/lib/asiolink/udp_socket.h
new file mode 100644
index 0000000..35fc7b1
--- /dev/null
+++ b/src/lib/asiolink/udp_socket.h
@@ -0,0 +1,322 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __UDP_SOCKET_H
+#define __UDP_SOCKET_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <log/dummylog.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+
+#include <cstddef>
+
+#include <config.h>
+
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_service.h>
+#include <asiolink/udp_endpoint.h>
+
+namespace asiolink {
+
+/// \brief The \c UDPSocket class is a concrete derived class of \c IOAsioSocket
+/// that represents a UDP socket.
+///
+/// \param C Callback type
+template <typename C>
+class UDPSocket : public IOAsioSocket<C> {
+private:
+ /// \brief Class is non-copyable
+ UDPSocket(const UDPSocket&);
+ UDPSocket& operator=(const UDPSocket&);
+
+public:
+ enum {
+ MIN_SIZE = 4096 // Minimum send and receive size
+ };
+
+ /// \brief Constructor from an ASIO UDP socket.
+ ///
+ /// \param socket The ASIO representation of the UDP socket. It is assumed
+ /// that the caller will open and close the socket, so these
+ /// operations are a no-op for that socket.
+ UDPSocket(asio::ip::udp::socket& socket);
+
+ /// \brief Constructor
+ ///
+ /// Used when the UDPSocket is being asked to manage its own internal
+ /// socket. In this case, the open() and close() methods are used.
+ ///
+ /// \param service I/O Service object used to manage the socket.
+ UDPSocket(IOService& service);
+
+ /// \brief Destructor
+ virtual ~UDPSocket();
+
+ /// \brief Return file descriptor of underlying socket
+ virtual int getNative() const {
+ return (socket_.native());
+ }
+
+ /// \brief Return protocol of socket
+ virtual int getProtocol() const {
+ return (IPPROTO_UDP);
+ }
+
+ /// \brief Is "open()" synchronous?
+ ///
+ /// Indicates that the opening of a UDP socket is synchronous.
+ virtual bool isOpenSynchronous() const {
+ return true;
+ }
+
+ /// \brief Open Socket
+ ///
+ /// Opens the UDP socket. This is a synchronous operation.
+ ///
+ /// \param endpoint Endpoint to which the socket will send data. This is
+ /// used to determine the address family trhat should be used for the
+ /// underlying socket.
+ /// \param callback Unused as the operation is synchronous.
+ virtual void open(const IOEndpoint* endpoint, C& callback);
+
+ /// \brief Send Asynchronously
+ ///
+ /// Calls the underlying socket's async_send_to() method to send a packet of
+ /// data asynchronously to the remote endpoint. The callback will be called
+ /// on completion.
+ ///
+ /// \param data Data to send
+ /// \param length Length of data to send
+ /// \param endpoint Target of the send
+ /// \param callback Callback object.
+ virtual void asyncSend(const void* data, size_t length,
+ const IOEndpoint* endpoint, C& callback);
+
+ /// \brief Receive Asynchronously
+ ///
+ /// Calls the underlying socket's async_receive_from() method to read a
+ /// packet of data from a remote endpoint. Arrival of the data is signalled
+ /// via a call to the callback function.
+ ///
+ /// \param data Buffer to receive incoming message
+ /// \param length Length of the data buffer
+ /// \param offset Offset into buffer where data is to be put
+ /// \param endpoint Source of the communication
+ /// \param callback Callback object
+ virtual void asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback);
+
+ /// \brief Process received data
+ ///
+ /// See the description of IOAsioSocket::receiveComplete for a complete
+ /// description of this method.
+ ///
+ /// \param staging Pointer to the start of the staging buffer.
+ /// \param length Amount of data in the staging buffer.
+ /// \param cumulative Amount of data received before the staging buffer is
+ /// processed.
+ /// \param offset Unused.
+ /// \param expected unused.
+ /// \param outbuff Output buffer. Data in the staging buffer is be copied
+ /// to this output buffer in the call.
+ ///
+ /// \return Always true
+ virtual bool processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::dns::OutputBufferPtr& outbuff);
+
+ /// \brief Cancel I/O On Socket
+ virtual void cancel();
+
+ /// \brief Close socket
+ virtual void close();
+
+
+private:
+ // Two variables to hold the socket - a socket and a pointer to it. This
+ // handles the case where a socket is passed to the UDPSocket on
+ // construction, or where it is asked to manage its own socket.
+ asio::ip::udp::socket* socket_ptr_; ///< Pointer to own socket
+ asio::ip::udp::socket& socket_; ///< Socket
+ bool isopen_; ///< true when socket is open
+};
+
+// Constructor - caller manages socket
+
+template <typename C>
+UDPSocket<C>::UDPSocket(asio::ip::udp::socket& socket) :
+ socket_ptr_(NULL), socket_(socket), isopen_(true)
+{
+}
+
+// Constructor - create socket on the fly
+
+template <typename C>
+UDPSocket<C>::UDPSocket(IOService& service) :
+ socket_ptr_(new asio::ip::udp::socket(service.get_io_service())),
+ socket_(*socket_ptr_), isopen_(false)
+{
+}
+
+// Destructor. Only delete the socket if we are managing it.
+
+template <typename C>
+UDPSocket<C>::~UDPSocket()
+{
+ delete socket_ptr_;
+}
+
+// Open the socket.
+
+template <typename C> void
+UDPSocket<C>::open(const IOEndpoint* endpoint, C&) {
+
+ // Ignore opens on already-open socket. (Don't throw a failure because
+ // of uncertainties as to what precedes whan when using asynchronous I/O.)
+ // It also allows us a treat a passed-in socket in exactly the same way as
+ // a self-managed socket (in that we can call the open() and close() methods
+ // of this class).
+ if (!isopen_) {
+ if (endpoint->getFamily() == AF_INET) {
+ socket_.open(asio::ip::udp::v4());
+ }
+ else {
+ socket_.open(asio::ip::udp::v6());
+ }
+ isopen_ = true;
+
+ // Ensure it can send and receive at least 4K buffers.
+ asio::ip::udp::socket::send_buffer_size snd_size;
+ socket_.get_option(snd_size);
+ if (snd_size.value() < MIN_SIZE) {
+ snd_size = MIN_SIZE;
+ socket_.set_option(snd_size);
+ }
+
+ asio::ip::udp::socket::receive_buffer_size rcv_size;
+ socket_.get_option(rcv_size);
+ if (rcv_size.value() < MIN_SIZE) {
+ rcv_size = MIN_SIZE;
+ socket_.set_option(rcv_size);
+ }
+ }
+}
+
+// Send a message. Should never do this if the socket is not open, so throw
+// an exception if this is the case.
+
+template <typename C> void
+UDPSocket<C>::asyncSend(const void* data, size_t length,
+ const IOEndpoint* endpoint, C& callback)
+{
+ if (isopen_) {
+
+ // Upconvert to a UDPEndpoint. We need to do this because although
+ // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
+ // does not contain a method for getting at the underlying endpoint
+ // type - that is in the derived class and the two classes differ on
+ // return type.
+ assert(endpoint->getProtocol() == IPPROTO_UDP);
+ const UDPEndpoint* udp_endpoint =
+ static_cast<const UDPEndpoint*>(endpoint);
+
+ // ... and send the message.
+ socket_.async_send_to(asio::buffer(data, length),
+ udp_endpoint->getASIOEndpoint(), callback);
+ } else {
+ isc_throw(SocketNotOpen,
+ "attempt to send on a UDP socket that is not open");
+ }
+}
+
+// Receive a message. Should never do this if the socket is not open, so throw
+// an exception if this is the case.
+
+template <typename C> void
+UDPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback)
+{
+ if (isopen_) {
+
+ // Upconvert the endpoint again.
+ assert(endpoint->getProtocol() == IPPROTO_UDP);
+ UDPEndpoint* udp_endpoint = static_cast<UDPEndpoint*>(endpoint);
+
+ // Ensure we can write into the buffer
+ if (offset >= length) {
+ isc_throw(BufferOverflow, "attempt to read into area beyond end of "
+ "UDP receive buffer");
+ }
+ void* buffer_start = static_cast<void*>(static_cast<uint8_t*>(data) + offset);
+
+ // Issue the read
+ socket_.async_receive_from(asio::buffer(buffer_start, length - offset),
+ udp_endpoint->getASIOEndpoint(), callback);
+ } else {
+ isc_throw(SocketNotOpen,
+ "attempt to receive from a UDP socket that is not open");
+ }
+}
+
+// Receive complete. Just copy the data across to the output buffer and
+// update arguments as appropriate.
+
+template <typename C> bool
+UDPSocket<C>::processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::dns::OutputBufferPtr& outbuff)
+{
+ // Set return values to what we should expect.
+ cumulative = length;
+ expected = length;
+ offset = 0;
+
+ // Copy data across
+ outbuff->writeData(staging, length);
+
+ // ... and mark that we have everything.
+ return (true);
+}
+
+// Cancel I/O on the socket. No-op if the socket is not open.
+
+template <typename C> void
+UDPSocket<C>::cancel() {
+ if (isopen_) {
+ socket_.cancel();
+ }
+}
+
+// Close the socket down. Can only do this if the socket is open and we are
+// managing it ourself.
+
+template <typename C> void
+UDPSocket<C>::close() {
+ if (isopen_ && socket_ptr_) {
+ socket_.close();
+ isopen_ = false;
+ }
+}
+
+} // namespace asiolink
+
+#endif // __UDP_SOCKET_H
diff --git a/src/lib/asiolink/udpdns.cc b/src/lib/asiolink/udpdns.cc
deleted file mode 100644
index 83f7b3c..0000000
--- a/src/lib/asiolink/udpdns.cc
+++ /dev/null
@@ -1,432 +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 <config.h>
-
-#include <unistd.h> // for some IPC/network system calls
-#include <sys/socket.h>
-#include <netinet/in.h>
-
-#include <boost/bind.hpp>
-
-#include <asio.hpp>
-#include <asio/deadline_timer.hpp>
-
-#include <memory>
-#include <boost/shared_ptr.hpp>
-#include <boost/date_time/posix_time/posix_time_types.hpp>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-#include <dns/messagerenderer.h>
-#include <log/dummylog.h>
-#include <dns/opcode.h>
-#include <dns/rcode.h>
-
-#include <asiolink.h>
-#include <internal/coroutine.h>
-#include <internal/udpdns.h>
-
-using namespace asio;
-using asio::ip::udp;
-using asio::ip::tcp;
-using isc::log::dlog;
-
-using namespace std;
-using namespace isc::dns;
-
-namespace asiolink {
-
-/*
- * Some of the member variables here are shared_ptrs and some are
- * auto_ptrs. There will be one instance of Data for the lifetime
- * of packet. The variables that are state only for a single packet
- * use auto_ptr, as it is more lightweight. In the case of shared
- * configuration (eg. the callbacks, socket), we use shared_ptrs.
- */
-struct UDPServer::Data {
- /*
- * Constructor from parameters passed to UDPServer constructor.
- * This instance will not be used to retrieve and answer the actual
- * query, it will only hold parameters until we wait for the
- * first packet. But we do initialize the socket in here.
- */
- Data(io_service& io_service, const ip::address& addr, const uint16_t port,
- SimpleCallback* checkin, DNSLookup* lookup, DNSAnswer* answer) :
- io_(io_service), done_(false), checkin_callback_(checkin),
- lookup_callback_(lookup), answer_callback_(answer)
- {
- // We must use different instantiations for v4 and v6;
- // otherwise ASIO will bind to both
- udp proto = addr.is_v4() ? udp::v4() : udp::v6();
- socket_.reset(new udp::socket(io_service, proto));
- socket_->set_option(socket_base::reuse_address(true));
- if (addr.is_v6()) {
- socket_->set_option(asio::ip::v6_only(true));
- }
- socket_->bind(udp::endpoint(addr, port));
- }
-
- /*
- * Copy constructor. Default one would probably do, but it is unnecessary
- * to copy many of the member variables every time we fork to handle
- * another packet.
- *
- * We also allocate data for receiving the packet here.
- */
- Data(const Data& other) :
- io_(other.io_), socket_(other.socket_), done_(false),
- checkin_callback_(other.checkin_callback_),
- lookup_callback_(other.lookup_callback_),
- answer_callback_(other.answer_callback_)
- {
- // Instantiate the data buffer and endpoint that will
- // be used by the asynchronous receive call.
- data_.reset(new char[MAX_LENGTH]);
- sender_.reset(new udp::endpoint());
- }
-
- // The ASIO service object
- asio::io_service& io_;
-
- // Class member variables which are dynamic, and changes to which
- // need to accessible from both sides of a coroutine fork or from
- // outside of the coroutine (i.e., from an asynchronous I/O call),
- // should be declared here as pointers and allocated in the
- // constructor or in the coroutine. This allows state information
- // to persist when an individual copy of the coroutine falls out
- // scope while waiting for an event, *so long as* there is another
- // object that is referencing the same data. As a side-benefit, using
- // pointers also reduces copy overhead for coroutine objects.
- //
- // Note: Currently these objects are allocated by "new" in the
- // constructor, or in the function operator while processing a query.
- // Repeated allocations from the heap for every incoming query is
- // clearly a performance issue; this must be optimized in the future.
- // The plan is to have a structure pre-allocate several "Data"
- // objects which can be pulled off a free list and placed on an in-use
- // list whenever a query comes in. This will serve the dual purpose
- // of improving performance and guaranteeing that state information
- // will *not* be destroyed when any one instance of the coroutine
- // falls out of scope while waiting for an event.
- //
- // Socket used to for listen for queries. Created in the
- // constructor and stored in a shared_ptr because socket objects
- // are not copyable.
- boost::shared_ptr<asio::ip::udp::socket> socket_;
-
- // The ASIO-internal endpoint object representing the client
- std::auto_ptr<asio::ip::udp::endpoint> sender_;
-
- // \c IOMessage and \c Message objects to be passed to the
- // DNS lookup and answer providers
- std::auto_ptr<asiolink::IOMessage> io_message_;
-
- // The original query as sent by the client
- isc::dns::MessagePtr query_message_;
-
- // The response message we are building
- isc::dns::MessagePtr answer_message_;
-
- // The buffer into which the response is written
- isc::dns::OutputBufferPtr respbuf_;
-
- // The buffer into which the query packet is written
- boost::shared_array<char> data_;
-
- // State information that is entirely internal to a given instance
- // of the coroutine can be declared here.
- size_t bytes_;
- bool done_;
-
- // Callback functions provided by the caller
- const SimpleCallback* checkin_callback_;
- const DNSLookup* lookup_callback_;
- const DNSAnswer* answer_callback_;
-
- std::auto_ptr<IOEndpoint> peer_;
- std::auto_ptr<IOSocket> iosock_;
-};
-
-/// The following functions implement the \c UDPServer class.
-///
-/// The constructor. It just creates new internal state object
-/// and lets it handle the initialization.
-UDPServer::UDPServer(io_service& io_service, const ip::address& addr,
- const uint16_t port, SimpleCallback* checkin, DNSLookup* lookup,
- DNSAnswer* answer) :
- data_(new Data(io_service, addr, port, checkin, lookup, answer))
-{ }
-
-/// The function operator is implemented with the "stackless coroutine"
-/// pattern; see internal/coroutine.h for details.
-void
-UDPServer::operator()(error_code ec, size_t length) {
- /// Because the coroutine reeentry block is implemented as
- /// a switch statement, inline variable declarations are not
- /// permitted. Certain variables used below can be declared here.
-
- CORO_REENTER (this) {
- do {
- /*
- * This is preparation for receiving a packet. We get a new
- * state object for the lifetime of the next packet to come.
- * It allocates the buffers to receive data into.
- */
- data_.reset(new Data(*data_));
-
- do {
- // Begin an asynchronous receive, then yield.
- // When the receive event is posted, the coroutine
- // will resume immediately after this point.
- CORO_YIELD data_->socket_->async_receive_from(
- buffer(data_->data_.get(), MAX_LENGTH), *data_->sender_,
- *this);
- } while (ec || length == 0);
-
- data_->bytes_ = length;
-
- /*
- * We fork the coroutine now. One (the child) will keep
- * the current state and handle the packet, then die and
- * drop ownership of the state. The other (parent) will just
- * go into the loop again and replace the current state with
- * a new one for a new object.
- *
- * Actually, both of the coroutines will be a copy of this
- * one, but that's just internal implementation detail.
- */
- CORO_FORK data_->io_.post(UDPServer(*this));
- } while (is_parent());
-
- // Create an \c IOMessage object to store the query.
- //
- // (XXX: It would be good to write a factory function
- // that would quickly generate an IOMessage object without
- // all these calls to "new".)
- data_->peer_.reset(new UDPEndpoint(*data_->sender_));
- data_->iosock_.reset(new UDPSocket(*data_->socket_));
- data_->io_message_.reset(new IOMessage(data_->data_.get(),
- data_->bytes_, *data_->iosock_, *data_->peer_));
-
- // Perform any necessary operations prior to processing an incoming
- // query (e.g., checking for queued configuration messages).
- //
- // (XXX: it may be a performance issue to check in for every single
- // incoming query; we may wish to throttle this in the future.)
- if (data_->checkin_callback_ != NULL) {
- (*data_->checkin_callback_)(*data_->io_message_);
- }
-
- // If we don't have a DNS Lookup provider, there's no point in
- // continuing; we exit the coroutine permanently.
- if (data_->lookup_callback_ == NULL) {
- CORO_YIELD return;
- }
-
- // Instantiate objects that will be needed by the
- // asynchronous DNS lookup and/or by the send call.
- data_->respbuf_.reset(new OutputBuffer(0));
- data_->query_message_.reset(new Message(Message::PARSE));
- data_->answer_message_.reset(new Message(Message::RENDER));
-
- // Schedule a DNS lookup, and yield. When the lookup is
- // finished, the coroutine will resume immediately after
- // this point.
- CORO_YIELD data_->io_.post(AsyncLookup<UDPServer>(*this));
-
- dlog("[XX] got an answer");
-
- // The 'done_' flag indicates whether we have an answer
- // to send back. If not, exit the coroutine permanently.
- if (!data_->done_) {
- CORO_YIELD return;
- }
-
- // Call the DNS answer provider to render the answer into
- // wire format
- (*data_->answer_callback_)(*data_->io_message_, data_->query_message_,
- data_->answer_message_, data_->respbuf_);
-
- // Begin an asynchronous send, and then yield. When the
- // send completes, we will resume immediately after this point
- // (though we have nothing further to do, so the coroutine
- // will simply exit at that time).
- CORO_YIELD data_->socket_->async_send_to(
- buffer(data_->respbuf_->getData(), data_->respbuf_->getLength()),
- *data_->sender_, *this);
- }
-}
-
-/// Call the DNS lookup provider. (Expected to be called by the
-/// AsyncLookup<UDPServer> handler.)
-void
-UDPServer::asyncLookup() {
- (*data_->lookup_callback_)(*data_->io_message_,
- data_->query_message_, data_->answer_message_, data_->respbuf_, this);
-}
-
-/// Post this coroutine on the ASIO service queue so that it will
-/// resume processing where it left off. The 'done' parameter indicates
-/// whether there is an answer to return to the client.
-void
-UDPServer::resume(const bool done) {
- data_->done_ = done;
- data_->io_.post(*this);
-}
-
-bool
-UDPServer::hasAnswer() {
- return (data_->done_);
-}
-
-// Private UDPQuery data (see internal/udpdns.h for reasons)
-struct UDPQuery::PrivateData {
- // Socket we send query to and expect reply from there
- udp::socket socket;
- // Where was the query sent
- udp::endpoint remote;
- // What we ask the server
- Question question;
- // We will store the answer here
- OutputBufferPtr buffer;
- OutputBufferPtr msgbuf;
- // Temporary buffer for answer
- boost::shared_array<char> data;
- // This will be called when the data arrive or timeouts
- Callback* callback;
- // Did we already stop operating (data arrived, we timed out, someone
- // called stop). This can be so when we are cleaning up/there are
- // still pointers to us.
- bool stopped;
- // Timer to measure timeouts.
- deadline_timer timer;
- // How many milliseconds are we willing to wait for answer?
- int timeout;
-
- PrivateData(io_service& service,
- const udp::socket::protocol_type& protocol, const Question &q,
- OutputBufferPtr b, Callback *c) :
- socket(service, protocol),
- question(q),
- buffer(b),
- msgbuf(new OutputBuffer(512)),
- callback(c),
- stopped(false),
- timer(service)
- { }
-};
-
-/// The following functions implement the \c UDPQuery class.
-///
-/// The constructor
-UDPQuery::UDPQuery(io_service& io_service,
- const Question& q, const IOAddress& addr, uint16_t port,
- OutputBufferPtr buffer, Callback *callback, int timeout) :
- data_(new PrivateData(io_service,
- addr.getFamily() == AF_INET ? udp::v4() : udp::v6(), q, buffer,
- callback))
-{
- data_->remote = UDPEndpoint(addr, port).getASIOEndpoint();
- data_->timeout = timeout;
-}
-
-/// The function operator is implemented with the "stackless coroutine"
-/// pattern; see internal/coroutine.h for details.
-void
-UDPQuery::operator()(error_code ec, size_t length) {
- if (ec || data_->stopped) {
- return;
- }
-
- CORO_REENTER (this) {
- /// Generate the upstream query and render it to wire format
- /// This is done in a different scope to allow inline variable
- /// declarations.
- {
- Message msg(Message::RENDER);
-
- // XXX: replace with boost::random or some other suitable PRNG
- msg.setQid(0);
- msg.setOpcode(Opcode::QUERY());
- msg.setRcode(Rcode::NOERROR());
- msg.setHeaderFlag(Message::HEADERFLAG_RD);
- msg.addQuestion(data_->question);
- MessageRenderer renderer(*data_->msgbuf);
- msg.toWire(renderer);
- dlog("Sending " + msg.toText() + " to " +
- data_->remote.address().to_string());
- }
-
-
- // If we timeout, we stop, which will shutdown everything and
- // cancel all other attempts to run inside the coroutine
- if (data_->timeout != -1) {
- data_->timer.expires_from_now(boost::posix_time::milliseconds(
- data_->timeout));
- data_->timer.async_wait(boost::bind(&UDPQuery::stop, *this,
- TIME_OUT));
- }
-
- // Begin an asynchronous send, and then yield. When the
- // send completes, we will resume immediately after this point.
- CORO_YIELD data_->socket.async_send_to(buffer(data_->msgbuf->getData(),
- data_->msgbuf->getLength()), data_->remote, *this);
-
- /// Allocate space for the response. (XXX: This should be
- /// optimized by maintaining a free list of pre-allocated blocks)
- data_->data.reset(new char[MAX_LENGTH]);
-
- /// Begin an asynchronous receive, and yield. When the receive
- /// completes, we will resume immediately after this point.
- CORO_YIELD data_->socket.async_receive_from(buffer(data_->data.get(),
- MAX_LENGTH), data_->remote, *this);
- // The message is not rendered yet, so we can't print it easilly
- dlog("Received response from " + data_->remote.address().to_string());
-
- /// Copy the answer into the response buffer. (XXX: If the
- /// OutputBuffer object were made to meet the requirements of
- /// a MutableBufferSequence, then it could be written to directly
- /// by async_recieve_from() and this additional copy step would
- /// be unnecessary.)
- data_->buffer->writeData(data_->data.get(), length);
-
- /// We are done
- stop(SUCCESS);
- }
-}
-
-void
-UDPQuery::stop(Result result) {
- if (!data_->stopped) {
- switch (result) {
- case TIME_OUT:
- dlog("Query timed out");
- break;
- case STOPPED:
- dlog("Query stopped");
- break;
- default:;
- }
- data_->stopped = true;
- data_->socket.cancel();
- data_->socket.close();
- data_->timer.cancel();
- if (data_->callback) {
- (*data_->callback)(result);
- }
- }
-}
-
-}
diff --git a/src/lib/bench/tests/benchmark_unittest.cc b/src/lib/bench/tests/benchmark_unittest.cc
index f16b47d..7bb8a60 100644
--- a/src/lib/bench/tests/benchmark_unittest.cc
+++ b/src/lib/bench/tests/benchmark_unittest.cc
@@ -12,7 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <unistd.h> // for usleep
+#include <time.h> // for nanosleep
#include <bench/benchmark.h>
@@ -26,16 +26,17 @@ namespace {
// number of iterations.
class TestBenchMark {
public:
- TestBenchMark(const int sub_iterations, const int sleep_time) :
+ TestBenchMark(const int sub_iterations,
+ const struct timespec& sleep_time) :
sub_iterations_(sub_iterations), sleep_time_(sleep_time),
setup_completed_(false), teardown_completed_(false)
{}
unsigned int run() {
- usleep(sleep_time_);
+ nanosleep(&sleep_time_, NULL);
return (sub_iterations_);
}
const int sub_iterations_;
- const int sleep_time_;
+ const struct timespec sleep_time_;
bool setup_completed_;
bool teardown_completed_;
};
@@ -67,6 +68,7 @@ TEST(BenchMarkTest, run) {
// use some uncommon iterations for testing purpose:
const int sub_iterations = 23;
const int sleep_time = 50000; // will sleep for 50ms
+ const struct timespec sleep_timespec = { 0, sleep_time * 1000 };
// we cannot expect particular accuracy on the measured duration, so
// we'll include some conservative margin (25%) and perform range
// comparison below.
@@ -75,12 +77,12 @@ TEST(BenchMarkTest, run) {
// Prerequisite check: since the tests in this case may depend on subtle
// timing, it may result in false positives. There are reportedly systems
- // where usleep() doesn't work as this test expects. So we check the
+ // where sleeping doesn't work as this test expects. So we check the
// conditions before the tests, and if it fails skip the tests at the
// risk of overlooking possible bugs.
struct timeval check_begin, check_end;
gettimeofday(&check_begin, NULL);
- usleep(sleep_time);
+ nanosleep(&sleep_timespec, 0);
gettimeofday(&check_end, NULL);
check_end.tv_sec -= check_begin.tv_sec;
if (check_end.tv_usec >= check_begin.tv_usec) {
@@ -97,7 +99,7 @@ TEST(BenchMarkTest, run) {
return;
}
- TestBenchMark test_bench(sub_iterations, sleep_time);
+ TestBenchMark test_bench(sub_iterations, sleep_timespec);
BenchMark<TestBenchMark> bench(1, test_bench, false);
// Check pre-test conditions.
EXPECT_FALSE(test_bench.setup_completed_);
@@ -130,7 +132,8 @@ TEST(BenchMarkTest, run) {
TEST(BenchMarkTest, runWithNoIteration) {
// we'll lie on the number of iteration (0). it will result in
// meaningless result, but at least it shouldn't crash.
- TestBenchMark test_bench(0, 0);
+ const struct timespec null_timespec = { 0, 0 };
+ TestBenchMark test_bench(0, null_timespec);
BenchMark<TestBenchMark> bench(1, test_bench, false);
bench.run();
EXPECT_EQ(0, bench.getIteration());
diff --git a/src/lib/bench/tests/loadquery_unittest.cc b/src/lib/bench/tests/loadquery_unittest.cc
index 3ac352a..a53e191 100644
--- a/src/lib/bench/tests/loadquery_unittest.cc
+++ b/src/lib/bench/tests/loadquery_unittest.cc
@@ -55,7 +55,7 @@ const char* const LoadQueryTest::DATA_DIR = TEST_DATA_DIR;
class QueryInserter {
public:
QueryInserter(stringstream& stream) : stream_(stream) {}
- void operator()(const QueryParam& query) {
+ void operator()(const QueryParam& query) const {
stream_ << query.first << " " << query.second << endl;
}
private:
diff --git a/src/lib/cache/Makefile.am b/src/lib/cache/Makefile.am
new file mode 100644
index 0000000..107fc9a
--- /dev/null
+++ b/src/lib/cache/Makefile.am
@@ -0,0 +1,34 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/nsas -I$(top_builddir)/src/lib/nsas
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/cache -I$(top_builddir)/src/lib/cache
+AM_CPPFLAGS += $(SQLITE_CFLAGS)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_CLANGPP
+# clang++ complains about unused function parameters in some boost header
+# files.
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+lib_LTLIBRARIES = libcache.la
+libcache_la_SOURCES = resolver_cache.h resolver_cache.cc
+libcache_la_SOURCES += message_cache.h message_cache.cc
+libcache_la_SOURCES += message_entry.h message_entry.cc
+libcache_la_SOURCES += rrset_cache.h rrset_cache.cc
+libcache_la_SOURCES += rrset_entry.h rrset_entry.cc
+libcache_la_SOURCES += cache_entry_key.h cache_entry_key.cc
+libcache_la_SOURCES += rrset_copy.h rrset_copy.cc
+libcache_la_SOURCES += local_zone_data.h local_zone_data.cc
+libcache_la_SOURCES += message_utility.h message_utility.cc
+
+CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/cache/TODO b/src/lib/cache/TODO
new file mode 100644
index 0000000..aa7e3b0
--- /dev/null
+++ b/src/lib/cache/TODO
@@ -0,0 +1,18 @@
+* Revisit the algorithm used by getRRsetTrustLevel() in message_entry.cc.
+* Implement dump/load/resize interfaces of rrset/message/recursor cache.
+* Once LRU hash table is implemented, it should be used by message/rrset cache.
+* Once the hash/lrulist related files in /lib/nsas is moved to seperated
+ folder, the code of recursor cache has to be updated.
+* Set proper AD flags once DNSSEC is supported by the cache.
+* When the message or rrset entry has expired, it should be removed
+ from the cache, or just moved to the head of LRU list, so that it
+ can removed first.
+* Make resolver cache be smart to refetch the messages that are about
+ to expire.
+* When the rrset beging updated is an NS rrset, NSAS should be updated
+ together.
+* Share the NXDOMAIN info between different type queries. current implementation
+ can only cache for the type that user quired, for example, if user query A
+ record of a.example. and the server replied with NXDOMAIN, this should be
+ cached for all the types queries of a.example.
+
diff --git a/src/lib/cache/cache_entry_key.cc b/src/lib/cache/cache_entry_key.cc
new file mode 100644
index 0000000..85c03a0
--- /dev/null
+++ b/src/lib/cache/cache_entry_key.cc
@@ -0,0 +1,42 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <sstream>
+#include "cache_entry_key.h"
+
+using namespace std;
+
+namespace isc {
+namespace cache {
+const std::string
+genCacheEntryName(const isc::dns::Name& name, const isc::dns::RRType& type) {
+ std::string keystr = name.toText();
+ ostringstream stream;
+ stream << type.getCode();
+ keystr += stream.str();
+ return (keystr);
+}
+
+const std::string
+genCacheEntryName(const std::string& namestr, const uint16_t type) {
+ std::string keystr = namestr;
+ ostringstream stream;
+ stream << type;
+ keystr += stream.str();
+ return (keystr);
+}
+
+} // namespace cache
+} // namespace isc
+
diff --git a/src/lib/cache/cache_entry_key.h b/src/lib/cache/cache_entry_key.h
new file mode 100644
index 0000000..674deb0
--- /dev/null
+++ b/src/lib/cache/cache_entry_key.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __CACHE_ENTRY_KEY_H
+#define __CACHE_ENTRY_KEY_H
+
+#include <string>
+#include <dns/name.h>
+#include <dns/rrtype.h>
+
+namespace isc {
+namespace cache {
+
+/// \brief Entry Name Generation Functions
+///
+/// Generate the name for message/rrset entries.
+///
+/// Concatenates the string representation of the Name and the
+/// string representation of the type number.
+///
+/// Note: the returned name is a text string, not wire format.
+/// eg. if name is 'example.com.', type is 'A', the return
+/// value is 'example.com.1'
+///
+/// \param name The Name to create a text entry for
+/// \param type The RRType to create a text entry for
+/// \return return the entry name.
+const std::string
+genCacheEntryName(const isc::dns::Name& name, const isc::dns::RRType& type);
+
+///
+/// \overload
+///
+/// \param namestr A string representation of a DNS Name
+/// \param type The value of a DNS RRType
+const std::string
+genCacheEntryName(const std::string& namestr, const uint16_t type);
+
+} // namespace cache
+} // namespace isc
+
+#endif // __CACHE_ENTRY_KEY_H
+
diff --git a/src/lib/cache/local_zone_data.cc b/src/lib/cache/local_zone_data.cc
new file mode 100644
index 0000000..61ce35a
--- /dev/null
+++ b/src/lib/cache/local_zone_data.cc
@@ -0,0 +1,56 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/rrset.h>
+#include "local_zone_data.h"
+#include "cache_entry_key.h"
+#include "rrset_copy.h"
+
+using namespace std;
+using namespace isc::dns;
+
+namespace isc {
+namespace cache {
+
+typedef pair<std::string, RRsetPtr> RRsetMapPair;
+typedef map<std::string, RRsetPtr>::iterator RRsetMapIterator;
+
+isc::dns::RRsetPtr
+LocalZoneData::lookup(const isc::dns::Name& name,
+ const isc::dns::RRType& type)
+{
+ string key = genCacheEntryName(name, type);
+ RRsetMapIterator iter = rrsets_map_.find(key);
+ if (iter == rrsets_map_.end()) {
+ return (RRsetPtr());
+ } else {
+ return (iter->second);
+ }
+}
+
+void
+LocalZoneData::update(const isc::dns::RRset& rrset) {
+ //TODO Do we really need to recreate the rrset again?
+ string key = genCacheEntryName(rrset.getName(), rrset.getType());
+ RRset* rrset_copy = new RRset(rrset.getName(), rrset.getClass(),
+ rrset.getType(), rrset.getTTL());
+
+ rrsetCopy(rrset, *rrset_copy);
+ RRsetPtr rrset_ptr(rrset_copy);
+ rrsets_map_[key] = rrset_ptr;
+}
+
+} // namespace cache
+} // namespace isc
+
diff --git a/src/lib/cache/local_zone_data.h b/src/lib/cache/local_zone_data.h
new file mode 100644
index 0000000..3015847
--- /dev/null
+++ b/src/lib/cache/local_zone_data.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef _LOCAL_ZONE_DATA
+#define _LOCAL_ZONE_DATA
+
+#include <map>
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include <dns/rrset.h>
+
+namespace isc {
+namespace cache {
+
+/// \brief Local Zone Data
+/// The object of LocalZoneData represents the data of one
+/// local zone. It provides the interface for lookup the rrsets
+/// in the zone.
+class LocalZoneData {
+public:
+ LocalZoneData(uint16_t rrset_class) : class_(rrset_class)
+ {}
+
+ /// \brief Look up one rrset.
+ ///
+ /// \param qname The query name to look up
+ /// \param qtype The query type to look up
+ /// \return return the shared_ptr of rrset if it is
+ /// found in the local zone, or else, return NULL.
+ isc::dns::RRsetPtr lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype);
+
+ /// \brief Update the rrset in the local zone.
+ ///
+ /// If the rrset doesn't exist, it will be added.
+ /// Otherwise, the existed one will be overwritten.
+ ///
+ /// \param rrset The rrset to update
+ void update(const isc::dns::RRset& rrset);
+
+private:
+ std::map<std::string, isc::dns::RRsetPtr> rrsets_map_; // RRsets of the zone
+ uint16_t class_; // The class of the zone
+};
+
+typedef boost::shared_ptr<LocalZoneData> LocalZoneDataPtr;
+typedef boost::shared_ptr<const LocalZoneData> ConstLocalZoneDataPtr;
+
+} // namespace cache
+} // namespace isc
+
+#endif // _LOCAL_ZONE_DATA
+
diff --git a/src/lib/cache/message_cache.cc b/src/lib/cache/message_cache.cc
new file mode 100644
index 0000000..50922fd
--- /dev/null
+++ b/src/lib/cache/message_cache.cc
@@ -0,0 +1,115 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <nsas/nsas_entry_compare.h>
+#include <nsas/hash_table.h>
+#include <nsas/hash_deleter.h>
+#include "message_cache.h"
+#include "message_utility.h"
+#include "cache_entry_key.h"
+
+namespace isc {
+namespace cache {
+
+using namespace isc::nsas;
+using namespace isc::dns;
+using namespace std;
+using namespace MessageUtility;
+
+MessageCache::MessageCache(const RRsetCachePtr& rrset_cache,
+ uint32_t cache_size, uint16_t message_class,
+ const RRsetCachePtr& negative_soa_cache):
+ message_class_(message_class),
+ rrset_cache_(rrset_cache),
+ negative_soa_cache_(negative_soa_cache),
+ message_table_(new NsasEntryCompare<MessageEntry>, cache_size),
+ message_lru_((3 * cache_size),
+ new HashDeleter<MessageEntry>(message_table_))
+{
+}
+
+bool
+MessageCache::lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ isc::dns::Message& response)
+{
+ std::string entry_name = genCacheEntryName(qname, qtype);
+ HashKey entry_key = HashKey(entry_name, RRClass(message_class_));
+ MessageEntryPtr msg_entry = message_table_.get(entry_key);
+ if(msg_entry) {
+ // Check whether the message entry has expired.
+ if (msg_entry->getExpireTime() > time(NULL)) {
+ message_lru_.touch(msg_entry);
+ return (msg_entry->genMessage(time(NULL), response));
+ } else {
+ // message entry expires, remove it from hash table and lru list.
+ message_table_.remove(entry_key);
+ message_lru_.remove(msg_entry);
+ return (false);
+ }
+ }
+
+ return (false);
+}
+
+bool
+MessageCache::update(const Message& msg) {
+ if (!canMessageBeCached(msg)){
+ return (false);
+ }
+
+ QuestionIterator iter = msg.beginQuestion();
+ std::string entry_name = genCacheEntryName((*iter)->getName(),
+ (*iter)->getType());
+ HashKey entry_key = HashKey(entry_name, RRClass(message_class_));
+
+ // The simplest way to update is removing the old message entry directly.
+ // We have find the existed message entry, since we need to delete it
+ // from lru list too.
+ // TODO, but there should be a better way, since we here have to remove and
+ // add the message entry, maybe there is one way to touch it once.
+ MessageEntryPtr old_msg_entry = message_table_.get(entry_key);
+ if (old_msg_entry) {
+ message_lru_.remove(old_msg_entry);
+ }
+
+ MessageEntryPtr msg_entry(new MessageEntry(msg, rrset_cache_,
+ negative_soa_cache_));
+ message_lru_.add(msg_entry);
+ return (message_table_.add(msg_entry, entry_key, true));
+}
+
+#if 0
+void
+MessageCache::dump(const std::string&) {
+ //TODO
+}
+
+void
+MessageCache::load(const std::string&) {
+ //TODO
+}
+
+bool
+MessageCache::resize(uint32_t) {
+ //TODO
+ return (true);
+}
+#endif
+
+} // namespace cache
+} // namespace isc
+
diff --git a/src/lib/cache/message_cache.h b/src/lib/cache/message_cache.h
new file mode 100644
index 0000000..63db681
--- /dev/null
+++ b/src/lib/cache/message_cache.h
@@ -0,0 +1,104 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __MESSAGE_CACHE_H
+#define __MESSAGE_CACHE_H
+
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include <dns/message.h>
+#include "message_entry.h"
+#include <nsas/hash_table.h>
+#include <nsas/lru_list.h>
+#include "rrset_cache.h"
+
+namespace isc {
+namespace cache {
+
+/// \brief Message Cache
+/// The object of MessageCache represents the cache for class-specific
+/// messages.
+///
+class MessageCache {
+// Noncopyable
+private:
+ MessageCache(const MessageCache& source);
+ MessageCache& operator=(const MessageCache& source);
+public:
+ /// \param rrset_cache The cache that stores the RRsets that the
+ /// message entry will points to
+ /// \param cache_size The size of message cache.
+ /// \param message_class The class of the message cache
+ /// \param negative_soa_cache The cache that stores the SOA record
+ /// that comes from negative response message
+ MessageCache(const RRsetCachePtr& rrset_cache,
+ uint32_t cache_size, uint16_t message_class,
+ const RRsetCachePtr& negative_soa_cache);
+
+ /// \brief Destructor function
+ virtual ~MessageCache() {}
+
+ /// \brief Look up message in cache.
+ /// \param message generated response message if the message entry
+ /// can be found.
+ ///
+ /// \return return true if the message can be found in cache, or else,
+ /// return false.
+ //TODO Maybe some user just want to get the message_entry.
+ bool lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ isc::dns::Message& message);
+
+ /// \brief Update the message in the cache with the new one.
+ /// If the message doesn't exist in the cache, it will be added
+ /// directly.
+ bool update(const isc::dns::Message& msg);
+
+#if 0
+ /// \brief Dump the message cache to specified file.
+ /// \todo It should can be dumped to one configured database.
+ void dump(const std::string& file_name);
+
+ /// \brief Load the cache from one file.
+ /// \todo It should can be loaded from one configured database.
+ void load(const std::string& file_name);
+
+ /// \brief Resize the size of message cache in runtime.
+ bool resize(uint32_t size);
+#endif
+
+protected:
+ /// \brief Get the hash key for the message entry in the cache.
+ /// \param name query name of the message.
+ /// \param type query type of the message.
+ /// \return return the hash key.
+ HashKey getEntryHashKey(const isc::dns::Name& name,
+ const isc::dns::RRType& type) const;
+
+ // Make these variants be protected for easy unittest.
+protected:
+ uint16_t message_class_; // The class of the message cache.
+ RRsetCachePtr rrset_cache_;
+ RRsetCachePtr negative_soa_cache_;
+ isc::nsas::HashTable<MessageEntry> message_table_;
+ isc::nsas::LruList<MessageEntry> message_lru_;
+};
+
+typedef boost::shared_ptr<MessageCache> MessageCachePtr;
+
+} // namespace cache
+} // namespace isc
+
+#endif // __MESSAGE_CACHE_H
+
diff --git a/src/lib/cache/message_entry.cc b/src/lib/cache/message_entry.cc
new file mode 100644
index 0000000..de4ea89
--- /dev/null
+++ b/src/lib/cache/message_entry.cc
@@ -0,0 +1,345 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <limits>
+#include <dns/message.h>
+#include <nsas/nsas_entry.h>
+#include "message_entry.h"
+#include "message_utility.h"
+#include "rrset_cache.h"
+
+using namespace isc::dns;
+using namespace std;
+
+// Put file scope functions in unnamed namespace.
+namespace {
+
+// Get the shortest existing ancestor which is the owner name of
+// one DNAME record for the given query name.
+// Note: there may be multiple DNAME records(DNAME chain) in answer
+// section. In most cases they are in order, but the code can't depend
+// on that, it has to find the starter by iterating the DNAME chain.
+Name
+getDNAMEChainStarter(const Message& message, const Name& query_name) {
+ Name dname = query_name;
+ RRsetIterator rrset_iter = message.beginSection(Message::SECTION_ANSWER);
+ while(rrset_iter != message.endSection(Message::SECTION_ANSWER)) {
+ if ((*rrset_iter)->getType() == RRType::DNAME()) {
+ const Name& rrname = (*rrset_iter)->getName();
+ if (NameComparisonResult::SUBDOMAIN ==
+ dname.compare(rrname).getRelation()) {
+ dname = rrname;
+ }
+ }
+ ++rrset_iter;
+ }
+
+ return (dname);
+}
+
+} // End of unnamed namespace
+
+namespace isc {
+namespace cache {
+
+static uint32_t MAX_UINT32 = numeric_limits<uint32_t>::max();
+
+// As with caching positive responses it is sensible for a resolver to
+// limit for how long it will cache a negative response as the protocol
+// supports caching for up to 68 years. Such a limit should not be
+// greater than that applied to positive answers and preferably be
+// tunable. Values of one to three hours have been found to work well
+// and would make sensible a default. Values exceeding one day have
+// been found to be problematic. (sec 5, RFC2308)
+// The default value is 3 hourse (10800 seconds)
+// TODO:Give an option to let user configure
+static uint32_t MAX_NEGATIVE_CACHE_TTL = 10800;
+
+// Sets the maximum time for which the server will cache ordinary (positive) answers. The
+// default is one week (7 days = 604800 seconds)
+// TODO:Give an option to let user configure
+static uint32_t MAX_NORMAL_CACHE_TTL = 604800;
+
+MessageEntry::MessageEntry(const isc::dns::Message& msg,
+ const RRsetCachePtr& rrset_cache,
+ const RRsetCachePtr& negative_soa_cache):
+ rrset_cache_(rrset_cache),
+ negative_soa_cache_(negative_soa_cache),
+ headerflag_aa_(false),
+ headerflag_tc_(false)
+{
+ initMessageEntry(msg);
+ entry_name_ = genCacheEntryName(query_name_, query_type_);
+ hash_key_ptr_ = new HashKey(entry_name_, RRClass(query_class_));
+}
+
+bool
+MessageEntry::getRRsetEntries(vector<RRsetEntryPtr>& rrset_entry_vec,
+ const time_t time_now)
+{
+ uint16_t entry_count = answer_count_ + authority_count_ + additional_count_;
+ rrset_entry_vec.reserve(rrset_entry_vec.size() + entry_count);
+ for (int index = 0; index < entry_count; ++index) {
+ RRsetCache* rrset_cache = rrsets_[index].cache_;
+ RRsetEntryPtr rrset_entry = rrset_cache->lookup(rrsets_[index].name_,
+ rrsets_[index].type_);
+ if (rrset_entry && time_now < rrset_entry->getExpireTime()) {
+ rrset_entry_vec.push_back(rrset_entry);
+ } else {
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+void
+MessageEntry::addRRset(isc::dns::Message& message,
+ const vector<RRsetEntryPtr>& rrset_entry_vec,
+ const isc::dns::Message::Section& section,
+ bool dnssec_need)
+{
+ uint16_t start_index = 0;
+ uint16_t end_index = answer_count_;
+ assert(section != Message::SECTION_QUESTION);
+
+ if (section == Message::SECTION_AUTHORITY) {
+ start_index = answer_count_;
+ end_index = answer_count_ + authority_count_;
+ } else if (section == Message::SECTION_ADDITIONAL) {
+ start_index = answer_count_ + authority_count_;
+ end_index = start_index + additional_count_;
+ }
+
+ for (uint16_t index = start_index; index < end_index; ++index) {
+ message.addRRset(section, rrset_entry_vec[index]->getRRset(),
+ dnssec_need);
+ }
+}
+
+bool
+MessageEntry::genMessage(const time_t& time_now,
+ isc::dns::Message& msg)
+{
+ if (time_now >= expire_time_) {
+ // The message entry has expired.
+ return (false);
+ } else {
+ // Before do any generation, we should check if some rrset
+ // has expired, if it is, return false.
+ vector<RRsetEntryPtr> rrset_entry_vec;
+ if (false == getRRsetEntries(rrset_entry_vec, time_now)) {
+ return (false);
+ }
+
+ // Begin message generation. We don't need to add question
+ // section, since it has been included in the message.
+ // Set cached header flags.
+ // The AA flag bit should be cleared because this is a response from
+ // resolver cache
+ msg.setHeaderFlag(Message::HEADERFLAG_AA, false);
+ msg.setHeaderFlag(Message::HEADERFLAG_TC, headerflag_tc_);
+
+ bool dnssec_need = msg.getEDNS().get();
+ addRRset(msg, rrset_entry_vec, Message::SECTION_ANSWER, dnssec_need);
+ addRRset(msg, rrset_entry_vec, Message::SECTION_AUTHORITY, dnssec_need);
+ addRRset(msg, rrset_entry_vec, Message::SECTION_ADDITIONAL, dnssec_need);
+
+ return (true);
+ }
+}
+
+RRsetTrustLevel
+MessageEntry::getRRsetTrustLevel(const Message& message,
+ const isc::dns::RRsetPtr& rrset,
+ const isc::dns::Message::Section& section)
+{
+ bool aa = message.getHeaderFlag(Message::HEADERFLAG_AA);
+ switch(section) {
+ case Message::SECTION_ANSWER: {
+ if (aa) {
+ // According RFC2181 section 5.4.1, only the record
+ // describing that ailas is necessarily authoritative.
+ // If there are CNAME(Not synchronized from DNAME)
+ // records in answer section, only the CNAME record
+ // whose owner name is same with qname is assumed as
+ // authoritative, all the left records are not authoritative.
+ //
+ // If there are DNAME records in answer section,
+ // Only the start DNAME and the synchronized CNAME record
+ // from it are authoritative, any other records in answer
+ // section are non-authoritative.
+ QuestionIterator quest_iter = message.beginQuestion();
+ // Make sure question section is not empty.
+ assert( quest_iter != message.endQuestion());
+
+ const Name& query_name = (*quest_iter)->getName();
+ const RRType& type = rrset->getType();
+ const Name& name = rrset->getName();
+ if ((type == RRType::CNAME() && name == query_name) ||
+ (type == RRType::DNAME() &&
+ name == getDNAMEChainStarter(message, query_name))) {
+ return (RRSET_TRUST_ANSWER_AA);
+ } else {
+ // If there is a CNAME record whose ower name is the same as
+ // the query name in answer section, the other records in answer
+ // section are non-authoritative, except the starter of DNAME
+ // chain (only checking CNAME is enough, because if the CNAME
+ // record is synthesized from a DNAME record, that DNAME
+ // record must be the starter of the DNAME chain).
+ RRsetIterator iter = message.beginSection(Message::SECTION_ANSWER);
+ while(iter != message.endSection(Message::SECTION_ANSWER)) {
+ if ((*iter)->getType() == RRType::CNAME() &&
+ (*iter)->getName() == query_name) {
+ return (RRSET_TRUST_ANSWER_NONAA);
+ }
+ ++iter;
+ }
+ }
+ return (RRSET_TRUST_ANSWER_AA);
+ } else {
+ return (RRSET_TRUST_ANSWER_NONAA);
+ }
+ break;
+ }
+
+ case Message::SECTION_AUTHORITY: {
+ if (aa) {
+ return (RRSET_TRUST_AUTHORITY_AA);
+ } else {
+ return (RRSET_TRUST_AUTHORITY_NONAA);
+ }
+ break;
+ }
+
+ case Message::SECTION_ADDITIONAL: {
+ if (aa) {
+ return (RRSET_TRUST_ADDITIONAL_AA);
+ } else {
+ return (RRSET_TRUST_ADDITIONAL_NONAA);
+ }
+ break;
+ }
+
+ default:
+ return (RRSET_TRUST_DEFAULT);
+ }
+}
+
+void
+MessageEntry::parseSection(const isc::dns::Message& msg,
+ const Message::Section& section,
+ uint32_t& smaller_ttl,
+ uint16_t& rrset_count)
+{
+ RRsetIterator iter;
+ int count = 0;
+ for (iter = msg.beginSection(section);
+ iter != msg.endSection(section);
+ ++iter) {
+ // Add the rrset entry to rrset_cache or update the existed
+ // rrset entry if the new one is more authoritative.
+ //TODO set proper rrset trust level.
+ RRsetPtr rrset_ptr = *iter;
+ RRsetTrustLevel level = getRRsetTrustLevel(msg, rrset_ptr, section);
+ RRsetEntryPtr rrset_entry = rrset_cache_->update(*rrset_ptr, level);
+ rrsets_.push_back(RRsetRef(rrset_ptr->getName(), rrset_ptr->getType(),
+ rrset_cache_.get()));
+
+ uint32_t rrset_ttl = rrset_entry->getTTL();
+ if (smaller_ttl > rrset_ttl) {
+ smaller_ttl = rrset_ttl;
+ }
+
+ count++;
+ }
+
+ rrset_count = count;
+}
+
+void
+MessageEntry::parseNegativeResponseAuthoritySection(const isc::dns::Message& msg,
+ uint32_t& min_ttl,
+ uint16_t& rrset_count)
+{
+ uint16_t count = 0;
+ for (RRsetIterator iter = msg.beginSection(Message::SECTION_AUTHORITY);
+ iter != msg.endSection(Message::SECTION_AUTHORITY);
+ ++iter) {
+ RRsetPtr rrset_ptr = *iter;
+ RRsetTrustLevel level = getRRsetTrustLevel(msg, rrset_ptr,
+ Message::SECTION_AUTHORITY);
+ boost::shared_ptr<RRsetCache> rrset_cache_ptr = rrset_cache_;
+ if (rrset_ptr->getType() == RRType::SOA()) {
+ rrset_cache_ptr = negative_soa_cache_;
+ }
+
+ RRsetEntryPtr rrset_entry = rrset_cache_ptr->update(*rrset_ptr, level);
+ rrsets_.push_back(RRsetRef(rrset_ptr->getName(),
+ rrset_ptr->getType(),
+ rrset_cache_ptr.get()));
+ uint32_t rrset_ttl = rrset_entry->getTTL();
+ if (min_ttl > rrset_ttl) {
+ min_ttl = rrset_ttl;
+ }
+ ++count;
+ }
+
+ rrset_count = count;
+}
+
+void
+MessageEntry::initMessageEntry(const isc::dns::Message& msg) {
+ //TODO better way to cache the header flags?
+ headerflag_aa_ = msg.getHeaderFlag(Message::HEADERFLAG_AA);
+ headerflag_tc_ = msg.getHeaderFlag(Message::HEADERFLAG_TC);
+
+ // We only cache the first question in question section.
+ // TODO, do we need to support muptiple questions?
+ query_count_ = 1;
+ QuestionIterator iter = msg.beginQuestion();
+ query_name_ = (*iter)->getName().toText();
+ query_type_ = (*iter)->getType().getCode();
+ query_class_ = (*iter)->getClass().getCode();
+
+ uint32_t min_ttl = MAX_UINT32;
+
+ bool isNegativeResponse = MessageUtility::isNegativeResponse(msg);
+
+ parseSection(msg, Message::SECTION_ANSWER, min_ttl, answer_count_);
+ if (!isNegativeResponse) {
+ parseSection(msg, Message::SECTION_AUTHORITY, min_ttl, authority_count_);
+ } else {
+ parseNegativeResponseAuthoritySection(msg, min_ttl, authority_count_);
+ }
+ parseSection(msg, Message::SECTION_ADDITIONAL, min_ttl, additional_count_);
+
+ // Limit the ttl to a prset max-value
+ if (!isNegativeResponse) {
+ if (min_ttl > MAX_NORMAL_CACHE_TTL) {
+ min_ttl = MAX_NORMAL_CACHE_TTL;
+ }
+ } else {
+ if (min_ttl > MAX_NEGATIVE_CACHE_TTL) {
+ min_ttl = MAX_NEGATIVE_CACHE_TTL;
+ }
+ }
+
+ expire_time_ = time(NULL) + min_ttl;
+}
+
+} // namespace cache
+} // namespace isc
diff --git a/src/lib/cache/message_entry.h b/src/lib/cache/message_entry.h
new file mode 100644
index 0000000..7d86495
--- /dev/null
+++ b/src/lib/cache/message_entry.h
@@ -0,0 +1,203 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __MESSAGE_ENTRY_H
+#define __MESSAGE_ENTRY_H
+
+#include <vector>
+#include <dns/message.h>
+#include <dns/rrset.h>
+#include <nsas/nsas_entry.h>
+#include "rrset_cache.h"
+#include "rrset_entry.h"
+
+using namespace isc::nsas;
+
+namespace isc {
+namespace cache {
+
+class RRsetEntry;
+
+/// \brief Message Entry
+///
+/// The object of MessageEntry represents one response message
+/// answered to the resolver client.
+class MessageEntry : public NsasEntry<MessageEntry> {
+// Noncopyable
+private:
+ MessageEntry(const MessageEntry& source);
+ MessageEntry& operator=(const MessageEntry& source);
+
+ /// \brief Information to refer an RRset.
+ ///
+ /// There is no class information here, since the rrsets are cached in
+ /// the class-specific rrset cache.
+ struct RRsetRef{
+ /// \brief Constructor
+ ///
+ /// \param name The Name for the RRset
+ /// \param type The RRType for the RRrset
+ /// \param cache Which cache the RRset is stored in
+ RRsetRef(const isc::dns::Name& name, const isc::dns::RRType& type,
+ RRsetCache* cache):
+ name_(name), type_(type), cache_(cache)
+ {}
+
+ isc::dns::Name name_; // Name of rrset.
+ isc::dns::RRType type_; // Type of rrset.
+ RRsetCache* cache_; //Which cache the RRset is stored
+ };
+
+public:
+
+ /// \brief Initialize the message entry object with one dns
+ /// message.
+ /// \param message The message used to initialize MessageEntry.
+ /// \param rrset_cache the pointer of RRsetCache. When one message
+ /// entry is created, rrset cache needs to be updated,
+ /// since some new rrset entries may be inserted into
+ /// rrset cache, or the existed rrset entries need
+ /// to be updated.
+ /// \param negative_soa_cache the pointer of RRsetCache. This
+ /// cache is used only for storing SOA rrset from negative
+ /// response (NXDOMAIN or NOERROR_NODATA)
+ MessageEntry(const isc::dns::Message& message,
+ const RRsetCachePtr& rrset_cache,
+ const RRsetCachePtr& negative_soa_cache);
+
+ /// \brief generate one dns message according
+ /// the rrsets information of the message.
+ ///
+ /// \param time_now set the ttl of each rrset in the message
+ /// as "expire_time - time_now" (expire_time is the
+ /// expiration time of the rrset).
+ /// \param response generated dns message.
+ /// \return return true if the response message can be generated
+ /// from the cached information, or else, return false.
+ bool genMessage(const time_t& time_now, isc::dns::Message& response);
+
+ /// \brief Get the hash key of the message entry.
+ ///
+ /// \return return hash key
+ virtual HashKey hashKey() const {
+ return (*hash_key_ptr_);
+ }
+
+ /// \brief Get expire time of the message entry.
+ /// \return return the expire time of message entry.
+ time_t getExpireTime() const {
+ return (expire_time_);
+ }
+
+ /// \short Protected memebers, so they can be accessed by tests.
+ //@{
+protected:
+ /// \brief Initialize the message entry with dns message.
+ ///
+ /// \param message The Message to initialize the entry with
+ void initMessageEntry(const isc::dns::Message& message);
+
+ /// \brief Parse the rrsets in specified section.
+ ///
+ /// \param msg The message to parse the RRsets from
+ /// \param section The Section to parse the RRsets from
+ /// \param smaller_ttl Get the smallest ttl of rrsets in
+ /// specified section, if it's smaller than the given value.
+ /// \param rrset_count the rrset count of the section.
+ /// (TODO for Message, getRRsetCount() should be one
+ /// interface provided by Message.)
+ void parseSection(const isc::dns::Message& msg,
+ const isc::dns::Message::Section& section,
+ uint32_t& smaller_ttl,
+ uint16_t& rrset_count);
+
+ /// \brief Parse the RRsets in the authority section of
+ /// negative response. The SOA RRset need to be located and
+ /// stored in a seperate cache
+ /// \param msg The message to parse the RRsets from
+ /// \param min_ttl Get the minimum ttl of rrset in the authority section
+ /// \param rrset_count the rrset count of the authority section
+ void parseNegativeResponseAuthoritySection(const isc::dns::Message& msg,
+ uint32_t& min_ttl,
+ uint16_t& rrset_count);
+
+ /// \brief Get RRset Trustworthiness
+ /// The algorithm refers to RFC2181 section 5.4.1
+ /// Only the rrset can be updated by the rrsets
+ /// with higher trust level.
+ ///
+ /// \param message Message that the rrset belongs to
+ /// \param rrset specified rrset which needs to get its
+ /// trust worthiness
+ /// \param section Section of the rrset
+ /// \return return rrset trust level.
+ RRsetTrustLevel getRRsetTrustLevel(const isc::dns::Message& message,
+ const isc::dns::RRsetPtr& rrset,
+ const isc::dns::Message::Section& section);
+
+ /// \brief Add rrset to one section of message.
+ ///
+ /// \param message The message to add rrsets to.
+ /// \param rrset_entry_vec vector for rrset entries in
+ /// different sections.
+ /// \param section The section to add to
+ /// \param dnssec_need need dnssec records or not.
+ void addRRset(isc::dns::Message& message,
+ const std::vector<RRsetEntryPtr>& rrset_entry_vec,
+ const isc::dns::Message::Section& section,
+ bool dnssec_need);
+
+ /// \brief Get the all the rrset entries for the message entry.
+ ///
+ /// \param rrset_entry_vec vector to add unexpired rrset entries to
+ /// \param time_now the time of now. Used to compare with rrset
+ /// entry's expire time.
+ /// \return return false if any rrset entry has expired, true
+ /// otherwise.
+ bool getRRsetEntries(std::vector<RRsetEntryPtr>& rrset_entry_vec,
+ const time_t time_now);
+
+ time_t expire_time_; // Expiration time of the message.
+ //@}
+
+private:
+ std::string entry_name_; // The name for this entry(name + type)
+ HashKey* hash_key_ptr_; // the key for messag entry in hash table.
+
+ std::vector<RRsetRef> rrsets_;
+ RRsetCachePtr rrset_cache_; //Normal rrset cache
+ // SOA rrset from negative response
+ RRsetCachePtr negative_soa_cache_;
+
+ std::string query_name_; // query name of the message.
+ uint16_t query_class_; // query class of the message.
+ uint16_t query_type_; // query type of message.
+
+ uint16_t query_count_; // query count in query section.
+ uint16_t answer_count_; // rrset count in answer section.
+ uint16_t authority_count_; // rrset count in authority section.
+ uint16_t additional_count_; // rrset count in addition section.
+
+ //TODO, there should be a better way to cache these header flags
+ bool headerflag_aa_; // Whether AA bit is set.
+ bool headerflag_tc_; // Whether TC bit is set.
+};
+
+typedef boost::shared_ptr<MessageEntry> MessageEntryPtr;
+
+} // namespace cache
+} // namespace isc
+
+#endif // __MESSAGE_ENTRY_H
+
diff --git a/src/lib/cache/message_utility.cc b/src/lib/cache/message_utility.cc
new file mode 100644
index 0000000..53a3352
--- /dev/null
+++ b/src/lib/cache/message_utility.cc
@@ -0,0 +1,80 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include "message_utility.h"
+#include <dns/rcode.h>
+
+using namespace isc::dns;
+
+namespace isc {
+namespace cache {
+namespace MessageUtility{
+
+bool
+hasTheRecordInAuthoritySection(const isc::dns::Message& msg,
+ const isc::dns::RRType& type)
+{
+ // isc::dns::Message provide one function hasRRset() should be used to
+ // determine whether the given section has an RRset matching the given
+ // name and type, but currently it is not const-qualified and cannot be
+ // used here
+ // TODO: use hasRRset() function when it is const qualified
+ for (RRsetIterator iter = msg.beginSection(Message::SECTION_AUTHORITY);
+ iter != msg.endSection(Message::SECTION_AUTHORITY);
+ ++iter) {
+ RRsetPtr rrset_ptr = *iter;
+ if (rrset_ptr->getType() == type) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+isNegativeResponse(const isc::dns::Message& msg) {
+ if (msg.getRcode() == Rcode::NXDOMAIN()) {
+ return (true);
+ } else if (msg.getRcode() == Rcode::NOERROR()) {
+ // no data in the answer section
+ if (msg.getRRCount(Message::SECTION_ANSWER) == 0) {
+ // NODATA type 1/ type 2 (ref sec2.2 of RFC2308)
+ if (hasTheRecordInAuthoritySection(msg, RRType::SOA())) {
+ return (true);
+ } else if (!hasTheRecordInAuthoritySection(msg, RRType::NS())) {
+ // NODATA type 3 (sec2.2 of RFC2308)
+ return (true);
+ }
+ }
+ }
+
+ return (false);
+}
+
+bool
+canMessageBeCached(const isc::dns::Message& msg) {
+ // If the message is a negative response, but no SOA record is found in
+ // the authority section, the message cannot be cached
+ if (isNegativeResponse(msg) &&
+ !hasTheRecordInAuthoritySection(msg, RRType::SOA())){
+ return (false);
+ }
+
+ return (true);
+}
+
+} // namespace MessageUtility
+} // namespace cache
+} // namespace isc
diff --git a/src/lib/cache/message_utility.h b/src/lib/cache/message_utility.h
new file mode 100644
index 0000000..a77af07
--- /dev/null
+++ b/src/lib/cache/message_utility.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __MESSAGE_UTILITY_H
+#define __MESSAGE_UTILITY_H
+
+#include <dns/message.h>
+
+namespace isc {
+namespace cache {
+
+/// \brief Some utility functions to extract info from message
+///
+/// We need to check the message before cache it, for example, if no SOA
+/// record is found in the Authority section of NXDOMAIN response, the
+/// message cannot be cached
+namespace MessageUtility{
+
+/// \brief Check whether there is some type of record in
+/// Authority section
+///
+/// \param msg The response message to be checked
+/// \param type The RR type that need to check
+bool hasTheRecordInAuthoritySection(const isc::dns::Message& msg,
+ const isc::dns::RRType& type);
+
+/// \brief Check whetehr the message is a negative response
+/// (NXDOMAIN or NOERROR_NODATA)
+///
+/// \param msg The response message
+bool isNegativeResponse(const isc::dns::Message& msg);
+
+/// \brief Check whether the message can be cached
+/// Negative responses without SOA records SHOULD NOT be cached as there
+/// is no way to prevent the negative responses looping forever between a
+/// pair of servers even with a short TTL.
+/// Despite the DNS forming a tree of servers, with various mis-
+/// configurations it is possible to form a loop in the query graph, e.g.
+/// two servers listing each other as forwarders, various lame server
+/// configurations. Without a TTL count down a cache negative response
+/// when received by the next server would have its TTL reset. This
+/// negative indication could then live forever circulating between the
+/// servers involved. (Sec 5, RFC2308)
+///
+/// \param msg The response message
+bool canMessageBeCached(const isc::dns::Message& msg);
+
+} // namespace MessageUtility
+} // namespace cache
+} // namespace isc
+
+
+#endif//__MESSAGE_UTILITY_H
diff --git a/src/lib/cache/resolver_cache.cc b/src/lib/cache/resolver_cache.cc
new file mode 100644
index 0000000..261db3c
--- /dev/null
+++ b/src/lib/cache/resolver_cache.cc
@@ -0,0 +1,252 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include "resolver_cache.h"
+#include "dns/message.h"
+#include "rrset_cache.h"
+#include <string>
+#include <algorithm>
+
+using namespace isc::dns;
+using namespace std;
+
+namespace isc {
+namespace cache {
+
+ResolverClassCache::ResolverClassCache(const RRClass& cache_class) :
+ cache_class_(cache_class)
+{
+ local_zone_data_ = LocalZoneDataPtr(new LocalZoneData(cache_class_.getCode()));
+ rrsets_cache_ = RRsetCachePtr(new RRsetCache(RRSET_CACHE_DEFAULT_SIZE,
+ cache_class_.getCode()));
+ // SOA rrset cache from negative response
+ negative_soa_cache_ = RRsetCachePtr(new RRsetCache(NEGATIVE_RRSET_CACHE_DEFAULT_SIZE,
+ cache_class_.getCode()));
+
+ messages_cache_ = MessageCachePtr(new MessageCache(rrsets_cache_,
+ MESSAGE_CACHE_DEFAULT_SIZE,
+ cache_class_.getCode(),
+ negative_soa_cache_));
+}
+
+ResolverClassCache::ResolverClassCache(const CacheSizeInfo& cache_info) :
+ cache_class_(cache_info.cclass)
+{
+ uint16_t klass = cache_class_.getCode();
+ // TODO We should find one way to load local zone data.
+ local_zone_data_ = LocalZoneDataPtr(new LocalZoneData(klass));
+ rrsets_cache_ = RRsetCachePtr(new
+ RRsetCache(cache_info.rrset_cache_size, klass));
+ // SOA rrset cache from negative response
+ negative_soa_cache_ = RRsetCachePtr(new RRsetCache(cache_info.rrset_cache_size,
+ klass));
+
+ messages_cache_ = MessageCachePtr(new MessageCache(rrsets_cache_,
+ cache_info.message_cache_size,
+ klass, negative_soa_cache_));
+}
+
+const RRClass&
+ResolverClassCache::getClass() const {
+ return (cache_class_);
+}
+
+bool
+ResolverClassCache::lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ isc::dns::Message& response) const
+{
+ // message response should has question section already.
+ if (response.beginQuestion() == response.endQuestion()) {
+ isc_throw(MessageNoQuestionSection, "Message has no question section");
+ }
+
+ // First, query in local zone, if the rrset(qname, qtype, qclass) can be
+ // found in local zone, generated reply message with only the rrset in
+ // answer section.
+ RRsetPtr rrset_ptr = local_zone_data_->lookup(qname, qtype);
+ if (rrset_ptr) {
+ response.addRRset(Message::SECTION_ANSWER, rrset_ptr);
+ return (true);
+ }
+
+ // Search in class-specific message cache.
+ return (messages_cache_->lookup(qname, qtype, response));
+}
+
+isc::dns::RRsetPtr
+ResolverClassCache::lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype) const
+{
+ // Algorithm:
+ // 1. Search in local zone data first,
+ // 2. Then do search in rrsets_cache_.
+ RRsetPtr rrset_ptr = local_zone_data_->lookup(qname, qtype);
+ if (rrset_ptr) {
+ return (rrset_ptr);
+ } else {
+ RRsetEntryPtr rrset_entry = rrsets_cache_->lookup(qname, qtype);
+ if (rrset_entry) {
+ return (rrset_entry->getRRset());
+ } else {
+ return (RRsetPtr());
+ }
+ }
+}
+
+bool
+ResolverClassCache::update(const isc::dns::Message& msg) {
+ return (messages_cache_->update(msg));
+}
+
+bool
+ResolverClassCache::updateRRsetCache(const isc::dns::ConstRRsetPtr& rrset_ptr,
+ RRsetCachePtr rrset_cache_ptr)
+{
+ RRsetTrustLevel level;
+ if (rrset_ptr->getType() == RRType::A() ||
+ rrset_ptr->getType() == RRType::AAAA()) {
+ level = RRSET_TRUST_PRIM_GLUE;
+ } else {
+ level = RRSET_TRUST_PRIM_ZONE_NONGLUE;
+ }
+
+ rrset_cache_ptr->update((*rrset_ptr.get()), level);
+ return (true);
+}
+
+bool
+ResolverClassCache::update(const isc::dns::ConstRRsetPtr& rrset_ptr) {
+ // First update local zone, then update rrset cache.
+ local_zone_data_->update((*rrset_ptr.get()));
+ updateRRsetCache(rrset_ptr, rrsets_cache_);
+ return (true);
+}
+
+
+ResolverCache::ResolverCache()
+{
+ class_caches_.push_back(new ResolverClassCache(RRClass::IN()));
+}
+
+ResolverCache::ResolverCache(std::vector<CacheSizeInfo> caches_info)
+{
+ for (int i = 0; i < caches_info.size(); ++i) {
+ class_caches_.push_back(new ResolverClassCache(caches_info[i]));
+ }
+}
+
+ResolverCache::~ResolverCache()
+{
+ for (int i = 0; i < class_caches_.size(); ++i) {
+ delete class_caches_[i];
+ }
+}
+
+bool
+ResolverCache::lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ const isc::dns::RRClass& qclass,
+ isc::dns::Message& response) const
+{
+ ResolverClassCache* cc = getClassCache(qclass);
+ if (cc) {
+ return (cc->lookup(qname, qtype, response));
+ } else {
+ return (false);
+ }
+}
+
+isc::dns::RRsetPtr
+ResolverCache::lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ const isc::dns::RRClass& qclass) const
+{
+ ResolverClassCache* cc = getClassCache(qclass);
+ if (cc) {
+ return (cc->lookup(qname, qtype));
+ } else {
+ return (RRsetPtr());
+ }
+}
+
+isc::dns::RRsetPtr
+ResolverCache::lookupDeepestNS(const isc::dns::Name& qname,
+ const isc::dns::RRClass& qclass) const
+{
+ isc::dns::RRType qtype = RRType::NS();
+ ResolverClassCache* cc = getClassCache(qclass);
+ if (cc) {
+ unsigned int count = qname.getLabelCount();
+ unsigned int level = 0;
+ while(level < count) {
+ Name close_name = qname.split(level);
+ RRsetPtr rrset_ptr = cc->lookup(close_name, qtype);
+ if (rrset_ptr) {
+ return (rrset_ptr);
+ } else {
+ ++level;
+ }
+ }
+ }
+
+ return (RRsetPtr());
+}
+
+bool
+ResolverCache::update(const isc::dns::Message& msg) {
+ QuestionIterator iter = msg.beginQuestion();
+ ResolverClassCache* cc = getClassCache((*iter)->getClass());
+ if (cc) {
+ return (cc->update(msg));
+ } else {
+ return (false);
+ }
+}
+
+bool
+ResolverCache::update(const isc::dns::ConstRRsetPtr& rrset_ptr) {
+ ResolverClassCache* cc = getClassCache(rrset_ptr->getClass());
+ if (cc) {
+ return (cc->update(rrset_ptr));
+ } else {
+ return (false);
+ }
+}
+
+void
+ResolverCache::dump(const std::string&) {
+ //TODO
+}
+
+void
+ResolverCache::load(const std::string&) {
+ //TODO
+}
+
+ResolverClassCache*
+ResolverCache::getClassCache(const isc::dns::RRClass& cache_class) const {
+ for (int i = 0; i < class_caches_.size(); ++i) {
+ if (class_caches_[i]->getClass() == cache_class) {
+ return (class_caches_[i]);
+ }
+ }
+ return (NULL);
+}
+
+} // namespace cache
+} // namespace isc
+
diff --git a/src/lib/cache/resolver_cache.h b/src/lib/cache/resolver_cache.h
new file mode 100644
index 0000000..49818b5
--- /dev/null
+++ b/src/lib/cache/resolver_cache.h
@@ -0,0 +1,337 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __RESOLVER_CACHE_H
+#define __RESOLVER_CACHE_H
+
+#include <map>
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include <dns/rrclass.h>
+#include <dns/message.h>
+#include <exceptions/exceptions.h>
+#include "message_cache.h"
+#include "rrset_cache.h"
+#include "local_zone_data.h"
+
+namespace isc {
+namespace cache {
+class RRsetCache;
+
+//TODO a better proper default cache size
+#define MESSAGE_CACHE_DEFAULT_SIZE 10000
+#define RRSET_CACHE_DEFAULT_SIZE 20000
+#define NEGATIVE_RRSET_CACHE_DEFAULT_SIZE 10000
+
+/// \brief Cache Size Information.
+///
+/// Used to initialize the size of class-specific rrset/message cache.
+struct CacheSizeInfo
+{
+public:
+ /// \brief Constructor
+ ///
+ /// \param cls The RRClass code
+ /// \param msg_cache_size The size for the message cache
+ /// \param rst_cache_size The size for the RRset cache
+ CacheSizeInfo(const isc::dns::RRClass& cls,
+ uint32_t msg_cache_size,
+ uint32_t rst_cache_size):
+ cclass(cls),
+ message_cache_size(msg_cache_size),
+ rrset_cache_size(rst_cache_size)
+ {}
+
+ isc::dns::RRClass cclass; // class of the cache.
+ uint32_t message_cache_size; // the size for message cache.
+ uint32_t rrset_cache_size; // The size for rrset cache.
+};
+
+/// \brief Message has no question section.
+///
+/// Thrown if the given message has no question section when looking up
+/// the message in cache.
+class MessageNoQuestionSection : public isc::Exception {
+public:
+ MessageNoQuestionSection(const char*file, size_t line, const char*what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+/// \brief Class-specific Resolver Cache.
+///
+/// The object of ResolverCache represents the cache of the resolver. It may hold
+/// a list of message/rrset cache which are in different class.
+///
+/// \note Public interaction with the cache should be through ResolverCache,
+/// not directly with this one. (TODO: make this private/hidden/local to the .cc?)
+class ResolverClassCache {
+public:
+ /// \brief Default Constructor.
+ ///
+ /// Only support for class "IN", and message cache size is
+ /// MESSAGE_CACHE_DEFAULT_SIZE, rrset cache size is
+ /// RRSET_CACHE_DEFAULT_SIZE
+ ResolverClassCache(const isc::dns::RRClass& cache_class);
+
+ /// \brief Construct Function.
+ /// \param caches_size cache size information for each
+ /// messages/rrsets of different classes.
+ ResolverClassCache(const CacheSizeInfo& cache_info);
+
+ /// \name Lookup Interfaces
+ //@{
+ /// \brief Look up message in cache.
+ ///
+ /// \param qname The query name to look up
+ /// \param qtype The query type to look up
+ /// \param response the query message (must be in RENDER mode)
+ /// which has question section already (exception
+ /// MessageNoQeustionSection will be thrown if it has
+ /// no question section). If the message can be found
+ /// in cache, rrsets for the message will be added to
+ /// different sections(answer, authority, additional).
+ /// \return return true if the message can be found, or else,
+ /// return false.
+ bool lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ isc::dns::Message& response) const;
+
+ /// \brief Look up rrset in cache.
+ ///
+ /// \param qname The query name to look up
+ /// \param qtype The query type to look up
+ ///
+ /// \return return the shared_ptr of rrset if it can be found,
+ /// or else, return NULL. When looking up, local zone
+ /// data will be searched first, if not found, then
+ /// search in rrset cache.
+ ///
+ /// \overload
+ ///
+ isc::dns::RRsetPtr lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype) const;
+
+ /// \brief Update the message in the cache with the new one.
+ ///
+ /// \param msg The message to update
+ ///
+ /// \return return true if the message is updated successfully,
+ /// or else, return false.
+ ///
+ /// \note the function doesn't do any message validation check,
+ /// the user should make sure the message is valid, and of
+ /// the right class
+ /// TODO: Share the NXDOMAIN info between different type queries
+ /// current implementation can only cache for the type that
+ /// user quired, for example, if user query A record of
+ /// a.example. and the server replied with NXDOMAIN, this
+ /// should be cached for all the types queries of a.example.
+ bool update(const isc::dns::Message& msg);
+
+ /// \brief Update the rrset in the cache with the new one.
+ ///
+ /// local zone data and rrset cache will be updated together.
+ /// If the rrset doesn't exist in both of them, then the rrset
+ /// will be added into both of them.
+ ///
+ /// \param rrset_ptr The RRset to update
+ ///
+ /// \return return false, if the class of the parameter rrset is
+ /// allowed to be cached.
+ ///
+ /// \overload
+ ///
+ /// \note The class of the RRset must have been checked. It is not
+ /// here.
+ bool update(const isc::dns::ConstRRsetPtr& rrset_ptr);
+
+ /// \brief Get the RRClass this cache is for
+ ///
+ /// \return The RRClass of this cache
+ const isc::dns::RRClass& getClass() const;
+
+private:
+ /// \brief Update rrset cache.
+ ///
+ /// \param rrset_ptr The rrset to update with
+ /// \param rrset_cache_ptr the rrset cache to update
+ ///
+ /// \return return true if the rrset is updated in the rrset cache,
+ /// or else return false if failed.
+ /// \param rrset_cache_ptr The rrset cache need to be updated.
+ bool updateRRsetCache(const isc::dns::ConstRRsetPtr& rrset_ptr,
+ RRsetCachePtr rrset_cache_ptr);
+
+ /// \brief Class this cache is for.
+ const isc::dns::RRClass cache_class_;
+
+ /// \brief map of message caches for configured classes(each message
+ /// cache is class-specific)
+ MessageCachePtr messages_cache_;
+
+ /// \name rrset caches
+ //@{
+ /// \brief Local Zone data cache
+ /// Cache for rrsets in local zones, rrsets
+ /// in it never expire.
+ LocalZoneDataPtr local_zone_data_;
+ //@}
+
+ /// \brief cache the rrsets parsed from the received message.
+ RRsetCachePtr rrsets_cache_;
+
+ /// \brief cache the SOA rrset parsed from the negative response message.
+ RRsetCachePtr negative_soa_cache_;
+};
+
+class ResolverCache {
+public:
+ /// \brief Default Constructor.
+ ///
+ /// Right now, only support for class "IN", and message cache size is
+ /// MESSAGE_CACHE_DEFAULT_SIZE, rrset cache size is
+ /// RRSET_CACHE_DEFAULT_SIZE
+ ResolverCache();
+
+ /// \brief Construct Function.
+ /// \param caches_size cache size information for each
+ /// messages/rrsets of different classes.
+ ResolverCache(std::vector<CacheSizeInfo> caches_size);
+
+ /// \brief Destructor
+ ~ResolverCache();
+
+ /// \name Lookup Interfaces
+ //@{
+ /// \brief Look up message in cache.
+ ///
+ /// \param qname The query name to look up
+ /// \param qtype The query type to look up
+ /// \param qclass The query class to look up
+ /// \param response the query message (must be in RENDER mode)
+ /// which has question section already (exception
+ /// MessageNoQeustionSection will be thrown if it has
+ /// no question section). If the message can be found
+ /// in cache, rrsets for the message will be added to
+ /// different sections(answer, authority, additional).
+ /// \return return true if the message can be found, or else,
+ /// return false.
+ bool lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ const isc::dns::RRClass& qclass,
+ isc::dns::Message& response) const;
+
+ /// \brief Look up rrset in cache.
+ ///
+ /// \param qname The query name to look up
+ /// \param qtype The query type to look up
+ /// \param qclass The query class to look up
+ ///
+ /// \return return the shared_ptr of rrset if it can be found,
+ /// or else, return NULL. When looking up, local zone
+ /// data will be searched first, if not found, then
+ /// search in rrset cache.
+ ///
+ /// \overload
+ ///
+ isc::dns::RRsetPtr lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ const isc::dns::RRClass& qclass) const;
+
+ /// \brief Look up closest enclosing NS rrset in cache.
+ ///
+ /// \param qname The query name to look up
+ /// \param qclass The query class to look up
+ ///
+ /// \return return the shared_ptr of closest enclosing ns rrset
+ /// if it can be found in cache, or else return NULL.
+ ///
+ /// Currently the implementation is: search exact ns rrset
+ /// label by lable, If the ns rrset can't be found, remove the last
+ /// label, then search again. The efficiency may be very low when
+ /// the name is very long but it's closest rrset's name is very short.
+ ///
+ /// If a good perfermance is needed when looking up the closest
+ /// enclosing ns rrset, cache structure(HashTable) should be
+ /// redesigned. By using HashTable, it can only garantee the
+ /// performance for looking up exact rrset.
+ ///
+ /// So here there is another question, which rrset looking up interface
+ /// is used frequently? Exact or closest enclosing ns looking up.
+ isc::dns::RRsetPtr lookupDeepestNS(const isc::dns::Name& qname,
+ const isc::dns::RRClass& qclass) const;
+ //@}
+
+ /// \brief Update the message in the cache with the new one.
+ ///
+ /// \param msg The message to update
+ ///
+ /// \return return true if the message is updated successfully,
+ /// or else, return false.
+ ///
+ /// \note the function doesn't do any message validation check,
+ /// the user should make sure the message is valid.
+ bool update(const isc::dns::Message& msg);
+
+ /// \brief Update the rrset in the cache with the new one.
+ ///
+ /// local zone data and rrset cache will be updated together.
+ /// If the rrset doesn't exist in both of them, then the rrset
+ /// will be added into both of them.
+ ///
+ /// \param rrset_ptr The RRset to update
+ ///
+ /// \return return false, if the class of the parameter rrset is
+ /// allowed to be cached.
+ ///
+ /// \overload
+ ///
+ bool update(const isc::dns::ConstRRsetPtr& rrset_ptr);
+
+ /// \name Cache Serialization
+ //@{
+ /// \brief Dump the cache content to one file.
+ ///
+ /// \param file_name file to write to
+ ///
+ /// \todo It should can be dumped to one configured database.
+ void dump(const std::string& file_name);
+
+ /// \brief Load the cache from one file.
+ ///
+ /// \param file to load from
+ ///
+ /// \todo It should can be loaded from one configured database.
+ void load(const std::string& file_name);
+ //@}
+
+private:
+ /// \brief Returns the class-specific subcache
+ ///
+ /// \param cache_class the class to get the subcache for
+ /// \return The subcache, or NULL if there is no cache for this class
+ ResolverClassCache* getClassCache(const isc::dns::RRClass& cache_class) const;
+
+ /// The class-specific caches.
+ /// TODO: I think we can optimize for IN, and always have that
+ /// one directly available, use the vector for the rest?
+ std::vector<ResolverClassCache*> class_caches_;
+};
+
+} // namespace cache
+} // namespace isc
+
+#endif // __RESOLVER_CACHE_H
+
diff --git a/src/lib/cache/rrset_cache.cc b/src/lib/cache/rrset_cache.cc
new file mode 100644
index 0000000..f538320
--- /dev/null
+++ b/src/lib/cache/rrset_cache.cc
@@ -0,0 +1,102 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <string>
+#include "rrset_cache.h"
+#include <nsas/nsas_entry_compare.h>
+#include <nsas/hash_table.h>
+#include <nsas/hash_deleter.h>
+
+using namespace isc::nsas;
+using namespace isc::dns;
+using namespace std;
+
+namespace isc {
+namespace cache {
+
+RRsetCache::RRsetCache(uint32_t cache_size,
+ uint16_t rrset_class):
+ class_(rrset_class),
+ rrset_table_(new NsasEntryCompare<RRsetEntry>, cache_size),
+ rrset_lru_((3 * cache_size),
+ new HashDeleter<RRsetEntry>(rrset_table_))
+{
+}
+
+RRsetEntryPtr
+RRsetCache::lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype)
+{
+ const string entry_name = genCacheEntryName(qname, qtype);
+ RRsetEntryPtr entry_ptr = rrset_table_.get(HashKey(entry_name, RRClass(class_)));
+ if (entry_ptr) {
+ if (entry_ptr->getExpireTime() > time(NULL)) {
+ // Only touch the non-expired rrset entries
+ rrset_lru_.touch(entry_ptr);
+ return (entry_ptr);
+ } else {
+ // the rrset entry has expired, so just remove it from
+ // hash table and lru list.
+ rrset_table_.remove(entry_ptr->hashKey());
+ rrset_lru_.remove(entry_ptr);
+ }
+ }
+
+ return (RRsetEntryPtr());
+}
+
+RRsetEntryPtr
+RRsetCache::update(const isc::dns::RRset& rrset, const RRsetTrustLevel& level) {
+ // TODO: If the RRset is an NS, we should update the NSAS as well
+ // lookup first
+ RRsetEntryPtr entry_ptr = lookup(rrset.getName(), rrset.getType());
+ if (entry_ptr) {
+ if (entry_ptr->getTrustLevel() > level) {
+ // existed rrset entry is more authoritative, just return it
+ return (entry_ptr);
+ } else {
+ // Remove the old rrset entry from the lru list.
+ rrset_lru_.remove(entry_ptr);
+ }
+ }
+
+ entry_ptr.reset(new RRsetEntry(rrset, level));
+ rrset_table_.add(entry_ptr, entry_ptr->hashKey(), true);
+ rrset_lru_.add(entry_ptr);
+ return (entry_ptr);
+}
+
+#if 0
+void
+RRsetCache::dump(const std::string&) {
+ //TODO
+}
+
+void
+RRsetCache::load(const std::string&) {
+ //TODO
+}
+
+bool
+RRsetCache::resize(uint32_t) {
+ //TODO
+ return (true);
+}
+#endif
+
+} // namespace cache
+} // namespace isc
+
diff --git a/src/lib/cache/rrset_cache.h b/src/lib/cache/rrset_cache.h
new file mode 100644
index 0000000..5bf2730
--- /dev/null
+++ b/src/lib/cache/rrset_cache.h
@@ -0,0 +1,109 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __RRSET_CACHE_H
+#define __RRSET_CACHE_H
+
+#include <cache/rrset_entry.h>
+#include <nsas/hash_table.h>
+#include <nsas/lru_list.h>
+
+using namespace isc::nsas;
+
+namespace isc {
+namespace cache {
+
+class RRsetEntry;
+
+/// \brief RRset Cache
+/// The object of RRsetCache represented the cache for class-specific
+/// RRsets.
+class RRsetCache{
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are intentionally
+ /// defined as private to make it uncopyable
+ //@{
+private:
+ RRsetCache(const RRsetCache&);
+ RRsetCache& operator=(const RRsetCache&);
+public:
+ /// \brief Constructor
+ ///
+ /// \param cache_size the size of rrset cache.
+ /// \param rrset_class the class of rrset cache.
+ RRsetCache(uint32_t cache_size, uint16_t rrset_class);
+ virtual ~RRsetCache() {}
+ //@}
+
+ /// \brief Look up rrset in cache.
+ ///
+ /// \param qname The query name to look up
+ /// \param qtype The query type
+ /// \return return the shared_ptr of rrset entry if it can be
+ /// found in the cache, or else, return NULL.
+ RRsetEntryPtr lookup(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype);
+
+ /// \brief Update RRset Cache
+ /// Update the rrset entry in the cache with the new one.
+ /// If the rrset has expired or doesn't exist in the cache,
+ /// it will be added directly. It may be ingored if the new
+ /// rrset is not more authoritative than the old rrset in cache.
+ ///
+ /// \param rrset The new rrset used to update cache.
+ /// \param level trustworthiness of the rrset.
+ /// \return return the rrset entry in the cache, it may be the
+ /// new added rrset entry or existed one if it is not replaced.
+ RRsetEntryPtr update(const isc::dns::RRset& rrset,
+ const RRsetTrustLevel& level);
+
+#if 0
+ /// \brief Dump the rrset cache to specified file.
+ ///
+ /// \param file_name The file to write to
+ ///
+ /// \todo It should can be dumped to one configured database.
+ void dump(const std::string& file_name);
+
+ /// \brief Load the cache from one file.
+ ///
+ /// \param file_name The file to read from
+ ///
+ /// \todo It should can be loaded from one configured database.
+ void load(const std::string& file_name);
+
+ /// \brief Resize the size of rrset cache in runtime.
+ ///
+ /// \param The size to resize to
+ /// \return true
+ bool resize(uint32_t size);
+#endif
+
+ /// \short Protected memebers, so they can be accessed by tests.
+protected:
+ uint16_t class_; // The class of the rrset cache.
+ isc::nsas::HashTable<RRsetEntry> rrset_table_;
+ isc::nsas::LruList<RRsetEntry> rrset_lru_;
+};
+
+typedef boost::shared_ptr<RRsetCache> RRsetCachePtr;
+typedef boost::shared_ptr<const RRsetCache> ConstRRsetCachePtr;
+
+} // namespace cache
+} // namespace isc
+
+#endif // __RRSET_CACHE_H
+
diff --git a/src/lib/cache/rrset_copy.cc b/src/lib/cache/rrset_copy.cc
new file mode 100644
index 0000000..05b139a
--- /dev/null
+++ b/src/lib/cache/rrset_copy.cc
@@ -0,0 +1,38 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "rrset_copy.h"
+
+using namespace isc::dns;
+
+namespace isc {
+namespace cache {
+
+void
+rrsetCopy(const isc::dns::RRset& src, isc::dns::RRset& dst) {
+ RdataIteratorPtr rdata_itor = src.getRdataIterator();
+ rdata_itor->first();
+ while(!rdata_itor->isLast()){
+ dst.addRdata(rdata_itor->getCurrent());
+ rdata_itor->next();
+ }
+
+ RRsetPtr rrsig = src.getRRsig();
+ if (rrsig != NULL){
+ dst.addRRsig(rrsig);
+ }
+}
+
+} // namespace cache
+} // namespace isc
diff --git a/src/lib/cache/rrset_copy.h b/src/lib/cache/rrset_copy.h
new file mode 100644
index 0000000..b6af8d6
--- /dev/null
+++ b/src/lib/cache/rrset_copy.h
@@ -0,0 +1,42 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __RRSET_COPY_
+#define __RRSET_COPY_
+
+#include <dns/rrset.h>
+
+namespace isc {
+namespace cache {
+
+/// \brief RRset Copy Function
+///
+/// Adds all Rdatas and the RRsig in the source RRset to the target
+/// RRset
+///
+/// \param src RRset to copy from
+/// \param dst RRset to copy to
+///
+/// \note RRset class doesn't provide the interface for
+/// doing RRset copy. But in cache's code, sometime
+/// we have to do the copy.
+
+void
+rrsetCopy(const isc::dns::RRset& src, isc::dns::RRset& dst);
+
+} // namespace cache
+} // namespace isc
+
+#endif // __RRSET_COPY_
+
diff --git a/src/lib/cache/rrset_entry.cc b/src/lib/cache/rrset_entry.cc
new file mode 100644
index 0000000..c829956
--- /dev/null
+++ b/src/lib/cache/rrset_entry.cc
@@ -0,0 +1,66 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <dns/message.h>
+#include <nsas/nsas_entry.h>
+#include <nsas/fetchable.h>
+#include "rrset_entry.h"
+#include "rrset_copy.h"
+
+using namespace isc::dns;
+
+namespace isc {
+namespace cache {
+
+RRsetEntry::RRsetEntry(const isc::dns::RRset& rrset, const RRsetTrustLevel& level):
+ entry_name_(genCacheEntryName(rrset.getName(), rrset.getType())),
+ expire_time_(time(NULL) + rrset.getTTL().getValue()),
+ trust_level_(level),
+ rrset_(new RRset(rrset.getName(), rrset.getClass(), rrset.getType(), rrset.getTTL())),
+ hash_key_(HashKey(entry_name_, rrset_->getClass()))
+{
+ rrsetCopy(rrset, *(rrset_.get()));
+}
+
+isc::dns::RRsetPtr
+RRsetEntry::getRRset() {
+ updateTTL();
+ return (rrset_);
+}
+
+time_t
+RRsetEntry::getExpireTime() const {
+ return (expire_time_);
+}
+
+void
+RRsetEntry::updateTTL(){
+ uint32_t oldTTL = rrset_->getTTL().getValue();
+ if(oldTTL == 0) {
+ return;
+ }
+
+ uint32_t now = time(NULL);
+ uint32_t newTTL = now < expire_time_ ? (expire_time_ - now) : 0;
+
+ RRTTL ttl(newTTL);
+ rrset_->setTTL(ttl);
+}
+
+} // namespace cache
+} // namespace isc
+
+
diff --git a/src/lib/cache/rrset_entry.h b/src/lib/cache/rrset_entry.h
new file mode 100644
index 0000000..5fa8f2c
--- /dev/null
+++ b/src/lib/cache/rrset_entry.h
@@ -0,0 +1,135 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __RRSET_ENTRY_H
+#define __RRSET_ENTRY_H
+
+#include <dns/rrset.h>
+#include <dns/message.h>
+#include <dns/rrttl.h>
+#include <nsas/nsas_entry.h>
+#include <nsas/fetchable.h>
+#include "cache_entry_key.h"
+
+using namespace isc::nsas;
+
+namespace isc {
+namespace cache {
+
+/// \enum RRset Trustworthiness
+/// For detail of RRset trustworthiness, please refer to
+/// RFC2181 section5.4.1.
+/// Bigger value is more trustworthy.
+enum RRsetTrustLevel {
+ /// Default trust for RRset.
+ RRSET_TRUST_DEFAULT = 0,
+ /// Additional information from non-authoritative answer.
+ RRSET_TRUST_ADDITIONAL_NONAA,
+ /// Data from the authority section of a non-authoritative answer
+ RRSET_TRUST_AUTHORITY_NONAA,
+ /// Additional information from an authoritative answer.
+ RRSET_TRUST_ADDITIONAL_AA,
+ /// Non-authoritative data from the answer section of authoritative
+ /// answers
+ RRSET_TRUST_NONAUTH_ANSWER_AA,
+ /// Data from the answer section of a non-authoritative answer.
+ RRSET_TRUST_ANSWER_NONAA,
+ /// Glue from a primary zone, or glue from a zone transfer.
+ RRSET_TRUST_PRIM_GLUE,
+ /// Data from the authority section of an authoritative answer.
+ RRSET_TRUST_AUTHORITY_AA,
+ /// Authoritative data included in the answer section of
+ /// an authoritative reply.
+ RRSET_TRUST_ANSWER_AA,
+ /// Data from a primary zone file, other than glue data.
+ RRSET_TRUST_PRIM_ZONE_NONGLUE
+};
+
+/// \brief RRset Entry
+/// The object of RRsetEntry represents one cached RRset.
+/// Each RRset entry may be refered using shared_ptr by several message
+/// entries.
+class RRsetEntry : public NsasEntry<RRsetEntry>
+{
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are intentionally
+ /// defined as private to make it uncopyable
+ //@{
+private:
+ RRsetEntry(const RRsetEntry&);
+ RRsetEntry& operator=(const RRsetEntry&);
+public:
+ /// \brief Constructor
+ /// \param rrset The RRset used to initialize the RRset entry.
+ /// \param level trustworthiness of the RRset.
+ RRsetEntry(const isc::dns::RRset& rrset, const RRsetTrustLevel& level);
+
+ /// The destructor.
+ ~RRsetEntry() {}
+ //@}
+
+ /// \brief Return a pointer to a generated RRset
+ ///
+ /// \return Pointer to the generated RRset
+ isc::dns::RRsetPtr getRRset();
+
+ /// \brief Get the expiration time of the RRset.
+ ///
+ /// \return The expiration time of the RRset
+ ///
+ /// \todo RRsig expiration processing
+ time_t getExpireTime() const;
+
+ /// \brief Get the ttl of the RRset.
+ ///
+ /// \return The TTL of the RRset
+ uint32_t getTTL() {
+ updateTTL();
+ return (rrset_->getTTL().getValue());
+ }
+
+ /// \brief Get the hash key
+ ///
+ /// \return return hash key
+ HashKey hashKey() const {
+ return (hash_key_);
+ }
+
+ /// \brief get RRset trustworthiness
+ ///
+ /// \return return the trust level
+ RRsetTrustLevel getTrustLevel() const {
+ return (trust_level_);
+ }
+private:
+ /// \brief Update TTL according to expiration time
+ void updateTTL();
+
+private:
+ std::string entry_name_; // The entry name for this rrset entry.
+ time_t expire_time_; // Expiration time of rrset.
+ RRsetTrustLevel trust_level_; // RRset trustworthiness.
+ boost::shared_ptr<isc::dns::RRset> rrset_;
+ HashKey hash_key_; // RRsetEntry hash key
+};
+
+typedef boost::shared_ptr<RRsetEntry> RRsetEntryPtr;
+
+} // namespace cache
+} // namespace isc
+
+#endif // __RRSET_ENTRY_H
+
diff --git a/src/lib/cache/tests/Makefile.am b/src/lib/cache/tests/Makefile.am
new file mode 100644
index 0000000..4763f55
--- /dev/null
+++ b/src/lib/cache/tests/Makefile.am
@@ -0,0 +1,80 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/cache -I$(top_builddir)/src/lib/cache
+AM_CPPFLAGS += -DTEST_DATA_SRCDIR=\"$(srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/cache/tests/testdata\"
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+
+AM_LDFLAGS = $(PTHREAD_LDFLAGS)
+if USE_STATIC_LINK
+AM_LDFLAGS += -static
+endif
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_CLANGPP
+# see ../Makefile.am
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
+run_unittests_SOURCES += rrset_entry_unittest.cc
+run_unittests_SOURCES += rrset_cache_unittest.cc
+run_unittests_SOURCES += message_cache_unittest.cc
+run_unittests_SOURCES += message_entry_unittest.cc
+run_unittests_SOURCES += local_zone_data_unittest.cc
+run_unittests_SOURCES += resolver_cache_unittest.cc
+run_unittests_SOURCES += negative_cache_unittest.cc
+run_unittests_SOURCES += cache_test_messagefromfile.h
+run_unittests_SOURCES += cache_test_sectioncount.h
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD = $(GTEST_LDADD)
+
+# NOTE: we may have to clean up this hack later (see the note in configure.ac)
+if NEED_LIBBOOST_THREAD
+run_unittests_LDADD += -lboost_thread
+endif
+
+run_unittests_LDADD += $(top_builddir)/src/lib/cache/libcache.la
+run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+endif
+
+noinst_PROGRAMS = $(TESTS)
+
+EXTRA_DIST = testdata/message_cname_referral.wire
+EXTRA_DIST += testdata/message_example_com_soa.wire
+EXTRA_DIST += testdata/message_fromWire1
+EXTRA_DIST += testdata/message_fromWire2
+EXTRA_DIST += testdata/message_fromWire3
+EXTRA_DIST += testdata/message_fromWire4
+EXTRA_DIST += testdata/message_fromWire5
+EXTRA_DIST += testdata/message_fromWire6
+EXTRA_DIST += testdata/message_fromWire7
+EXTRA_DIST += testdata/message_fromWire8
+EXTRA_DIST += testdata/message_fromWire9
+EXTRA_DIST += testdata/message_large_ttl.wire
+EXTRA_DIST += testdata/message_nodata_with_soa.wire
+EXTRA_DIST += testdata/message_nxdomain_cname.wire
+EXTRA_DIST += testdata/message_nxdomain_large_ttl.wire
+EXTRA_DIST += testdata/message_nxdomain_no_soa.wire
+EXTRA_DIST += testdata/message_nxdomain_with_soa.wire
+EXTRA_DIST += testdata/message_referral.wire
diff --git a/src/lib/cache/tests/cache_test_messagefromfile.h b/src/lib/cache/tests/cache_test_messagefromfile.h
new file mode 100644
index 0000000..62e237c
--- /dev/null
+++ b/src/lib/cache/tests/cache_test_messagefromfile.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <vector>
+#include <dns/tests/unittest_util.h>
+#include <dns/buffer.h>
+#include <dns/message.h>
+
+using namespace isc;
+using namespace isc::dns;
+
+namespace {
+
+/// \brief Reads a Message from a data file
+///
+/// \param message Message to put the read data in
+/// \param datafile The file to read from
+void
+messageFromFile(Message& message, const char* datafile) {
+ std::vector<unsigned char> data;
+ UnitTestUtil::readWireData(datafile, data);
+
+ InputBuffer buffer(&data[0], data.size());
+ message.fromWire(buffer);
+}
+
+} // namespace
+
diff --git a/src/lib/cache/tests/cache_test_sectioncount.h b/src/lib/cache/tests/cache_test_sectioncount.h
new file mode 100644
index 0000000..537ca81
--- /dev/null
+++ b/src/lib/cache/tests/cache_test_sectioncount.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <vector>
+#include <dns/tests/unittest_util.h>
+#include <dns/buffer.h>
+#include <dns/message.h>
+
+using namespace isc;
+using namespace isc::dns;
+
+namespace {
+
+/// \brief Counts the number of rrsets in the given section
+///
+/// \param msg The message to count in
+/// \param section The section to count
+///
+/// \return The number of RRsets in the given section
+int
+sectionRRsetCount(Message& msg, Message::Section section) {
+ int count = 0;
+ for (RRsetIterator rrset_iter = msg.beginSection(section);
+ rrset_iter != msg.endSection(section);
+ ++rrset_iter) {
+ ++count;
+ }
+
+ return count;
+}
+
+} // namespace
+
diff --git a/src/lib/cache/tests/local_zone_data_unittest.cc b/src/lib/cache/tests/local_zone_data_unittest.cc
new file mode 100644
index 0000000..6877eae
--- /dev/null
+++ b/src/lib/cache/tests/local_zone_data_unittest.cc
@@ -0,0 +1,64 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <string>
+#include <gtest/gtest.h>
+#include <cache/local_zone_data.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include "cache_test_messagefromfile.h"
+
+using namespace isc::cache;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+
+class LocalZoneDataTest: public testing::Test {
+protected:
+ LocalZoneDataTest(): local_zone_data(1)
+ {
+ }
+
+ LocalZoneData local_zone_data;
+};
+
+TEST_F(LocalZoneDataTest, updateAndLookup) {
+ Message msg(Message::PARSE);
+ messageFromFile(msg, "message_fromWire3");
+ RRsetIterator rrset_iter = msg.beginSection(Message::SECTION_AUTHORITY);
+ Name name = (*rrset_iter)->getName();
+ RRType type = (*rrset_iter)->getType();
+
+ EXPECT_FALSE(local_zone_data.lookup(name, type));
+ local_zone_data.update((*(*rrset_iter).get()));
+ EXPECT_TRUE(local_zone_data.lookup(name, type));
+
+ // Test whether the old one is replaced
+ uint32_t ttl = (*rrset_iter)->getTTL().getValue();
+ // Make sure it is not zero
+ ASSERT_NE(ttl / 2, ttl);
+
+ RRsetPtr rrset_ptr = local_zone_data.lookup(name, type);
+ EXPECT_EQ(ttl, rrset_ptr->getTTL().getValue());
+
+ (*rrset_iter)->setTTL(RRTTL(ttl/2));
+
+ local_zone_data.update((*(*rrset_iter).get()));
+ rrset_ptr = local_zone_data.lookup(name, type);
+ EXPECT_EQ(ttl/2, rrset_ptr->getTTL().getValue());
+}
+
+}
diff --git a/src/lib/cache/tests/message_cache_unittest.cc b/src/lib/cache/tests/message_cache_unittest.cc
new file mode 100644
index 0000000..fc62e21
--- /dev/null
+++ b/src/lib/cache/tests/message_cache_unittest.cc
@@ -0,0 +1,162 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <string>
+#include <gtest/gtest.h>
+#include <dns/tests/unittest_util.h>
+#include <dns/buffer.h>
+#include "../message_cache.h"
+#include "../rrset_cache.h"
+#include "../resolver_cache.h"
+#include "cache_test_messagefromfile.h"
+
+using namespace isc::cache;
+using namespace isc;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+
+/// \brief Derived from base class to make it easy to test
+/// its internals.
+class DerivedMessageCache: public MessageCache {
+public:
+ DerivedMessageCache(const RRsetCachePtr& rrset_cache,
+ uint32_t cache_size, uint16_t message_class,
+ const RRsetCachePtr& negative_soa_cache):
+ MessageCache(rrset_cache, cache_size, message_class, negative_soa_cache)
+ {}
+
+ uint16_t messages_count() {
+ return message_lru_.size();
+ }
+};
+
+/// \brief Derived from base class to make it easy to test
+/// its internals.
+class DerivedRRsetCache: public RRsetCache {
+public:
+ DerivedRRsetCache(uint32_t cache_size, uint16_t rrset_class):
+ RRsetCache(cache_size, rrset_class)
+ {}
+
+ /// \brief Remove one rrset entry from rrset cache.
+ void removeRRsetEntry(Name& name, const RRType& type) {
+ const string entry_name = genCacheEntryName(name, type);
+ HashKey entry_key = HashKey(entry_name, RRClass(class_));
+ RRsetEntryPtr rrset_entry = rrset_table_.get(entry_key);
+ if (rrset_entry) {
+ rrset_lru_.remove(rrset_entry);
+ rrset_table_.remove(entry_key);
+ }
+ }
+};
+
+class MessageCacheTest: public testing::Test {
+public:
+ MessageCacheTest(): message_parse(Message::PARSE),
+ message_render(Message::RENDER)
+ {
+ uint16_t class_ = RRClass::IN().getCode();
+ rrset_cache_.reset(new DerivedRRsetCache(RRSET_CACHE_DEFAULT_SIZE, class_));
+ negative_soa_cache_.reset(new RRsetCache(NEGATIVE_RRSET_CACHE_DEFAULT_SIZE, class_));
+ // Set the message cache size to 1, make it easy for unittest.
+ message_cache_.reset(new DerivedMessageCache(rrset_cache_, 1, class_,
+ negative_soa_cache_));
+ }
+
+protected:
+ boost::shared_ptr<DerivedMessageCache> message_cache_;
+ boost::shared_ptr<DerivedRRsetCache> rrset_cache_;
+ RRsetCachePtr negative_soa_cache_;
+ Message message_parse;
+ Message message_render;
+};
+
+void
+updateMessageCache(const char* message_file,
+ boost::shared_ptr<DerivedMessageCache> cache)
+{
+ Message msg(Message::PARSE);
+ messageFromFile(msg, message_file);
+ cache->update(msg);
+}
+
+TEST_F(MessageCacheTest, testLookup) {
+ messageFromFile(message_parse, "message_fromWire1");
+ EXPECT_TRUE(message_cache_->update(message_parse));
+
+ Name qname("test.example.com.");
+ EXPECT_TRUE(message_cache_->lookup(qname, RRType::A(), message_render));
+ EXPECT_EQ(message_cache_->messages_count(), 1);
+
+ Message message_net(Message::PARSE);
+ messageFromFile(message_net, "message_fromWire2");
+ EXPECT_TRUE(message_cache_->update(message_net));
+ EXPECT_EQ(message_cache_->messages_count(), 2);
+
+ Name qname1("test.example.net.");
+ EXPECT_TRUE(message_cache_->lookup(qname1, RRType::A(), message_render));
+
+ // Test looking up message which has expired rrset or some rrset
+ // has been removed from the rrset cache.
+ rrset_cache_->removeRRsetEntry(qname1, RRType::A());
+ EXPECT_FALSE(message_cache_->lookup(qname1, RRType::A(), message_render));
+
+ // Update one message entry which has expired to message cache.
+ updateMessageCache("message_fromWire9", message_cache_);
+ EXPECT_EQ(message_cache_->messages_count(), 3);
+ // The message entry has been added, but can't be looked up, since
+ // it has expired and is removed automatically when being looked up.
+ Name qname_org("test.example.org.");
+ EXPECT_FALSE(message_cache_->lookup(qname_org, RRType::A(), message_render));
+ EXPECT_EQ(message_cache_->messages_count(), 2);
+}
+
+TEST_F(MessageCacheTest, testUpdate) {
+ messageFromFile(message_parse, "message_fromWire4");
+ EXPECT_TRUE(message_cache_->update(message_parse));
+
+ Name qname("example.com.");
+ EXPECT_TRUE(message_cache_->lookup(qname, RRType::SOA(), message_render));
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_AA));
+
+ Message new_msg(Message::PARSE);
+ messageFromFile(new_msg, "message_fromWire3");
+ EXPECT_TRUE(message_cache_->update(new_msg));
+ Message new_msg_render(Message::RENDER);
+ EXPECT_TRUE(message_cache_->lookup(qname, RRType::SOA(), new_msg_render));
+ EXPECT_FALSE(new_msg_render.getHeaderFlag(Message::HEADERFLAG_AA));
+}
+
+TEST_F(MessageCacheTest, testCacheLruBehavior) {
+ // qname = "test.example.com.", qtype = A
+ updateMessageCache("message_fromWire1", message_cache_);
+ // qname = "test.example.net.", qtype = A
+ updateMessageCache("message_fromWire2", message_cache_);
+ // qname = "example.com.", qtype = SOA
+ updateMessageCache("message_fromWire4", message_cache_);
+
+ Name qname_net("test.example.net.");
+ EXPECT_TRUE(message_cache_->lookup(qname_net, RRType::A(), message_render));
+
+ // qname = "a.example.com.", qtype = A
+ updateMessageCache("message_fromWire5", message_cache_);
+ Name qname_com("test.example.com.");
+ EXPECT_FALSE(message_cache_->lookup(qname_com, RRType::A(), message_render));
+}
+
+} // namespace
+
diff --git a/src/lib/cache/tests/message_entry_unittest.cc b/src/lib/cache/tests/message_entry_unittest.cc
new file mode 100644
index 0000000..2ca33ec
--- /dev/null
+++ b/src/lib/cache/tests/message_entry_unittest.cc
@@ -0,0 +1,309 @@
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <string>
+#include <gtest/gtest.h>
+#include <dns/tests/unittest_util.h>
+#include <dns/message.h>
+#include <dns/buffer.h>
+#include "../message_entry.h"
+#include "../rrset_cache.h"
+#include "../resolver_cache.h"
+#include "cache_test_messagefromfile.h"
+#include "cache_test_sectioncount.h"
+
+using namespace isc::cache;
+using namespace isc;
+using namespace isc::dns;
+using namespace std;
+
+static uint32_t MAX_UINT32 = numeric_limits<uint32_t>::max();
+
+namespace {
+
+/// \brief Derived from base class to make it easy to test
+/// its internals.
+class DerivedMessageEntry: public MessageEntry {
+public:
+ DerivedMessageEntry(const isc::dns::Message& message,
+ const RRsetCachePtr& rrset_cache_,
+ const RRsetCachePtr& negative_soa_cache_):
+ MessageEntry(message, rrset_cache_, negative_soa_cache_)
+ {}
+
+ /// \brief Wrap the protected function so that it can be tested.
+ void parseSectionForTest(const Message& msg,
+ const Message::Section& section,
+ uint32_t& smaller_ttl,
+ uint16_t& rrset_count)
+ {
+ parseSection(msg, section, smaller_ttl, rrset_count);
+ }
+
+ RRsetTrustLevel getRRsetTrustLevelForTest(const Message& message,
+ const RRsetPtr rrset,
+ const Message::Section& section)
+ {
+ return getRRsetTrustLevel(message, rrset, section);
+ }
+
+ bool getRRsetEntriesForTest(vector<RRsetEntryPtr> vec, time_t now) {
+ return getRRsetEntries(vec, now);
+ }
+
+ time_t getExpireTime() {
+ return expire_time_;
+ }
+
+};
+
+class MessageEntryTest: public testing::Test {
+public:
+ MessageEntryTest(): class_(1),
+ message_parse(Message::PARSE),
+ message_render(Message::RENDER)
+ {
+ rrset_cache_.reset(new RRsetCache(RRSET_CACHE_DEFAULT_SIZE, class_));
+ negative_soa_cache_.reset(new RRsetCache(NEGATIVE_RRSET_CACHE_DEFAULT_SIZE, class_));
+ }
+
+protected:
+ uint16_t class_;
+ RRsetCachePtr rrset_cache_;
+ RRsetCachePtr negative_soa_cache_;
+ Message message_parse;
+ Message message_render;
+};
+
+TEST_F(MessageEntryTest, testParseRRset) {
+ messageFromFile(message_parse, "message_fromWire3");
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
+ uint32_t ttl = MAX_UINT32;
+ uint16_t rrset_count = 0;
+ message_entry.parseSectionForTest(message_parse, Message::SECTION_ANSWER, ttl, rrset_count);
+ EXPECT_EQ(ttl, 21600);
+ EXPECT_EQ(rrset_count, 1);
+
+ ttl = MAX_UINT32;
+ message_entry.parseSectionForTest(message_parse, Message::SECTION_AUTHORITY, ttl, rrset_count);
+ EXPECT_EQ(ttl, 21600);
+ EXPECT_EQ(rrset_count, 1);
+
+ ttl = MAX_UINT32;
+ message_entry.parseSectionForTest(message_parse, Message::SECTION_ADDITIONAL, ttl, rrset_count);
+ EXPECT_EQ(ttl, 10800);
+ EXPECT_EQ(rrset_count, 5);
+}
+
+TEST_F(MessageEntryTest, testGetRRsetTrustLevel_AA) {
+ messageFromFile(message_parse, "message_fromWire3");
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
+
+ RRsetIterator rrset_iter = message_parse.beginSection(Message::SECTION_ANSWER);
+ RRsetTrustLevel level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA);
+
+ rrset_iter = message_parse.beginSection(Message::SECTION_AUTHORITY);
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_AUTHORITY);
+ EXPECT_EQ(level, RRSET_TRUST_AUTHORITY_AA);
+
+ rrset_iter = message_parse.beginSection(Message::SECTION_ADDITIONAL);
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ADDITIONAL);
+ EXPECT_EQ(level, RRSET_TRUST_ADDITIONAL_AA);
+}
+
+TEST_F(MessageEntryTest, testGetRRsetTrustLevel_NONAA) {
+ messageFromFile(message_parse, "message_fromWire4");
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
+ RRsetIterator rrset_iter = message_parse.beginSection(Message::SECTION_ANSWER);
+ RRsetTrustLevel level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_NONAA);
+
+ rrset_iter = message_parse.beginSection(Message::SECTION_AUTHORITY);
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_AUTHORITY);
+ EXPECT_EQ(level, RRSET_TRUST_AUTHORITY_NONAA);
+
+ rrset_iter = message_parse.beginSection(Message::SECTION_ADDITIONAL);
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ADDITIONAL);
+ EXPECT_EQ(level, RRSET_TRUST_ADDITIONAL_NONAA);
+}
+
+TEST_F(MessageEntryTest, testGetRRsetTrustLevel_CNAME) {
+ messageFromFile(message_parse, "message_fromWire5");
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
+ RRsetIterator rrset_iter = message_parse.beginSection(Message::SECTION_ANSWER);
+ RRsetTrustLevel level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA);
+
+ ++rrset_iter; // Get the rrset after the first cname rrset.
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_NONAA);
+}
+
+TEST_F(MessageEntryTest, testGetRRsetTrustLevel_CNAME_and_DNAME) {
+ messageFromFile(message_parse, "message_fromWire7");
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
+ RRsetIterator rrset_iter = message_parse.beginSection(Message::SECTION_ANSWER);
+ RRsetTrustLevel level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA);
+ // All the left rrset are non-authoritative
+ ++rrset_iter;
+ while (rrset_iter != message_parse.endSection(Message::SECTION_ANSWER)) {
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ ++rrset_iter;
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_NONAA);
+ }
+}
+
+TEST_F(MessageEntryTest, testGetRRsetTrustLevel_DNAME_and_CNAME) {
+ messageFromFile(message_parse, "message_fromWire8");
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
+ RRsetIterator rrset_iter = message_parse.beginSection(Message::SECTION_ANSWER);
+ RRsetTrustLevel level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ // Test the deepest DNAME
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA);
+ ++rrset_iter;
+ // Test the synchronized CNAME
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ ++rrset_iter;
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA);
+
+ ++rrset_iter;
+ // All the left rrset are non-authoritative
+ while (rrset_iter != message_parse.endSection(Message::SECTION_ANSWER)) {
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ ++rrset_iter;
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_NONAA);
+ }
+}
+
+TEST_F(MessageEntryTest, testGetRRsetTrustLevel_DNAME) {
+ messageFromFile(message_parse, "message_fromWire6");
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
+ RRsetIterator rrset_iter = message_parse.beginSection(Message::SECTION_ANSWER);
+ RRsetTrustLevel level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA);
+
+ ++rrset_iter; // Get the rrset after the first dname rrset.
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA);
+
+ ++rrset_iter; // Get the second cname rrset
+ level = message_entry.getRRsetTrustLevelForTest(message_parse,
+ *rrset_iter,
+ Message::SECTION_ANSWER);
+ EXPECT_EQ(level, RRSET_TRUST_ANSWER_NONAA);
+}
+
+// We only test the expire_time of the message entry.
+// The test for genMessage() will make sure whether InitMessageEntry()
+// is right
+TEST_F(MessageEntryTest, testInitMessageEntry) {
+ messageFromFile(message_parse, "message_fromWire3");
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
+ time_t expire_time = message_entry.getExpireTime();
+ // 1 second should be enough to do the compare
+ EXPECT_TRUE((time(NULL) + 10801) > expire_time);
+}
+
+TEST_F(MessageEntryTest, testGetRRsetEntries) {
+ messageFromFile(message_parse, "message_fromWire3");
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
+ vector<RRsetEntryPtr> vec;
+
+ // the time is bigger than the smallest expire time of
+ // the rrset in message.
+ time_t expire_time = time(NULL) + 10802;
+ EXPECT_FALSE(message_entry.getRRsetEntriesForTest(vec, expire_time));
+}
+
+TEST_F(MessageEntryTest, testGenMessage) {
+ messageFromFile(message_parse, "message_fromWire3");
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
+ time_t expire_time = message_entry.getExpireTime();
+
+ Message msg(Message::RENDER);
+ EXPECT_FALSE(message_entry.genMessage(expire_time + 2, msg));
+ message_entry.genMessage(time(NULL), msg);
+ // Check whether the generated message is same with cached one.
+ EXPECT_FALSE(msg.getHeaderFlag(Message::HEADERFLAG_AA));
+ EXPECT_FALSE(msg.getHeaderFlag(Message::HEADERFLAG_TC));
+ EXPECT_EQ(1, sectionRRsetCount(msg, Message::SECTION_ANSWER));
+ EXPECT_EQ(1, sectionRRsetCount(msg, Message::SECTION_AUTHORITY));
+ EXPECT_EQ(5, sectionRRsetCount(msg, Message::SECTION_ADDITIONAL));
+
+ // Check the rrset in answer section.
+ EXPECT_EQ(1, msg.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(5, msg.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(7, msg.getRRCount(Message::SECTION_ADDITIONAL));
+}
+
+TEST_F(MessageEntryTest, testMaxTTL) {
+ messageFromFile(message_parse, "message_large_ttl.wire");
+
+ // The ttl of rrset from Answer and Authority sections are both 604801 seconds
+ RRsetIterator iter = message_parse.beginSection(Message::SECTION_ANSWER);
+ EXPECT_EQ(604801, (*iter)->getTTL().getValue());
+ iter = message_parse.beginSection(Message::SECTION_AUTHORITY);
+ EXPECT_EQ(604801, (*iter)->getTTL().getValue());
+
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
+
+ // The ttl is limited to 604800 seconds (7days)
+ EXPECT_EQ(time(NULL) + 604800, message_entry.getExpireTime());
+}
+
+TEST_F(MessageEntryTest, testMaxNegativeTTL) {
+ messageFromFile(message_parse, "message_nxdomain_large_ttl.wire");
+
+ // The ttl of rrset Authority sections are 10801 seconds
+ RRsetIterator iter = message_parse.beginSection(Message::SECTION_AUTHORITY);
+ EXPECT_EQ(10801, (*iter)->getTTL().getValue());
+
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
+
+ // The ttl is limited to 10800 seconds (3 hours)
+ EXPECT_EQ(time(NULL) + 10800, message_entry.getExpireTime());
+}
+
+} // namespace
diff --git a/src/lib/cache/tests/negative_cache_unittest.cc b/src/lib/cache/tests/negative_cache_unittest.cc
new file mode 100644
index 0000000..56d777d
--- /dev/null
+++ b/src/lib/cache/tests/negative_cache_unittest.cc
@@ -0,0 +1,242 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+#include <config.h>
+#include <string>
+#include <gtest/gtest.h>
+#include <dns/rrset.h>
+#include <dns/rcode.h>
+#include "resolver_cache.h"
+#include "cache_test_messagefromfile.h"
+
+using namespace isc::cache;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+
+class NegativeCacheTest: public testing::Test{
+public:
+ NegativeCacheTest() {
+ vector<CacheSizeInfo> vec;
+ CacheSizeInfo class_in(RRClass::IN(), 100, 200);
+ vec.push_back(class_in);
+ cache = new ResolverCache(vec);
+ }
+
+ ~NegativeCacheTest() {
+ delete cache;
+ }
+
+ ResolverCache *cache;
+};
+
+TEST_F(NegativeCacheTest, testNXDOMAIN){
+ // NXDOMAIN response for nonexist.example.com
+ Message msg_nxdomain(Message::PARSE);
+ messageFromFile(msg_nxdomain, "message_nxdomain_with_soa.wire");
+ cache->update(msg_nxdomain);
+
+ msg_nxdomain.makeResponse();
+
+ Name non_exist_qname("nonexist.example.com.");
+ EXPECT_TRUE(cache->lookup(non_exist_qname, RRType::A(), RRClass::IN(), msg_nxdomain));
+
+ RRsetIterator iter = msg_nxdomain.beginSection(Message::SECTION_AUTHORITY);
+ RRsetPtr rrset_ptr = *iter;
+
+ // The TTL should equal to the TTL of SOA record
+ const RRTTL& nxdomain_ttl1 = rrset_ptr->getTTL();
+ EXPECT_EQ(nxdomain_ttl1.getValue(), 86400);
+
+ // SOA response for example.com
+ Message msg_example_com_soa(Message::PARSE);
+ messageFromFile(msg_example_com_soa, "message_example_com_soa.wire");
+ cache->update(msg_example_com_soa);
+
+ msg_example_com_soa.makeResponse();
+ Name soa_qname("example.com.");
+ EXPECT_TRUE(cache->lookup(soa_qname, RRType::SOA(), RRClass::IN(), msg_example_com_soa));
+
+ iter = msg_example_com_soa.beginSection(Message::SECTION_ANSWER);
+ rrset_ptr = *iter;
+
+ // The TTL should equal to the TTL of SOA record in answer section
+ const RRTTL& soa_ttl = rrset_ptr->getTTL();
+ EXPECT_EQ(soa_ttl.getValue(), 172800);
+
+ sleep(1);
+
+ // Query nonexist.example.com again
+ Message msg_nxdomain2(Message::PARSE);
+ messageFromFile(msg_nxdomain2, "message_nxdomain_with_soa.wire");
+ msg_nxdomain2.makeResponse();
+
+ EXPECT_TRUE(cache->lookup(non_exist_qname, RRType::A(), RRClass::IN(), msg_nxdomain2));
+ iter = msg_nxdomain2.beginSection(Message::SECTION_AUTHORITY);
+ rrset_ptr = *iter;
+
+ // The TTL should equal to the TTL of negative response SOA record
+ const RRTTL& nxdomain_ttl2 = rrset_ptr->getTTL();
+ EXPECT_TRUE(86398 <= nxdomain_ttl2.getValue() && nxdomain_ttl2.getValue() <= 86399);
+ // No RRset in ANSWER section
+ EXPECT_TRUE(msg_nxdomain2.getRRCount(Message::SECTION_ANSWER) == 0);
+ // Check that only one SOA record exist in AUTHORITY section
+ EXPECT_TRUE(msg_nxdomain2.getRRCount(Message::SECTION_AUTHORITY) == 1);
+ iter = msg_nxdomain2.beginSection(Message::SECTION_AUTHORITY);
+ rrset_ptr = *iter;
+ EXPECT_TRUE(rrset_ptr->getType() == RRType::SOA());
+
+ // Check the normal SOA cache again
+ Message msg_example_com_soa2(Message::PARSE);
+ messageFromFile(msg_example_com_soa2, "message_example_com_soa.wire");
+ msg_example_com_soa2.makeResponse();
+ EXPECT_TRUE(cache->lookup(soa_qname, RRType::SOA(), RRClass::IN(), msg_example_com_soa2));
+
+ iter = msg_example_com_soa2.beginSection(Message::SECTION_ANSWER);
+ rrset_ptr = *iter;
+ const RRTTL& soa_ttl2 = rrset_ptr->getTTL();
+ // The TTL should equal to the TTL of SOA record in answer section
+ EXPECT_TRUE(172798 <= soa_ttl2.getValue() && soa_ttl2.getValue() <= 172799);
+}
+
+TEST_F(NegativeCacheTest, testNXDOMAINWithoutSOA){
+ // NXDOMAIN response for nonexist.example.com
+ Message msg_nxdomain(Message::PARSE);
+ messageFromFile(msg_nxdomain, "message_nxdomain_no_soa.wire");
+ cache->update(msg_nxdomain);
+
+ msg_nxdomain.makeResponse();
+
+ Name non_exist_qname("nonexist.example.com.");
+ // The message should not be cached
+ EXPECT_FALSE(cache->lookup(non_exist_qname, RRType::A(), RRClass::IN(), msg_nxdomain));
+}
+
+TEST_F(NegativeCacheTest, testNXDOMAINCname){
+ // a.example.org points to b.example.org
+ // b.example.org points to c.example.org
+ // c.example.org does not exist
+ Message msg_nxdomain_cname(Message::PARSE);
+ messageFromFile(msg_nxdomain_cname, "message_nxdomain_cname.wire");
+ cache->update(msg_nxdomain_cname);
+
+ msg_nxdomain_cname.makeResponse();
+
+ Name a_example_org("a.example.org.");
+ // The message should be cached
+ EXPECT_TRUE(cache->lookup(a_example_org, RRType::A(), RRClass::IN(), msg_nxdomain_cname));
+
+ EXPECT_EQ(msg_nxdomain_cname.getRcode().getCode(), Rcode::NXDOMAIN().getCode());
+
+ // It should include 2 CNAME records in Answer section
+ EXPECT_TRUE(msg_nxdomain_cname.getRRCount(Message::SECTION_ANSWER) == 2);
+ RRsetIterator iter = msg_nxdomain_cname.beginSection(Message::SECTION_ANSWER);
+ EXPECT_TRUE((*iter)->getType() == RRType::CNAME());
+ ++iter;
+ EXPECT_TRUE((*iter)->getType() == RRType::CNAME());
+
+ // It should include 1 SOA record in Authority section
+ EXPECT_TRUE(msg_nxdomain_cname.getRRCount(Message::SECTION_AUTHORITY) == 1);
+ iter = msg_nxdomain_cname.beginSection(Message::SECTION_AUTHORITY);
+ EXPECT_TRUE((*iter)->getType() == RRType::SOA());
+
+ const RRTTL& soa_ttl = (*iter)->getTTL();
+ EXPECT_EQ(soa_ttl.getValue(), 600);
+}
+
+TEST_F(NegativeCacheTest, testNoerrorNodata){
+ // NODATA/NOERROR response for MX type query of example.com
+ Message msg_nodata(Message::PARSE);
+ messageFromFile(msg_nodata, "message_nodata_with_soa.wire");
+ cache->update(msg_nodata);
+
+ msg_nodata.makeResponse();
+
+ Name example_dot_com("example.com.");
+ EXPECT_TRUE(cache->lookup(example_dot_com, RRType::MX(), RRClass::IN(), msg_nodata));
+
+ RRsetIterator iter = msg_nodata.beginSection(Message::SECTION_AUTHORITY);
+ RRsetPtr rrset_ptr = *iter;
+
+ // The TTL should equal to the TTL of SOA record
+ const RRTTL& nodata_ttl1 = rrset_ptr->getTTL();
+ EXPECT_EQ(nodata_ttl1.getValue(), 86400);
+
+
+ // Normal SOA response for example.com
+ Message msg_example_com_soa(Message::PARSE);
+ messageFromFile(msg_example_com_soa, "message_example_com_soa.wire");
+ cache->update(msg_example_com_soa);
+
+ msg_example_com_soa.makeResponse();
+ Name soa_qname("example.com.");
+ EXPECT_TRUE(cache->lookup(soa_qname, RRType::SOA(), RRClass::IN(), msg_example_com_soa));
+
+ iter = msg_example_com_soa.beginSection(Message::SECTION_ANSWER);
+ rrset_ptr = *iter;
+
+ // The TTL should equal to the TTL of SOA record in answer section
+ const RRTTL& soa_ttl = rrset_ptr->getTTL();
+ EXPECT_EQ(soa_ttl.getValue(), 172800);
+
+ // Query MX record of example.com again
+ Message msg_nodata2(Message::PARSE);
+ messageFromFile(msg_nodata2, "message_nodata_with_soa.wire");
+ msg_nodata2.makeResponse();
+
+ sleep(1);
+
+ EXPECT_TRUE(cache->lookup(example_dot_com, RRType::MX(), RRClass::IN(), msg_nodata2));
+
+ // No answer
+ EXPECT_EQ(msg_nodata2.getRRCount(Message::SECTION_ANSWER), 0);
+ // One SOA record in authority section
+ EXPECT_EQ(msg_nodata2.getRRCount(Message::SECTION_AUTHORITY), 1);
+
+ iter = msg_nodata2.beginSection(Message::SECTION_AUTHORITY);
+ rrset_ptr = *iter;
+
+ // The TTL should equal to the TTL of negative response SOA record and counted down
+ const RRTTL& nodata_ttl2 = rrset_ptr->getTTL();
+ EXPECT_TRUE(86398 <= nodata_ttl2.getValue() && nodata_ttl2.getValue() <= 86399);
+}
+
+TEST_F(NegativeCacheTest, testReferralResponse){
+ // CNAME exist, but it points to out of zone data, so the server give some reference data
+ Message msg_cname_referral(Message::PARSE);
+ messageFromFile(msg_cname_referral, "message_cname_referral.wire");
+ cache->update(msg_cname_referral);
+
+ msg_cname_referral.makeResponse();
+
+ Name x_example_org("x.example.org.");
+ EXPECT_TRUE(cache->lookup(x_example_org, RRType::A(), RRClass::IN(), msg_cname_referral));
+
+ // The Rcode should be NOERROR
+ EXPECT_EQ(msg_cname_referral.getRcode().getCode(), Rcode::NOERROR().getCode());
+
+ // One CNAME record in Answer section
+ EXPECT_EQ(msg_cname_referral.getRRCount(Message::SECTION_ANSWER), 1);
+ RRsetIterator iter = msg_cname_referral.beginSection(Message::SECTION_ANSWER);
+ EXPECT_EQ((*iter)->getType(), RRType::CNAME());
+
+ // 13 NS records in Authority section
+ EXPECT_EQ(msg_cname_referral.getRRCount(Message::SECTION_AUTHORITY), 13);
+ iter = msg_cname_referral.beginSection(Message::SECTION_AUTHORITY);
+ EXPECT_EQ((*iter)->getType(), RRType::NS());
+}
+
+}
diff --git a/src/lib/cache/tests/resolver_cache_unittest.cc b/src/lib/cache/tests/resolver_cache_unittest.cc
new file mode 100644
index 0000000..7b686f5
--- /dev/null
+++ b/src/lib/cache/tests/resolver_cache_unittest.cc
@@ -0,0 +1,128 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <string>
+#include <gtest/gtest.h>
+#include <dns/rrset.h>
+#include "resolver_cache.h"
+#include "cache_test_messagefromfile.h"
+#include "cache_test_sectioncount.h"
+
+using namespace isc::cache;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+
+class ResolverCacheTest: public testing::Test {
+public:
+ ResolverCacheTest() {
+ vector<CacheSizeInfo> vec;
+ CacheSizeInfo class_in(RRClass::IN(), 100, 200);
+ CacheSizeInfo class_ch(RRClass::CH(), 100, 200);
+ vec.push_back(class_in);
+ vec.push_back(class_ch);
+ cache = new ResolverCache(vec);
+ }
+
+ ~ResolverCacheTest() {
+ delete cache;
+ }
+
+ ResolverCache* cache;
+};
+
+TEST_F(ResolverCacheTest, testUpdateMessage) {
+ Message msg(Message::PARSE);
+ messageFromFile(msg, "message_fromWire3");
+ cache->update(msg);
+
+ Name qname("example.com.");
+
+ msg.makeResponse();
+ EXPECT_TRUE(cache->lookup(qname, RRType::SOA(), RRClass::IN(), msg));
+ EXPECT_FALSE(msg.getHeaderFlag(Message::HEADERFLAG_AA));
+
+ // Test whether the old message can be updated
+ Message new_msg(Message::PARSE);
+ messageFromFile(new_msg, "message_fromWire4");
+ cache->update(new_msg);
+
+ new_msg.makeResponse();
+ EXPECT_TRUE(cache->lookup(qname, RRType::SOA(), RRClass::IN(), new_msg));
+ EXPECT_FALSE(new_msg.getHeaderFlag(Message::HEADERFLAG_AA));
+}
+
+TEST_F(ResolverCacheTest, testUpdateRRset) {
+ Message msg(Message::PARSE);
+ messageFromFile(msg, "message_fromWire3");
+ cache->update(msg);
+
+ Name qname("example.com.");
+
+ msg.makeResponse();
+ EXPECT_TRUE(cache->lookup(qname, RRType::SOA(), RRClass::IN(), msg));
+
+ Message except_msg(Message::RENDER);
+ EXPECT_THROW(cache->lookup(qname, RRType::SOA(), RRClass::IN(), except_msg),
+ MessageNoQuestionSection);
+
+ // Get one rrset in the message, then use it to
+ // update rrset cache-> Test whether the local zone
+ // data is updated.
+ RRsetIterator iter = msg.beginSection(Message::SECTION_AUTHORITY);
+ RRsetPtr rrset_ptr = *iter;
+ cache->update(rrset_ptr);
+
+ Message new_msg(Message::RENDER);
+ Question question(qname, RRClass::IN(), RRType::NS());
+ new_msg.addQuestion(question);
+ EXPECT_TRUE(cache->lookup(qname, RRType::NS(), RRClass::IN(), new_msg));
+ EXPECT_EQ(0, sectionRRsetCount(new_msg, Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, sectionRRsetCount(new_msg, Message::SECTION_ADDITIONAL));
+}
+
+TEST_F(ResolverCacheTest, testLookupUnsupportedClass) {
+ Message msg(Message::PARSE);
+ messageFromFile(msg, "message_fromWire3");
+ cache->update(msg);
+
+ Name qname("example.com.");
+
+ msg.makeResponse();
+ EXPECT_FALSE(cache->lookup(qname, RRType::SOA(), RRClass::CH(), msg));
+ EXPECT_FALSE(cache->lookup(qname, RRType::SOA(), RRClass::CH()));
+}
+
+TEST_F(ResolverCacheTest, testLookupClosestRRset) {
+ Message msg(Message::PARSE);
+ messageFromFile(msg, "message_fromWire3");
+ cache->update(msg);
+
+ Name qname("www.test.example.com.");
+
+ RRsetPtr rrset_ptr = cache->lookupDeepestNS(qname, RRClass::IN());
+ EXPECT_TRUE(rrset_ptr);
+ EXPECT_EQ(rrset_ptr->getName(), Name("example.com."));
+
+ rrset_ptr = cache->lookupDeepestNS(Name("example.com."), RRClass::IN());
+ EXPECT_TRUE(rrset_ptr);
+ EXPECT_EQ(rrset_ptr->getName(), Name("example.com."));
+
+ rrset_ptr = cache->lookupDeepestNS(Name("com."), RRClass::IN());
+ EXPECT_FALSE(rrset_ptr);
+}
+
+}
diff --git a/src/lib/cache/tests/rrset_cache_unittest.cc b/src/lib/cache/tests/rrset_cache_unittest.cc
new file mode 100644
index 0000000..b61f5c4
--- /dev/null
+++ b/src/lib/cache/tests/rrset_cache_unittest.cc
@@ -0,0 +1,127 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <string>
+#include <gtest/gtest.h>
+#include <cache/resolver_cache.h>
+#include <cache/cache_entry_key.h>
+#include <cache/rrset_entry.h>
+#include <cache/rrset_cache.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rrset.h>
+
+using namespace isc::cache;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+
+class RRsetCacheTest : public testing::Test {
+protected:
+ RRsetCacheTest():
+ cache_(1, RRClass::IN().getCode()),
+ name_("example.com"),
+ rrset1_(name_, RRClass::IN(), RRType::A(), RRTTL(20)),
+ rrset2_(name_, RRClass::IN(), RRType::A(), RRTTL(10)),
+ rrset_entry1_(rrset1_, RRSET_TRUST_ADDITIONAL_AA),
+ rrset_entry2_(rrset2_, RRSET_TRUST_PRIM_ZONE_NONGLUE)
+ {
+ }
+
+ RRsetCache cache_;
+ Name name_;
+ RRset rrset1_;
+ RRset rrset2_;
+ RRsetEntry rrset_entry1_;
+ RRsetEntry rrset_entry2_;
+};
+
+void
+updateRRsetCache(RRsetCache& cache, Name& rrset_name,
+ uint32_t ttl = 20,
+ RRsetTrustLevel level = RRSET_TRUST_ADDITIONAL_AA)
+{
+ RRset rrset(rrset_name, RRClass::IN(), RRType::A(), RRTTL(ttl));
+ cache.update(rrset, level);
+}
+
+TEST_F(RRsetCacheTest, lookup) {
+ const RRType& type = RRType::A();
+ EXPECT_TRUE(cache_.lookup(name_, type) == NULL);
+
+ cache_.update(rrset1_, rrset_entry1_.getTrustLevel());
+ RRsetEntryPtr rrset_entry_ptr = cache_.lookup(name_, type);
+ EXPECT_EQ(rrset_entry_ptr->getTrustLevel(), rrset_entry1_.getTrustLevel());
+ EXPECT_EQ(rrset_entry_ptr->getRRset()->getName(), rrset_entry1_.getRRset()->getName());
+ EXPECT_EQ(rrset_entry_ptr->getRRset()->getType(), rrset_entry1_.getRRset()->getType());
+ EXPECT_EQ(rrset_entry_ptr->getRRset()->getClass(), rrset_entry1_.getRRset()->getClass());
+
+ // Check whether the expired rrset entry will be removed automatically
+ // when looking up.
+ Name name_test("test.example.com.");
+ updateRRsetCache(cache_, name_test, 0); // Add a rrset with TTL 0 to cache.
+ EXPECT_FALSE(cache_.lookup(name_test, RRType::A()));
+}
+
+TEST_F(RRsetCacheTest, update) {
+ const RRType& type = RRType::A();
+
+ cache_.update(rrset1_, rrset_entry1_.getTrustLevel());
+ RRsetEntryPtr rrset_entry_ptr = cache_.lookup(name_, type);
+ EXPECT_EQ(rrset_entry_ptr->getTrustLevel(), rrset_entry1_.getTrustLevel());
+
+ cache_.update(rrset2_, rrset_entry2_.getTrustLevel());
+ rrset_entry_ptr = cache_.lookup(name_, type);
+ // The trust level should be updated
+ EXPECT_EQ(rrset_entry_ptr->getTrustLevel(), rrset_entry2_.getTrustLevel());
+
+ cache_.update(rrset1_, rrset_entry1_.getTrustLevel());
+ // The trust level should not be updated
+ EXPECT_EQ(rrset_entry_ptr->getTrustLevel(), rrset_entry2_.getTrustLevel());
+}
+
+// Test whether the lru list in rrset cache works as expected.
+TEST_F(RRsetCacheTest, cacheLruBehavior) {
+ Name name1("1.example.com.");
+ Name name2("2.example.com.");
+ Name name3("3.example.com.");
+ Name name4("4.example.com.");
+
+ updateRRsetCache(cache_, name1);
+ updateRRsetCache(cache_, name2);
+ updateRRsetCache(cache_, name3);
+
+ EXPECT_TRUE(cache_.lookup(name1, RRType::A()));
+
+ // Now update the fourth rrset, rrset with name "2.example.com."
+ // should has been removed from cache.
+ updateRRsetCache(cache_, name4);
+ EXPECT_FALSE(cache_.lookup(name2, RRType::A()));
+
+ // Test Update rrset with higher trust level
+ updateRRsetCache(cache_, name1, RRSET_TRUST_PRIM_GLUE);
+ // Test update rrset with lower trust level.
+ updateRRsetCache(cache_, name3, RRSET_TRUST_ADDITIONAL_NONAA);
+
+ // When add rrset with name2, rrset with name4
+ // has been removed from the cache.
+ updateRRsetCache(cache_, name2);
+ EXPECT_FALSE(cache_.lookup(name4, RRType::A()));
+}
+
+}
diff --git a/src/lib/cache/tests/rrset_entry_unittest.cc b/src/lib/cache/tests/rrset_entry_unittest.cc
new file mode 100644
index 0000000..c7c3c6e
--- /dev/null
+++ b/src/lib/cache/tests/rrset_entry_unittest.cc
@@ -0,0 +1,106 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <string>
+#include <gtest/gtest.h>
+#include <cache/cache_entry_key.h>
+#include <cache/rrset_entry.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rrset.h>
+
+using namespace isc::cache;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+class GenCacheKeyTest: public testing::Test {
+};
+
+TEST_F(GenCacheKeyTest, genCacheEntryKey1) {
+ string name = "example.com.";
+ uint16_t type = 12;
+ string name_type = "example.com.12";
+
+ EXPECT_EQ(name_type, genCacheEntryName(name, type));
+}
+
+TEST_F(GenCacheKeyTest, genCacheEntryKey2) {
+ Name name("example.com");
+ RRType type(1234);
+ string keystr = "example.com.1234";
+ EXPECT_EQ(keystr, genCacheEntryName(name, type));
+}
+
+class DerivedRRsetEntry: public RRsetEntry {
+public:
+ DerivedRRsetEntry(const isc::dns::RRset& rrset, const RRsetTrustLevel& level) : RRsetEntry(rrset, level) {};
+
+ void updateTTLForTest() {
+
+ }
+};
+
+#define TEST_TTL 100
+class RRsetEntryTest : public ::testing::Test {
+protected:
+ RRsetEntryTest():
+ name("test.example.com"),
+ rrset(name, RRClass::IN(), RRType::A(), RRTTL(TEST_TTL)),
+ trust_level(RRSET_TRUST_ADDITIONAL_AA),
+ rrset_entry(rrset, trust_level)
+ {
+ }
+ Name name;
+ RRset rrset;
+ RRsetTrustLevel trust_level;
+ RRsetEntry rrset_entry;
+};
+
+TEST_F(RRsetEntryTest, constructor) {
+ EXPECT_EQ(trust_level, rrset_entry.getTrustLevel());
+ EXPECT_EQ(rrset.getName(), rrset_entry.getRRset()->getName());
+ EXPECT_EQ(rrset.getClass(), rrset_entry.getRRset()->getClass());
+ EXPECT_EQ(rrset.getType(), rrset_entry.getRRset()->getType());
+ EXPECT_EQ(rrset.getRdataCount(), rrset_entry.getRRset()->getRdataCount());
+}
+
+TEST_F(RRsetEntryTest, updateTTL) {
+ uint32_t ttl = rrset_entry.getTTL();
+ sleep(1);
+ // The TTL should be decreased
+ EXPECT_TRUE(rrset_entry.getTTL() < ttl);
+}
+
+TEST_F(RRsetEntryTest, TTLExpire) {
+ RRset exp_rrset(name, RRClass::IN(), RRType::A(), RRTTL(1));
+ RRsetEntry rrset_entry(exp_rrset, RRSET_TRUST_ANSWER_AA);
+ sleep(1);
+ uint32_t ttl = rrset_entry.getTTL();
+ EXPECT_LT(ttl, 1);
+ sleep(1);
+ ttl = rrset_entry.getTTL();
+ EXPECT_LT(ttl, 1);
+}
+
+TEST_F(RRsetEntryTest, getExpireTime){
+ uint32_t exp_time = time(NULL) + TEST_TTL;
+ EXPECT_EQ(exp_time, rrset_entry.getExpireTime());
+}
+
+} // namespace
+
diff --git a/src/lib/cache/tests/run_unittests.cc b/src/lib/cache/tests/run_unittests.cc
new file mode 100644
index 0000000..2c86581
--- /dev/null
+++ b/src/lib/cache/tests/run_unittests.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::UnitTestUtil::addDataPath(TEST_DATA_SRCDIR);
+ isc::UnitTestUtil::addDataPath(TEST_DATA_BUILDDIR);
+
+ return (RUN_ALL_TESTS());
+}
diff --git a/src/lib/cache/tests/testdata/message_cname_referral.wire b/src/lib/cache/tests/testdata/message_cname_referral.wire
new file mode 100644
index 0000000..7c6b285
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_cname_referral.wire
@@ -0,0 +1,56 @@
+#
+# Request A record for x.example.org, the CNAME record exist for x.example.org
+# it poinst to x.example.net, but the server has no idea whether x.example.net exist
+# so it give some NS records for reference
+#
+# Transaction ID: 0xaf71
+# Flags: 0x8480 (Standard query response, No error)
+af71 8480
+# Questions: 1
+# Answer RRs: 1
+# Authority RRs: 13
+# Additional RRs: 0
+00 01 00 01 00 0d 00 00
+##
+## query
+##
+# x.example.org: type A, class IN
+##
+## Answer
+##
+# x.example.org: type CNAME, class IN, cname x.example.net
+# TTL: 360s
+01 78 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 00 01 00 01
+c0 0c 00 05 00 01 00 00 0e 10 00 0f 01 78 07 65 78
+61 6d 70 6c 65 03 6e 65 74 00
+##
+## Authority
+##
+# TTL:518400
+# <Root>: type NS, class IN, ns G.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns E.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns J.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns L.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns H.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns I.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns K.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns M.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns F.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns B.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns C.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns D.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns A.ROOT-SERVERS.net
+00 00 02 00 01 00
+07 e9 00 00 11 01 47 0c 52 4f 4f 54 2d 53 45 52
+56 45 52 53 c0 35 00 00 02 00 01 00 07 e9 00 00
+04 01 45 c0 47 00 00 02 00 01 00 07 e9 00 00 04
+01 4a c0 47 00 00 02 00 01 00 07 e9 00 00 04 01
+4c c0 47 00 00 02 00 01 00 07 e9 00 00 04 01 48
+c0 47 00 00 02 00 01 00 07 e9 00 00 04 01 49 c0
+47 00 00 02 00 01 00 07 e9 00 00 04 01 4b c0 47
+00 00 02 00 01 00 07 e9 00 00 04 01 4d c0 47 00
+00 02 00 01 00 07 e9 00 00 04 01 46 c0 47 00 00
+02 00 01 00 07 e9 00 00 04 01 42 c0 47 00 00 02
+00 01 00 07 e9 00 00 04 01 43 c0 47 00 00 02 00
+01 00 07 e9 00 00 04 01 44 c0 47 00 00 02 00 01
+00 07 e9 00 00 04 01 41 c0 47
diff --git a/src/lib/cache/tests/testdata/message_example_com_soa.wire b/src/lib/cache/tests/testdata/message_example_com_soa.wire
new file mode 100644
index 0000000..6d70ed7
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_example_com_soa.wire
@@ -0,0 +1,57 @@
+#
+# SOA request response for example.com
+#
+# Transaction ID: 0x7f36
+# Flags: 0x8400 (Standard query response, No error)
+7f 36 84 00
+# Questions: 1
+00 01
+# Answer RRs: 1
+00 01
+# Authority RRs: 2
+00 02
+# Additional RRs: 0
+00 00
+##
+## Query
+##
+# Name: example.com
+07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# Type: SOA (Start of zone of authority)
+00 06
+# Class: IN (0x0001)
+00 01
+##
+## Answers
+##
+# Name: example.com
+c0 0c
+# Type: SOA (Start of zone of authority)
+00 06
+# Class: IN (0x0001)
+00 01
+# Time to live: 2 days (172800s)
+00 02 a3 00
+# Data length: 49
+00 31
+# Primary name server: dns1.icann.org
+04 64 6e 73 31 05 69 63 61 6e 6e 03 6f 72 67 00
+# Responsible authority's mailbox: hostmaster.icann.org
+0a 68 6f 73 74 6d 61 73 74 65 72 c0 2e
+# Serial number: 2010072301
+77 cf 44 ed
+# Refresh interval: 2 hours
+00 00 1c 20
+# Retry interval: 1 hour
+00 00 0e 10
+# Expiration limit: 14 days
+00 12 75 00
+# Minimum TTL: 1 day
+00 01 51 80
+##
+## Authoritative nameservers
+##
+# example.com: type NS, class IN, ns a.iana-servers.net
+c0 0c 00 02 00 01 00 02 a3 00 00 14 01 61 0c 69 61 6e 61 2d 73 65 72 76 65 72 73 03 6e 65 74 00
+# example.com: type NS, class IN, ns b.iana-servers.net
+c0 0c 00 02 00 01 00 02 a3 00 00 04 01 62 c0 68
diff --git a/src/lib/cache/tests/testdata/message_fromWire1 b/src/lib/cache/tests/testdata/message_fromWire1
new file mode 100644
index 0000000..5b76e3f
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire1
@@ -0,0 +1,22 @@
+#
+# A simple DNS response message
+# ID = 0x1035
+# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=2, other COUNTS=0
+# Question: test.example.com. IN A
+# Answer:
+# test.example.com. 3600 IN A 192.0.2.1
+# test.example.com. 7200 IN A 192.0.2.2
+#
+1035 8500
+0001 0002 0000 0000
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0001 0001
+# same name, fully compressed
+c0 0c
+# TTL=3600, A, IN, RDLENGTH=4, RDATA
+0001 0001 00000e10 0004 c0 00 02 01
+# mostly same, with the slight difference in RDATA and TTL
+c0 0c
+0001 0001 00001c20 0004 c0 00 02 02
diff --git a/src/lib/cache/tests/testdata/message_fromWire2 b/src/lib/cache/tests/testdata/message_fromWire2
new file mode 100644
index 0000000..c8fddbd
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire2
@@ -0,0 +1,22 @@
+#
+# A simple DNS response message
+# ID = 0x1035
+# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=2, other COUNTS=0
+# Question: test.example.net. IN A
+# Answer:
+# test.example.net. 3600 IN A 192.0.2.1
+# test.example.net. 7200 IN A 192.0.2.2
+#
+1035 8500
+0001 0002 0000 0000
+#(4) t e s t (7) e x a m p l e (3) n e t .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 6e 65 74 00
+0001 0001
+# same name, fully compressed
+c0 0c
+# TTL=3600, A, IN, RDLENGTH=4, RDATA
+0001 0001 00000e10 0004 c0 00 02 01
+# mostly same, with the slight difference in RDATA and TTL
+c0 0c
+0001 0001 00001c20 0004 c0 00 02 02
diff --git a/src/lib/cache/tests/testdata/message_fromWire3 b/src/lib/cache/tests/testdata/message_fromWire3
new file mode 100644
index 0000000..f7b3a4a
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire3
@@ -0,0 +1,76 @@
+#
+# A simple DNS response message
+# ID = 0x0513
+# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=1, AUTHORITY COUNT=5, ADDITIONAL COUNT=7
+# Question: example.com. IN SOA
+# ANSWER:
+# ;; QUESTION SECTION:
+# ;example.com. IN SOA
+
+# ;; ANSWER SECTION:
+# example.com. 21600 IN SOA a.dns.example.com. root.example.com. 2009070811 7200 3600 2419200 21600
+
+# ;; AUTHORITY SECTION:
+# example.com. 21600 IN NS b.dns.example.com.
+# example.com. 21600 IN NS c.dns.example.com.
+# example.com. 21600 IN NS a.dns.example.com.
+# example.com. 21600 IN NS e.dns.example.com.
+# example.com. 21600 IN NS d.dns.example.com.
+
+# ;; ADDITIONAL SECTION:
+# a.dns.example.com. 21600 IN A 1.1.1.1
+# a.dns.example.com. 21600 IN A 2.2.2.2
+# b.dns.example.com. 21600 IN A 3.3.3.3
+# c.dns.example.com. 10800 IN A 4.4.4.4
+# d.dns.example.com. 43200 IN A 5.5.5.5
+# e.dns.example.com. 21600 IN A 7.7.7.7
+# e.dns.example.com. 21600 IN A 6.6.6.6
+
+0513 8500
+0001 0001 0005 0007
+#(7) e x a m p l e (3) c o m .
+ 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0006 0001
+# same name, fully compressed
+c0 0c
+# SOA IN TTL=6h RDLENGTH=35 rdata
+0006 0001 00005460 0023 01 61 03 64 6e 73 c0 0c 04 72 6f 6f 74 c0 0c 77 bf fc db 00 00 1c 20 00 00 0e 10 00 24 ea 00 00 00 54 60
+#Authority section begin
+c0 0c
+# NS IN TTL=6h RDLENGTH=4 b.dns.example.com.
+0002 0001 00005460 0004 01 62 c0 2b
+# NS IN TTL=6h c.dns.example.com.
+c0 0c
+0002 0001 00005460 00 04 01 63 c0 2b
+# NS IN a.dns.example.com.
+c0 0c
+0002 0001 00005460 00 02 c0 29
+# NS IN e.dns.example.com.
+c0 0c
+0002 0001 00005460 0004 01 65 c0 2b
+# NS IN d.dns.example.com.
+c0 0c
+0002 0001 00005460 0004 01 64 c0 2b
+# additional section begin
+# a.dns.example.com. A
+c0 29
+0001 0001 00005460 0004 01 01 01 01
+# a.dns.example.com. A
+c0 29
+0001 0001 00005460 0004 02 02 02 02
+#b.dns.example.com. A
+c0 58
+0001 0001 00002A30 0004 03 03 03 03
+#c.dns.example.com. A
+c0 68
+0001 0001 00005460 0004 04 04 04 04
+# d.dns.example.com. A
+c0 96
+0001 0001 0000A8C0 0004 05 05 05 05
+# e.dns.example.com. A
+c0 86
+0001 0001 00005460 0004 07 07 07 07
+# e.dns.example.com. A
+c0 86
+0001 0001 00005460 0004 06 06 06 06
diff --git a/src/lib/cache/tests/testdata/message_fromWire4 b/src/lib/cache/tests/testdata/message_fromWire4
new file mode 100644
index 0000000..251abd5
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire4
@@ -0,0 +1,80 @@
+# Note: This message is same with message_fromWire3, except
+# AA bit is not set. There should be a better way to
+# avoid the duplicated file by clear the AA bit flags
+# after reading the message from message_fromWire4.
+#
+# A simple DNS response message
+# ID = 0x0513
+# QR=1 (response), Opcode=0, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=1, AUTHORITY COUNT=5, ADDITIONAL COUNT=7
+# Question: example.com. IN SOA
+# ANSWER:
+# ;; QUESTION SECTION:
+# ;example.com. IN SOA
+
+# ;; ANSWER SECTION:
+# example.com. 21600 IN SOA a.dns.example.com. root.example.com. 2009070811 7200 3600 2419200 21600
+
+# ;; AUTHORITY SECTION:
+# example.com. 21600 IN NS b.dns.example.com.
+# example.com. 21600 IN NS c.dns.example.com.
+# example.com. 21600 IN NS a.dns.example.com.
+# example.com. 21600 IN NS e.dns.example.com.
+# example.com. 21600 IN NS d.dns.example.com.
+
+# ;; ADDITIONAL SECTION:
+# a.dns.example.com. 21600 IN A 1.1.1.1
+# a.dns.example.com. 21600 IN A 2.2.2.2
+# b.dns.example.com. 21600 IN A 3.3.3.3
+# c.dns.example.com. 10800 IN A 4.4.4.4
+# d.dns.example.com. 43200 IN A 5.5.5.5
+# e.dns.example.com. 21600 IN A 7.7.7.7
+# e.dns.example.com. 21600 IN A 6.6.6.6
+
+0513 8100
+0001 0001 0005 0007
+#(7) e x a m p l e (3) c o m .
+ 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0006 0001
+# same name, fully compressed
+c0 0c
+# SOA IN TTL=6h RDLENGTH=35 rdata
+0006 0001 00005460 0023 01 61 03 64 6e 73 c0 0c 04 72 6f 6f 74 c0 0c 77 bf fc db 00 00 1c 20 00 00 0e 10 00 24 ea 00 00 00 54 60
+#Authority section begin
+c0 0c
+# NS IN TTL=6h RDLENGTH=4 b.dns.example.com.
+0002 0001 00005460 0004 01 62 c0 2b
+# NS IN TTL=6h c.dns.example.com.
+c0 0c
+0002 0001 00005460 00 04 01 63 c0 2b
+# NS IN a.dns.example.com.
+c0 0c
+0002 0001 00005460 00 02 c0 29
+# NS IN e.dns.example.com.
+c0 0c
+0002 0001 00005460 0004 01 65 c0 2b
+# NS IN d.dns.example.com.
+c0 0c
+0002 0001 00005460 0004 01 64 c0 2b
+# additional section begin
+# a.dns.example.com. A
+c0 29
+0001 0001 00005460 0004 01 01 01 01
+# a.dns.example.com. A
+c0 29
+0001 0001 00005460 0004 02 02 02 02
+#b.dns.example.com. A
+c0 58
+0001 0001 00002A30 0004 03 03 03 03
+#c.dns.example.com. A
+c0 68
+0001 0001 00005460 0004 04 04 04 04
+# d.dns.example.com. A
+c0 96
+0001 0001 0000A8C0 0004 05 05 05 05
+# e.dns.example.com. A
+c0 86
+0001 0001 00005460 0004 07 07 07 07
+# e.dns.example.com. A
+c0 86
+0001 0001 00005460 0004 06 06 06 06
diff --git a/src/lib/cache/tests/testdata/message_fromWire5 b/src/lib/cache/tests/testdata/message_fromWire5
new file mode 100644
index 0000000..965f250
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire5
@@ -0,0 +1,36 @@
+#
+# A simple DNS response message
+# ID = 0x07b2
+# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=2, other COUNTS=0
+# Question: a.example.net. IN A
+# Answer:
+# ANSWER SECTION:
+# a.example.com. 21600 IN CNAME cname.example.com.
+# cname.example.com. 21600 IN A 1.1.1.1
+#
+# AUTHORITY SECTION:
+# example.com. 21600 IN NS a.dns.example.com.
+#
+# ADDITIONAL SECTION:
+# a.dns.example.com. 21600 IN A 1.1.1.1
+#
+07b2 8500
+0001 0002 0001 0001
+#(1) a (7) e x a m p l e (3) c o m .
+ 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# A IN
+0001 0001
+#
+c0 0c
+#CNAME IN TTL RDATA_LEN
+0005 0001 00005460 0008 05 63 6e 61 6d 65 c0 0e
+#
+c0 2b
+0001 0001 00005460 0004 01 01 01 01
+#
+c0 0e
+0002 0001 00005460 0008 01 61 03 64 6e 73 c0 0e
+#
+c0 4f
+0001 0001 00005460 0004 01 01 01 01
diff --git a/src/lib/cache/tests/testdata/message_fromWire6 b/src/lib/cache/tests/testdata/message_fromWire6
new file mode 100644
index 0000000..23684ba
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire6
@@ -0,0 +1,40 @@
+#
+# A simple DNS response message
+# ID = 0x005e
+# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=2, other COUNTS=0
+# Question: a.d.example.net. IN A
+# Answer:
+# ;; ANSWER SECTION:
+# d.example.com. 21600 IN DNAME dname.example.com.
+# a.d.example.com. 21600 IN CNAME a.dname.example.com.
+# a.dname.example.com. 21600 IN A 1.1.1.1
+#
+# ;; AUTHORITY SECTION:
+# example.com. 21600 IN NS a.dns.example.com.
+#
+# ;; ADDITIONAL SECTION:
+# a.dns.example.com. 21600 IN A 1.1.1.1
+#
+#
+005e 8500
+0001 0003 0001 0001
+#(1)a (1) b (7) e x a m p l e (3) c o m .
+ 01 61 01 64 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# A IN
+0001 0001
+#
+c0 0e
+0027 0001 00005460 0013 05 64 6e 61 6d 65 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+#
+c0 0c
+0005 0001 00005460 0004 01 61 c0 2d
+#
+c0 4c
+0001 0001 00005460 0004 01 01 01 01
+#
+c0 33
+0002 0001 00005460 0008 01 61 03 64 6e 73 c0 33
+#
+c0 6c
+0001 0001 00005460 0004 01 01 01 01
diff --git a/src/lib/cache/tests/testdata/message_fromWire7 b/src/lib/cache/tests/testdata/message_fromWire7
new file mode 100644
index 0000000..7b10b5d
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire7
@@ -0,0 +1,27 @@
+#
+# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1155
+# ;; flags: qr aa rd; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 0
+# ;; WARNING: recursion requested but not available
+#
+# ;; QUESTION SECTION:
+# ;test.example.com. IN A
+#
+# ;; ANSWER SECTION:
+# test.example.com. 21600 IN CNAME cname.a.dname.example.com.
+# dname.example.com. 21600 IN DNAME dname.example.org.
+# cname.a.dname.example.com. 21600 IN CNAME cname.a.dname.example.org.
+# dname.example.org. 21600 IN DNAME dname.example.org.
+# cname.a.dname.example.org. 21600 IN CNAME cname.a.dname.example.org.
+
+0424 8500
+ 00 01 00 05 00 00 00 00 04 74 65 73
+ 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 01
+ 00 01 c0 0c 00 05 00 01 00 00 54 60 00 10 05 63
+ 6e 61 6d 65 01 61 05 64 6e 61 6d 65 c0 11 c0 36
+ 00 27 00 01 00 00 54 60 00 13 05 64 6e 61 6d 65
+ 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 c0 2e 00
+ 05 00 01 00 00 54 60 00 0a 05 63 6e 61 6d 65 01
+ 61 c0 4a c0 4a 00 27 00 01 00 00 54 60 00 13 05
+ 64 6e 61 6d 65 07 65 78 61 6d 70 6c 65 03 6f 72
+ 67 00 c0 69 00 05 00 01 00 00 54 60 00 02 c0 69
+
diff --git a/src/lib/cache/tests/testdata/message_fromWire8 b/src/lib/cache/tests/testdata/message_fromWire8
new file mode 100644
index 0000000..bc9e144
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire8
@@ -0,0 +1,23 @@
+# A response includes multiple DNAME and synchronized CNAME records
+# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 900
+# ;; flags: qr aa rd; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 0
+# ;; WARNING: recursion requested but not available
+#
+# ;; QUESTION SECTION:
+# ;a.dname.example.com. IN NS
+#
+# ;; ANSWER SECTION:
+# dname.example.com. 21600 IN DNAME dname.example.org.
+# a.dname.example.com. 21600 IN CNAME a.dname.example.org.
+# dname.example.org. 21600 IN DNAME dname.example.org.
+# a.dname.example.org. 21600 IN CNAME a.dname.example.org.
+0384 8500
+ 00 01 00 04 00 00 00 00 01 61 05 64
+ 6e 61 6d 65 07 65 78 61 6d 70 6c 65 03 63 6f 6d
+ 00 00 02 00 01 c0 0e 00 27 00 01 00 00 54 60 00
+ 13 05 64 6e 61 6d 65 07 65 78 61 6d 70 6c 65 03
+ 6f 72 67 00 c0 0c 00 05 00 01 00 00 54 60 00 04
+ 01 61 c0 31 c0 31 00 27 00 01 00 00 54 60 00 13
+ 05 64 6e 61 6d 65 07 65 78 61 6d 70 6c 65 03 6f
+ 72 67 00 c0 50 00 05 00 01 00 00 54 60 00 02 c0
+ 50
diff --git a/src/lib/cache/tests/testdata/message_fromWire9 b/src/lib/cache/tests/testdata/message_fromWire9
new file mode 100644
index 0000000..e2dbd06
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire9
@@ -0,0 +1,25 @@
+#
+# The TTL for a record in answer section is 0, so it
+# will expire immediately after being cached.
+#
+# A simple DNS response message
+# ID = 0x1035
+# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=2, other COUNTS=0
+# Question: test.example.org. IN A
+# Answer:
+# test.example.org. 0000 IN A 192.0.2.1
+# test.example.org. 7200 IN A 192.0.2.2
+#
+1035 8500
+0001 0002 0000 0000
+#(4) t e s t (7) e x a m p l e (3) o r g .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 6f 72 67 00
+0001 0001
+# same name, fully compressed
+c0 0c
+# TTL=3600, A, IN, RDLENGTH=4, RDATA
+0001 0001 00000000 0004 c0 00 02 01
+# mostly same, with the slight difference in RDATA and TTL
+c0 0c
+0001 0001 00001c20 0004 c0 00 02 02
diff --git a/src/lib/cache/tests/testdata/message_large_ttl.wire b/src/lib/cache/tests/testdata/message_large_ttl.wire
new file mode 100644
index 0000000..6e152ef
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_large_ttl.wire
@@ -0,0 +1,31 @@
+#
+# A response that the TTL is quite large(> 7days)
+#
+##
+## header
+##
+# Transaction ID: 0x0d1f
+# Flags: 0x8580 (Standard query response, No error)
+0d1f 8580
+# Questions: 1
+# Answer RRs: 1
+# Authority RRs: 3
+# Additional RRs: 3
+00 01 00 01 00 01 00 00
+##
+## Query
+##
+# test.example.org: type A, class IN
+04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 00 01 00 01
+##
+## Answer
+##
+# test.example.org: type A, class IN, addr 127.0.0.1
+# TTL: 7 days, 1 second (604801 seconds)
+c0 0c 00 01 00 01 00 09 3a 81 00 04 7f 00 00 01
+##
+## Authority
+##
+# example.org: type NS, class IN, ns ns1.example.org
+# TTL: 7 days, 1 second (604801 seconds)
+c0 11 00 02 00 01 00 09 3a 81 00 06 03 6e 73 31 c0 11
diff --git a/src/lib/cache/tests/testdata/message_nodata_with_soa.wire b/src/lib/cache/tests/testdata/message_nodata_with_soa.wire
new file mode 100644
index 0000000..67a4adc
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_nodata_with_soa.wire
@@ -0,0 +1,32 @@
+#
+# NOERROR/NODATA response with SOA record
+#
+##
+## header
+##
+#Transaction ID: 0x0284
+#Flags: 0x8500 (Standard query response, No error)
+0284 8500
+#Question:1
+00 01
+#Answer RRs:0
+00 00
+#Authority RRs:1
+00 01
+#Additional RRs:0
+00 00
+##
+## Queries
+##
+# example.com: type MX, class IN
+07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 0f 00 01
+##
+## Authoritative nameservers
+##
+# example.com: type SOA, class IN, mname dns1.icann.org
+# TTL:86400
+c0 0c 00
+06 00 01 00 01 51 80 00 31 04 64 6e 73 31 05 69
+63 61 6e 6e 03 6f 72 67 00 0a 68 6f 73 74 6d 61
+73 74 65 72 c0 2e 77 cf 44 ed 00 00 1c 20 00 00
+0e 10 00 12 75 00 00 01 51 80
diff --git a/src/lib/cache/tests/testdata/message_nxdomain_cname.wire b/src/lib/cache/tests/testdata/message_nxdomain_cname.wire
new file mode 100644
index 0000000..1ae3d76
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_nxdomain_cname.wire
@@ -0,0 +1,36 @@
+#
+# NXDOMAIN response
+# The cname type of a.example.org exist, it points to b.example.org
+# b.example.org points to c.example.org
+# but c.example.org does not exist
+#
+##
+## header
+##
+# Transaction ID: 0xc2aa
+# Flags: 0x8583 (Standard query response, No such name)
+c2aa 8583
+# Questions: 1
+# Answer RRs: 2
+# Authority RRs: 1
+# dditional RRs: 0
+00 01 00 02 00 01 00 00
+##
+## Queries
+##
+# a.example.org: type A, class IN
+01 61 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 00 01 00 01
+##
+## Answers
+##
+# a.example.org: type CNAME, class IN, cname b.example.org
+c0 0c 00 05 00 01 00 00 0e 10 00 04 01 62 c0 0e
+# b.example.org: type CNAME, class IN, cname c.example.org
+c0 2b 00 05 00 01 00 00 0e 10 00 04 01 63 c0 0e
+##
+## Authority
+##
+# example.org: type SOA, class IN, mname ns1.example.org
+c0 0e 00 06 00 01 00 00 02 58 00 22 03 6e 73 31 c0
+0e 05 61 64 6d 69 6e c0 0e 00 00 04 d2 00 00 0e
+10 00 00 07 08 00 24 ea 00 00 00 02 58
diff --git a/src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire b/src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire
new file mode 100644
index 0000000..142d8cf
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire
@@ -0,0 +1,25 @@
+#
+# Negative response (NXDOMAIN) with large TTL (3hours + 1second)
+#
+##
+## Header
+##
+# Transaction ID: 0xb1fe
+# Flags: 0x8583 (Standard query response, No such name)
+b1fe 8583
+# Questions: 1
+# Authority RRs: 1
+00 01 00 00 00 01 00 00
+##
+## Query
+##
+# c.example.org: type A, class IN
+01 63 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 00 01 00 01
+##
+## Authority
+##
+# example.org: type SOA, class IN, mname ns1.example.org
+# TTL: 3 Hourse, 1 second (10801seconds)
+c0 0e 00 06 00 01 00 00 2a 31 00 22 03 6e 73 31 c0
+0e 05 61 64 6d 69 6e c0 0e 00 00 04 d2 00 00 0e
+10 00 00 07 08 00 24 ea 00 00 00 2a 31
diff --git a/src/lib/cache/tests/testdata/message_nxdomain_no_soa.wire b/src/lib/cache/tests/testdata/message_nxdomain_no_soa.wire
new file mode 100644
index 0000000..b138cc2
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_nxdomain_no_soa.wire
@@ -0,0 +1,26 @@
+#
+# NXDOMAIN response with SOA record
+#
+##
+## Header
+##
+# ID = 0x3da0
+# QR = 1 (response), Opcode = 0, AA = 1, RCODE=3 (NXDOMAIN)
+3da0 8403
+# Question : 1
+00 01
+# Answer : 0
+00 00
+# Authority : 0
+00 00
+# Additional : 0
+00 00
+##
+## Query
+##
+#(4) n o n e x i s t (7) e x a m p l e (3) c o m (0)
+ 08 6e 6f 6e 65 78 69 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# Type:A
+00 01
+# class: IN
+00 01
diff --git a/src/lib/cache/tests/testdata/message_nxdomain_with_soa.wire b/src/lib/cache/tests/testdata/message_nxdomain_with_soa.wire
new file mode 100644
index 0000000..04fd66f
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_nxdomain_with_soa.wire
@@ -0,0 +1,55 @@
+#
+# NXDOMAIN response with SOA record
+#
+##
+## Header
+##
+# ID = 0x3da0
+# QR = 1 (response), Opcode = 0, AA = 1, RCODE=3 (NXDOMAIN)
+3da0 8403
+# Question : 1
+00 01
+# Answer : 0
+00 00
+# Authority : 1
+00 01
+# Additional : 0
+00 00
+##
+## Query
+##
+#(4) n o n e x i s t (7) e x a m p l e (3) c o m (0)
+ 08 6e 6f 6e 65 78 69 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# Type:A
+00 01
+# class: IN
+00 01
+##
+## Authority
+##
+# name: example.com
+c0 15
+# Type:SOA
+00 06
+# Class: IN
+00 01
+# TTL: 86400
+00 01 51 80
+# Data Length: 49
+00 31
+# Name Server:
+#(4) d n s 1 (5) i c a n n (3) o r g (0)
+ 04 64 6e 73 31 05 69 63 61 6e 6e 03 6f 72 67 00
+# MX:
+# (10) h o s t m a s t e r .icann.org.
+ 0a 68 6f 73 74 6d 61 73 74 65 72 c0 37
+# Serial Number:2010072301
+77 cf 44 ed
+# Refresh Interval:2 hours
+00 00 1c 20
+# Retry Interval: 1 hour
+00 00 0e 10
+# Expiration: 14 days
+00 12 75 00
+# Minimum TTL 1 day
+00 01 51 80
diff --git a/src/lib/cache/tests/testdata/message_referral.wire b/src/lib/cache/tests/testdata/message_referral.wire
new file mode 100644
index 0000000..bb897ca
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_referral.wire
@@ -0,0 +1,36 @@
+#
+# Query x.example.net to nameservr of example.org
+# It will just give some referral info
+#
+#
+# Transaction ID: 0x8b61
+# Flags: 0x8080 (Standard query response, No error)
+8b61 8080
+# Questions: 1
+# Authority RRs: 13
+00 01 00 00 00 0d 00 00
+##
+## Query
+##
+# x.example.net: type A, class IN
+01 78 07 65 78 61 6d 70 6c 65 03 6e 65 74 00 00 01 00 01
+##
+## Authority
+##
+# <Root>: type NS, class IN, ns B.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns M.ROOT-SERVERS.net
+# ...
+# <Root>: type NS, class IN, ns H.ROOT-SERVERS.net
+00 00 02 00 01 00 07 e9 00 00 11 01 42 0c 52 4f 4f
+54 2d 53 45 52 56 45 52 53 c0 16 00 00 02 00 01
+00 07 e9 00 00 04 01 4d c0 2c 00 00 02 00 01 00
+07 e9 00 00 04 01 44 c0 2c 00 00 02 00 01 00 07
+e9 00 00 04 01 4c c0 2c 00 00 02 00 01 00 07 e9
+00 00 04 01 4b c0 2c 00 00 02 00 01 00 07 e9 00
+00 04 01 43 c0 2c 00 00 02 00 01 00 07 e9 00 00
+04 01 41 c0 2c 00 00 02 00 01 00 07 e9 00 00 04
+01 49 c0 2c 00 00 02 00 01 00 07 e9 00 00 04 01
+45 c0 2c 00 00 02 00 01 00 07 e9 00 00 04 01 46
+c0 2c 00 00 02 00 01 00 07 e9 00 00 04 01 4a c0
+2c 00 00 02 00 01 00 07 e9 00 00 04 01 47 c0 2c
+00 00 02 00 01 00 07 e9 00 00 04 01 48 c0 2c
diff --git a/src/lib/cc/tests/session_unittests.cc b/src/lib/cc/tests/session_unittests.cc
index d61cbd3..5f6e595 100644
--- a/src/lib/cc/tests/session_unittests.cc
+++ b/src/lib/cc/tests/session_unittests.cc
@@ -74,7 +74,7 @@ public:
}
void
- acceptHandler(const asio::error_code&) {
+ acceptHandler(const asio::error_code&) const {
}
void
diff --git a/src/lib/config/ccsession.cc b/src/lib/config/ccsession.cc
index 7049565..69621a4 100644
--- a/src/lib/config/ccsession.cc
+++ b/src/lib/config/ccsession.cc
@@ -170,10 +170,10 @@ ModuleCCSession::readModuleSpecification(const std::string& filename) {
try {
module_spec = moduleSpecFromFile(file, true);
- } catch (JSONError pe) {
+ } catch (const JSONError& pe) {
cout << "Error parsing module specification file: " << pe.what() << endl;
exit(1);
- } catch (ModuleSpecError dde) {
+ } catch (const ModuleSpecError& dde) {
cout << "Error reading module specification file: " << dde.what() << endl;
exit(1);
}
diff --git a/src/lib/config/module_spec.cc b/src/lib/config/module_spec.cc
index ad40689..fd07dde 100644
--- a/src/lib/config/module_spec.cc
+++ b/src/lib/config/module_spec.cc
@@ -121,7 +121,7 @@ void
check_module_specification(ConstElementPtr def) {
try {
check_data_specification(def);
- } catch (TypeError te) {
+ } catch (const TypeError& te) {
throw ModuleSpecError(te.what());
}
}
diff --git a/src/lib/config/tests/ccsession_unittests.cc b/src/lib/config/tests/ccsession_unittests.cc
index 4ed3ce2..43663bd 100644
--- a/src/lib/config/tests/ccsession_unittests.cc
+++ b/src/lib/config/tests/ccsession_unittests.cc
@@ -31,7 +31,7 @@ using namespace std;
namespace {
std::string
-ccspecfile(const std::string name) {
+ccspecfile(const std::string& name) {
return (std::string(TEST_DATA_PATH) + "/" + name);
}
diff --git a/src/lib/config/tests/module_spec_unittests.cc b/src/lib/config/tests/module_spec_unittests.cc
index 8490aa0..59f5459 100644
--- a/src/lib/config/tests/module_spec_unittests.cc
+++ b/src/lib/config/tests/module_spec_unittests.cc
@@ -23,7 +23,7 @@
using namespace isc::data;
using namespace isc::config;
-std::string specfile(const std::string name) {
+std::string specfile(const std::string& name) {
return (std::string(TEST_DATA_PATH) + "/" + name);
}
@@ -36,7 +36,7 @@ moduleSpecError(const std::string& file,
EXPECT_THROW(moduleSpecFromFile(specfile(file)), ModuleSpecError);
try {
ModuleSpec dd = moduleSpecFromFile(specfile(file));
- } catch (ModuleSpecError dde) {
+ } catch (const ModuleSpecError& dde) {
std::string ddew = dde.what();
EXPECT_EQ(error1 + error2 + error3, ddew);
}
diff --git a/src/lib/config/tests/testdata/spec22.spec b/src/lib/config/tests/testdata/spec22.spec
index be6d51f..cccd77b 100644
--- a/src/lib/config/tests/testdata/spec22.spec
+++ b/src/lib/config/tests/testdata/spec22.spec
@@ -1,6 +1,6 @@
{
"module_spec": {
- "module_name": "Spec2",
+ "module_name": "Spec22",
"config_data": [
{ "item_name": "value1",
"item_type": "integer",
@@ -81,7 +81,7 @@
{ "item_name": "value9",
"item_type": "map",
"item_optional": false,
- "item_default": {},
+ "item_default": { "v91": "def", "v92": {} },
"map_item_spec": [
{ "item_name": "v91",
"item_type": "string",
diff --git a/src/lib/datasrc/data_source.cc b/src/lib/datasrc/data_source.cc
index 8b2b47e..0ee656f 100644
--- a/src/lib/datasrc/data_source.cc
+++ b/src/lib/datasrc/data_source.cc
@@ -189,6 +189,19 @@ checkCache(QueryTask& task, RRsetList& target) {
rrsets.addRRset(rrset);
target.append(rrsets);
}
+
+ // Reset the referral flag and treat CNAME as "not found".
+ // This emulates the behavior of the sqlite3 data source.
+ // XXX: this is not ideal in that the responsibility for handling
+ // operation specific cases is spread over various classes at
+ // different abstraction levels. For longer terms we should
+ // revisit the whole datasource/query design, and clarify this
+ // point better.
+ flags &= ~DataSrc::REFERRAL;
+ if ((flags & DataSrc::CNAME_FOUND) != 0) {
+ flags &= ~DataSrc::CNAME_FOUND;
+ flags |= DataSrc::TYPE_NOT_FOUND;
+ }
task.flags = flags;
return (true);
}
@@ -1157,7 +1170,7 @@ MetaDataSrc::addDataSrc(ConstDataSrcPtr data_src) {
void
MetaDataSrc::removeDataSrc(ConstDataSrcPtr data_src) {
std::vector<ConstDataSrcPtr>::iterator it, itr;
- for (it = data_sources.begin(); it != data_sources.end(); it++) {
+ for (it = data_sources.begin(); it != data_sources.end(); ++it) {
if (*it == data_src) {
itr = it;
}
diff --git a/src/lib/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index 31c5a95..5230ced 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -35,7 +35,8 @@ namespace datasrc {
struct MemoryZone::MemoryZoneImpl {
// Constructor
MemoryZoneImpl(const RRClass& zone_class, const Name& origin) :
- zone_class_(zone_class), origin_(origin), origin_data_(NULL)
+ zone_class_(zone_class), origin_(origin), origin_data_(NULL),
+ domains_(true)
{
// We create the node for origin (it needs to exist anyway in future)
domains_.insert(origin, &origin_data_);
@@ -61,6 +62,7 @@ struct MemoryZone::MemoryZoneImpl {
// The tree stores domains
typedef RBTree<Domain> DomainTree;
typedef RBNode<Domain> DomainNode;
+ static const DomainNode::Flags DOMAINFLAG_WILD = DomainNode::FLAG_USER1;
// Information about the zone
RRClass zone_class_;
@@ -71,6 +73,47 @@ struct MemoryZone::MemoryZoneImpl {
// The actual zone data
DomainTree domains_;
+ // Add the necessary magic for any wildcard contained in 'name'
+ // (including itself) to be found in the zone.
+ //
+ // In order for wildcard matching to work correctly in find(),
+ // we must ensure that a node for the wildcarding level exists in the
+ // backend RBTree.
+ // E.g. if the wildcard name is "*.sub.example." then we must ensure
+ // that "sub.example." exists and is marked as a wildcard level.
+ // Note: the "wildcarding level" is for the parent name of the wildcard
+ // name (such as "sub.example.").
+ //
+ // We also perform the same trick for empty wild card names possibly
+ // contained in 'name' (e.g., '*.foo.example' in 'bar.*.foo.example').
+ void addWildcards(DomainTree& domains, const Name& name) {
+ Name wname(name);
+ const unsigned int labels(wname.getLabelCount());
+ const unsigned int origin_labels(origin_.getLabelCount());
+ for (unsigned int l = labels;
+ l > origin_labels;
+ --l, wname = wname.split(1)) {
+ if (wname.isWildcard()) {
+ // Ensure a separate level exists for the "wildcarding" name,
+ // and mark the node as "wild".
+ DomainNode* node;
+ DomainTree::Result result(domains.insert(wname.split(1),
+ &node));
+ assert(result == DomainTree::SUCCESS ||
+ result == DomainTree::ALREADYEXISTS);
+ node->setFlag(DOMAINFLAG_WILD);
+
+ // Ensure a separate level exists for the wildcard name.
+ // Note: for 'name' itself we do this later anyway, but the
+ // overhead should be marginal because wildcard names should
+ // be rare.
+ result = domains.insert(wname, &node);
+ assert(result == DomainTree::SUCCESS ||
+ result == DomainTree::ALREADYEXISTS);
+ }
+ }
+ }
+
/*
* Does some checks in context of the data that are already in the zone.
* Currently checks for forbidden combinations of RRsets in the same
@@ -113,13 +156,10 @@ struct MemoryZone::MemoryZoneImpl {
}
}
- /*
- * Implementation of longer methods. We put them here, because the
- * access is without the impl_-> and it will get inlined anyway.
- */
- // Implementation of MemoryZone::add
- result::Result add(const ConstRRsetPtr& rrset, DomainTree* domains) {
- // Sanitize input
+ // Validate rrset before adding it to the zone. If something is wrong
+ // it throws an exception. It doesn't modify the zone, and provides
+ // the strong exception guarantee.
+ void addValidation(const ConstRRsetPtr rrset) {
if (!rrset) {
isc_throw(NullRRset, "The rrset provided is NULL");
}
@@ -136,26 +176,55 @@ struct MemoryZone::MemoryZoneImpl {
<< rrset->getName());
}
- Name name(rrset->getName());
- NameComparisonResult compare(origin_.compare(name));
+ NameComparisonResult compare(origin_.compare(rrset->getName()));
if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN &&
compare.getRelation() != NameComparisonResult::EQUAL)
{
- isc_throw(OutOfZone, "The name " << name <<
+ isc_throw(OutOfZone, "The name " << rrset->getName() <<
" is not contained in zone " << origin_);
}
+
+ // Some RR types do not really work well with a wildcard.
+ // Even though the protocol specifically doesn't completely ban such
+ // usage, we refuse to load a zone containing such RR in order to
+ // keep the lookup logic simpler and more predictable.
+ // See RFC4592 and (for DNAME) draft-ietf-dnsext-rfc2672bis-dname
+ // for more technical background. Note also that BIND 9 refuses
+ // NS at a wildcard, so in that sense we simply provide compatible
+ // behavior.
+ if (rrset->getName().isWildcard()) {
+ if (rrset->getType() == RRType::NS()) {
+ isc_throw(AddError, "Invalid NS owner name (wildcard): " <<
+ rrset->getName());
+ }
+ if (rrset->getType() == RRType::DNAME()) {
+ isc_throw(AddError, "Invalid DNAME owner name (wildcard): " <<
+ rrset->getName());
+ }
+ }
+ }
+
+ /*
+ * Implementation of longer methods. We put them here, because the
+ * access is without the impl_-> and it will get inlined anyway.
+ */
+ // Implementation of MemoryZone::add
+ result::Result add(const ConstRRsetPtr& rrset, DomainTree* domains) {
+ // Sanitize input
+ addValidation(rrset);
+
+ // Add wildcards possibly contained in the owner name to the domain
+ // tree.
+ // Note: this can throw an exception, breaking strong exception
+ // guarantee. (see also the note for contextCheck() below).
+ addWildcards(*domains, rrset->getName());
+
// Get the node
DomainNode* node;
- switch (domains->insert(name, &node)) {
- // Just check it returns reasonable results
- case DomainTree::SUCCESS:
- case DomainTree::ALREADYEXISTS:
- break;
- // Something odd got out
- default:
- assert(0);
- }
- assert(node != NULL);
+ DomainTree::Result result = domains->insert(rrset->getName(), &node);
+ // Just check it returns reasonable results
+ assert((result == DomainTree::SUCCESS ||
+ result == DomainTree::ALREADYEXISTS) && node!= NULL);
// Now get the domain
DomainPtr domain;
@@ -182,10 +251,10 @@ struct MemoryZone::MemoryZoneImpl {
// indicating the need for callback in find().
if (rrset->getType() == RRType::NS() &&
rrset->getName() != origin_) {
- node->enableCallback();
+ node->setFlag(DomainNode::FLAG_CALLBACK);
// If it is DNAME, we have a callback as well here
} else if (rrset->getType() == RRType::DNAME()) {
- node->enableCallback();
+ node->setFlag(DomainNode::FLAG_CALLBACK);
}
return (result::SUCCESS);
@@ -282,6 +351,35 @@ struct MemoryZone::MemoryZoneImpl {
return (false);
}
+ /*
+ * Prepares a rrset to be return as a result.
+ *
+ * If rename is false, it returns the one provided. If it is true, it
+ * creates a new rrset with the same data but with provided name.
+ * It is designed for wildcard case, where we create the rrsets
+ * dynamically.
+ */
+ static ConstRRsetPtr prepareRRset(const Name& name, const ConstRRsetPtr&
+ rrset, bool rename)
+ {
+ if (rename) {
+ /*
+ * We lose a signature here. But it would be wrong anyway, because
+ * the name changed. This might turn out to be unimportant in
+ * future, because wildcards will probably be handled somehow
+ * by DNSSEC.
+ */
+ RRsetPtr result(new RRset(name, rrset->getClass(),
+ rrset->getType(), rrset->getTTL()));
+ for (RdataIteratorPtr i(rrset->getRdataIterator()); !i->isLast();
+ i->next()) {
+ result->addRdata(i->getCurrent());
+ }
+ return (result);
+ } else {
+ return (rrset);
+ }
+ }
// Implementation of MemoryZone::find
FindResult find(const Name& name, RRType type,
@@ -291,6 +389,7 @@ struct MemoryZone::MemoryZoneImpl {
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:
/*
@@ -314,15 +413,77 @@ struct MemoryZone::MemoryZoneImpl {
if (state.dname_node_ != NULL) {
// We were traversing a DNAME node (and wanted to go
// lower below it), so return the DNAME
- return (FindResult(DNAME, state.rrset_));
+ return (FindResult(DNAME, prepareRRset(name, state.rrset_,
+ rename)));
}
if (state.zonecut_node_ != NULL) {
- return (FindResult(DELEGATION, state.rrset_));
+ return (FindResult(DELEGATION, prepareRRset(name,
+ state.rrset_, rename)));
}
- // TODO: we should also cover empty non-terminal cases, which
- // will require non trivial code and is deferred for later
- // development. For now, we regard any partial match that
- // didn't hit a zone cut as "not found".
+
+ // If the RBTree search stopped at a node for a super domain
+ // of the search name, it means the search name exists in
+ // the zone but is empty. Treat it as NXRRSET.
+ if (node_path.getLastComparisonResult().getRelation() ==
+ NameComparisonResult::SUPERDOMAIN) {
+ return (FindResult(NXRRSET, ConstRRsetPtr()));
+ }
+
+ /*
+ * No redirection anywhere. Let's try if it is a wildcard.
+ *
+ * The wildcard is checked after the empty non-terminal domain
+ * case above, because if that one triggers, it means we should
+ * not match according to 4.3.3 of RFC 1034 (the query name
+ * is known to exist).
+ */
+ if (node->getFlag(DOMAINFLAG_WILD)) {
+ /* Should we cancel this match?
+ *
+ * If we compare with some node and get a common ancestor,
+ * it might mean we are comparing with a non-wildcard node.
+ * In that case, we check which part is common. If we have
+ * something in common that lives below the node we got
+ * (the one above *), then we should cancel the match
+ * according to section 4.3.3 of RFC 1034 (as the name
+ * between the wildcard domain and the query name is known
+ * to exist).
+ *
+ * Because the way the tree stores relative names, we will
+ * have exactly one common label (the ".") in case we have
+ * nothing common under the node we got and we will get
+ * more common labels otherwise (yes, this relies on the
+ * internal RBTree structure, which leaks out through this
+ * little bit).
+ *
+ * If the empty non-terminal node actually exists in the
+ * tree, then this cancellation is not needed, because we
+ * will not get here at all.
+ */
+ if (node_path.getLastComparisonResult().getRelation() ==
+ NameComparisonResult::COMMONANCESTOR && node_path.
+ getLastComparisonResult().getCommonLabels() > 1) {
+ return (FindResult(NXDOMAIN, ConstRRsetPtr()));
+ }
+ Name wildcard(Name("*").concatenate(
+ node_path.getAbsoluteName()));
+ DomainTree::Result result(domains_.find(wildcard, &node));
+ /*
+ * Otherwise, why would the DOMAINFLAG_WILD be there if
+ * there was no wildcard under it?
+ */
+ assert(result == DomainTree::EXACTMATCH);
+ /*
+ * We have the wildcard node now. Jump below the switch,
+ * where handling of the common (exact-match) case is.
+ *
+ * However, rename it to the searched name.
+ */
+ rename = true;
+ break;
+ }
+
+ // fall through
case DomainTree::NOTFOUND:
return (FindResult(NXDOMAIN, ConstRRsetPtr()));
case DomainTree::EXACTMATCH: // This one is OK, handle it
@@ -330,17 +491,23 @@ struct MemoryZone::MemoryZoneImpl {
default:
assert(0);
}
- assert(node);
- assert(!node->isEmpty());
+ assert(node != NULL);
+
+ // If there is an exact match but the node is empty, it's equivalent
+ // to NXRRSET.
+ if (node->isEmpty()) {
+ return (FindResult(NXRRSET, ConstRRsetPtr()));
+ }
Domain::const_iterator found;
// If the node callback is enabled, this may be a zone cut. If it
// has a NS RR, we should return a delegation, but not in the apex.
- if (node->isCallbackEnabled() && node != origin_data_) {
+ if (node->getFlag(DomainNode::FLAG_CALLBACK) && node != origin_data_) {
found = node->getData()->find(RRType::NS());
if (found != node->getData()->end()) {
- return (FindResult(DELEGATION, found->second));
+ return (FindResult(DELEGATION, prepareRRset(name,
+ found->second, rename)));
}
}
@@ -348,10 +515,11 @@ struct MemoryZone::MemoryZoneImpl {
if (target != NULL && !node->getData()->empty()) {
// Empty domain will be handled as NXRRSET by normal processing
for (found = node->getData()->begin();
- found != node->getData()->end(); found++)
+ found != node->getData()->end(); ++found)
{
target->addRRset(
- boost::const_pointer_cast<RRset>(found->second));
+ boost::const_pointer_cast<RRset>(prepareRRset(name,
+ found->second, rename)));
}
return (FindResult(SUCCESS, ConstRRsetPtr()));
}
@@ -359,12 +527,14 @@ struct MemoryZone::MemoryZoneImpl {
found = node->getData()->find(type);
if (found != node->getData()->end()) {
// Good, it is here
- return (FindResult(SUCCESS, found->second));
+ return (FindResult(SUCCESS, prepareRRset(name, found->second,
+ rename)));
} else {
// Next, try CNAME.
found = node->getData()->find(RRType::CNAME());
if (found != node->getData()->end()) {
- return (FindResult(CNAME, found->second));
+ return (FindResult(CNAME, prepareRRset(name, found->second,
+ rename)));
}
}
// No exact match or CNAME. Return NXRRSET.
diff --git a/src/lib/datasrc/rbtree.h b/src/lib/datasrc/rbtree.h
index cf413e8..03a6967 100644
--- a/src/lib/datasrc/rbtree.h
+++ b/src/lib/datasrc/rbtree.h
@@ -23,6 +23,8 @@
/// issue, the design and interface are not fixed, and RBTree isn't ready
/// to be used as a base data structure by other modules.
+#include <exceptions/exceptions.h>
+
#include <dns/name.h>
#include <boost/utility.hpp>
#include <boost/shared_ptr.hpp>
@@ -110,6 +112,29 @@ public:
/// \brief Alias for shared pointer to the data.
typedef boost::shared_ptr<T> NodeDataPtr;
+ /// Node flags.
+ ///
+ /// Each flag value defines a non default property for a specific node.
+ /// These are defined as bitmask type values for the convenience of
+ /// internal implementation, but applications are expected to use
+ /// each flag separately via the enum definitions.
+ ///
+ /// All (settable) flags are off by default; they must be explicitly
+ /// set to on by the \c setFlag() method.
+ enum Flags {
+ FLAG_CALLBACK = 1, ///< Callback enabled. See \ref callback
+ FLAG_USER1 = 0x80000000U ///< Application specific flag
+ };
+private:
+ // Some flag values are expected to be used for internal purposes
+ // (e.g., representing the node color) in future versions, so we
+ // limit the settable flags via the \c setFlag() method to those
+ // explicitly defined in \c Flags. This constant represents all
+ // such flags.
+ static const uint32_t SETTABLE_FLAGS = (FLAG_CALLBACK | FLAG_USER1);
+
+public:
+
/// \brief Destructor
///
/// It might seem strange that constructors are private and destructor
@@ -153,6 +178,52 @@ public:
void setData(const NodeDataPtr& data) { data_ = data; }
//@}
+ /// \name Node flag manipulation methods
+ //@{
+ /// Get the status of a node flag.
+ ///
+ /// This method returns whether the given node flag is set (enabled)
+ /// on the node. The \c flag parameter is expected to be one of the
+ /// defined \c Flags constants. For simplicity, the method interface
+ /// does not prohibit passing an undefined flag or combined flags, but
+ /// the return value in such a case will be meaningless for the caller
+ /// (an application would have to use an ugly cast for such an unintended
+ /// form of call, which will hopefully avoid accidental misuse).
+ ///
+ /// \exception None
+ /// \param flag The flag to be tested.
+ /// \return \c true if the \c flag is set; \c false otherwise.
+ bool getFlag(Flags flag) const {
+ return ((flags_ & flag) != 0);
+ }
+
+ /// Set or clear a node flag.
+ ///
+ /// This method changes the status of the specified node flag to either
+ /// "on" (enabled) or "off" (disabled). The new status is specified by
+ /// the \c on parameter.
+ /// Like the \c getFlag() method, \c flag is expected to be one of the
+ /// defined \c Flags constants. If an undefined or unsettable flag is
+ /// specified, \c isc::InvalidParameter exception will be thrown.
+ ///
+ /// \exception isc::InvalidParameter Unsettable flag is specified
+ /// \exception None otherwise
+ /// \param flag The node flag to be changed.
+ /// \on If \c true, set the flag to on; otherwise set it to off.
+ void setFlag(Flags flag, bool on = true) {
+ if ((flag & ~SETTABLE_FLAGS) != 0) {
+ isc_throw(isc::InvalidParameter,
+ "Unsettable RBTree flag is being set");
+ }
+ if (on) {
+ flags_ |= flag;
+ } else {
+ flags_ &= ~flag;
+ }
+ }
+ //@}
+
+private:
/// \name Callback related methods
///
/// See the description of \c RBTree<T>::find() about callbacks.
@@ -160,16 +231,8 @@ public:
/// These methods never throw an exception.
//@{
/// Return if callback is enabled at the node.
- bool isCallbackEnabled() const { return (callback_required_); }
-
- /// Enable callback at the node.
- void enableCallback() { callback_required_ = true; }
-
- /// Disable callback at the node.
- void disableCallback() { callback_required_ = false; }
//@}
-
private:
/// \brief Define rbnode color
enum RBNodeColor {BLACK, RED};
@@ -224,7 +287,7 @@ private:
/// RBTree::find().
///
/// \todo It might be needed to put it into more general attributes field.
- bool callback_required_;
+ uint32_t flags_;
};
@@ -238,7 +301,7 @@ RBNode<T>::RBNode() :
// dummy name, the value doesn't matter:
name_(isc::dns::Name::ROOT_NAME()),
down_(this),
- callback_required_(false)
+ flags_(0)
{
}
@@ -250,7 +313,7 @@ RBNode<T>::RBNode(const isc::dns::Name& name) :
color_(RED),
name_(name),
down_(NULL_NODE()),
- callback_required_(false)
+ flags_(0)
{
}
@@ -286,8 +349,19 @@ RBNode<T>::successor() const {
}
-/// \brief RBTreeNodeChain is used to keep track of the sequence of
-/// nodes to reach any given node from the root of RBTree.
+/// \brief RBTreeNodeChain stores detailed information of \c RBTree::find()
+/// result.
+///
+/// - The \c RBNode that was last compared with the search name, and
+/// the comparison result at that point in the form of
+/// \c isc::dns::NameComparisonResult.
+/// - A sequence of nodes that forms a path to the found node (which is
+/// not yet implemented).
+///
+/// The comparison result can be used to handle some rare cases such as
+/// empty node processing.
+/// The node sequence keeps track of the nodes to reach any given node from
+/// the root of RBTree.
///
/// Currently, RBNode does not have "up" pointers in them (i.e., back pointers
/// from the root of one level of tree of trees to the node in the parent
@@ -299,6 +373,10 @@ RBNode<T>::successor() const {
/// quite likely we want to have that pointer if we want to optimize name
/// compression by exploiting the structure of the zone. If and when that
/// happens we should also revisit the need for the chaining.
+/// Also, the class name may not be appropriate now that it contains other
+/// information than a node "chain", and the chain itself may even be
+/// deprecated. Something like "RBTreeFindContext" may be a better name.
+/// This point should be revisited later.
///
/// RBTreeNodeChain is constructed and manipulated only inside the \c RBTree
/// class.
@@ -323,7 +401,11 @@ public:
/// The default constructor.
///
/// \exception None
- RBTreeNodeChain() : node_count_(0) {}
+ RBTreeNodeChain() : node_count_(0), last_compared_(NULL),
+ // XXX: meaningless initial values:
+ last_comparison_(0, 0,
+ isc::dns::NameComparisonResult::EQUAL)
+ {}
private:
RBTreeNodeChain(const RBTreeNodeChain<T>&);
@@ -331,6 +413,46 @@ private:
//@}
public:
+ /// Clear the state of the chain.
+ ///
+ /// This method re-initializes the internal state of the chain so that
+ /// it can be reused for subsequent operations.
+ ///
+ /// \exception None
+ void clear() {
+ node_count_ = 0;
+ last_compared_ = NULL;
+ }
+
+ /// Return the \c RBNode that was last compared in \c RBTree::find().
+ ///
+ /// If this chain has been passed to \c RBTree::find() and there has
+ /// been name comparison against the search name, the last compared
+ /// \c RBNode is recorded within the chain. This method returns that
+ /// node.
+ /// If \c RBTree::find() hasn't been called with this chain or name
+ /// comparison hasn't taken place (which is possible if the tree is empty),
+ /// this method returns \c NULL.
+ ///
+ /// \exception None
+ const RBNode<T>* getLastComparedNode() const {
+ return (last_compared_);
+ }
+
+ /// Return the result of last name comparison in \c RBTree::find().
+ ///
+ /// Like \c getLastComparedNode(), \c RBTree::find() records the result
+ /// of the last name comparison in the chain. This method returns the
+ /// result.
+ /// The return value of this method is only meaningful when comparison
+ /// has taken place, i.e, when \c getLastComparedNode() would return a
+ /// non \c NULL value.
+ ///
+ /// \exception None
+ const isc::dns::NameComparisonResult& getLastComparisonResult() const {
+ return (last_comparison_);
+ }
+
/// \brief Return the number of levels stored in the chain.
///
/// It's equal to the number of nodes in the chain; for an empty
@@ -411,15 +533,14 @@ private:
private:
// The max label count for one domain name is Name::MAX_LABELS (128).
- // Since each node in rbtree stores at least one label, and the root
- // name always shares the same level with some others (which means
- // all top level nodes except the one for the root name contain at least
- // two labels), the possible maximum level is MAX_LABELS - 1.
- // It's also the possible maximum nodes stored in a chain.
- const static int RBT_MAX_LEVEL = isc::dns::Name::MAX_LABELS - 1;
+ // Since each node in rbtree stores at least one label, it's also equal
+ // to the possible maximum level.
+ const static int RBT_MAX_LEVEL = isc::dns::Name::MAX_LABELS;
- const RBNode<T>* nodes_[RBT_MAX_LEVEL];
int node_count_;
+ const RBNode<T>* nodes_[RBT_MAX_LEVEL];
+ const RBNode<T>* last_compared_;
+ isc::dns::NameComparisonResult last_comparison_;
};
@@ -589,7 +710,7 @@ public:
///
/// This version of \c find() calls the callback whenever traversing (on
/// the way from root down the tree) a marked node on the way down through
- /// the domain namespace (see RBNode::enableCallback and related
+ /// the domain namespace (see \c RBNode::enableCallback and related
/// functions).
///
/// If you return true from the callback, the search is stopped and a
@@ -603,12 +724,18 @@ public:
/// The callbacks are not general functors for the same reason - we don't
/// expect it to be needed.
///
- /// Another special feature of this version is the ability to provide
- /// a node chain containing a path to the found node. The chain will be
- /// returned via the \c node_path parameter.
+ /// Another special feature of this version is the ability to record
+ /// more detailed information regarding the search result.
+ ///
+ /// This information will be returned via the \c node_path parameter,
+ /// which is an object of class \c RBTreeNodeChain.
/// The passed parameter must be empty.
- /// On success, it will contain all the ancestor nodes from the found
- /// node towards the root.
+ ///
+ /// \note The rest of the description isn't yet implemented. It will be
+ /// handled in Trac ticket #517.
+ ///
+ /// On success, the node sequence stoed in \c node_path will contain all
+ /// the ancestor nodes from the found node towards the root.
/// For example, if we look for o.w.y.d.e.f in the example \ref diagram,
/// \c node_path will contain w.y and d.e.f; the \c top() node of the
/// chain will be o, w.f and d.e.f will be stored below it.
@@ -622,13 +749,13 @@ public:
/// node of a given node in the entire RBTree; the \c nextNode() method
/// takes a node chain as a parameter.
///
- /// \exception isc::BadValue node_path is not empty.
+ /// \exception isc::BadValue node_path is not empty (not yet implemented).
///
/// \param name Target to be found
/// \param node On success (either \c EXACTMATCH or \c PARTIALMATCH)
/// it will store a pointer to the matching node
- /// \param node_path It will store all the ancestor nodes in the RBTree
- /// from the found node to the root. The found node is stored.
+ /// \param node_path Other search details will be stored (see the
+ /// description)
/// \param callback If non \c NULL, a call back function to be called
/// at marked nodes (see above).
/// \param callback_arg A caller supplied argument to be passed to
@@ -853,10 +980,11 @@ RBTree<T>::find(const isc::dns::Name& target_name,
isc::dns::Name name = target_name;
while (node != NULLNODE) {
- const isc::dns::NameComparisonResult compare_result =
- name.compare(node->name_);
+ node_path.last_compared_ = node;
+ node_path.last_comparison_ = name.compare(node->name_);
const isc::dns::NameComparisonResult::NameRelation relation =
- compare_result.getRelation();
+ node_path.last_comparison_.getRelation();
+
if (relation == isc::dns::NameComparisonResult::EQUAL) {
if (needsReturnEmptyNode_ || !node->isEmpty()) {
node_path.push(node);
@@ -865,17 +993,26 @@ RBTree<T>::find(const isc::dns::Name& target_name,
}
break;
} else {
- const int common_label_count = compare_result.getCommonLabels();
+ const int common_label_count =
+ node_path.last_comparison_.getCommonLabels();
// If the common label count is 1, there is no common label between
- // the two names, except the trailing "dot".
- if (common_label_count == 1) {
- node = (compare_result.getOrder() < 0) ?
+ // the two names, except the trailing "dot". In this case the two
+ // sequences of labels have essentially no hierarchical
+ // relationship in terms of matching, so we should continue the
+ // binary search. One important exception is when the node
+ // represents the root name ("."), in which case the comparison
+ // result must indeed be considered subdomain matching. (We use
+ // getLength() to check if the name is root, which is an equivalent
+ // but cheaper way).
+ if (common_label_count == 1 && node->name_.getLength() != 1) {
+ node = (node_path.last_comparison_.getOrder() < 0) ?
node->left_ : node->right_;
} else if (relation == isc::dns::NameComparisonResult::SUBDOMAIN) {
if (needsReturnEmptyNode_ || !node->isEmpty()) {
ret = PARTIALMATCH;
*target = node;
- if (callback != NULL && node->callback_required_) {
+ if (callback != NULL &&
+ node->getFlag(RBNode<T>::FLAG_CALLBACK)) {
if ((callback)(*node, callback_arg)) {
break;
}
@@ -960,7 +1097,8 @@ RBTree<T>::insert(const isc::dns::Name& target_name, RBNode<T>** new_node) {
return (ALREADYEXISTS);
} else {
const int common_label_count = compare_result.getCommonLabels();
- if (common_label_count == 1) {
+ // Note: see find() for the check of getLength().
+ if (common_label_count == 1 && current->name_.getLength() != 1) {
parent = current;
order = compare_result.getOrder();
current = order < 0 ? current->left_ : current->right_;
@@ -1027,7 +1165,7 @@ RBTree<T>::nodeFission(RBNode<T>& node, const isc::dns::Name& base_name) {
// consistent behavior (i.e., a weak form of strong exception guarantee)
// even if code after the call to this function throws an exception.
std::swap(node.data_, down_node->data_);
- std::swap(node.callback_required_, down_node->callback_required_);
+ std::swap(node.flags_, down_node->flags_);
down_node->down_ = node.down_;
node.down_ = down_node.get();
// root node of sub tree, the initial color is BLACK
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 3c36e2d..f09b4b7 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -31,6 +31,7 @@ run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
run_unittests_LDADD = $(GTEST_LDADD)
run_unittests_LDADD += $(SQLITE_LIBS)
+run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
run_unittests_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
diff --git a/src/lib/datasrc/tests/datasrc_unittest.cc b/src/lib/datasrc/tests/datasrc_unittest.cc
index bff7949..ab5cd85 100644
--- a/src/lib/datasrc/tests/datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/datasrc_unittest.cc
@@ -38,6 +38,7 @@
#include <datasrc/sqlite3_datasrc.h>
#include <datasrc/static_datasrc.h>
+#include <testutils/dnsmessage_test.h>
#include <dns/tests/unittest_util.h>
#include <datasrc/tests/test_datasrc.h>
@@ -47,6 +48,7 @@ using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::datasrc;
using namespace isc::data;
+using namespace isc::testutils;
namespace {
ConstElementPtr SQLITE_DBFILE_EXAMPLE = Element::fromJSON(
@@ -54,7 +56,9 @@ ConstElementPtr SQLITE_DBFILE_EXAMPLE = Element::fromJSON(
class DataSrcTest : public ::testing::Test {
protected:
- DataSrcTest() : obuffer(0), renderer(obuffer), msg(Message::PARSE) {
+ DataSrcTest() : obuffer(0), renderer(obuffer), msg(Message::PARSE),
+ opcodeval(Opcode::QUERY().getCode()), qid(0)
+ {
DataSrcPtr sql3_source = DataSrcPtr(new Sqlite3DataSrc);
sql3_source->init(SQLITE_DBFILE_EXAMPLE);
DataSrcPtr test_source = DataSrcPtr(new TestDataSrc);
@@ -73,6 +77,8 @@ protected:
OutputBuffer obuffer;
MessageRenderer renderer;
Message msg;
+ const uint16_t opcodeval;
+ qid_t qid;
};
void
@@ -91,29 +97,16 @@ DataSrcTest::createAndProcessQuery(const Name& qname, const RRClass& qclass,
msg.setOpcode(Opcode::QUERY());
msg.addQuestion(Question(qname, qclass, qtype));
msg.setHeaderFlag(Message::HEADERFLAG_RD);
+ qid = msg.getQid();
performQuery(meta_source, cache, msg);
}
void
-headerCheck(const Message& message, const Rcode& rcode, const bool qrflag,
- const bool aaflag, const bool rdflag, const unsigned int ancount,
- const unsigned int nscount, const unsigned int arcount)
-{
- EXPECT_EQ(rcode, message.getRcode());
- EXPECT_EQ(qrflag, message.getHeaderFlag(Message::HEADERFLAG_QR));
- EXPECT_EQ(aaflag, message.getHeaderFlag(Message::HEADERFLAG_AA));
- EXPECT_EQ(rdflag, message.getHeaderFlag(Message::HEADERFLAG_RD));
-
- EXPECT_EQ(ancount, message.getRRCount(Message::SECTION_ANSWER));
- EXPECT_EQ(nscount, message.getRRCount(Message::SECTION_AUTHORITY));
- EXPECT_EQ(arcount, message.getRRCount(Message::SECTION_ADDITIONAL));
-}
-
-void
DataSrcTest::QueryCommon(const RRClass& qclass) {
createAndProcessQuery(Name("www.example.com"), qclass, RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 2, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 2, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -163,12 +156,8 @@ TEST_F(DataSrcTest, Query) {
// should be the same as "NxZone".
TEST_F(DataSrcTest, QueryClassMismatch) {
createAndProcessQuery(Name("www.example.com"), RRClass::CH(), RRType::A());
- headerCheck(msg, Rcode::REFUSED(), true, false, true, 0, 0, 0);
-
- EXPECT_EQ(Rcode::REFUSED(), msg.getRcode());
- EXPECT_TRUE(msg.getHeaderFlag(Message::HEADERFLAG_QR));
- EXPECT_FALSE(msg.getHeaderFlag(Message::HEADERFLAG_AA));
- EXPECT_TRUE(msg.getHeaderFlag(Message::HEADERFLAG_RD));
+ headerCheck(msg, qid, Rcode::REFUSED(), opcodeval, QR_FLAG | RD_FLAG,
+ 1, 0, 0, 0);
}
// Query class of any should match the first data source.
@@ -179,7 +168,8 @@ TEST_F(DataSrcTest, QueryClassAny) {
TEST_F(DataSrcTest, NSQuery) {
createAndProcessQuery(Name("example.com"), RRClass::IN(),
RRType::NS());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 0, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 4, 0, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -201,7 +191,8 @@ TEST_F(DataSrcTest, NSQuery) {
TEST_F(DataSrcTest, DuplicateQuery) {
createAndProcessQuery(Name("example.com"), RRClass::IN(),
RRType::NS());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 0, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 4, 0, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -221,7 +212,8 @@ TEST_F(DataSrcTest, DuplicateQuery) {
msg.clear(Message::PARSE);
createAndProcessQuery(Name("example.com"), RRClass::IN(),
RRType::NS());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 0, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 4, 0, 6);
rit = msg.beginSection(Message::SECTION_ANSWER);
rrset = *rit;
@@ -242,7 +234,8 @@ TEST_F(DataSrcTest, DuplicateQuery) {
TEST_F(DataSrcTest, DNSKEYQuery) {
createAndProcessQuery(Name("example.com"), RRClass::IN(),
RRType::DNSKEY());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 4, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -257,7 +250,8 @@ TEST_F(DataSrcTest, DNSKEYQuery) {
TEST_F(DataSrcTest, DNSKEYDuplicateQuery) {
createAndProcessQuery(Name("example.com"), RRClass::IN(),
RRType::DNSKEY());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 4, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -279,7 +273,8 @@ TEST_F(DataSrcTest, NxRRset) {
createAndProcessQuery(Name("example.com"), RRClass::IN(),
RRType::PTR());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 0, 4, 0);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 4, 0);
RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
@@ -291,7 +286,8 @@ TEST_F(DataSrcTest, Nxdomain) {
createAndProcessQuery(Name("glork.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NXDOMAIN(), true, true, true, 0, 6, 0);
+ headerCheck(msg, qid, Rcode::NXDOMAIN(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 6, 0);
RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
@@ -301,11 +297,46 @@ TEST_F(DataSrcTest, Nxdomain) {
// XXX: check for other authority section answers
}
+TEST_F(DataSrcTest, NxdomainAfterSOAQuery) {
+ // There was a bug where once SOA RR is stored in the hot spot cache
+ // subsequent negative search fails due to "missing SOA". This test
+ // checks that situation.
+
+ // First, run the scenario with disabling the cache.
+ cache.setEnabled(false);
+ createAndProcessQuery(Name("example.com"), RRClass::IN(),
+ RRType::SOA());
+ msg.clear(Message::PARSE);
+ createAndProcessQuery(Name("notexistent.example.com"), RRClass::IN(),
+ RRType::A());
+ {
+ SCOPED_TRACE("NXDOMAIN after SOA, without hot spot cache");
+ headerCheck(msg, qid, Rcode::NXDOMAIN(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 6, 0);
+ }
+
+ // Then enable the cache and perform the same queries. This should
+ // produce the same result.
+ cache.setEnabled(true);
+ msg.clear(Message::PARSE);
+ createAndProcessQuery(Name("example.com"), RRClass::IN(),
+ RRType::SOA());
+ msg.clear(Message::PARSE);
+ createAndProcessQuery(Name("notexistent.example.com"), RRClass::IN(),
+ RRType::A());
+ {
+ SCOPED_TRACE("NXDOMAIN after SOA, without hot spot cache");
+ headerCheck(msg, qid, Rcode::NXDOMAIN(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 6, 0);
+ }
+}
+
TEST_F(DataSrcTest, NxZone) {
createAndProcessQuery(Name("spork.example"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::REFUSED(), true, false, true, 0, 0, 0);
+ headerCheck(msg, qid, Rcode::REFUSED(), opcodeval,
+ QR_FLAG | RD_FLAG, 1, 0, 0, 0);
EXPECT_EQ(Rcode::REFUSED(), msg.getRcode());
EXPECT_TRUE(msg.getHeaderFlag(Message::HEADERFLAG_QR));
@@ -317,7 +348,8 @@ TEST_F(DataSrcTest, Wildcard) {
createAndProcessQuery(Name("www.wild.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 2, 6, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 2, 6, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -369,7 +401,8 @@ TEST_F(DataSrcTest, WildcardNodata) {
// returns NOERROR
createAndProcessQuery(Name("www.wild.example.com"), RRClass::IN(),
RRType::AAAA());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 0, 2, 0);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 2, 0);
}
TEST_F(DataSrcTest, DISABLED_WildcardAgainstMultiLabel) {
@@ -377,7 +410,8 @@ TEST_F(DataSrcTest, DISABLED_WildcardAgainstMultiLabel) {
// a single label), and it should result in NXDOMAIN.
createAndProcessQuery(Name("www.xxx.wild.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NXDOMAIN(), true, true, true, 0, 1, 0);
+ headerCheck(msg, qid, Rcode::NXDOMAIN(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 1, 0);
}
TEST_F(DataSrcTest, WildcardCname) {
@@ -386,7 +420,8 @@ TEST_F(DataSrcTest, WildcardCname) {
createAndProcessQuery(Name("www.wild2.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 6, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 4, 6, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -450,7 +485,8 @@ TEST_F(DataSrcTest, WildcardCnameNodata) {
// data of this type.
createAndProcessQuery(Name("www.wild2.example.com"), RRClass::IN(),
RRType::AAAA());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 2, 4, 0);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 2, 4, 0);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -481,7 +517,8 @@ TEST_F(DataSrcTest, WildcardCnameNxdomain) {
// A wildcard containing a CNAME whose target does not exist
createAndProcessQuery(Name("www.wild3.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 2, 6, 0);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 2, 6, 0);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -518,7 +555,8 @@ TEST_F(DataSrcTest, AuthDelegation) {
createAndProcessQuery(Name("www.sql1.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 2, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 2, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -562,7 +600,8 @@ TEST_F(DataSrcTest, Dname) {
createAndProcessQuery(Name("www.dname.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 5, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 5, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -610,14 +649,16 @@ TEST_F(DataSrcTest, DnameExact) {
// confuse delegation processing.
createAndProcessQuery(Name("dname2.foo.example.org"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 0, 1, 0);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 1, 0);
}
TEST_F(DataSrcTest, Cname) {
createAndProcessQuery(Name("foo.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 2, 0, 0);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 2, 0, 0);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -635,7 +676,8 @@ TEST_F(DataSrcTest, CnameInt) {
createAndProcessQuery(Name("cname-int.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 4, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -661,7 +703,8 @@ TEST_F(DataSrcTest, CnameExt) {
createAndProcessQuery(Name("cname-ext.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 4, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -685,7 +728,8 @@ TEST_F(DataSrcTest, Delegation) {
createAndProcessQuery(Name("www.subzone.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, false, true, 0, 5, 2);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | RD_FLAG, 1, 0, 5, 2);
RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
@@ -714,7 +758,8 @@ TEST_F(DataSrcTest, NSDelegation) {
createAndProcessQuery(Name("subzone.example.com"), RRClass::IN(),
RRType::NS());
- headerCheck(msg, Rcode::NOERROR(), true, false, true, 0, 5, 2);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | RD_FLAG, 1, 0, 5, 2);
RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
@@ -750,7 +795,8 @@ TEST_F(DataSrcTest, NSECZonecut) {
createAndProcessQuery(Name("subzone.example.com"), RRClass::IN(),
RRType::NSEC());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 2, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 2, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -778,7 +824,8 @@ TEST_F(DataSrcTest, DNAMEZonecut) {
createAndProcessQuery(Name("subzone.example.com"), RRClass::IN(),
RRType::DNAME());
- headerCheck(msg, Rcode::NOERROR(), true, false, true, 0, 5, 2);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | RD_FLAG, 1, 0, 5, 2);
RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("subzone.example.com."), rrset->getName());
@@ -806,7 +853,8 @@ TEST_F(DataSrcTest, DS) {
createAndProcessQuery(Name("subzone.example.com"), RRClass::IN(),
RRType::DS());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 3, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 3, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -847,7 +895,8 @@ TEST_F(DataSrcTest, NSECZonecutOfNonsecureZone) {
createAndProcessQuery(Name("sub.example.org"), RRClass::IN(),
RRType::NSEC());
- headerCheck(msg, Rcode::NOERROR(), true, false, true, 0, 1, 1);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | RD_FLAG, 1, 0, 1, 1);
RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
ConstRRsetPtr rrset = *rit;
@@ -879,7 +928,8 @@ TEST_F(DataSrcTest, NSECZonecutOfNonsecureZone) {
TEST_F(DataSrcTest, RootDSQuery1) {
EXPECT_NO_THROW(createAndProcessQuery(Name("."), RRClass::IN(),
RRType::DS()));
- headerCheck(msg, Rcode::REFUSED(), true, false, true, 0, 0, 0);
+ headerCheck(msg, qid, Rcode::REFUSED(), opcodeval,
+ QR_FLAG | RD_FLAG, 1, 0, 0, 0);
}
// The same, but when we have the root zone
@@ -898,7 +948,8 @@ TEST_F(DataSrcTest, RootDSQuery2) {
// Make the query
EXPECT_NO_THROW(performQuery(*sql3_source, cache, msg));
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 0, 1, 0);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 1, 0);
}
TEST_F(DataSrcTest, DSQueryFromCache) {
@@ -916,7 +967,8 @@ TEST_F(DataSrcTest, DSQueryFromCache) {
// returning refused is probably a bad behavior, but it's a different
// issue -- see Trac Ticket #306.
- headerCheck(msg, Rcode::REFUSED(), true, false, true, 0, 0, 0);
+ headerCheck(msg, qid, Rcode::REFUSED(), opcodeval,
+ QR_FLAG | RD_FLAG, 1, 0, 0, 0);
}
// Non-existent name in the "static" data source. The purpose of this test
@@ -925,7 +977,8 @@ TEST_F(DataSrcTest, DSQueryFromCache) {
TEST_F(DataSrcTest, StaticNxDomain) {
createAndProcessQuery(Name("www.version.bind"), RRClass::CH(),
RRType::TXT());
- headerCheck(msg, Rcode::NXDOMAIN(), true, true, true, 0, 1, 0);
+ headerCheck(msg, qid, Rcode::NXDOMAIN(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 1, 0);
RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("version.bind"), rrset->getName());
@@ -973,6 +1026,15 @@ TEST_F(DataSrcTest, noSOAZone) {
DataSourceError);
}
+TEST_F(DataSrcTest, apexCNAMEZone) {
+ // The query name doesn't exist in the best matching zone,
+ // and there's a CNAME at the apex (which is bogus), so query handling
+ // will fail due to missing SOA.
+ EXPECT_THROW(createAndProcessQuery(Name("notexist.apexcname.example"),
+ RRClass::IN(), RRType::A()),
+ DataSourceError);
+}
+
// currently fails
TEST_F(DataSrcTest, DISABLED_synthesizedCnameTooLong) {
// qname has the possible max length (255 octets). it matches a DNAME,
diff --git a/src/lib/datasrc/tests/memory_datasrc_unittest.cc b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
index be71e7f..16d749c 100644
--- a/src/lib/datasrc/tests/memory_datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
@@ -13,8 +13,14 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <sstream>
+#include <vector>
+
+#include <boost/bind.hpp>
+
#include <exceptions/exceptions.h>
+#include <dns/masterload.h>
#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
@@ -27,6 +33,7 @@
#include <gtest/gtest.h>
+using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::datasrc;
@@ -140,62 +147,91 @@ TEST_F(MemoryDataSrcTest, getZoneCount) {
EXPECT_EQ(2, memory_datasrc.getZoneCount());
}
+// A helper callback of masterLoad() used in MemoryZoneTest.
+void
+setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
+ *(*it) = rrset;
+ ++it;
+}
+
/// \brief Test fixture for the MemoryZone class
class MemoryZoneTest : public ::testing::Test {
+ // A straightforward pair of textual RR(set) and a RRsetPtr variable
+ // to store the RRset. Used to build test data below.
+ struct RRsetData {
+ const char* const text; // textual representation of an RRset
+ RRsetPtr* rrset;
+ };
public:
MemoryZoneTest() :
class_(RRClass::IN()),
origin_("example.org"),
- ns_name_("ns.example.org"),
- cname_name_("cname.example.org"),
- dname_name_("dname.example.org"),
- child_ns_name_("child.example.org"),
- child_glue_name_("ns.child.example.org"),
- grandchild_ns_name_("grand.child.example.org"),
- grandchild_glue_name_("ns.grand.child.example.org"),
- child_dname_name_("dname.child.example.org"),
- zone_(class_, origin_),
- rr_out_(new RRset(Name("example.com"), class_, RRType::A(),
- RRTTL(300))),
- rr_ns_(new RRset(origin_, class_, RRType::NS(), RRTTL(300))),
- rr_ns_a_(new RRset(ns_name_, class_, RRType::A(), RRTTL(300))),
- rr_ns_aaaa_(new RRset(ns_name_, class_, RRType::AAAA(), RRTTL(300))),
- rr_a_(new RRset(origin_, class_, RRType::A(), RRTTL(300))),
- rr_cname_(new RRset(cname_name_, class_, RRType::CNAME(), RRTTL(300))),
- rr_cname_a_(new RRset(cname_name_, class_, RRType::A(), RRTTL(300))),
- rr_dname_(new RRset(dname_name_, class_, RRType::DNAME(), RRTTL(300))),
- rr_dname_a_(new RRset(dname_name_, class_, RRType::A(),
- RRTTL(300))),
- rr_dname_ns_(new RRset(dname_name_, class_, RRType::NS(), RRTTL(300))),
- rr_dname_apex_(new RRset(origin_, class_, RRType::DNAME(),
- RRTTL(300))),
- rr_child_ns_(new RRset(child_ns_name_, class_, RRType::NS(),
- RRTTL(300))),
- rr_child_glue_(new RRset(child_glue_name_, class_, RRType::A(),
- RRTTL(300))),
- rr_grandchild_ns_(new RRset(grandchild_ns_name_, class_, RRType::NS(),
- RRTTL(300))),
- rr_grandchild_glue_(new RRset(grandchild_glue_name_, class_,
- RRType::AAAA(), RRTTL(300))),
- rr_child_dname_(new RRset(child_dname_name_, class_, RRType::DNAME(),
- RRTTL(300)))
+ zone_(class_, origin_)
{
+ // Build test RRsets. Below, we construct an RRset for
+ // each textual RR(s) of zone_data, and assign it to the corresponding
+ // rr_xxx.
+ const RRsetData zone_data[] = {
+ {"example.org. 300 IN NS ns.example.org.", &rr_ns_},
+ {"example.org. 300 IN A 192.0.2.1", &rr_a_},
+ {"ns.example.org. 300 IN A 192.0.2.2", &rr_ns_a_},
+ {"ns.example.org. 300 IN AAAA 2001:db8::2", &rr_ns_aaaa_},
+ {"cname.example.org. 300 IN CNAME canonical.example.org",
+ &rr_cname_},
+ {"cname.example.org. 300 IN A 192.0.2.3", &rr_cname_a_},
+ {"dname.example.org. 300 IN DNAME target.example.org.",
+ &rr_dname_},
+ {"dname.example.org. 300 IN A 192.0.2.39", &rr_dname_a_},
+ {"dname.example.org. 300 IN NS ns.dname.example.org.",
+ &rr_dname_ns_},
+ {"example.org. 300 IN DNAME example.com.", &rr_dname_apex_},
+ {"child.example.org. 300 IN NS ns.child.example.org.",
+ &rr_child_ns_},
+ {"ns.child.example.org. 300 IN A 192.0.2.153",
+ &rr_child_glue_},
+ {"grand.child.example.org. 300 IN NS ns.grand.child.example.org.",
+ &rr_grandchild_ns_},
+ {"ns.grand.child.example.org. 300 IN AAAA 2001:db8::253",
+ &rr_grandchild_glue_},
+ {"dname.child.example.org. 300 IN DNAME example.com.",
+ &rr_child_dname_},
+ {"example.com. 300 IN A 192.0.2.10", &rr_out_},
+ {"*.wild.example.org. 300 IN A 192.0.2.1", &rr_wild_},
+ {"foo.wild.example.org. 300 IN A 192.0.2.3", &rr_under_wild_},
+ {"wild.*.foo.example.org. 300 IN A 192.0.2.1", &rr_emptywild_},
+ {"wild.*.foo.*.bar.example.org. 300 IN A 192.0.2.1",
+ &rr_nested_emptywild_},
+ {"*.nswild.example.org. 300 IN NS nswild.example.", &rr_nswild_},
+ {"*.dnamewild.example.org. 300 IN DNAME dnamewild.example.",
+ &rr_dnamewild_},
+ {"*.child.example.org. 300 IN A 192.0.2.1", &rr_child_wild_},
+ {"bar.foo.wild.example.org. 300 IN A 192.0.2.2", &rr_not_wild_},
+ {"baz.foo.wild.example.org. 300 IN A 192.0.2.3",
+ &rr_not_wild_another_},
+ {NULL, NULL}
+ };
+
+ stringstream zone_data_stream;
+ vector<RRsetPtr*> rrsets;
+ for (unsigned int i = 0; zone_data[i].text != NULL; ++i) {
+ zone_data_stream << zone_data[i].text << "\n";
+ rrsets.push_back(zone_data[i].rrset);
+ }
+
+ vector<RRsetPtr*>::iterator it = rrsets.begin();
+ masterLoad(zone_data_stream, Name::ROOT_NAME(), class_,
+ boost::bind(setRRset, _1, it));
}
// Some data to test with
const RRClass class_;
- const Name origin_, ns_name_, cname_name_, dname_name_, child_ns_name_,
- child_glue_name_, grandchild_ns_name_, grandchild_glue_name_,
- child_dname_name_;
+ const Name origin_;
// The zone to torture by tests
MemoryZone zone_;
/*
* Some RRsets to put inside the zone.
- * They are empty, but the MemoryZone does not have a reason to look
- * inside anyway. We will check it finds them and does not change
- * the pointer.
*/
- ConstRRsetPtr
+ RRsetPtr
// Out of zone RRset
rr_out_,
// NS of example.org
@@ -207,16 +243,24 @@ public:
// A of example.org
rr_a_;
RRsetPtr rr_cname_; // CNAME in example.org (RDATA will be added)
- ConstRRsetPtr rr_cname_a_; // for mixed CNAME + A case
+ RRsetPtr rr_cname_a_; // for mixed CNAME + A case
RRsetPtr rr_dname_; // DNAME in example.org (RDATA will be added)
- ConstRRsetPtr rr_dname_a_; // for mixed DNAME + A case
- ConstRRsetPtr rr_dname_ns_; // for mixed DNAME + NS case
- ConstRRsetPtr rr_dname_apex_; // for mixed DNAME + NS case in the apex
- ConstRRsetPtr rr_child_ns_; // NS of a child domain (for delegation)
- ConstRRsetPtr rr_child_glue_; // glue RR of the child domain
- ConstRRsetPtr rr_grandchild_ns_; // NS below a zone cut (unusual)
- ConstRRsetPtr rr_grandchild_glue_; // glue RR below a deeper zone cut
- ConstRRsetPtr rr_child_dname_; // A DNAME under NS
+ RRsetPtr rr_dname_a_; // for mixed DNAME + A case
+ RRsetPtr rr_dname_ns_; // for mixed DNAME + NS case
+ RRsetPtr rr_dname_apex_; // for mixed DNAME + NS case in the apex
+ RRsetPtr rr_child_ns_; // NS of a child domain (for delegation)
+ RRsetPtr rr_child_glue_; // glue RR of the child domain
+ RRsetPtr rr_grandchild_ns_; // NS below a zone cut (unusual)
+ RRsetPtr rr_grandchild_glue_; // glue RR below a deeper zone cut
+ RRsetPtr rr_child_dname_; // A DNAME under NS
+ RRsetPtr rr_wild_;
+ RRsetPtr rr_emptywild_;
+ RRsetPtr rr_nested_emptywild_;
+ RRsetPtr rr_nswild_, rr_dnamewild_;
+ RRsetPtr rr_child_wild_;
+ RRsetPtr rr_under_wild_;
+ RRsetPtr rr_not_wild_;
+ RRsetPtr rr_not_wild_another_;
/**
* \brief Test one find query to the zone.
@@ -233,13 +277,18 @@ public:
* \param answer The expected rrset, if any should be returned.
* \param zone Check different MemoryZone object than zone_ (if NULL,
* uses zone_)
+ * \param check_wild_answer Checks that the answer has the same RRs, type
+ * class and TTL as the eqxpected answer and that the name corresponds
+ * to the one searched. It is meant for checking answers for wildcard
+ * queries.
*/
void findTest(const Name& name, const RRType& rrtype, Zone::Result result,
bool check_answer = true,
const ConstRRsetPtr& answer = ConstRRsetPtr(),
RRsetList* target = NULL,
MemoryZone* zone = NULL,
- Zone::FindOptions options = Zone::FIND_DEFAULT)
+ Zone::FindOptions options = Zone::FIND_DEFAULT,
+ bool check_wild_answer = false)
{
if (!zone) {
zone = &zone_;
@@ -253,9 +302,39 @@ public:
EXPECT_EQ(result, find_result.code);
if (check_answer) {
EXPECT_EQ(answer, find_result.rrset);
+ } else if (check_wild_answer) {
+ ASSERT_NE(ConstRRsetPtr(), answer) <<
+ "Wrong test, don't check for wild names if you expect "
+ "empty answer";
+ ASSERT_NE(ConstRRsetPtr(), find_result.rrset) <<
+ "No answer found";
+ RdataIteratorPtr expectedIt(answer->getRdataIterator());
+ RdataIteratorPtr actualIt(
+ find_result.rrset->getRdataIterator());
+ while (!expectedIt->isLast() && !actualIt->isLast()) {
+ EXPECT_EQ(0, expectedIt->getCurrent().compare(
+ actualIt->getCurrent())) << "The RRs differ ('" <<
+ expectedIt->getCurrent().toText() << "', '" <<
+ actualIt->getCurrent().toText() << "')";
+ expectedIt->next();
+ actualIt->next();
+ }
+ EXPECT_TRUE(expectedIt->isLast()) <<
+ "Result has less RRs than expected";
+ EXPECT_TRUE(actualIt->isLast()) <<
+ "Result has more RRs than expected";
+ EXPECT_EQ(answer->getClass(),
+ find_result.rrset->getClass());
+ EXPECT_EQ(answer->getType(),
+ find_result.rrset->getType());
+ EXPECT_EQ(answer->getTTL(),
+ find_result.rrset->getTTL());
+ EXPECT_EQ(name, find_result.rrset->getName());
}
});
}
+ // Internal part of the cancelWildcard test that is multiple times
+ void doCancelWildcardTest();
};
/**
@@ -292,33 +371,30 @@ TEST_F(MemoryZoneTest, add) {
}
TEST_F(MemoryZoneTest, addMultipleCNAMEs) {
- rr_cname_->addRdata(generic::CNAME("canonical1.example.org."));
rr_cname_->addRdata(generic::CNAME("canonical2.example.org."));
EXPECT_THROW(zone_.add(rr_cname_), MemoryZone::AddError);
}
TEST_F(MemoryZoneTest, addCNAMEThenOther) {
- rr_cname_->addRdata(generic::CNAME("canonical.example.org."));
EXPECT_EQ(SUCCESS, zone_.add(rr_cname_));
EXPECT_THROW(zone_.add(rr_cname_a_), MemoryZone::AddError);
}
TEST_F(MemoryZoneTest, addOtherThenCNAME) {
- rr_cname_->addRdata(generic::CNAME("canonical.example.org."));
EXPECT_EQ(SUCCESS, zone_.add(rr_cname_a_));
EXPECT_THROW(zone_.add(rr_cname_), MemoryZone::AddError);
}
TEST_F(MemoryZoneTest, findCNAME) {
// install CNAME RR
- rr_cname_->addRdata(generic::CNAME("canonical.example.org."));
EXPECT_EQ(SUCCESS, zone_.add(rr_cname_));
// Find A RR of the same. Should match the CNAME
- findTest(cname_name_, RRType::NS(), Zone::CNAME, true, rr_cname_);
+ findTest(rr_cname_->getName(), RRType::NS(), Zone::CNAME, true, rr_cname_);
// Find the CNAME itself. Should result in normal SUCCESS
- findTest(cname_name_, RRType::CNAME(), Zone::SUCCESS, true, rr_cname_);
+ findTest(rr_cname_->getName(), RRType::CNAME(), Zone::SUCCESS, true,
+ rr_cname_);
}
TEST_F(MemoryZoneTest, findCNAMEUnderZoneCut) {
@@ -339,8 +415,7 @@ TEST_F(MemoryZoneTest, findCNAMEUnderZoneCut) {
// Having a CNAME there is disallowed too, but it is tested by
// addOtherThenCNAME and addCNAMEThenOther.
TEST_F(MemoryZoneTest, addMultipleDNAMEs) {
- rr_dname_->addRdata(generic::DNAME("dname1.example.org."));
- rr_dname_->addRdata(generic::DNAME("dname2.example.org."));
+ rr_dname_->addRdata(generic::DNAME("target2.example.org."));
EXPECT_THROW(zone_.add(rr_dname_), MemoryZone::AddError);
}
@@ -366,7 +441,8 @@ TEST_F(MemoryZoneTest, DNAMEAndNSAtApex) {
// The NS should be possible to be found, below should be DNAME, not
// delegation
findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
- findTest(child_ns_name_, RRType::A(), Zone::DNAME, true, rr_dname_apex_);
+ findTest(rr_child_ns_->getName(), RRType::A(), Zone::DNAME, true,
+ rr_dname_apex_);
}
TEST_F(MemoryZoneTest, NSAndDNAMEAtApex) {
@@ -379,7 +455,6 @@ TEST_F(MemoryZoneTest, NSAndDNAMEAtApex) {
// Search under a DNAME record. It should return the DNAME
TEST_F(MemoryZoneTest, findBelowDNAME) {
- rr_dname_->addRdata(generic::DNAME("target.example.org."));
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_)));
findTest(Name("below.dname.example.org"), RRType::A(), Zone::DNAME, true,
rr_dname_);
@@ -388,13 +463,13 @@ TEST_F(MemoryZoneTest, findBelowDNAME) {
// Search at the domain with DNAME. It should act as DNAME isn't there, DNAME
// influences only the data below (see RFC 2672, section 3)
TEST_F(MemoryZoneTest, findAtDNAME) {
- rr_dname_->addRdata(generic::DNAME("target.example.org."));
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_)));
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_a_)));
- findTest(dname_name_, RRType::A(), Zone::SUCCESS, true, rr_dname_a_);
- findTest(dname_name_, RRType::DNAME(), Zone::SUCCESS, true, rr_dname_);
- findTest(dname_name_, RRType::TXT(), Zone::NXRRSET, true);
+ const Name dname_name(rr_dname_->getName());
+ findTest(dname_name, RRType::A(), Zone::SUCCESS, true, rr_dname_a_);
+ findTest(dname_name, RRType::DNAME(), Zone::SUCCESS, true, rr_dname_);
+ findTest(dname_name, RRType::TXT(), Zone::NXRRSET, true);
}
// Try searching something that is both under NS and DNAME, without and with
@@ -458,7 +533,7 @@ TEST_F(MemoryZoneTest, findAny) {
EXPECT_EQ(0, out_rrsets.size());
RRsetList glue_child_rrsets;
- findTest(child_glue_name_, RRType::ANY(), Zone::SUCCESS, true,
+ findTest(rr_child_glue_->getName(), RRType::ANY(), Zone::SUCCESS, true,
ConstRRsetPtr(), &glue_child_rrsets);
EXPECT_EQ(rr_child_glue_, glue_child_rrsets.findRRset(RRType::A(),
RRClass::IN()));
@@ -472,13 +547,13 @@ TEST_F(MemoryZoneTest, findAny) {
// zone cut
RRsetList child_rrsets;
- findTest(child_ns_name_, RRType::ANY(), Zone::DELEGATION, true,
+ findTest(rr_child_ns_->getName(), RRType::ANY(), Zone::DELEGATION, true,
rr_child_ns_, &child_rrsets);
EXPECT_EQ(0, child_rrsets.size());
// glue for this zone cut
RRsetList new_glue_child_rrsets;
- findTest(child_glue_name_, RRType::ANY(), Zone::DELEGATION, true,
+ findTest(rr_child_glue_->getName(), RRType::ANY(), Zone::DELEGATION, true,
rr_child_ns_, &new_glue_child_rrsets);
EXPECT_EQ(0, new_glue_child_rrsets.size());
}
@@ -495,29 +570,25 @@ TEST_F(MemoryZoneTest, glue) {
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_grandchild_glue_)));
// by default glue is hidden due to the zone cut
- findTest(child_glue_name_, RRType::A(), Zone::DELEGATION, true,
+ findTest(rr_child_glue_->getName(), RRType::A(), Zone::DELEGATION, true,
rr_child_ns_);
// If we do it in the "glue OK" mode, we should find the exact match.
- findTest(child_glue_name_, RRType::A(), Zone::SUCCESS, true,
+ findTest(rr_child_glue_->getName(), RRType::A(), Zone::SUCCESS, true,
rr_child_glue_, NULL, NULL, Zone::FIND_GLUE_OK);
// glue OK + NXRRSET case
- findTest(child_glue_name_, RRType::AAAA(), Zone::NXRRSET, true,
+ findTest(rr_child_glue_->getName(), RRType::AAAA(), Zone::NXRRSET, true,
ConstRRsetPtr(), NULL, NULL, Zone::FIND_GLUE_OK);
// glue OK + NXDOMAIN case
findTest(Name("www.child.example.org"), RRType::A(), Zone::DELEGATION,
true, rr_child_ns_, NULL, NULL, Zone::FIND_GLUE_OK);
- // TODO:
- // glue name would match a wildcard under a zone cut: wildcard match
- // shouldn't happen under a cut and result must be PARTIALMATCH
- // (This case cannot be tested yet)
-
// nested cut case. The glue should be found.
- findTest(grandchild_glue_name_, RRType::AAAA(), Zone::SUCCESS,
+ findTest(rr_grandchild_glue_->getName(), RRType::AAAA(),
+ Zone::SUCCESS,
true, rr_grandchild_glue_, NULL, NULL, Zone::FIND_GLUE_OK);
// A non-existent name in nested cut. This should result in delegation
@@ -544,17 +615,56 @@ TEST_F(MemoryZoneTest, find) {
// These two should be successful
findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
- findTest(ns_name_, RRType::A(), Zone::SUCCESS, true, rr_ns_a_);
+ findTest(rr_ns_a_->getName(), RRType::A(), Zone::SUCCESS, true, rr_ns_a_);
// These domain exist but don't have the provided RRType
findTest(origin_, RRType::AAAA(), Zone::NXRRSET);
- findTest(ns_name_, RRType::NS(), Zone::NXRRSET);
+ findTest(rr_ns_a_->getName(), RRType::NS(), Zone::NXRRSET);
// These domains don't exist (and one is out of the zone)
findTest(Name("nothere.example.org"), RRType::A(), Zone::NXDOMAIN);
findTest(Name("example.net"), RRType::A(), Zone::NXDOMAIN);
}
+TEST_F(MemoryZoneTest, emptyNode) {
+ /*
+ * The backend RBTree for this test should look like as follows:
+ * example.org
+ * |
+ * baz (empty; easy case)
+ * / | \
+ * bar | x.foo ('foo' part is empty; a bit trickier)
+ * bbb
+ * /
+ * aaa
+ */
+
+ // Construct the test zone
+ const char* const names[] = {
+ "bar.example.org", "x.foo.example.org", "aaa.baz.example.org",
+ "bbb.baz.example.org.", NULL};
+ for (int i = 0; names[i] != NULL; ++i) {
+ ConstRRsetPtr rrset(new RRset(Name(names[i]), class_, RRType::A(),
+ RRTTL(300)));
+ EXPECT_EQ(SUCCESS, zone_.add(rrset));
+ }
+
+ // empty node matching, easy case: the node for 'baz' exists with
+ // no data.
+ findTest(Name("baz.example.org"), RRType::A(), Zone::NXRRSET);
+
+ // empty node matching, a trickier case: the node for 'foo' is part of
+ // "x.foo", which should be considered an empty node.
+ findTest(Name("foo.example.org"), RRType::A(), Zone::NXRRSET);
+
+ // "org" is contained in "example.org", but it shouldn't be treated as
+ // NXRRSET because it's out of zone.
+ // Note: basically we don't expect such a query to be performed (the common
+ // operation is to identify the best matching zone first then perform
+ // search it), but we shouldn't be confused even in the unexpected case.
+ findTest(Name("org"), RRType::A(), Zone::NXDOMAIN);
+}
+
TEST_F(MemoryZoneTest, load) {
// Put some data inside the zone
EXPECT_NO_THROW(EXPECT_EQ(result::SUCCESS, zone_.add(rr_ns_)));
@@ -577,14 +687,298 @@ TEST_F(MemoryZoneTest, load) {
findTest(Name("a.root-servers.net."), RRType::A(), Zone::SUCCESS, false,
ConstRRsetPtr(), NULL, &rootzone);
// But this should no longer be here
- findTest(ns_name_, RRType::AAAA(), Zone::NXDOMAIN, true, ConstRRsetPtr(),
- NULL, &rootzone);
+ findTest(rr_ns_a_->getName(), RRType::AAAA(), Zone::NXDOMAIN, true,
+ ConstRRsetPtr(), NULL, &rootzone);
// Try loading zone that is wrong in a different way
EXPECT_THROW(zone_.load(TEST_DATA_DIR "/duplicate_rrset.zone"),
MasterLoadError);
}
+/*
+ * Test that puts a (simple) wildcard into the zone and checks we can
+ * correctly find the data.
+ */
+TEST_F(MemoryZoneTest, wildcard) {
+ /*
+ * example.org.
+ * |
+ * wild (not *.wild, should have wild mark)
+ * |
+ * *
+ */
+ EXPECT_EQ(SUCCESS, zone_.add(rr_wild_));
+
+ // Search at the parent. The parent will not have the A, but it will
+ // be in the wildcard (so check the wildcard isn't matched at the parent)
+ {
+ SCOPED_TRACE("Search at parrent");
+ findTest(Name("wild.example.org"), RRType::A(), Zone::NXRRSET);
+ }
+
+ // Search the original name of wildcard
+ {
+ SCOPED_TRACE("Search directly at *");
+ findTest(Name("*.wild.example.org"), RRType::A(), Zone::SUCCESS, true,
+ rr_wild_);
+ }
+ // Search "created" name.
+ {
+ SCOPED_TRACE("Search at created child");
+ findTest(Name("a.wild.example.org"), RRType::A(), Zone::SUCCESS, false,
+ rr_wild_, NULL, NULL, Zone::FIND_DEFAULT, true);
+ }
+
+ // Search another created name, this time little bit lower
+ {
+ SCOPED_TRACE("Search at created grand-child");
+ findTest(Name("a.b.wild.example.org"), RRType::A(), Zone::SUCCESS,
+ false, rr_wild_, NULL, NULL, Zone::FIND_DEFAULT, true);
+ }
+
+ EXPECT_EQ(SUCCESS, zone_.add(rr_under_wild_));
+ {
+ SCOPED_TRACE("Search under non-wildcard");
+ findTest(Name("bar.foo.wild.example.org"), RRType::A(),
+ Zone::NXDOMAIN);
+ }
+}
+
+/*
+ * Test that we don't match a wildcard if we get under delegation.
+ * By 4.3.3 of RFC1034:
+ * "Wildcard RRs do not apply:
+ * - When the query is in another zone. That is, delegation cancels
+ * the wildcard defaults."
+ */
+TEST_F(MemoryZoneTest, delegatedWildcard) {
+ EXPECT_EQ(SUCCESS, zone_.add(rr_child_wild_));
+ EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_));
+
+ {
+ SCOPED_TRACE("Looking under delegation point");
+ findTest(Name("a.child.example.org"), RRType::A(), Zone::DELEGATION,
+ true, rr_child_ns_);
+ }
+
+ {
+ SCOPED_TRACE("Looking under delegation point in GLUE_OK mode");
+ findTest(Name("a.child.example.org"), RRType::A(), Zone::DELEGATION,
+ true, rr_child_ns_, NULL, NULL, Zone::FIND_GLUE_OK);
+ }
+}
+
+// Tests combination of wildcard and ANY.
+TEST_F(MemoryZoneTest, anyWildcard) {
+ EXPECT_EQ(SUCCESS, zone_.add(rr_wild_));
+
+ // First try directly the name (normal match)
+ {
+ SCOPED_TRACE("Asking direcly for *");
+ RRsetList target;
+ findTest(Name("*.wild.example.org"), RRType::ANY(), Zone::SUCCESS,
+ true, ConstRRsetPtr(), &target);
+ ASSERT_EQ(1, target.size());
+ EXPECT_EQ(RRType::A(), (*target.begin())->getType());
+ EXPECT_EQ(Name("*.wild.example.org"), (*target.begin())->getName());
+ }
+
+ // Then a wildcard match
+ {
+ SCOPED_TRACE("Asking in the wild way");
+ RRsetList target;
+ findTest(Name("a.wild.example.org"), RRType::ANY(), Zone::SUCCESS,
+ true, ConstRRsetPtr(), &target);
+ ASSERT_EQ(1, target.size());
+ EXPECT_EQ(RRType::A(), (*target.begin())->getType());
+ EXPECT_EQ(Name("a.wild.example.org"), (*target.begin())->getName());
+ }
+}
+
+// Test there's nothing in the wildcard in the middle if we load
+// wild.*.foo.example.org.
+TEST_F(MemoryZoneTest, emptyWildcard) {
+ /*
+ * example.org.
+ * foo
+ * *
+ * wild
+ */
+ EXPECT_EQ(SUCCESS, zone_.add(rr_emptywild_));
+
+ {
+ SCOPED_TRACE("Asking for the original record under wildcard");
+ findTest(Name("wild.*.foo.example.org"), RRType::A(), Zone::SUCCESS,
+ true, rr_emptywild_);
+ }
+
+ {
+ SCOPED_TRACE("Asking for A record");
+ findTest(Name("a.foo.example.org"), RRType::A(), Zone::NXRRSET);
+ findTest(Name("*.foo.example.org"), RRType::A(), Zone::NXRRSET);
+ findTest(Name("foo.example.org"), RRType::A(), Zone::NXRRSET);
+ }
+
+ {
+ SCOPED_TRACE("Asking for ANY record");
+ RRsetList normalTarget;
+ findTest(Name("*.foo.example.org"), RRType::ANY(), Zone::NXRRSET, true,
+ ConstRRsetPtr(), &normalTarget);
+ EXPECT_EQ(0, normalTarget.size());
+
+ RRsetList wildTarget;
+ findTest(Name("a.foo.example.org"), RRType::ANY(), Zone::NXRRSET, true,
+ ConstRRsetPtr(), &wildTarget);
+ EXPECT_EQ(0, wildTarget.size());
+ }
+
+ {
+ SCOPED_TRACE("Asking on the non-terminal");
+ findTest(Name("wild.bar.foo.example.org"), RRType::A(),
+ Zone::NXRRSET);
+ }
+}
+
+// Same as emptyWildcard, but with multiple * in the path.
+TEST_F(MemoryZoneTest, nestedEmptyWildcard) {
+ EXPECT_EQ(SUCCESS, zone_.add(rr_nested_emptywild_));
+
+ {
+ SCOPED_TRACE("Asking for the original record under wildcards");
+ findTest(Name("wild.*.foo.*.bar.example.org"), RRType::A(),
+ Zone::SUCCESS, true, rr_nested_emptywild_);
+ }
+
+ {
+ SCOPED_TRACE("Matching wildcard against empty nonterminal");
+
+ const char* names[] = {
+ "baz.foo.*.bar.example.org",
+ "baz.foo.baz.bar.example.org",
+ "*.foo.baz.bar.example.org",
+ NULL
+ };
+
+ for (const char** name(names); *name != NULL; ++ name) {
+ SCOPED_TRACE(string("Node ") + *name);
+ findTest(Name(*name), RRType::A(), Zone::NXRRSET);
+ }
+ }
+
+ // Domains to test
+ const char* names[] = {
+ "*.foo.*.bar.example.org",
+ "foo.*.bar.example.org",
+ "*.bar.example.org",
+ "bar.example.org",
+ NULL
+ };
+
+ {
+ SCOPED_TRACE("Asking directly for A on parent nodes");
+
+ for (const char** name(names); *name != NULL; ++ name) {
+ SCOPED_TRACE(string("Node ") + *name);
+ findTest(Name(*name), RRType::A(), Zone::NXRRSET);
+ }
+ }
+
+ {
+ SCOPED_TRACE("Asking for ANY on parent nodes");
+
+ for (const char** name(names); *name != NULL; ++ name) {
+ SCOPED_TRACE(string("Node ") + *name);
+
+ RRsetList target;
+ findTest(Name(*name), RRType::ANY(), Zone::NXRRSET, true,
+ ConstRRsetPtr(), &target);
+ EXPECT_EQ(0, target.size());
+ }
+ }
+}
+
+// We run this part twice from the below test, in two slightly different
+// situations
+void
+MemoryZoneTest::doCancelWildcardTest() {
+ // These should be canceled
+ {
+ SCOPED_TRACE("Canceled under foo.wild.example.org");
+ findTest(Name("aaa.foo.wild.example.org"), RRType::A(),
+ Zone::NXDOMAIN);
+ findTest(Name("zzz.foo.wild.example.org"), RRType::A(),
+ Zone::NXDOMAIN);
+ }
+
+ // This is existing, non-wildcard domain, shouldn't wildcard at all
+ {
+ SCOPED_TRACE("Existing domain under foo.wild.example.org");
+ findTest(Name("bar.foo.wild.example.org"), RRType::A(), Zone::SUCCESS,
+ true, rr_not_wild_);
+ }
+
+ // These should be caught by the wildcard
+ {
+ SCOPED_TRACE("Neighbor wildcards to foo.wild.example.org");
+
+ const char* names[] = {
+ "aaa.bbb.wild.example.org",
+ "aaa.zzz.wild.example.org",
+ "zzz.wild.example.org",
+ NULL
+ };
+
+ for (const char** name(names); *name != NULL; ++ name) {
+ SCOPED_TRACE(string("Node ") + *name);
+
+ findTest(Name(*name), RRType::A(), Zone::SUCCESS, false, rr_wild_,
+ NULL, NULL, Zone::FIND_DEFAULT, true);
+ }
+ }
+
+ // This shouldn't be wildcarded, it's an existing domain
+ {
+ SCOPED_TRACE("The foo.wild.example.org itself");
+ findTest(Name("foo.wild.example.org"), RRType::A(), Zone::NXRRSET);
+ }
+}
+
+/*
+ * This tests that if there's a name between the wildcard domain and the
+ * searched one, it will not trigger wildcard, for example, if we have
+ * *.wild.example.org and bar.foo.wild.example.org, then we know
+ * foo.wild.example.org exists and is not wildcard. Therefore, search for
+ * aaa.foo.wild.example.org should return NXDOMAIN.
+ *
+ * Tests few cases "around" the canceled wildcard match, to see something that
+ * shouldn't be canceled isn't.
+ */
+TEST_F(MemoryZoneTest, cancelWildcard) {
+ EXPECT_EQ(SUCCESS, zone_.add(rr_wild_));
+ EXPECT_EQ(SUCCESS, zone_.add(rr_not_wild_));
+
+ {
+ SCOPED_TRACE("Runnig with single entry under foo.wild.example.org");
+ doCancelWildcardTest();
+ }
+
+ // Try putting another one under foo.wild....
+ // The result should be the same but it will be done in another way in the
+ // code, because the foo.wild.example.org will exist in the tree.
+ EXPECT_EQ(SUCCESS, zone_.add(rr_not_wild_another_));
+ {
+ SCOPED_TRACE("Runnig with two entries under foo.wild.example.org");
+ doCancelWildcardTest();
+ }
+}
+
+TEST_F(MemoryZoneTest, loadBadWildcard) {
+ // We reject loading the zone if it contains a wildcard name for
+ // NS or DNAME.
+ EXPECT_THROW(zone_.add(rr_nswild_), MemoryZone::AddError);
+ EXPECT_THROW(zone_.add(rr_dnamewild_), MemoryZone::AddError);
+}
+
TEST_F(MemoryZoneTest, swap) {
// build one zone with some data
MemoryZone zone1(class_, origin_);
diff --git a/src/lib/datasrc/tests/rbtree_unittest.cc b/src/lib/datasrc/tests/rbtree_unittest.cc
index 24d94a3..dd1b7fe 100644
--- a/src/lib/datasrc/tests/rbtree_unittest.cc
+++ b/src/lib/datasrc/tests/rbtree_unittest.cc
@@ -12,7 +12,6 @@
// 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>
@@ -57,7 +56,7 @@ const size_t Name::MAX_LABELS;
namespace {
class RBTreeTest : public::testing::Test {
protected:
- RBTreeTest() : rbtree_expose_empty_node(true) {
+ RBTreeTest() : rbtree_expose_empty_node(true), crbtnode(NULL) {
const char* const domain_names[] = {
"c", "b", "a", "x.d.e.f", "z.d.e.f", "g.h", "i.g.h", "o.w.y.d.e.f",
"j.z.d.e.f", "p.w.y.d.e.f", "q.w.y.d.e.f"};
@@ -188,6 +187,30 @@ TEST_F(RBTreeTest, findError) {
BadValue);
}
+TEST_F(RBTreeTest, flags) {
+ EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("flags.example"),
+ &rbtnode));
+
+ // by default, flags are all off
+ EXPECT_FALSE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+
+ // set operation, by default it enables the flag
+ rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK);
+ EXPECT_TRUE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+
+ // try disable the flag explicitly
+ rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK, false);
+ EXPECT_FALSE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+
+ // try enable the flag explicitly
+ rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK, true);
+ EXPECT_TRUE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+
+ // setting an unknown flag will trigger an exception
+ EXPECT_THROW(rbtnode->setFlag(static_cast<RBNode<int>::Flags>(2), true),
+ isc::InvalidParameter);
+}
+
bool
testCallback(const RBNode<int>&, bool* callack_checker) {
*callack_checker = true;
@@ -199,16 +222,16 @@ TEST_F(RBTreeTest, callback) {
EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("callback.example"),
&rbtnode));
rbtnode->setData(RBNode<int>::NodeDataPtr(new int(1)));
- EXPECT_FALSE(rbtnode->isCallbackEnabled());
+ EXPECT_FALSE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
// enable/re-disable callback
- rbtnode->enableCallback();
- EXPECT_TRUE(rbtnode->isCallbackEnabled());
- rbtnode->disableCallback();
- EXPECT_FALSE(rbtnode->isCallbackEnabled());
+ rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK);
+ EXPECT_TRUE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+ rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK, false);
+ EXPECT_FALSE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
// enable again for subsequent tests
- rbtnode->enableCallback();
+ rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK);
// add more levels below and above the callback node for partial match.
RBNode<int>* subrbtnode;
EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("sub.callback.example"),
@@ -222,9 +245,9 @@ TEST_F(RBTreeTest, callback) {
// it.
EXPECT_EQ(RBTree<int>::EXACTMATCH, rbtree.find(Name("callback.example"),
&rbtnode));
- EXPECT_TRUE(rbtnode->isCallbackEnabled());
- EXPECT_FALSE(subrbtnode->isCallbackEnabled());
- EXPECT_FALSE(parentrbtnode->isCallbackEnabled());
+ EXPECT_TRUE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+ EXPECT_FALSE(subrbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+ EXPECT_FALSE(parentrbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
// check if the callback is called from find()
RBTreeNodeChain<int> node_path1;
@@ -237,7 +260,7 @@ TEST_F(RBTreeTest, callback) {
// enable callback at the parent node, but it doesn't have data so
// the callback shouldn't be called.
RBTreeNodeChain<int> node_path2;
- parentrbtnode->enableCallback();
+ parentrbtnode->setFlag(RBNode<int>::FLAG_CALLBACK);
callback_called = false;
EXPECT_EQ(RBTree<int>::EXACTMATCH,
rbtree.find(Name("callback.example"), &crbtnode, node_path2,
@@ -261,21 +284,21 @@ TEST_F(RBTreeTest, chainLevel) {
EXPECT_EQ(1, chain.getLevelCount());
/*
- * Now creating a possibly deepest tree with MAX_LABELS - 1 levels.
+ * Now creating a possibly deepest tree with MAX_LABELS levels.
* it should look like:
+ * (.)
+ * |
* a
- * /|
- * (.)a
* |
* a
* : (MAX_LABELS - 1) "a"'s
*
* then confirm that find() for the deepest name succeeds without any
* disruption, and the resulting chain has the expected level.
- * Note that "a." and the root name (".") belong to the same level.
- * So the possible maximum level is MAX_LABELS - 1, not MAX_LABELS.
+ * Note that the root name (".") solely belongs to a single level,
+ * so the levels begin with 2.
*/
- for (unsigned int i = 1; i < Name::MAX_LABELS; ++i) {
+ for (unsigned int i = 2; i <= Name::MAX_LABELS; ++i) {
node_name = Name("a.").concatenate(node_name);
EXPECT_EQ(RBTree<int>::SUCCESS, tree.insert(node_name, &rbtnode));
RBTreeNodeChain<int> found_chain;
@@ -342,6 +365,128 @@ TEST_F(RBTreeTest, nextNodeError) {
EXPECT_THROW(rbtree.nextNode(chain), BadValue);
}
+// A helper function for getLastComparedNode() below.
+void
+comparisonChecks(const RBTreeNodeChain<int>& chain,
+ int expected_order, int expected_common_labels,
+ NameComparisonResult::NameRelation expected_reln)
+{
+ if (expected_order > 0) {
+ EXPECT_LT(0, chain.getLastComparisonResult().getOrder());
+ } else if (expected_order < 0) {
+ EXPECT_GT(0, chain.getLastComparisonResult().getOrder());
+ } else {
+ EXPECT_EQ(0, chain.getLastComparisonResult().getOrder());
+ }
+ EXPECT_EQ(expected_common_labels,
+ chain.getLastComparisonResult().getCommonLabels());
+ EXPECT_EQ(expected_reln,
+ chain.getLastComparisonResult().getRelation());
+}
+
+TEST_F(RBTreeTest, getLastComparedNode) {
+ RBTree<int>& tree = rbtree_expose_empty_node; // use the "empty OK" mode
+ RBTreeNodeChain<int> chain;
+
+ // initially there should be no 'last compared'.
+ EXPECT_EQ(static_cast<void*>(NULL), chain.getLastComparedNode());
+
+ // A search for an empty tree should result in no 'last compared', too.
+ RBTree<int> empty_tree;
+ EXPECT_EQ(RBTree<int>::NOTFOUND,
+ empty_tree.find<void*>(Name("a"), &crbtnode, chain, NULL, NULL));
+ EXPECT_EQ(static_cast<void*>(NULL), chain.getLastComparedNode());
+ chain.clear();
+
+ const RBNode<int>* expected_node;
+
+ // Exact match case. The returned node should be last compared.
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ tree.find<void*>(Name("x.d.e.f"), &expected_node, chain,
+ NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // 2 = # labels of "x."
+ comparisonChecks(chain, 0, 2, NameComparisonResult::EQUAL);
+ chain.clear();
+
+ // Partial match, search stopped at the matching node, which should be
+ // the last compared node.
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ tree.find(Name("i.g.h"), &expected_node));
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ tree.find<void*>(Name("x.i.g.h"), &crbtnode, chain,
+ NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // i.g.h < x.i.g.h, 2 = # labels of "i."
+ comparisonChecks(chain, 1, 2, NameComparisonResult::SUBDOMAIN);
+ chain.clear();
+
+ // Partial match, search stopped in the subtree below the matching node
+ // after following a left branch.
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ tree.find(Name("x.d.e.f"), &expected_node));
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ tree.find<void*>(Name("a.d.e.f"), &crbtnode, chain,
+ NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // a < x, 1 = # labels of "." (trailing dot)
+ comparisonChecks(chain, -1, 1, NameComparisonResult::COMMONANCESTOR);
+ chain.clear();
+
+ // Partial match, search stopped in the subtree below the matching node
+ // after following a right branch.
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ tree.find(Name("z.d.e.f"), &expected_node));
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ tree.find<void*>(Name("zz.d.e.f"), &crbtnode, chain,
+ NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // zz > z, 1 = # labels of "." (trailing dot)
+ comparisonChecks(chain, 1, 1, NameComparisonResult::COMMONANCESTOR);
+ chain.clear();
+
+ // Partial match, search stopped at a node for a super domain of the
+ // search name in the subtree below the matching node.
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ tree.find(Name("w.y.d.e.f"), &expected_node));
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ tree.find<void*>(Name("y.d.e.f"), &crbtnode, chain,
+ NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // y < w.y, 2 = # labels of "y."
+ comparisonChecks(chain, -1, 2, NameComparisonResult::SUPERDOMAIN);
+ chain.clear();
+
+ // Partial match, search stopped at a node that share a common ancestor
+ // with the search name in the subtree below the matching node.
+ // (the expected node is the same as the previous case)
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ tree.find<void*>(Name("z.y.d.e.f"), &crbtnode, chain,
+ NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // z.y > w.y, 2 = # labels of "y."
+ comparisonChecks(chain, 1, 2, NameComparisonResult::COMMONANCESTOR);
+ chain.clear();
+
+ // Search stops in the highest level after following a left branch.
+ EXPECT_EQ(RBTree<int>::EXACTMATCH, tree.find(Name("c"), &expected_node));
+ EXPECT_EQ(RBTree<int>::NOTFOUND,
+ tree.find<void*>(Name("bb"), &crbtnode, chain, NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // bb < c, 1 = # labels of "." (trailing dot)
+ comparisonChecks(chain, -1, 1, NameComparisonResult::COMMONANCESTOR);
+ chain.clear();
+
+ // Search stops in the highest level after following a right branch.
+ // (the expected node is the same as the previous case)
+ EXPECT_EQ(RBTree<int>::NOTFOUND,
+ tree.find<void*>(Name("d"), &crbtnode, chain, NULL, NULL));
+ EXPECT_EQ(expected_node, chain.getLastComparedNode());
+ // d > c, 1 = # labels of "." (trailing dot)
+ comparisonChecks(chain, 1, 1, NameComparisonResult::COMMONANCESTOR);
+ chain.clear();
+}
+
TEST_F(RBTreeTest, dumpTree) {
std::ostringstream str;
std::ostringstream str2;
@@ -378,4 +523,44 @@ TEST_F(RBTreeTest, swap) {
tree2.dumpTree(out);
ASSERT_EQ(str1.str(), out.str());
}
+
+// Matching in the "root zone" may be special (e.g. there's no parent,
+// any domain names should be considered a subdomain of it), so it makes
+// sense to test cases with the root zone explicitly.
+TEST_F(RBTreeTest, root) {
+ RBTree<int> root;
+ root.insert(Name::ROOT_NAME(), &rbtnode);
+ rbtnode->setData(RBNode<int>::NodeDataPtr(new int(1)));
+
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ root.find(Name::ROOT_NAME(), &crbtnode));
+ EXPECT_EQ(rbtnode, crbtnode);
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ root.find(Name("example.com"), &crbtnode));
+ EXPECT_EQ(rbtnode, crbtnode);
+
+ // Insert a new name that better matches the query name. find() should
+ // find the better one.
+ root.insert(Name("com"), &rbtnode);
+ rbtnode->setData(RBNode<int>::NodeDataPtr(new int(2)));
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ root.find(Name("example.com"), &crbtnode));
+ EXPECT_EQ(rbtnode, crbtnode);
+
+ // Perform the same tests for the tree that allows matching against empty
+ // nodes.
+ RBTree<int> root_emptyok(true);
+ root_emptyok.insert(Name::ROOT_NAME(), &rbtnode);
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ root_emptyok.find(Name::ROOT_NAME(), &crbtnode));
+ EXPECT_EQ(rbtnode, crbtnode);
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ root_emptyok.find(Name("example.com"), &crbtnode));
+ EXPECT_EQ(rbtnode, crbtnode);
+
+ root.insert(Name("com"), &rbtnode);
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ root.find(Name("example.com"), &crbtnode));
+ EXPECT_EQ(rbtnode, crbtnode);
+}
}
diff --git a/src/lib/datasrc/tests/test_datasrc.cc b/src/lib/datasrc/tests/test_datasrc.cc
index 8145539..9493e1a 100644
--- a/src/lib/datasrc/tests/test_datasrc.cc
+++ b/src/lib/datasrc/tests/test_datasrc.cc
@@ -273,6 +273,18 @@ const struct RRData nosoa_example_records[] = {
};
//
+// zone data for apexcname.example.
+//
+const struct RRData apexcname_example_records[] = {
+ {"apexcname.example", "CNAME", "canonical.apexcname.example"},
+ {"canonical.apexcname.example", "SOA",
+ "master.apexcname.example "
+ "admin.apexcname.example. 1234 3600 1800 2419200 7200"},
+ {NULL, NULL, NULL}
+};
+
+
+//
// empty data set, for convenience.
//
const struct RRData empty_records[] = {
@@ -288,7 +300,8 @@ const struct ZoneData zone_data[] = {
{ "loop.example", "IN", loop_example_records, empty_records },
{ "nons.example", "IN", nons_example_records, empty_records },
{ "nons-dname.example", "IN", nonsdname_example_records, empty_records },
- { "nosoa.example", "IN", nosoa_example_records, empty_records }
+ { "nosoa.example", "IN", nosoa_example_records, empty_records },
+ { "apexcname.example", "IN", nosoa_example_records, empty_records }
};
const size_t NUM_ZONES = sizeof(zone_data) / sizeof(zone_data[0]);
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index 4b1a64b..c5c5cd1 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -85,6 +85,8 @@ libdns___la_SOURCES += rrtype.cc
libdns___la_SOURCES += question.h question.cc
libdns___la_SOURCES += util/sha1.h util/sha1.cc
libdns___la_SOURCES += tsigkey.h tsigkey.cc
+libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
+libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc
nodist_libdns___la_SOURCES = rdataclass.cc rrclass.h rrtype.h
nodist_libdns___la_SOURCES += rrparamregistry.cc
@@ -117,5 +119,6 @@ libdns___include_HEADERS = \
tsigkey.h
# Purposely not installing these headers:
# util/*.h: used only internally, and not actually DNS specific
+# rdata/*/detail/*.h: these are internal use only
# rrclass-placeholder.h
# rrtype-placeholder.h
diff --git a/src/lib/dns/dnssectime.cc b/src/lib/dns/dnssectime.cc
index 04643e2..c889178 100644
--- a/src/lib/dns/dnssectime.cc
+++ b/src/lib/dns/dnssectime.cc
@@ -12,6 +12,10 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <stdint.h>
+
+#include <sys/time.h>
+
#include <string>
#include <iomanip>
#include <iostream>
@@ -26,30 +30,121 @@
using namespace std;
+namespace {
+int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+inline bool
+isLeap(const int y) {
+ return ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0);
+}
+
+unsigned int
+yearSecs(const int year) {
+ return ((isLeap(year) ? 366 : 365 ) * 86400);
+}
+
+unsigned int
+monthSecs(const int month, const int year) {
+ return ((days[month] + ((month == 1 && isLeap(year)) ? 1 : 0 )) * 86400);
+}
+}
+
namespace isc {
namespace dns {
string
-timeToText(const time_t timeval) {
- struct tm* const t = gmtime(&timeval);
-
- // gmtime() will keep most values within range, but it can
- // produce a five-digit year; check for this.
- if ((t->tm_year + 1900) > 9999) {
- isc_throw(InvalidTime, "Time value out of range: year > 9999");
+timeToText64(uint64_t value) {
+ struct tm tm;
+ unsigned int secs;
+
+ // We cannot rely on gmtime() because time_t may not be of 64 bit
+ // integer. The following conversion logic is borrowed from BIND 9.
+ tm.tm_year = 70;
+ while ((secs = yearSecs(tm.tm_year + 1900)) <= value) {
+ value -= secs;
+ ++tm.tm_year;
+ if (tm.tm_year + 1900 > 9999) {
+ isc_throw(InvalidTime,
+ "Time value out of range (year > 9999): " <<
+ tm.tm_year + 1900);
+ }
+ }
+ tm.tm_mon = 0;
+ while ((secs = monthSecs(tm.tm_mon, tm.tm_year + 1900)) <= value) {
+ value -= secs;
+ tm.tm_mon++;
}
+ tm.tm_mday = 1;
+ while (86400 <= value) {
+ value -= 86400;
+ ++tm.tm_mday;
+ }
+ tm.tm_hour = 0;
+ while (3600 <= value) {
+ value -= 3600;
+ ++tm.tm_hour;
+ }
+ tm.tm_min = 0;
+ while (60 <= value) {
+ value -= 60;
+ ++tm.tm_min;
+ }
+ tm.tm_sec = value; // now t < 60, so this substitution is safe.
ostringstream oss;
oss << setfill('0')
- << setw(4) << t->tm_year + 1900
- << setw(2) << t->tm_mon + 1
- << setw(2) << t->tm_mday
- << setw(2) << t->tm_hour
- << setw(2) << t->tm_min
- << setw(2) << t->tm_sec;
+ << setw(4) << tm.tm_year + 1900
+ << setw(2) << tm.tm_mon + 1
+ << setw(2) << tm.tm_mday
+ << setw(2) << tm.tm_hour
+ << setw(2) << tm.tm_min
+ << setw(2) << tm.tm_sec;
return (oss.str());
}
+// timeToText32() below uses the current system time. To test it with
+// unusual current time values we introduce the following function pointer;
+// when it's non NULL, we call it to get the (normally faked) current time.
+// Otherwise we use the standard gettimeofday(2). This hook is specifically
+// intended for testing purposes, so, even if it's visible outside of this
+// library, it's not even declared in a header file.
+namespace dnssectime {
+namespace detail {
+int64_t (*gettimeFunction)() = NULL;
+}
+}
+
+namespace {
+int64_t
+gettimeofdayWrapper() {
+ using namespace dnssectime::detail;
+ if (gettimeFunction != NULL) {
+ return (gettimeFunction());
+ }
+
+ struct timeval now;
+ gettimeofday(&now, NULL);
+
+ return (static_cast<int64_t>(now.tv_sec));
+}
+}
+
+string
+timeToText32(const uint32_t value) {
+ // We first adjust the time to the closest epoch based on the current time.
+ // Note that the following variables must be signed in order to handle
+ // time until year 2038 correctly.
+ const int64_t start = gettimeofdayWrapper() - 0x7fffffff;
+ int64_t base = 0;
+ int64_t t;
+ while ((t = (base + value)) < start) {
+ base += 0x100000000LL;
+ }
+
+ // Then convert it to text.
+ return (timeToText64(t));
+}
+
namespace {
const size_t DATE_LEN = 14; // YYYYMMDDHHmmSS
@@ -62,27 +157,20 @@ checkRange(const int min, const int max, const int value,
}
isc_throw(InvalidTime, "Invalid " << valname << "value: " << value);
}
-
-int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
-
-inline bool
-isLeap(const int y) {
- return ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0);
-}
}
-time_t
-timeFromText(const string& time_txt) {
- // first try reading YYYYMMDDHHmmSS format
- int year, month, day, hour, minute, second;
-
+uint64_t
+timeFromText64(const string& time_txt) {
+ // Confirm the source only consists digits. sscanf() allows some
+ // minor exceptions.
for (int i = 0; i < time_txt.length(); ++i) {
if (!isdigit(time_txt.at(i))) {
- isc_throw(InvalidTime,
- "Couldn't convert non-numeric time value: " << time_txt);
+ isc_throw(InvalidTime, "Couldn't convert non-numeric time value: "
+ << time_txt);
}
}
+ int year, month, day, hour, minute, second;
if (time_txt.length() != DATE_LEN ||
sscanf(time_txt.c_str(), "%4d%2d%2d%2d%2d%2d",
&year, &month, &day, &hour, &minute, &second) != 6)
@@ -98,9 +186,9 @@ timeFromText(const string& time_txt) {
checkRange(0, 59, minute, "minute");
checkRange(0, 60, second, "second"); // 60 == leap second.
- time_t timeval = second + (60 * minute) + (3600 * hour) +
+ uint64_t timeval = second + (60 * minute) + (3600 * hour) +
((day - 1) * 86400);
- for (int m = 0; m < (month - 1); m++) {
+ for (int m = 0; m < (month - 1); ++m) {
timeval += days[m] * 86400;
}
if (isLeap(year) && month > 2) {
@@ -112,5 +200,12 @@ timeFromText(const string& time_txt) {
return (timeval);
}
+
+uint32_t
+timeFromText32(const string& time_txt) {
+ // The implicit conversion from uint64_t to uint32_t should just work here,
+ // because we only need to drop higher 32 bits.
+ return (timeFromText64(time_txt));
+}
}
}
diff --git a/src/lib/dns/dnssectime.h b/src/lib/dns/dnssectime.h
index 5069650..baf866f 100644
--- a/src/lib/dns/dnssectime.h
+++ b/src/lib/dns/dnssectime.h
@@ -17,7 +17,6 @@
#include <sys/types.h>
#include <stdint.h>
-#include <time.h>
#include <exceptions/exceptions.h>
@@ -40,11 +39,102 @@ public:
isc::Exception(file, line, what) {}
};
-time_t
-timeFromText(const std::string& time_txt);
+///
+/// \name DNSSEC time conversion functions.
+///
+/// These functions convert between times represented in seconds (in integer)
+/// since epoch and those in the textual form used in the RRSIG records.
+/// For integers we provide both 32-bit and 64-bit versions.
+/// The RRSIG expiration and inception fields are both 32-bit unsigned
+/// integers, so 32-bit versions would be more useful for protocol operations.
+/// However, with 32-bit integers we need to take into account wrap-around
+/// points and compare values using the serial number arithmetic as specified
+/// in RFC4034, which would be more error prone. We therefore provide 64-bit
+/// versions, too.
+///
+/// The timezone is always UTC for these functions.
+//@{
+/// Convert textual DNSSEC time to integer, 64-bit version.
+///
+/// The textual form must only consist of digits and be in the form of
+/// YYYYMMDDHHmmSS, where:
+/// - YYYY must be between 1970 and 9999
+/// - MM must be between 01 and 12
+/// - DD must be between 01 and 31 and must be a valid day for the month
+/// represented in 'MM'. For example, if MM is 04, DD cannot be 31.
+/// DD can be 29 when MM is 02 only when YYYY is a leap year.
+/// - HH must be between 00 and 23
+/// - mm must be between 00 and 59
+/// - SS must be between 00 and 60
+///
+/// For all fields the range includes the begin and end values. Note that
+/// 60 is allowed for 'SS', intending a leap second, although in real operation
+/// it's unlikely to be specified.
+///
+/// If the given text is valid, this function converts it to an unsigned
+/// 64-bit number of seconds since epoch (1 January 1970 00:00:00) and returns
+/// the converted value. 64 bits are sufficient to represent all possible
+/// values for the valid format uniquely, so there is no overflow.
+///
+/// \note RFC4034 also defines the textual form of an unsigned decimal integer
+/// for the corresponding time in seconds. This function doesn't support
+/// this form, and if given it throws an exception of class \c InvalidTime.
+///
+/// \exception InvalidTime The given textual representation is invalid.
+///
+/// \param time_txt Textual time in the form of YYYYMMDDHHmmSS
+/// \return Seconds since epoch corresponding to \c time_txt
+uint64_t
+timeFromText64(const std::string& time_txt);
+/// Convert textual DNSSEC time to integer, 32-bit version.
+///
+/// This version is the same as \c timeFromText64() except that the return
+/// value is wrapped around to an unsigned 32-bit integer, simply dropping
+/// the upper 32 bits.
+uint32_t
+timeFromText32(const std::string& time_txt);
+
+/// Convert integral DNSSEC time to textual form, 64-bit version.
+///
+/// This function takes an integer that would be seconds since epoch and
+/// converts it in the form of YYYYMMDDHHmmSS. For example, if \c value is
+/// 0, it returns "19700101000000". If the value corresponds to a point
+/// of time on and after year 10,000, which cannot be represented in the
+/// YYYY... form, an exception of class \c InvalidTime will be thrown.
+///
+/// \exception InvalidTime The given time specifies on or after year 10,000.
+/// \exception Other A standard exception, if resource allocation for the
+/// returned text fails.
+///
+/// \param value Seconds since epoch to be converted.
+/// \return Textual representation of \c value in the form of YYYYMMDDHHmmSS.
std::string
-timeToText(const time_t timeval);
+timeToText64(uint64_t value);
+
+/// Convert integral DNSSEC time to textual form, 32-bit version.
+///
+/// This version is the same as \c timeToText64(), but the time value
+/// is expected to be the lower 32 bits of the full 64-bit value.
+/// These two will be different on and after a certain point of time
+/// in year 2106, so this function internally resolves the ambiguity
+/// using the current system time at the time of function call;
+/// it first identifies the range of [N*2^32 - 2^31, N*2^32 + 2^31)
+/// that contains the current time, and interprets \c value in the context
+/// of that range. It then applies the same process as \c timeToText64().
+///
+/// There is one important exception in this processing, however.
+/// Until 19 Jan 2038 03:14:08 (2^31 seconds since epoch), this range
+/// would contain time before epoch. In order to ensure the returned
+/// value is also a valid input to \c timeFromText, this function uses
+/// a special range [0, 2^32) until that time. As a result, all upper
+/// half of the 32-bit values are treated as a future time. For example,
+/// 2^32-1 (the highest value in 32-bit unsigned integers) will be converted
+/// to "21060207062815", instead of "19691231235959".
+std::string
+timeToText32(const uint32_t value);
+
+//@}
}
}
diff --git a/src/lib/dns/message.cc b/src/lib/dns/message.cc
index e9ab1b9..9eae605 100644
--- a/src/lib/dns/message.cc
+++ b/src/lib/dns/message.cc
@@ -113,10 +113,8 @@ public:
vector<RRsetPtr> rrsets_[NUM_SECTIONS];
ConstEDNSPtr edns_;
-#ifdef notyet
// tsig/sig0: TODO
- RRsetsSorter* sorter_;
-#endif
+ // RRsetsSorter* sorter_; : TODO
void init();
void setOpcode(const Opcode& opcode);
@@ -338,6 +336,14 @@ Message::removeRRset(const Section section, RRsetIterator& iterator) {
return (removed);
}
+void
+Message::clearSection(const Section section) {
+ if (section >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << section);
+ }
+ impl_->rrsets_[section].clear();
+ impl_->counts_[section] = 0;
+}
void
Message::addQuestion(const QuestionPtr question) {
@@ -769,6 +775,27 @@ Message::clear(Mode mode) {
}
void
+Message::appendSection(const Section section, const Message& source) {
+ if (section >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << section);
+ }
+
+ if (section == SECTION_QUESTION) {
+ for (QuestionIterator qi = source.beginQuestion();
+ qi != source.endQuestion();
+ ++qi) {
+ addQuestion(*qi);
+ }
+ } else {
+ for (RRsetIterator rrsi = source.beginSection(section);
+ rrsi != source.endSection(section);
+ ++rrsi) {
+ addRRset(section, *rrsi);
+ }
+ }
+}
+
+void
Message::makeResponse() {
if (impl_->mode_ != Message::PARSE) {
isc_throw(InvalidMessageOperation,
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h
index 62b3016..91a241a 100644
--- a/src/lib/dns/message.h
+++ b/src/lib/dns/message.h
@@ -483,6 +483,11 @@ public:
/// found in the specified section.
bool removeRRset(const Section section, RRsetIterator& iterator);
+ /// \brief Remove all RRSets from the given Section
+ ///
+ /// \param section Section to remove all rrsets from
+ void clearSection(const Section section);
+
// The following methods are not currently implemented.
//void removeQuestion(QuestionPtr question);
// notyet:
@@ -493,6 +498,13 @@ public:
/// specified mode.
void clear(Mode mode);
+ /// \brief Adds all rrsets from the source the given section in the
+ /// source message to the same section of this message
+ ///
+ /// \param section the section to append
+ /// \param target The source Message
+ void appendSection(const Section section, const Message& source);
+
/// \brief Prepare for making a response from a request.
///
/// This will clear the DNS header except those fields that should be kept
diff --git a/src/lib/dns/python/messagerenderer_python.cc b/src/lib/dns/python/messagerenderer_python.cc
index 91ab0c5..a00d8d4 100644
--- a/src/lib/dns/python/messagerenderer_python.cc
+++ b/src/lib/dns/python/messagerenderer_python.cc
@@ -37,9 +37,10 @@ static PyObject* MessageRenderer_getData(s_MessageRenderer* self);
static PyObject* MessageRenderer_getLength(s_MessageRenderer* self);
static PyObject* MessageRenderer_isTruncated(s_MessageRenderer* self);
static PyObject* MessageRenderer_getLengthLimit(s_MessageRenderer* self);
-// TODO: set/get compressmode
+static PyObject* MessageRenderer_getCompressMode(s_MessageRenderer* self);
static PyObject* MessageRenderer_setTruncated(s_MessageRenderer* self);
static PyObject* MessageRenderer_setLengthLimit(s_MessageRenderer* self, PyObject* args);
+static PyObject* MessageRenderer_setCompressMode(s_MessageRenderer* self, PyObject* args);
static PyObject* MessageRenderer_clear(s_MessageRenderer* self);
static PyMethodDef MessageRenderer_methods[] = {
@@ -51,10 +52,14 @@ static PyMethodDef MessageRenderer_methods[] = {
"Returns True if the data is truncated" },
{ "get_length_limit", reinterpret_cast<PyCFunction>(MessageRenderer_getLengthLimit), METH_NOARGS,
"Returns the length limit of the data" },
+ { "get_compress_mode", reinterpret_cast<PyCFunction>(MessageRenderer_getCompressMode), METH_NOARGS,
+ "Returns the current compression mode" },
{ "set_truncated", reinterpret_cast<PyCFunction>(MessageRenderer_setTruncated), METH_NOARGS,
"Sets truncated to true" },
{ "set_length_limit", reinterpret_cast<PyCFunction>(MessageRenderer_setLengthLimit), METH_VARARGS,
"Sets the length limit of the data to the given number" },
+ { "set_compress_mode", reinterpret_cast<PyCFunction>(MessageRenderer_setCompressMode), METH_VARARGS,
+ "Sets the compression mode of the MessageRenderer" },
{ "clear", reinterpret_cast<PyCFunction>(MessageRenderer_clear),
METH_NOARGS,
"Clear the internal buffer and other internal resources." },
@@ -159,6 +164,11 @@ MessageRenderer_getLengthLimit(s_MessageRenderer* self) {
}
static PyObject*
+MessageRenderer_getCompressMode(s_MessageRenderer* self) {
+ return (Py_BuildValue("I", self->messagerenderer->getCompressMode()));
+}
+
+static PyObject*
MessageRenderer_setTruncated(s_MessageRenderer* self) {
self->messagerenderer->setTruncated();
Py_RETURN_NONE;
@@ -177,6 +187,31 @@ MessageRenderer_setLengthLimit(s_MessageRenderer* self,
}
static PyObject*
+MessageRenderer_setCompressMode(s_MessageRenderer* self,
+ PyObject* args)
+{
+ unsigned int mode;
+ if (!PyArg_ParseTuple(args, "I", &mode)) {
+ return (NULL);
+ }
+
+ if (mode == MessageRenderer::CASE_INSENSITIVE) {
+ self->messagerenderer->setCompressMode(MessageRenderer::CASE_INSENSITIVE);
+ // If we return NULL it is seen as an error, so use this for
+ // None returns, it also applies to CASE_SENSITIVE.
+ Py_RETURN_NONE;
+ } else if (mode == MessageRenderer::CASE_SENSITIVE) {
+ self->messagerenderer->setCompressMode(MessageRenderer::CASE_SENSITIVE);
+ Py_RETURN_NONE;
+ } else {
+ PyErr_SetString(PyExc_TypeError,
+ "MessageRenderer compress mode must be MessageRenderer.CASE_INSENSITIVE"
+ "or MessageRenderer.CASE_SENSITIVE");
+ return (NULL);
+ }
+}
+
+static PyObject*
MessageRenderer_clear(s_MessageRenderer* self) {
self->messagerenderer->clear();
Py_RETURN_NONE;
@@ -203,6 +238,14 @@ initModulePart_MessageRenderer(PyObject* mod) {
return (false);
}
Py_INCREF(&messagerenderer_type);
+
+ // Class variables
+ // These are added to the tp_dict of the type object
+ addClassVariable(messagerenderer_type, "CASE_INSENSITIVE",
+ Py_BuildValue("I", MessageRenderer::CASE_INSENSITIVE));
+ addClassVariable(messagerenderer_type, "CASE_SENSITIVE",
+ Py_BuildValue("I", MessageRenderer::CASE_SENSITIVE));
+
PyModule_AddObject(mod, "MessageRenderer",
reinterpret_cast<PyObject*>(&messagerenderer_type));
diff --git a/src/lib/dns/python/tests/messagerenderer_python_test.py b/src/lib/dns/python/tests/messagerenderer_python_test.py
index 62e2d51..544ad23 100644
--- a/src/lib/dns/python/tests/messagerenderer_python_test.py
+++ b/src/lib/dns/python/tests/messagerenderer_python_test.py
@@ -28,7 +28,7 @@ class MessageRendererTest(unittest.TestCase):
c = RRClass("IN")
t = RRType("A")
ttl = RRTTL("3600")
-
+
message = Message(Message.RENDER)
message.set_qid(123)
message.set_opcode(Opcode.QUERY())
@@ -56,14 +56,14 @@ class MessageRendererTest(unittest.TestCase):
self.message1.to_wire(self.renderer1)
self.message2.to_wire(self.renderer2)
self.message2.to_wire(self.renderer3)
-
-
+
+
def test_messagerenderer_get_data(self):
data1 = b'\x00{\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\x01\x00\x01'
self.assertEqual(data1, self.renderer1.get_data())
data2 = b'\x00{\x84\x00\x00\x01\x00\x00\x00\x02\x00\x00\x07example\x03com\x00\x00\x01\x00\x01\xc0\x0c\x00\x01\x00\x01\x00\x00\x0e\x10\x00\x04\xc0\x00\x02b\xc0\x0c\x00\x01\x00\x01\x00\x00\x0e\x10\x00\x04\xc0\x00\x02c'
self.assertEqual(data2, self.renderer2.get_data())
-
+
def test_messagerenderer_get_length(self):
self.assertEqual(29, self.renderer1.get_length())
self.assertEqual(61, self.renderer2.get_length())
@@ -79,6 +79,14 @@ class MessageRendererTest(unittest.TestCase):
self.assertEqual(512, self.renderer2.get_length_limit())
self.assertEqual(50, self.renderer3.get_length_limit())
+ def test_messagerenderer_get_compress_mode(self):
+ self.assertEqual(MessageRenderer.CASE_INSENSITIVE,
+ self.renderer1.get_compress_mode())
+ self.assertEqual(MessageRenderer.CASE_INSENSITIVE,
+ self.renderer2.get_compress_mode())
+ self.assertEqual(MessageRenderer.CASE_INSENSITIVE,
+ self.renderer3.get_compress_mode())
+
def test_messagerenderer_set_truncated(self):
self.assertFalse(self.renderer1.is_truncated())
self.renderer1.set_truncated()
@@ -91,5 +99,17 @@ class MessageRendererTest(unittest.TestCase):
self.assertEqual(1024, renderer.get_length_limit())
self.assertRaises(TypeError, renderer.set_length_limit, "wrong")
+ def test_messagerenderer_set_compress_mode(self):
+ renderer = MessageRenderer()
+ self.assertEqual(MessageRenderer.CASE_INSENSITIVE,
+ renderer.get_compress_mode())
+ renderer.set_compress_mode(MessageRenderer.CASE_SENSITIVE)
+ self.assertEqual(MessageRenderer.CASE_SENSITIVE,
+ renderer.get_compress_mode())
+ renderer.set_compress_mode(MessageRenderer.CASE_INSENSITIVE)
+ self.assertEqual(MessageRenderer.CASE_INSENSITIVE,
+ renderer.get_compress_mode())
+ self.assertRaises(TypeError, renderer.set_compress_mode, "wrong")
+
if __name__ == '__main__':
unittest.main()
diff --git a/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc b/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
new file mode 100644
index 0000000..a72058f
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
@@ -0,0 +1,78 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <stdint.h>
+
+#include <vector>
+
+#include <dns/exceptions.h>
+
+using namespace std;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+namespace nsec {
+void
+checkRRTypeBitmaps(const char* const rrtype_name,
+ const vector<uint8_t>& typebits)
+{
+ bool first = true;
+ unsigned int lastblock = 0;
+ const size_t total_len = typebits.size();
+ size_t i = 0;
+
+ while (i < total_len) {
+ if (i + 2 > total_len) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " RDATA from wire: incomplete bit map field");
+ }
+ const unsigned int block = typebits[i];
+ const size_t len = typebits[i + 1];
+ // Check that bitmap window blocks are in the correct order.
+ if (!first && block <= lastblock) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " RDATA from wire: Disordered window blocks found: "
+ << lastblock << " then " << block);
+ }
+ // Check for legal length
+ if (len < 1 || len > 32) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " RDATA from wire: Invalid bitmap length: " << len);
+ }
+ // Check for overflow.
+ i += 2;
+ if (i + len > total_len) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " RDATA from wire: bitmap length too large: " << len);
+ }
+ // The last octet of the bitmap must be non zero.
+ if (typebits[i + len - 1] == 0) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " RDATA from wire: bitmap ending an all-zero byte");
+ }
+
+ i += len;
+ lastblock = block;
+ first = false;
+ }
+}
+}
+}
+}
+}
+}
+}
diff --git a/src/lib/dns/rdata/generic/detail/nsec_bitmap.h b/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
new file mode 100644
index 0000000..6431e10
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
@@ -0,0 +1,51 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <stdint.h>
+
+#include <vector>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+namespace nsec {
+/// Check if a given "type bitmap" for NSEC/NSEC3 is valid.
+///
+/// This helper function checks given wire format data (stored in a
+/// \c std::vector) is a valid type bitmaps used for the NSEC and NSEC3 RRs
+/// according to RFC4034 and RFC5155. The validation logic is the same
+/// for these two RRs, so a unified check function is provided.
+/// This function is essentially private and is only expected to be called
+/// from the \c NSEC and \c NSEC3 class implementations.
+///
+/// \exception DNSMessageFORMERR The bitmap is not valid.
+///
+/// \param rrtype_name Either "NSEC" or "NSEC3"; used as part of exception
+/// messages.
+/// \param typebits The type bitmaps in wire format. The size of vector
+/// is the total length of the bitmaps.
+void checkRRTypeBitmaps(const char* const rrtype_name,
+ const std::vector<uint8_t>& typebits);
+}
+}
+}
+}
+}
+}
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/nsec3_50.cc b/src/lib/dns/rdata/generic/nsec3_50.cc
index c20fda2..fbe6612 100644
--- a/src/lib/dns/rdata/generic/nsec3_50.cc
+++ b/src/lib/dns/rdata/generic/nsec3_50.cc
@@ -30,11 +30,13 @@
#include <dns/rrttl.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/nsec_bitmap.h>
#include <stdio.h>
#include <time.h>
using namespace std;
+using namespace isc::dns::rdata::generic::detail::nsec;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -61,50 +63,70 @@ NSEC3::NSEC3(const string& nsec3_str) :
{
istringstream iss(nsec3_str);
unsigned int hashalg, flags, iterations;
- string salthex;
+ string iterations_str, salthex, nexthash;
- iss >> hashalg >> flags >> iterations >> salthex;
+ iss >> hashalg >> flags >> iterations_str >> salthex >> nexthash;
if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid NSEC3 text");
+ isc_throw(InvalidRdataText, "Invalid NSEC3 text: " << nsec3_str);
}
- if (hashalg > 0xf) {
- isc_throw(InvalidRdataText, "NSEC3 hash algorithm out of range");
+ if (hashalg > 0xff) {
+ isc_throw(InvalidRdataText,
+ "NSEC3 hash algorithm out of range: " << hashalg);
}
if (flags > 0xff) {
- isc_throw(InvalidRdataText, "NSEC3 flags out of range");
+ isc_throw(InvalidRdataText, "NSEC3 flags out of range: " << flags);
+ }
+ // Convert iteration. To reject an invalid case where there's no space
+ // between iteration and salt, we extract this field as string and convert
+ // to integer.
+ try {
+ iterations = boost::lexical_cast<unsigned int>(iterations_str);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(InvalidRdataText, "Bad NSEC3 iteration: " << iterations_str);
}
if (iterations > 0xffff) {
- isc_throw(InvalidRdataText, "NSEC3 iterations out of range");
+ isc_throw(InvalidRdataText, "NSEC3 iterations out of range: " <<
+ iterations);
}
vector<uint8_t> salt;
- decodeHex(salthex, salt);
+ if (salthex != "-") { // "-" means a 0-length salt
+ decodeHex(salthex, salt);
+ }
+ if (salt.size() > 255) {
+ isc_throw(InvalidRdataText, "NSEC3 salt is too long: "
+ << salt.size() << " bytes");
+ }
- string nextstr;
- iss >> setw(32) >> nextstr;
vector<uint8_t> next;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid NSEC3 hash algorithm");
+ decodeBase32Hex(nexthash, next);
+ if (next.size() > 255) {
+ isc_throw(InvalidRdataText, "NSEC3 hash is too long: "
+ << next.size() << " bytes");
}
- decodeBase32Hex(nextstr, next);
- uint8_t bitmap[8 * 1024]; // 64k bits
- vector<uint8_t> typebits;
+ // For NSEC3 empty bitmap is possible and allowed.
+ if (iss.eof()) {
+ impl_ = new NSEC3Impl(hashalg, flags, iterations, salt, next,
+ vector<uint8_t>());
+ return;
+ }
+ vector<uint8_t> typebits;
+ uint8_t bitmap[8 * 1024]; // 64k bits
memset(bitmap, 0, sizeof(bitmap));
do {
string type;
- int code;
iss >> type;
if (type.length() != 0) {
try {
- code = RRType(type).getCode();
+ const int code = RRType(type).getCode();
bitmap[code / 8] |= (0x80 >> (code % 8));
} catch (...) {
isc_throw(InvalidRdataText, "Invalid RRtype in NSEC3");
}
}
- } while(!iss.eof());
+ } while (!iss.eof());
for (int window = 0; window < 256; window++) {
int octet;
@@ -126,56 +148,46 @@ NSEC3::NSEC3(const string& nsec3_str) :
}
NSEC3::NSEC3(InputBuffer& buffer, size_t rdata_len) {
+ // NSEC3 RR must have at least 5 octets:
+ // hash algorithm(1), flags(1), iteration(2), saltlen(1)
if (rdata_len < 5) {
- isc_throw(InvalidRdataLength, "NSEC3 too short");
+ isc_throw(DNSMessageFORMERR, "NSEC3 too short, length: " << rdata_len);
}
- uint8_t hashalg = buffer.readUint8();
- uint8_t flags = buffer.readUint8();
- uint16_t iterations = buffer.readUint16();
- rdata_len -= 4;
-
- uint8_t saltlen = buffer.readUint8();
- --rdata_len;
+ const uint8_t hashalg = buffer.readUint8();
+ const uint8_t flags = buffer.readUint8();
+ const uint16_t iterations = buffer.readUint16();
+ const uint8_t saltlen = buffer.readUint8();
+ rdata_len -= 5;
if (rdata_len < saltlen) {
- isc_throw(InvalidRdataLength, "NSEC3 salt too short");
+ isc_throw(DNSMessageFORMERR, "NSEC3 salt length is too large: " <<
+ static_cast<unsigned int>(saltlen));
}
vector<uint8_t> salt(saltlen);
- buffer.readData(&salt[0], saltlen);
- rdata_len -= saltlen;
+ if (saltlen > 0) {
+ buffer.readData(&salt[0], saltlen);
+ rdata_len -= saltlen;
+ }
- uint8_t nextlen = buffer.readUint8();
+ const uint8_t nextlen = buffer.readUint8();
--rdata_len;
-
- if (rdata_len < nextlen) {
- isc_throw(InvalidRdataLength, "NSEC3 next hash too short");
+ if (nextlen == 0 || rdata_len < nextlen) {
+ isc_throw(DNSMessageFORMERR, "NSEC3 invalid hash length: " <<
+ static_cast<unsigned int>(nextlen));
}
vector<uint8_t> next(nextlen);
buffer.readData(&next[0], nextlen);
rdata_len -= nextlen;
- if (rdata_len == 0) {
- isc_throw(InvalidRdataLength, "NSEC3 type bitmap too short");
- }
-
vector<uint8_t> typebits(rdata_len);
- buffer.readData(&typebits[0], rdata_len);
-
- int len = 0;
- for (int i = 0; i < typebits.size(); i += len) {
- if (i + 2 > typebits.size()) {
- isc_throw(DNSMessageFORMERR, "Invalid rdata: "
- "bad NSEC3 type bitmap");
- }
- len = typebits[i + 1];
- if (len > 31) {
- isc_throw(DNSMessageFORMERR, "Invalid rdata: "
- "bad NSEC3 type bitmap");
- }
- i += 2;
+ if (rdata_len > 0) {
+ // Read and parse the bitmaps only when they exist; empty bitmap
+ // is possible for NSEC3.
+ buffer.readData(&typebits[0], rdata_len);
+ checkRRTypeBitmaps("NSEC3", typebits);
}
impl_ = new NSEC3Impl(hashalg, flags, iterations, salt, next, typebits);
@@ -327,10 +339,15 @@ NSEC3::getIterations() const {
return (impl_->iterations_);
}
-vector<uint8_t>&
+const vector<uint8_t>&
NSEC3::getSalt() const {
return (impl_->salt_);
}
+const vector<uint8_t>&
+NSEC3::getNext() const {
+ return (impl_->next_);
+}
+
// END_RDATA_NAMESPACE
// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/nsec3_50.h b/src/lib/dns/rdata/generic/nsec3_50.h
index 5532071..c766ade 100644
--- a/src/lib/dns/rdata/generic/nsec3_50.h
+++ b/src/lib/dns/rdata/generic/nsec3_50.h
@@ -43,7 +43,8 @@ public:
uint8_t getHashalg() const;
uint8_t getFlags() const;
uint16_t getIterations() const;
- std::vector<uint8_t>& getSalt() const;
+ const std::vector<uint8_t>& getSalt() const;
+ const std::vector<uint8_t>& getNext() const;
private:
NSEC3Impl* impl_;
diff --git a/src/lib/dns/rdata/generic/nsec_47.cc b/src/lib/dns/rdata/generic/nsec_47.cc
index 0859edd..72eb946 100644
--- a/src/lib/dns/rdata/generic/nsec_47.cc
+++ b/src/lib/dns/rdata/generic/nsec_47.cc
@@ -26,11 +26,13 @@
#include <dns/rrttl.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/nsec_bitmap.h>
#include <stdio.h>
#include <time.h>
using namespace std;
+using namespace isc::dns::rdata::generic::detail::nsec;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -61,10 +63,9 @@ NSEC::NSEC(const string& nsec_str) :
memset(bitmap, 0, sizeof(bitmap));
do {
string type;
- int code;
iss >> type;
try {
- code = RRType(type).getCode();
+ const int code = RRType(type).getCode();
bitmap[code / 8] |= (0x80 >> (code % 8));
} catch (...) {
isc_throw(InvalidRdataText, "Invalid RRtype in NSEC");
@@ -103,43 +104,7 @@ NSEC::NSEC(InputBuffer& buffer, size_t rdata_len) {
vector<uint8_t> typebits(rdata_len);
buffer.readData(&typebits[0], rdata_len);
-
- int len = 0;
- bool first = true;
- unsigned int block, lastblock = 0;
- for (int i = 0; i < rdata_len; i += len) {
- if (i + 2 > rdata_len) {
- isc_throw(DNSMessageFORMERR, "NSEC RDATA from wire: "
- "incomplete bit map field");
- }
- block = typebits[i];
- len = typebits[i + 1];
- // Check that bitmap window blocks are in the correct order.
- if (!first && block <= lastblock) {
- isc_throw(DNSMessageFORMERR, "NSEC RDATA from wire: Disordered "
- "window blocks found: " << lastblock <<
- " then " << block);
- }
- // Check for legal length
- if (len < 1 || len > 32) {
- isc_throw(DNSMessageFORMERR, "NSEC RDATA from wire: Invalid bitmap "
- "length: " << len);
- }
- // Check for overflow.
- i += 2;
- if (i + len > rdata_len) {
- isc_throw(DNSMessageFORMERR, "NSEC RDATA from wire: bitmap length "
- "too large: " << len);
- }
- // The last octet of the bitmap must be non zero.
- if (typebits[i + len - 1] == 0) {
- isc_throw(DNSMessageFORMERR, "NSEC RDATA from wire: bitmap ending "
- "an all-zero byte");
- }
-
- lastblock = block;
- first = false;
- }
+ checkRRTypeBitmaps("NSEC", typebits);
impl_ = new NSECImpl(nextname, typebits);
}
diff --git a/src/lib/dns/rdata/generic/rrsig_46.cc b/src/lib/dns/rdata/generic/rrsig_46.cc
index 6e6c5fb..c9d1e52 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.cc
+++ b/src/lib/dns/rdata/generic/rrsig_46.cc
@@ -93,8 +93,8 @@ RRSIG::RRSIG(const string& rrsig_str) :
isc_throw(InvalidRdataText, "RRSIG labels out of range");
}
- uint32_t timeexpire = timeFromText(expire_txt);
- uint32_t timeinception = timeFromText(inception_txt);
+ const uint32_t timeexpire = timeFromText32(expire_txt);
+ const uint32_t timeinception = timeFromText32(inception_txt);
vector<uint8_t> signature;
decodeBase64(signaturebuf.str(), signature);
@@ -157,15 +157,12 @@ RRSIG::~RRSIG() {
string
RRSIG::toText() const {
- string expire = timeToText(impl_->timeexpire_);
- string inception = timeToText(impl_->timeinception_);
-
return (impl_->covered_.toText() +
" " + boost::lexical_cast<string>(static_cast<int>(impl_->algorithm_))
+ " " + boost::lexical_cast<string>(static_cast<int>(impl_->labels_))
+ " " + boost::lexical_cast<string>(impl_->originalttl_)
- + " " + expire
- + " " + inception
+ + " " + timeToText32(impl_->timeexpire_)
+ + " " + timeToText32(impl_->timeinception_)
+ " " + boost::lexical_cast<string>(impl_->tag_)
+ " " + impl_->signer_.toText()
+ " " + encodeBase64(impl_->signature_));
diff --git a/src/lib/dns/rrset.h b/src/lib/dns/rrset.h
index c17bd88..926a58f 100644
--- a/src/lib/dns/rrset.h
+++ b/src/lib/dns/rrset.h
@@ -229,8 +229,8 @@ public:
/// \brief Updates the owner name of the \c RRset.
///
- /// \param name A reference to a \c RRTTL class object to be copied as the
- /// new TTL.
+ /// \param name A reference to a \c Name class object to be copied as the
+ /// new name.
virtual void setName(const Name& name) = 0;
/// \brief Updates the TTL of the \c RRset.
@@ -586,8 +586,8 @@ public:
/// internal copy of the \c name involves resource allocation and it
/// fails.
///
- /// \param name A reference to a \c RRTTL class object to be copied as the
- /// new TTL.
+ /// \param name A reference to a \c Name class object to be copied as the
+ /// new name.
virtual void setName(const Name& name);
/// \brief Updates the TTL of the \c RRset.
@@ -718,7 +718,7 @@ public:
void removeRRsig() { rrsig_ = RRsetPtr(); }
/// \brief Return a pointer to this RRset's RRSIG RRset
- RRsetPtr getRRsig() { return (rrsig_); }
+ RRsetPtr getRRsig() const { return (rrsig_); }
private:
RRsetPtr rrsig_;
};
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index 48e4650..246adb7 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -36,6 +36,7 @@ run_unittests_SOURCES += rdata_dnskey_unittest.cc
run_unittests_SOURCES += rdata_ds_unittest.cc
run_unittests_SOURCES += rdata_nsec_unittest.cc
run_unittests_SOURCES += rdata_nsec3_unittest.cc
+run_unittests_SOURCES += rdata_nsecbitmap_unittest.cc
run_unittests_SOURCES += rdata_nsec3param_unittest.cc
run_unittests_SOURCES += rdata_rrsig_unittest.cc
run_unittests_SOURCES += rdata_tsig_unittest.cc
diff --git a/src/lib/dns/tests/dnssectime_unittest.cc b/src/lib/dns/tests/dnssectime_unittest.cc
index 2479a29..b2708cc 100644
--- a/src/lib/dns/tests/dnssectime_unittest.cc
+++ b/src/lib/dns/tests/dnssectime_unittest.cc
@@ -23,48 +23,141 @@
using namespace std;
using namespace isc::dns;
+// See dnssectime.cc
+namespace isc {
+namespace dns {
+namespace dnssectime {
+namespace detail {
+extern int64_t (*gettimeFunction)();
+}
+}
+}
+}
+
namespace {
-TEST(DNSSECTimeTest, fromText) {
+class DNSSECTimeTest : public ::testing::Test {
+protected:
+ ~DNSSECTimeTest() {
+ dnssectime::detail::gettimeFunction = NULL;
+ }
+};
+
+TEST_F(DNSSECTimeTest, fromText) {
+ // In most cases (in practice) the 32-bit and 64-bit versions should
+ // behave identically, so we'll mainly test the 32-bit version, which
+ // will be more commonly used in actual code (because many of the wire
+ // format time field are 32-bit). The subtle cases where these two
+ // return different values will be tested at the end of this test case.
+
// These are bogus and should be rejected
- EXPECT_THROW(timeFromText("2011 101120000"), InvalidTime);
- EXPECT_THROW(timeFromText("201101011200-0"), InvalidTime);
+ EXPECT_THROW(timeFromText32("2011 101120000"), InvalidTime);
+ EXPECT_THROW(timeFromText32("201101011200-0"), InvalidTime);
- // Short length
- EXPECT_THROW(timeFromText("20100223"), InvalidTime);
+ // Short length (or "decimal integer" version of representation;
+ // it's valid per RFC4034, but is not supported in this implementation)
+ EXPECT_THROW(timeFromText32("20100223"), InvalidTime);
// Leap year checks
- EXPECT_THROW(timeFromText("20110229120000"), InvalidTime);
- EXPECT_THROW(timeFromText("21000229120000"), InvalidTime);
- EXPECT_NO_THROW(timeFromText("20000229120000"));
- EXPECT_NO_THROW(timeFromText("20120229120000"));
+ EXPECT_THROW(timeFromText32("20110229120000"), InvalidTime);
+ EXPECT_THROW(timeFromText32("21000229120000"), InvalidTime);
+ EXPECT_NO_THROW(timeFromText32("20000229120000"));
+ EXPECT_NO_THROW(timeFromText32("20120229120000"));
// unusual case: this implementation allows SS=60 for "leap seconds"
- EXPECT_NO_THROW(timeFromText("20110101120060"));
+ EXPECT_NO_THROW(timeFromText32("20110101120060"));
// Out of range parameters
- EXPECT_THROW(timeFromText("19100223214617"), InvalidTime); // YY<1970
- EXPECT_THROW(timeFromText("20110001120000"), InvalidTime); // MM=00
- EXPECT_THROW(timeFromText("20111301120000"), InvalidTime); // MM=13
- EXPECT_THROW(timeFromText("20110100120000"), InvalidTime); // DD=00
- EXPECT_THROW(timeFromText("20110132120000"), InvalidTime); // DD=32
- EXPECT_THROW(timeFromText("20110431120000"), InvalidTime); // 'Apr31'
- EXPECT_THROW(timeFromText("20110101250000"), InvalidTime); // HH=25
- EXPECT_THROW(timeFromText("20110101126000"), InvalidTime); // mm=60
- EXPECT_THROW(timeFromText("20110101120061"), InvalidTime); // SS=61
+ EXPECT_THROW(timeFromText32("19100223214617"), InvalidTime); // YY<1970
+ EXPECT_THROW(timeFromText32("20110001120000"), InvalidTime); // MM=00
+ EXPECT_THROW(timeFromText32("20111301120000"), InvalidTime); // MM=13
+ EXPECT_THROW(timeFromText32("20110100120000"), InvalidTime); // DD=00
+ EXPECT_THROW(timeFromText32("20110132120000"), InvalidTime); // DD=32
+ EXPECT_THROW(timeFromText32("20110431120000"), InvalidTime); // 'Apr31'
+ EXPECT_THROW(timeFromText32("20110101250000"), InvalidTime); // HH=25
+ EXPECT_THROW(timeFromText32("20110101126000"), InvalidTime); // mm=60
+ EXPECT_THROW(timeFromText32("20110101120061"), InvalidTime); // SS=61
+
+ // Feb 7, 06:28:15 UTC 2106 is the possible maximum time that can be
+ // represented as an unsigned 32bit integer without overflow.
+ EXPECT_EQ(4294967295LU, timeFromText32("21060207062815"));
+
+ // After that, timeFromText32() should start returning the second count
+ // modulo 2^32.
+ EXPECT_EQ(0, timeFromText32("21060207062816"));
+ EXPECT_EQ(10, timeFromText32("21060207062826"));
+
+ // On the other hand, the 64-bit version should return monotonically
+ // increasing counters.
+ EXPECT_EQ(4294967296LL, timeFromText64("21060207062816"));
+ EXPECT_EQ(4294967306LL, timeFromText64("21060207062826"));
}
-TEST(DNSSECTimeTest, toText) {
- EXPECT_EQ("19700101000000", timeToText(0));
- EXPECT_EQ("20100311233000", timeToText(1268350200));
+// This helper templated function tells timeToText32 a faked current time.
+// The template parameter is that faked time in the form of int64_t seconds
+// since epoch.
+template <int64_t NOW>
+int64_t
+testGetTime() {
+ return (NOW);
}
-TEST(DNSSECTimeTest, overflow) {
+// Seconds since epoch for the year 10K eve. Commonly used in some tests
+// below.
+const uint64_t YEAR10K_EVE = 253402300799LL;
+
+TEST_F(DNSSECTimeTest, toText) {
+ // Check a basic case with the default (normal) gettimeFunction
+ // based on the "real current time".
+ // Note: this will fail after year 2078, but at that point we won't use
+ // this program anyway:-)
+ EXPECT_EQ("20100311233000", timeToText32(1268350200));
+
+ // Set the current time to: Feb 18 09:04:14 UTC 2012 (an arbitrary choice
+ // in the range of the first half of uint32 since epoch).
+ dnssectime::detail::gettimeFunction = testGetTime<1329555854LL>;
+
+ // Test the "year 2038" problem.
+ // Check the result of toText() for "INT_MIN" in int32_t. It's in the
+ // 68-year range from the faked current time, so the result should be
+ // in year 2038, instead of 1901.
+ EXPECT_EQ("20380119031408", timeToText64(0x80000000L));
+ EXPECT_EQ("20380119031408", timeToText32(0x80000000L));
+
+ // A controversial case: what should we do with "-1"? It's out of range
+ // in future, but according to RFC time before epoch doesn't seem to be
+ // considered "in-range" either. Our toText() implementation handles
+ // this range as a special case and always treats them as future time
+ // until year 2038. This won't be a real issue in practice, though,
+ // since such too large values won't be used in actual deployment by then.
+ EXPECT_EQ("21060207062815", timeToText32(0xffffffffL));
+
+ // After the singular point of year 2038, the first half of uint32 can
+ // point to a future time.
+ // Set the current time to: Apr 1 00:00:00 UTC 2038:
+ dnssectime::detail::gettimeFunction = testGetTime<2153692800LL>;
+ // then time "10" is Feb 7 06:28:26 UTC 2106
+ EXPECT_EQ("21060207062826", timeToText32(10));
+ // in 64-bit, it's 2^32 + 10
+ EXPECT_EQ("21060207062826", timeToText64(0x10000000aLL));
+
+ // After year 2106, the upper half of uint32 can point to past time
+ // (as it should).
+ dnssectime::detail::gettimeFunction = testGetTime<0x10000000aLL>;
+ EXPECT_EQ("21060207062815", timeToText32(0xffffffffL));
+
+ // Try very large time value. Actually it's the possible farthest time
+ // that can be represented in the form of YYYYMMDDHHmmSS.
+ EXPECT_EQ("99991231235959", timeToText64(YEAR10K_EVE));
+ dnssectime::detail::gettimeFunction = testGetTime<YEAR10K_EVE - 10>;
+ EXPECT_EQ("99991231235959", timeToText32(4294197631LU));
+}
+
+TEST_F(DNSSECTimeTest, overflow) {
// Jan 1, Year 10,000.
- if (sizeof(time_t) > 4) {
- EXPECT_THROW(timeToText(static_cast<time_t>(253402300800LL)),
- InvalidTime);
- }
+ EXPECT_THROW(timeToText64(253402300800LL), InvalidTime);
+ dnssectime::detail::gettimeFunction = testGetTime<YEAR10K_EVE - 10>;
+ EXPECT_THROW(timeToText32(4294197632LU), InvalidTime);
}
}
diff --git a/src/lib/dns/tests/message_unittest.cc b/src/lib/dns/tests/message_unittest.cc
index ee1375a..92adbc9 100644
--- a/src/lib/dns/tests/message_unittest.cc
+++ b/src/lib/dns/tests/message_unittest.cc
@@ -297,6 +297,75 @@ TEST_F(MessageTest, removeRRset) {
EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER));
}
+TEST_F(MessageTest, clearQuestionSection) {
+ QuestionPtr q(new Question(Name("www.example.com"), RRClass::IN(),
+ RRType::A()));
+ message_render.addQuestion(q);
+ ASSERT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION));
+
+ message_render.clearSection(Message::SECTION_QUESTION);
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_QUESTION));
+}
+
+
+TEST_F(MessageTest, clearAnswerSection) {
+ // Add two RRsets, check they are present, clear the section,
+ // check if they are gone.
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_aaaa);
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ ASSERT_EQ(3, message_render.getRRCount(Message::SECTION_ANSWER));
+
+ message_render.clearSection(Message::SECTION_ANSWER);
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ANSWER));
+}
+
+TEST_F(MessageTest, clearAuthoritySection) {
+ // Add two RRsets, check they are present, clear the section,
+ // check if they are gone.
+ message_render.addRRset(Message::SECTION_AUTHORITY, rrset_a);
+ message_render.addRRset(Message::SECTION_AUTHORITY, rrset_aaaa);
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+ RRClass::IN(), RRType::A()));
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ ASSERT_EQ(3, message_render.getRRCount(Message::SECTION_AUTHORITY));
+
+ message_render.clearSection(Message::SECTION_AUTHORITY);
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_AUTHORITY));
+}
+
+TEST_F(MessageTest, clearAdditionalSection) {
+ // Add two RRsets, check they are present, clear the section,
+ // check if they are gone.
+ message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_a);
+ message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_aaaa);
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::A()));
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ ASSERT_EQ(3, message_render.getRRCount(Message::SECTION_ADDITIONAL));
+
+ message_render.clearSection(Message::SECTION_ADDITIONAL);
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL));
+}
+
+
TEST_F(MessageTest, badBeginSection) {
// valid cases are tested via other tests
EXPECT_THROW(message_render.beginSection(Message::SECTION_QUESTION),
@@ -311,6 +380,63 @@ TEST_F(MessageTest, badEndSection) {
EXPECT_THROW(message_render.endSection(bogus_section), OutOfRange);
}
+TEST_F(MessageTest, appendSection) {
+ Message target(Message::RENDER);
+
+ // Section check
+ EXPECT_THROW(target.appendSection(bogus_section, message_render),
+ OutOfRange);
+
+ // Make sure nothing is copied if there is nothing to copy
+ target.appendSection(Message::SECTION_QUESTION, message_render);
+ EXPECT_EQ(0, target.getRRCount(Message::SECTION_QUESTION));
+ target.appendSection(Message::SECTION_ANSWER, message_render);
+ EXPECT_EQ(0, target.getRRCount(Message::SECTION_ANSWER));
+ target.appendSection(Message::SECTION_AUTHORITY, message_render);
+ EXPECT_EQ(0, target.getRRCount(Message::SECTION_AUTHORITY));
+ target.appendSection(Message::SECTION_ADDITIONAL, message_render);
+ EXPECT_EQ(0, target.getRRCount(Message::SECTION_ADDITIONAL));
+
+ // Now add some data, copy again, and see if it got added
+ message_render.addQuestion(Question(Name("test.example.com"),
+ RRClass::IN(), RRType::A()));
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+ message_render.addRRset(Message::SECTION_AUTHORITY, rrset_a);
+ message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_a);
+ message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_aaaa);
+
+ target.appendSection(Message::SECTION_QUESTION, message_render);
+ EXPECT_EQ(1, target.getRRCount(Message::SECTION_QUESTION));
+
+ target.appendSection(Message::SECTION_ANSWER, message_render);
+ EXPECT_EQ(2, target.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+
+ target.appendSection(Message::SECTION_AUTHORITY, message_render);
+ EXPECT_EQ(2, target.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_AUTHORITY, test_name,
+ RRClass::IN(), RRType::A()));
+
+ target.appendSection(Message::SECTION_ADDITIONAL, message_render);
+ EXPECT_EQ(3, target.getRRCount(Message::SECTION_ADDITIONAL));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::AAAA()));
+
+ // One more test, test to see if the section gets added, not replaced
+ Message source2(Message::RENDER);
+ source2.addRRset(Message::SECTION_ANSWER, rrset_aaaa);
+ target.appendSection(Message::SECTION_ANSWER, source2);
+ EXPECT_EQ(3, target.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::AAAA()));
+
+}
+
TEST_F(MessageTest, fromWire) {
factoryFromFile(message_parse, "message_fromWire1");
EXPECT_EQ(0x1035, message_parse.getQid());
diff --git a/src/lib/dns/tests/name_unittest.cc b/src/lib/dns/tests/name_unittest.cc
index b79fd74..5daba9c 100644
--- a/src/lib/dns/tests/name_unittest.cc
+++ b/src/lib/dns/tests/name_unittest.cc
@@ -290,7 +290,7 @@ TEST_F(NameTest, assignment) {
// Self assignment
copy = copy;
- EXPECT_EQ(copy, example_name);
+ EXPECT_EQ(example_name, copy);
}
TEST_F(NameTest, toText) {
diff --git a/src/lib/dns/tests/rdata_mx_unittest.cc b/src/lib/dns/tests/rdata_mx_unittest.cc
index 4491f86..dd7677d 100644
--- a/src/lib/dns/tests/rdata_mx_unittest.cc
+++ b/src/lib/dns/tests/rdata_mx_unittest.cc
@@ -74,12 +74,9 @@ TEST_F(Rdata_MX_Test, toWireRenderer) {
TEST_F(Rdata_MX_Test, toWireBuffer) {
renderer.writeName(Name("example.com"));
rdata_mx.toWire(obuffer);
-}
-TEST_F(Rdata_MX_Test, DISABLED_toWireBuffer) {
-// XXX: does not pass
vector<unsigned char> data;
- UnitTestUtil::readWireData("rdata_mx_toWire1", data);
+ UnitTestUtil::readWireData("rdata_mx_toWire2", data);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
obuffer.getLength(), &data[0], data.size());
}
diff --git a/src/lib/dns/tests/rdata_nsec3_unittest.cc b/src/lib/dns/tests/rdata_nsec3_unittest.cc
index 6b3a0b5..749e262 100644
--- a/src/lib/dns/tests/rdata_nsec3_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3_unittest.cc
@@ -46,59 +46,136 @@ public:
string nsec3_txt;
};
+TEST_F(Rdata_NSEC3_Test, fromText) {
+ // A normal case: the test constructor should successfully parse the
+ // text and construct nsec3_txt. It will be tested against the wire format
+ // representation in the createFromWire test.
+
+ // Numeric parameters have possible maximum values. Unusual, but must
+ // be accepted.
+ EXPECT_NO_THROW(generic::NSEC3("255 255 65535 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
+ "NS SOA RRSIG DNSKEY NSEC3PARAM"));
+
+ // 0-length salt
+ EXPECT_EQ(0, generic::NSEC3("1 1 1 - H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
+ "A").getSalt().size());
+
+ // salt that has the possible max length
+ EXPECT_EQ(255, generic::NSEC3("1 1 1 " + string(255 * 2, '0') +
+ " H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
+ "NS").getSalt().size());
+
+ // hash that has the possible max length (see badText about the magic
+ // numbers)
+ EXPECT_EQ(255, generic::NSEC3("1 1 1 D399EAAB " +
+ string((255 * 8) / 5, '0') +
+ " NS").getNext().size());
+
+ // type bitmap is empty. it's possible and allowed for NSEC3.
+ EXPECT_NO_THROW(generic::NSEC3(
+ "1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6"));
+}
+
TEST_F(Rdata_NSEC3_Test, toText) {
const generic::NSEC3 rdata_nsec3(nsec3_txt);
EXPECT_EQ(nsec3_txt, rdata_nsec3.toText());
}
TEST_F(Rdata_NSEC3_Test, badText) {
- EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1 1 ADDAFEEE "
- "0123456789ABCDEFGHIJKLMNOPQRSTUV "
- "BIFF POW SPOON"),
+ EXPECT_THROW(generic::NSEC3("1 1 1 ADDAFEEE "
+ "0123456789ABCDEFGHIJKLMNOPQRSTUV "
+ "BIFF POW SPOON"),
InvalidRdataText);
- EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1 1 ADDAFEE "
- "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW "
- "A NS SOA"),
+ EXPECT_THROW(generic::NSEC3("1 1 1 ADDAFEE "
+ "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW A NS SOA"),
BadValue); // bad hex
- EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1 1 ADDAFEEE "
- "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW "
- "A NS SOA"),
+ EXPECT_THROW(generic::NSEC3("1 1 1 -- H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
+ "A"),
+ BadValue); // this shouldn't be confused a valid empty salt
+ EXPECT_THROW(generic::NSEC3("1 1 1 ADDAFEEE "
+ "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW A NS SOA"),
BadValue); // bad base32hex
- EXPECT_THROW(generic::NSEC3 rdata_nsec3("1000000 1 1 ADDAFEEE "
- "0123456789ABCDEFGHIJKLMNOPQRSTUV "
- "A NS SOA"),
+ EXPECT_THROW(generic::NSEC3("1000000 1 1 ADDAFEEE "
+ "0123456789ABCDEFGHIJKLMNOPQRSTUV A NS SOA"),
InvalidRdataText);
- EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1000000 1 ADDAFEEE "
- "0123456789ABCDEFGHIJKLMNOPQRSTUV "
- "A NS SOA"),
+ EXPECT_THROW(generic::NSEC3("1 1000000 1 ADDAFEEE "
+ "0123456789ABCDEFGHIJKLMNOPQRSTUV A NS SOA"),
InvalidRdataText);
- EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1 1000000 ADDAFEEE "
- "0123456789ABCDEFGHIJKLMNOPQRSTUV "
- "A NS SOA"),
+ EXPECT_THROW(generic::NSEC3("1 1 1000000 ADDAFEEE "
+ "0123456789ABCDEFGHIJKLMNOPQRSTUV A NS SOA"),
InvalidRdataText);
-}
-TEST_F(Rdata_NSEC3_Test, DISABLED_badText) { // this currently fails
+ // There should be a space between "1" and "D399EAAB" (salt)
EXPECT_THROW(generic::NSEC3(
"1 1 1D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
"NS SOA RRSIG DNSKEY NSEC3PARAM"), InvalidRdataText);
+
+ // Salt is too long (possible max + 1 bytes)
+ EXPECT_THROW(generic::NSEC3("1 1 1 " + string(256 * 2, '0') +
+ " H9RSFB7FPF2L8HG35CMPC765TDK23RP6 NS"),
+ InvalidRdataText);
+
+ // Hash is too long. Max = 255 bytes, base32-hex converts each 5 bytes
+ // of the original to 8 characters, so 260 * 8 / 5 is the smallest length
+ // of the encoded string that exceeds the max and doesn't require padding.
+ EXPECT_THROW(generic::NSEC3("1 1 1 D399EAAB " + string((260 * 8) / 5, '0') +
+ " NS"),
+ InvalidRdataText);
}
TEST_F(Rdata_NSEC3_Test, createFromWire) {
+ // Normal case
const generic::NSEC3 rdata_nsec3(nsec3_txt);
EXPECT_EQ(0, rdata_nsec3.compare(
*rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
"rdata_nsec3_fromWire1")));
- // Too short RDLENGTH
+ // A valid NSEC3 RR with empty type bitmap.
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire15.wire"));
+
+ // Too short RDLENGTH: it doesn't even contain the first 5 octets.
EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
- "rdata_nsec3_fromWire2"),
- InvalidRdataLength);
+ "rdata_nsec3_fromWire2.wire"),
+ DNSMessageFORMERR);
+
+ // Invalid bitmap cases are tested in Rdata_NSECBITMAP_Test.
- // Invalid type bits
+ // salt length is too large
EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
- "rdata_nsec3_fromWire3"),
+ "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"),
+ DNSMessageFORMERR);
+
+ // empty hash. invalid.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire14.wire"),
+ DNSMessageFORMERR);
+
+ //
+ // Short buffer cases. The data is valid NSEC3 RDATA, but the buffer
+ // is trimmed at the end. All cases should result in an exception from
+ // the buffer class.
+ vector<uint8_t> data;
+ UnitTestUtil::readWireData("rdata_nsec3_fromWire1", data);
+ const uint16_t rdlen = (data.at(0) << 8) + data.at(1);
+ for (int i = 0; i < rdlen; ++i) {
+ // intentionally construct a short buffer
+ InputBuffer b(&data[0] + 2, i);
+ EXPECT_THROW(createRdata(RRType::NSEC3(), RRClass::IN(), b, 39),
+ InvalidBufferPosition);
+ }
}
TEST_F(Rdata_NSEC3_Test, toWireRenderer) {
diff --git a/src/lib/dns/tests/rdata_nsec_unittest.cc b/src/lib/dns/tests/rdata_nsec_unittest.cc
index f9ad027..8286dee 100644
--- a/src/lib/dns/tests/rdata_nsec_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec_unittest.cc
@@ -62,46 +62,7 @@ TEST_F(Rdata_NSEC_Test, createFromWire_NSEC) {
"rdata_nsec_fromWire2"),
DNSMessageFORMERR);
- EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire3"),
- DNSMessageFORMERR);
-
- // A malformed NSEC bitmap length field that could cause overflow.
- EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire4.wire"),
- DNSMessageFORMERR);
-
- // The bitmap field is incomplete (only the first byte is included)
- EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire5.wire"),
- DNSMessageFORMERR);
-
- // Bitmap length is 0, which is invalid.
- EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire6.wire"),
- DNSMessageFORMERR);
-
- // A boundary case: longest possible bitmaps (32 maps). This should be
- // accepted.
- EXPECT_NO_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire7.wire"));
-
- // Another boundary condition: 33 bitmaps, which should be rejected.
- EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire8.wire"),
- DNSMessageFORMERR);
-
- // Disordered bitmap window blocks.
- EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire9.wire"),
- DNSMessageFORMERR);
-
- // Bitmap ending with all-zero bytes. Not necessarily harmful except
- // the additional overhead of parsing, but invalid according to the
- // spec anyway.
- EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
- "rdata_nsec_fromWire10.wire"),
- DNSMessageFORMERR);
+ // Invalid bitmap cases are tested in Rdata_NSECBITMAP_Test.
}
TEST_F(Rdata_NSEC_Test, toWireRenderer_NSEC) {
diff --git a/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc b/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
new file mode 100644
index 0000000..8a90878
--- /dev/null
+++ b/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
@@ -0,0 +1,103 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/rdata_unittest.h>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+class Rdata_NSECBITMAP_Test : public RdataTest {
+ // there's nothing to specialize
+};
+
+// Tests against various types of bogus NSEC/NSEC3 type bitmaps.
+// The syntax and semantics are common for both RR types, and our
+// implementation of that part is shared, so in theory it should be sufficient
+// to test for only one RR type. But we check for both just in case.
+TEST_F(Rdata_NSECBITMAP_Test, createFromWire_NSEC) {
+ // A malformed NSEC bitmap length field that could cause overflow.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire4.wire"),
+ DNSMessageFORMERR);
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire4.wire"),
+ DNSMessageFORMERR);
+
+ // The bitmap field is incomplete (only the first byte is included)
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire5.wire"),
+ DNSMessageFORMERR);
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire5.wire"),
+ DNSMessageFORMERR);
+
+ // Bitmap length is 0, which is invalid.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire6.wire"),
+ DNSMessageFORMERR);
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire6.wire"),
+ DNSMessageFORMERR);
+
+ // Too large bitmap length with a short buffer.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire3"),
+ DNSMessageFORMERR);
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire3"),
+ DNSMessageFORMERR);
+
+ // A boundary case: longest possible bitmaps (32 maps). This should be
+ // accepted.
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire7.wire"));
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire7.wire"));
+
+ // Another boundary condition: 33 bitmaps, which should be rejected.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire8.wire"),
+ DNSMessageFORMERR);
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire8.wire"),
+ DNSMessageFORMERR);
+
+ // Disordered bitmap window blocks.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire9.wire"),
+ DNSMessageFORMERR);
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire9.wire"),
+ DNSMessageFORMERR);
+
+ // Bitmap ending with all-zero bytes. Not necessarily harmful except
+ // the additional overhead of parsing, but invalid according to the
+ // spec anyway.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire10.wire"),
+ DNSMessageFORMERR);
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire10.wire"),
+ DNSMessageFORMERR);
+}
+}
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index f6eb90b..1aaddb6 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -8,9 +8,16 @@ BUILT_SOURCES += rdata_nsec_fromWire4.wire rdata_nsec_fromWire5.wire
BUILT_SOURCES += rdata_nsec_fromWire6.wire rdata_nsec_fromWire7.wire
BUILT_SOURCES += rdata_nsec_fromWire8.wire rdata_nsec_fromWire9.wire
BUILT_SOURCES += rdata_nsec_fromWire10.wire
+BUILT_SOURCES += rdata_nsec3_fromWire2.wire
+BUILT_SOURCES += rdata_nsec3_fromWire4.wire rdata_nsec3_fromWire5.wire
+BUILT_SOURCES += rdata_nsec3_fromWire6.wire rdata_nsec3_fromWire7.wire
+BUILT_SOURCES += rdata_nsec3_fromWire8.wire rdata_nsec3_fromWire9.wire
+BUILT_SOURCES += rdata_nsec3_fromWire10.wire rdata_nsec3_fromWire11.wire
+BUILT_SOURCES += rdata_nsec3_fromWire12.wire rdata_nsec3_fromWire13.wire
+BUILT_SOURCES += rdata_nsec3_fromWire14.wire rdata_nsec3_fromWire15.wire
BUILT_SOURCES += rdata_rrsig_fromWire2.wire
BUILT_SOURCES += rdata_soa_toWireUncompressed.wire
-BUILT_SOURCES += rdata_txt_fromWire2.wire rdata_txt_fromWire3.wire
+BUILT_SOURCES += rdata_txt_fromWire2.wire rdata_txt_fromWire3.wire
BUILT_SOURCES += rdata_txt_fromWire4.wire rdata_txt_fromWire5.wire
BUILT_SOURCES += rdata_tsig_fromWire1.wire rdata_tsig_fromWire2.wire
BUILT_SOURCES += rdata_tsig_fromWire3.wire rdata_tsig_fromWire4.wire
@@ -43,14 +50,21 @@ EXTRA_DIST += name_toWire5.spec name_toWire6.spec
EXTRA_DIST += question_fromWire question_toWire1 question_toWire2
EXTRA_DIST += rdata_cname_fromWire rdata_dname_fromWire rdata_dnskey_fromWire
EXTRA_DIST += rdata_ds_fromWire rdata_in_a_fromWire rdata_in_aaaa_fromWire
-EXTRA_DIST += rdata_mx_fromWire rdata_mx_toWire1 rdata_ns_fromWire
-EXTRA_DIST += rdata_nsec3_fromWire1 rdata_nsec3_fromWire2 rdata_nsec3_fromWire3
-EXTRA_DIST += rdata_nsec3param_fromWire1 rdata_nsec_fromWire1
-EXTRA_DIST += rdata_nsec_fromWire2 rdata_nsec_fromWire3
+EXTRA_DIST += rdata_mx_fromWire rdata_mx_toWire1 rdata_mx_toWire2
+EXTRA_DIST += rdata_ns_fromWire
+EXTRA_DIST += rdata_nsec_fromWire1 rdata_nsec_fromWire2 rdata_nsec_fromWire3
EXTRA_DIST += rdata_nsec_fromWire4.spec rdata_nsec_fromWire5.spec
EXTRA_DIST += rdata_nsec_fromWire6.spec rdata_nsec_fromWire7.spec
EXTRA_DIST += rdata_nsec_fromWire8.spec rdata_nsec_fromWire9.spec
EXTRA_DIST += rdata_nsec_fromWire10.spec
+EXTRA_DIST += rdata_nsec3param_fromWire1
+EXTRA_DIST += rdata_nsec3_fromWire1 rdata_nsec3_fromWire3
+EXTRA_DIST += rdata_nsec3_fromWire4.spec rdata_nsec3_fromWire5.spec
+EXTRA_DIST += rdata_nsec3_fromWire6.spec rdata_nsec3_fromWire7.spec
+EXTRA_DIST += rdata_nsec3_fromWire8.spec rdata_nsec3_fromWire9.spec
+EXTRA_DIST += rdata_nsec3_fromWire10.spec rdata_nsec3_fromWire11.spec
+EXTRA_DIST += rdata_nsec3_fromWire12.spec rdata_nsec3_fromWire13.spec
+EXTRA_DIST += rdata_nsec3_fromWire14.spec rdata_nsec3_fromWire15.spec
EXTRA_DIST += rdata_opt_fromWire rdata_rrsig_fromWire1
EXTRA_DIST += rdata_rrsig_fromWire2.spec
EXTRA_DIST += rdata_soa_fromWire rdata_soa_toWireUncompressed.spec
@@ -68,6 +82,7 @@ EXTRA_DIST += rdata_tsig_fromWire9.spec
EXTRA_DIST += rdata_tsig_toWire1.spec rdata_tsig_toWire2.spec
EXTRA_DIST += rdata_tsig_toWire3.spec rdata_tsig_toWire4.spec
EXTRA_DIST += rdata_tsig_toWire5.spec
+EXTRA_DIST += rdata_nsec3_fromWire2.spec
.spec.wire:
./gen-wiredata.py -o $@ $<
diff --git a/src/lib/dns/tests/testdata/gen-wiredata.py.in b/src/lib/dns/tests/testdata/gen-wiredata.py.in
index e59063f..645430c 100755
--- a/src/lib/dns/tests/testdata/gen-wiredata.py.in
+++ b/src/lib/dns/tests/testdata/gen-wiredata.py.in
@@ -52,8 +52,11 @@ rdict_rrclass = dict([(dict_rrclass[k], k.upper()) for k in \
dict_rrclass.keys()])
dict_algorithm = { 'rsamd5' : 1, 'dh' : 2, 'dsa' : 3, 'ecc' : 4,
'rsasha1' : 5 }
+dict_nsec3_algorithm = { 'reserved' : 0, 'sha1' : 1 }
rdict_algorithm = dict([(dict_algorithm[k], k.upper()) for k in \
dict_algorithm.keys()])
+rdict_nsec3_algorithm = dict([(dict_nsec3_algorithm[k], k.upper()) for k in \
+ dict_nsec3_algorithm.keys()])
header_xtables = { 'qr' : dict_qr, 'opcode' : dict_opcode,
'rcode' : dict_rcode }
@@ -274,14 +277,16 @@ class TXT:
' ' if len(wirestring_list[i]) > 0 else '',
wirestring_list[i]))
-class NSEC:
- rdlen = -1 # auto-calculate
- nextname = 'next.example.com'
+class NSECBASE:
+ '''Implements rendering NSEC/NSEC3 type bitmaps commonly used for
+ these RRs. The NSEC and NSEC3 classes will be inherited from this
+ class.'''
nbitmap = 1 # number of bitmaps
block = 0
- maplen = -1 # default bitmap length, auto-calculate
+ maplen = None # default bitmap length, auto-calculate
bitmap = '040000000003' # an arbtrarily chosen bitmap sample
def dump(self, f):
+ # first, construct the bitmpa data
block_list = []
maplen_list = []
bitmap_list = []
@@ -296,30 +301,72 @@ class NSEC:
maplen_list.append(self.__dict__[key_maplen])
else:
maplen_list.append(self.maplen)
- if maplen_list[-1] < 0:
+ if maplen_list[-1] is None: # calculate it if not specified
maplen_list[-1] = int(len(bitmap_list[-1]) / 2)
key_block = 'block' + str(i)
if key_block in self.__dict__:
block_list.append(self.__dict__[key_block])
else:
block_list.append(self.block)
+
+ # dump RR-type specific part (NSEC or NSEC3)
+ self.dump_fixedpart(f, 2 * self.nbitmap + \
+ int(len(''.join(bitmap_list)) / 2))
+
+ # dump the bitmap
+ for i in range(0, self.nbitmap):
+ f.write('# Bitmap: Block=%d, Length=%d\n' %
+ (block_list[i], maplen_list[i]))
+ f.write('%02x %02x %s\n' %
+ (block_list[i], maplen_list[i], bitmap_list[i]))
+
+class NSEC(NSECBASE):
+ rdlen = None # auto-calculate
+ nextname = 'next.example.com'
+ def dump_fixedpart(self, f, bitmap_totallen):
name_wire = encode_name(self.nextname)
- rdlen = self.rdlen
- if rdlen < 0:
+ if self.rdlen is None:
# if rdlen needs to be calculated, it must be based on the bitmap
# length, because the configured maplen can be fake.
- rdlen = int(len(name_wire) / 2) + 2 * self.nbitmap
- rdlen = rdlen + int(len(''.join(bitmap_list)) / 2)
- f.write('\n# NSEC RDATA (RDLEN=%d)\n' % rdlen)
- f.write('%04x\n' % rdlen);
+ self.rdlen = int(len(name_wire) / 2) + bitmap_totallen
+ f.write('\n# NSEC RDATA (RDLEN=%d)\n' % self.rdlen)
+ f.write('%04x\n' % self.rdlen);
f.write('# Next Name=%s (%d bytes)\n' % (self.nextname,
int(len(name_wire) / 2)))
f.write('%s\n' % name_wire)
- for i in range(0, self.nbitmap):
- f.write('# Bitmap: Block=%d, Length=%d\n' %
- (block_list[i], maplen_list[i]))
- f.write('%02x %02x %s\n' %
- (block_list[i], maplen_list[i], bitmap_list[i]))
+
+class NSEC3(NSECBASE):
+ rdlen = None # auto-calculate
+ hashalg = 1 # SHA-1
+ optout = False # opt-out flag
+ mbz = 0 # other flag fields (none defined yet)
+ iterations = 1
+ saltlen = 5
+ salt = 's' * saltlen
+ hashlen = 20
+ hash = 'h' * hashlen
+ def dump_fixedpart(self, f, bitmap_totallen):
+ if self.rdlen is None:
+ # if rdlen needs to be calculated, it must be based on the bitmap
+ # length, because the configured maplen can be fake.
+ self.rdlen = 4 + 1 + len(self.salt) + 1 + len(self.hash) \
+ + bitmap_totallen
+ f.write('\n# NSEC3 RDATA (RDLEN=%d)\n' % self.rdlen)
+ f.write('%04x\n' % self.rdlen)
+ optout_val = 1 if self.optout else 0
+ f.write('# Hash Alg=%s, Opt-Out=%d, Other Flags=%0x, Iterations=%d\n' %
+ (code_totext(self.hashalg, rdict_nsec3_algorithm),
+ optout_val, self.mbz, self.iterations))
+ f.write('%02x %02x %04x\n' %
+ (self.hashalg, (self.mbz << 1) | optout_val, self.iterations))
+ f.write("# Salt Len=%d, Salt='%s'\n" % (self.saltlen, self.salt))
+ f.write('%02x%s%s\n' % (self.saltlen,
+ ' ' if len(self.salt) > 0 else '',
+ encode_string(self.salt)))
+ f.write("# Hash Len=%d, Hash='%s'\n" % (self.hashlen, self.hash))
+ f.write('%02x%s%s\n' % (self.hashlen,
+ ' ' if len(self.hash) > 0 else '',
+ encode_string(self.hash)))
class RRSIG:
rdlen = -1 # auto-calculate
@@ -415,7 +462,7 @@ def get_config_param(section):
'question' : (DNSQuestion, question_xtables),
'edns' : (EDNS, {}), 'soa' : (SOA, {}), 'txt' : (TXT, {}),
'rrsig' : (RRSIG, {}), 'nsec' : (NSEC, {}),
- 'tsig' : (TSIG, {}) }
+ 'nsec3' : (NSEC3, {}), 'tsig' : (TSIG, {}) }
s = section
m = re.match('^([^:]+)/\d+$', section)
if m:
diff --git a/src/lib/dns/tests/testdata/rdata_mx_toWire2 b/src/lib/dns/tests/testdata/rdata_mx_toWire2
new file mode 100644
index 0000000..ebd2f27
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_mx_toWire2
@@ -0,0 +1,12 @@
+#
+# compressed MX RDATA stored in an output buffer
+#
+# sentinel name: example.com.
+# 0 1 2 3 4 5 6 7 8 9 10 1 2 (bytes)
+#(7) e x a m p l e (3) c o m .
+ 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# PREFERENCE: 10
+ 00 0a
+# EXCHANGE: not compressed
+#(4) m x ptr=0
+ 02 6d 78 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire1.spec
new file mode 100644
index 0000000..39a78d7
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire1.spec
@@ -0,0 +1,7 @@
+#
+# A malformed NSEC3 RDATA: bit map length is too large, causing overflow
+#
+
+[custom]
+sections: nsec3
+[nsec3]
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.spec
new file mode 100644
index 0000000..30417f5
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.spec
@@ -0,0 +1,8 @@
+#
+# An invalid NSEC3 RDATA: a bitmap block containing empty bytes
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+bitmap: '01000000'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.spec
new file mode 100644
index 0000000..80ec59f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.spec
@@ -0,0 +1,8 @@
+#
+# An invalid NSEC3 RDATA: Saltlen is too large
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+rdlen: 7
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.spec
new file mode 100644
index 0000000..1e01655
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.spec
@@ -0,0 +1,9 @@
+#
+# An invalid NSEC3 RDATA: Hash length is too large
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+# only contains the first byte of hash
+rdlen: 12
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.spec
new file mode 100644
index 0000000..fcc9d53
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.spec
@@ -0,0 +1,9 @@
+#
+# A valid (but unusual) NSEC3 RDATA: salt is empty.
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+saltlen: 0
+salt: ''
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.spec
new file mode 100644
index 0000000..a0550d5
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.spec
@@ -0,0 +1,9 @@
+#
+# An invalid NSEC3 RDATA: empty hash
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+hashlen: 0
+hash: ''
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.spec
new file mode 100644
index 0000000..4993e03
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.spec
@@ -0,0 +1,10 @@
+#
+# NSEC3 RDATA with empty type bitmap. It's okay.
+# The test data includes bytes for a bitmap field, but RDLEN indicates
+# it's not part of the RDATA and so it will be ignored.
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+rdlen: 31
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2 b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2
deleted file mode 100644
index 0965a27..0000000
--- a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2
+++ /dev/null
@@ -1,12 +0,0 @@
-#
-# NSEC3 RDATA with a bogus RDLEN (too short)
-#
-
-# RDLENGTH, 29 bytes (should be 39)
-00 1e
-
-# NSEC3 record:
-# 1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 NS SOA RRSIG DNSKEY NSEC3PARAM
-01 01 00 01 04 d3 99 ea ab 14 8a 77 c7 ac ef cb
-c5 54 46 03 2b 2d 96 1c c5 eb 68 21 ef 26 00 07
-22 00 00 00 00 02 90
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec
new file mode 100644
index 0000000..0b6a5af
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec
@@ -0,0 +1,9 @@
+#
+# A malformed NSEC3 RDATA: RDLEN indicates it doesn't even contain the fixed
+# 5 octects
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+rdlen: 4
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.spec
new file mode 100644
index 0000000..06d6eb4
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.spec
@@ -0,0 +1,9 @@
+#
+# A malformed NSEC3 RDATA: bit map length is too large, causing overflow
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+maplen: 31
+bitmap: '01'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.spec
new file mode 100644
index 0000000..2d5713c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.spec
@@ -0,0 +1,13 @@
+#
+# A malformed NSEC3 RDATA: incomplete bit map field
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+# only containing the block field of the bitmap
+rdlen: 32
+#dummy data
+maplen: 31
+#dummy data
+bitmap: '00'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.spec
new file mode 100644
index 0000000..36e9e59
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.spec
@@ -0,0 +1,11 @@
+#
+# A malformed NSEC3 RDATA: bit map length being 0
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+rdlen: 33
+maplen: 0
+# dummy data:
+bitmap: '01'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.spec
new file mode 100644
index 0000000..338c0c9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.spec
@@ -0,0 +1,9 @@
+#
+# NSEC3 RDATA with a longest bitmap field (32 bitmap bytes)
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+maplen: 32
+bitmap: '0101010101010101010101010101010101010101010101010101010101010101'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.spec
new file mode 100644
index 0000000..041714e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.spec
@@ -0,0 +1,9 @@
+#
+# An invalid NSEC3 RDATA with an oversized bitmap field (33 bitmap bytes)
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+maplen: 33
+bitmap: '010101010101010101010101010101010101010101010101010101010101010101'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.spec
new file mode 100644
index 0000000..b04c84f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.spec
@@ -0,0 +1,10 @@
+#
+# An invalid NSEC3 RDATA: disordered bitmap blocks
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+nbitmap: 2
+block0: 2
+block1: 1
diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am
index 900f11b..d941b01 100644
--- a/src/lib/log/Makefile.am
+++ b/src/lib/log/Makefile.am
@@ -1,21 +1,18 @@
-if USE_LOG4CXX
SUBDIRS = . compiler tests
-endif
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CPPFLAGS += $(LOG4CXX_INCLUDES)
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
CLEANFILES = *.gcno *.gcda
lib_LTLIBRARIES = liblog.la
liblog_la_SOURCES =
-liblog_la_SOURCES += dbglevels.h
+liblog_la_SOURCES += debug_levels.h logger_levels.h
liblog_la_SOURCES += dummylog.h dummylog.cc
-if USE_LOG4CXX
liblog_la_SOURCES += filename.h filename.cc
liblog_la_SOURCES += logger.cc logger.h
+liblog_la_SOURCES += logger_impl.cc logger_impl.h
liblog_la_SOURCES += logger_support.cc logger_support.h
liblog_la_SOURCES += messagedef.cc messagedef.h
liblog_la_SOURCES += message_dictionary.cc message_dictionary.h
@@ -25,10 +22,11 @@ liblog_la_SOURCES += message_reader.cc message_reader.h
liblog_la_SOURCES += message_types.h
liblog_la_SOURCES += root_logger_name.cc root_logger_name.h
liblog_la_SOURCES += strutil.h strutil.cc
-liblog_la_SOURCES += xdebuglevel.cc xdebuglevel.h
-liblog_la_LDFLAGS = $(LOG4CXX_LDFLAGS)
-endif
+EXTRA_DIST = README
+EXTRA_DIST += messagedef.mes
+EXTRA_DIST += logger_impl_log4cxx.cc logger_impl_log4cxx.h
+EXTRA_DIST += xdebuglevel.cc xdebuglevel.h
# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS)
diff --git a/src/lib/log/README b/src/lib/log/README
new file mode 100644
index 0000000..072649e
--- /dev/null
+++ b/src/lib/log/README
@@ -0,0 +1,376 @@
+This directory holds the first release of the logging system.
+
+Basic Ideas
+===========
+The BIND-10 logging system merges two ideas:
+
+* A hierarchical logging system similar to that used in Java (i.e. log4j)
+* Separation of message definitions and text
+
+
+Hierarchical Logging System
+===========================
+When a program writes a message to the logging system, it does so using an
+instance of the Logger class. As well as performing the write of the message,
+the logger identifies the source of the message: different sources can write
+to different destinations and can log different severities of messages.
+For example, the "cache" logger could write messages of DEBUG severity or
+above to a file while all other components write messages of "INFO" severity
+or above to the Syslog file.
+
+The loggers are hierarchical in that each logger is the child of another
+logger. The top of the hierarchy is the root logger, which does not have
+a parent. The point of the hierarchy is that unless a logger is explicitly
+assigned an attribute (such as severity of message being logger), it picks
+it up from the parent. (In BIND-10, there is the root logger (named after
+the program) and every other logger is a child of that.) So in the example
+above, the INFO/Syslog attributes could be associated with the root logger
+while the DEBUG/file attributes are associated with the "cache" logger.
+
+
+Separation of Messages Definitions And Text
+===========================================
+The reason for this is to allow the message text to be overridden by versions
+in a local language. To do this, each message is identified by an identifier
+e.g. "OPENIN". Within the program, this is the symbol passed to the logging
+system. The logger system uses the symbol as an index into a dictionary to
+retrieve the message associated with it (e.g. "unable to open %s for input").
+substitutes any message parameters (in this example, the string that is an
+invalid filename) and logs it to the destination.
+
+In the BIND-10 system, a set of default messages are linked into the
+program. At run-time. each program reads a message file, updating the
+stored definitions; this updated text is logged. However, to aid support,
+the message identifier so in the example above, the message finally logged
+would be something like:
+
+ OPENIN, unable to open a.txt for input
+
+
+Using The System
+================
+The steps in using the system are:
+
+1. Create a message file. This defines messages by an identification - a
+ mnemonic for the message, typically 6-12 characters long - and a message.
+ The file is described in more detail below.
+
+ Ideally the file should have a file type of ".msg".
+
+2. Run it through the message compiler to produce the .h and .cc files. It
+ is intended that this step be included in the build process. However,
+ for now run the compiler (found in the "compiler" subdirectory) manually.
+ The only argument is the name of the message file: it will produce as
+ output two files, having the same name as the input file but with file
+ types of ".h" and ".cc".
+
+ The compiler is built in the "compiler" subdirectory of the "src/lib/log"
+ directory.
+
+3. Include the .h file in your source code to define message symbols, and
+ make sure that the .cc file is compiled and linked into your program -
+ static initialization will add the symbols to the global dictionary.
+
+4. Declare loggers in your code and use them to log messages. This is
+ described in more detail below.
+
+5. To set the debug level and run-time message file, call initLogger (declared
+ in logger_support.h) in the main program unit. This is a temporary solution
+ for Year 2, and will be replaced at a later date, the information coming
+ from the configuration database.
+
+
+Message Files
+=============
+
+File Contents and Format
+------------------------
+A message file is a file containing message definitions. Typically there
+will be one message file for each component that declares message symbols.
+An example file could be:
+
+-- BEGIN --
+
+# Example message file
+# $ID:$
+
+$PREFIX TEST_
+$NAMESPACE isc::log
+TEST1 message %s is much too large
++ This message is a test for the general message code
+
+UNKNOWN unknown message
++ Issued when the message is unknown.
+
+-- END --
+
+Points to note:
+* Leading and trailing space are trimmed from the line. Although the above
+ example has every line starting at column 1, the lines could be indented
+ if desired.
+
+* Blank lines are ignored.
+
+* Lines starting with "#" are comments are are ignored. Comments must be on
+ a line by themselves - inline comments will be interpreted as part of the
+ text of the line.
+
+* Lines starting $ are directives. At present, two directives are recognised:
+
+ * $PREFIX, which has one argument: the string used to prefix symbols. If
+ absent, there is no prefix to the symbols. (Prefixes are explained below.)
+ * $NAMESPACE, which has one argument: the namespace in which the symbols are
+ created. In the absence of a $NAMESPACE directive, symbols will be put
+ in the global namespace.
+
+* Lines starting + indicate an explanation for the preceding message. These
+ are intended to be processed by a separate program and used to generate
+ an error messages manual. However they are treated like comments by the
+ message compiler. As with comments, these must be on a line by themselves;
+ if inline, the text (including the leading "+") will be interpreted as
+ part of the line.
+
+* Message lines. These comprise a symbol name and a message, which may
+ include zero or more printf-style tokens. Symbol names will be upper-cased
+ by the compiler.
+
+
+Message Compiler
+----------------
+The message compiler is a program built in the src/log/compiler directory.
+It is invoked by the command:
+
+ message [-h] [-v] <message-file>
+
+("-v" prints the version number and exits; "-h" prints brief help text.)
+The message compiler processes the message file to produce two files:
+
+1) A C++ header file (called <message-file-name>.h) that holds lines of
+the form:
+
+ namespace <namespace> {
+ extern const isc::log::MessageID PREFIX_IDENTIFIER;
+ :
+ }
+
+The symbols define the keys in the global message dictionary.
+
+The namespace enclosing the symbols is set by the $NAMESPACE directive.
+
+The "PREFIX_" part of the symbol name is the string defined in the $PREFIX
+the argument to the directive. So "$PREFIX MSG_" would prefix the identifer
+ABC with "MSG_" to give the symbol MSG_ABC. Similarly "$PREFIX E" would
+prefix it with "E" to give the symbol EABC. If no $PREFIX is given, no
+prefix appears (so the symbol in this example would be ABC).
+
+2) A C++ source file (called <message-file-name>.cc) that holds the definitions
+of the global symbols and code to insert the symbols and messages into the map.
+
+Symbols are defined to be equal to strings holding the identifier, e.g.
+
+ extern const isc::log::MessageID MSG_DUPLNS = "DUPLNS";
+
+(The implementation allows symbols to be compared. However, use of strings
+should not be assumed - a future implementation may change this.)
+
+In addition, the file declares an array of identifiers/messages and an object
+to add them to the global dictionary:
+
+ namespace {
+ const char* values[] = {
+ identifier1, text1,
+ identifier2, text2,
+ :
+ NULL
+ };
+
+ const isc::log::MessageInitializer initializer(values);
+ }
+
+The constructor of the MessageInitializer object retrieves the singleton
+global Dictionary object (created using standard methods to avoid the
+"static initialization fiasco") and adds each identifier and text to it.
+A check is made as each is added; if the identifier already exists, it is
+added to "overflow" vector; the vector is printed to the main logging output
+when logging is finally enabled (to indicate a programming error).
+
+
+Using the Logging
+=================
+To use the current version of the logging:
+
+1. Build message header file and source file as describe above.
+
+2. In the main module of the program, declare an instance of the
+ RootLoggerName class to define the name of the program's root logger, e.g.
+
+ #include <log/root_logger_name.h>
+
+ isc::log::RootLoggerName("b10-auth");
+
+ This can be declared inside or outside an execution unit.
+
+2. In the code that needs to do logging, declare a logger with a given name,
+ e.g.
+
+ #include <log/logger.h>
+ :
+ isc::log::Logger logger("myname"); // "myname" can be anything
+
+ The above example assumes declaration outside a function. If declaring
+ non-statically within a function, declare it as:
+
+ isc::log::Logger logger("myname", true);
+
+ (The argument is required to support a possible future implementation of
+ logging. Currently it has no effect.)
+
+3. The main program unit should include a call to isc::log::initLogger()
+ (defined in logger_support.h) to set the logging severity, debug log level,
+ and external message file.
+
+ a) The logging severity is one of the enum defined in logger.h, i.e.
+
+ isc::log::DEBUG
+ isc::log::INFO
+ isc::log::WARN
+ isc::log::ERROR
+ isc::log::FATAL
+ isc::log::NONE
+
+ b) The debug log level is only interpreted when the severity is DEBUG and
+ is an integer ranging from 0 to 99. 0 should be used for the
+ highest-level debug messages and 99 for the lowest-level (and typically
+ more verbose) messages.
+
+ c) Name of an external message file. This is the same as a standard message
+ file, although it should not include any directives. (A single directive
+ of a particular type will be ignored; multiple directives will cause the
+ read of the file to fail with an error.) If a message is replaced, the
+ message should include the same printf-format directives in the same order
+ as the original message.
+
+4. Issue logging calls using methods on logger, e.g.
+
+ logger.error(DPS_NSTIMEOUT, "isc.org");
+
+ (where, in the example above we might have defined the symbol in the message
+ file with something along the lines of:
+
+ $PREFIX DPS_
+ :
+ NSTIMEOUT queries to all nameservers for %s have timed out
+
+ At present, the only logging is to the console.
+
+
+Severity Guidelines
+===================
+When using logging, the question arises, what severity should a message be
+logged at? The following is a suggestion - as always, the decision must be
+made in the context of which the message is logged.
+
+FATAL
+-----
+The program has encountered an error that is so severe that it cannot
+continue (or there is no point in continuing). When a fatal error has been
+logged, the program will usually exit immediately (via a call to abort()) or
+shortly afterwards, after dumping some diagnostic information.
+
+ERROR
+-----
+Something has happened such that the program can continue but the results
+for the current (or future) operations cannot be guaranteed to be correct,
+or the results will be correct but the service is impaired. For example,
+the program started but attempts to open one or more network interfaces failed.
+
+WARN
+----
+An unusual event happened. Although the program will continue working
+normally, the event was sufficiently out of the ordinary to warrant drawing
+attention to it. For example, at program start-up a zone was loaded that
+contained no resource records,
+
+INFO
+----
+A normal but significant event has occurred that should be recorded,
+e.g. the program has started or is just about to terminate, a new zone has
+been created, etc.
+
+DEBUG
+-----
+This severity is only enabled on for debugging purposes. A debug level is
+associated with debug messages, level 0 (the default) being for high-level
+messages and level 99 (the maximum) for the lowest level. How the messages
+are distributed between the levels is up to the developer. So if debugging
+the NSAS (for example), a level 0 message might record the creation of a new
+zone, a level 10 recording a timeout when trying to get a nameserver address,
+but a level 50 would record every query for an address. (And we might add
+level 51 to record every update of the RTT.)
+
+Note that like severities, levels are cumulative; so if level 25 is set as the
+debug level, all debug levels from 0 to 25 will be output. In fact, it is
+probably easier to visualise the debug levels as part of the severity system:
+
+ FATAL High
+ ERROR
+ WARN
+ INFO
+ DEBUG level 0
+ DEBUG level 1
+ :
+ DEBUG level 99 Low
+
+When a particular severity is set, it - and all severities and/or debug
+levels above it - will be logged.
+
+Logging Sources v Logging Severities
+------------------------------------
+When logging events, make a distinction between events related to the server
+and events related to DNS messages received. Caution needs to be exercised
+with the latter as, if the logging is enabled in the normal course of events,
+such logging could be a denial of service vector. For example, suppose that
+the main authoritiative service logger were to log both zone loading and
+unloading as INFO and a warning message if it received an invalid packet. An
+attacker could make the INFO messages unusable by flooding the server with
+malformed packets.
+
+There are two approaches to get round this:
+
+a) Make the logging of packet-dependent events a DEBUG-severity message.
+DEBUG is not enabled by default, so these events will not be recorded unless
+DEBUG is specifically chosen.
+
+b) Record system-related and packet-related messages via different loggers
+(e.g. in the example given, server events could be logged using the logger
+"auth" and packet-related events at that level logged using the logger
+"pkt-auth".) As the loggers are independent and the severity levels
+independent, fine-tuning of what and what is not recorded can be achieved.
+
+
+Notes
+=====
+The message compiler is written in C++ (instead of Python) because it
+contains a component that reads the message file. This component is used
+in both the message compiler and the server; in the server it is used when
+the server starts up (or when triggered by a command) to read in a message
+file to overwrite the internal dictionary. Writing it in C++ means there
+is only one piece of code that does this functionality.
+
+
+Outstanding Issues
+==================
+* Ability to configure system according to configuration database.
+* Update the build procedure to create .cc and .h files from the .msg file
+ during the build process. (Requires that the message compiler is built
+ first.)
+
+
+log4cxx Issues
+==============
+Some experimental code to utilise log4cxx as an underlying implementation
+is present in the source code directory although it is not currently used.
+The files are:
+
+ logger_impl_log4cxx.{cc,h}
+ xdebuglevel.{cc,h}
diff --git a/src/lib/log/compiler/Makefile.am b/src/lib/log/compiler/Makefile.am
index 2475036..9343793 100644
--- a/src/lib/log/compiler/Makefile.am
+++ b/src/lib/log/compiler/Makefile.am
@@ -10,11 +10,9 @@ if USE_STATIC_LINK
AM_LDFLAGS = -static
endif
-pkglibexecdir = $(libexecdir)/@PACKAGE@
-
CLEANFILES = *.gcno *.gcda
-pkglibexec_PROGRAMS = message
+noinst_PROGRAMS = message
message_SOURCES = message.cc
message_LDADD = $(top_builddir)/src/lib/log/liblog.la
diff --git a/src/lib/log/compiler/message.cc b/src/lib/log/compiler/message.cc
index 50e33a6..6f9c4e0 100644
--- a/src/lib/log/compiler/message.cc
+++ b/src/lib/log/compiler/message.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,9 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <cctype>
+#include <cstddef>
#include <fstream>
#include <iostream>
#include <string>
@@ -47,7 +46,7 @@ static const char* VERSION = "1.0-0";
///
/// \li A .h file containing message definition
/// \li A .cc file containing code that adds the messages to the program's
-/// message disctionary at start-up time.
+/// message dictionary at start-up time.
///
/// Alternatively, the program can produce a .py file that contains the
/// message definitions.
@@ -56,20 +55,21 @@ static const char* VERSION = "1.0-0";
/// \b Invocation<BR>
/// The program is invoked with the command:
///
-/// <tt>message [-p] \<message-file\></tt>
+/// <tt>message [-v | -h | \<message-file\>]</tt>
///
/// It reads the message file and writes out two files of the same name but with
/// extensions of .h and .cc.
///
-/// If \c -p is specified, the C++ files are not written; instead a Python file
-/// of the same name (but with the file extension .py) is written.
+/// \-v causes it to print the version number and exit. \-h prints a help
+/// message (and exits).
/// \brief Print Version
///
/// Prints the program's version number.
-static void version() {
+void
+version() {
cout << VERSION << "\n";
}
@@ -77,16 +77,12 @@ static void version() {
///
/// Prints program usage to stdout.
-static void usage() {
+void
+usage() {
cout <<
- "Usage: message [-h] [-p] [-v] <message-file>\n" <<
+ "Usage: message [-h] [-v] <message-file>\n" <<
"\n" <<
"-h Print this message and exit\n" <<
- "-p Output a Python module holding the message definitions.\n" <<
- " By default a C++ header file and implementation file are\n" <<
-
-
- " written.\n" <<
"-v Print the program version and exit\n" <<
"\n" <<
"<message-file> is the name of the input message file.\n";
@@ -99,15 +95,13 @@ static void usage() {
///
/// \return Current time
-static string currentTime() {
+string
+currentTime() {
- // Get the current time.
+ // Get a text representation of the current time.
time_t curtime;
time(&curtime);
-
- // Format it
- char buffer[32];
- ctime_r(&curtime, buffer);
+ char* buffer = ctime(&curtime);
// Convert to string and strip out the trailing newline
string current_time = buffer;
@@ -115,8 +109,6 @@ static string currentTime() {
}
-
-
/// \brief Create Header Sentinel
///
/// Given the name of a file, create an #ifdef sentinel name. The name is
@@ -127,7 +119,8 @@ static string currentTime() {
///
/// \return Sentinel name
-static string sentinel(Filename& file) {
+string
+sentinel(Filename& file) {
string name = file.name();
string ext = file.extension();
@@ -143,11 +136,12 @@ static string sentinel(Filename& file) {
/// characters. This is used to handle the fact that the input file does not
/// contain quotes, yet the string will be included in a C++ literal string.
-string quoteString(const string& instring) {
+string
+quoteString(const string& instring) {
// Create the output string and reserve the space needed to hold the input
// string. (Most input strings will not contain quotes, so this single
- // reservation should be all that is needed.)
+ // reservation should be all that is needed.)
string outstring;
outstring.reserve(instring.size());
@@ -172,11 +166,12 @@ string quoteString(const string& instring) {
///
/// \return Sorted list of message IDs
-vector<MessageID> sortedIdentifiers(MessageDictionary* dictionary) {
- vector<MessageID> ident;
+vector<string>
+sortedIdentifiers(MessageDictionary& dictionary) {
+ vector<string> ident;
- for (MessageDictionary::const_iterator i = dictionary->begin();
- i != dictionary->end(); ++i) {
+ for (MessageDictionary::const_iterator i = dictionary.begin();
+ i != dictionary.end(); ++i) {
ident.push_back(i->first);
}
sort(ident.begin(), ident.end());
@@ -185,17 +180,83 @@ vector<MessageID> sortedIdentifiers(MessageDictionary* dictionary) {
}
+/// \brief Split Namespace
+///
+/// The $NAMESPACE directive may well specify a namespace in the form a::b.
+/// Unfortunately, the C++ "namespace" statement can only accept a single
+/// string - to set up the namespace of "a::b" requires two statements, one
+/// for "namspace a" and the other for "namespace b".
+///
+/// This function returns the set of namespace components as a vector of
+/// strings. A vector of one element, containing the empty string, is returned
+/// if the anonymous namespace is specified.
+///
+/// \param ns Argument to $NAMESPACE (passed by value, as we will be modifying
+/// it.)
+
+vector<string>
+splitNamespace(string ns) {
+
+ // Namespaces components are separated by double colon characters -
+ // convert to single colons.
+ size_t dcolon;
+ while ((dcolon = ns.find("::")) != string::npos) {
+ ns.replace(dcolon, 2, ":");
+ }
+
+ // ... and return the vector of namespace components split on the single
+ // colon.
+ return isc::strutil::tokens(ns, ":");
+}
+
+
+/// \brief Write Opening Namespace(s)
+///
+/// Writes the lines listing the namespaces in use.
+void
+writeOpeningNamespace(ostream& output, const vector<string>& ns) {
+ if (!ns.empty()) {
+
+ // Output namespaces in correct order
+ for (int i = 0; i < ns.size(); ++i) {
+ output << "namespace " << ns[i] << " {\n";
+ }
+ output << "\n";
+ }
+}
+
+
+/// \brief Write Closing Namespace(s)
+///
+/// Writes the lines listing the namespaces in use.
+void
+writeClosingNamespace(ostream& output, const vector<string>& ns) {
+ if (!ns.empty()) {
+ for (int i = ns.size() - 1; i >= 0; --i) {
+ output << "} // namespace " << ns[i] << "\n";
+ }
+ output << "\n";
+ }
+}
+
+
/// \brief Write Header File
///
-/// Writes the C++ header file containing the symbol definitions.
+/// Writes the C++ header file containing the symbol definitions. These are
+/// "extern" references to definitions in the .cc file. As such, they should
+/// take up no space in the module in which they are included, and redundant
+/// references should be removed by the compiler.
///
/// \param file Name of the message file. The header file is written to a
/// file of the same name but with a .h suffix.
/// \param prefix Prefix string to use in symbols
+/// \param ns Namespace in which the definitions are to be placed. An empty
+/// string indicates no namespace.
/// \param dictionary Dictionary holding the message definitions.
-void writeHeaderFile(const string& file, const string& prefix,
- MessageDictionary* dictionary)
+void
+writeHeaderFile(const string& file, const string& prefix,
+ const vector<string>& ns_components, MessageDictionary& dictionary)
{
Filename message_file(file);
Filename header_file(message_file.useAsDefault(".h"));
@@ -208,7 +269,7 @@ void writeHeaderFile(const string& file, const string& prefix,
try {
if (hfile.fail()) {
- throw MessageException(MSG_OPENOUT, header_file.fullName(),
+ throw MessageException(MSG_OPNMSGOUT, header_file.fullName(),
strerror(errno));
}
@@ -223,27 +284,26 @@ void writeHeaderFile(const string& file, const string& prefix,
"#define " << sentinel_text << "\n" <<
"\n" <<
"#include <log/message_types.h>\n" <<
- "\n" <<
- "namespace {\n" <<
"\n";
- vector<MessageID> idents = sortedIdentifiers(dictionary);
- for (vector<MessageID>::const_iterator j = idents.begin();
+ // Write the message identifiers, bounded by a namespace declaration
+ writeOpeningNamespace(hfile, ns_components);
+
+ vector<string> idents = sortedIdentifiers(dictionary);
+ for (vector<string>::const_iterator j = idents.begin();
j != idents.end(); ++j) {
- hfile << "isc::log::MessageID " << prefix << *j <<
- " = \"" << *j << "\";\n";
+ hfile << "extern const isc::log::MessageID " << prefix << *j << ";\n";
}
+ hfile << "\n";
+
+ writeClosingNamespace(hfile, ns_components);
// ... and finally the postamble
- hfile <<
- "\n" <<
- "} // Anonymous namespace\n" <<
- "\n" <<
- "#endif // " << sentinel_text << "\n";
+ hfile << "#endif // " << sentinel_text << "\n";
// Report errors (if any) and exit
if (hfile.fail()) {
- throw MessageException(MSG_WRITERR, header_file.fullName(),
+ throw MessageException(MSG_MSGWRTERR, header_file.fullName(),
strerror(errno));
}
@@ -260,18 +320,40 @@ void writeHeaderFile(const string& file, const string& prefix,
///
/// Simple function for use in a call to transform
-char replaceNonAlphaNum(char c) {
+char
+replaceNonAlphaNum(char c) {
return (isalnum(c) ? c : '_');
}
/// \brief Write Program File
///
-/// Writes the C++ source code file. This defines an external objects whose
-/// constructor is run at initialization time. The constructor adds the message
-/// definitions to the main global dictionary.
-
-void writeProgramFile(const string& file, MessageDictionary* dictionary)
+/// Writes the C++ source code file. This defines the text of the message
+/// symbols, as well as the initializer object that sets the entries in the
+/// global dictionary.
+///
+/// The construction of the initializer object loads the dictionary with the
+/// message text. However, nothing actually references it. If the initializer
+/// were in a file by itself, the lack of things referencing it would cause the
+/// linker to ignore it when pulling modules out of the logging library in a
+/// static link. By including it in the file with the symbol definitions, the
+/// module will get included in the link process to resolve the symbol
+/// definitions, and so the initializer object will be included in the final
+/// image. (Note that there are no such problems when the logging library is
+/// built as a dynamically-linked library: the whole library - including the
+/// initializer module - gets mapped into address space when the library is
+/// loaded, after which all the initializing code (including the constructors
+/// of objects declared outside functions) gets run.)
+///
+/// There _may_ be a problem when we come to port this to Windows. Microsoft
+/// Visual Studio contains a "Whole Program Optimisation" option, where the
+/// optimisation is done at link-time, not compiler-time. In this it _may_
+/// decide to remove the initializer object because of a lack of references
+/// to it. But until BIND-10 is ported to Windows, we won't know.
+
+void
+writeProgramFile(const string& file, const string& prefix,
+ const vector<string>& ns_components, MessageDictionary& dictionary)
{
Filename message_file(file);
Filename program_file(message_file.useAsDefault(".cc"));
@@ -280,7 +362,7 @@ void writeProgramFile(const string& file, MessageDictionary* dictionary)
ofstream ccfile(program_file.fullName().c_str());
try {
if (ccfile.fail()) {
- throw MessageException(MSG_OPENOUT, program_file.fullName(),
+ throw MessageException(MSG_OPNMSGOUT, program_file.fullName(),
strerror(errno));
}
@@ -292,45 +374,53 @@ void writeProgramFile(const string& file, MessageDictionary* dictionary)
currentTime() << "\n" <<
"\n" <<
"#include <cstddef>\n" <<
+ "#include <log/message_types.h>\n" <<
"#include <log/message_initializer.h>\n" <<
- "\n" <<
- "using namespace isc::log;\n" <<
- "\n" <<
+ "\n";
+
+ // Declare the message symbols themselves.
+
+ writeOpeningNamespace(ccfile, ns_components);
+
+ vector<string> idents = sortedIdentifiers(dictionary);
+ for (vector<string>::const_iterator j = idents.begin();
+ j != idents.end(); ++j) {
+ ccfile << "extern const isc::log::MessageID " << prefix << *j <<
+ " = \"" << *j << "\";\n";
+ }
+ ccfile << "\n";
+
+ writeClosingNamespace(ccfile, ns_components);
+
+ // Now the code for the message initialization.
+
+ ccfile <<
"namespace {\n" <<
"\n" <<
"const char* values[] = {\n";
// Output the identifiers and the associated text.
- vector<MessageID> idents = sortedIdentifiers(dictionary);
- for (vector<MessageID>::const_iterator i = idents.begin();
+ idents = sortedIdentifiers(dictionary);
+ for (vector<string>::const_iterator i = idents.begin();
i != idents.end(); ++i) {
ccfile << " \"" << *i << "\", \"" <<
- quoteString(dictionary->getText(*i)) << "\",\n";
+ quoteString(dictionary.getText(*i)) << "\",\n";
}
+
// ... and the postamble
ccfile <<
" NULL\n" <<
"};\n" <<
"\n" <<
+ "const isc::log::MessageInitializer initializer(values);\n" <<
+ "\n" <<
"} // Anonymous namespace\n" <<
"\n";
- // Now construct a unique name. We don't put the message initializer as
- // a static variable or in an anonymous namespace lest the C++
- // compiler's optimizer decides it can optimise it away.
- string unique_name = program_file.name() + program_file.extension() +
- "_" + currentTime();
- transform(unique_name.begin(), unique_name.end(), unique_name.begin(),
- replaceNonAlphaNum);
-
- // ... and write the initialization code
- ccfile <<
- "MessageInitializer " << unique_name << "(values);\n";
-
// Report errors (if any) and exit
if (ccfile.fail()) {
- throw MessageException(MSG_WRITERR, program_file.fullName(),
+ throw MessageException(MSG_MSGWRTERR, program_file.fullName(),
strerror(errno));
}
@@ -350,7 +440,8 @@ void writeProgramFile(const string& file, MessageDictionary* dictionary)
///
/// \param reader Message Reader used to read the file
-static void warnDuplicates(MessageReader& reader) {
+void
+warnDuplicates(MessageReader& reader) {
// Get the duplicates (the overflow) and, if present, sort them into some
// order and remove those which occur more than once (which mean that they
@@ -375,19 +466,15 @@ static void warnDuplicates(MessageReader& reader) {
/// Parses the options then dispatches to the appropriate function. See the
/// main file header for the invocation.
-int main(int argc, char** argv) {
-
- const struct option loptions[] = { // Long options
- {"help", no_argument, NULL, 'h'},
- {"version", no_argument, NULL, 'v'},
- {NULL, 0, NULL, 0 }
- };
+int
+main(int argc, char* argv[]) {
+
const char* soptions = "hv"; // Short options
optind = 1; // Ensure we start a new scan
int opt; // Value of the option
- while ((opt = getopt_long(argc, argv, soptions, loptions, NULL)) != -1) {
+ while ((opt = getopt(argc, argv, soptions)) != -1) {
switch (opt) {
case 'h':
usage();
@@ -424,19 +511,28 @@ int main(int argc, char** argv) {
MessageReader reader(&dictionary);
reader.readFile(message_file);
- // Now write the header file.
- writeHeaderFile(message_file, reader.getPrefix(), &dictionary);
+ // Get the namespace into which the message definitions will be put and
+ // split it into components.
+ vector<string> ns_components = splitNamespace(reader.getNamespace());
+
+ // Write the header file.
+ writeHeaderFile(message_file, reader.getPrefix(), ns_components,
+ dictionary);
+
+ // Write the file that defines the message symbols and text
+ writeProgramFile(message_file, reader.getPrefix(), ns_components,
+ dictionary);
- // ... and the message text file.
- writeProgramFile(message_file, &dictionary);
// Finally, warn of any duplicates encountered.
warnDuplicates(reader);
}
catch (MessageException& e) {
// Create an error message from the ID and the text
- MessageDictionary* global = MessageDictionary::globalDictionary();
- string text = e.id() + ", " + global->getText(e.id());
+ MessageDictionary& global = MessageDictionary::globalDictionary();
+ string text = e.id();
+ text += ", ";
+ text += global.getText(e.id());
// Format with arguments
text = isc::strutil::format(text, e.arguments());
diff --git a/src/lib/log/dbglevels.h b/src/lib/log/dbglevels.h
deleted file mode 100644
index 35c6878..0000000
--- a/src/lib/log/dbglevels.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-// $Id$
-
-#ifndef __DBGLEVELS_H
-#define __DBGLEVELS_H
-
-/// \brief Defines Debug Levels
-///
-/// Defines the maximum and minimum debug levels and the number of levels.
-/// These are defined using #define as they are referenced in the construction
-/// of variables declared outside execution units. (In this way we avoid the
-/// "static initialization fiasco" problem.)
-
-#define MIN_DEBUG_LEVEL (0)
-#define MAX_DEBUG_LEVEL (99)
-#define NUM_DEBUG_LEVEL (MAX_DEBUG_LEVEL - MIN_DEBUG_LEVEL + 1)
-
-#endif // __DBGLEVELS_H
diff --git a/src/lib/log/debug_levels.h b/src/lib/log/debug_levels.h
new file mode 100644
index 0000000..bb2b524
--- /dev/null
+++ b/src/lib/log/debug_levels.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __DEBUG_LEVELS_H
+#define __DEBUG_LEVELS_H
+
+/// \brief Defines Debug Levels
+///
+/// Defines the maximum and minimum debug levels and the number of levels.
+/// These are defined using #define as they are referenced in the construction
+/// of variables declared outside execution units. (In this way we avoid the
+/// "static initialization fiasco" problem.)
+
+#define MIN_DEBUG_LEVEL (0)
+#define MAX_DEBUG_LEVEL (99)
+#define NUM_DEBUG_LEVEL (MAX_DEBUG_LEVEL - MIN_DEBUG_LEVEL + 1)
+
+#endif // __DEBUG_LEVELS_H
diff --git a/src/lib/log/documentation.txt b/src/lib/log/documentation.txt
deleted file mode 100644
index 1439ad5..0000000
--- a/src/lib/log/documentation.txt
+++ /dev/null
@@ -1,371 +0,0 @@
-This directory holds the first release of the logging system.
-
-Basic Ideas
-===========
-The BIND-10 logging system merges two ideas:
-
-* A hierarchical logging system similar to that used in Java (i.e. log4j)
-* Separation of message definitions and text
-
-
-Hierarchical Logging System
-===========================
-When a program writes a message to the logging system, it does so using an
-instance of the Logger class. As well as performing the write of the message,
-the logger identifies the source of the message: different sources can write
-to different destinations and can log different severities of messages. For
-example, the "cache" logger could write messages of DEBUG severity or above
-to a file while all other components write messages of "INFO" severity or above
-to the Syslog file.
-
-The loggers are hierarchical in that each logger is the child of another logger.
-The top of the hierarchy is the root logger, which does not have a parent. The
-point of the hierarchy is that unless a logger is explicitly assigned an
-attribute (such as severity of message being logger), it picks it up from the
-parent. (In BIND-10, there is the root logger (named after the program) and
-every other logger is a child of that.) So in the example above, the
-INFO/Syslog attributes could be associated with the root logger while the
-DEBUG/file attributes are associated with the "cache" logger.
-
-
-Separation of Messages Definitions And Text
-===========================================
-The reason for this is to allow the message text to be overridden by versions
-in a local language. To do this, each message is identified by an identifier
-e.g. "OPENIN". Within the program, this is the symbol passed to the logging
-system. The logger system uses the symbol as an index into a dictionary to
-retrieve the message associated with it (e.g. "unable to open %s for input").
-substitutes any message parameters (in this example, the string that is an
-invalid filename) and logs it to the destination.
-
-In the BIND-10 system, a set of default messages are linked into the program.
-At run-time. each program reads a message file, updating the stored definitions;
-this updated text is logged. However, to aid support, the message identifier
-so in the example above, the message finally logged would be something like:
-
- OPENIN, unable to open a.txt for input
-
-
-Using The System
-================
-The steps in using the system are:
-
-1. Create a message file. This defines messages by an identification - a
- mnemonic for the message, typically 6-12 characters long - and a message.
- The file is described in more detail below.
-
- Ideally the file should have a file type of ".msg".
-
-2. Run it through the message compiler to produce the .h and .cc files. It
- is intended that this step be included in the build process. However, for
- not run the compiler (found in the "compiler" subdirectory) manually. The
- only argument is the name of the message file: it will produce as output
- two files, having the same name as the input file but with file types of
- ".h" and ".cc".
-
- The compiler is built in the "compiler" subdirectory of the "src/lib/log"
- directory.
-
-3. Include the .h file in your source code to define message symbols, and
- make sure that the .cc file is compiled and linked into your program -
- static initialization will add the symbols to the global dictionary.
-
-4. Declare loggers in your code and use them to log messages. This is described
- in more detail below.
-
-5. To set the debug level and run-time message file, call runTimeInit (declared
- in logger_support.h) in the main program unit. This is a temporary solution
- for Year 2, and will be replaced at a later date, the information coming from
- the configuration database.
-
-
-Message Files
-=============
-
-File Contents and Format
-------------------------
-A message file is a file containing message definitions. Typically there will
-be one message file for each component that declares message symbols. An
-example file could be:
-
--- BEGIN --
-
-# Example message file
-# $ID:$
-
-$PREFIX TEST_
-TEST1 message %s is much too large
-+ This message is a test for the general message code
-
-UNKNOWN unknown message
-+ Issued when the message is unknown.
-
--- END --
-
-Points to note:
-* Leading and trailing space are trimmed from the line. Although the above
- exampl,e has every line starting at column 1, the lines could be indented if
- desired.
-
-* Blank lines are ignored.
-
-* Lines starting with "#" are comments are are ignored. Comments must be on
- a line by themselves - inline comments will be interpreted as part of the
- text of the line.
-
-* Lines starting $ are directives. At present, the only directive recognised
- is $PREFIX, which has one argument: the string used to prefix symbols. If
- there is no facility directive, there is no prefix to the symbols. (Prefixes
- are explained below.)
-
-* Lines starting + indicate an explanation for the preceding message. These
- are intended to be processed by a separate program and used to generate an
- error messages manual. However they are treated like comments by the message
- compiler. As with comments, these must be on a line by themselves; if inline,
- the text (including the leading "+") will be interpreted as part of the line.
-
-* Message lines. These comprise a symbol name and a message, which may
- include zero or more printf-style tokens. Symbol names will be upper-cased
- by the compiler.
-
-
-Message Compiler
-----------------
-The message compiler is a program built in the src/log/compiler directory.
-It processes the message file to produce two files:
-
-1) A C++ header file (called <message-file-name>.h) that holds lines of
-the form:
-
- namespace {
- isc::log::MessageID PREFIX_IDENTIFIER = "IDENTIFIER";
- :
- }
-
-The symbols define the keys in the global message dictionary. At present
-they are defined as std::strings, but a future implementation could redefine
-them as numeric values.
-
-The "PREFIX_" part of the symbol name is the string defined in the $PREFIX
-the argument to the directive. So "$PREFIX MSG_" would prefix the identifer
-ABC with "MSG_" to give the symbol MSG_ABC. Similarly "$PREFIX E" would
-prefix it with "E" to give the symbol EABC. If no $PREFIX is given, no
-prefix appears (so the symbol in this example would be ABC).
-
-
-2) A C++ source file (called <message-file-name>.cc) that holds the code to
-insert the symbols and messages into the map.
-
-This file declares an array of identifiers/messages in the form:
-
- namespace {
- const char* values[] = {
- identifier1, text1,
- identifier2, text2,
- :
- NULL
- };
- }
-
-(A more complex structure to group identifiers and their messages could be
-imposed, but as the array is generated by code and will be read by code,
-it is not needed.)
-
-It then declares an object that will add information to the global dictionary:
-
- MessageInitializer <message-file-name>_<time>(values);
-
-(Declaring the object as "static" or in the anonymous namespace runs the risk
-of it being optimised away when the module is compiled with optimisation.
-But giving it a standard name would cause a clash when multiple files are
-used, hence an attempt at disambiguation.)
-
-The constructor of the MessageInitializer object retrieves the singleton
-global Dictionary object (created using standard methods to avoid the
-"static initialization fiasco") and adds each identifier and text to it.
-A check is made as each is added; if the identifier already exists, it is
-added to "overflow" vector; the vector is printed to the main logging output
-when logging is finally enabled (to indicate a programming error).
-
-
-Using the Logging
-=================
-To use the current version of the logging:
-
-1. Build message header file and source file as describe above.
-
-2. In the main module of the program, declare an instance of the
- RootLoggerName class to define the name of the program's root logger, e.g.
-
- #include <log/root_logger_name.h>
-
- isc::log::RootLoggerName("b10-auth");
-
- It should be declared outside an execution unit to allow other statically-
- declared loggers to pick it up.
-
-2. In the code that needs to do logging, declare a logger with a given name,
- e.g.
-
- #include <log/logger.h>
- :
- isc::log::Logger logger("myname"); // "myname" can be anything
-
- The above example assumes declaration outside a function. If declaring
- non-statically within a function, declare it as:
-
- isc::log::Logger logger("myname", true);
-
- This is due to an apparent bug in the underlying log4cxx, where the deletion
- of a statically-declared object at program termination can cause a segment
- fault. (The destruction of internal memory structures can sometimes happen
- out of order.) By default the Logger class creates the structures in its
- constructor but does not delete them in the destruction. The default
- behavious works because instead of reclaiming memory at program run-down,
- the operating system reclaims it when the process is deleted.
-
- Setting the second argument "true" causes the Logger's destructor to delete
- the log4cxx structures. This does not cause a problem if the program is
- not terminating. So use the second form when declaring an automatic
- instance of isc::log::Logger on the stack.
-
-3. The main program unit should include a call to isc::log::runTimeInit()
- (defined in logger_support.h) to set the logging severity, debug log level,
- and external message file.
-
- a) The logging severity is one of the enum defined in logger.h, i.e.
-
- isc::log::Logger::DEBUG
- isc::log::Logger::INFO
- isc::log::Logger::WARN
- isc::log::Logger::ERROR
- isc::log::Logger::FATAL
- isc::log::Logger::NONE
-
- b) The debug log level is only interpreted when the severity is DEBUG and
- is an integer raning from 0 to 99. 0 should be used for the highest-level
- debug messages and 99 for the lowest-level (and typically more verbose)
- messages.
-
- c) Name of an external message file. This is the same as a standard message
- file, although it should not include the $PREFIX directive. (A single
- $PREFIX directive will be ignored; multiple directives will cause the
- read of the file to fail with an error.) If a message is replaced, the
- message should include the same printf-format directives in the same order
- as the original message.
-
-4. Issue logging calls using methods on logger, e.g.
-
- logger.error(DPS_NSTIMEOUT, "isc.org");
-
- (where, in the example above we might have defined the symbol in the message
- file with something along the lines of:
-
- $PREFIX DPS_
- :
- NSTIMEOUT queries to all nameservers for %s have timed out
-
- At present, the only logging is to the console.
-
-
-Severity Guidelines
-===================
-When using logging, the question arises, what severity should a message be
-logged at? The following is a suggestion - as always, the decision must be
-made in the context of which the message is logged.
-
-FATAL
------
-The program has encountered an error that is so severe that it cannot
-continue (or there is no point in continuing). When a fatal error has been
-logged, the program will usually exit immediately (via a call to abort()) or
-shortly afterwards, after dumping some diagnostic information.
-
-ERROR
------
-Something has happened such that the program can continue but the results
-for the current (or future) operations cannot be guaranteed to be correct,
-or the results will be correct but the service is impaired. For example,
-the program started but attempts to open one or more network interfaces failed.
-
-WARN
-----
-An unusual event happened. Although the program will continue working
-normally, the event was sufficiently out of the ordinary to warrant drawings
-attention to it. For example, at program start-up a zone was loaded that
-contained no resource records,
-
-INFO
-----
-A normal but significant event has occurred that should be recorded,
-e.g. the program has started or is just about to terminate, a new zone has
-been created, etc.
-
-DEBUG
------
-This severity is only enabled on for debugging purposes. A debug level is
-associated with debug messages, level 0 (the default) being for high-level
-messages and level 99 (the maximum) for the lowest level. How the messages
-are distributed between the levels is up to the developer. So if debugging
-the NSAS (for example), a level 0 message might record the creation of a new
-zone, a level 10 recording a timeout when trying to get a nameserver address,
-but a level 50 would record every query for an address. (And we might add
-level 51 to record every update of the RTT.)
-
-Note that like severities, levels are cumulative; so if level 25 is set as the
-debug level, all debug levels from 0 to 25 will be output. In fact, it is
-probably easier to visualise the debug levels as part of the severity system:
-
- FATAL High
- ERROR
- WARN
- INFO
- DEBUG level 0
- DEBUG level 1
- :
- DEBUG level 99 Low
-
-When a particular severity is set, it - and all severities and/or debug
-levels above it - will be logged.
-
-Logging Sources v Logging Severities
-------------------------------------
-When logging events, make a distinction between events related to the server
-and events related to DNS messages received. Caution needs to be exercised
-with the latter as, if the logging is enabled in the normal course of events,
-such logging could be a denoial of service vector. For example, suppose that
-the main authoritiative service logger were to log both zone loading and
-unloading as INFO and a warning message if it received an invalid packet. An
-attacker could make the INFO messages unusable by flooding the server with
-malformed packets.
-
-There are two approaches to get round this:
-
-a) Make the logging of packet-dependent events a DEBUG-severity message.
-DEBUG is not enabled by default, so these events will not be recorded unless
-DEBUG is specifically chosen.
-
-b) Record system-related and packet-related messages via different loggers
-(e.g. in the example given, sever events could be logged using the logger
-"auth" and packet-related events at that level logged using the logger
-"pkt-auth".)
-As the loggers are independent and the severity levels independent, fine-tuning
-of what and what is not recorded can be achieved.
-
-
-Outstanding Issues
-==================
-* Ability to configure system according to configuration database.
-* Update the build procedure to create .cc and .h files from the .msg file
- during the build process. (Requires that the message compiler is built first.)
-
-
-Notes
-=====
-The message compiler is written in C++ (instead of Python) because it
-contains a component that reads the message file. This component is used
-in both the message compiler and the server; in the server it is used when
-the server starts up (or when triggered by a command) to read in a message
-file to overwrite the internal dictionary. Writing it in C++ means there
-is only one piece of code that does this functionality.
-
diff --git a/src/lib/log/filename.cc b/src/lib/log/filename.cc
index 949ed9f..91835af 100644
--- a/src/lib/log/filename.cc
+++ b/src/lib/log/filename.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <iostream>
#include <algorithm>
#include <string>
@@ -106,7 +104,7 @@ Filename::expandWithDefault(const string& defname) const {
(directory_.empty() ? def_directory : directory_) +
(name_.empty() ? def_name : name_) +
(extension_.empty() ? def_extension : extension_);
- return retstring;
+ return (retstring);
}
// Use the stored name as default for a given name
@@ -132,7 +130,7 @@ Filename::useAsDefault(const string& name) const {
(name_directory.empty() ? directory_ : name_directory) +
(name_name.empty() ? name_ : name_name) +
(name_extension.empty() ? extension_ : name_extension);
- return retstring;
+ return (retstring);
}
diff --git a/src/lib/log/filename.h b/src/lib/log/filename.h
index 8313b04..e3cda16 100644
--- a/src/lib/log/filename.h
+++ b/src/lib/log/filename.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __FILENAME_H
#define __FILENAME_H
@@ -80,22 +78,22 @@ public:
/// \return Stored Filename
std::string fullName() const {
- return full_name_;
+ return (full_name_);
}
/// \return Directory of Given File Name
std::string directory() const {
- return directory_;
+ return (directory_);
}
/// \return Name of Given File Name
std::string name() const {
- return name_;
+ return (name_);
}
/// \return Extension of Given File Name
std::string extension() const {
- return extension_;
+ return (extension_);
}
/// \brief Expand Name with Default
diff --git a/src/lib/log/logger.cc b/src/lib/log/logger.cc
index 494e7bc..a2465de 100644
--- a/src/lib/log/logger.cc
+++ b/src/lib/log/logger.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,296 +12,164 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE
-// $Id$
-
-#include <iostream>
-
#include <stdarg.h>
#include <stdio.h>
-#include <log4cxx/appender.h>
-#include <log4cxx/basicconfigurator.h>
-#include <log4cxx/patternlayout.h>
-#include <log4cxx/consoleappender.h>
-
-#include <log/root_logger_name.h>
#include <log/logger.h>
+#include <log/logger_impl.h>
#include <log/message_dictionary.h>
#include <log/message_types.h>
+#include <log/root_logger_name.h>
#include <log/strutil.h>
-#include <log/xdebuglevel.h>
using namespace std;
namespace isc {
namespace log {
-// Static initializations
-
-bool Logger::init_ = false;
+// Initialize Logger implementation. Does not check whether the implementation
+// has already been initialized - that was done by the caller (getLoggerPtr()).
+void Logger::initLoggerImpl() {
+ loggerptr_ = new LoggerImpl(name_, infunc_);
+}
-// Destructor. Delete log4cxx stuff if "don't delete" is clear.
+// Destructor.
Logger::~Logger() {
- if (exit_delete_) {
- delete loggerptr_;
- }
+ delete loggerptr_;
}
-// Initialize logger - create a logger as a child of the root logger. With
-// log4cxx this is assured by naming the logger <parent>.<child>.
-
-void
-Logger::initLogger() {
-
- // Initialize basic logging if not already done. This is a one-off for
- // all loggers.
- if (!init_) {
-
- // TEMPORARY
- // Add a suitable console logger to the log4cxx root logger. (This
- // is the logger at the root of the log4cxx tree, not the BIND-10 root
- // logger, which is one level down.) The chosen format is:
- //
- // YYYY-MM-DD hh:mm:ss.sss [logger] SEVERITY: text
- //
- // As noted, this is a temporary hack: it is done here to ensure that
- // a suitable output and output pattern is set. Future versions of the
- // software will set this based on configuration data.
-
- log4cxx::LayoutPtr layout(
- new log4cxx::PatternLayout(
- "%d{yyyy-MM-DD HH:mm:ss.SSS} %-5p [%c] %m\n"));
- log4cxx::AppenderPtr console(
- new log4cxx::ConsoleAppender(layout));
- log4cxx::LoggerPtr sys_root_logger = log4cxx::Logger::getRootLogger();
- sys_root_logger->addAppender(console);
-
- // Set the default logging to INFO
- sys_root_logger->setLevel(log4cxx::Level::getInfo());
-
- // All static stuff initialized
- init_ = true;
- }
+// Get Name of Logger
- // Initialize this logger. Name this as to whether the BIND-10 root logger
- // name has been set. (If not, this mucks up the hierarchy :-( ).
- string root_name = RootLoggerName::getName();
- if (root_name.empty() || (name_ == root_name)) {
- loggerptr_ = new log4cxx::LoggerPtr(log4cxx::Logger::getLogger(name_));
- }
- else {
- loggerptr_ = new log4cxx::LoggerPtr(
- log4cxx::Logger::getLogger(root_name + "." + name_)
- );
- }
+std::string
+Logger::getName() {
+ return (getLoggerPtr()->getName());
}
-
-// Set the severity for logging. There is a 1:1 mapping between the logging
-// severity and the log4cxx logging levels, apart from DEBUG.
-//
-// In log4cxx, each of the logging levels (DEBUG, INFO, WARN etc.) has a numeric
-// value. The level is set to one of these and any numeric level equal to or
-// above it that is reported. For example INFO has a value of 20000 and ERROR
-// a value of 40000. So if a message of WARN severity (= 30000) is logged, it is
-// not logged when the logger's severity level is ERROR (as 30000 !>= 40000).
-// It is reported if the logger's severity level is set to WARN (as 30000 >=
-/// 30000) or INFO (30000 >= 20000).
-//
-// This gives a simple system for handling different debug levels. The debug
-// level is a number between 0 and 99, with 0 being least verbose and 99 the
-// most. To implement this seamlessly, when DEBUG is set, the numeric value
-// of the logging level is actually set to (DEBUG - debug-level). Similarly
-// messages of level "n" are logged at a logging level of (DEBUG - n). Thus if
-// the logging level is set to DEBUG and the debug level set to 25, the actual
-// level set is 10000 - 25 = 99975.
-//
-// Attempting to log a debug message of level 26 is an attempt to log a message
-// of level 10000 - 26 = 9974. As 9974 !>= 9975, it is not logged. A
-// message of level 25 is, because 9975 >= 9975.
-//
-// The extended set of logging levels is implemented by the XDebugLevel class.
+// Set the severity for logging.
void
-Logger::setSeverity(Severity severity, int dbglevel) {
- switch (severity) {
- case NONE:
- getLogger()->setLevel(log4cxx::Level::getOff());
- break;
-
- case FATAL:
- getLogger()->setLevel(log4cxx::Level::getFatal());
- break;
-
- case ERROR:
- getLogger()->setLevel(log4cxx::Level::getError());
- break;
-
- case WARN:
- getLogger()->setLevel(log4cxx::Level::getWarn());
- break;
-
- case INFO:
- getLogger()->setLevel(log4cxx::Level::getInfo());
- break;
-
- case DEBUG:
- getLogger()->setLevel(
- log4cxx::XDebugLevel::getExtendedDebug(dbglevel));
- break;
-
- // Will get here for DEFAULT or any other value. This disables the
- // logger's own severity and it defaults to the severity of the parent
- // logger.
- default:
- getLogger()->setLevel(0);
- }
+Logger::setSeverity(isc::log::Severity severity, int dbglevel) {
+ getLoggerPtr()->setSeverity(severity, dbglevel);
}
-// Convert between numeric log4cxx logging level and BIND-10 logging severity.
-
-Logger::Severity
-Logger::convertLevel(int value) const {
-
- // The order is optimised. This is only likely to be called when testing
- // for writing debug messages, so the check for DEBUG_INT is first.
- if (value <= log4cxx::Level::DEBUG_INT) {
- return (DEBUG);
- } else if (value <= log4cxx::Level::INFO_INT) {
- return (INFO);
- } else if (value <= log4cxx::Level::WARN_INT) {
- return (WARN);
- } else if (value <= log4cxx::Level::ERROR_INT) {
- return (ERROR);
- } else if (value <= log4cxx::Level::FATAL_INT) {
- return (FATAL);
- } else {
- return (NONE);
- }
-}
+// Return the severity of the logger.
+isc::log::Severity
+Logger::getSeverity() {
+ return (getLoggerPtr()->getSeverity());
+}
-// Return the logging severity associated with this logger.
+// Get Effective Severity Level for Logger
-Logger::Severity
-Logger::getSeverityCommon(const log4cxx::LoggerPtr& ptrlogger,
- bool check_parent) const {
+isc::log::Severity
+Logger::getEffectiveSeverity() {
+ return (getLoggerPtr()->getEffectiveSeverity());
+}
- log4cxx::LevelPtr level = ptrlogger->getLevel();
- if (level == log4cxx::LevelPtr()) {
+// Debug level (only relevant if messages of severity DEBUG are being logged).
- // Null level returned, logging should be that of the parent.
+int
+Logger::getDebugLevel() {
+ return (getLoggerPtr()->getDebugLevel());
+}
- if (check_parent) {
- log4cxx::LoggerPtr parent = ptrlogger->getParent();
- if (parent == log4cxx::LoggerPtr()) {
+// Check on the current severity settings
- // No parent, so reached the end of the chain. Return INFO
- // severity.
- return (INFO);
- }
- else {
- return getSeverityCommon(parent, check_parent);
- }
- }
- else {
- return (DEFAULT);
- }
- } else {
- return convertLevel(level->toInt());
- }
+bool
+Logger::isDebugEnabled(int dbglevel) {
+ return (getLoggerPtr()->isDebugEnabled(dbglevel));
}
+bool
+Logger::isInfoEnabled() {
+ return (getLoggerPtr()->isInfoEnabled());
+}
-// Get the debug level. This returns 0 unless the severity is DEBUG.
+bool
+Logger::isWarnEnabled() {
+ return (getLoggerPtr()->isWarnEnabled());
+}
-int
-Logger::getDebugLevel() {
+bool
+Logger::isErrorEnabled() {
+ return (getLoggerPtr()->isErrorEnabled());
+}
- log4cxx::LevelPtr level = getLogger()->getLevel();
- if (level == log4cxx::LevelPtr()) {
-
- // Null pointer returned, logging should be that of the parent.
- return (0);
-
- } else {
- int severity = level->toInt();
- if (severity <= log4cxx::Level::DEBUG_INT) {
- return (log4cxx::Level::DEBUG_INT - severity);
- }
- else {
- return (0);
- }
- }
+bool
+Logger::isFatalEnabled() {
+ return (getLoggerPtr()->isFatalEnabled());
}
-// Log an error message:
-// Common code. Owing to the use of variable arguments, this must be inline
-// (hence the definition of the macro). Also note that it expects that the
-// message buffer "message" is declared in the compilation unit.
-
-#define MESSAGE_SIZE (256)
-
-#define FORMAT_MESSAGE(message) \
- { \
- MessageDictionary* global = MessageDictionary::globalDictionary(); \
- string format = global->getText(ident); \
- va_list ap; \
- va_start(ap, ident); \
- vsnprintf(message, sizeof(message), format.c_str(), ap); \
- message[sizeof(message) - 1] = '\0'; \
- va_end(ap); \
- }
-
+// Format a message: looks up the message text in the dictionary and formats
+// it, replacing tokens with arguments.
+//
+// Owing to the use of variable arguments, this must be inline (hence the
+// definition of the macro). Also note that it expects that the message buffer
+// "message" is declared in the compilation unit.
// Output methods
void
-Logger::debug(int dbglevel, isc::log::MessageID ident, ...) {
+Logger::debug(int dbglevel, const isc::log::MessageID& ident, ...) {
if (isDebugEnabled(dbglevel)) {
- char message[MESSAGE_SIZE];
- FORMAT_MESSAGE(message);
- LOG4CXX_DEBUG(getLogger(), ident << ", " << message);
+ va_list ap;
+ va_start(ap, ident);
+ getLoggerPtr()->debug(ident, ap);
+ va_end(ap);
}
}
void
-Logger::info(isc::log::MessageID ident, ...) {
+Logger::info(const isc::log::MessageID& ident, ...) {
if (isInfoEnabled()) {
- char message[MESSAGE_SIZE];
- FORMAT_MESSAGE(message);
- LOG4CXX_INFO(getLogger(), ident << ", " << message);
+ va_list ap;
+ va_start(ap, ident);
+ getLoggerPtr()->info(ident, ap);
+ va_end(ap);
}
}
void
-Logger::warn(isc::log::MessageID ident, ...) {
+Logger::warn(const isc::log::MessageID& ident, ...) {
if (isWarnEnabled()) {
- char message[MESSAGE_SIZE];
- FORMAT_MESSAGE(message);
- LOG4CXX_WARN(getLogger(), ident << ", " << message);
+ va_list ap;
+ va_start(ap, ident);
+ getLoggerPtr()->warn(ident, ap);
+ va_end(ap);
}
}
void
-Logger::error(isc::log::MessageID ident, ...) {
+Logger::error(const isc::log::MessageID& ident, ...) {
if (isErrorEnabled()) {
- char message[MESSAGE_SIZE];
- FORMAT_MESSAGE(message);
- LOG4CXX_ERROR(getLogger(), ident << ", " << message);
+ va_list ap;
+ va_start(ap, ident);
+ getLoggerPtr()->error(ident, ap);
+ va_end(ap);
}
}
void
-Logger::fatal(isc::log::MessageID ident, ...) {
+Logger::fatal(const isc::log::MessageID& ident, ...) {
if (isFatalEnabled()) {
- char message[MESSAGE_SIZE];
- FORMAT_MESSAGE(message);
- LOG4CXX_FATAL(getLogger(), ident << ", " << message);
+ va_list ap;
+ va_start(ap, ident);
+ getLoggerPtr()->fatal(ident, ap);
+ va_end(ap);
}
}
+bool Logger::operator==(Logger& other) {
+ return (*getLoggerPtr() == *other.getLoggerPtr());
+}
+
+// Protected methods (used for testing)
+
+void
+Logger::reset() {
+ LoggerImpl::reset();
+}
} // namespace log
} // namespace isc
diff --git a/src/lib/log/logger.h b/src/lib/log/logger.h
index f26f6d1..691eb73 100644
--- a/src/lib/log/logger.h
+++ b/src/lib/log/logger.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,36 +12,42 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __LOGGER_H
#define __LOGGER_H
#include <cstdlib>
#include <string>
-#include <boost/lexical_cast.hpp>
-#include <log4cxx/logger.h>
-#include <log/dbglevels.h>
+#include <log/debug_levels.h>
+#include <log/logger_levels.h>
#include <log/message_types.h>
namespace isc {
namespace log {
+/// \brief Logging API
+///
+/// This module forms the interface into the logging subsystem. Features of the
+/// system and its implementation are:
+///
+/// # Multiple logging objects can be created, each given a name; those with the
+/// same name share characteristics (like destination, level being logged
+/// etc.)
+/// # Messages can be logged at severity levels of FATAL, ERROR, WARN, INFO or
+/// DEBUG. The DEBUG level has further sub-levels numbered 0 (least
+/// informative) to 99 (most informative).
+/// # Each logger has a severity level set associated with it. When a message
+/// is logged, it is output only if it is logged at a level equal to the
+/// logger severity level or greater, e.g. if the logger's severity is WARN,
+/// only messages logged at WARN, ERROR or FATAL will be output.
+/// # Messages are identified by message identifiers, which are keys into a
+/// message dictionary.
+
+class LoggerImpl; // Forward declaration of the implementation class
+
class Logger {
public:
- /// \brief Severity Levels
- typedef enum {
- DEFAULT, // Default to logging level of parent
- DEBUG,
- INFO,
- WARN,
- ERROR,
- FATAL,
- NONE // Disable logging
- } Severity;
-
/// \brief Constructor
///
/// Creates/attaches to a logger of a specific name.
@@ -50,35 +56,31 @@ public:
/// this creates an instance of the root logger; otherwise it creates a
/// child of the root logger.
///
- /// \param exit_delete This argument is present to get round a bug in
- /// log4cxx. If a log4cxx logger is declared outside an execution unit, it
- /// is not deleted until the program runs down. At that point all such
- /// objects - including internal log4cxx objects - are deleted. However,
- /// there seems to be a bug in log4cxx where the way that such objects are
- /// destroyed causes a MutexException to be thrown (this is described in
+ /// \param infunc This argument is present to get round a bug in some
+ /// implementations of the logging system. If the logger is declared in
+ /// a function (such that it will be deleted when the function exits,
+ /// before the program ends), set this true. If declared outside a
+ /// function (such that it gets deleted during program rundown), set false
+ /// (the default).\n
+ /// \n
+ /// The problems encountered was that during program rundown, one logging
+ /// implementation (log4cxx) threw a MutexException (this is described in
/// https://issues.apache.org/jira/browse/LOGCXX-322). As this only occurs
/// during program rundown, the issue is not serious - it just looks bad to
/// have the program crash instead of shut down cleanly.\n
/// \n
- /// The original implementation of the isc::log::Logger had as a member a
- /// log4cxx logger (actually a LoggerPtr). If the isc:: Logger was declared
- /// statically, when it was destroyed at the end of the program the internal
- /// LoggerPtr was destroyed, which triggered the problem. The problem did
- /// not occur if the isc::log::Logger was created on the stack. To get
- /// round this, the internal LoggerPtr is now created dynamically. The
- /// exit_delete argument controls its destruction: if true, it is destroyed
- /// in the ISC Logger destructor. If false, it is not.\n
+ /// If log4cxx is chosen as the implementation, this flag controls the
+ /// deletion of the underlying log4cxx data structures when the logger is
+ /// deleted. Setting it false for externally-declared loggers inhibits
+ /// their deletion; so at program exit the memory is not reclaimed during
+ /// program rundown, only when the process is delected. Setting it true
+ /// for loggers that will be deleted in the normal running of the program
+ /// enables their deletion - which causes no issues as the problem only
+ /// manifests itself during program rundown.
/// \n
- /// When creating an isc::log::Logger on the stack, the argument should be
- /// false (the default); when the Logger is destroyed, all the internal
- /// log4cxx objects are destroyed. As only the logger (and not the internal
- /// log4cxx data structures are being destroyed), all is well. However,
- /// when creating the logger statically, the argument should be false. This
- /// means that the log4cxx objects are not destroyed at program rundown;
- /// instead memory is reclaimed and files are closed when the process is
- /// destroyed, something that does not trigger the bug.
- Logger(const std::string& name, bool exit_delete = false) :
- loggerptr_(), name_(name), exit_delete_(exit_delete)
+ /// The flag has no effect on non-log4cxx implementations.
+ Logger(const std::string& name, bool infunc = false) :
+ loggerptr_(NULL), name_(name), infunc_(infunc)
{}
@@ -86,28 +88,10 @@ public:
virtual ~Logger();
- /// \brief Configure Options
- ///
- /// TEMPORARY: Pass in the command-line options to set the logging severity
- /// for the root logger. Future versions of the logger will get this
- /// information from the configuration database.
- ///
- /// \param severity Severity level to log
- /// \param dbglevel If the severity is DEBUG, this is the debug level.
- /// This can be in the range 1 to 100 and controls the verbosity. A value
- /// outside these limits is silently coerced to the nearest boundary.
- /// \param local_file If provided, the name of a message file to read in and
- /// supersede one or more of the current messages.
- static void runTimeInit(Severity severity = INFO, int dbglevel = 1,
- const char* local_file = NULL);
-
-
/// \brief Get Name of Logger
///
/// \return The full name of the logger (including the root name)
- virtual std::string getName() {
- return getLogger()->getName();
- }
+ virtual std::string getName();
/// \brief Set Severity Level for Logger
@@ -119,31 +103,28 @@ public:
/// \param dbglevel If the severity is DEBUG, this is the debug level.
/// This can be in the range 1 to 100 and controls the verbosity. A value
/// outside these limits is silently coerced to the nearest boundary.
- virtual void setSeverity(Severity severity, int dbglevel = 1);
+ virtual void setSeverity(isc::log::Severity severity, int dbglevel = 1);
/// \brief Get Severity Level for Logger
///
/// \return The current logging level of this logger. In most cases though,
/// the effective logging level is what is required.
- virtual Severity getSeverity() {
- return getSeverityCommon(getLogger(), false);
- }
+ virtual isc::log::Severity getSeverity();
+
/// \brief Get Effective Severity Level for Logger
///
/// \return The effective severity level of the logger. This is the same
/// as getSeverity() if the logger has a severity level set, but otherwise
/// is the severity of the parent.
- virtual Severity getEffectiveSeverity() {
- return getSeverityCommon(getLogger(), true);
- }
+ virtual isc::log::Severity getEffectiveSeverity();
/// \brief Return DEBUG Level
///
/// \return Current setting of debug level. This is returned regardless of
- /// whether the
+ /// whether the severity is set to debug.
virtual int getDebugLevel();
@@ -152,35 +133,23 @@ public:
/// \param dbglevel Level for which debugging is checked. Debugging is
/// enabled only if the logger has DEBUG enabled and if the dbglevel
/// checked is less than or equal to the debug level set for the logger.
- virtual bool
- isDebugEnabled(int dbglevel = MIN_DEBUG_LEVEL) {
- return (getLogger()->getEffectiveLevel()->toInt() <=
- (log4cxx::Level::DEBUG_INT - dbglevel));
- }
+ virtual bool isDebugEnabled(int dbglevel = MIN_DEBUG_LEVEL);
/// \brief Is INFO Enabled?
- virtual bool isInfoEnabled() {
- return (getLogger()->isInfoEnabled());
- }
+ virtual bool isInfoEnabled();
/// \brief Is WARNING Enabled?
- virtual bool isWarnEnabled() {
- return (getLogger()->isWarnEnabled());
- }
+ virtual bool isWarnEnabled();
/// \brief Is ERROR Enabled?
- virtual bool isErrorEnabled() {
- return (getLogger()->isErrorEnabled());
- }
+ virtual bool isErrorEnabled();
/// \brief Is FATAL Enabled?
- virtual bool isFatalEnabled() {
- return (getLogger()->isFatalEnabled());
- }
+ virtual bool isFatalEnabled();
/// \brief Output Debug Message
@@ -189,38 +158,35 @@ public:
/// are used for more verbose output.
/// \param ident Message identification.
/// \param ... Optional arguments for the message.
- void debug(int dbglevel, MessageID ident, ...);
+ void debug(int dbglevel, const MessageID& ident, ...);
/// \brief Output Informational Message
///
/// \param ident Message identification.
/// \param ... Optional arguments for the message.
- void info(MessageID ident, ...);
+ void info(const MessageID& ident, ...);
/// \brief Output Warning Message
///
/// \param ident Message identification.
/// \param ... Optional arguments for the message.
- void warn(MessageID ident, ...);
+ void warn(const MessageID& ident, ...);
/// \brief Output Error Message
///
/// \param ident Message identification.
/// \param ... Optional arguments for the message.
- void error(MessageID ident, ...);
+ void error(const MessageID& ident, ...);
/// \brief Output Fatal Message
///
/// \param ident Message identification.
/// \param ... Optional arguments for the message.
- void fatal(MessageID ident, ...);
-
-
-protected:
+ void fatal(const MessageID& ident, ...);
/// \brief Equality
///
@@ -228,96 +194,55 @@ protected:
/// (This method is principally for testing.)
///
/// \return true if the logger objects are instances of the same logger.
- bool operator==(const Logger& other) const {
- return (*loggerptr_ == *other.loggerptr_);
- }
-
-
- /// \brief Logger Initialized
- ///
- /// Check that the logger has been properly initialized. (This method
- /// is principally for testing.)
- ///
- /// \return true if this logger object has been initialized.
- bool isInitialized() const {
- return (loggerptr_ != NULL);
- }
+ bool operator==(Logger& other);
+protected:
- /// \brief Get Severity Level for Logger
- ///
- /// This is common code for getSeverity() and getEffectiveSeverity() -
- /// it returns the severity of the logger; if not set (and the check_parent)
- /// flag is set, it searches up the parent-child tree until a severity
- /// level is found and uses that.
+ /// \brief Reset Global Data
///
- /// \param ptrlogger Pointer to the log4cxx logger to check.
- /// \param check_parent true to search up the tree, false to return the
- /// current level.
- ///
- /// \return The effective severity level of the logger. This is the same
- /// as getSeverity() if the logger has a severity level set, but otherwise
- /// is the severity of the parent.
- Logger::Severity getSeverityCommon(const log4cxx::LoggerPtr& ptrlogger,
- bool check_parent) const;
-
+ /// Used for testing, this calls upon the underlying logger implementation
+ /// to clear any global data.
+ static void reset();
- /// \brief Convert Between BIND-10 and log4cxx Logging Levels
- ///
- /// Converts between the numeric value of the log4cxx logging level
- /// and the BIND-10 severity level.
- ///
- /// \param value log4cxx numeric logging level
+private:
+ /// \brief Copy Constructor
///
- /// \return BIND-10 logging severity
- Severity convertLevel(int value) const;
-
+ /// Disabled (marked private) as it makes no sense to copy the logger -
+ /// just create another one of the same name.
+ Logger(const Logger&);
- /// \brief Initialize log4cxx Logger
+ /// \brief Assignment Operator
///
- /// Creates the log4cxx logger used internally. A function is provided for
- /// this so that the creation does not take place when this Logger object
- /// is created but when it is used. As the latter occurs in executable
- /// code but the former can occur during initialization, this order
- /// guarantees that anything that is statically initialized has completed
- /// its initialization by the time the logger is used.
- void initLogger();
+ /// Disabled (marked private) as it makes no sense to copy the logger -
+ /// just create another one of the same name.
+ Logger& operator=(const Logger&);
-
- /// \brief Return log4cxx Logger
- ///
- /// Returns the log4cxx logger, initializing it if not already initialized.
+ /// \brief Initialize Implementation
///
- /// \return Loggerptr object
- log4cxx::LoggerPtr& getLogger() {
- if (loggerptr_ == NULL) {
- initLogger();
+ /// Returns the logger pointer. If not yet set, the underlying
+ /// implementation class is initialized.\n
+ /// \n
+ /// The reason for this indirection is to avoid the "static initialization
+ /// fiacso", whereby we cannot rely on the order of static initializations.
+ /// The main problem is the root logger name - declared statically - which
+ /// is referenced by various loggers. By deferring a reference to it until
+ /// after the program starts executing - by which time the root name object
+ /// will be initialized - we avoid this problem.
+ ///
+ /// \return Returns pointer to implementation
+ LoggerImpl* getLoggerPtr() {
+ if (!loggerptr_) {
+ initLoggerImpl();
}
- return *loggerptr_;
+ return (loggerptr_);
}
+ /// \brief Initialize Underlying Implementation and Set loggerptr_
+ void initLoggerImpl();
- /// \brief Read Local Message File
- ///
- /// Reads a local message file into the global dictionary, replacing any
- /// definitions there. Any messages found in the local file that do not
- /// replace ones in the global dictionary are listed.
- ///
- /// \param file Local message file to be read.
- static void readLocalMessageFile(const char* file);
-
-private:
- // Note that loggerptr_ is a pointer to a LoggerPtr, which is itself a
- // pointer to the underlying log4cxx logger. This is due to the problems
- // with memory deletion on program exit, explained in the comments for
- // the "exit_delete" parameter in this class's constructor.
-
- log4cxx::LoggerPtr* loggerptr_; ///< Pointer to the underlying logger
- std::string name_; ///< Name of this logger]
- bool exit_delete_; ///< Delete loggerptr_ on exit?
-
- // NOTE - THIS IS A PLACE HOLDER
- static bool init_; ///< Set true when initialized
+ LoggerImpl* loggerptr_; ///< Pointer to the underlying logger
+ std::string name_; ///< Copy of the logger name
+ bool infunc_; ///< Copy of the infunc argument
};
} // namespace log
diff --git a/src/lib/log/logger_impl.cc b/src/lib/log/logger_impl.cc
new file mode 100644
index 0000000..4b19360
--- /dev/null
+++ b/src/lib/log/logger_impl.cc
@@ -0,0 +1,221 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE
+
+#include <iostream>
+#include <algorithm>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <boost/lexical_cast.hpp>
+
+#include <log/debug_levels.h>
+#include <log/root_logger_name.h>
+#include <log/logger.h>
+#include <log/logger_impl.h>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+#include <log/root_logger_name.h>
+#include <log/strutil.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// Static initializations
+
+LoggerImpl::LoggerInfoMap LoggerImpl::logger_info_;
+LoggerImpl::LoggerInfo LoggerImpl::root_logger_info_(isc::log::INFO, 0);
+
+// Constructor
+LoggerImpl::LoggerImpl(const std::string& name, bool)
+{
+ // Are we the root logger?
+ if (name == getRootLoggerName()) {
+ is_root_ = true;
+ name_ = name;
+ } else {
+ is_root_ = false;
+ name_ = getRootLoggerName() + "." + name;
+ }
+}
+
+// Destructor. (Here because of virtual declaration.)
+
+LoggerImpl::~LoggerImpl() {
+}
+
+// Set the severity for logging.
+
+void
+LoggerImpl::setSeverity(isc::log::Severity severity, int dbglevel) {
+
+ // Silently coerce the debug level into the valid range of 0 to 99
+
+ int debug_level = max(MIN_DEBUG_LEVEL, min(MAX_DEBUG_LEVEL, dbglevel));
+ if (is_root_) {
+
+ // Can only set severity for the root logger, you can't disable it.
+ // Any attempt to do so is silently ignored.
+ if (severity != isc::log::DEFAULT) {
+ root_logger_info_ = LoggerInfo(severity, debug_level);
+ }
+
+ } else if (severity == isc::log::DEFAULT) {
+
+ // Want to set to default; this means removing the information
+ // about this logger from the logger_info_ if it is set.
+ LoggerInfoMap::iterator i = logger_info_.find(name_);
+ if (i != logger_info_.end()) {
+ logger_info_.erase(i);
+ }
+
+ } else {
+
+ // Want to set this information
+ logger_info_[name_] = LoggerInfo(severity, debug_level);
+ }
+}
+
+// Return severity level
+
+isc::log::Severity
+LoggerImpl::getSeverity() {
+
+ if (is_root_) {
+ return (root_logger_info_.severity);
+ }
+ else {
+ LoggerInfoMap::iterator i = logger_info_.find(name_);
+ if (i != logger_info_.end()) {
+ return ((i->second).severity);
+ }
+ else {
+ return (isc::log::DEFAULT);
+ }
+ }
+}
+
+// Get effective severity. Either the current severity or, if not set, the
+// severity of the root level.
+
+isc::log::Severity
+LoggerImpl::getEffectiveSeverity() {
+
+ if (!is_root_ && !logger_info_.empty()) {
+
+ // Not root logger and there is at least one item in the info map for a
+ // logger.
+ LoggerInfoMap::iterator i = logger_info_.find(name_);
+ if (i != logger_info_.end()) {
+
+ // Found, so return the severity.
+ return ((i->second).severity);
+ }
+ }
+
+ // Must be the root logger, or this logger is defaulting to the root logger
+ // settings.
+ return (root_logger_info_.severity);
+}
+
+// Get the debug level. This returns 0 unless the severity is DEBUG.
+
+int
+LoggerImpl::getDebugLevel() {
+
+ if (!is_root_ && !logger_info_.empty()) {
+
+ // Not root logger and there is something in the map, check if there
+ // is a setting for this one.
+ LoggerInfoMap::iterator i = logger_info_.find(name_);
+ if (i != logger_info_.end()) {
+
+ // Found, so return the debug level.
+ if ((i->second).severity == isc::log::DEBUG) {
+ return ((i->second).dbglevel);
+ } else {
+ return (0);
+ }
+ }
+ }
+
+ // Must be the root logger, or this logger is defaulting to the root logger
+ // settings.
+ if (root_logger_info_.severity == isc::log::DEBUG) {
+ return (root_logger_info_.dbglevel);
+ } else {
+ return (0);
+ }
+}
+
+// The code for isXxxEnabled is quite simple and is in the header. The only
+// exception is isDebugEnabled() where we have the complication of the debug
+// levels.
+
+bool
+LoggerImpl::isDebugEnabled(int dbglevel) {
+
+ if (!is_root_ && !logger_info_.empty()) {
+
+ // Not root logger and there is something in the map, check if there
+ // is a setting for this one.
+ LoggerInfoMap::iterator i = logger_info_.find(name_);
+ if (i != logger_info_.end()) {
+
+ // Found, so return the debug level.
+ if ((i->second).severity <= isc::log::DEBUG) {
+ return ((i->second).dbglevel >= dbglevel);
+ } else {
+ return (false); // Nothing lower than debug
+ }
+ }
+ }
+
+ // Must be the root logger, or this logger is defaulting to the root logger
+ // settings.
+ if (root_logger_info_.severity <= isc::log::DEBUG) {
+ return (root_logger_info_.dbglevel >= dbglevel);
+ } else {
+ return (false);
+ }
+}
+
+// Output a general message
+
+void
+LoggerImpl::output(const char* sev_text, const MessageID& ident,
+ va_list ap)
+{
+ char message[512]; // Should be large enough for any message
+
+ // Obtain text of the message and substitute arguments.
+ const string format = MessageDictionary::globalDictionary().getText(ident);
+ vsnprintf(message, sizeof(message), format.c_str(), ap);
+
+ // Get the time in a struct tm format, and convert to text
+ time_t t_time;
+ time(&t_time);
+ struct tm* tm_time = localtime(&t_time);
+
+ char chr_time[32];
+ (void) strftime(chr_time, sizeof(chr_time), "%Y-%m-%d %H:%M:%S", tm_time);
+
+ // Now output.
+ std::cout << chr_time << " " << sev_text << " [" << getName() << "] " <<
+ ident << ", " << message << "\n";
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/logger_impl.h b/src/lib/log/logger_impl.h
new file mode 100644
index 0000000..9fc9cf9
--- /dev/null
+++ b/src/lib/log/logger_impl.h
@@ -0,0 +1,267 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __LOGGER_IMPL_H
+#define __LOGGER_IMPL_H
+
+#include <stdarg.h>
+#include <time.h>
+
+#include <cstdlib>
+#include <string>
+#include <map>
+#include <utility>
+
+#include <log/debug_levels.h>
+#include <log/logger.h>
+#include <log/message_types.h>
+#include <log/root_logger_name.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Console Logger Implementation
+///
+/// The logger uses a "pimpl" idiom for implementation, where the base logger
+/// class contains little more than a pointer to the implementation class, and
+/// all actions are carried out by the latter. This class is an implementation
+/// class that just outputs to stdout.
+
+class LoggerImpl {
+public:
+
+ /// \brief Information About Logger
+ ///
+ /// Holds a information about a logger, namely its severity and its debug
+ /// level. This could be a std::pair, except that it gets confusing when
+ /// accessing the LoggerInfoMap: that returns a pair, so we to reference
+ /// elements we would use constructs like ((i->first).second);
+ struct LoggerInfo {
+ isc::log::Severity severity;
+ int dbglevel;
+
+ LoggerInfo(isc::log::Severity sev = isc::log::INFO,
+ int dbg = MIN_DEBUG_LEVEL) : severity(sev), dbglevel(dbg)
+ {}
+ };
+
+
+ /// \brief Information About All Loggers
+ ///
+ /// Information about all loggers in the system - except the root logger -
+ /// is held in a map, linking name of the logger (excluding the root
+ /// name component) and its set severity and debug levels. The root
+ /// logger information is held separately.
+ typedef std::map<std::string, LoggerInfo> LoggerInfoMap;
+
+
+ /// \brief Constructor
+ ///
+ /// Creates a logger of the specific name.
+ ///
+ /// \param name Name of the logger.
+ ///
+ /// \param exit_delete This argument is present to get round a bug in
+ /// the log4cxx implementation. It is unused here.
+ LoggerImpl(const std::string& name, bool);
+
+
+ /// \brief Destructor
+ virtual ~LoggerImpl();
+
+
+ /// \brief Get the full name of the logger (including the root name)
+ virtual std::string getName() {
+ return (name_);
+ }
+
+
+ /// \brief Set Severity Level for Logger
+ ///
+ /// Sets the level at which this logger will log messages. If none is set,
+ /// the level is inherited from the parent.
+ ///
+ /// \param severity Severity level to log
+ /// \param dbglevel If the severity is DEBUG, this is the debug level.
+ /// This can be in the range 1 to 100 and controls the verbosity. A value
+ /// outside these limits is silently coerced to the nearest boundary.
+ virtual void setSeverity(isc::log::Severity severity, int dbglevel = 1);
+
+
+ /// \brief Get Severity Level for Logger
+ ///
+ /// \return The current logging level of this logger. In most cases though,
+ /// the effective logging level is what is required.
+ virtual isc::log::Severity getSeverity();
+
+
+ /// \brief Get Effective Severity Level for Logger
+ ///
+ /// \return The effective severity level of the logger. This is the same
+ /// as getSeverity() if the logger has a severity level set, but otherwise
+ /// is the severity of the parent.
+ virtual isc::log::Severity getEffectiveSeverity();
+
+
+ /// \brief Return DEBUG Level
+ ///
+ /// \return Current setting of debug level. This is returned regardless of
+ /// whether the
+ virtual int getDebugLevel();
+
+
+ /// \brief Returns if Debug Message Should Be Output
+ ///
+ /// \param dbglevel Level for which debugging is checked. Debugging is
+ /// enabled only if the logger has DEBUG enabled and if the dbglevel
+ /// checked is less than or equal to the debug level set for the logger.
+ virtual bool
+ isDebugEnabled(int dbglevel = MIN_DEBUG_LEVEL);
+
+ /// \brief Is INFO Enabled?
+ virtual bool isInfoEnabled() {
+ return (isEnabled(isc::log::INFO));
+ }
+
+ /// \brief Is WARNING Enabled?
+ virtual bool isWarnEnabled() {
+ return (isEnabled(isc::log::WARN));
+ }
+
+ /// \brief Is ERROR Enabled?
+ virtual bool isErrorEnabled() {
+ return (isEnabled(isc::log::ERROR));
+ }
+
+ /// \brief Is FATAL Enabled?
+ virtual bool isFatalEnabled() {
+ return (isEnabled(isc::log::FATAL));
+ }
+
+
+ /// \brief Common Severity check
+ ///
+ /// Implements the common severity check. As an optimisation, this checks
+ /// to see if any logger-specific levels have been set (a quick check as it
+ /// just involves seeing if the collection of logger information is empty).
+ /// if not, it returns the information for the root level; if so, it has
+ /// to take longer and look up the information in the map holding the
+ /// logging details.
+ virtual bool isEnabled(isc::log::Severity severity) {
+ if (logger_info_.empty()) {
+ return (root_logger_info_.severity <= severity);
+ }
+ else {
+ return (getSeverity() <= severity);
+ }
+ }
+
+
+ /// \brief Output General Message
+ ///
+ /// The message is formatted to include the date and time, the severity
+ /// and the logger generating the message.
+ ///
+ /// \param sev_text Severity level as a text string
+ /// \param ident Message identification
+ /// \param ap Variable argument list holding message arguments
+ void output(const char* sev_text, const MessageID& ident,
+ va_list ap);
+
+
+ /// \brief Output Debug Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ /// \param ap Variable argument list holding message arguments
+ void debug(const MessageID& ident, va_list ap) {
+ output("DEBUG", ident, ap);
+ }
+
+
+ /// \brief Output Informational Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ /// \param ap Variable argument list holding message arguments
+ void info(const MessageID& ident, va_list ap) {
+ output("INFO ", ident, ap);
+ }
+
+ /// \brief Output Warning Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ /// \param ap Variable argument list holding message arguments
+ void warn(const MessageID& ident, va_list ap) {
+ output("WARN ", ident, ap);
+ }
+
+ /// \brief Output Error Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ /// \param ap Variable argument list holding message arguments
+ void error(const MessageID& ident, va_list ap) {
+ output("ERROR", ident, ap);
+ }
+
+ /// \brief Output Fatal Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ /// \param ap Variable argument list holding message arguments
+ void fatal(const MessageID& ident, va_list ap) {
+ output("FATAL", ident, ap);
+ }
+
+ /// \brief Equality
+ ///
+ /// Check if two instances of this logger refer to the same stream.
+ /// (This method is principally for testing.)
+ ///
+ /// \return true if the logger objects are instances of the same logger.
+ bool operator==(const LoggerImpl& other) {
+ return (name_ == other.name_);
+ }
+
+
+ /// \brief Reset Global Data
+ ///
+ /// Only used for testing, this clears all the logger information and
+ /// resets it back to default values.
+ static void reset() {
+ root_logger_info_ = LoggerInfo(isc::log::INFO, MIN_DEBUG_LEVEL);
+ logger_info_.clear();
+ }
+
+
+private:
+ bool is_root_; ///< true if a root logger
+ std::string name_; ///< Name of this logger
+
+ // Split the status of the root logger from this logger. If - is will
+ // probably be the usual case - no per-logger setting is enabled, a
+ // quick check of logger_info_.empty() will return true and we can quickly
+ // return the root logger status without a length lookup in the map.
+
+ static LoggerInfo root_logger_info_; ///< Status of root logger
+ static LoggerInfoMap logger_info_; ///< Store of debug levels etc.
+};
+
+} // namespace log
+} // namespace isc
+
+
+#endif // __LOGGER_IMPL_H
diff --git a/src/lib/log/logger_impl_log4cxx.cc b/src/lib/log/logger_impl_log4cxx.cc
new file mode 100644
index 0000000..404fd03
--- /dev/null
+++ b/src/lib/log/logger_impl_log4cxx.cc
@@ -0,0 +1,241 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE
+
+#include <iostream>
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <log4cxx/appender.h>
+#include <log4cxx/basicconfigurator.h>
+#include <log4cxx/patternlayout.h>
+#include <log4cxx/consoleappender.h>
+
+#include <log/root_logger_name.h>
+#include <log/logger.h>
+#include <log/logger_impl.h>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+#include <log/strutil.h>
+#include <log/xdebuglevel.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// Static initializations
+
+bool LoggerImpl::init_ = false;
+
+// Destructor. Delete log4cxx stuff if "don't delete" is clear.
+
+LoggerImpl::~LoggerImpl() {
+ if (exit_delete_) {
+ delete loggerptr_;
+ }
+}
+
+// Initialize logger - create a logger as a child of the root logger. With
+// log4cxx this is assured by naming the logger <parent>.<child>.
+
+void
+LoggerImpl::initLogger() {
+
+ // Initialize basic logging if not already done. This is a one-off for
+ // all loggers.
+ if (!init_) {
+
+ // TEMPORARY
+ // Add a suitable console logger to the log4cxx root logger. (This
+ // is the logger at the root of the log4cxx tree, not the BIND-10 root
+ // logger, which is one level down.) The chosen format is:
+ //
+ // YYYY-MM-DD hh:mm:ss.sss [logger] SEVERITY: text
+ //
+ // As noted, this is a temporary hack: it is done here to ensure that
+ // a suitable output and output pattern is set. Future versions of the
+ // software will set this based on configuration data.
+
+ log4cxx::LayoutPtr layout(
+ new log4cxx::PatternLayout(
+ "%d{yyyy-MM-DD HH:mm:ss.SSS} %-5p [%c] %m\n"));
+ log4cxx::AppenderPtr console(
+ new log4cxx::ConsoleAppender(layout));
+ log4cxx::LoggerPtr sys_root_logger = log4cxx::Logger::getRootLogger();
+ sys_root_logger->addAppender(console);
+
+ // Set the default logging to INFO
+ sys_root_logger->setLevel(log4cxx::Level::getInfo());
+
+ // All static stuff initialized
+ init_ = true;
+ }
+
+ // Initialize this logger. Name this as to whether the BIND-10 root logger
+ // name has been set. (If not, this mucks up the hierarchy :-( ).
+ string root_name = RootLoggerName::getName();
+ if (root_name.empty() || (name_ == root_name)) {
+ loggerptr_ = new log4cxx::LoggerPtr(log4cxx::Logger::getLogger(name_));
+ }
+ else {
+ loggerptr_ = new log4cxx::LoggerPtr(
+ log4cxx::Logger::getLogger(root_name + "." + name_)
+ );
+ }
+}
+
+
+// Set the severity for logging. There is a 1:1 mapping between the logging
+// severity and the log4cxx logging levels, apart from DEBUG.
+//
+// In log4cxx, each of the logging levels (DEBUG, INFO, WARN etc.) has a numeric
+// value. The level is set to one of these and any numeric level equal to or
+// above it that is reported. For example INFO has a value of 20000 and ERROR
+// a value of 40000. So if a message of WARN severity (= 30000) is logged, it is
+// not logged when the logger's severity level is ERROR (as 30000 !>= 40000).
+// It is reported if the logger's severity level is set to WARN (as 30000 >=
+/// 30000) or INFO (30000 >= 20000).
+//
+// This gives a simple system for handling different debug levels. The debug
+// level is a number between 0 and 99, with 0 being least verbose and 99 the
+// most. To implement this seamlessly, when DEBUG is set, the numeric value
+// of the logging level is actually set to (DEBUG - debug-level). Similarly
+// messages of level "n" are logged at a logging level of (DEBUG - n). Thus if
+// the logging level is set to DEBUG and the debug level set to 25, the actual
+// level set is 10000 - 25 = 99975.
+//
+// Attempting to log a debug message of level 26 is an attempt to log a message
+// of level 10000 - 26 = 9974. As 9974 !>= 9975, it is not logged. A
+// message of level 25 is, because 9975 >= 9975.
+//
+// The extended set of logging levels is implemented by the XDebugLevel class.
+
+void
+LoggerImpl::setSeverity(isc::log::Severity severity, int dbglevel) {
+ switch (severity) {
+ case NONE:
+ getLogger()->setLevel(log4cxx::Level::getOff());
+ break;
+
+ case FATAL:
+ getLogger()->setLevel(log4cxx::Level::getFatal());
+ break;
+
+ case ERROR:
+ getLogger()->setLevel(log4cxx::Level::getError());
+ break;
+
+ case WARN:
+ getLogger()->setLevel(log4cxx::Level::getWarn());
+ break;
+
+ case INFO:
+ getLogger()->setLevel(log4cxx::Level::getInfo());
+ break;
+
+ case DEBUG:
+ getLogger()->setLevel(
+ log4cxx::XDebugLevel::getExtendedDebug(dbglevel));
+ break;
+
+ // Will get here for DEFAULT or any other value. This disables the
+ // logger's own severity and it defaults to the severity of the parent
+ // logger.
+ default:
+ getLogger()->setLevel(0);
+ }
+}
+
+// Convert between numeric log4cxx logging level and BIND-10 logging severity.
+
+isc::log::Severity
+LoggerImpl::convertLevel(int value) {
+
+ // The order is optimised. This is only likely to be called when testing
+ // for writing debug messages, so the check for DEBUG_INT is first.
+ if (value <= log4cxx::Level::DEBUG_INT) {
+ return (DEBUG);
+ } else if (value <= log4cxx::Level::INFO_INT) {
+ return (INFO);
+ } else if (value <= log4cxx::Level::WARN_INT) {
+ return (WARN);
+ } else if (value <= log4cxx::Level::ERROR_INT) {
+ return (ERROR);
+ } else if (value <= log4cxx::Level::FATAL_INT) {
+ return (FATAL);
+ } else {
+ return (NONE);
+ }
+}
+
+
+// Return the logging severity associated with this logger.
+
+isc::log::Severity
+LoggerImpl::getSeverityCommon(const log4cxx::LoggerPtr& ptrlogger,
+ bool check_parent) {
+
+ log4cxx::LevelPtr level = ptrlogger->getLevel();
+ if (level == log4cxx::LevelPtr()) {
+
+ // Null level returned, logging should be that of the parent.
+
+ if (check_parent) {
+ log4cxx::LoggerPtr parent = ptrlogger->getParent();
+ if (parent == log4cxx::LoggerPtr()) {
+
+ // No parent, so reached the end of the chain. Return INFO
+ // severity.
+ return (INFO);
+ }
+ else {
+ return (getSeverityCommon(parent, check_parent));
+ }
+ }
+ else {
+ return (DEFAULT);
+ }
+ } else {
+ return (convertLevel(level->toInt()));
+ }
+}
+
+
+// Get the debug level. This returns 0 unless the severity is DEBUG.
+
+int
+LoggerImpl::getDebugLevel() {
+
+ log4cxx::LevelPtr level = getLogger()->getLevel();
+ if (level == log4cxx::LevelPtr()) {
+
+ // Null pointer returned, logging should be that of the parent.
+ return (0);
+
+ } else {
+ int severity = level->toInt();
+ if (severity <= log4cxx::Level::DEBUG_INT) {
+ return (log4cxx::Level::DEBUG_INT - severity);
+ }
+ else {
+ return (0);
+ }
+ }
+}
+
+
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/logger_impl_log4cxx.h b/src/lib/log/logger_impl_log4cxx.h
new file mode 100644
index 0000000..3101347
--- /dev/null
+++ b/src/lib/log/logger_impl_log4cxx.h
@@ -0,0 +1,315 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __LOGGER_IMPL_LOG4CXX_H
+#define __LOGGER_IMPL_LOG4CXX_H
+
+#include <cstdlib>
+#include <string>
+#include <boost/lexical_cast.hpp>
+#include <log4cxx/logger.h>
+#include <log4cxx/logger.h>
+
+#include <log/debug_levels.h>
+#include <log/logger.h>
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Log4cxx Logger Implementation
+///
+/// The logger uses a "pimpl" idiom for implementation, where the base logger
+/// class contains little more than a pointer to the implementation class, and
+/// all actions are carried out by the latter. This class is an implementation
+/// class interfacing to the log4cxx logging system.
+
+class LoggerImpl {
+public:
+
+ /// \brief Constructor
+ ///
+ /// Creates/attaches to a logger of a specific name.
+ ///
+ /// \param name Name of the logger. If the name is that of the root name,
+ /// this creates an instance of the root logger; otherwise it creates a
+ /// child of the root logger.
+ ///
+ /// \param exit_delete This argument is present to get round a bug in
+ /// log4cxx. If a log4cxx logger is declared outside an execution unit, it
+ /// is not deleted until the program runs down. At that point all such
+ /// objects - including internal log4cxx objects - are deleted. However,
+ /// there seems to be a bug in log4cxx where the way that such objects are
+ /// destroyed causes a MutexException to be thrown (this is described in
+ /// https://issues.apache.org/jira/browse/LOGCXX-322). As this only occurs
+ /// during program rundown, the issue is not serious - it just looks bad to
+ /// have the program crash instead of shut down cleanly.\n
+ /// \n
+ /// The original implementation of the isc::log::Logger had as a member a
+ /// log4cxx logger (actually a LoggerPtr). If the isc:: Logger was declared
+ /// statically, when it was destroyed at the end of the program the internal
+ /// LoggerPtr was destroyed, which triggered the problem. The problem did
+ /// not occur if the isc::log::Logger was created on the stack. To get
+ /// round this, the internal LoggerPtr is now created dynamically. The
+ /// exit_delete argument controls its destruction: if true, it is destroyed
+ /// in the ISC Logger destructor. If false, it is not.\n
+ /// \n
+ /// When creating an isc::log::Logger on the stack, the argument should be
+ /// false (the default); when the Logger is destroyed, all the internal
+ /// log4cxx objects are destroyed. As only the logger (and not the internal
+ /// log4cxx data structures are being destroyed), all is well. However,
+ /// when creating the logger statically, the argument should be false. This
+ /// means that the log4cxx objects are not destroyed at program rundown;
+ /// instead memory is reclaimed and files are closed when the process is
+ /// destroyed, something that does not trigger the bug.
+ LoggerImpl(const std::string& name, bool exit_delete = false) :
+ loggerptr_(NULL), name_(name), exit_delete_(exit_delete)
+ {}
+
+
+ /// \brief Destructor
+ virtual ~LoggerImpl();
+
+
+ /// \brief Get the full name of the logger (including the root name)
+ virtual std::string getName() {
+ return (getLogger()->getName());
+ }
+
+
+ /// \brief Set Severity Level for Logger
+ ///
+ /// Sets the level at which this logger will log messages. If none is set,
+ /// the level is inherited from the parent.
+ ///
+ /// \param severity Severity level to log
+ /// \param dbglevel If the severity is DEBUG, this is the debug level.
+ /// This can be in the range 1 to 100 and controls the verbosity. A value
+ /// outside these limits is silently coerced to the nearest boundary.
+ virtual void setSeverity(isc::log::Severity severity, int dbglevel = 1);
+
+
+ /// \brief Get Severity Level for Logger
+ ///
+ /// \return The current logging level of this logger. In most cases though,
+ /// the effective logging level is what is required.
+ virtual isc::log::Severity getSeverity() {
+ return (getSeverityCommon(getLogger(), false));
+ }
+
+
+ /// \brief Get Effective Severity Level for Logger
+ ///
+ /// \return The effective severity level of the logger. This is the same
+ /// as getSeverity() if the logger has a severity level set, but otherwise
+ /// is the severity of the parent.
+ virtual isc::log::Severity getEffectiveSeverity() {
+ return (getSeverityCommon(getLogger(), true));
+ }
+
+
+ /// \brief Return DEBUG Level
+ ///
+ /// \return Current setting of debug level. This is returned regardless of
+ /// whether the
+ virtual int getDebugLevel();
+
+
+ /// \brief Returns if Debug Message Should Be Output
+ ///
+ /// \param dbglevel Level for which debugging is checked. Debugging is
+ /// enabled only if the logger has DEBUG enabled and if the dbglevel
+ /// checked is less than or equal to the debug level set for the logger.
+ virtual bool
+ isDebugEnabled(int dbglevel = MIN_DEBUG_LEVEL) {
+ return (getLogger()->getEffectiveLevel()->toInt() <=
+ (log4cxx::Level::DEBUG_INT - dbglevel));
+ }
+
+
+ /// \brief Is INFO Enabled?
+ virtual bool isInfoEnabled() {
+ return (getLogger()->isInfoEnabled());
+ }
+
+
+ /// \brief Is WARNING Enabled?
+ virtual bool isWarnEnabled() {
+ return (getLogger()->isWarnEnabled());
+ }
+
+
+ /// \brief Is ERROR Enabled?
+ virtual bool isErrorEnabled() {
+ return (getLogger()->isErrorEnabled());
+ }
+
+
+ /// \brief Is FATAL Enabled?
+ virtual bool isFatalEnabled() {
+ return (getLogger()->isFatalEnabled());
+ }
+
+
+ /// \brief Output Debug Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ void debug(const MessageID& ident, const char* text) {
+ LOG4CXX_DEBUG(getLogger(), ident << ", " << text);
+ }
+
+
+ /// \brief Output Informational Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ void info(const MessageID& ident, const char* text) {
+ LOG4CXX_INFO(getLogger(), ident << ", " << text);
+ }
+
+
+ /// \brief Output Warning Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ void warn(const MessageID& ident, const char* text) {
+ LOG4CXX_WARN(getLogger(), ident << ", " << text);
+ }
+
+
+ /// \brief Output Error Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ void error(const MessageID& ident, const char* text) {
+ LOG4CXX_ERROR(getLogger(), ident << ", " << text);
+ }
+
+
+ /// \brief Output Fatal Message
+ ///
+ /// \param ident Message identification.
+ /// \param text Text to log
+ void fatal(const MessageID& ident, const char* text) {
+ LOG4CXX_FATAL(getLogger(), ident << ", " << text);
+ }
+
+ //@{
+ /// \brief Testing Methods
+ ///
+ /// The next set of methods are used in testing. As they are accessed from
+ /// the main logger class, they must be public.
+
+ /// \brief Equality
+ ///
+ /// Check if two instances of this logger refer to the same stream.
+ /// (This method is principally for testing.)
+ ///
+ /// \return true if the logger objects are instances of the same logger.
+ bool operator==(LoggerImpl& other) {
+ return (*loggerptr_ == *other.loggerptr_);
+ }
+
+
+ /// \brief Logger Initialized
+ ///
+ /// Check that the logger has been properly initialized. (This method
+ /// is principally for testing.)
+ ///
+ /// \return true if this logger object has been initialized.
+ bool isInitialized() {
+ return (loggerptr_ != NULL);
+ }
+
+ /// \brief Reset Global Data
+ ///
+ /// Only used for testing, this clears all the logger information and
+ /// resets it back to default values. This is a no-op for log4cxx.
+ static void reset() {
+ }
+
+ //@}
+
+protected:
+
+ /// \brief Convert Between BIND-10 and log4cxx Logging Levels
+ ///
+ /// This method is marked protected to allow for unit testing.
+ ///
+ /// \param value log4cxx numeric logging level
+ ///
+ /// \return BIND-10 logging severity
+ isc::log::Severity convertLevel(int value);
+
+private:
+
+ /// \brief Get Severity Level for Logger
+ ///
+ /// This is common code for getSeverity() and getEffectiveSeverity() -
+ /// it returns the severity of the logger; if not set (and the check_parent)
+ /// flag is set, it searches up the parent-child tree until a severity
+ /// level is found and uses that.
+ ///
+ /// \param ptrlogger Pointer to the log4cxx logger to check.
+ /// \param check_parent true to search up the tree, false to return the
+ /// current level.
+ ///
+ /// \return The effective severity level of the logger. This is the same
+ /// as getSeverity() if the logger has a severity level set, but otherwise
+ /// is the severity of the parent.
+ isc::log::Severity getSeverityCommon(const log4cxx::LoggerPtr& ptrlogger,
+ bool check_parent);
+
+
+
+ /// \brief Initialize log4cxx Logger
+ ///
+ /// Creates the log4cxx logger used internally. A function is provided for
+ /// this so that the creation does not take place when this Logger object
+ /// is created but when it is used. As the latter occurs in executable
+ /// code but the former can occur during initialization, this order
+ /// guarantees that anything that is statically initialized has completed
+ /// its initialization by the time the logger is used.
+ void initLogger();
+
+
+ /// \brief Return underlying log4cxx logger, initializing it if necessary
+ ///
+ /// \return Loggerptr object
+ log4cxx::LoggerPtr& getLogger() {
+ if (loggerptr_ == NULL) {
+ initLogger();
+ }
+ return (*loggerptr_);
+ }
+
+ // Members. Note that loggerptr_ is a pointer to a LoggerPtr, which is
+ // itself a pointer to the underlying log4cxx logger. This is due to the
+ // problems with memory deletion on program exit, explained in the comments
+ // for the "exit_delete" parameter in this class's constructor.
+
+ log4cxx::LoggerPtr* loggerptr_; ///< Pointer to the underlying logger
+ std::string name_; ///< Name of this logger]
+ bool exit_delete_; ///< Delete loggerptr_ on exit?
+
+ // NOTE - THIS IS A PLACE HOLDER
+ static bool init_; ///< Set true when initialized
+};
+
+} // namespace log
+} // namespace isc
+
+
+#endif // __LOGGER_IMPL_LOG4CXX_H
diff --git a/src/lib/log/logger_levels.h b/src/lib/log/logger_levels.h
new file mode 100644
index 0000000..2f123e8
--- /dev/null
+++ b/src/lib/log/logger_levels.h
@@ -0,0 +1,42 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __LOGGER_LEVELS_H
+#define __LOGGER_LEVELS_H
+
+namespace isc {
+namespace log {
+
+/// \brief Severity Levels
+///
+/// Defines the severity levels for logging. This is shared between the logger
+/// and the implementations classes.
+///
+/// N.B. The order of the levels - DEBUG less than INFO less that WARN etc. is
+/// implicitly assumed in several implementations. They must not be changed.
+
+typedef enum {
+ DEFAULT = 0, // Default to logging level of the parent
+ DEBUG = 1,
+ INFO = 2,
+ WARN = 3,
+ ERROR = 4,
+ FATAL = 5,
+ NONE = 6 // Disable logging
+} Severity;
+
+} // namespace log
+} // namespace isc
+
+#endif // __LOGGER_LEVELS_H
diff --git a/src/lib/log/logger_support.cc b/src/lib/log/logger_support.cc
index c9ba858..1ac4481 100644
--- a/src/lib/log/logger_support.cc
+++ b/src/lib/log/logger_support.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,10 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE
-// $Id$
-
-
-
/// \brief Temporary Logger Support
///
/// Performs run-time initialization of the logger system. In particular, it
@@ -28,13 +24,17 @@
/// These functions will be replaced once the code has bneen written to obtain
/// the logging parameters from the configuration database.
+#include <algorithm>
+#include <string>
#include <vector>
+#include <boost/lexical_cast.hpp>
#include <log/logger.h>
#include <log/logger_support.h>
#include <log/messagedef.h>
#include <log/message_dictionary.h>
#include <log/message_exception.h>
+#include <log/message_initializer.h>
#include <log/message_reader.h>
#include <log/message_types.h>
#include <log/root_logger_name.h>
@@ -44,7 +44,8 @@ namespace log {
using namespace std;
-// Declare a logger for the logging subsystem
+// Declare a logger for the logging subsystem. This is a sub-logger of the
+// root logger and is used in all functions in this file.
Logger logger("log");
@@ -57,22 +58,24 @@ Logger logger("log");
/// \param file Name of the local message file
static void
readLocalMessageFile(const char* file) {
-
- MessageDictionary* dictionary = MessageDictionary::globalDictionary();
- MessageReader reader(dictionary);
+
+ MessageDictionary& dictionary = MessageDictionary::globalDictionary();
+ MessageReader reader(&dictionary);
try {
+ logger.info(MSG_RDLOCMES, file);
reader.readFile(file, MessageReader::REPLACE);
// File successfully read, list the duplicates
MessageReader::MessageIDCollection unknown = reader.getNotAdded();
for (MessageReader::MessageIDCollection::const_iterator
i = unknown.begin(); i != unknown.end(); ++i) {
- logger.warn(MSG_IDNOTFND, (*i).c_str());
+ string message_id = boost::lexical_cast<string>(*i);
+ logger.warn(MSG_IDNOTFND, message_id.c_str());
}
}
catch (MessageException& e) {
MessageID ident = e.id();
- vector<MessageID> args = e.arguments();
+ vector<string> args = e.arguments();
switch (args.size()) {
case 0:
logger.error(ident);
@@ -91,20 +94,33 @@ readLocalMessageFile(const char* file) {
/// Logger Run-Time Initialization
void
-runTimeInit(Logger::Severity severity, int dbglevel, const char* file) {
+initLogger(const string& root, isc::log::Severity severity, int dbglevel,
+ const char* file) {
- // Create the application root logger. This is the logger that has the
- // name of the application (and is one level down from the log4cxx root
- // logger). All other loggers created in this application will be its
- // child.
- //
- // The main purpose of the application root logger is to provide the root
- // name in output message for all other loggers.
- Logger logger(RootLoggerName::getName());
+ // Create the application root logger and set the default severity and
+ // debug level. This is the logger that has the name of the application.
+ // All other loggers created in this application will be its children.
+ setRootLoggerName(root);
+ Logger root_logger(isc::log::getRootLoggerName(), true);
// Set the severity associated with it. If no other logger has a severity,
// this will be the default.
- logger.setSeverity(severity, dbglevel);
+ root_logger.setSeverity(severity, dbglevel);
+
+ // Check if there were any duplicate message IDs in the default dictionary
+ // and if so, log them. Log using the logging facility root logger.
+ vector<string>& duplicates = MessageInitializer::getDuplicates();
+ if (!duplicates.empty()) {
+
+ // There are - sort and remove any duplicates.
+ sort(duplicates.begin(), duplicates.end());
+ vector<string>::iterator new_end =
+ unique(duplicates.begin(), duplicates.end());
+ for (vector<string>::iterator i = duplicates.begin(); i != new_end; ++i) {
+ logger.warn(MSG_DUPMSGID, i->c_str());
+ }
+
+ }
// Replace any messages with local ones (if given)
if (file) {
diff --git a/src/lib/log/logger_support.h b/src/lib/log/logger_support.h
index 85f838f..57d8383 100644
--- a/src/lib/log/logger_support.h
+++ b/src/lib/log/logger_support.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,11 +12,10 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __LOGGER_SUPPORT_H
#define __LOGGER_SUPPORT_H
+#include <string>
#include <log/logger.h>
namespace isc {
@@ -24,17 +23,21 @@ namespace log {
/// \brief Run-Time Initialization
///
-/// This code will be used until the logger is fully integrated into the BIND-10
-/// configuration database. It performs run-time initialization of th logger,
-/// in particular supplying run-time choices to it:
+/// Performs run-time initialization of the logger in particular supplying:
+///
+/// - Name of the root logger
+/// - The severity (and if applicable, debug level) for the root logger.
+/// - Name of a local message file, containing localisation of message text.
///
-/// * The severity (and if applicable, debug level) at which to log
-/// * Name of a local message file, containing localisation of message text.
+/// This function is likely to change over time as more debugging options are
+/// held in the configuration database.
///
+/// \param root Name of the root logger
/// \param severity Severity at which to log
/// \param dbglevel Debug severiy (ignored if "severity" is not "DEBUG")
/// \param file Name of the local message file.
-void runTimeInit(Logger::Severity severity, int dbglevel, const char* file);
+void initLogger(const std::string& root, isc::log::Severity severity,
+ int dbglevel, const char* file);
} // namespace log
} // namespace isc
diff --git a/src/lib/log/message_dictionary.cc b/src/lib/log/message_dictionary.cc
index 05c79f8..c091369 100644
--- a/src/lib/log/message_dictionary.cc
+++ b/src/lib/log/message_dictionary.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <cstddef>
#include <log/message_dictionary.h>
#include <log/message_types.h>
@@ -31,82 +29,81 @@ MessageDictionary::~MessageDictionary() {
// Add message and note if ID already exists
bool
-MessageDictionary::add(const MessageID& ident, const std::string& text) {
- map<MessageID, string>::iterator i = dictionary_.find(ident);
+MessageDictionary::add(const string& ident, const string& text) {
+ Dictionary::iterator i = dictionary_.find(ident);
bool not_found = (i == dictionary_.end());
if (not_found) {
// Message not already in the dictionary, so add it.
dictionary_[ident] = text;
}
-
+
return (not_found);
}
// Add message and note if ID does not already exist
bool
-MessageDictionary::replace(const MessageID& ident, const std::string& text) {
- map<MessageID, string>::iterator i = dictionary_.find(ident);
+MessageDictionary::replace(const string& ident, const string& text) {
+ Dictionary::iterator i = dictionary_.find(ident);
bool found = (i != dictionary_.end());
if (found) {
// Exists, so replace it.
dictionary_[ident] = text;
}
-
+
return (found);
}
// Load a set of messages
-vector<MessageID>
+vector<std::string>
MessageDictionary::load(const char* messages[]) {
- vector<MessageID> duplicates;
+ vector<std::string> duplicates;
int i = 0;
while (messages[i]) {
// ID present, so note it and point to text.
- MessageID ident(messages[i++]);
+ const MessageID ident(messages[i++]);
if (messages[i]) {
- // Text not null, note it and point to next ident.
+ // Text not null, note it and point to next ident.
string text(messages[i++]);
// Add ID and text to message dictionary, noting if the ID was
// already present.
bool added = add(ident, text);
if (!added) {
- duplicates.push_back(ident);
+ duplicates.push_back(boost::lexical_cast<string>(ident));
}
}
}
- return duplicates;
+ return (duplicates);
}
-// Return message text or blank string
+// Return message text or blank string. A reference is returned to a string
+// in the dictionary - this is fine, as the string is immediately used for
+// output.
-string
-MessageDictionary::getText(const MessageID& ident) const {
- map<MessageID, string>::const_iterator i = dictionary_.find(ident);
+const string&
+MessageDictionary::getText(const string& ident) const {
+ static const string empty("");
+ Dictionary::const_iterator i = dictionary_.find(ident);
if (i == dictionary_.end()) {
- return string("");
+ return (empty);
}
else {
- return i->second;
+ return (i->second);
}
}
// Return global dictionary
-MessageDictionary*
+MessageDictionary&
MessageDictionary::globalDictionary() {
- static MessageDictionary* global = NULL;
-
- if (global == NULL) {
- global = new MessageDictionary();
- }
- return global;
+ static MessageDictionary global;
+ return (global);
}
diff --git a/src/lib/log/message_dictionary.h b/src/lib/log/message_dictionary.h
index a3070cc..23f76d7 100644
--- a/src/lib/log/message_dictionary.h
+++ b/src/lib/log/message_dictionary.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __MESSAGE_DICTIONARY_H
#define __MESSAGE_DICTIONARY_H
@@ -22,6 +20,8 @@
#include <map>
#include <vector>
+#include <boost/lexical_cast.hpp>
+
#include <log/message_types.h>
namespace isc {
@@ -48,6 +48,9 @@ namespace log {
class MessageDictionary {
public:
+ typedef std::map<std::string, std::string> Dictionary;
+ typedef Dictionary::const_iterator const_iterator;
+
// Default constructor and assignment operator are OK for this class
/// \brief Virtual Destructor
@@ -63,7 +66,20 @@ public:
///
/// \return true if the message was added to the dictionary, false if the
/// message existed and it was not added.
- virtual bool add(const MessageID& ident, const std::string& text);
+ virtual bool add(const MessageID& ident, const std::string& text) {
+ return (add(boost::lexical_cast<std::string>(ident), text));
+ }
+
+ /// \brief Add Message
+ ///
+ /// Alternate signature.
+ ///
+ /// \param ident Identification of the message to add
+ /// \param text Message text
+ ///
+ /// \return true if the message was added to the dictionary, false if the
+ /// message existed and it was not added.
+ virtual bool add (const std::string& ident, const std::string& test);
/// \brief Replace Message
@@ -76,7 +92,21 @@ public:
///
/// \return true if the message was added to the dictionary, false if the
/// message did not exist and it was not added.
- virtual bool replace(const MessageID& ident, const std::string& text);
+ virtual bool replace(const MessageID& ident, const std::string& text) {
+ return (replace(boost::lexical_cast<std::string>(ident), text));
+ }
+
+
+ /// \brief Replace Message
+ ///
+ /// Alternate signature.
+ ///
+ /// \param ident Identification of the message to replace
+ /// \param text Message text
+ ///
+ /// \return true if the message was added to the dictionary, false if the
+ /// message did not exist and it was not added.
+ virtual bool replace(const std::string& ident, const std::string& text);
/// \brief Load Dictionary
@@ -94,7 +124,7 @@ public:
/// \return Vector of message IDs that were not loaded because an ID of the
/// same name already existing in the dictionary. This vector may be
/// empty.
- virtual std::vector<MessageID> load(const char* elements[]);
+ virtual std::vector<std::string> load(const char* elements[]);
/// \brief Get Message Text
@@ -106,30 +136,40 @@ public:
/// \return Text associated with message or empty string if the ID is not
/// recognised. (Note: this precludes an ID being associated with an empty
/// string.)
- virtual std::string getText(const MessageID& ident) const;
+ virtual const std::string& getText(const MessageID& ident) const {
+ return(getText(boost::lexical_cast<std::string>(ident)));
+ }
+
+
+ /// \brief Get Message Text
+ ///
+ /// Alternate signature.
+ ///
+ /// \param ident Message identification
+ ///
+ /// \return Text associated with message or empty string if the ID is not
+ /// recognised. (Note: this precludes an ID being associated with an empty
+ /// string.)
+ virtual const std::string& getText(const std::string& ident) const;
/// \brief Number of Items in Dictionary
///
/// \return Number of items in the dictionary
virtual size_t size() const {
- return dictionary_.size();
+ return (dictionary_.size());
}
- // Allow access to the internal map structure, but don't allow alteration.
- typedef std::map<MessageID, std::string>::const_iterator const_iterator;
-
-
/// \brief Return begin() iterator of internal map
const_iterator begin() const {
- return dictionary_.begin();
+ return (dictionary_.begin());
}
/// \brief Return end() iterator of internal map
const_iterator end() const {
- return dictionary_.end();
+ return (dictionary_.end());
}
@@ -138,10 +178,10 @@ public:
/// Returns a pointer to the singleton global dictionary.
///
/// \return Pointer to global dictionary.
- static MessageDictionary* globalDictionary();
+ static MessageDictionary& globalDictionary();
private:
- std::map<MessageID, std::string> dictionary_;
+ Dictionary dictionary_; ///< Holds the ID to text lookups
};
} // namespace log
diff --git a/src/lib/log/message_exception.cc b/src/lib/log/message_exception.cc
index 562c381..1a69ca5 100644
--- a/src/lib/log/message_exception.cc
+++ b/src/lib/log/message_exception.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
/// \brief Body of Virtual Destructor
#include <log/message_exception.h>
diff --git a/src/lib/log/message_exception.h b/src/lib/log/message_exception.h
index 537392d..30c6618 100644
--- a/src/lib/log/message_exception.h
+++ b/src/lib/log/message_exception.h
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __MESSAGE_EXCEPTION_H
#define __MESSAGE_EXCEPTION_H
@@ -76,7 +74,7 @@ public:
///
/// \return Exception Arguments
std::vector<std::string> arguments() const {
- return args_;
+ return (args_);
}
private:
diff --git a/src/lib/log/message_initializer.cc b/src/lib/log/message_initializer.cc
index 914ed17..0113497 100644
--- a/src/lib/log/message_initializer.cc
+++ b/src/lib/log/message_initializer.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <log/message_dictionary.h>
#include <log/message_initializer.h>
@@ -24,8 +22,22 @@ namespace log {
// associated text into it.
MessageInitializer::MessageInitializer(const char* values[]) {
- MessageDictionary* global = MessageDictionary::globalDictionary();
- global->load(values);
+ MessageDictionary& global = MessageDictionary::globalDictionary();
+ std::vector<std::string> repeats = global.load(values);
+
+ // Append the IDs in the list just loaded (the "repeats") to the global list
+ // of duplicate IDs.
+ if (!repeats.empty()) {
+ std::vector<std::string>& duplicates = getDuplicates();
+ duplicates.insert(duplicates.end(), repeats.begin(), repeats.end());
+ }
+}
+
+// Return reference to duplicate array
+
+std::vector<std::string>& MessageInitializer::getDuplicates() {
+ static std::vector<std::string> duplicates;
+ return (duplicates);
}
} // namespace log
diff --git a/src/lib/log/message_initializer.h b/src/lib/log/message_initializer.h
index a776a02..7516823 100644
--- a/src/lib/log/message_initializer.h
+++ b/src/lib/log/message_initializer.h
@@ -12,11 +12,11 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __MESSAGEINITIALIZER_H
#define __MESSAGEINITIALIZER_H
+#include <string>
+#include <vector>
#include <log/message_dictionary.h>
namespace isc {
@@ -52,9 +52,21 @@ public:
/// \brief Constructor
///
- /// The only method in the class, this adds the array of values to the
- /// global dictionary.
+ /// Adds the array of values to the global dictionary, and notes any
+ /// duplicates.
+ ///
+ /// \param values NULL-terminated array of alternating identifier strings
+ /// and associated message text.
MessageInitializer(const char* values[]);
+
+ /// \brief Return Duplicates
+ ///
+ /// When messages are added to the global dictionary, any duplicates are
+ /// recorded. They can later be output through the logging system.
+ ///
+ /// \return List of duplicate message IDs when the global dictionary was
+ /// loaded. Note that the duplicates list itself may contain duplicates.
+ static std::vector<std::string>& getDuplicates();
};
} // namespace log
diff --git a/src/lib/log/message_reader.cc b/src/lib/log/message_reader.cc
index 203b836..7ae7ae0 100644
--- a/src/lib/log/message_reader.cc
+++ b/src/lib/log/message_reader.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <errno.h>
#include <string.h>
@@ -47,7 +45,7 @@ MessageReader::readFile(const string& file, MessageReader::Mode mode) {
// Open the file
ifstream infile(file.c_str());
if (infile.fail()) {
- throw MessageException(MSG_OPENIN, file, strerror(errno));
+ throw MessageException(MSG_OPNMSGIN, file, strerror(errno));
}
// Loop round reading it.
@@ -60,7 +58,7 @@ MessageReader::readFile(const string& file, MessageReader::Mode mode) {
// Why did the loop terminate?
if (!infile.eof()) {
- throw MessageException(MSG_READERR, file, strerror(errno));
+ throw MessageException(MSG_MSGRDERR, file, strerror(errno));
}
infile.close();
}
@@ -93,32 +91,45 @@ MessageReader::processLine(const string& line, MessageReader::Mode mode) {
void
MessageReader::parseDirective(const std::string& text) {
- static string valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
- // Regardless of what happens, all prefixes will be uppercase (as will
- // all symbols).
- string line = text;
- isc::strutil::uppercase(line);
- vector<string> tokens = isc::strutil::tokens(line);
+ // Break into tokens
+ vector<string> tokens = isc::strutil::tokens(text);
- // Only $PREFIX is recognised so far, so we'll handle it here.
- if (tokens[0] != string("$PREFIX")) {
+ // Uppercase directive and branch on valid ones
+ isc::strutil::uppercase(tokens[0]);
+ if (tokens[0] == string("$PREFIX")) {
+ parsePrefix(tokens);
+ } else if (tokens[0] == string("$NAMESPACE")) {
+ parseNamespace(tokens);
+ } else {
throw MessageException(MSG_UNRECDIR, tokens[0]);
+ }
+}
- } else if (tokens.size() < 2) {
- throw MessageException(MSG_PRFNOARG);
+// Process $PREFIX
+
+void
+MessageReader::parsePrefix(const vector<string>& tokens) {
+
+ // Check argument count
+ static string valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
+ if (tokens.size() < 2) {
+ throw MessageException(MSG_PRFNOARG);
} else if (tokens.size() > 2) {
throw MessageException(MSG_PRFEXTRARG);
}
+ // As a style, we are going to have the symbols in uppercase
+ string prefix = tokens[1];
+ isc::strutil::uppercase(prefix);
+
// Token is potentially valid providing it only contains alphabetic
// and numeric characters (and underscores) and does not start with a
// digit.
-
- if ((tokens[1].find_first_not_of(valid) != string::npos) ||
- (std::isdigit(tokens[1][0]))) {
+ if ((prefix.find_first_not_of(valid) != string::npos) ||
+ (std::isdigit(prefix[0]))) {
// Invalid character in string or it starts with a digit.
throw MessageException(MSG_PRFINVARG, tokens[1]);
@@ -132,7 +143,45 @@ MessageReader::parseDirective(const std::string& text) {
// Prefix has not been set, so set it and return success.
- prefix_ = tokens[1];
+ prefix_ = prefix;
+}
+
+// Process $NAMESPACE. A lot of the processing is similar to that of $PREFIX,
+// except that only limited checks will be done on the namespace (to avoid a
+// lot of parsing and separating out of the namespace components.)
+
+void
+MessageReader::parseNamespace(const vector<string>& tokens) {
+
+ // Check argument count
+
+ static string valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_:"
+ "abcdefghijklmnopqrstuvwxyz";
+
+ if (tokens.size() < 2) {
+ throw MessageException(MSG_NSNOARG);
+
+ } else if (tokens.size() > 2) {
+ throw MessageException(MSG_NSEXTRARG);
+
+ }
+
+ // Token is potentially valid providing it only contains alphabetic
+ // and numeric characters (and underscores and colons).
+ if (tokens[1].find_first_not_of(valid) != string::npos) {
+
+ // Invalid character in string or it starts with a digit.
+ throw MessageException(MSG_NSINVARG, tokens[1]);
+ }
+
+ // All OK - unless the namespace has already been set.
+ if (ns_.size() != 0) {
+ throw MessageException(MSG_DUPLNS);
+ }
+
+ // Prefix has not been set, so set it and return success.
+
+ ns_ = tokens[1];
}
// Process message. By the time this method is called, the line has been
@@ -150,11 +199,11 @@ 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(MSG_ONETOKEN, text);
+ throw MessageException(MSG_NOMSGTXT, text);
}
// Extract the first token into the message ID
- MessageID ident = text.substr(0, first_delim);
+ string ident = text.substr(0, first_delim);
// Locate the start of the message text
size_t first_text = text.find_first_not_of(delimiters, first_delim);
@@ -163,7 +212,7 @@ 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(MSG_ONETOKEN, text);
+ throw MessageException(MSG_NOMSGTXT, text);
}
// 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 84ffce9..d07c7f2 100644
--- a/src/lib/log/message_reader.h
+++ b/src/lib/log/message_reader.h
@@ -12,12 +12,9 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __MESSAGE_READER_H
#define __MESSAGE_READER_H
-#include <iostream>
#include <map>
#include <string>
#include <vector>
@@ -50,7 +47,7 @@ public:
} Mode;
/// \brief Visible collection types
- typedef std::vector<MessageID> MessageIDCollection;
+ typedef std::vector<std::string> MessageIDCollection;
/// \brief Constructor
///
@@ -79,7 +76,7 @@ public:
///
/// \return Pointer to current dictionary object
MessageDictionary* getDictionary() const {
- return dictionary_;
+ return (dictionary_);
}
@@ -116,11 +113,27 @@ public:
virtual void processLine(const std::string& line, Mode mode = ADD);
+ /// \brief Get Namespace
+ ///
+ /// \return Argument to the $NAMESPACE directive (if present)
+ virtual std::string getNamespace() const {
+ return (ns_);
+ }
+
+
+ /// \brief Clear Namespace
+ ///
+ /// Clears the current namespace.
+ virtual void clearNamespace() {
+ ns_ = "";
+ }
+
+
/// \brief Get Prefix
///
/// \return Argument to the $PREFIX directive (if present)
virtual std::string getPrefix() const {
- return prefix_;
+ return (prefix_);
}
@@ -139,7 +152,7 @@ public:
///
/// \return Collection of messages not added
MessageIDCollection getNotAdded() const {
- return not_added_;
+ return (not_added_);
}
private:
@@ -163,10 +176,24 @@ private:
/// \param line Line of text that starts with "$",
void parseDirective(const std::string& line);
+
+ /// \brief Parse $PREFIX line
+ ///
+ /// \param tokens $PREFIX line split into tokens
+ void parsePrefix(const std::vector<std::string>& tokens);
+
+
+ /// \brief Parse $NAMESPACE line
+ ///
+ /// \param tokens $NAMESPACE line split into tokens
+ void parseNamespace(const std::vector<std::string>& tokens);
+
+
/// Attributes
MessageDictionary* dictionary_; ///< Dictionary to add messages to
MessageIDCollection not_added_; ///< List of IDs not added
- std::string prefix_; ///< Input of $PREFIX statement
+ std::string prefix_; ///< Argument of $PREFIX statement
+ std::string ns_; ///< Argument of $NAMESPACE statement
};
} // namespace log
diff --git a/src/lib/log/message_types.h b/src/lib/log/message_types.h
index b101401..9f625a9 100644
--- a/src/lib/log/message_types.h
+++ b/src/lib/log/message_types.h
@@ -12,17 +12,22 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __MESSAGE_TYPES_H
#define __MESSAGE_TYPES_H
-#include <string>
+#include <string.h>
namespace isc {
namespace log {
-typedef std::string MessageID;
+typedef const char* MessageID;
+
+/// \brief Compare MessageID for Equality
+///
+/// \param m1 First message ID
+/// \param m2 Second message ID
+/// \return true if they are equal, false if not
+bool equalMessageID(const MessageID& m1, const MessageID& m2);
} // namespace log
} // namespace isc
diff --git a/src/lib/log/messagedef.cc b/src/lib/log/messagedef.cc
index 7dfa4f6..f680a74 100644
--- a/src/lib/log/messagedef.cc
+++ b/src/lib/log/messagedef.cc
@@ -1,27 +1,57 @@
-// File created from messagedef.mes on Mon Jan 17 15:25:32 2011
+// File created from messagedef.mes on Mon Feb 14 11:07:45 2011
#include <cstddef>
+#include <log/message_types.h>
#include <log/message_initializer.h>
-using namespace isc::log;
+namespace isc {
+namespace log {
+
+extern const isc::log::MessageID MSG_DUPLNS = "DUPLNS";
+extern const isc::log::MessageID MSG_DUPLPRFX = "DUPLPRFX";
+extern const isc::log::MessageID MSG_DUPMSGID = "DUPMSGID";
+extern const isc::log::MessageID MSG_IDNOTFND = "IDNOTFND";
+extern const isc::log::MessageID MSG_MSGRDERR = "MSGRDERR";
+extern const isc::log::MessageID MSG_MSGWRTERR = "MSGWRTERR";
+extern const isc::log::MessageID MSG_NOMSGTXT = "NOMSGTXT";
+extern const isc::log::MessageID MSG_NSEXTRARG = "NSEXTRARG";
+extern const isc::log::MessageID MSG_NSINVARG = "NSINVARG";
+extern const isc::log::MessageID MSG_NSNOARG = "NSNOARG";
+extern const isc::log::MessageID MSG_OPNMSGIN = "OPNMSGIN";
+extern const isc::log::MessageID MSG_OPNMSGOUT = "OPNMSGOUT";
+extern const isc::log::MessageID MSG_PRFEXTRARG = "PRFEXTRARG";
+extern const isc::log::MessageID MSG_PRFINVARG = "PRFINVARG";
+extern const isc::log::MessageID MSG_PRFNOARG = "PRFNOARG";
+extern const isc::log::MessageID MSG_RDLOCMES = "RDLOCMES";
+extern const isc::log::MessageID MSG_UNRECDIR = "UNRECDIR";
+
+} // namespace log
+} // namespace isc
namespace {
const char* values[] = {
+ "DUPLNS", "duplicate $NAMESPACE directive found",
"DUPLPRFX", "duplicate $PREFIX directive found",
+ "DUPMSGID", "duplicate message ID (%s) in compiled code",
"IDNOTFND", "could not replace message for '%s': no such message identification",
- "ONETOKEN", "a line containing a message ID ('%s') and nothing else was found",
- "OPENIN", "unable to open message file %s for input: %s",
- "OPENOUT", "unable to open %s for output: %s",
+ "MSGRDERR", "error reading from message file %s: %s",
+ "MSGWRTERR", "error writing to %s: %s",
+ "NOMSGTXT", "a line containing a message ID ('%s') and nothing else was found",
+ "NSEXTRARG", "$NAMESPACE directive has too many arguments",
+ "NSINVARG", "$NAMESPACE directive has an invalid argument ('%s')",
+ "NSNOARG", "no arguments were given to the $NAMESPACE directive",
+ "OPNMSGIN", "unable to open message file %s for input: %s",
+ "OPNMSGOUT", "unable to open %s for output: %s",
"PRFEXTRARG", "$PREFIX directive has too many arguments",
"PRFINVARG", "$PREFIX directive has an invalid argument ('%s')",
"PRFNOARG", "no arguments were given to the $PREFIX directive",
- "READERR", "error reading from %s: %s",
+ "RDLOCMES", "reading local message file %s",
"UNRECDIR", "unrecognised directive '%s'",
- "WRITERR", "error writing to %s: %s",
NULL
};
+const isc::log::MessageInitializer initializer(values);
+
} // Anonymous namespace
-MessageInitializer messagedef_cc_Mon_Jan_17_15_25_32_2011(values);
diff --git a/src/lib/log/messagedef.h b/src/lib/log/messagedef.h
index ae0a99d..eb8f4ea 100644
--- a/src/lib/log/messagedef.h
+++ b/src/lib/log/messagedef.h
@@ -1,24 +1,32 @@
-// File created from messagedef.mes on Mon Jan 17 15:25:32 2011
+// File created from messagedef.mes on Mon Feb 14 11:07:45 2011
#ifndef __MESSAGEDEF_H
#define __MESSAGEDEF_H
#include <log/message_types.h>
-namespace {
+namespace isc {
+namespace log {
-isc::log::MessageID MSG_DUPLPRFX = "DUPLPRFX";
-isc::log::MessageID MSG_IDNOTFND = "IDNOTFND";
-isc::log::MessageID MSG_ONETOKEN = "ONETOKEN";
-isc::log::MessageID MSG_OPENIN = "OPENIN";
-isc::log::MessageID MSG_OPENOUT = "OPENOUT";
-isc::log::MessageID MSG_PRFEXTRARG = "PRFEXTRARG";
-isc::log::MessageID MSG_PRFINVARG = "PRFINVARG";
-isc::log::MessageID MSG_PRFNOARG = "PRFNOARG";
-isc::log::MessageID MSG_READERR = "READERR";
-isc::log::MessageID MSG_UNRECDIR = "UNRECDIR";
-isc::log::MessageID MSG_WRITERR = "WRITERR";
+extern const isc::log::MessageID MSG_DUPLNS;
+extern const isc::log::MessageID MSG_DUPLPRFX;
+extern const isc::log::MessageID MSG_DUPMSGID;
+extern const isc::log::MessageID MSG_IDNOTFND;
+extern const isc::log::MessageID MSG_MSGRDERR;
+extern const isc::log::MessageID MSG_MSGWRTERR;
+extern const isc::log::MessageID MSG_NOMSGTXT;
+extern const isc::log::MessageID MSG_NSEXTRARG;
+extern const isc::log::MessageID MSG_NSINVARG;
+extern const isc::log::MessageID MSG_NSNOARG;
+extern const isc::log::MessageID MSG_OPNMSGIN;
+extern const isc::log::MessageID MSG_OPNMSGOUT;
+extern const isc::log::MessageID MSG_PRFEXTRARG;
+extern const isc::log::MessageID MSG_PRFINVARG;
+extern const isc::log::MessageID MSG_PRFNOARG;
+extern const isc::log::MessageID MSG_RDLOCMES;
+extern const isc::log::MessageID MSG_UNRECDIR;
-} // Anonymous namespace
+} // namespace log
+} // namespace isc
#endif // __MESSAGEDEF_H
diff --git a/src/lib/log/messagedef.mes b/src/lib/log/messagedef.mes
index 1535fc6..55b3e7c 100644
--- a/src/lib/log/messagedef.mes
+++ b/src/lib/log/messagedef.mes
@@ -12,9 +12,8 @@
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
-# $Id$
-
$PREFIX MSG_
+$NAMESPACE isc::log
# \brief Message Utility Message File
#
@@ -24,6 +23,22 @@ $PREFIX MSG_
# chicken-and-egg situation where we need the files to build the message
# compiler, yet we need the compiler to build the files.
+DUPMSGID duplicate message ID (%s) in compiled code
++ Indicative of a programming error, when it started up, BIND10 detected that
++ the given message ID had been registered by one or more modules. (All message
++ IDs should be unique throughout BIND10.) This has no impact on the operation
++ of the server other that erroneous messages may be logged. (When BIND10 loads
++ the message IDs (and their associated text), if a duplicate ID is found it is
++ discarded. However, when the module that supplied the duplicate ID logs that
++ particular message, the text supplied by the module that added the original
++ ID will be output - something that may bear no relation to the condition being
++ logged.
+
+DUPLNS duplicate $NAMESPACE directive found
++ When reading a message file, more than one $NAMESPACE directive was found. In
++ this version of the code, such a condition is regarded as an error and the
++ read will be abandonded.
+
DUPLPRFX duplicate $PREFIX directive found
+ When reading a message file, more than one $PREFIX directive was found. In
+ this version of the code, such a condition is regarded as an error and the
@@ -40,17 +55,40 @@ IDNOTFND could not replace message for '%s': no such message identification
+ This message may appear a number of times in the file, once for every such
+ unknown mnessage identification.
-ONETOKEN a line containing a message ID ('%s') and nothing else was found
+MSGRDERR error reading from message file %s: %s
++ The specified error was encountered reading from the named message file.
+
+MSGWRTERR error writing to %s: %s
++ The specified error was encountered by the message compiler when writing to
++ the named output file.
+
+NSEXTRARG $NAMESPACE directive has too many arguments
++ The $NAMESPACE directive takes a single argument, a namespace in which all the
++ generated symbol names are placed. This error is generated when the
++ compiler finds a $NAMESPACE directive with more than one argument.
+
+NSINVARG $NAMESPACE directive has an invalid argument ('%s')
++ The $NAMESPACE argument should be a valid C++ namespace. The reader does a
++ cursory check on its validity, checking that the characters in the namspace
++ are correct. The error is generated when the reader finds an invalid
++ character. (Valid are alphanumeric characters, underscroes and colons.)
+
+NOMSGTXT a line containing a message ID ('%s') and nothing else was found
+ Message definitions comprise lines starting with a message identification (a
+ symbolic name for the message) and followed by the text of the message. This
+ error is generated when a line is found in the message file that contains just
-+ the message identification.
++ the message identification and no text.
-OPENIN unable to open message file %s for input: %s
+NSNOARG no arguments were given to the $NAMESPACE directive
++ The $NAMESPACE directive takes a single argument, a namespace in which all the
++ generated symbol names are placed. This error is generated when the
++ compiler finds a $NAMESPACE directive with no arguments.
+
+OPNMSGIN unable to open message file %s for input: %s
+ The program was not able to open the specified input message file for the
+ reason given.
-OPENOUT unable to open %s for output: %s
+OPNMSGOUT unable to open %s for output: %s
+ The program was not able to open the specified output file for the reason
+ given.
@@ -69,14 +107,13 @@ PRFINVARG $PREFIX directive has an invalid argument ('%s')
PRFNOARG no arguments were given to the $PREFIX directive
+ The $PREFIX directive takes a single argument, a prefix to be added to the
+ symbol names when a C++ .h file is created. This error is generated when the
-+ compiler finds a $PREFIX directive with noa rguments.
++ compiler finds a $PREFIX directive with no arguments.
-READERR error reading from %s: %s
-+ The specified error was encountered reading from the named input file.
+RDLOCMES reading local message file %s
++ This is an informational message output by BIND10 when it starts to read a
++ local message file. (A local message file may replace the text of one of more
++ messages; the ID of the message will not be changed though.)
UNRECDIR unrecognised directive '%s'
+ A line starting with a dollar symbol was found, but the first word on the line
+ (shown in the message) was not a recognised message compiler directive.
-
-WRITERR error writing to %s: %s
-+ The specified error was encountered writing to the named output file.
diff --git a/src/lib/log/root_logger_name.cc b/src/lib/log/root_logger_name.cc
index 9378857..58d9407 100644
--- a/src/lib/log/root_logger_name.cc
+++ b/src/lib/log/root_logger_name.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,15 +12,33 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <string>
#include <root_logger_name.h>
namespace isc {
namespace log {
-std::string RootLoggerName::name_("");
+namespace {
+
+// Obtain the root logger name in a way that is safe for statically-initialized
+// objects.
+std::string&
+getRootLoggerNameInternal() {
+ static std::string root_name;
+ return (root_name);
}
+
+} // Anonymous namespace
+
+void
+setRootLoggerName(const std::string& name) {
+ getRootLoggerNameInternal() = name;
}
+
+const std::string& getRootLoggerName() {
+ return (getRootLoggerNameInternal());
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/root_logger_name.h b/src/lib/log/root_logger_name.h
index 80691d1..9d50332 100644
--- a/src/lib/log/root_logger_name.h
+++ b/src/lib/log/root_logger_name.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __ROOT_LOGGER_NAME_H
#define __ROOT_LOGGER_NAME_H
@@ -21,44 +19,26 @@
/// \brief Define Name of Root Logger
///
-/// In the log4cxx system, the root logger is ".". The definition for the
-/// BIND-10 system is that the root logger of a program has the name of the
-/// program. This (trivial) class stores the name of the program in a
-/// location accessible to the logger classes.
+/// In BIND-10, the name root logger of a program is the name of the program
+/// itself (in contrast to packages such as log4cxx where the root logger name
+// is something like "."). These trivial functions allow the setting and
+// getting of that name by the logger classes.
namespace isc {
namespace log {
-class RootLoggerName {
-public:
-
- /// \brief Constructor
- ///
- /// Sets the root logger name. Although the name is static, setting the
- /// name in the constructor allows static initialization of the name by
- /// declaring an external instance of the class in the main execution unit.
- RootLoggerName(const std::string& name) {
- setName(name);
- }
-
- /// \brief Set Root Logger Name
- ///
- /// \param name Name of the root logger. This should be the program
- /// name.
- static void setName(const std::string& name) {
- name_ = name;
- }
+/// \brief Set Root Logger Name
+///
+/// This function should be called by the program's initialization code before
+/// any logging functions are called.
+///
+/// \param name Name of the root logger. This should be the program name.
+void setRootLoggerName(const std::string& name);
- /// \brief Get Root Logger Name
- ///
- /// \return Name of the root logger.
- static std::string getName() {
- return name_;
- }
-
-private:
- static std::string name_; ///< Name of the root logger
-};
+/// \brief Get Root Logger Name
+///
+/// \return Name of the root logger.
+const std::string& getRootLoggerName();
}
}
diff --git a/src/lib/log/strutil.cc b/src/lib/log/strutil.cc
index 4b96601..65fb0cd 100644
--- a/src/lib/log/strutil.cc
+++ b/src/lib/log/strutil.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,10 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <numeric>
-#include <iostream>
#include <string.h>
#include <strutil.h>
@@ -58,14 +55,14 @@ trim(const string& instring) {
}
}
- return retstring;
+ return (retstring);
}
// Tokenise string. As noted in the header, this is locally written to avoid
// another dependency on a Boost library.
vector<string>
-tokens(const std::string text, const std::string& delim) {
+tokens(const std::string& text, const std::string& delim) {
vector<string> result;
// Search for the first non-delimiter character
@@ -89,7 +86,7 @@ tokens(const std::string text, const std::string& delim) {
}
}
- return result;
+ return (result);
}
// Local function to pass to accumulate() for summing up string lengths.
@@ -131,7 +128,7 @@ format(const std::string& format, const std::vector<std::string>& args) {
}
}
- return result;
+ return (result);
}
} // namespace log
diff --git a/src/lib/log/strutil.h b/src/lib/log/strutil.h
index cb0b793..f44b0d0 100644
--- a/src/lib/log/strutil.h
+++ b/src/lib/log/strutil.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __STRUTIL_H
#define __STRUTIL_H
@@ -73,7 +71,7 @@ std::string trim(const std::string& instring);
/// \param delim Delimiter characters
///
/// \return Vector of tokens.
-std::vector<std::string> tokens(const std::string text,
+std::vector<std::string> tokens(const std::string& text,
const std::string& delim = std::string(" \t\n"));
@@ -88,7 +86,7 @@ std::vector<std::string> tokens(const std::string text,
///
/// \return Uppercase version of the argument
inline char toUpper(char chr) {
- return static_cast<char>(std::toupper(static_cast<int>(chr)));
+ return (static_cast<char>(std::toupper(static_cast<int>(chr))));
}
@@ -113,7 +111,7 @@ inline void uppercase(std::string& text) {
///
/// \return Lowercase version of the argument
inline char toLower(char chr) {
- return static_cast<char>(std::tolower(static_cast<int>(chr)));
+ return (static_cast<char>(std::tolower(static_cast<int>(chr))));
}
/// \brief Lowercase String
diff --git a/src/lib/log/tests/Makefile.am b/src/lib/log/tests/Makefile.am
index 01973c9..1845706 100644
--- a/src/lib/log/tests/Makefile.am
+++ b/src/lib/log/tests/Makefile.am
@@ -22,13 +22,13 @@ run_unittests_SOURCES += message_reader_unittest.cc
run_unittests_SOURCES += message_initializer_unittest.cc
run_unittests_SOURCES += message_initializer_unittest_2.cc
run_unittests_SOURCES += strutil_unittest.cc
-run_unittests_SOURCES += xdebuglevel_unittest.cc
run_unittests_SOURCES += run_unittests.cc
+
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
run_unittests_LDADD = $(GTEST_LDADD)
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
-run_unittests_LDADD += -llog4cxx
endif
TESTS += logger_support_test
diff --git a/src/lib/log/tests/filename_unittest.cc b/src/lib/log/tests/filename_unittest.cc
index c33be9f..f3032eb 100644
--- a/src/lib/log/tests/filename_unittest.cc
+++ b/src/lib/log/tests/filename_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
-
#include <string>
#include <gtest/gtest.h>
diff --git a/src/lib/log/tests/localdef.mes b/src/lib/log/tests/localdef.mes
deleted file mode 100644
index 98e197d..0000000
--- a/src/lib/log/tests/localdef.mes
+++ /dev/null
@@ -1,23 +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.
-
-# \brief Local Definitions
-#
-# Holds local definitions of some of the messages produced by the program
-# logger_support_test, and is used as input to check that run-time message
-# replacement works.
-
-NOTHERE this message is not in the global dictionary
-READERR replacement read error, parameters: '%s' and '%s'
-UNRECDIR replacement unrecognised directive message, parameter is '%s'
diff --git a/src/lib/log/tests/logger_impl_log4cxx_unittest.cc b/src/lib/log/tests/logger_impl_log4cxx_unittest.cc
new file mode 100644
index 0000000..cab2678
--- /dev/null
+++ b/src/lib/log/tests/logger_impl_log4cxx_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <iostream>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <log/root_logger_name.h>
+#include <log/logger.h>
+#include <log/logger_impl.h>
+#include <log/messagedef.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+/// \brief Log4cxx Implementation Tests
+///
+/// Some tests of methods that are not directly tested by the logger unit tests
+/// (when the logger is configured to use log4cxx)
+
+namespace isc {
+namespace log {
+
+/// \brief Test Logger
+///
+/// This logger is a subclass of the logger implementation class under test, but
+/// makes protected methods public (for testing)
+
+class TestLoggerImpl : public LoggerImpl {
+public:
+ /// \brief constructor
+ TestLoggerImpl(const string& name) : LoggerImpl(name, true)
+ {}
+
+
+ /// \brief Conversion Between log4cxx Number and BIND-10 Severity
+ Severity convertLevel(int value) {
+ return (LoggerImpl::convertLevel(value));
+ }
+};
+
+} // namespace log
+} // namespace isc
+
+
+class LoggerImplTest : public ::testing::Test {
+protected:
+ LoggerImplTest()
+ {
+ }
+};
+
+// Test the number to severity conversion function
+
+TEST_F(LoggerImplTest, ConvertLevel) {
+
+ // Create a logger
+ RootLoggerName::setName("test3");
+ TestLoggerImpl logger("alpha");
+
+ // Basic 1:1
+ EXPECT_EQ(isc::log::DEBUG, logger.convertLevel(log4cxx::Level::DEBUG_INT));
+ EXPECT_EQ(isc::log::INFO, logger.convertLevel(log4cxx::Level::INFO_INT));
+ EXPECT_EQ(isc::log::WARN, logger.convertLevel(log4cxx::Level::WARN_INT));
+ EXPECT_EQ(isc::log::WARN, logger.convertLevel(log4cxx::Level::WARN_INT));
+ EXPECT_EQ(isc::log::ERROR, logger.convertLevel(log4cxx::Level::ERROR_INT));
+ EXPECT_EQ(isc::log::FATAL, logger.convertLevel(log4cxx::Level::FATAL_INT));
+ EXPECT_EQ(isc::log::FATAL, logger.convertLevel(log4cxx::Level::FATAL_INT));
+ EXPECT_EQ(isc::log::NONE, logger.convertLevel(log4cxx::Level::OFF_INT));
+
+ // Now some debug levels
+ EXPECT_EQ(isc::log::DEBUG,
+ logger.convertLevel(log4cxx::Level::DEBUG_INT - 1));
+ EXPECT_EQ(isc::log::DEBUG,
+ logger.convertLevel(log4cxx::Level::DEBUG_INT - MAX_DEBUG_LEVEL));
+ EXPECT_EQ(isc::log::DEBUG,
+ logger.convertLevel(log4cxx::Level::DEBUG_INT - 2 * MAX_DEBUG_LEVEL));
+}
diff --git a/src/lib/log/tests/logger_support_test.cc b/src/lib/log/tests/logger_support_test.cc
index acca4f6..4d8863e 100644
--- a/src/lib/log/tests/logger_support_test.cc
+++ b/src/lib/log/tests/logger_support_test.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,12 +12,11 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: $
-
/// \brief Example Program
///
/// Simple example program showing how to use the logger.
+#include <stdlib.h>
#include <unistd.h>
#include <string.h>
@@ -32,12 +31,8 @@
using namespace isc::log;
-// Declare root logger and a logger to use an example.
-//RootLoggerName root_name("testing");
-
-RootLoggerName root("alpha");
+// Declare logger to use an example.
Logger logger_ex("example");
-Logger logger_dlm("dlm");
// The program is invoked:
//
@@ -47,31 +42,31 @@ Logger logger_dlm("dlm");
// "level" is the debug level, a number between 0 and 99
// "local_file" is the name of a local file.
//
-// The program sets the attributes on the root logger. Looking
-// at the output determines whether the program worked.e root logger. Looking
-// at the output determines whether the
+// The program sets the attributes on the root logger and logs a set of
+// messages. Looking at the output determines whether the program worked.
int main(int argc, char** argv) {
- Logger::Severity severity = Logger::INFO;
- int dbglevel = -1;
- const char* localfile = NULL;
- int option;
+ isc::log::Severity severity = isc::log::INFO; // Default logger severity
+ int dbglevel = -1; // Logger debug level
+ const char* localfile = NULL; // Local message file
+ int option; // For getopt() processing
+ Logger logger_dlm("dlm", true); // Another example logger
// Parse options
while ((option = getopt(argc, argv, "s:d:")) != -1) {
switch (option) {
case 's':
if (strcmp(optarg, "debug") == 0) {
- severity = Logger::DEBUG;
+ severity = isc::log::DEBUG;
} else if (strcmp(optarg, "info") == 0) {
- severity = Logger::INFO;
+ severity = isc::log::INFO;
} else if (strcmp(optarg, "warn") == 0) {
- severity = Logger::WARN;
+ severity = isc::log::WARN;
} else if (strcmp(optarg, "error") == 0) {
- severity = Logger::ERROR;
+ severity = isc::log::ERROR;
} else if (strcmp(optarg, "fatal") == 0) {
- severity = Logger::FATAL;
+ severity = isc::log::FATAL;
} else {
std::cout << "Unrecognised severity option: " <<
optarg << "\n";
@@ -94,16 +89,16 @@ int main(int argc, char** argv) {
}
// Update the logging parameters
- runTimeInit(severity, dbglevel, localfile);
+ initLogger("alpha", severity, dbglevel, localfile);
// Log a few messages
- logger_ex.fatal(MSG_WRITERR, "test1", "42");
+ logger_ex.fatal(MSG_MSGWRTERR, "test1", "42");
logger_ex.error(MSG_UNRECDIR, "false");
- logger_dlm.warn(MSG_READERR, "a.txt", "dummy test");
- logger_dlm.info(MSG_OPENIN, "example.msg", "dummy test");
+ logger_dlm.warn(MSG_MSGRDERR, "a.txt", "dummy test");
+ logger_dlm.info(MSG_OPNMSGIN, "example.msg", "dummy test");
logger_ex.debug(0, MSG_UNRECDIR, "[abc]");
logger_ex.debug(24, MSG_UNRECDIR, "[24]");
logger_ex.debug(25, MSG_UNRECDIR, "[25]");
logger_ex.debug(26, MSG_UNRECDIR, "[26]");
- return 0;
+ return (0);
}
diff --git a/src/lib/log/tests/logger_unittest.cc b/src/lib/log/tests/logger_unittest.cc
index e15ec42..4eff622 100644
--- a/src/lib/log/tests/logger_unittest.cc
+++ b/src/lib/log/tests/logger_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: $
-
#include <iostream>
#include <string>
@@ -41,19 +39,8 @@ public:
TestLogger(const string& name) : Logger(name, true)
{}
- /// \brief Logger Equality
- bool operator==(const TestLogger& other) {
- return Logger::operator==(other);
- }
-
- /// \brief Logger is Null
- bool isInitialized() const {
- return Logger::isInitialized();
- }
-
- /// \brief Conversion Between log4cxx Number and BIND-10 Severity
- Severity convertLevel(int value) {
- return Logger::convertLevel(value);
+ static void reset() {
+ Logger::reset();
}
};
@@ -66,6 +53,10 @@ protected:
LoggerTest()
{
}
+
+ ~LoggerTest() {
+ TestLogger::reset();
+ }
};
@@ -74,7 +65,7 @@ protected:
TEST_F(LoggerTest, Name) {
// Create a logger
- RootLoggerName::setName("test1");
+ setRootLoggerName("test1");
Logger logger("alpha");
// ... and check the name
@@ -88,7 +79,7 @@ TEST_F(LoggerTest, GetLogger) {
// Set the root logger name (not strictly needed, but this will be the
// case in the program(.
- RootLoggerName::setName("test2");
+ setRootLoggerName("test2");
const string name1 = "alpha";
const string name2 = "beta";
@@ -96,85 +87,44 @@ TEST_F(LoggerTest, GetLogger) {
// Instantiate two loggers that should be the same
TestLogger logger1(name1);
TestLogger logger2(name1);
-
- // And check they are null at this point.
- EXPECT_FALSE(logger1.isInitialized());
- EXPECT_FALSE(logger2.isInitialized());
-
- // Do some random operation
- EXPECT_TRUE(logger1.isFatalEnabled());
- EXPECT_TRUE(logger2.isFatalEnabled());
-
- // And check they initialized and equal
- EXPECT_TRUE(logger1.isInitialized());
- EXPECT_TRUE(logger2.isInitialized());
+ // And check they equal
EXPECT_TRUE(logger1 == logger2);
// Instantiate another logger with another name and check that it
// is different to the previously instantiated ones.
TestLogger logger3(name2);
- EXPECT_FALSE(logger3.isInitialized());
- EXPECT_TRUE(logger3.isFatalEnabled());
- EXPECT_TRUE(logger3.isInitialized());
EXPECT_FALSE(logger1 == logger3);
}
-// Test the number to severity conversion function
-
-TEST_F(LoggerTest, ConvertLevel) {
-
- // Create a logger
- RootLoggerName::setName("test3");
- TestLogger logger("alpha");
-
- // Basic 1:1
- EXPECT_EQ(Logger::DEBUG, logger.convertLevel(log4cxx::Level::DEBUG_INT));
- EXPECT_EQ(Logger::INFO, logger.convertLevel(log4cxx::Level::INFO_INT));
- EXPECT_EQ(Logger::WARN, logger.convertLevel(log4cxx::Level::WARN_INT));
- EXPECT_EQ(Logger::WARN, logger.convertLevel(log4cxx::Level::WARN_INT));
- EXPECT_EQ(Logger::ERROR, logger.convertLevel(log4cxx::Level::ERROR_INT));
- EXPECT_EQ(Logger::FATAL, logger.convertLevel(log4cxx::Level::FATAL_INT));
- EXPECT_EQ(Logger::FATAL, logger.convertLevel(log4cxx::Level::FATAL_INT));
- EXPECT_EQ(Logger::NONE, logger.convertLevel(log4cxx::Level::OFF_INT));
-
- // Now some debug levels
- EXPECT_EQ(Logger::DEBUG,
- logger.convertLevel(log4cxx::Level::DEBUG_INT - 1));
- EXPECT_EQ(Logger::DEBUG,
- logger.convertLevel(log4cxx::Level::DEBUG_INT - MAX_DEBUG_LEVEL));
- EXPECT_EQ(Logger::DEBUG,
- logger.convertLevel(log4cxx::Level::DEBUG_INT - 2 * MAX_DEBUG_LEVEL));
-}
-
// Check that the logger levels are get set properly.
TEST_F(LoggerTest, Severity) {
// Create a logger
- RootLoggerName::setName("test3");
+ setRootLoggerName("test3");
TestLogger logger("alpha");
// Now check the levels
- logger.setSeverity(Logger::NONE);
- EXPECT_EQ(Logger::NONE, logger.getSeverity());
+ logger.setSeverity(isc::log::NONE);
+ EXPECT_EQ(isc::log::NONE, logger.getSeverity());
- logger.setSeverity(Logger::FATAL);
- EXPECT_EQ(Logger::FATAL, logger.getSeverity());
+ logger.setSeverity(isc::log::FATAL);
+ EXPECT_EQ(isc::log::FATAL, logger.getSeverity());
- logger.setSeverity(Logger::ERROR);
- EXPECT_EQ(Logger::ERROR, logger.getSeverity());
+ logger.setSeverity(isc::log::ERROR);
+ EXPECT_EQ(isc::log::ERROR, logger.getSeverity());
- logger.setSeverity(Logger::WARN);
- EXPECT_EQ(Logger::WARN, logger.getSeverity());
+ logger.setSeverity(isc::log::WARN);
+ EXPECT_EQ(isc::log::WARN, logger.getSeverity());
- logger.setSeverity(Logger::INFO);
- EXPECT_EQ(Logger::INFO, logger.getSeverity());
+ logger.setSeverity(isc::log::INFO);
+ EXPECT_EQ(isc::log::INFO, logger.getSeverity());
- logger.setSeverity(Logger::DEBUG);
- EXPECT_EQ(Logger::DEBUG, logger.getSeverity());
+ logger.setSeverity(isc::log::DEBUG);
+ EXPECT_EQ(isc::log::DEBUG, logger.getSeverity());
- logger.setSeverity(Logger::DEFAULT);
- EXPECT_EQ(Logger::DEFAULT, logger.getSeverity());
+ logger.setSeverity(isc::log::DEFAULT);
+ EXPECT_EQ(isc::log::DEFAULT, logger.getSeverity());
}
// Check that the debug level is set correctly.
@@ -182,40 +132,40 @@ TEST_F(LoggerTest, Severity) {
TEST_F(LoggerTest, DebugLevels) {
// Create a logger
- RootLoggerName::setName("test4");
+ setRootLoggerName("test4");
TestLogger logger("alpha");
// Debug level should be 0 if not at debug severity
- logger.setSeverity(Logger::NONE, 20);
+ logger.setSeverity(isc::log::NONE, 20);
EXPECT_EQ(0, logger.getDebugLevel());
- logger.setSeverity(Logger::INFO, 42);
+ logger.setSeverity(isc::log::INFO, 42);
EXPECT_EQ(0, logger.getDebugLevel());
// Should be the value set if the severity is set to DEBUG though.
- logger.setSeverity(Logger::DEBUG, 32);
+ logger.setSeverity(isc::log::DEBUG, 32);
EXPECT_EQ(32, logger.getDebugLevel());
- logger.setSeverity(Logger::DEBUG, 97);
+ logger.setSeverity(isc::log::DEBUG, 97);
EXPECT_EQ(97, logger.getDebugLevel());
// Try the limits
- logger.setSeverity(Logger::DEBUG, -1);
+ logger.setSeverity(isc::log::DEBUG, -1);
EXPECT_EQ(0, logger.getDebugLevel());
- logger.setSeverity(Logger::DEBUG, 0);
+ logger.setSeverity(isc::log::DEBUG, 0);
EXPECT_EQ(0, logger.getDebugLevel());
- logger.setSeverity(Logger::DEBUG, 1);
+ logger.setSeverity(isc::log::DEBUG, 1);
EXPECT_EQ(1, logger.getDebugLevel());
- logger.setSeverity(Logger::DEBUG, 98);
+ logger.setSeverity(isc::log::DEBUG, 98);
EXPECT_EQ(98, logger.getDebugLevel());
- logger.setSeverity(Logger::DEBUG, 99);
+ logger.setSeverity(isc::log::DEBUG, 99);
EXPECT_EQ(99, logger.getDebugLevel());
- logger.setSeverity(Logger::DEBUG, 100);
+ logger.setSeverity(isc::log::DEBUG, 100);
EXPECT_EQ(99, logger.getDebugLevel());
}
@@ -228,28 +178,28 @@ TEST_F(LoggerTest, SeverityInheritance) {
// implementation (in this case log4cxx) will set a parent-child
// relationship if the loggers are named <parent> and <parent>.<child>.
- RootLoggerName::setName("test5");
+ setRootLoggerName("test5");
TestLogger parent("alpha");
TestLogger child("alpha.beta");
// By default, newly created loggers should have a level of DEFAULT
// (i.e. default to parent)
- EXPECT_EQ(Logger::DEFAULT, parent.getSeverity());
- EXPECT_EQ(Logger::DEFAULT, child.getSeverity());
+ EXPECT_EQ(isc::log::DEFAULT, parent.getSeverity());
+ EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
// Set the severity of the child to something other than the default -
// check it changes and that of the parent does not.
- child.setSeverity(Logger::INFO);
- EXPECT_EQ(Logger::DEFAULT, parent.getSeverity());
- EXPECT_EQ(Logger::INFO, child.getSeverity());
+ child.setSeverity(isc::log::INFO);
+ EXPECT_EQ(isc::log::DEFAULT, parent.getSeverity());
+ EXPECT_EQ(isc::log::INFO, child.getSeverity());
// Reset the child severity and set that of the parent
- child.setSeverity(Logger::DEFAULT);
- EXPECT_EQ(Logger::DEFAULT, parent.getSeverity());
- EXPECT_EQ(Logger::DEFAULT, child.getSeverity());
- parent.setSeverity(Logger::WARN);
- EXPECT_EQ(Logger::WARN, parent.getSeverity());
- EXPECT_EQ(Logger::DEFAULT, child.getSeverity());
+ child.setSeverity(isc::log::DEFAULT);
+ EXPECT_EQ(isc::log::DEFAULT, parent.getSeverity());
+ EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
+ parent.setSeverity(isc::log::WARN);
+ EXPECT_EQ(isc::log::WARN, parent.getSeverity());
+ EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
}
// Check that severity is inherited.
@@ -260,7 +210,7 @@ TEST_F(LoggerTest, EffectiveSeverityInheritance) {
// implementation (in this case log4cxx) will set a parent-child
// relationship if the loggers are named <parent> and <parent>.<child>.
- RootLoggerName::setName("test6");
+ setRootLoggerName("test6");
Logger parent("test6");
Logger child("test6.beta");
@@ -268,58 +218,58 @@ TEST_F(LoggerTest, EffectiveSeverityInheritance) {
// (i.e. default to parent) and the root should have a default severity
// of INFO. However, the latter is only enforced when created by the
// RootLogger class, so explicitly set it for the parent for now.
- parent.setSeverity(Logger::INFO);
- EXPECT_EQ(Logger::INFO, parent.getEffectiveSeverity());
+ parent.setSeverity(isc::log::INFO);
+ EXPECT_EQ(isc::log::INFO, parent.getEffectiveSeverity());
- EXPECT_EQ(Logger::DEFAULT, child.getSeverity());
- EXPECT_EQ(Logger::INFO, child.getEffectiveSeverity());
+ EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
+ EXPECT_EQ(isc::log::INFO, child.getEffectiveSeverity());
// Set the severity of the child to something other than the default -
// check it changes and that of the parent does not.
- child.setSeverity(Logger::FATAL);
- EXPECT_EQ(Logger::INFO, parent.getEffectiveSeverity());
- EXPECT_EQ(Logger::FATAL, child.getEffectiveSeverity());
+ child.setSeverity(isc::log::FATAL);
+ EXPECT_EQ(isc::log::INFO, parent.getEffectiveSeverity());
+ EXPECT_EQ(isc::log::FATAL, child.getEffectiveSeverity());
// Reset the child severity and check again.
- child.setSeverity(Logger::DEFAULT);
- EXPECT_EQ(Logger::INFO, parent.getEffectiveSeverity());
- EXPECT_EQ(Logger::INFO, child.getEffectiveSeverity());
+ child.setSeverity(isc::log::DEFAULT);
+ EXPECT_EQ(isc::log::INFO, parent.getEffectiveSeverity());
+ EXPECT_EQ(isc::log::INFO, child.getEffectiveSeverity());
// Change the parwnt's severity and check it is reflects in the child.
- parent.setSeverity(Logger::WARN);
- EXPECT_EQ(Logger::WARN, parent.getEffectiveSeverity());
- EXPECT_EQ(Logger::WARN, child.getEffectiveSeverity());
+ parent.setSeverity(isc::log::WARN);
+ EXPECT_EQ(isc::log::WARN, parent.getEffectiveSeverity());
+ EXPECT_EQ(isc::log::WARN, child.getEffectiveSeverity());
}
// Test the isXxxxEnabled methods.
TEST_F(LoggerTest, IsXxxEnabled) {
- RootLoggerName::setName("test7");
+ setRootLoggerName("test7");
Logger logger("test7");
- logger.setSeverity(Logger::INFO);
+ logger.setSeverity(isc::log::INFO);
EXPECT_FALSE(logger.isDebugEnabled());
EXPECT_TRUE(logger.isInfoEnabled());
EXPECT_TRUE(logger.isWarnEnabled());
EXPECT_TRUE(logger.isErrorEnabled());
EXPECT_TRUE(logger.isFatalEnabled());
- logger.setSeverity(Logger::WARN);
+ logger.setSeverity(isc::log::WARN);
EXPECT_FALSE(logger.isDebugEnabled());
EXPECT_FALSE(logger.isInfoEnabled());
EXPECT_TRUE(logger.isWarnEnabled());
EXPECT_TRUE(logger.isErrorEnabled());
EXPECT_TRUE(logger.isFatalEnabled());
- logger.setSeverity(Logger::ERROR);
+ logger.setSeverity(isc::log::ERROR);
EXPECT_FALSE(logger.isDebugEnabled());
EXPECT_FALSE(logger.isInfoEnabled());
EXPECT_FALSE(logger.isWarnEnabled());
EXPECT_TRUE(logger.isErrorEnabled());
EXPECT_TRUE(logger.isFatalEnabled());
- logger.setSeverity(Logger::FATAL);
+ logger.setSeverity(isc::log::FATAL);
EXPECT_FALSE(logger.isDebugEnabled());
EXPECT_FALSE(logger.isInfoEnabled());
EXPECT_FALSE(logger.isWarnEnabled());
@@ -328,14 +278,14 @@ TEST_F(LoggerTest, IsXxxEnabled) {
// Check various debug levels
- logger.setSeverity(Logger::DEBUG);
+ logger.setSeverity(isc::log::DEBUG);
EXPECT_TRUE(logger.isDebugEnabled());
EXPECT_TRUE(logger.isInfoEnabled());
EXPECT_TRUE(logger.isWarnEnabled());
EXPECT_TRUE(logger.isErrorEnabled());
EXPECT_TRUE(logger.isFatalEnabled());
- logger.setSeverity(Logger::DEBUG, 45);
+ logger.setSeverity(isc::log::DEBUG, 45);
EXPECT_TRUE(logger.isDebugEnabled());
EXPECT_TRUE(logger.isInfoEnabled());
EXPECT_TRUE(logger.isWarnEnabled());
@@ -346,14 +296,14 @@ TEST_F(LoggerTest, IsXxxEnabled) {
// the severity of the parent logger.
Logger child("test7.child");
- logger.setSeverity(Logger::FATAL);
+ logger.setSeverity(isc::log::FATAL);
EXPECT_FALSE(child.isDebugEnabled());
EXPECT_FALSE(child.isInfoEnabled());
EXPECT_FALSE(child.isWarnEnabled());
EXPECT_FALSE(child.isErrorEnabled());
EXPECT_TRUE(child.isFatalEnabled());
- logger.setSeverity(Logger::INFO);
+ logger.setSeverity(isc::log::INFO);
EXPECT_FALSE(child.isDebugEnabled());
EXPECT_TRUE(child.isInfoEnabled());
EXPECT_TRUE(child.isWarnEnabled());
@@ -366,29 +316,29 @@ TEST_F(LoggerTest, IsXxxEnabled) {
TEST_F(LoggerTest, IsDebugEnabledLevel) {
- RootLoggerName::setName("test8");
+ setRootLoggerName("test8");
Logger logger("test8");
int MID_LEVEL = (MIN_DEBUG_LEVEL + MAX_DEBUG_LEVEL) / 2;
- logger.setSeverity(Logger::DEBUG);
+ logger.setSeverity(isc::log::DEBUG);
EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
EXPECT_FALSE(logger.isDebugEnabled(MID_LEVEL));
EXPECT_FALSE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
- logger.setSeverity(Logger::DEBUG, MIN_DEBUG_LEVEL);
+ logger.setSeverity(isc::log::DEBUG, MIN_DEBUG_LEVEL);
EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
EXPECT_FALSE(logger.isDebugEnabled(MID_LEVEL));
EXPECT_FALSE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
- logger.setSeverity(Logger::DEBUG, MID_LEVEL);
+ logger.setSeverity(isc::log::DEBUG, MID_LEVEL);
EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL - 1));
EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL));
EXPECT_FALSE(logger.isDebugEnabled(MID_LEVEL + 1));
EXPECT_FALSE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
- logger.setSeverity(Logger::DEBUG, MAX_DEBUG_LEVEL);
+ logger.setSeverity(isc::log::DEBUG, MAX_DEBUG_LEVEL);
EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL));
EXPECT_TRUE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
diff --git a/src/lib/log/tests/message_dictionary_unittest.cc b/src/lib/log/tests/message_dictionary_unittest.cc
index 78aa851..a92585c 100644
--- a/src/lib/log/tests/message_dictionary_unittest.cc
+++ b/src/lib/log/tests/message_dictionary_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,21 +12,36 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
-
#include <cstddef>
#include <string>
#include <gtest/gtest.h>
#include <log/message_dictionary.h>
+#include <log/message_initializer.h>
#include <log/message_types.h>
using namespace isc;
using namespace isc::log;
using namespace std;
+// set up another message initializer. This will add a symbol found in the
+// logging library and a symbol not found in the logging library. When the
+// global dictionary is loaded, the former should be marked as a duplicate
+// and the latter should be present.
+
+static const char* values[] = {
+ "DUPLNS", "duplicate $NAMESPACE directive found",
+ "NEWSYM", "new symbol added",
+ NULL
+};
+
+MessageInitializer init(values);
+
+
+
+
class MessageDictionaryTest : public ::testing::Test {
protected:
- MessageDictionaryTest() :
+ MessageDictionaryTest() :
alpha_id("ALPHA"), alpha_text("This is alpha"),
beta_id("BETA"), beta_text("This is beta"),
gamma_id("GAMMA"), gamma_text("This is gamma")
@@ -42,17 +57,6 @@ protected:
};
-
-// Check that the global dictionary is a singleton.
-
-TEST_F(MessageDictionaryTest, GlobalTest) {
- MessageDictionary* global = MessageDictionary::globalDictionary();
- EXPECT_FALSE(NULL == global);
-
- MessageDictionary* global2 = MessageDictionary::globalDictionary();
- EXPECT_EQ(global2, global);
-}
-
// Check that adding messages works
TEST_F(MessageDictionaryTest, Add) {
@@ -122,7 +126,7 @@ TEST_F(MessageDictionaryTest, LoadTest) {
EXPECT_EQ(0, dictionary1.size());
// Load a dictionary1.
- vector<MessageID> duplicates = dictionary1.load(data1);
+ vector<string> duplicates = dictionary1.load(data1);
EXPECT_EQ(3, dictionary1.size());
EXPECT_EQ(string(data1[1]), dictionary1.getText(data1[0]));
EXPECT_EQ(string(data1[3]), dictionary1.getText(data1[2]));
@@ -157,7 +161,7 @@ TEST_F(MessageDictionaryTest, Lookups) {
};
MessageDictionary dictionary;
- vector<MessageID> duplicates = dictionary.load(data);
+ vector<string> duplicates = dictionary.load(data);
EXPECT_EQ(3, dictionary.size());
EXPECT_EQ(0, duplicates.size());
@@ -171,3 +175,23 @@ TEST_F(MessageDictionaryTest, Lookups) {
EXPECT_EQ(string(""), dictionary.getText(""));
EXPECT_EQ(string(""), dictionary.getText("\n\n\n"));
}
+
+// Check that the global dictionary is a singleton.
+
+TEST_F(MessageDictionaryTest, GlobalTest) {
+ MessageDictionary& global = MessageDictionary::globalDictionary();
+ MessageDictionary& global2 = MessageDictionary::globalDictionary();
+ EXPECT_TRUE(&global2 == &global);
+}
+
+// Check that the global dictionary has detected the duplicate and the
+// new symbol.
+
+TEST_F(MessageDictionaryTest, GlobalLoadTest) {
+ vector<string>& duplicates = MessageInitializer::getDuplicates();
+ ASSERT_EQ(1, duplicates.size());
+ EXPECT_EQ(string("DUPLNS"), duplicates[0]);
+
+ string text = MessageDictionary::globalDictionary().getText("NEWSYM");
+ EXPECT_EQ(string("new symbol added"), text);
+}
diff --git a/src/lib/log/tests/message_initializer_unittest.cc b/src/lib/log/tests/message_initializer_unittest.cc
index 6a1019f..0cd1879 100644
--- a/src/lib/log/tests/message_initializer_unittest.cc
+++ b/src/lib/log/tests/message_initializer_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
-
#include <cstddef>
#include <string>
#include <gtest/gtest.h>
@@ -61,12 +59,12 @@ protected:
// messages.
TEST_F(MessageInitializerTest, MessageTest) {
- MessageDictionary* global = MessageDictionary::globalDictionary();
+ 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"));
+ 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
index c005033..94abb08 100644
--- a/src/lib/log/tests/message_initializer_unittest_2.cc
+++ b/src/lib/log/tests/message_initializer_unittest_2.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
-
// 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
diff --git a/src/lib/log/tests/message_reader_unittest.cc b/src/lib/log/tests/message_reader_unittest.cc
index 2891805..36288f2 100644
--- a/src/lib/log/tests/message_reader_unittest.cc
+++ b/src/lib/log/tests/message_reader_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
-
#include <algorithm>
#include <string>
#include <gtest/gtest.h>
@@ -76,7 +74,7 @@ TEST_F(MessageReaderTest, BlanksAndComments) {
// ... and (b) nothing gets added to either the map or the not-added section.
EXPECT_EQ(0, dictionary_->size());
- vector<MessageID> not_added = reader_.getNotAdded();
+ vector<string> not_added = reader_.getNotAdded();
EXPECT_EQ(0, not_added.size());
}
@@ -85,14 +83,15 @@ TEST_F(MessageReaderTest, BlanksAndComments) {
void
processLineException(MessageReader& reader, const char* what,
- MessageID& expected) {
+ const MessageID& expected) {
try {
reader.processLine(what);
FAIL() << "MessageReader::processLine() should throw an exception " <<
" with message ID " << expected << " for '" << what << "'\n";
} catch (MessageException& e) {
- EXPECT_EQ(expected, e.id());
+ EXPECT_EQ(boost::lexical_cast<string>(expected),
+ boost::lexical_cast<string>(e.id()));
} catch (...) {
FAIL() << "Unknown exception thrown by MessageReader::processLine()\n";
}
@@ -102,13 +101,13 @@ processLineException(MessageReader& reader, const char* what,
TEST_F(MessageReaderTest, Prefix) {
- // Check that no prefix is present
+ // Check that no $PREFIX is present
EXPECT_EQ(string(""), reader_.getPrefix());
- // Check that a prefix directive with no argument generates an error.
+ // Check that a $PREFIX directive with no argument generates an error.
processLineException(reader_, "$PREFIX", MSG_PRFNOARG);
- // Check a prefix with multiple arguments is invalid
+ // Check a $PREFIX with multiple arguments is invalid
processLineException(reader_, "$prefix A B", MSG_PRFEXTRARG);
// Prefixes should be alphanumeric (with underscores) and not start
@@ -132,6 +131,43 @@ TEST_F(MessageReaderTest, Prefix) {
EXPECT_EQ(string(""), reader_.getPrefix());
}
+// Check that it can parse a namespace
+
+TEST_F(MessageReaderTest, Namespace) {
+
+ // Check that no $NAMESPACE is present
+ EXPECT_EQ(string(""), reader_.getNamespace());
+
+ // Check that a $NAMESPACE directive with no argument generates an error.
+ processLineException(reader_, "$NAMESPACE", MSG_NSNOARG);
+
+ // Check a $NAMESPACE with multiple arguments is invalid
+ processLineException(reader_, "$namespace A B", MSG_NSEXTRARG);
+
+ // Namespaces should be alphanumeric (with underscores and colons)
+ processLineException(reader_, "$namespace ab[cd", MSG_NSINVARG);
+
+ // A valid $NAMESPACE should be accepted
+ EXPECT_NO_THROW(reader_.processLine("$NAMESPACE isc"));
+ EXPECT_EQ(string("isc"), reader_.getNamespace());
+
+ // (Check that we can clear the namespace)
+ reader_.clearNamespace();
+ EXPECT_EQ(string(""), reader_.getNamespace());
+
+ // Check that a valid namespace can include colons
+ EXPECT_NO_THROW(reader_.processLine("$NAMESPACE isc::log"));
+ EXPECT_EQ(string("isc::log"), reader_.getNamespace());
+
+ // Check that the indication of the anonymous namespace will be recognised.
+ reader_.clearNamespace();
+ EXPECT_NO_THROW(reader_.processLine("$NAMESPACE ::"));
+ EXPECT_EQ(string("::"), reader_.getNamespace());
+
+ // ... and that another $NAMESPACE is rejected
+ processLineException(reader_, "$NAMESPACE ABC", MSG_DUPLNS);
+}
+
// Check that it can parse a line
TEST_F(MessageReaderTest, ValidMessageAddDefault) {
@@ -148,7 +184,7 @@ TEST_F(MessageReaderTest, ValidMessageAddDefault) {
EXPECT_EQ(2, dictionary_->size());
// ... and ensure no messages were not added
- vector<MessageID> not_added = reader_.getNotAdded();
+ vector<string> not_added = reader_.getNotAdded();
EXPECT_EQ(0, not_added.size());
}
@@ -168,7 +204,7 @@ TEST_F(MessageReaderTest, ValidMessageAdd) {
EXPECT_EQ(2, dictionary_->size());
// ... and ensure no messages were not added
- vector<MessageID> not_added = reader_.getNotAdded();
+ vector<string> not_added = reader_.getNotAdded();
EXPECT_EQ(0, not_added.size());
}
@@ -191,7 +227,7 @@ TEST_F(MessageReaderTest, ValidMessageReplace) {
EXPECT_EQ(2, dictionary_->size());
// ... and ensure no messages were not added
- vector<MessageID> not_added = reader_.getNotAdded();
+ vector<string> not_added = reader_.getNotAdded();
EXPECT_EQ(0, not_added.size());
}
@@ -219,7 +255,7 @@ TEST_F(MessageReaderTest, Overflows) {
EXPECT_EQ(2, dictionary_->size());
// ... and ensure no overflows
- vector<MessageID> not_added = reader_.getNotAdded();
+ vector<string> not_added = reader_.getNotAdded();
ASSERT_EQ(2, not_added.size());
sort(not_added.begin(), not_added.end());
diff --git a/src/lib/log/tests/root_logger_name_unittest.cc b/src/lib/log/tests/root_logger_name_unittest.cc
index 6994dc6..8665794 100644
--- a/src/lib/log/tests/root_logger_name_unittest.cc
+++ b/src/lib/log/tests/root_logger_name_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
-
#include <string>
#include <gtest/gtest.h>
@@ -37,8 +35,8 @@ TEST_F(RootLoggerNameTest, SetGet) {
const std::string name2 = "test2";
// Check that Set/Get works
- RootLoggerName::setName(name1);
- EXPECT_EQ(name1, RootLoggerName::getName());
+ setRootLoggerName(name1);
+ EXPECT_EQ(name1, getRootLoggerName());
// We could not test that the root logger name is initialised
// correctly (as there is one instance of it and we don't know
@@ -47,6 +45,6 @@ TEST_F(RootLoggerNameTest, SetGet) {
//
// (There was always the outside chance that the root logger name
// was initialised with name1 and that setName() has no effect.)
- RootLoggerName::setName(name2);
- EXPECT_EQ(name2, RootLoggerName::getName());
+ setRootLoggerName(name2);
+ EXPECT_EQ(name2, getRootLoggerName());
}
diff --git a/src/lib/log/tests/run_time_init_test.sh.in b/src/lib/log/tests/run_time_init_test.sh.in
index be52ded..e2bdf6f 100755
--- a/src/lib/log/tests/run_time_init_test.sh.in
+++ b/src/lib/log/tests/run_time_init_test.sh.in
@@ -14,7 +14,7 @@
# PERFORMANCE OF THIS SOFTWARE.
failcount=0
-localmes=@abs_srcdir@/localdef.mes
+localmes=@abs_builddir@/localdef_mes_$$
tempfile=@abs_builddir@/run_time_init_test_tempfile_$$
passfail() {
@@ -25,20 +25,28 @@ passfail() {
fi
failcount=`expr $failcount + $1`
}
-
+
+# Create the local message file for testing
+
+cat > $localmes << .
+NOTHERE this message is not in the global dictionary
+MSGRDERR replacement read error, parameters: '%s' and '%s'
+UNRECDIR replacement unrecognised directive message, parameter is '%s'
+.
+
echo -n "1. runInitTest default parameters: "
cat > $tempfile << .
-FATAL [alpha.example] WRITERR, error writing to test1: 42
+FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
ERROR [alpha.example] UNRECDIR, unrecognised directive 'false'
-WARN [alpha.dlm] READERR, error reading from a.txt: dummy test
-INFO [alpha.dlm] OPENIN, unable to open message file example.msg for input: dummy test
+WARN [alpha.dlm] MSGRDERR, error reading from message file a.txt: dummy test
+INFO [alpha.dlm] OPNMSGIN, unable to open message file example.msg for input: dummy test
.
./logger_support_test | cut -d' ' -f3- | diff $tempfile -
passfail $?
echo -n "2. Severity filter: "
cat > $tempfile << .
-FATAL [alpha.example] WRITERR, error writing to test1: 42
+FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
ERROR [alpha.example] UNRECDIR, unrecognised directive 'false'
.
./logger_support_test -s error | cut -d' ' -f3- | diff $tempfile -
@@ -46,10 +54,10 @@ passfail $?
echo -n "3. Debug level: "
cat > $tempfile << .
-FATAL [alpha.example] WRITERR, error writing to test1: 42
+FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
ERROR [alpha.example] UNRECDIR, unrecognised directive 'false'
-WARN [alpha.dlm] READERR, error reading from a.txt: dummy test
-INFO [alpha.dlm] OPENIN, unable to open message file example.msg for input: dummy test
+WARN [alpha.dlm] MSGRDERR, error reading from message file a.txt: dummy test
+INFO [alpha.dlm] OPNMSGIN, unable to open message file example.msg for input: dummy test
DEBUG [alpha.example] UNRECDIR, unrecognised directive '[abc]'
DEBUG [alpha.example] UNRECDIR, unrecognised directive '[24]'
DEBUG [alpha.example] UNRECDIR, unrecognised directive '[25]'
@@ -60,14 +68,14 @@ passfail $?
echo -n "4. Local message replacement: "
cat > $tempfile << .
WARN [alpha.log] IDNOTFND, could not replace message for 'NOTHERE': no such message identification
-FATAL [alpha.example] WRITERR, error writing to test1: 42
+FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
ERROR [alpha.example] UNRECDIR, replacement unrecognised directive message, parameter is 'false'
-WARN [alpha.dlm] READERR, replacement read error, parameters: 'a.txt' and 'dummy test'
-INFO [alpha.dlm] OPENIN, unable to open message file example.msg for input: dummy test
+WARN [alpha.dlm] MSGRDERR, replacement read error, parameters: 'a.txt' and 'dummy test'
.
-./logger_support_test $localmes | cut -d' ' -f3- | diff $tempfile -
+./logger_support_test -s warn $localmes | cut -d' ' -f3- | diff $tempfile -
passfail $?
+rm -f $localmes
rm -f $tempfile
if [ $failcount -eq 0 ]; then
diff --git a/src/lib/log/tests/run_unittests.cc b/src/lib/log/tests/run_unittests.cc
index b91ce24..bd3c4c9 100644
--- a/src/lib/log/tests/run_unittests.cc
+++ b/src/lib/log/tests/run_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: run_unittests.cc 3020 2010-09-26 03:47:26Z jinmei $
-
#include <gtest/gtest.h>
int
diff --git a/src/lib/log/tests/strutil_unittest.cc b/src/lib/log/tests/strutil_unittest.cc
index 6e657f3..b3fceef 100644
--- a/src/lib/log/tests/strutil_unittest.cc
+++ b/src/lib/log/tests/strutil_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
-
#include <string>
#include <gtest/gtest.h>
diff --git a/src/lib/log/tests/xdebuglevel_unittest.cc b/src/lib/log/tests/xdebuglevel_unittest.cc
index 2cb0952..ca80e5a 100644
--- a/src/lib/log/tests/xdebuglevel_unittest.cc
+++ b/src/lib/log/tests/xdebuglevel_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id: $
-
#include <iostream>
#include <string>
@@ -21,7 +19,7 @@
#include <log4cxx/level.h>
#include <log/xdebuglevel.h>
-#include <log/dbglevels.h>
+#include <log/debug_levels.h>
/// \brief XDebugLevel (Debug Extension to Level Class)
///
diff --git a/src/lib/log/xdebuglevel.cc b/src/lib/log/xdebuglevel.cc
index 7dddcff..c17a515 100644
--- a/src/lib/log/xdebuglevel.cc
+++ b/src/lib/log/xdebuglevel.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#include <cassert>
#include <algorithm>
#include <syslog.h>
@@ -21,7 +19,7 @@
#include <boost/lexical_cast.hpp>
#include <xdebuglevel.h>
-#include <dbglevels.h>
+#include <debug_levels.h>
#include <log4cxx/helpers/stringhelper.h>
using namespace log4cxx;
@@ -71,7 +69,7 @@ XDebugLevel::getExtendedDebug(int level) {
int actual = std::max(MIN_DEBUG_LEVEL, std::min(MAX_DEBUG_LEVEL, level));
// ... and return a pointer to the appropriate logging level object
- return dbglevels_[actual - MIN_DEBUG_LEVEL];
+ return (dbglevels_[actual - MIN_DEBUG_LEVEL]);
}
// Convert an integer (an absolute logging level number, not a debug level) to a
@@ -80,7 +78,7 @@ XDebugLevel::getExtendedDebug(int level) {
LevelPtr
XDebugLevel::toLevel(int val) {
- return toLevel(val, getExtendedDebug(MIN_DEBUG_LEVEL));
+ return (toLevel(val, getExtendedDebug(MIN_DEBUG_LEVEL)));
}
LevelPtr
@@ -89,10 +87,10 @@ XDebugLevel::toLevel(int val, const LevelPtr& defaultLevel) {
// Note the reversal of the notion of MIN and MAX - see the header file for
// details.
if ((val >= XDEBUG_MAX_LEVEL_INT) && (val <= XDEBUG_MIN_LEVEL_INT)) {
- return getExtendedDebug(XDEBUG_MIN_LEVEL_INT - val);
+ return (getExtendedDebug(XDEBUG_MIN_LEVEL_INT - val));
}
else {
- return defaultLevel;
+ return (defaultLevel);
}
}
@@ -100,7 +98,7 @@ XDebugLevel::toLevel(int val, const LevelPtr& defaultLevel) {
LevelPtr
XDebugLevel::toLevelLS(const LogString& sArg) {
- return toLevelLS(sArg, getExtendedDebug(0));
+ return (toLevelLS(sArg, getExtendedDebug(0)));
}
LevelPtr
@@ -111,7 +109,7 @@ XDebugLevel::toLevelLS(const LogString& sArg, const LevelPtr& defaultLevel) {
if (length < 5) {
// String can't possibly start DEBUG so we don't know what it is.
- return defaultLevel;
+ return (defaultLevel);
}
else {
if (strncasecmp(name.c_str(), "DEBUG", 5) == 0) {
@@ -121,7 +119,7 @@ XDebugLevel::toLevelLS(const LogString& sArg, const LevelPtr& defaultLevel) {
if (length == 5) {
// It is plain "DEBUG". Take this as level 0.
- return getExtendedDebug(0);
+ return (getExtendedDebug(0));
}
else {
@@ -132,17 +130,17 @@ XDebugLevel::toLevelLS(const LogString& sArg, const LevelPtr& defaultLevel) {
// if DEBUG99 has been specified.
try {
int level = boost::lexical_cast<int>(name.substr(5));
- return getExtendedDebug(level);
+ return (getExtendedDebug(level));
}
- catch (boost::bad_lexical_cast&) {
- return defaultLevel;
+ catch ((boost::bad_lexical_cast&) ){
+ return (defaultLevel);
}
}
}
else {
// Unknown string - return default.
- return defaultLevel;
+ return (defaultLevel);
}
}
}
diff --git a/src/lib/log/xdebuglevel.h b/src/lib/log/xdebuglevel.h
index ca0aac6..e580b77 100644
--- a/src/lib/log/xdebuglevel.h
+++ b/src/lib/log/xdebuglevel.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,15 +12,13 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// $Id$
-
#ifndef __XDEBUGLEVEL_H
#define __XDEBUGLEVEL_H
#include <syslog.h>
#include <log4cxx/level.h>
-#include <dbglevels.h>
+#include <debug_levels.h>
namespace log4cxx {
diff --git a/src/lib/nsas/Makefile.am b/src/lib/nsas/Makefile.am
index a88bd22..04a765b 100644
--- a/src/lib/nsas/Makefile.am
+++ b/src/lib/nsas/Makefile.am
@@ -25,6 +25,7 @@ libnsas_la_SOURCES += asiolink.h
libnsas_la_SOURCES += hash.cc hash.h
libnsas_la_SOURCES += hash_deleter.h
libnsas_la_SOURCES += hash_key.cc hash_key.h
+libnsas_la_SOURCES += locks.h
libnsas_la_SOURCES += hash_table.h
libnsas_la_SOURCES += lru_list.h
libnsas_la_SOURCES += nameserver_address_store.cc nameserver_address_store.h
diff --git a/src/lib/nsas/address_entry.h b/src/lib/nsas/address_entry.h
index 148d479..8698017 100644
--- a/src/lib/nsas/address_entry.h
+++ b/src/lib/nsas/address_entry.h
@@ -21,7 +21,7 @@
/// convenience methods for accessing and updating the information.
#include <stdint.h>
-#include "asiolink.h"
+#include <asiolink/io_address.h>
namespace isc {
namespace nsas {
@@ -39,7 +39,7 @@ public:
{}
/// \return Address object
- asiolink::IOAddress getAddress() const {
+ const asiolink::IOAddress& getAddress() const {
return address_;
}
diff --git a/src/lib/nsas/asiolink.h b/src/lib/nsas/asiolink.h
index d0f2bda..d95868f 100644
--- a/src/lib/nsas/asiolink.h
+++ b/src/lib/nsas/asiolink.h
@@ -18,41 +18,4 @@
#include <string>
#include <sys/socket.h>
-namespace asiolink {
-
-/// \brief IO Address Dummy Class
-///
-/// As part of ther resolver, Evan has written the asiolink.h file, which
-/// encapsulates some of the boost::asio classes. Until these are checked
-/// into trunk and merged with this branch, these dummy classes should fulfill
-/// their function.
-
-class IOAddress {
-public:
- /// \param address_str String representing the address
- IOAddress(const std::string& address_str) : address_(address_str)
- {}
-
- /// \brief Just a virtual destructor
- virtual ~ IOAddress() { }
-
- /// \return Textual representation of the address
- std::string toText() const
- {return address_;}
-
- /// \return Address family of the address
- virtual short getFamily() const {
- return ((address_.find(".") != std::string::npos) ? AF_INET : AF_INET6);
- }
-
- /// \return true if two addresses are equal
- bool equal(const IOAddress& address)
- {return (toText() == address.toText());}
-
-private:
- std::string address_; ///< Address represented
-};
-
-} // namespace asiolink
-
#endif // __ASIOLINK_H
diff --git a/src/lib/nsas/hash_table.h b/src/lib/nsas/hash_table.h
index f3889e0..c4a9913 100644
--- a/src/lib/nsas/hash_table.h
+++ b/src/lib/nsas/hash_table.h
@@ -15,25 +15,11 @@
#ifndef __HASH_TABLE_H
#define __HASH_TABLE_H
-// 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 <list>
#include <boost/shared_ptr.hpp>
-#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 <list>
+#include "locks.h"
#include "hash.h"
#include "hash_key.h"
@@ -61,7 +47,7 @@ struct HashTableSlot {
typedef typename std::list<boost::shared_ptr<T> >::iterator iterator;
///< Iterator over elements with same hash
- typedef boost::interprocess::interprocess_upgradable_mutex mutex_type;
+ typedef isc::locks::upgradable_mutex mutex_type;
///< Mutex protecting this slot
//@}
@@ -128,11 +114,11 @@ public:
///
//@{
typedef typename
- boost::interprocess::sharable_lock<typename HashTableSlot<T>::mutex_type>
+ isc::locks::sharable_lock<typename HashTableSlot<T>::mutex_type>
sharable_lock; ///< Type for a scope-limited read-lock
typedef typename
- boost::interprocess::scoped_lock<typename HashTableSlot<T>::mutex_type>
+ isc::locks::scoped_lock<typename HashTableSlot<T>::mutex_type>
scoped_lock; ///< Type for a scope-limited write-lock
//@}
diff --git a/src/lib/nsas/locks.h b/src/lib/nsas/locks.h
new file mode 100644
index 0000000..98197c3
--- /dev/null
+++ b/src/lib/nsas/locks.h
@@ -0,0 +1,116 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// This file (right now) provides dummy locks
+/// It also contains code to use boost/threads locks:
+///
+/// if USE_BOOST_THREADS is defined, we typedef the relevant classes
+/// and derive from the relevant templates so our dummy locks are
+/// replaced by the boost locks (--enable-boost-threads)
+///
+/// If USE_BOOST_THREADS is NOT defined, all locks are dummy classes
+/// that don't actually do anything. At this moment, only the very
+/// minimal set of methods that we actually use is defined.
+///
+/// Note that we need to include <config.h> in our .cc files for that
+/// to be set. we might want to enfore this at compile time with a check
+/// (TODO)
+/// Note that this also contains a workaround for Sunstudio; which
+/// probably won't completely work right now (that is, if the TODO
+/// above is completed), since that would also require some changes
+/// in most (at first glance unrelated) Makefiles
+/// (TODO2)
+
+#ifndef __LOCKS_
+#define __LOCKS_
+
+#ifndef USE_BOOST_THREADS
+
+namespace isc {
+namespace locks {
+
+class mutex {
+};
+
+class recursive_mutex {
+};
+
+class upgradable_mutex {
+};
+
+template <typename T>
+class sharable_lock {
+public:
+ sharable_lock(T) { }
+};
+
+template <typename T>
+class scoped_lock {
+public:
+ scoped_lock(T) { }
+
+ void lock() {}
+ void unlock() {}
+};
+
+}
+}
+
+#else // USE_BOOST_THREADS
+
+// Workaround for a problem with boost and sunstudio 5.10
+// There is a version check in there that appears wrong,
+// which makes including boost/thread.hpp fail
+// This will probably be fixed in a future version of boost,
+// in which case this part can be removed then
+#ifdef NEED_SUNPRO_WORKAROUND
+#if defined(__SUNPRO_CC) && __SUNPRO_CC == 0x5100
+#undef __SUNPRO_CC
+#define __SUNPRO_CC 0x5090
+#endif
+#endif // NEED_SUNPRO_WORKAROUND
+
+#include <boost/thread.hpp>
+#include <boost/interprocess/sync/sharable_lock.hpp>
+#include <boost/interprocess/sync/scoped_lock.hpp>
+#include <boost/interprocess/sync/interprocess_upgradable_mutex.hpp>
+#include <boost/interprocess/sync/interprocess_recursive_mutex.hpp>
+
+namespace isc {
+namespace locks {
+
+typedef boost::mutex mutex;
+typedef boost::interprocess::interprocess_upgradable_mutex upgradable_mutex;
+typedef boost::interprocess::interprocess_recursive_mutex recursive_mutex;
+
+template <typename T>
+struct sharable_lock : public boost::interprocess::sharable_lock<T> {
+public:
+ sharable_lock(T& mtype) : boost::interprocess::sharable_lock<T>(mtype) {}
+};
+
+
+template <class T>
+struct scoped_lock : public boost::interprocess::scoped_lock<T> {
+public:
+ scoped_lock(T& mtype) : boost::interprocess::scoped_lock<T>(mtype) { }
+};
+
+}
+}
+
+
+#endif // USE_BOOST_THREADS
+
+#endif // __LOCKS_
diff --git a/src/lib/nsas/lru_list.h b/src/lib/nsas/lru_list.h
index b332551..b7bd386 100644
--- a/src/lib/nsas/lru_list.h
+++ b/src/lib/nsas/lru_list.h
@@ -18,22 +18,10 @@
#include <list>
#include <string>
-// 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/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
-#include <boost/thread.hpp>
-#include <boost/interprocess/sync/scoped_lock.hpp>
+
+#include "locks.h"
namespace isc {
namespace nsas {
@@ -151,7 +139,7 @@ public:
}
private:
- boost::mutex mutex_; ///< List protection
+ isc::locks::mutex mutex_; ///< List protection
std::list<boost::shared_ptr<T> > lru_; ///< The LRU list itself
uint32_t max_size_; ///< Max size of the list
uint32_t count_; ///< Count of elements
@@ -163,7 +151,7 @@ template <typename T>
void LruList<T>::add(boost::shared_ptr<T>& element) {
// Protect list against concurrent access
- boost::interprocess::scoped_lock<boost::mutex> lock(mutex_);
+ isc::locks::scoped_lock<isc::locks::mutex> lock(mutex_);
// Add the entry and set its pointer field to point into the list.
// insert() is used to get the pointer.
@@ -212,7 +200,7 @@ void LruList<T>::remove(boost::shared_ptr<T>& element) {
if (element->iteratorValid()) {
// Is valid, so protect list against concurrent access
- boost::interprocess::scoped_lock<boost::mutex> lock(mutex_);
+ isc::locks::scoped_lock<isc::locks::mutex> lock(mutex_);
lru_.erase(element->getLruIterator()); // Remove element from list
element->invalidateIterator(); // Invalidate pointer
@@ -228,7 +216,7 @@ void LruList<T>::touch(boost::shared_ptr<T>& element) {
if (element->iteratorValid()) {
// Protect list against concurrent access
- boost::interprocess::scoped_lock<boost::mutex> lock(mutex_);
+ isc::locks::scoped_lock<isc::locks::mutex> lock(mutex_);
// Move the element to the end of the list.
lru_.splice(lru_.end(), lru_, element->getLruIterator());
diff --git a/src/lib/nsas/nameserver_address.cc b/src/lib/nsas/nameserver_address.cc
index b2ed55c..b76d7b8 100644
--- a/src/lib/nsas/nameserver_address.cc
+++ b/src/lib/nsas/nameserver_address.cc
@@ -23,7 +23,9 @@ namespace nsas {
void
NameserverAddress::updateRTT(uint32_t rtt) const {
// We delegate it to the address entry inside the nameserver entry
- ns_->updateAddressRTT(rtt, address_.getAddress(), family_);
+ if (ns_) {
+ ns_->updateAddressRTT(rtt, address_.getAddress(), family_);
+ }
}
} // namespace nsas
diff --git a/src/lib/nsas/nameserver_address_store.cc b/src/lib/nsas/nameserver_address_store.cc
index 0ba9c8e..4efe491 100644
--- a/src/lib/nsas/nameserver_address_store.cc
+++ b/src/lib/nsas/nameserver_address_store.cc
@@ -14,20 +14,6 @@
#include <config.h>
-// 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/shared_ptr.hpp>
#include <boost/foreach.hpp>
#include <boost/bind.hpp>
@@ -35,6 +21,7 @@
#include <config.h>
#include <dns/rdataclass.h>
+#include "locks.h"
#include "hash_table.h"
#include "lru_list.h"
#include "hash_deleter.h"
@@ -106,5 +93,18 @@ NameserverAddressStore::lookup(const string& zone, const RRClass& class_code,
zone_obj.second->addCallback(callback, family);
}
+void
+NameserverAddressStore::cancel(const string& zone,
+ const RRClass& class_code,
+ const boost::shared_ptr<AddressRequestCallback>& callback,
+ AddressFamily family)
+{
+ boost::shared_ptr<ZoneEntry> entry(zone_hash_->get(HashKey(zone,
+ class_code)));
+ if (entry) {
+ entry->removeCallback(callback, family);
+ }
+}
+
} // namespace nsas
} // namespace isc
diff --git a/src/lib/nsas/nameserver_address_store.h b/src/lib/nsas/nameserver_address_store.h
index ab52984..98eb2dd 100644
--- a/src/lib/nsas/nameserver_address_store.h
+++ b/src/lib/nsas/nameserver_address_store.h
@@ -87,6 +87,13 @@ public:
boost::shared_ptr<AddressRequestCallback> callback, AddressFamily
family = ANY_OK);
+ /// \brief cancel the given lookup action
+ ///
+ /// \param callback Callback object that would be called
+ void cancel(const std::string& zone, const dns::RRClass& class_code,
+ const boost::shared_ptr<AddressRequestCallback>& callback,
+ AddressFamily family = ANY_OK);
+
/// \brief Protected Members
///
/// These members should be private. However, with so few public methods
diff --git a/src/lib/nsas/nameserver_entry.cc b/src/lib/nsas/nameserver_entry.cc
index 53f4233..5c7873e 100644
--- a/src/lib/nsas/nameserver_entry.cc
+++ b/src/lib/nsas/nameserver_entry.cc
@@ -35,6 +35,8 @@
#include <dns/question.h>
#include <resolve/resolver_interface.h>
+#include <asiolink/io_address.h>
+
#include "address_entry.h"
#include "nameserver_address.h"
#include "nameserver_entry.h"
@@ -50,7 +52,7 @@ namespace nsas {
namespace {
// Just shorter type alias
-typedef boost::recursive_mutex::scoped_lock Lock;
+typedef isc::locks::scoped_lock<isc::locks::recursive_mutex> Lock;
}
@@ -140,7 +142,7 @@ NameserverEntry::setAddressRTT(const IOAddress& address, uint32_t rtt) {
AddressFamily family(V4_ONLY);
for (;;) {
BOOST_FOREACH(AddressEntry& entry, addresses_[family]) {
- if (entry.getAddress().equal(address)) {
+ if (entry.getAddress().equals(address)) {
entry.setRTT(rtt);
return;
}
@@ -181,7 +183,7 @@ NameserverEntry::updateAddressRTT(uint32_t rtt,
{
Lock lock(mutex_);
for (size_t i(0); i < addresses_[family].size(); ++ i) {
- if (addresses_[family][i].getAddress().equal(address)) {
+ if (addresses_[family][i].getAddress().equals(address)) {
updateAddressRTTAtIndex(rtt, i, family);
return;
}
@@ -226,8 +228,9 @@ class NameserverEntry::ResolverCallback :
response_message->getRcode() != isc::dns::Rcode::NOERROR() ||
response_message->getRRCount(isc::dns::Message::SECTION_ANSWER) == 0) {
failureInternal(lock);
+ return;
}
-
+
isc::dns::RRsetIterator rrsi =
response_message->beginSection(isc::dns::Message::SECTION_ANSWER);
const isc::dns::RRsetPtr response = *rrsi;
diff --git a/src/lib/nsas/nameserver_entry.h b/src/lib/nsas/nameserver_entry.h
index 817460f..77937d1 100644
--- a/src/lib/nsas/nameserver_entry.h
+++ b/src/lib/nsas/nameserver_entry.h
@@ -15,21 +15,8 @@
#ifndef __NAMESERVER_ENTRY_H
#define __NAMESERVER_ENTRY_H
-// 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 <string>
#include <vector>
-#include <boost/thread.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <exceptions/exceptions.h>
@@ -118,7 +105,14 @@ public:
name_(name),
classCode_(class_code),
expiration_(0)
- {}
+ {
+ has_address_[V4_ONLY] = false;
+ has_address_[V6_ONLY] = false;
+ has_address_[ANY_OK] = false;
+ expect_address_[V4_ONLY] = false;
+ expect_address_[V6_ONLY] = false;
+ expect_address_[ANY_OK] = false;
+ }
/*
* \brief Return Address
@@ -252,7 +246,7 @@ public:
//@}
private:
- mutable boost::recursive_mutex mutex_; ///< Mutex protecting this object
+ mutable isc::locks::recursive_mutex mutex_;///< Mutex protecting this object
std::string name_; ///< Canonical name of the nameserver
isc::dns::RRClass classCode_; ///< Class of the nameserver
/**
diff --git a/src/lib/nsas/tests/Makefile.am b/src/lib/nsas/tests/Makefile.am
index 530b730..9d9e61c 100644
--- a/src/lib/nsas/tests/Makefile.am
+++ b/src/lib/nsas/tests/Makefile.am
@@ -54,6 +54,7 @@ endif
run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
endif
diff --git a/src/lib/nsas/tests/address_entry_unittest.cc b/src/lib/nsas/tests/address_entry_unittest.cc
index 716068c..02fef51 100644
--- a/src/lib/nsas/tests/address_entry_unittest.cc
+++ b/src/lib/nsas/tests/address_entry_unittest.cc
@@ -24,12 +24,12 @@
#include <stdint.h>
-#include "../asiolink.h"
+#include <asiolink/io_address.h>
#include "../address_entry.h"
static std::string V4A_TEXT("1.2.3.4");
static std::string V4B_TEXT("5.6.7.8");
-static std::string V6A_TEXT("2001:dead:beef::0");
+static std::string V6A_TEXT("2001:dead:beef::");
static std::string V6B_TEXT("1984:1985::1986:1987");
using namespace asiolink;
diff --git a/src/lib/nsas/tests/nameserver_address_unittest.cc b/src/lib/nsas/tests/nameserver_address_unittest.cc
index 35a46f0..1f924b3 100644
--- a/src/lib/nsas/tests/nameserver_address_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_address_unittest.cc
@@ -100,7 +100,7 @@ protected:
// Test that the address is equal to the address in NameserverEntry
TEST_F(NameserverAddressTest, Address) {
- EXPECT_TRUE(ns_address_.getAddress().equal( ns_sample_.getAddressAtIndex(TEST_ADDRESS_INDEX)));
+ EXPECT_TRUE(ns_address_.getAddress().equals( ns_sample_.getAddressAtIndex(TEST_ADDRESS_INDEX)));
boost::shared_ptr<NameserverEntry> empty_ne((NameserverEntry*)NULL);
// It will throw an NullNameserverEntryPointer exception with the empty NameserverEntry shared pointer
diff --git a/src/lib/nsas/tests/nameserver_entry_unittest.cc b/src/lib/nsas/tests/nameserver_entry_unittest.cc
index 9e4cec7..398c568 100644
--- a/src/lib/nsas/tests/nameserver_entry_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_entry_unittest.cc
@@ -153,7 +153,7 @@ TEST_F(NameserverEntryTest, SetRTT) {
int matchcount = 0;
for (NameserverEntry::AddressVectorIterator i = newvec.begin();
i != newvec.end(); ++i) {
- if (i->getAddress().equal(first_address)) {
+ if (i->getAddress().equals(first_address)) {
++matchcount;
EXPECT_EQ(i->getAddressEntry().getRTT(), new_rtt);
}
@@ -188,7 +188,7 @@ TEST_F(NameserverEntryTest, Unreachable) {
int matchcount = 0;
for (NameserverEntry::AddressVectorIterator i = newvec.begin();
i != newvec.end(); ++i) {
- if (i->getAddress().equal(first_address)) {
+ if (i->getAddress().equals(first_address)) {
++matchcount;
EXPECT_TRUE(i->getAddressEntry().isUnreachable());
}
diff --git a/src/lib/nsas/tests/nsas_test.h b/src/lib/nsas/tests/nsas_test.h
index b4446a4..926e859 100644
--- a/src/lib/nsas/tests/nsas_test.h
+++ b/src/lib/nsas/tests/nsas_test.h
@@ -22,6 +22,7 @@
#include <string>
#include <vector>
+#include <map>
#include <config.h>
diff --git a/src/lib/nsas/tests/zone_entry_unittest.cc b/src/lib/nsas/tests/zone_entry_unittest.cc
index 8a3c6f2..d10f12d 100644
--- a/src/lib/nsas/tests/zone_entry_unittest.cc
+++ b/src/lib/nsas/tests/zone_entry_unittest.cc
@@ -165,9 +165,9 @@ protected:
EXPECT_EQ(failure_count, callback_->unreachable_count_);
EXPECT_EQ(success_count, callback_->successes_.size());
for (size_t i = 0; i < callback_->successes_.size(); ++ i) {
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[i].getAddress()) ||
- IOAddress("2001:db8::1").equal(
+ IOAddress("2001:db8::1").equals(
callback_->successes_[i].getAddress()));
}
}
@@ -234,7 +234,7 @@ TEST_F(ZoneEntryTest, ChangedNS) {
EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
rdata::in::A("192.0.2.1")));
ASSERT_EQ(1, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[0].getAddress()));
EXPECT_NO_THROW(resolver_->answer(2, ns_name_, RRType::AAAA(),
rdata::in::AAAA("2001:db8::1")));
@@ -257,7 +257,7 @@ TEST_F(ZoneEntryTest, ChangedNS) {
EXPECT_NO_THROW(resolver_->answer(4, different_name, RRType::A(),
rdata::in::A("192.0.2.2")));
ASSERT_EQ(2, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.2").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.2").equals(
callback_->successes_[1].getAddress()));
// And now, switch back, as it timed out again
@@ -270,7 +270,7 @@ TEST_F(ZoneEntryTest, ChangedNS) {
EXPECT_EQ(7, resolver_->requests.size());
EXPECT_EQ(Fetchable::READY, zone->getState());
ASSERT_EQ(3, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[0].getAddress()));
}
@@ -306,9 +306,9 @@ TEST_F(ZoneEntryTest, CallbacksAnswered) {
rdata::in::A("192.0.2.1")));
// Two are answered (ANY and V4)
ASSERT_EQ(2, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[0].getAddress()));
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[1].getAddress()));
// None are rejected
EXPECT_EQ(0, callback_->unreachable_count_);
@@ -318,7 +318,7 @@ TEST_F(ZoneEntryTest, CallbacksAnswered) {
// This should answer the third callback
EXPECT_EQ(0, callback_->unreachable_count_);
ASSERT_EQ(3, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("2001:db8::1").equal(
+ EXPECT_TRUE(IOAddress("2001:db8::1").equals(
callback_->successes_[2].getAddress()));
// It should think it is ready
EXPECT_EQ(Fetchable::READY, zone->getState());
@@ -326,7 +326,7 @@ TEST_F(ZoneEntryTest, CallbacksAnswered) {
zone->addCallback(callback_, V4_ONLY);
EXPECT_EQ(3, resolver_->requests.size());
ASSERT_EQ(4, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[3].getAddress()));
EXPECT_EQ(0, callback_->unreachable_count_);
}
@@ -366,9 +366,9 @@ TEST_F(ZoneEntryTest, CallbacksAOnly) {
EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
rdata::in::A("192.0.2.1")));
ASSERT_EQ(2, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[0].getAddress()));
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[1].getAddress()));
EXPECT_EQ(1, callback_->unreachable_count_);
// Everything arriwed, so we are ready
@@ -377,7 +377,7 @@ TEST_F(ZoneEntryTest, CallbacksAOnly) {
zone->addCallback(callback_, V4_ONLY);
EXPECT_EQ(3, resolver_->requests.size());
ASSERT_EQ(3, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[2].getAddress()));
EXPECT_EQ(1, callback_->unreachable_count_);
@@ -439,9 +439,9 @@ TEST_F(ZoneEntryTest, CallbackTwoNS) {
// The other callbacks should be answered now
EXPECT_EQ(2, callback_->unreachable_count_);
ASSERT_EQ(2, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("2001:db8::1").equal(
+ EXPECT_TRUE(IOAddress("2001:db8::1").equals(
callback_->successes_[0].getAddress()));
- EXPECT_TRUE(IOAddress("2001:db8::1").equal(
+ EXPECT_TRUE(IOAddress("2001:db8::1").equals(
callback_->successes_[1].getAddress()));
}
@@ -534,7 +534,7 @@ TEST_F(ZoneEntryTest, AddressTimeout) {
rdata::in::A("192.0.2.1"), 0));
// It answers, not rejects
ASSERT_EQ(1, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[0].getAddress()));
EXPECT_EQ(0, callback_->unreachable_count_);
// As well with IPv6
@@ -551,7 +551,7 @@ TEST_F(ZoneEntryTest, AddressTimeout) {
rdata::in::A("192.0.2.1"), 0));
EXPECT_EQ(0, callback_->unreachable_count_);
ASSERT_EQ(2, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[1].getAddress()));
}
diff --git a/src/lib/nsas/zone_entry.cc b/src/lib/nsas/zone_entry.cc
index 395b06c..36e01d2 100644
--- a/src/lib/nsas/zone_entry.cc
+++ b/src/lib/nsas/zone_entry.cc
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <map>
+
#include <config.h>
#include "zone_entry.h"
@@ -49,7 +51,7 @@ ZoneEntry::ZoneEntry(
namespace {
// Shorter aliases for frequently used types
-typedef boost::recursive_mutex::scoped_lock Lock; // Local lock, nameservers not locked
+typedef isc::locks::scoped_lock<isc::locks::recursive_mutex> Lock; // Local lock, nameservers not locked
typedef boost::shared_ptr<AddressRequestCallback> CallbackPtr;
/*
@@ -141,7 +143,7 @@ class ZoneEntry::ResolverCallback :
Name ns_name(dynamic_cast<const rdata::generic::NS&>(
iterator->getCurrent()).getNSName());
// Try to find it in the old ones
- map<string, NameserverPtr>::iterator old_ns(old.find(
+ std::map<string, NameserverPtr>::iterator old_ns(old.find(
ns_name.toText()));
/*
* We didn't have this nameserver before. So we just
@@ -259,6 +261,23 @@ ZoneEntry::addCallback(CallbackPtr callback, AddressFamily family) {
}
}
+void
+ZoneEntry::removeCallback(const CallbackPtr& callback, AddressFamily family) {
+ Lock lock(mutex_);
+ std::vector<boost::shared_ptr<AddressRequestCallback> >::iterator i =
+ callbacks_[family].begin();
+ for (; i != callbacks_[family].end(); ++i) {
+ if (*i == callback) {
+ callbacks_[family].erase(i);
+ // At this point, a callback should only be in the list
+ // once (enforced by RunningQuery doing only one at a time)
+ // If that changes, we need to revise this (can't delete
+ // elements from a list we're looping over)
+ return;
+ }
+ }
+}
+
namespace {
// This just moves items from one container to another
diff --git a/src/lib/nsas/zone_entry.h b/src/lib/nsas/zone_entry.h
index fea6fc8..a1f12bc 100644
--- a/src/lib/nsas/zone_entry.h
+++ b/src/lib/nsas/zone_entry.h
@@ -15,22 +15,9 @@
#ifndef __ZONE_ENTRY_H
#define __ZONE_ENTRY_H
-// 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 <string>
#include <vector>
#include <set>
-#include <boost/thread.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
@@ -38,6 +25,7 @@
#include <resolve/resolver_interface.h>
+#include "locks.h"
#include "hash_key.h"
#include "nsas_entry.h"
#include "asiolink.h"
@@ -74,8 +62,9 @@ public:
* \param name Name of the zone
* \param class_code Class of this zone (zones of different classes have
* different objects.
- * \param nameserver_table TODO
- * \param nameserver_lru TODO
+ * \param nameserver_table Hashtable of NameServerEntry objects for
+ * this zone
+ * \param namesever_lru LRU for the nameserver entries
* \todo Move to cc file, include the lookup (if NSAS uses resolver for
* everything)
*/
@@ -112,6 +101,15 @@ public:
void addCallback(boost::shared_ptr<AddressRequestCallback>
callback, AddressFamily family);
+ /**
+ * \short Remove a callback from the list
+ *
+ * \param callback The callback itself.
+ * \param family Which address family is acceptable as an answer?
+ */
+ void removeCallback(const boost::shared_ptr<AddressRequestCallback>&
+ callback, AddressFamily family);
+
/// \short Protected members, so they can be accessed by tests.
//@{
protected:
@@ -130,7 +128,7 @@ protected:
time_t expiry_; ///< Expiry time of this entry, 0 means not set
//}@
private:
- mutable boost::recursive_mutex mutex_; ///< Mutex protecting this zone entry
+ mutable isc::locks::recursive_mutex mutex_;///< Mutex protecting this zone entry
std::string name_; ///< Canonical zone name
isc::dns::RRClass class_code_; ///< Class code
/**
diff --git a/src/lib/python/isc/cc/data.py b/src/lib/python/isc/cc/data.py
index c75fbfe..ce1bba0 100644
--- a/src/lib/python/isc/cc/data.py
+++ b/src/lib/python/isc/cc/data.py
@@ -100,8 +100,8 @@ def split_identifier_list_indices(identifier):
i = id_str.find('[')
if i < 0:
- if identifier.find(']') >= 0:
- raise DataTypeError("Bad format in identifier: " + str(identifier))
+ if id_str.find(']') >= 0:
+ raise DataTypeError("Bad format in identifier (] but no [): " + str(identifier))
return identifier, None
# keep the non-index part of that to replace later
@@ -110,7 +110,7 @@ def split_identifier_list_indices(identifier):
while i >= 0:
e = id_str.find(']')
if e < i + 1:
- raise DataTypeError("Bad format in identifier: " + str(identifier))
+ raise DataTypeError("Bad format in identifier (] before [): " + str(identifier))
try:
indices.append(int(id_str[i+1:e]))
except ValueError:
@@ -118,9 +118,9 @@ def split_identifier_list_indices(identifier):
id_str = id_str[e + 1:]
i = id_str.find('[')
if i > 0:
- raise DataTypeError("Bad format in identifier: " + str(identifier))
+ raise DataTypeError("Bad format in identifier ([ within []): " + str(identifier))
if id.find(']') >= 0 or len(id_str) > 0:
- raise DataTypeError("Bad format in identifier: " + str(identifier))
+ raise DataTypeError("Bad format in identifier (extra ]): " + str(identifier))
# we replace the final part of the original identifier with
# the stripped string
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index e7fdb86..0e602b7 100644
--- a/src/lib/python/isc/config/ccsession.py
+++ b/src/lib/python/isc/config/ccsession.py
@@ -374,20 +374,34 @@ class UIModuleCCSession(MultiConfigData):
self._set_current_config(config)
- def add_value(self, identifier, value_str):
+ def add_value(self, identifier, value_str = None):
"""Add a value to a configuration list. Raises a DataTypeError
if the value does not conform to the list_item_spec field
- of the module config data specification"""
+ of the module config data specification. If value_str is
+ not given, we add the default as specified by the .spec
+ file."""
module_spec = self.find_spec_part(identifier)
if (type(module_spec) != dict or "list_item_spec" not in module_spec):
raise isc.cc.data.DataNotFoundError(str(identifier) + " is not a list")
- value = isc.cc.data.parse_value_str(value_str)
+
cur_list, status = self.get_value(identifier)
if not cur_list:
cur_list = []
+
+ # Hmm. Do we need to check for duplicates?
+ value = None
+ if value_str is not None:
+ value = isc.cc.data.parse_value_str(value_str)
+ else:
+ if "item_default" in module_spec["list_item_spec"]:
+ value = module_spec["list_item_spec"]["item_default"]
+
+ if value is None:
+ raise isc.cc.data.DataNotFoundError("No value given and no default for " + str(identifier))
+
if value not in cur_list:
cur_list.append(value)
- self.set_value(identifier, cur_list)
+ self.set_value(identifier, cur_list)
def remove_value(self, identifier, value_str):
"""Remove a value from a configuration list. The value string
diff --git a/src/lib/python/isc/config/config_data.py b/src/lib/python/isc/config/config_data.py
index 1e3afd1..582c11c 100644
--- a/src/lib/python/isc/config/config_data.py
+++ b/src/lib/python/isc/config/config_data.py
@@ -121,6 +121,7 @@ def find_spec_part(element, identifier):
# strip list selector part
# don't need it for the spec part, so just drop it
id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
+ # is this part still needed? (see below)
if type(cur_el) == dict and 'map_item_spec' in cur_el.keys():
found = False
for cur_el_item in cur_el['map_item_spec']:
@@ -128,15 +129,24 @@ def find_spec_part(element, identifier):
cur_el = cur_el_item
found = True
if not found:
- raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el))
+ raise isc.cc.data.DataNotFoundError(id + " not found")
+ elif type(cur_el) == dict and 'list_item_spec' in cur_el.keys():
+ cur_el = cur_el['list_item_spec']
elif type(cur_el) == list:
found = False
for cur_el_item in cur_el:
if cur_el_item['item_name'] == id:
cur_el = cur_el_item
+ # if we need to go further, we may need to 'skip' a step here
+ # but not if we're done
+ if id_parts[-1] != id_part and type(cur_el) == dict:
+ if "map_item_spec" in cur_el:
+ cur_el = cur_el["map_item_spec"]
+ elif "list_item_spec" in cur_el:
+ cur_el = cur_el["list_item_spec"]
found = True
if not found:
- raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el))
+ raise isc.cc.data.DataNotFoundError(id + " not found")
else:
raise isc.cc.data.DataNotFoundError("Not a correct config specification")
return cur_el
@@ -354,26 +364,63 @@ class MultiConfigData:
See get_value() for a general way to find a configuration
value
"""
- if identifier[0] == '/':
- identifier = identifier[1:]
- module, sep, id = identifier.partition("/")
try:
+ if identifier[0] == '/':
+ identifier = identifier[1:]
+ module, sep, id = identifier.partition("/")
+ # if there is a 'higher-level' list index specified, we need
+ # to check if that list specification has a default that
+ # overrides the more specific default in the final spec item
+ # (ie. list_default = [1, 2, 3], list_item_spec=int, default=0)
+ # def default list[1] should return 2, not 0
+ id_parts = isc.cc.data.split_identifier(id)
+ id_prefix = ""
+ while len(id_parts) > 0:
+ id_part = id_parts.pop(0)
+ item_id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
+ id_list = module + "/" + id_prefix + "/" + item_id
+ id_prefix += "/" + id_part
+ if list_indices is not None:
+ # there's actually two kinds of default here for
+ # lists; they can have a default value (like an
+ # empty list), but their elements can also have
+ # default values.
+ # So if the list item *itself* is a default,
+ # we need to get the value out of that. If not, we
+ # need to find the default for the specific element.
+ list_value, type = self.get_value(id_list)
+ list_spec = find_spec_part(self._specifications[module].get_config_spec(), id_prefix)
+ if type == self.DEFAULT:
+ if 'item_default' in list_spec:
+ list_value = list_spec['item_default']
+ for i in list_indices:
+ if i < len(list_value):
+ list_value = list_value[i]
+ else:
+ # out of range, return None
+ return None
+
+ if len(id_parts) > 0:
+ rest_of_id = "/".join(id_parts)
+ return isc.cc.data.find(list_value, rest_of_id)
+ else:
+ return list_value
+ else:
+ # we do have a non-default list, see if our indices
+ # exist
+ for i in list_indices:
+ if i < len(list_value):
+ list_value = list_value[i]
+ else:
+ # out of range, return None
+ return None
+
spec = find_spec_part(self._specifications[module].get_config_spec(), id)
if 'item_default' in spec:
- id, list_indices = isc.cc.data.split_identifier_list_indices(id)
- if list_indices is not None and \
- type(spec['item_default']) == list:
- if len(list_indices) == 1:
- default_list = spec['item_default']
- index = list_indices[0]
- if index < len(default_list):
- return default_list[index]
- else:
- return None
- else:
- return spec['item_default']
+ return spec['item_default']
else:
return None
+
except isc.cc.data.DataNotFoundError as dnfe:
return None
@@ -398,13 +445,60 @@ class MultiConfigData:
return value, self.DEFAULT
return None, self.NONE
- def get_value_maps(self, identifier = None):
+ def _append_value_item(self, result, spec_part, identifier, all, first = False):
+ # Look at the spec; it is a list of items, or a map containing 'item_name' etc
+ if type(spec_part) == list:
+ for spec_part_element in spec_part:
+ spec_part_element_name = spec_part_element['item_name']
+ self._append_value_item(result, spec_part_element, identifier + "/" + spec_part_element_name, all)
+ elif type(spec_part) == dict:
+ # depending on item type, and the value of argument 'all'
+ # we need to either add an item, or recursively go on
+ # In the case of a list that is empty, we do need to show that
+ item_name = spec_part['item_name']
+ item_type = spec_part['item_type']
+ if item_type == "list" and (all or first):
+ spec_part_list = spec_part['list_item_spec']
+ list_value, status = self.get_value(identifier)
+ if list_value is None:
+ print("Error: identifier '%s' not found" % identifier)
+ return
+ if type(list_value) != list:
+ # the identifier specified a single element
+ self._append_value_item(result, spec_part_list, identifier, all)
+ else:
+ list_len = len(list_value)
+ if len(list_value) == 0 and (all or first):
+ entry = _create_value_map_entry(identifier,
+ item_type,
+ [], status)
+ result.append(entry)
+ else:
+ for i in range(len(list_value)):
+ self._append_value_item(result, spec_part_list, "%s[%d]" % (identifier, i), all)
+ elif item_type == "map":
+ # just show the specific contents of a map, we are
+ # almost never interested in just its name
+ spec_part_map = spec_part['map_item_spec']
+ self._append_value_item(result, spec_part_map, identifier, all)
+ else:
+ value, status = self.get_value(identifier)
+ entry = _create_value_map_entry(identifier,
+ item_type,
+ value, status)
+ result.append(entry)
+ return
+
+
+ def get_value_maps(self, identifier = None, all = False):
"""Returns a list of dicts, containing the following values:
name: name of the entry (string)
type: string containing the type of the value (or 'module')
value: value of the entry if it is a string, int, double or bool
- modified: true if the value is a local change
- default: true if the value has been changed
+ modified: true if the value is a local change that has not
+ been committed
+ default: true if the value has not been changed (i.e. the
+ value is the default from the specification)
TODO: use the consts for those last ones
Throws DataNotFoundError if the identifier is bad
"""
@@ -412,8 +506,14 @@ class MultiConfigData:
if not identifier:
# No identifier, so we need the list of current modules
for module in self._specifications.keys():
- entry = _create_value_map_entry(module, 'module', None)
- result.append(entry)
+ if all:
+ spec = self.get_module_spec(module)
+ if spec:
+ spec_part = spec.get_config_spec()
+ self._append_value_item(result, spec_part, module, all, True)
+ else:
+ entry = _create_value_map_entry(module, 'module', None)
+ result.append(entry)
else:
if identifier[0] == '/':
identifier = identifier[1:]
@@ -421,42 +521,7 @@ class MultiConfigData:
spec = self.get_module_spec(module)
if spec:
spec_part = find_spec_part(spec.get_config_spec(), id)
- if type(spec_part) == list:
- # list of items to show
- for item in spec_part:
- value, status = self.get_value("/" + identifier\
- + "/" + item['item_name'])
- entry = _create_value_map_entry(item['item_name'],
- item['item_type'],
- value, status)
- result.append(entry)
- elif type(spec_part) == dict:
- # Sub-specification
- item = spec_part
- if item['item_type'] == 'list':
- li_spec = item['list_item_spec']
- value, status = self.get_value("/" + identifier)
- if type(value) == list:
- for list_value in value:
- result_part2 = _create_value_map_entry(
- li_spec['item_name'],
- li_spec['item_type'],
- list_value)
- result.append(result_part2)
- elif value is not None:
- entry = _create_value_map_entry(
- li_spec['item_name'],
- li_spec['item_type'],
- value, status)
- result.append(entry)
- else:
- value, status = self.get_value("/" + identifier)
- if value is not None:
- entry = _create_value_map_entry(
- item['item_name'],
- item['item_type'],
- value, status)
- result.append(entry)
+ self._append_value_item(result, spec_part, identifier, all, True)
return result
def set_value(self, identifier, value):
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 4710b00..1aded94 100644
--- a/src/lib/python/isc/config/tests/config_data_test.py
+++ b/src/lib/python/isc/config/tests/config_data_test.py
@@ -358,6 +358,8 @@ class TestMultiConfigData(unittest.TestCase):
self.assertEqual(1, value)
value = self.mcd.get_default_value("Spec2/item5[0]")
self.assertEqual('a', value)
+ value = self.mcd.get_default_value("Spec2/item5[1]")
+ self.assertEqual('b', value)
value = self.mcd.get_default_value("Spec2/item5[5]")
self.assertEqual(None, value)
value = self.mcd.get_default_value("Spec2/item5[0][1]")
@@ -392,6 +394,10 @@ class TestMultiConfigData(unittest.TestCase):
self.assertEqual(None, value)
self.assertEqual(MultiConfigData.NONE, status)
+ value, status = self.mcd.get_value("Spec2/item5")
+ self.assertEqual(['a', 'b'], value)
+ self.assertEqual(MultiConfigData.DEFAULT, status)
+
value, status = self.mcd.get_value("Spec2/item5[0]")
self.assertEqual("a", value)
self.assertEqual(MultiConfigData.DEFAULT, status)
@@ -400,6 +406,11 @@ class TestMultiConfigData(unittest.TestCase):
self.assertEqual(None, value)
self.assertEqual(MultiConfigData.NONE, status)
+ value, status = self.mcd.get_value("Spec2/item5[1]")
+ self.assertEqual("b", value)
+ self.assertEqual(MultiConfigData.DEFAULT, status)
+
+
def test_get_value_maps(self):
maps = self.mcd.get_value_maps()
@@ -423,32 +434,34 @@ class TestMultiConfigData(unittest.TestCase):
self.mcd._set_current_config({ "Spec2": { "item1": 2 } })
self.mcd.set_value("Spec2/item3", False)
maps = self.mcd.get_value_maps("/Spec2")
- self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False},
- {'default': True, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False},
- {'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True},
- {'default': True, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False},
- {'default': True, 'type': 'list', 'name': 'item5', 'value': ['a', 'b'], 'modified': False},
- {'default': True, 'type': 'map', 'name': 'item6', 'value': {}, 'modified': False}], maps)
+ self.assertEqual([{'default': False, 'type': 'integer', 'name': 'Spec2/item1', 'value': 2, 'modified': False},
+ {'default': True, 'type': 'real', 'name': 'Spec2/item2', 'value': 1.1, 'modified': False},
+ {'default': False, 'type': 'boolean', 'name': 'Spec2/item3', 'value': False, 'modified': True},
+ {'default': True, 'type': 'string', 'name': 'Spec2/item4', 'value': 'test', 'modified': False},
+ {'default': True, 'type': 'list', 'name': 'Spec2/item5', 'value': ['a', 'b'], 'modified': False},
+ {'default': True, 'type': 'string', 'name': 'Spec2/item6/value1', 'value': 'default', 'modified': False},
+ {'default': False, 'type': 'integer', 'name': 'Spec2/item6/value2', 'value': None, 'modified': False}], maps)
maps = self.mcd.get_value_maps("Spec2")
- self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False},
- {'default': True, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False},
- {'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True},
- {'default': True, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False},
- {'default': True, 'type': 'list', 'name': 'item5', 'value': ['a', 'b'], 'modified': False},
- {'default': True, 'type': 'map', 'name': 'item6', 'value': {}, 'modified': False}], maps)
+ self.assertEqual([{'default': False, 'type': 'integer', 'name': 'Spec2/item1', 'value': 2, 'modified': False},
+ {'default': True, 'type': 'real', 'name': 'Spec2/item2', 'value': 1.1, 'modified': False},
+ {'default': False, 'type': 'boolean', 'name': 'Spec2/item3', 'value': False, 'modified': True},
+ {'default': True, 'type': 'string', 'name': 'Spec2/item4', 'value': 'test', 'modified': False},
+ {'default': True, 'type': 'list', 'name': 'Spec2/item5', 'value': ['a', 'b'], 'modified': False},
+ {'default': True, 'type': 'string', 'name': 'Spec2/item6/value1', 'value': 'default', 'modified': False},
+ {'default': False, 'type': 'integer', 'name': 'Spec2/item6/value2', 'value': None, 'modified': False}], maps)
maps = self.mcd.get_value_maps("/Spec2/item5")
- self.assertEqual([{'default': False, 'type': 'string', 'name': 'list_element', 'value': 'a', 'modified': False},
- {'default': False, 'type': 'string', 'name': 'list_element', 'value': 'b', 'modified': False}], maps)
+ self.assertEqual([{'default': True, 'type': 'string', 'name': 'Spec2/item5[0]', 'value': 'a', 'modified': False},
+ {'default': True, 'type': 'string', 'name': 'Spec2/item5[1]', 'value': 'b', 'modified': False}], maps)
maps = self.mcd.get_value_maps("/Spec2/item5[0]")
- self.assertEqual([{'default': True, 'modified': False, 'name': 'list_element', 'type': 'string', 'value': 'a'}], maps)
+ self.assertEqual([{'default': True, 'modified': False, 'name': 'Spec2/item5[0]', 'type': 'string', 'value': 'a'}], maps)
maps = self.mcd.get_value_maps("/Spec2/item1")
- self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False}], maps)
+ self.assertEqual([{'default': False, 'type': 'integer', 'name': 'Spec2/item1', 'value': 2, 'modified': False}], maps)
maps = self.mcd.get_value_maps("/Spec2/item2")
- self.assertEqual([{'default': True, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False}], maps)
+ self.assertEqual([{'default': True, 'type': 'real', 'name': 'Spec2/item2', 'value': 1.1, 'modified': False}], maps)
maps = self.mcd.get_value_maps("/Spec2/item3")
- self.assertEqual([{'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True}], maps)
+ self.assertEqual([{'default': False, 'type': 'boolean', 'name': 'Spec2/item3', 'value': False, 'modified': True}], maps)
maps = self.mcd.get_value_maps("/Spec2/item4")
- self.assertEqual([{'default': True, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False}], maps)
+ self.assertEqual([{'default': True, 'type': 'string', 'name': 'Spec2/item4', 'value': 'test', 'modified': False}], maps)
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec24.spec")
self.mcd.set_specification(module_spec)
@@ -456,9 +469,30 @@ class TestMultiConfigData(unittest.TestCase):
self.assertEqual([], maps)
self.mcd._set_current_config({ "Spec24": { "item": [] } })
maps = self.mcd.get_value_maps("/Spec24/item")
- self.assertEqual([], maps)
-
+ self.assertEqual([{'default': False, 'modified': False, 'name': 'Spec24/item', 'type': 'list', 'value': []}], maps)
+ module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec22.spec")
+ self.mcd.set_specification(module_spec)
+ expected = [{'default': True,
+ 'modified': False,
+ 'name': 'Spec22/value9/v91',
+ 'type': 'string',
+ 'value': 'def'},
+ {'default': True,
+ 'modified': False,
+ 'name': 'Spec22/value9/v92/v92a',
+ 'type': 'string',
+ 'value': 'Hello'
+ },
+ {'default': True,
+ 'modified': False,
+ 'name': 'Spec22/value9/v92/v92b',
+ 'type': 'integer',
+ 'value': 47806
+ }
+ ]
+ maps = self.mcd.get_value_maps("/Spec22/value9")
+ self.assertEqual(expected, maps)
def test_set_value(self):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
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 0816e07..c4c149c 100644
--- a/src/lib/python/isc/notify/tests/notify_out_test.py
+++ b/src/lib/python/isc/notify/tests/notify_out_test.py
@@ -22,9 +22,45 @@ import socket
from isc.datasrc import sqlite3_ds
from isc.notify import notify_out, SOCK_DATA
+# our fake socket, where we can read and insert messages
+class MockSocket():
+ def __init__(self, family, type):
+ self.family = family
+ self.type = type
+ self._local_sock, self._remote_sock = socket.socketpair()
+
+ def connect(self, to):
+ pass
+
+ def fileno(self):
+ return self._local_sock.fileno()
+
+ def close(self):
+ self._local_sock.close()
+ self._remote_sock.close()
+
+ def sendto(self, data, flag, dst):
+ return self._local_sock.send(data)
+
+ def recvfrom(self, length):
+ data = self._local_sock.recv(length)
+ return (data, None)
+
+ # provide a remote end which can write data to MockSocket for testing.
+ def remote_end(self):
+ return self._remote_sock
+
+# We subclass the ZoneNotifyInfo class we're testing here, only
+# to override the prepare_notify_out() method.
+class MockZoneNotifyInfo(notify_out.ZoneNotifyInfo):
+ def prepare_notify_out(self):
+ super().prepare_notify_out();
+ self._sock.close()
+ self._sock = MockSocket(socket.AF_INET, socket.SOCK_DGRAM)
+
class TestZoneNotifyInfo(unittest.TestCase):
def setUp(self):
- self.info = notify_out.ZoneNotifyInfo('cn.', 'IN')
+ self.info = notify_out.ZoneNotifyInfo('example.net.', 'IN')
def test_prepare_finish_notify_out(self):
self.info.prepare_notify_out()
@@ -46,7 +82,7 @@ class TestZoneNotifyInfo(unittest.TestCase):
self.info.set_next_notify_target()
self.assertIsNone(self.info.get_current_notify_target())
- temp_info = notify_out.ZoneNotifyInfo('com.', 'IN')
+ temp_info = notify_out.ZoneNotifyInfo('example.com.', 'IN')
temp_info.prepare_notify_out()
self.assertIsNone(temp_info.get_current_notify_target())
@@ -54,16 +90,16 @@ class TestZoneNotifyInfo(unittest.TestCase):
class TestNotifyOut(unittest.TestCase):
def setUp(self):
self._db_file = tempfile.NamedTemporaryFile(delete=False)
- sqlite3_ds.load(self._db_file.name, 'cn.', self._cn_data_reader)
- sqlite3_ds.load(self._db_file.name, 'com.', self._com_data_reader)
+ sqlite3_ds.load(self._db_file.name, 'example.net.', self._example_net_data_reader)
+ sqlite3_ds.load(self._db_file.name, 'example.com.', self._example_com_data_reader)
self._notify = notify_out.NotifyOut(self._db_file.name)
- self._notify._notify_infos[('com.', 'IN')] = notify_out.ZoneNotifyInfo('com.', 'IN')
- self._notify._notify_infos[('com.', 'CH')] = notify_out.ZoneNotifyInfo('com.', 'CH')
- self._notify._notify_infos[('cn.', 'IN')] = notify_out.ZoneNotifyInfo('cn.', 'IN')
- self._notify._notify_infos[('org.', 'IN')] = notify_out.ZoneNotifyInfo('org.', 'IN')
- self._notify._notify_infos[('org.', 'CH')] = notify_out.ZoneNotifyInfo('org.', 'CH')
-
- info = self._notify._notify_infos[('cn.', 'IN')]
+ self._notify._notify_infos[('example.com.', 'IN')] = MockZoneNotifyInfo('example.com.', 'IN')
+ self._notify._notify_infos[('example.com.', 'CH')] = MockZoneNotifyInfo('example.com.', 'CH')
+ self._notify._notify_infos[('example.net.', 'IN')] = MockZoneNotifyInfo('example.net.', 'IN')
+ self._notify._notify_infos[('example.org.', 'IN')] = MockZoneNotifyInfo('example.org.', 'IN')
+ self._notify._notify_infos[('example.org.', 'CH')] = MockZoneNotifyInfo('example.org.', 'CH')
+
+ info = self._notify._notify_infos[('example.net.', 'IN')]
info.notify_slaves.append(('127.0.0.1', 53))
info.notify_slaves.append(('1.1.1.1', 5353))
@@ -72,62 +108,59 @@ class TestNotifyOut(unittest.TestCase):
os.unlink(self._db_file.name)
def test_send_notify(self):
- self._notify.send_notify('cn')
+ self._notify.send_notify('example.net')
self.assertEqual(self._notify.notify_num, 1)
- self.assertEqual(self._notify._notifying_zones[0], ('cn.','IN'))
+ self.assertEqual(self._notify._notifying_zones[0], ('example.net.','IN'))
- self._notify.send_notify('com')
+ self._notify.send_notify('example.com')
self.assertEqual(self._notify.notify_num, 2)
- self.assertEqual(self._notify._notifying_zones[1], ('com.','IN'))
+ self.assertEqual(self._notify._notifying_zones[1], ('example.com.','IN'))
notify_out._MAX_NOTIFY_NUM = 3
- self._notify.send_notify('com', 'CH')
+ self._notify.send_notify('example.com', 'CH')
self.assertEqual(self._notify.notify_num, 3)
- self.assertEqual(self._notify._notifying_zones[2], ('com.','CH'))
-
- self._notify.send_notify('org.')
- self.assertEqual(self._notify._waiting_zones[0], ('org.', 'IN'))
- self._notify.send_notify('org.')
+ self.assertEqual(self._notify._notifying_zones[2], ('example.com.','CH'))
+
+ self._notify.send_notify('example.org.')
+ self.assertEqual(self._notify._waiting_zones[0], ('example.org.', 'IN'))
+ self._notify.send_notify('example.org.')
self.assertEqual(1, len(self._notify._waiting_zones))
- self._notify.send_notify('org.', 'CH')
+ self._notify.send_notify('example.org.', 'CH')
self.assertEqual(2, len(self._notify._waiting_zones))
- self.assertEqual(self._notify._waiting_zones[1], ('org.', 'CH'))
+ self.assertEqual(self._notify._waiting_zones[1], ('example.org.', 'CH'))
def test_wait_for_notify_reply(self):
- self._notify.send_notify('cn.')
- self._notify.send_notify('com.')
-
+ self._notify.send_notify('example.net.')
+ self._notify.send_notify('example.com.')
+
notify_out._MAX_NOTIFY_NUM = 2
- self._notify.send_notify('org.')
+ self._notify.send_notify('example.org.')
replied_zones, timeout_zones = self._notify._wait_for_notify_reply()
self.assertEqual(len(replied_zones), 0)
self.assertEqual(len(timeout_zones), 2)
# Now make one socket be readable
- addr = ('localhost', 12340)
- self._notify._notify_infos[('cn.', 'IN')]._sock.bind(addr)
- self._notify._notify_infos[('cn.', 'IN')].notify_timeout = time.time() + 10
- self._notify._notify_infos[('com.', 'IN')].notify_timeout = time.time() + 10
-
- send_fd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ self._notify._notify_infos[('example.net.', 'IN')].notify_timeout = time.time() + 10
+ self._notify._notify_infos[('example.com.', 'IN')].notify_timeout = time.time() + 10
+
#Send some data to socket 12340, to make the target socket be readable
- send_fd.sendto(b'data', addr)
+ self._notify._notify_infos[('example.net.', 'IN')]._sock.remote_end().send(b'data')
replied_zones, timeout_zones = self._notify._wait_for_notify_reply()
self.assertEqual(len(replied_zones), 1)
self.assertEqual(len(timeout_zones), 1)
- self.assertTrue(('cn.', 'IN') in replied_zones.keys())
- self.assertTrue(('com.', 'IN') in timeout_zones.keys())
- self.assertLess(time.time(), self._notify._notify_infos[('com.', 'IN')].notify_timeout)
-
+ self.assertTrue(('example.net.', 'IN') in replied_zones.keys())
+ self.assertTrue(('example.com.', 'IN') in timeout_zones.keys())
+ self.assertLess(time.time(), self._notify._notify_infos[('example.com.', 'IN')].notify_timeout)
+
def test_wait_for_notify_reply_2(self):
# Test the returned value when the read_side socket is readable.
- self._notify.send_notify('cn.')
- self._notify.send_notify('com.')
+ self._notify.send_notify('example.net.')
+ self._notify.send_notify('example.com.')
# Now make one socket be readable
- self._notify._notify_infos[('cn.', 'IN')].notify_timeout = time.time() + 10
- self._notify._notify_infos[('com.', 'IN')].notify_timeout = time.time() + 10
+ self._notify._notify_infos[('example.net.', 'IN')].notify_timeout = time.time() + 10
+ self._notify._notify_infos[('example.com.', 'IN')].notify_timeout = time.time() + 10
self._notify._read_sock, self._notify._write_sock = socket.socketpair()
self._notify._write_sock.send(SOCK_DATA)
replied_zones, timeout_zones = self._notify._wait_for_notify_reply()
@@ -135,13 +168,13 @@ class TestNotifyOut(unittest.TestCase):
self.assertEqual(0, len(timeout_zones))
def test_notify_next_target(self):
- self._notify.send_notify('cn.')
- self._notify.send_notify('com.')
+ self._notify.send_notify('example.net.')
+ self._notify.send_notify('example.com.')
notify_out._MAX_NOTIFY_NUM = 2
- self._notify.send_notify('org.')
- self._notify.send_notify('com.', 'CH')
+ self._notify.send_notify('example.org.')
+ self._notify.send_notify('example.com.', 'CH')
- info = self._notify._notify_infos[('cn.', 'IN')]
+ info = self._notify._notify_infos[('example.net.', 'IN')]
self._notify._notify_next_target(info)
self.assertEqual(0, info.notify_try_num)
self.assertEqual(info.get_current_notify_target(), ('1.1.1.1', 5353))
@@ -153,101 +186,101 @@ class TestNotifyOut(unittest.TestCase):
self.assertEqual(2, self._notify.notify_num)
self.assertEqual(1, len(self._notify._waiting_zones))
- com_info = self._notify._notify_infos[('com.', 'IN')]
- self._notify._notify_next_target(com_info)
+ example_com_info = self._notify._notify_infos[('example.com.', 'IN')]
+ self._notify._notify_next_target(example_com_info)
self.assertEqual(2, self._notify.notify_num)
self.assertEqual(2, len(self._notify._notifying_zones))
-
+
def test_handle_notify_reply(self):
self.assertEqual(notify_out._BAD_REPLY_PACKET, self._notify._handle_notify_reply(None, b'badmsg'))
- com_info = self._notify._notify_infos[('com.', 'IN')]
- com_info.notify_msg_id = 0X2f18
+ example_com_info = self._notify._notify_infos[('example.com.', 'IN')]
+ example_com_info.notify_msg_id = 0X2f18
# test with right notify reply message
- data = b'\x2f\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03com\x00\x00\x06\x00\x01'
- self.assertEqual(notify_out._REPLY_OK, self._notify._handle_notify_reply(com_info, data))
+ data = b'\x2f\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03com\x00\x00\x06\x00\x01'
+ self.assertEqual(notify_out._REPLY_OK, self._notify._handle_notify_reply(example_com_info, data))
# test with unright query id
- data = b'\x2e\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03com\x00\x00\x06\x00\x01'
- self.assertEqual(notify_out._BAD_QUERY_ID, self._notify._handle_notify_reply(com_info, data))
+ data = b'\x2e\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03com\x00\x00\x06\x00\x01'
+ self.assertEqual(notify_out._BAD_QUERY_ID, self._notify._handle_notify_reply(example_com_info, data))
# test with unright query name
- data = b'\x2f\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x02cn\x00\x00\x06\x00\x01'
- self.assertEqual(notify_out._BAD_QUERY_NAME, self._notify._handle_notify_reply(com_info, data))
+ data = b'\x2f\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03net\x00\x00\x06\x00\x01'
+ self.assertEqual(notify_out._BAD_QUERY_NAME, self._notify._handle_notify_reply(example_com_info, data))
# test with unright opcode
- data = b'\x2f\x18\x80\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03com\x00\x00\x06\x00\x01'
- self.assertEqual(notify_out._BAD_OPCODE, self._notify._handle_notify_reply(com_info, data))
+ data = b'\x2f\x18\x80\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03com\x00\x00\x06\x00\x01'
+ self.assertEqual(notify_out._BAD_OPCODE, self._notify._handle_notify_reply(example_com_info, data))
# test with unright qr
- data = b'\x2f\x18\x10\x10\x00\x01\x00\x00\x00\x00\x00\x00\x03com\x00\x00\x06\x00\x01'
- self.assertEqual(notify_out._BAD_QR, self._notify._handle_notify_reply(com_info, data))
+ data = b'\x2f\x18\x10\x10\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03com\x00\x00\x06\x00\x01'
+ self.assertEqual(notify_out._BAD_QR, self._notify._handle_notify_reply(example_com_info, data))
def test_send_notify_message_udp(self):
- com_info = self._notify._notify_infos[('cn.', 'IN')]
- com_info.prepare_notify_out()
- ret = self._notify._send_notify_message_udp(com_info, ('1.1.1.1', 53))
+ example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
+ example_com_info.prepare_notify_out()
+ ret = self._notify._send_notify_message_udp(example_com_info, ('1.1.1.1', 53))
self.assertTrue(ret)
def test_zone_notify_handler(self):
old_send_msg = self._notify._send_notify_message_udp
- def _fake_send_notify_message_udp(va1, va2):
+ def _fake_send_notify_message_udp(va1, va2):
pass
self._notify._send_notify_message_udp = _fake_send_notify_message_udp
- self._notify.send_notify('cn.')
- self._notify.send_notify('com.')
+ self._notify.send_notify('example.net.')
+ self._notify.send_notify('example.com.')
notify_out._MAX_NOTIFY_NUM = 2
- self._notify.send_notify('org.')
+ self._notify.send_notify('example.org.')
- cn_info = self._notify._notify_infos[('cn.', 'IN')]
- cn_info.prepare_notify_out()
+ example_net_info = self._notify._notify_infos[('example.net.', 'IN')]
+ example_net_info.prepare_notify_out()
- cn_info.notify_try_num = 2
- self._notify._zone_notify_handler(cn_info, notify_out._EVENT_TIMEOUT)
- self.assertEqual(3, cn_info.notify_try_num)
+ example_net_info.notify_try_num = 2
+ self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_TIMEOUT)
+ self.assertEqual(3, example_net_info.notify_try_num)
- time1 = cn_info.notify_timeout
- self._notify._zone_notify_handler(cn_info, notify_out._EVENT_TIMEOUT)
- self.assertEqual(4, cn_info.notify_try_num)
- self.assertGreater(cn_info.notify_timeout, time1 + 2) # bigger than 2 seconds
+ time1 = example_net_info.notify_timeout
+ self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_TIMEOUT)
+ self.assertEqual(4, example_net_info.notify_try_num)
+ self.assertGreater(example_net_info.notify_timeout, time1 + 2) # bigger than 2 seconds
- cur_tgt = cn_info._notify_current
- cn_info.notify_try_num = notify_out._MAX_NOTIFY_TRY_NUM
- self._notify._zone_notify_handler(cn_info, notify_out._EVENT_NONE)
- self.assertNotEqual(cur_tgt, cn_info._notify_current)
+ cur_tgt = example_net_info._notify_current
+ example_net_info.notify_try_num = notify_out._MAX_NOTIFY_TRY_NUM
+ self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_NONE)
+ self.assertNotEqual(cur_tgt, example_net_info._notify_current)
- def _cn_data_reader(self):
+ def _example_net_data_reader(self):
zone_data = [
- ('cn.', '1000', 'IN', 'SOA', 'a.dns.cn. mail.cn. 1 1 1 1 1'),
- ('cn.', '1000', 'IN', 'NS', 'a.dns.cn.'),
- ('cn.', '1000', 'IN', 'NS', 'b.dns.cn.'),
- ('cn.', '1000', 'IN', 'NS', 'c.dns.cn.'),
- ('a.dns.cn.', '1000', 'IN', 'A', '1.1.1.1'),
- ('a.dns.cn.', '1000', 'IN', 'AAAA', '2:2::2:2'),
- ('b.dns.cn.', '1000', 'IN', 'A', '3.3.3.3'),
- ('b.dns.cn.', '1000', 'IN', 'AAAA', '4:4::4:4'),
- ('b.dns.cn.', '1000', 'IN', 'AAAA', '5:5::5:5'),
- ('c.dns.cn.', '1000', 'IN', 'A', '6.6.6.6'),
- ('c.dns.cn.', '1000', 'IN', 'A', '7.7.7.7'),
- ('c.dns.cn.', '1000', 'IN', 'AAAA', '8:8::8:8')]
+ ('example.net.', '1000', 'IN', 'SOA', 'a.dns.example.net. mail.example.net. 1 1 1 1 1'),
+ ('example.net.', '1000', 'IN', 'NS', 'a.dns.example.net.'),
+ ('example.net.', '1000', 'IN', 'NS', 'b.dns.example.net.'),
+ ('example.net.', '1000', 'IN', 'NS', 'c.dns.example.net.'),
+ ('a.dns.example.net.', '1000', 'IN', 'A', '1.1.1.1'),
+ ('a.dns.example.net.', '1000', 'IN', 'AAAA', '2:2::2:2'),
+ ('b.dns.example.net.', '1000', 'IN', 'A', '3.3.3.3'),
+ ('b.dns.example.net.', '1000', 'IN', 'AAAA', '4:4::4:4'),
+ ('b.dns.example.net.', '1000', 'IN', 'AAAA', '5:5::5:5'),
+ ('c.dns.example.net.', '1000', 'IN', 'A', '6.6.6.6'),
+ ('c.dns.example.net.', '1000', 'IN', 'A', '7.7.7.7'),
+ ('c.dns.example.net.', '1000', 'IN', 'AAAA', '8:8::8:8')]
for item in zone_data:
yield item
- def _com_data_reader(self):
+ def _example_com_data_reader(self):
zone_data = [
- ('com.', '1000', 'IN', 'SOA', 'a.dns.com. mail.com. 1 1 1 1 1'),
- ('com.', '1000', 'IN', 'NS', 'a.dns.com.'),
- ('com.', '1000', 'IN', 'NS', 'b.dns.com.'),
- ('com.', '1000', 'IN', 'NS', 'c.dns.com.'),
- ('a.dns.com.', '1000', 'IN', 'A', '1.1.1.1'),
- ('b.dns.com.', '1000', 'IN', 'A', '3.3.3.3'),
- ('b.dns.com.', '1000', 'IN', 'AAAA', '4:4::4:4'),
- ('b.dns.com.', '1000', 'IN', 'AAAA', '5:5::5:5')]
+ ('example.com.', '1000', 'IN', 'SOA', 'a.dns.example.com. mail.example.com. 1 1 1 1 1'),
+ ('example.com.', '1000', 'IN', 'NS', 'a.dns.example.com.'),
+ ('example.com.', '1000', 'IN', 'NS', 'b.dns.example.com.'),
+ ('example.com.', '1000', 'IN', 'NS', 'c.dns.example.com.'),
+ ('a.dns.example.com.', '1000', 'IN', 'A', '1.1.1.1'),
+ ('b.dns.example.com.', '1000', 'IN', 'A', '3.3.3.3'),
+ ('b.dns.example.com.', '1000', 'IN', 'AAAA', '4:4::4:4'),
+ ('b.dns.example.com.', '1000', 'IN', 'AAAA', '5:5::5:5')]
for item in zone_data:
yield item
def test_get_notify_slaves_from_ns(self):
- records = self._notify._get_notify_slaves_from_ns('cn.')
+ records = self._notify._get_notify_slaves_from_ns('example.net.')
self.assertEqual(6, len(records))
self.assertEqual('8:8::8:8', records[5])
self.assertEqual('7.7.7.7', records[4])
@@ -256,36 +289,36 @@ class TestNotifyOut(unittest.TestCase):
self.assertEqual('4:4::4:4', records[1])
self.assertEqual('3.3.3.3', records[0])
- records = self._notify._get_notify_slaves_from_ns('com.')
+ records = self._notify._get_notify_slaves_from_ns('example.com.')
self.assertEqual(3, len(records))
self.assertEqual('5:5::5:5', records[2])
self.assertEqual('4:4::4:4', records[1])
self.assertEqual('3.3.3.3', records[0])
-
+
def test_init_notify_out(self):
self._notify._init_notify_out(self._db_file.name)
- self.assertListEqual([('3.3.3.3', 53), ('4:4::4:4', 53), ('5:5::5:5', 53)],
- self._notify._notify_infos[('com.', 'IN')].notify_slaves)
-
+ self.assertListEqual([('3.3.3.3', 53), ('4:4::4:4', 53), ('5:5::5:5', 53)],
+ self._notify._notify_infos[('example.com.', 'IN')].notify_slaves)
+
def test_prepare_select_info(self):
timeout, valid_fds, notifying_zones = self._notify._prepare_select_info()
self.assertEqual(notify_out._IDLE_SLEEP_TIME, timeout)
self.assertListEqual([], valid_fds)
- self._notify._notify_infos[('cn.', 'IN')]._sock = 1
- self._notify._notify_infos[('cn.', 'IN')].notify_timeout = time.time() + 5
+ self._notify._notify_infos[('example.net.', 'IN')]._sock = 1
+ self._notify._notify_infos[('example.net.', 'IN')].notify_timeout = time.time() + 5
timeout, valid_fds, notifying_zones = self._notify._prepare_select_info()
self.assertGreater(timeout, 0)
self.assertListEqual([1], valid_fds)
- self._notify._notify_infos[('cn.', 'IN')]._sock = 1
- self._notify._notify_infos[('cn.', 'IN')].notify_timeout = time.time() - 5
+ self._notify._notify_infos[('example.net.', 'IN')]._sock = 1
+ self._notify._notify_infos[('example.net.', 'IN')].notify_timeout = time.time() - 5
timeout, valid_fds, notifying_zones = self._notify._prepare_select_info()
self.assertEqual(timeout, 0)
self.assertListEqual([1], valid_fds)
- self._notify._notify_infos[('com.', 'IN')]._sock = 2
- self._notify._notify_infos[('com.', 'IN')].notify_timeout = time.time() + 5
+ self._notify._notify_infos[('example.com.', 'IN')]._sock = 2
+ self._notify._notify_infos[('example.com.', 'IN')].notify_timeout = time.time() + 5
timeout, valid_fds, notifying_zones = self._notify._prepare_select_info()
self.assertEqual(timeout, 0)
self.assertListEqual([2, 1], valid_fds)
diff --git a/src/lib/python/isc/util/socketserver_mixin.py b/src/lib/python/isc/util/socketserver_mixin.py
index fb5f9a2..e954fe1 100644
--- a/src/lib/python/isc/util/socketserver_mixin.py
+++ b/src/lib/python/isc/util/socketserver_mixin.py
@@ -64,7 +64,7 @@ class NoPollMixIn:
called in anther thread. Note, parameter 'poll_interval' is
just used for interface compatibility; it's never used in this
function.
- '''
+ '''
while True:
# block until the self.socket or self.__read_sock is readable
try:
@@ -74,11 +74,11 @@ class NoPollMixIn:
continue
else:
break
-
+
if self.__read_sock in r:
break
else:
- self._handle_request_noblock()
+ self._handle_request_noblock();
self._is_shut_down.set()
diff --git a/src/lib/python/isc/util/tests/socketserver_mixin_test.py b/src/lib/python/isc/util/tests/socketserver_mixin_test.py
index 61bc248..a6686d8 100644
--- a/src/lib/python/isc/util/tests/socketserver_mixin_test.py
+++ b/src/lib/python/isc/util/tests/socketserver_mixin_test.py
@@ -25,7 +25,7 @@ class MyHandler(socketserver.BaseRequestHandler):
data = self.request.recv(20)
self.request.send(data)
-class MyServer(NoPollMixIn,
+class MyServer(NoPollMixIn,
socketserver.ThreadingMixIn,
socketserver.TCPServer):
diff --git a/src/lib/python/isc/utils/Makefile.am b/src/lib/python/isc/utils/Makefile.am
deleted file mode 100644
index 65a39ad..0000000
--- a/src/lib/python/isc/utils/Makefile.am
+++ /dev/null
@@ -1,5 +0,0 @@
-SUBDIRS = tests
-
-python_PYTHON = __init__.py process.py
-
-pythondir = $(pyexecdir)/isc/utils
diff --git a/src/lib/python/isc/utils/__init__.py b/src/lib/python/isc/utils/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/lib/python/isc/utils/process.py b/src/lib/python/isc/utils/process.py
deleted file mode 100644
index 25775af..0000000
--- a/src/lib/python/isc/utils/process.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright (C) 2010 CZ NIC
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# 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.
-
-"""
-Module to manipulate the python processes.
-
-It contains only function to rename the process, which is currently
-wrapper around setproctitle library. Does not fail if the setproctitle
-module is missing, but does nothing in that case.
-"""
-try:
- from setproctitle import setproctitle
-except ImportError:
- def setproctitle(_): pass
-import sys
-import os.path
-
-"""
-Rename the current process to given name (so it can be found in ps).
-If name is None, use zero'th command line argument.
-"""
-def rename(name=None):
- if name is None:
- name = os.path.basename(sys.argv[0])
- setproctitle(name)
diff --git a/src/lib/python/isc/utils/tests/Makefile.am b/src/lib/python/isc/utils/tests/Makefile.am
deleted file mode 100644
index e58b5b6..0000000
--- a/src/lib/python/isc/utils/tests/Makefile.am
+++ /dev/null
@@ -1,16 +0,0 @@
-PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = process_test.py
-EXTRA_DIST = $(PYTESTS)
-
-# test using command-line arguments, so use check-local target instead of TESTS
-check-local:
-if ENABLE_PYTHON_COVERAGE
- touch $(abs_top_srcdir)/.coverage
- rm -f .coverage
- ${LN_S} $(abs_top_srcdir)/.coverage .coverage
-endif
- for pytest in $(PYTESTS) ; do \
- echo Running test: $$pytest ; \
- env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
- $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
- done
diff --git a/src/lib/python/isc/utils/tests/process_test.py b/src/lib/python/isc/utils/tests/process_test.py
deleted file mode 100644
index 0e7c8b1..0000000
--- a/src/lib/python/isc/utils/tests/process_test.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (C) 2010 CZ NIC
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# 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.utils.process."""
-import unittest
-import isc.utils.process
-run_tests = True
-try:
- import setproctitle
-except ImportError:
- run_tests = False
-
-class TestRename(unittest.TestCase):
- """Testcase for isc.process.rename."""
- def __get_self_name(self):
- return setproctitle.getproctitle()
-
- @unittest.skipIf(not run_tests, "Setproctitle not installed, not testing")
- def test_rename(self):
- """Test if the renaming function works."""
- isc.utils.process.rename("rename-test")
- self.assertEqual("rename-test", self.__get_self_name())
- isc.utils.process.rename()
- self.assertEqual("process_test.py", self.__get_self_name())
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/src/lib/resolve/Makefile.am b/src/lib/resolve/Makefile.am
index 8edb594..0b29da4 100644
--- a/src/lib/resolve/Makefile.am
+++ b/src/lib/resolve/Makefile.am
@@ -10,8 +10,20 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
CLEANFILES = *.gcno *.gcda
lib_LTLIBRARIES = libresolve.la
-libresolve_la_SOURCES = resolve.h
+libresolve_la_SOURCES = resolve.h resolve.cc
libresolve_la_SOURCES += resolver_interface.h
libresolve_la_SOURCES += resolver_callback.h resolver_callback.cc
+libresolve_la_SOURCES += response_classifier.cc response_classifier.h
+libresolve_la_SOURCES += recursive_query.cc recursive_query.h
libresolve_la_LIBADD = $(top_builddir)/src/lib/dns/libdns++.la
libresolve_la_LIBADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# B10_CXXFLAGS)
+libresolve_la_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_CLANGPP
+# For clang++, we need to turn off -Werror completely.
+libresolve_la_CXXFLAGS += -Wno-error
+endif
+libresolve_la_CPPFLAGS = $(AM_CPPFLAGS)
+
diff --git a/src/lib/resolve/recursive_query.cc b/src/lib/resolve/recursive_query.cc
new file mode 100644
index 0000000..eee98ca
--- /dev/null
+++ b/src/lib/resolve/recursive_query.cc
@@ -0,0 +1,757 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+
+#include <boost/lexical_cast.hpp>
+#include <boost/bind.hpp>
+
+#include <config.h>
+
+#include <log/dummylog.h>
+
+#include <dns/question.h>
+#include <dns/message.h>
+#include <dns/opcode.h>
+
+#include <resolve/resolve.h>
+#include <cache/resolver_cache.h>
+#include <nsas/address_request_callback.h>
+#include <nsas/nameserver_address.h>
+
+#include <asio.hpp>
+#include <asiolink/dns_service.h>
+#include <asiolink/io_fetch.h>
+#include <asiolink/io_service.h>
+#include <resolve/recursive_query.h>
+
+using isc::log::dlog;
+using namespace isc::dns;
+
+namespace asiolink {
+
+typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
+
+// Here we do not use the typedef above, as the SunStudio compiler
+// mishandles this in its name mangling, and wouldn't compile.
+// We can probably use a typedef, but need to move it to a central
+// location and use it consistently.
+RecursiveQuery::RecursiveQuery(DNSService& dns_service,
+ isc::nsas::NameserverAddressStore& nsas,
+ isc::cache::ResolverCache& cache,
+ const std::vector<std::pair<std::string, uint16_t> >& upstream,
+ const std::vector<std::pair<std::string, uint16_t> >& upstream_root,
+ int query_timeout, int client_timeout, int lookup_timeout,
+ unsigned retries) :
+ dns_service_(dns_service),
+ nsas_(nsas), cache_(cache),
+ upstream_(new AddressVector(upstream)),
+ upstream_root_(new AddressVector(upstream_root)),
+ test_server_("", 0),
+ query_timeout_(query_timeout), client_timeout_(client_timeout),
+ lookup_timeout_(lookup_timeout), retries_(retries)
+{
+}
+
+// Set the test server - only used for unit testing.
+
+void
+RecursiveQuery::setTestServer(const std::string& address, uint16_t port) {
+ dlog("Setting test server to " + address + "(" +
+ boost::lexical_cast<std::string>(port) + ")");
+ test_server_.first = address;
+ test_server_.second = port;
+}
+
+
+namespace {
+
+typedef std::pair<std::string, uint16_t> addr_t;
+
+/*
+ * This is a query in progress. When a new query is made, this one holds
+ * the context information about it, like how many times we are allowed
+ * to retry on failure, what to do when we succeed, etc.
+ *
+ * Used by RecursiveQuery::sendQuery.
+ */
+class RunningQuery : public IOFetch::Callback {
+
+class ResolverNSASCallback : public isc::nsas::AddressRequestCallback {
+public:
+ ResolverNSASCallback(RunningQuery* rq) : rq_(rq) {}
+
+ void success(const isc::nsas::NameserverAddress& address) {
+ dlog("Found a nameserver, sending query to " + address.getAddress().toText());
+ rq_->nsasCallbackCalled();
+ rq_->sendTo(address);
+ }
+
+ void unreachable() {
+ dlog("Nameservers unreachable");
+ // Drop query or send servfail?
+ rq_->nsasCallbackCalled();
+ rq_->makeSERVFAIL();
+ rq_->callCallback(true);
+ rq_->stop();
+ }
+
+private:
+ RunningQuery* rq_;
+};
+
+
+private:
+ // The io service to handle async calls
+ IOService& io_;
+
+ // Info for (re)sending the query (the question and destination)
+ Question question_;
+
+ // This is where we build and store our final answer
+ MessagePtr answer_message_;
+
+ // currently we use upstream as the current list of NS records
+ // we should differentiate between forwarding and resolving
+ boost::shared_ptr<AddressVector> upstream_;
+
+ // Test server - only used for testing. This takes precedence over all
+ // other servers if the port is non-zero.
+ std::pair<std::string, uint16_t> test_server_;
+
+ // Buffer to store the intermediate results.
+ OutputBufferPtr buffer_;
+
+ // The callback will be called when we have either decided we
+ // are done, or when we give up
+ isc::resolve::ResolverInterface::CallbackPtr resolvercallback_;
+
+ // Protocol used for the last query. This is set to IOFetch::UDP when a
+ // new upstream query is initiated, and changed to IOFetch::TCP if a
+ // packet is returned with the TC bit set. It is stored here to detect the
+ // case of a TCP packet being returned with the TC bit set.
+ IOFetch::Protocol protocol_;
+
+ // To prevent both unreasonably long cname chains and cname loops,
+ // we simply keep a counter of the number of CNAMEs we have
+ // followed so far (and error if it exceeds RESOLVER_MAX_CNAME_CHAIN
+ // from lib/resolve/response_classifier.h)
+ unsigned cname_count_;
+
+ /*
+ * TODO Do something more clever with timeouts. In the long term, some
+ * computation of average RTT, increase with each retry, etc.
+ */
+ // Timeout information for outgoing queries
+ int query_timeout_;
+ unsigned retries_;
+
+ // normal query state
+
+ // Update the question that will be sent to the server
+ void setQuestion(const Question& new_question) {
+ question_ = new_question;
+ }
+
+ // TODO: replace by our wrapper
+ asio::deadline_timer client_timer;
+ asio::deadline_timer lookup_timer;
+
+ // If we timed out ourselves (lookup timeout), stop issuing queries
+ bool done_;
+
+ // If we have a client timeout, we call back with a failure message,
+ // but we do not stop yet. We use this variable to make sure we
+ // don't call back a second time later
+ bool callback_called_;
+
+ // Reference to our NSAS
+ isc::nsas::NameserverAddressStore& nsas_;
+
+ // Reference to our cache
+ isc::cache::ResolverCache& cache_;
+
+ // the 'current' zone we are in (i.e.) we start out at the root,
+ // and for each delegation this gets updated with the zone the
+ // delegation points to.
+ // TODO: make this a Name (it is a string right now because most
+ // of the call we use it in take a string, we need update those
+ // too).
+ std::string cur_zone_;
+
+ // This is the handler we pass on to the NSAS; it is called when
+ // the NSAS has an address for us to query
+ boost::shared_ptr<ResolverNSASCallback> nsas_callback_;
+
+ // this is set to true if we have asked the nsas to give us
+ // an address and we are waiting for it to call us back.
+ // We use is to cancel the outstanding callback in case we
+ // have a lookup timeout and decide to give up
+ bool nsas_callback_out_;
+
+ // This is the nameserver we have an outstanding query to.
+ // It is used to update the RTT once the query returns
+ isc::nsas::NameserverAddress current_ns_address;
+
+ // The moment in time we sent a query to the nameserver above.
+ struct timeval current_ns_qsent_time;
+
+ // RunningQuery deletes itself when it is done. In order for us
+ // to do this safely, we must make sure that there are no events
+ // that might call back to it. There are two types of events in
+ // this sense; the timers we set ourselves (lookup and client),
+ // and outstanding queries to nameservers. When each of these is
+ // started, we increase this value. When they fire, it is decreased
+ // again. We cannot delete ourselves until this value is back to 0.
+ //
+ // Note that the NSAS callback is *not* seen as an outstanding
+ // event; we can cancel the NSAS callback safely.
+ size_t outstanding_events_;
+
+ // perform a single lookup; first we check the cache to see
+ // if we have a response for our query stored already. if
+ // so, call handlerecursiveresponse(), if not, we call send()
+ void doLookup() {
+ dlog("doLookup: try cache");
+ Message cached_message(Message::RENDER);
+ isc::resolve::initResponseMessage(question_, cached_message);
+ if (cache_.lookup(question_.getName(), question_.getType(),
+ question_.getClass(), cached_message)) {
+ dlog("Message found in cache, continuing with that");
+ // Should these be set by the cache too?
+ cached_message.setOpcode(Opcode::QUERY());
+ cached_message.setRcode(Rcode::NOERROR());
+ cached_message.setHeaderFlag(Message::HEADERFLAG_QR);
+ if (handleRecursiveAnswer(cached_message)) {
+ callCallback(true);
+ stop();
+ }
+ } else {
+ cur_zone_ = ".";
+ send();
+ }
+
+ }
+
+ // Send the current question to the given nameserver address
+ void sendTo(const isc::nsas::NameserverAddress& address) {
+ // We need to keep track of the Address, so that we can update
+ // the RTT
+ current_ns_address = address;
+ gettimeofday(¤t_ns_qsent_time, NULL);
+ ++outstanding_events_;
+ IOFetch query(protocol_, io_, question_,
+ current_ns_address.getAddress(),
+ 53, buffer_, this,
+ query_timeout_);
+ io_.get_io_service().post(query);
+ }
+
+ // 'general' send; if we are in forwarder mode, send a query to
+ // a random nameserver in our forwarders list. If we are in
+ // recursive mode, ask the NSAS to give us an address.
+ void send(IOFetch::Protocol protocol = IOFetch::UDP) {
+ // If are in forwarder mode, send it to a random
+ // forwarder. If not, ask the NSAS for an address
+ const int uc = upstream_->size();
+ protocol_ = protocol; // Store protocol being used for this
+ if (test_server_.second != 0) {
+ dlog("Sending upstream query (" + question_.toText() +
+ ") to test server at " + test_server_.first);
+ ++outstanding_events_;
+ IOFetch query(protocol, io_, question_,
+ test_server_.first,
+ test_server_.second, buffer_, this,
+ query_timeout_);
+ io_.get_io_service().post(query);
+ } else if (uc > 0) {
+ // TODO: use boost, or rand()-utility function we provide
+ int serverIndex = rand() % uc;
+ dlog("Sending upstream query (" + question_.toText() +
+ ") to " + upstream_->at(serverIndex).first);
+ ++outstanding_events_;
+ IOFetch query(protocol, io_, question_,
+ upstream_->at(serverIndex).first,
+ upstream_->at(serverIndex).second, buffer_, this,
+ query_timeout_);
+ io_.get_io_service().post(query);
+ } else {
+ // Ask the NSAS for an address for the current zone,
+ // the callback will call the actual sendTo()
+ dlog("Look up nameserver for " + cur_zone_ + " in NSAS");
+ // Can we have multiple calls to nsas_out? Let's assume not
+ // for now
+ assert(!nsas_callback_out_);
+ nsas_callback_out_ = true;
+ nsas_.lookup(cur_zone_, question_.getClass(), nsas_callback_);
+ }
+ }
+
+ // Called by our NSAS callback handler so we know we do not have
+ // an outstanding NSAS call anymore.
+ void nsasCallbackCalled() {
+ nsas_callback_out_ = false;
+ }
+
+ // This function is called by operator() and lookup();
+ // We have an answer either from a nameserver or the cache, and
+ // we do not know yet if this is a final answer we can send back or
+ // that more recursive processing needs to be done.
+ // Depending on the content, we go on recursing or return
+ //
+ // This method also updates the cache, depending on the content
+ // of the message
+ //
+ // returns true if we are done (either we have an answer or an
+ // error message)
+ // returns false if we are not done
+ bool handleRecursiveAnswer(const Message& incoming) {
+ dlog("Handle response");
+ // In case we get a CNAME, we store the target
+ // here (classify() will set it when it walks through
+ // the cname chain to verify it).
+ Name cname_target(question_.getName());
+
+ isc::resolve::ResponseClassifier::Category category =
+ isc::resolve::ResponseClassifier::classify(
+ question_, incoming, cname_target, cname_count_);
+
+ bool found_ns_address = false;
+
+ switch (category) {
+ case isc::resolve::ResponseClassifier::ANSWER:
+ case isc::resolve::ResponseClassifier::ANSWERCNAME:
+ // Done. copy and return.
+ dlog("Response is an answer");
+ isc::resolve::copyResponseMessage(incoming, answer_message_);
+ cache_.update(*answer_message_);
+ return true;
+ break;
+ case isc::resolve::ResponseClassifier::CNAME:
+ dlog("Response is CNAME!");
+ // (unfinished) CNAME. We set our question_ to the CNAME
+ // target, then start over at the beginning (for now, that
+ // is, we reset our 'current servers' to the root servers).
+ if (cname_count_ >= RESOLVER_MAX_CNAME_CHAIN) {
+ // just give up
+ dlog("CNAME chain too long");
+ makeSERVFAIL();
+ return true;
+ }
+
+ answer_message_->appendSection(Message::SECTION_ANSWER,
+ incoming);
+
+ question_ = Question(cname_target, question_.getClass(),
+ question_.getType());
+
+ dlog("Following CNAME chain to " + question_.toText());
+ doLookup();
+ return false;
+ break;
+ case isc::resolve::ResponseClassifier::NXDOMAIN:
+ case isc::resolve::ResponseClassifier::NXRRSET:
+ dlog("Response is NXDOMAIN or NXRRSET");
+ // NXDOMAIN, just copy and return.
+ dlog(incoming.toText());
+ isc::resolve::copyResponseMessage(incoming, answer_message_);
+ // no negcache yet
+ //cache_.update(*answer_message_);
+ return true;
+ break;
+ case isc::resolve::ResponseClassifier::REFERRAL:
+ dlog("Response is referral");
+ cache_.update(incoming);
+ // Referral. For now we just take the first glue address
+ // we find and continue with that
+
+ // auth section should have at least one RRset
+ // and one of them should be an NS (otherwise
+ // classifier should have error'd)
+ // TODO: should we check if it really is subzone?
+ for (RRsetIterator rrsi = incoming.beginSection(Message::SECTION_AUTHORITY);
+ rrsi != incoming.endSection(Message::SECTION_AUTHORITY) && !found_ns_address;
+ ++rrsi) {
+ ConstRRsetPtr rrs = *rrsi;
+ if (rrs->getType() == RRType::NS()) {
+ // TODO: make cur_zone_ a Name instead of a string
+ // (this requires a few API changes in related
+ // libraries, so as not to need many conversions)
+ cur_zone_ = rrs->getName().toText();
+ dlog("Referred to zone " + cur_zone_);
+ found_ns_address = true;
+ break;
+ }
+ }
+
+ if (found_ns_address) {
+ // next resolver round
+ // we do NOT use doLookup() here, but send() (i.e. we
+ // skip the cache), since if we had the final answer
+ // instead of a delegation cached, we would have been
+ // there by now.
+ send();
+ return false;
+ } else {
+ dlog("No NS RRset in referral?");
+ // TODO this will result in answering with the delegation. oh well
+ isc::resolve::copyResponseMessage(incoming, answer_message_);
+ return true;
+ }
+ break;
+ case isc::resolve::ResponseClassifier::TRUNCATED:
+ // Truncated packet. If the protocol we used for the last one is
+ // UDP, re-query using TCP. Otherwise regard it as an error.
+ if (protocol_ == IOFetch::UDP) {
+ dlog("Response truncated, re-querying over TCP");
+ send(IOFetch::TCP);
+ return false;
+ }
+ // Was a TCP query so we have received a packet over TCP with the TC
+ // bit set: drop through to common error processing.
+ // TODO: Can we use what we have received instead of discarding it?
+
+ case isc::resolve::ResponseClassifier::EMPTY:
+ case isc::resolve::ResponseClassifier::EXTRADATA:
+ case isc::resolve::ResponseClassifier::INVNAMCLASS:
+ case isc::resolve::ResponseClassifier::INVTYPE:
+ case isc::resolve::ResponseClassifier::MISMATQUEST:
+ case isc::resolve::ResponseClassifier::MULTICLASS:
+ case isc::resolve::ResponseClassifier::NOTONEQUEST:
+ case isc::resolve::ResponseClassifier::NOTRESPONSE:
+ case isc::resolve::ResponseClassifier::NOTSINGLE:
+ case isc::resolve::ResponseClassifier::OPCODE:
+ case isc::resolve::ResponseClassifier::RCODE:
+ // Should we try a different server rather than SERVFAIL?
+ makeSERVFAIL();
+ return true;
+ break;
+ }
+
+ // Since we do not have a default in the switch above,
+ // the compiler should have errored on any missing case
+ // statements.
+ assert(false);
+ return true;
+ }
+
+public:
+ RunningQuery(IOService& io,
+ const Question& question,
+ MessagePtr answer_message,
+ boost::shared_ptr<AddressVector> upstream,
+ std::pair<std::string, uint16_t>& test_server,
+ OutputBufferPtr buffer,
+ isc::resolve::ResolverInterface::CallbackPtr cb,
+ int query_timeout, int client_timeout, int lookup_timeout,
+ unsigned retries,
+ isc::nsas::NameserverAddressStore& nsas,
+ isc::cache::ResolverCache& cache) :
+ io_(io),
+ question_(question),
+ answer_message_(answer_message),
+ upstream_(upstream),
+ test_server_(test_server),
+ buffer_(buffer),
+ resolvercallback_(cb),
+ protocol_(IOFetch::UDP),
+ cname_count_(0),
+ query_timeout_(query_timeout),
+ retries_(retries),
+ client_timer(io.get_io_service()),
+ lookup_timer(io.get_io_service()),
+ done_(false),
+ callback_called_(false),
+ nsas_(nsas),
+ cache_(cache),
+ nsas_callback_(new ResolverNSASCallback(this)),
+ nsas_callback_out_(false),
+ outstanding_events_(0)
+ {
+ // Setup the timer to stop trying (lookup_timeout)
+ if (lookup_timeout >= 0) {
+ lookup_timer.expires_from_now(
+ boost::posix_time::milliseconds(lookup_timeout));
+ ++outstanding_events_;
+ lookup_timer.async_wait(boost::bind(&RunningQuery::lookupTimeout, this));
+ }
+
+ // Setup the timer to send an answer (client_timeout)
+ if (client_timeout >= 0) {
+ client_timer.expires_from_now(
+ boost::posix_time::milliseconds(client_timeout));
+ ++outstanding_events_;
+ client_timer.async_wait(boost::bind(&RunningQuery::clientTimeout, this));
+ }
+
+ doLookup();
+ }
+
+ // called if we have a lookup timeout; if our callback has
+ // not been called, call it now. Then stop.
+ void lookupTimeout() {
+ if (!callback_called_) {
+ makeSERVFAIL();
+ callCallback(true);
+ }
+ assert(outstanding_events_ > 0);
+ --outstanding_events_;
+ stop();
+ }
+
+ // called if we have a client timeout; if our callback has
+ // not been called, call it now. But do not stop.
+ void clientTimeout() {
+ if (!callback_called_) {
+ makeSERVFAIL();
+ callCallback(true);
+ }
+ assert(outstanding_events_ > 0);
+ --outstanding_events_;
+ if (outstanding_events_ == 0) {
+ stop();
+ }
+ }
+
+ // If the callback has not been called yet, call it now
+ // If success is true, we call 'success' with our answer_message
+ // If it is false, we call failure()
+ void callCallback(bool success) {
+ if (!callback_called_) {
+ callback_called_ = true;
+
+ // There are two types of messages we could store in the
+ // cache;
+ // 1. answers to our fetches from authoritative servers,
+ // exactly as we receive them, and
+ // 2. answers to queries we received from clients, which
+ // have received additional processing (following CNAME
+ // chains, for instance)
+ //
+ // Doing only the first would mean we would have to re-do
+ // processing when we get data from our cache, and doing
+ // only the second would miss out on the side-effect of
+ // having nameserver data in our cache.
+ //
+ // So right now we do both. Since the cache (currently)
+ // stores Messages on their question section only, this
+ // does mean that we overwrite the messages we stored in
+ // the previous iteration if we are following a delegation.
+ if (success) {
+ resolvercallback_->success(answer_message_);
+ } else {
+ resolvercallback_->failure();
+ }
+ }
+ }
+
+ // We are done. If there are no more outstanding events, we delete
+ // ourselves. If there are any, we do not.
+ void stop() {
+ done_ = true;
+ if (nsas_callback_out_) {
+ nsas_.cancel(cur_zone_, question_.getClass(), nsas_callback_);
+ nsas_callback_out_ = false;
+ }
+ client_timer.cancel();
+ lookup_timer.cancel();
+ if (outstanding_events_ > 0) {
+ return;
+ } else {
+ delete this;
+ }
+ }
+
+ // This function is used as callback from DNSQuery.
+ virtual void operator()(IOFetch::Result result) {
+ // XXX is this the place for TCP retry?
+ assert(outstanding_events_ > 0);
+ --outstanding_events_;
+
+ if (!done_ && result != IOFetch::TIME_OUT) {
+ // we got an answer
+
+ // Update the NSAS with the time it took
+ struct timeval cur_time;
+ gettimeofday(&cur_time, NULL);
+ uint32_t rtt;
+ if (cur_time.tv_sec >= current_ns_qsent_time.tv_sec ||
+ cur_time.tv_usec > current_ns_qsent_time.tv_usec) {
+ rtt = 1000 * (cur_time.tv_sec - current_ns_qsent_time.tv_sec);
+ rtt += (cur_time.tv_usec - current_ns_qsent_time.tv_usec) / 1000;
+ } else {
+ rtt = 1;
+ }
+
+ dlog("RTT: " + boost::lexical_cast<std::string>(rtt));
+ current_ns_address.updateRTT(rtt);
+
+ Message incoming(Message::PARSE);
+ InputBuffer ibuf(buffer_->getData(), buffer_->getLength());
+ incoming.fromWire(ibuf);
+
+ buffer_->clear();
+ if (recursive_mode() &&
+ incoming.getRcode() == Rcode::NOERROR()) {
+ done_ = handleRecursiveAnswer(incoming);
+ } else {
+ isc::resolve::copyResponseMessage(incoming, answer_message_);
+ done_ = true;
+ }
+
+ if (done_) {
+ callCallback(true);
+ stop();
+ }
+ } else if (!done_ && retries_--) {
+ // Query timed out, but we have some retries, so send again
+ dlog("Timeout for " + question_.toText() + " to " + current_ns_address.getAddress().toText() + ", resending query");
+ if (recursive_mode()) {
+ current_ns_address.updateRTT(isc::nsas::AddressEntry::UNREACHABLE);
+ }
+ send();
+ } else {
+ // We are either already done, or out of retries
+ if (recursive_mode() && result == IOFetch::TIME_OUT) {
+ dlog("Timeout for " + question_.toText() + " to " + current_ns_address.getAddress().toText() + ", giving up");
+ current_ns_address.updateRTT(isc::nsas::AddressEntry::UNREACHABLE);
+ }
+ if (!callback_called_) {
+ makeSERVFAIL();
+ callCallback(true);
+ }
+ stop();
+ }
+ }
+
+ // Clear the answer parts of answer_message, and set the rcode
+ // to servfail
+ void makeSERVFAIL() {
+ isc::resolve::makeErrorMessage(answer_message_, Rcode::SERVFAIL());
+ }
+
+ // Returns true if we are in 'recursive' mode
+ // Returns false if we are in 'forwarding' mode
+ // (i.e. if we have anything in upstream_)
+ bool recursive_mode() const {
+ return upstream_->empty();
+ }
+};
+
+}
+
+void
+RecursiveQuery::resolve(const QuestionPtr& question,
+ const isc::resolve::ResolverInterface::CallbackPtr callback)
+{
+ IOService& io = dns_service_.getIOService();
+
+ MessagePtr answer_message(new Message(Message::RENDER));
+ isc::resolve::initResponseMessage(*question, *answer_message);
+
+ OutputBufferPtr buffer(new OutputBuffer(0));
+
+ dlog("Asked to resolve: " + question->toText());
+
+ dlog("Try out cache first (direct call to resolve)");
+ // First try to see if we have something cached in the messagecache
+ if (cache_.lookup(question->getName(), question->getType(),
+ question->getClass(), *answer_message) &&
+ answer_message->getRRCount(Message::SECTION_ANSWER) > 0) {
+ dlog("Message found in cache, returning that");
+ // TODO: err, should cache set rcode as well?
+ answer_message->setRcode(Rcode::NOERROR());
+ callback->success(answer_message);
+ } else {
+ // Perhaps we only have the one RRset?
+ // TODO: can we do this? should we check for specific types only?
+ RRsetPtr cached_rrset = cache_.lookup(question->getName(),
+ question->getType(),
+ question->getClass());
+ if (cached_rrset) {
+ dlog("Found single RRset in cache");
+ answer_message->addRRset(Message::SECTION_ANSWER,
+ cached_rrset);
+ answer_message->setRcode(Rcode::NOERROR());
+ callback->success(answer_message);
+ } else {
+ dlog("Message not found in cache, starting recursive query");
+ // It will delete itself when it is done
+ new RunningQuery(io, *question, answer_message, upstream_,
+ test_server_, buffer, callback,
+ query_timeout_, client_timeout_,
+ lookup_timeout_, retries_, nsas_, cache_);
+ }
+ }
+}
+
+void
+RecursiveQuery::resolve(const Question& question,
+ MessagePtr answer_message,
+ OutputBufferPtr buffer,
+ DNSServer* server)
+{
+ // XXX: eventually we will need to be able to determine whether
+ // the message should be sent via TCP or UDP, or sent initially via
+ // UDP and then fall back to TCP on failure, but for the moment
+ // we're only going to handle UDP.
+ IOService& io = dns_service_.getIOService();
+
+ isc::resolve::ResolverInterface::CallbackPtr crs(
+ new isc::resolve::ResolverCallbackServer(server));
+
+ // TODO: general 'prepareinitialanswer'
+ answer_message->setOpcode(isc::dns::Opcode::QUERY());
+ answer_message->addQuestion(question);
+
+ dlog("Asked to resolve: " + question.toText());
+
+ // First try to see if we have something cached in the messagecache
+ dlog("Try out cache first (started by incoming event)");
+ if (cache_.lookup(question.getName(), question.getType(),
+ question.getClass(), *answer_message) &&
+ answer_message->getRRCount(Message::SECTION_ANSWER) > 0) {
+ dlog("Message found in cache, returning that");
+ // TODO: err, should cache set rcode as well?
+ answer_message->setRcode(Rcode::NOERROR());
+ crs->success(answer_message);
+ } else {
+ // Perhaps we only have the one RRset?
+ // TODO: can we do this? should we check for specific types only?
+ RRsetPtr cached_rrset = cache_.lookup(question.getName(),
+ question.getType(),
+ question.getClass());
+ if (cached_rrset) {
+ dlog("Found single RRset in cache");
+ answer_message->addRRset(Message::SECTION_ANSWER,
+ cached_rrset);
+ answer_message->setRcode(Rcode::NOERROR());
+ crs->success(answer_message);
+ } else {
+ dlog("Message not found in cache, starting recursive query");
+ // It will delete itself when it is done
+ new RunningQuery(io, question, answer_message, upstream_,
+ test_server_, buffer, crs, query_timeout_,
+ client_timeout_, lookup_timeout_, retries_,
+ nsas_, cache_);
+ }
+ }
+}
+
+
+
+} // namespace asiolink
diff --git a/src/lib/resolve/recursive_query.h b/src/lib/resolve/recursive_query.h
new file mode 100644
index 0000000..1180d55
--- /dev/null
+++ b/src/lib/resolve/recursive_query.h
@@ -0,0 +1,133 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __RECURSIVE_QUERY_H
+#define __RECURSIVE_QUERY_H 1
+
+#include <asiolink/dns_service.h>
+#include <asiolink/dns_server.h>
+#include <dns/buffer.h>
+#include <nsas/nameserver_address_store.h>
+#include <cache/resolver_cache.h>
+
+namespace asiolink {
+/// \brief The \c RecursiveQuery class provides a layer of abstraction around
+/// the ASIO code that carries out an upstream query.
+///
+/// This design is very preliminary; currently it is only capable of
+/// handling simple forward requests to a single resolver.
+class RecursiveQuery {
+ ///
+ /// \name Constructors
+ ///
+ //@{
+public:
+ /// \brief Constructor
+ ///
+ /// This is currently the only way to construct \c RecursiveQuery
+ /// object. If the addresses of the forward nameservers is specified,
+ /// and every upstream query will be sent to one random address, and
+ /// the result sent back directly. If not, it will do full resolving.
+ ///
+ /// \param dns_service The DNS Service to perform the recursive
+ /// query on.
+ /// \param upstream Addresses and ports of the upstream servers
+ /// to forward queries to.
+ /// \param upstream_root Addresses and ports of the root servers
+ /// to use when resolving.
+ /// \param query_timeout Timeout value for queries we sent, in ms
+ /// \param client_timeout Timeout value for when we send back an
+ /// error, in ms
+ /// \param lookup_timeout Timeout value for when we give up, in ms
+ /// \param retries how many times we try again (0 means just send and
+ /// and return if it returs).
+ RecursiveQuery(DNSService& dns_service,
+ isc::nsas::NameserverAddressStore& nsas,
+ isc::cache::ResolverCache& cache,
+ const std::vector<std::pair<std::string, uint16_t> >&
+ upstream,
+ const std::vector<std::pair<std::string, uint16_t> >&
+ upstream_root,
+ int query_timeout = 2000,
+ int client_timeout = 4000,
+ int lookup_timeout = 30000,
+ unsigned retries = 3);
+ //@}
+
+ /// \brief Initiate resolving
+ ///
+ /// When sendQuery() is called, a (set of) message(s) is sent
+ /// asynchronously. If upstream servers are set, one is chosen
+ /// and the response (if any) from that server will be returned.
+ ///
+ /// If not upstream is set, a root server is chosen from the
+ /// root_servers, and the RunningQuery shall do a full resolve
+ /// (i.e. if the answer is a delegation, it will be followed, etc.)
+ /// until there is an answer or an error.
+ ///
+ /// When there is a response or an error and we give up, the given
+ /// CallbackPtr object shall be called (with either success() or
+ /// failure(). See ResolverInterface::Callback for more information.
+ ///
+ /// \param question The question being answered <qname/qclass/qtype>
+ /// \param callback Callback object. See
+ /// \c ResolverInterface::Callback for more information
+ void resolve(const isc::dns::QuestionPtr& question,
+ const isc::resolve::ResolverInterface::CallbackPtr callback);
+
+
+ /// \brief Initiates resolving for the given question.
+ ///
+ /// This actually calls the previous sendQuery() with a default
+ /// callback object, which calls resume() on the given DNSServer
+ /// object.
+ ///
+ /// \param question The question being answered <qname/qclass/qtype>
+ /// \param answer_message An output Message into which the final response will be copied
+ /// \param buffer An output buffer into which the intermediate responses will be copied
+ /// \param server A pointer to the \c DNSServer object handling the client
+ void resolve(const isc::dns::Question& question,
+ isc::dns::MessagePtr answer_message,
+ isc::dns::OutputBufferPtr buffer,
+ DNSServer* server);
+
+ /// \brief Set Test Server
+ ///
+ /// This method is *only* for unit testing the class. If set, it enables
+ /// recursive behaviour but, regardless of responses received, sends every
+ /// query to the test server.
+ ///
+ /// The test server is enabled by setting a non-zero port number.
+ ///
+ /// \param address IP address of the test server.
+ /// \param port Port number of the test server
+ void setTestServer(const std::string& address, uint16_t port);
+
+private:
+ DNSService& dns_service_;
+ isc::nsas::NameserverAddressStore& nsas_;
+ isc::cache::ResolverCache& cache_;
+ boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
+ upstream_;
+ boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
+ upstream_root_;
+ std::pair<std::string, uint16_t> test_server_;
+ int query_timeout_;
+ int client_timeout_;
+ int lookup_timeout_;
+ unsigned retries_;
+};
+
+} // namespace asiolink
+#endif // __RECURSIVE_QUERY_H
diff --git a/src/lib/resolve/resolve.cc b/src/lib/resolve/resolve.cc
new file mode 100644
index 0000000..f741121
--- /dev/null
+++ b/src/lib/resolve/resolve.cc
@@ -0,0 +1,78 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <resolve/resolve.h>
+
+#include <dns/message.h>
+#include <dns/opcode.h>
+
+using namespace isc::dns;
+
+namespace {
+ class SectionInserter {
+ public:
+ SectionInserter(MessagePtr message, const Message::Section sect) :
+ message_(message), section_(sect)
+ {}
+ void operator()(const RRsetPtr rrset) {
+ message_->addRRset(section_, rrset, true);
+ }
+ MessagePtr message_;
+ const Message::Section section_;
+ };
+}
+
+namespace isc {
+namespace resolve {
+
+void
+makeErrorMessage(MessagePtr answer_message,
+ const Rcode& error_code)
+{
+ answer_message->clearSection(Message::SECTION_ANSWER);
+ answer_message->clearSection(Message::SECTION_AUTHORITY);
+ answer_message->clearSection(Message::SECTION_ADDITIONAL);
+
+ answer_message->setRcode(error_code);
+}
+
+void initResponseMessage(const isc::dns::Message& query_message,
+ isc::dns::Message& response_message)
+{
+ response_message.setOpcode(query_message.getOpcode());
+ response_message.setQid(query_message.getQid());
+ assert(response_message.getRRCount(Message::SECTION_QUESTION) == 0);
+ response_message.appendSection(Message::SECTION_QUESTION,
+ query_message);
+}
+
+void initResponseMessage(const isc::dns::Question& question,
+ isc::dns::Message& response_message)
+{
+ response_message.setOpcode(isc::dns::Opcode::QUERY());
+ response_message.addQuestion(question);
+}
+
+void copyResponseMessage(const Message& source, MessagePtr target) {
+ target->setRcode(source.getRcode());
+
+ target->appendSection(Message::SECTION_ANSWER, source);
+ target->appendSection(Message::SECTION_AUTHORITY, source);
+ target->appendSection(Message::SECTION_ADDITIONAL, source);
+}
+
+
+} // namespace resolve
+} // namespace isc
+
diff --git a/src/lib/resolve/resolve.h b/src/lib/resolve/resolve.h
index 63c9b58..550b620 100644
--- a/src/lib/resolve/resolve.h
+++ b/src/lib/resolve/resolve.h
@@ -15,6 +15,82 @@
#ifndef _ISC_RESOLVE_H
#define _ISC_RESOLVE_H 1
+/// This file includes all other libresolve headers, and provides
+/// several helper functions used in resolving.
+
#include <resolve/resolver_interface.h>
#include <resolve/resolver_callback.h>
+#include <resolve/response_classifier.h>
+
+#include <dns/rcode.h>
+
+namespace isc {
+namespace resolve {
+
+/// \brief Create an error response
+///
+/// Clears the answer, authority, and additional section of the
+/// given MessagePtr and sets the given error code
+///
+/// Notes: Assuming you have already done initial preparations
+/// on the given answer message (copy the opcode, qid and question
+/// section), you can simply use this to create an error response.
+///
+/// \param answer_message The message to clear and place the error in
+/// \param question The question to add to the
+/// \param error_code The error Rcode
+void makeErrorMessage(isc::dns::MessagePtr answer_message,
+ const isc::dns::Rcode& error_code);
+
+
+/// \brief Initialize a response message
+///
+/// Based on the given query message, this fills in the very
+/// first details of the response (i.e. the Question section and
+/// the Opcode). This allows for direct usage of makeErrorMessage(),
+/// as well as ResolveCache.lookup().
+///
+/// Raises an isc::dns::InvalidMessageOperation if reponse_message is
+/// not in RENDER mode.
+///
+/// \param query_message The query message to take the Question, Qid,
+/// and Opcode from.
+/// \param response_message The fresh response message to initialize
+/// (must be in RENDER mode)
+void initResponseMessage(const isc::dns::Message& query_message,
+ isc::dns::Message& response_message);
+
+
+/// \brief Initialize a response message
+///
+/// Based on the given question, this fills in the very
+/// first details of the response (i.e. the Question section and the
+/// Opcode Query). This allows for direct usage of makeErrorMessage(),
+/// as well as ResolveCache.lookup().
+///
+/// Raises an isc::dns::InvalidMessageOperation if reponse_message is
+/// not in RENDER mode.
+///
+/// \param question The question to place in the Question section
+/// \param response_message The fresh response message to initialize
+/// (must be in RENDER mode)
+void initResponseMessage(const isc::dns::Question& question,
+ isc::dns::Message& response_message);
+
+
+/// \brief Copies the parts relevant for a DNS response to the
+/// target message
+///
+/// This adds all the RRsets in the answer, authority and
+/// additional sections to the target, as well as the response
+/// code
+/// \param source The Message to copy the data from
+/// \param target The Message to copy the data to
+void copyResponseMessage(const isc::dns::Message& source,
+ isc::dns::MessagePtr target);
+
+
+} // namespace resolve
+} // namespace isc
+
#endif // ISC_RESOLVE_H_
diff --git a/src/lib/resolve/response_classifier.cc b/src/lib/resolve/response_classifier.cc
new file mode 100644
index 0000000..02808e4
--- /dev/null
+++ b/src/lib/resolve/response_classifier.cc
@@ -0,0 +1,278 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <cstddef>
+#include <vector>
+
+#include <resolve/response_classifier.h>
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/rrset.h>
+
+using namespace isc::dns;
+using namespace std;
+
+namespace isc {
+namespace resolve {
+
+// Classify the response in the "message" object.
+
+ResponseClassifier::Category ResponseClassifier::classify(
+ const Question& question, const Message& message,
+ Name& cname_target, unsigned int& cname_count, bool tcignore
+ )
+{
+ // Check header bits
+ if (!message.getHeaderFlag(Message::HEADERFLAG_QR)) {
+ return (NOTRESPONSE); // Query-response bit not set in the response
+ }
+
+ // We only recognise responses to queries here
+ if (message.getOpcode() != Opcode::QUERY()) {
+ return (OPCODE);
+ }
+
+ // Apparently have a response. There must be a single question in it...
+ const vector<QuestionPtr> msgquestion(message.beginQuestion(),
+ message.endQuestion());
+ if (msgquestion.size() != 1) {
+ return (NOTONEQUEST); // Not one question in response question section
+ }
+
+ // ... and the question should be equal to the question given.
+ // XXX: This means that "question" may not be the question sent by the
+ // client. In the case of a CNAME response, the qname of subsequent
+ // questions needs to be altered.
+ if (question != *(msgquestion[0])) {
+ return (MISMATQUEST);
+ }
+
+ // Check for Rcode-related errors.
+ const Rcode& rcode = message.getRcode();
+ if (rcode != Rcode::NOERROR()) {
+ if (rcode == Rcode::NXDOMAIN()) {
+
+ // No such domain. According to RFC2308, the domain referred to by
+ // the QNAME does not exist, although there may be a CNAME in the
+ // answer section and there may be an SOA and/or NS RRs in the
+ // authority section (ignoring any DNSSEC RRs for now).
+ //
+ // Note the "may". There may not be anything. Also, note that if
+ // there is a CNAME in the answer section, the authoritative server
+ // has verified that the name given in the CNAME's RDATA field does
+ // not exist. And that if a CNAME is returned in the answer, then
+ // the QNAME of the RRs in the authority section will refer to the
+ // authority for the CNAME's RDATA and not to the original question.
+ //
+ // Without doing further classification, it is sufficient to say
+ // that if an NXDOMAIN is received, there was no translation of the
+ // QNAME available.
+ return (NXDOMAIN); // Received NXDOMAIN from parent.
+
+ } else {
+
+ // Not NXDOMAIN but not NOERROR either. Must be an RCODE-related
+ // error.
+ return (RCODE);
+ }
+ }
+
+ // All seems OK and we can start looking at the content. However, one
+ // more header check remains - was the response truncated? If so, we'll
+ // probably want to re-query over TCP. However, in some circumstances we
+ // might want to go with what we have. So give the caller the option of
+ // ignoring the TC bit.
+ if (message.getHeaderFlag(Message::HEADERFLAG_TC) && (!tcignore)) {
+ return (TRUNCATED);
+ }
+
+ // By the time we get here, we're assured that the packet format is correct.
+ // We now need to decide as to whether it is an answer, a CNAME, or a
+ // referral. For this, we need to inspect the contents of the answer
+ // and authority sections.
+ const vector<RRsetPtr> answer(
+ message.beginSection(Message::SECTION_ANSWER),
+ message.endSection(Message::SECTION_ANSWER)
+ );
+ const vector<RRsetPtr> authority(
+ message.beginSection(Message::SECTION_AUTHORITY),
+ message.endSection(Message::SECTION_AUTHORITY)
+ );
+
+ // If there is nothing in the answer section, it is a referral - unless
+ // there is no NS in the authority section
+ if (answer.empty()) {
+ if (authority.empty()) {
+ return (EMPTY);
+ }
+ for (int i = 0; i < authority.size(); ++i) {
+ if (authority[i]->getType() == RRType::NS()) {
+ return (REFERRAL);
+ }
+ }
+ return (NXRRSET);
+ }
+
+ // Look at two cases - one RRset in the answer and multiple RRsets in
+ // the answer.
+ if (answer.size() == 1) {
+
+ // Does the name and class of the answer match that of the question?
+ if ((answer[0]->getName() == question.getName()) &&
+ (answer[0]->getClass() == question.getClass())) {
+
+ // It does. How about the type of the response? The response
+ // is an answer if the type matches that of the question, or if the
+ // question was for type ANY. It is a CNAME reply if the answer
+ // type is CNAME. And it is an error for anything else.
+ if ((answer[0]->getType() == question.getType()) ||
+ (question.getType() == RRType::ANY())) {
+ return (ANSWER);
+ } else if (answer[0]->getType() == RRType::CNAME()) {
+ RdataIteratorPtr it = answer[0]->getRdataIterator();
+ cname_target = Name(it->getCurrent().toText());
+ ++cname_count;
+ return (CNAME);
+ } else {
+ return (INVTYPE);
+ }
+ }
+ else {
+
+ // Either the name and/or class of the reply don't match that of
+ // the question.
+ return (INVNAMCLASS);
+ }
+ }
+
+ // There are multiple RRsets in the answer. They should all have the same
+ // QCLASS, else there is some error in the response.
+ for (int i = 1; i < answer.size(); ++i) {
+ if (answer[0]->getClass() != answer[i]->getClass()) {
+ return (MULTICLASS);
+ }
+ }
+
+ // If the request type was ANY and they all have the same QNAME, we have
+ // an answer. But if they don't have the same QNAME, we must have an error;
+ // the only way we could get different QNAMES in an answer is if one were a
+ // CNAME - in which case there should no other record types at that QNAME.
+ if (question.getType() == RRType::ANY()) {
+ bool all_same = true;
+ for (int i = 1; (i < answer.size()) && all_same; ++i) {
+ all_same = (answer[0]->getName() == answer[i]->getName());
+ }
+ if (all_same) {
+ return (ANSWER);
+ } else {
+ return (EXTRADATA);
+ }
+ }
+
+ // Multiple RRs in the answer, and not all the same QNAME. This
+ // is either an answer, a CNAME (in either case, there could be multiple
+ // CNAMEs in the chain) or an error.
+ //
+ // So we need to follow the CNAME chain to resolve this. For this to work:
+ //
+ // a) There must be one RR that matches the name, class and type of
+ // the question, and this is a CNAME.
+ // b) The CNAME chain is followed until the end of the chain does not
+ // exist (answer is a CNAME) or it is not of type CNAME (ANSWER).
+ //
+ // In the latter case, if there are additional RRs, it must be an error.
+
+ vector<RRsetPtr> ansrrset(answer);
+ vector<int> present(ansrrset.size(), 1);
+ return cnameChase(question.getName(), question.getType(),
+ cname_target, cname_count,
+ ansrrset, present, ansrrset.size());
+}
+
+// Search the CNAME chain.
+ResponseClassifier::Category ResponseClassifier::cnameChase(
+ const Name& qname, const RRType& qtype,
+ Name& cname_target, unsigned int& cname_count,
+ vector<RRsetPtr>& ansrrset, vector<int>& present, size_t size)
+{
+ // Search through the vector of RRset pointers until we find one with the
+ // right QNAME.
+ for (int i = 0; i < ansrrset.size(); ++i) {
+ if (present[i]) {
+
+ // This entry has not been logically removed, so look at it.
+ if (ansrrset[i]->getName() == qname) {
+
+ // QNAME match. If this RRset is a CNAME, remove it from
+ // further consideration. If nothing is left, the end of the
+ // chain is a CNAME so this is a CNAME. Otherwise replace
+ // the name with the RDATA of the CNAME and call ourself
+ // recursively.
+ if (ansrrset[i]->getType() == RRType::CNAME()) {
+
+ // Don't consider it in the next iteration (although we
+ // can still access it for now).
+ present[i] = 0;
+ --size;
+ if (size == 0) {
+ RdataIteratorPtr it = ansrrset[i]->getRdataIterator();
+ cname_target = Name(it->getCurrent().toText());
+ return (CNAME);
+ } else {
+ if (ansrrset[i]->getRdataCount() != 1) {
+
+ // Multiple RDATA for a CNAME? This is invalid.
+
+ return (NOTSINGLE);
+ }
+ RdataIteratorPtr it = ansrrset[i]->getRdataIterator();
+ Name newname(it->getCurrent().toText());
+
+ // Increase CNAME count, and continue
+ return cnameChase(newname, qtype, cname_target,
+ ++cname_count, ansrrset, present, size);
+ }
+
+ } else {
+
+ // We've got here because the element is not a CNAME. If
+ // this is the last element and the type is the one we are
+ // after, we've found the answer, or it is an error. If
+ // there is more than one RRset left in the list we are
+ // searching, we have extra data in the answer.
+ if (ansrrset[i]->getType() == qtype) {
+ if (size == 1) {
+ return (ANSWERCNAME);
+ } else {
+ return (EXTRADATA);
+ }
+ }
+ return (INVTYPE);
+ }
+ }
+ }
+ }
+
+ // We get here if we've dropped off the end of the list without finding the
+ // QNAME we are looking for. This means that the CNAME chain has ended
+ // but there are additional RRsets in the data.
+
+ return (EXTRADATA);
+}
+
+} // namespace resolve
+} // namespace isc
diff --git a/src/lib/resolve/response_classifier.h b/src/lib/resolve/response_classifier.h
new file mode 100644
index 0000000..3821560
--- /dev/null
+++ b/src/lib/resolve/response_classifier.h
@@ -0,0 +1,157 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __RESPONSE_CLASSIFIER_H
+#define __RESPONSE_CLASSIFIER_H
+
+#include <cstddef>
+
+#include <dns/question.h>
+#include <dns/message.h>
+#include <dns/question.h>
+
+#define RESOLVER_MAX_CNAME_CHAIN 16
+
+namespace isc {
+namespace resolve {
+
+/// \brief Classify Server Response
+///
+/// This class is used in the recursive server. It is passed an answer received
+/// from an upstream server and categorises it.
+///
+/// TODO: The code here does not take into account any EDNS0 fields.
+
+class ResponseClassifier {
+public:
+
+ /// \brief Category of Answer
+ ///
+ /// In the valid answers, not the distinction between REFERRAL and CNAME.
+ /// A REFERRAL answer means that the answer section of the message is
+ /// empty, but there is something in the authority section. A CNAME means
+ /// that the answer section contains one or more CNAMES in a chain that
+ /// do not end with a non-CNAME RRset.
+ enum Category {
+
+ // Codes indicating that a message is valid.
+
+ ANSWER, ///< Response contains the answer
+ ANSWERCNAME, ///< Response was a CNAME chain ending in an answer
+ CNAME, ///< Response was a CNAME
+ NXDOMAIN, ///< Response was an NXDOMAIN
+ NXRRSET, ///< Response was name exists, but type does not
+ REFERRAL, ///< Response contains a referral
+
+ // Codes indicating that a message is invalid. Note that the error()
+ // method relies on these appearing after the "message valid" codes.
+
+ EMPTY, ///< No answer or authority sections
+ EXTRADATA, ///< Answer section contains more RRsets than needed
+ INVNAMCLASS, ///< Invalid name or class in answer
+ INVTYPE, ///< Name/class of answer correct, type is wrong
+ MISMATQUEST, ///< Response question section != question
+ MULTICLASS, ///< Multiple classes in multi-RR answer
+ NOTONEQUEST, ///< Not one question in response question section
+ NOTRESPONSE, ///< Response has the Query/Response bit clear
+ NOTSINGLE, ///< CNAME has multiple RDATA elements.
+ OPCODE, ///< Opcode field does not indicate a query
+ RCODE, ///< RCODE indicated an error
+ TRUNCATED ///< Response was truncated
+ };
+
+ /// \brief Check Error
+ ///
+ /// An inline routine to quickly classify whether the return category is
+ /// an error or not. This makes use of internal knowledge of the order of
+ /// codes in the Category enum.
+ ///
+ /// \param code Return category from classify()
+ ///
+ /// \return true if the category is an error, false if not.
+ static bool error(Category code) {
+ return (code > REFERRAL);
+ }
+
+ /// \brief Classify
+ ///
+ /// Classify the response in the "message" object.
+ ///
+ /// \param question Question that was sent to the server
+ /// \param message Pointer to the associated response from the server.
+ /// \param cname_target If the message contains an (unfinished) CNAME
+ /// chain, this Name will be replaced by the target of the last CNAME
+ /// in the chain
+ /// \param cname_count This unsigned int will be incremented with
+ /// the number of CNAMEs followed
+ /// \param tcignore If set, the TC bit in a response packet is
+ /// ignored. Otherwise the error code TRUNCATED will be returned. The
+ /// only time this is likely to be used is in development where we are not
+ /// going to fail over to TCP and will want to use what is returned, even
+ /// if some of the response was lost.
+ static Category classify(const isc::dns::Question& question,
+ const isc::dns::Message& message,
+ isc::dns::Name& cname_target, unsigned int& cname_count,
+ bool tcignore = false);
+
+private:
+ /// \brief Follow CNAMEs
+ ///
+ /// Given a QNAME and an answer section that contains CNAMEs, assume that
+ /// they form a CNAME chain and search through them. Possible outcomes
+ /// are:
+ ///
+ /// a) All CNAMES and they form a chain. The result is a referral.
+ /// b) All but one are CNAMES and they form a chain. The other is pointed
+ /// to by the last element of the chain and is the correct QTYPE. The
+ /// result is an answer.
+ /// c) Having followed the CNAME chain as far as we can, there is one
+ /// remaining RRset that is of the wrong type, or there are multiple
+ /// RRsets remaining. return the EXTRADATA code.
+ ///
+ /// \param qname Question name we are searching for
+ /// \param qtype Question type we are search for. (This is assumed not
+ /// to be "ANY".)
+ /// \param ansrrset Vector of RRsetPtr pointing to the RRsets we are
+ /// considering.
+ /// \param present Array of "int" the same size of ansrrset, with each
+ /// element set to "1" to allow the corresponding element of ansrrset to
+ /// be checked, and "0" to skip it. This might be premature optimisation,
+ /// but the algorithm would otherwise involve duplicating the RRset
+ /// vector then removing elements from random positions one by one. As
+ /// each removal involves the destruction of an "xxxPtr" element (which
+ /// presently is implemented by boost::shared_ptr), the overhad of memory
+ /// management seemed high. This solution imposes some additional loop
+ /// cycles, but that should be minimal compared with the overhead of the
+ /// memory management.
+ /// \param cname_target If the message contains an (unfinished) CNAME
+ /// chain, this Name will be replaced by the target of the last CNAME
+ /// in the chain
+ /// \param cname_count This unsigned int will be incremented with
+ /// the number of CNAMEs followed
+ /// \param size Number of elements to check. See description of \c present
+ /// for details.
+ static Category cnameChase(const isc::dns::Name& qname,
+ const isc::dns::RRType& qtype,
+ isc::dns::Name& cname_target, unsigned int& cname_count,
+ std::vector<isc::dns::RRsetPtr>& ansrrset, std::vector<int>& present,
+ size_t size);
+};
+
+#endif // __RESPONSE_CLASSIFIER_H
+
+} // namespace resolve
+} // namespace isc
diff --git a/src/lib/resolve/tests/Makefile.am b/src/lib/resolve/tests/Makefile.am
index e064cc0..da28f78 100644
--- a/src/lib/resolve/tests/Makefile.am
+++ b/src/lib/resolve/tests/Makefile.am
@@ -14,10 +14,21 @@ TESTS += run_unittests
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.h
+run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
+run_unittests_SOURCES += resolve_unittest.cc
run_unittests_SOURCES += resolver_callback_unittest.cc
+run_unittests_SOURCES += response_classifier_unittest.cc
+run_unittests_SOURCES += recursive_query_unittest.cc
+run_unittests_SOURCES += recursive_query_unittest_2.cc
run_unittests_LDADD = $(GTEST_LDADD)
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cache/libcache.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/resolve/libresolve.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
endif
diff --git a/src/lib/resolve/tests/recursive_query_unittest.cc b/src/lib/resolve/tests/recursive_query_unittest.cc
new file mode 100644
index 0000000..ab1ffa3
--- /dev/null
+++ b/src/lib/resolve/tests/recursive_query_unittest.cc
@@ -0,0 +1,861 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <string.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/rcode.h>
+
+#include <dns/buffer.h>
+#include <dns/message.h>
+
+#include <nsas/nameserver_address_store.h>
+#include <cache/resolver_cache.h>
+
+// IMPORTANT: We shouldn't directly use ASIO definitions in this test.
+// In particular, we must not include asio.hpp in this file.
+// The asiolink module is primarily intended to be a wrapper that hide the
+// details of the underlying implementations. We need to test the wrapper
+// level behaviors. In addition, some compilers reject to compile this file
+// if we include asio.hpp unless we specify a special compiler option.
+// If we need to test something at the level of underlying ASIO and need
+// their definition, that test should go to asiolink/internal/tests.
+#include <resolve/recursive_query.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_message.h>
+#include <asiolink/io_error.h>
+#include <asiolink/dns_lookup.h>
+#include <asiolink/simple_callback.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace asiolink;
+using namespace isc::dns;
+
+namespace {
+const char* const TEST_SERVER_PORT = "53535";
+const char* const TEST_CLIENT_PORT = "53536";
+const char* const TEST_IPV6_ADDR = "::1";
+const char* const TEST_IPV4_ADDR = "127.0.0.1";
+// This data is intended to be valid as a DNS/TCP-like message: the first
+// two octets encode the length of the rest of the data. This is crucial
+// for the tests below.
+const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
+
+// This function returns an addrinfo structure for use by tests, using
+// different addresses and ports depending on whether we're testing
+// IPv4 or v6, TCP or UDP, and client or server operation.
+struct addrinfo*
+resolveAddress(const int family, const int protocol, const bool client) {
+ const char* const addr = (family == AF_INET6) ?
+ TEST_IPV6_ADDR : TEST_IPV4_ADDR;
+ const char* const port = client ? TEST_CLIENT_PORT : TEST_SERVER_PORT;
+
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
+ hints.ai_protocol = protocol;
+ hints.ai_flags = AI_NUMERICSERV;
+
+ struct addrinfo* res;
+ const int error = getaddrinfo(addr, port, &hints, &res);
+ if (error != 0) {
+ isc_throw(IOError, "getaddrinfo failed: " << gai_strerror(error));
+ }
+
+ return (res);
+}
+
+// This fixture is a framework for various types of network operations
+// using the ASIO interfaces. Each test case creates an IOService object,
+// opens a local "client" socket for testing, sends data via the local socket
+// to the service that would run in the IOService object.
+// A mock callback function (an ASIOCallBack object) is registered with the
+// IOService object, so the test code should be able to examine the data
+// received on the server side. It then checks the received data matches
+// expected parameters.
+// If initialization parameters of the IOService should be modified, the test
+// case can do it using the setDNSService() method.
+// Note: the set of tests in RecursiveQueryTest use actual network services and may
+// involve undesirable side effects such as blocking.
+class RecursiveQueryTest : public ::testing::Test {
+protected:
+ RecursiveQueryTest();
+ ~RecursiveQueryTest() {
+ if (res_ != NULL) {
+ freeaddrinfo(res_);
+ }
+ if (sock_ != -1) {
+ close(sock_);
+ }
+ delete dns_service_;
+ delete callback_;
+ delete io_service_;
+ }
+
+ // Send a test UDP packet to a mock server
+ void sendUDP(const int family) {
+ res_ = resolveAddress(family, IPPROTO_UDP, false);
+
+ sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+ const int cc = sendto(sock_, test_data, sizeof(test_data), 0,
+ res_->ai_addr, res_->ai_addrlen);
+ if (cc != sizeof(test_data)) {
+ isc_throw(IOError, "unexpected sendto result: " << cc);
+ }
+ io_service_->run();
+ }
+
+ // Send a test TCP packet to a mock server
+ void sendTCP(const int family) {
+ res_ = resolveAddress(family, IPPROTO_TCP, false);
+
+ sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+ if (connect(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+ isc_throw(IOError, "failed to connect to the test server");
+ }
+ const int cc = send(sock_, test_data, sizeof(test_data), 0);
+ if (cc != sizeof(test_data)) {
+ isc_throw(IOError, "unexpected send result: " << cc);
+ }
+ io_service_->run();
+ }
+
+ // Receive a UDP packet from a mock server; used for testing
+ // recursive lookup. The caller must place a RecursiveQuery
+ // on the IO Service queue before running this routine.
+ void recvUDP(const int family, void* buffer, size_t& size) {
+ res_ = resolveAddress(family, IPPROTO_UDP, true);
+
+ sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+
+ if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+ isc_throw(IOError, "bind failed: " << strerror(errno));
+ }
+
+ // The IO service queue should have a RecursiveQuery object scheduled
+ // to run at this point. This call will cause it to begin an
+ // async send, then return.
+ io_service_->run_one();
+
+ // ... and this one will block until the send has completed
+ io_service_->run_one();
+
+ // Now we attempt to recv() whatever was sent.
+ // XXX: there's no guarantee the receiving socket can immediately get
+ // the packet. Normally we can perform blocking recv to wait for it,
+ // but in theory it's even possible that the packet is lost.
+ // In order to prevent the test from hanging in such a worst case
+ // we add an ad hoc timeout.
+ const struct timeval timeo = { 10, 0 };
+ int recv_options = 0;
+ if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo,
+ sizeof(timeo))) {
+ if (errno == ENOPROTOOPT) {
+ // Workaround for Solaris: it doesn't accept SO_RCVTIMEO
+ // with the error of ENOPROTOOPT. Since this is a workaround
+ // for rare error cases anyway, we simply switch to the
+ // "don't wait" mode. If we still find an error in recv()
+ // can happen often we'll consider a more complete solution.
+ recv_options = MSG_DONTWAIT;
+ } else {
+ isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
+ }
+ }
+ const int ret = recv(sock_, buffer, size, recv_options);
+ if (ret < 0) {
+ isc_throw(IOError, "recvfrom failed: " << strerror(errno));
+ }
+
+ // Pass the message size back via the size parameter
+ size = ret;
+ }
+
+
+ // Set up an IO Service queue using the specified address
+ void setDNSService(const char& address) {
+ delete dns_service_;
+ dns_service_ = NULL;
+ delete io_service_;
+ io_service_ = new IOService();
+ callback_ = new ASIOCallBack(this);
+ dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, address, callback_, NULL, NULL);
+ }
+
+ // Set up an IO Service queue using the "any" address, on IPv4 if
+ // 'use_ipv4' is true and on IPv6 if 'use_ipv6' is true.
+ void setDNSService(const bool use_ipv4, const bool use_ipv6) {
+ delete dns_service_;
+ dns_service_ = NULL;
+ delete io_service_;
+ io_service_ = new IOService();
+ callback_ = new ASIOCallBack(this);
+ dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, use_ipv4, use_ipv6, callback_,
+ NULL, NULL);
+ }
+
+ // Set up empty DNS Service
+ // Set up an IO Service queue without any addresses
+ void setDNSService() {
+ delete dns_service_;
+ dns_service_ = NULL;
+ delete io_service_;
+ io_service_ = new IOService();
+ callback_ = new ASIOCallBack(this);
+ dns_service_ = new DNSService(*io_service_, callback_, NULL, NULL);
+ }
+
+ // Run a simple server test, on either IPv4 or IPv6, and over either
+ // UDP or TCP. Calls the sendUDP() or sendTCP() methods, which will
+ // start the IO Service queue. The UDPServer or TCPServer that was
+ // created by setIOService() will receive the test packet and issue a
+ // callback, which enables us to check that the data it received
+ // matches what we sent.
+ void doTest(const int family, const int protocol) {
+ if (protocol == IPPROTO_UDP) {
+ sendUDP(family);
+ } else {
+ sendTCP(family);
+ }
+
+ // There doesn't seem to be an effective test for the validity of
+ // 'native'.
+ // One thing we are sure is it must be different from our local socket.
+ EXPECT_NE(sock_, callback_native_);
+ EXPECT_EQ(protocol, callback_protocol_);
+ EXPECT_EQ(family == AF_INET6 ? TEST_IPV6_ADDR : TEST_IPV4_ADDR,
+ callback_address_);
+
+ const uint8_t* expected_data =
+ protocol == IPPROTO_UDP ? test_data : test_data + 2;
+ const size_t expected_datasize =
+ protocol == IPPROTO_UDP ? sizeof(test_data) :
+ sizeof(test_data) - 2;
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &callback_data_[0],
+ callback_data_.size(),
+ expected_data, expected_datasize);
+ }
+
+protected:
+ // This is a nonfunctional mockup of a DNSServer object. Its purpose
+ // is to resume after a recursive query or other asynchronous call
+ // has completed.
+ class MockServer : public DNSServer {
+ public:
+ explicit MockServer(IOService& io_service,
+ SimpleCallback* checkin = NULL,
+ DNSLookup* lookup = NULL,
+ DNSAnswer* answer = NULL) :
+ io_(io_service),
+ done_(false),
+ message_(new Message(Message::PARSE)),
+ answer_message_(new Message(Message::RENDER)),
+ respbuf_(new OutputBuffer(0)),
+ checkin_(checkin), lookup_(lookup), answer_(answer)
+ {}
+
+ void operator()(asio::error_code ec = asio::error_code(),
+ size_t length = 0)
+ {}
+
+ void resume(const bool) {
+ // should never be called in our tests
+ }
+
+ DNSServer* clone() {
+ MockServer* s = new MockServer(*this);
+ return (s);
+ }
+
+ inline void asyncLookup() {
+ if (lookup_) {
+ (*lookup_)(*io_message_, message_, answer_message_,
+ respbuf_, this);
+ }
+ }
+
+ protected:
+ IOService& io_;
+ bool done_;
+
+ private:
+ // Currently unused; these will be used for testing
+ // asynchronous lookup calls via the asyncLookup() method
+ boost::shared_ptr<asiolink::IOMessage> io_message_;
+ isc::dns::MessagePtr message_;
+ isc::dns::MessagePtr answer_message_;
+ isc::dns::OutputBufferPtr respbuf_;
+
+ // Callback functions provided by the caller
+ const SimpleCallback* checkin_;
+ const DNSLookup* lookup_;
+ const DNSAnswer* answer_;
+ };
+
+ // This version of mock server just stops the io_service when it is resumed
+ class MockServerStop : public MockServer {
+ public:
+ explicit MockServerStop(IOService& io_service, bool* done) :
+ MockServer(io_service),
+ done_(done)
+ {}
+
+ void resume(const bool done) {
+ *done_ = done;
+ io_.stop();
+ }
+
+ DNSServer* clone() {
+ return (new MockServerStop(*this));
+ }
+ private:
+ bool* done_;
+ };
+
+ class MockResolver : public isc::resolve::ResolverInterface {
+ void resolve(const QuestionPtr& question,
+ const ResolverInterface::CallbackPtr& callback) {
+ }
+ };
+
+ // This version of mock server just stops the io_service when it is resumed
+ // the second time. (Used in the clientTimeout test, where resume
+ // is called initially with the error answer, and later when the
+ // lookup times out, it is called without an answer to send back)
+ class MockServerStop2 : public MockServer {
+ public:
+ explicit MockServerStop2(IOService& io_service,
+ bool* done1, bool* done2) :
+ MockServer(io_service),
+ done1_(done1),
+ done2_(done2),
+ stopped_once_(false)
+ {}
+
+ void resume(const bool done) {
+ if (stopped_once_) {
+ *done2_ = done;
+ io_.stop();
+ } else {
+ *done1_ = done;
+ stopped_once_ = true;
+ }
+ }
+
+ DNSServer* clone() {
+ return (new MockServerStop2(*this));
+ }
+ private:
+ bool* done1_;
+ bool* done2_;
+ bool stopped_once_;
+ };
+
+private:
+ class ASIOCallBack : public SimpleCallback {
+ public:
+ ASIOCallBack(RecursiveQueryTest* test_obj) : test_obj_(test_obj) {}
+ void operator()(const IOMessage& io_message) const {
+ test_obj_->callBack(io_message);
+ }
+ private:
+ RecursiveQueryTest* test_obj_;
+ };
+ void callBack(const IOMessage& io_message) {
+ callback_protocol_ = io_message.getSocket().getProtocol();
+ callback_native_ = io_message.getSocket().getNative();
+ callback_address_ =
+ io_message.getRemoteEndpoint().getAddress().toText();
+ callback_data_.assign(
+ static_cast<const uint8_t*>(io_message.getData()),
+ static_cast<const uint8_t*>(io_message.getData()) +
+ io_message.getDataSize());
+ io_service_->stop();
+ }
+protected:
+ // We use a pointer for io_service_, because for some tests we
+ // need to recreate a new one within one onstance of this class
+ IOService* io_service_;
+ DNSService* dns_service_;
+ isc::nsas::NameserverAddressStore* nsas_;
+ isc::cache::ResolverCache cache_;
+ ASIOCallBack* callback_;
+ int callback_protocol_;
+ int callback_native_;
+ string callback_address_;
+ vector<uint8_t> callback_data_;
+ int sock_;
+ struct addrinfo* res_;
+};
+
+RecursiveQueryTest::RecursiveQueryTest() :
+ dns_service_(NULL), callback_(NULL), callback_protocol_(0),
+ callback_native_(-1), sock_(-1), res_(NULL)
+{
+ io_service_ = new IOService();
+ setDNSService(true, true);
+ boost::shared_ptr<MockResolver>mock_resolver(new MockResolver());
+ nsas_ = new isc::nsas::NameserverAddressStore(mock_resolver);
+}
+
+TEST_F(RecursiveQueryTest, v6UDPSend) {
+ doTest(AF_INET6, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v6TCPSend) {
+ doTest(AF_INET6, IPPROTO_TCP);
+}
+
+TEST_F(RecursiveQueryTest, v4UDPSend) {
+ doTest(AF_INET, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v4TCPSend) {
+ doTest(AF_INET, IPPROTO_TCP);
+}
+
+TEST_F(RecursiveQueryTest, v6UDPSendSpecific) {
+ // Explicitly set a specific address to be bound to the socket.
+ // The subsequent test does not directly ensures the underlying socket
+ // is bound to the expected address, but the success of the tests should
+ // reasonably suggest it works as intended.
+ // Specifying an address also implicitly means the service runs in a
+ // single address-family mode. In tests using TCP we can confirm that
+ // by trying to make a connection and seeing a failure. In UDP, it'd be
+ // more complicated because we need to use a connected socket and catch
+ // an error on a subsequent read operation. We could do it, but for
+ // simplicity we only tests the easier cases for now.
+
+ setDNSService(*TEST_IPV6_ADDR);
+ doTest(AF_INET6, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v6TCPSendSpecific) {
+ setDNSService(*TEST_IPV6_ADDR);
+ doTest(AF_INET6, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v4UDPSendSpecific) {
+ setDNSService(*TEST_IPV4_ADDR);
+ doTest(AF_INET, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v4TCPSendSpecific) {
+ setDNSService(*TEST_IPV4_ADDR);
+ doTest(AF_INET, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v6AddServer) {
+ setDNSService();
+ dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV6_ADDR);
+ doTest(AF_INET6, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v4AddServer) {
+ setDNSService();
+ dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV4_ADDR);
+ doTest(AF_INET, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(RecursiveQueryTest, clearServers) {
+ setDNSService();
+ dns_service_->clearServers();
+
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v6TCPOnly) {
+ // Open only IPv6 TCP socket. A subsequent attempt of establishing an
+ // IPv4/TCP connection should fail. See above for why we only test this
+ // for TCP.
+ setDNSService(false, true);
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v4TCPOnly) {
+ setDNSService(true, false);
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+vector<pair<string, uint16_t> >
+singleAddress(const string &address, uint16_t port) {
+ vector<pair<string, uint16_t> > result;
+ result.push_back(pair<string, uint16_t>(address, port));
+ return (result);
+}
+
+TEST_F(RecursiveQueryTest, recursiveSetupV4) {
+ setDNSService(true, false);
+ uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port)));
+}
+
+TEST_F(RecursiveQueryTest, recursiveSetupV6) {
+ setDNSService(false, true);
+ uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV6_ADDR, port),
+ singleAddress(TEST_IPV6_ADDR,port)));
+}
+
+// XXX:
+// This is very inadequate unit testing. It should be generalized into
+// a routine that can do this with variable address family, address, and
+// port, and with the various callbacks defined in such a way as to ensure
+// full code coverage including error cases.
+TEST_F(RecursiveQueryTest, forwarderSend) {
+ setDNSService(true, false);
+
+ // Note: We use the test prot plus one to ensure we aren't binding
+ // to the same port as the actual server
+ uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+
+ MockServer server(*io_service_);
+ RecursiveQuery rq(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port));
+
+ Question q(Name("example.com"), RRClass::IN(), RRType::TXT());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ rq.resolve(q, answer, buffer, &server);
+
+ char data[4096];
+ size_t size = sizeof(data);
+ ASSERT_NO_THROW(recvUDP(AF_INET, data, size));
+
+ Message m(Message::PARSE);
+ InputBuffer ibuf(data, size);
+
+ // Make sure we can parse the message that was sent
+ EXPECT_NO_THROW(m.parseHeader(ibuf));
+ EXPECT_NO_THROW(m.fromWire(ibuf));
+
+ // Check that the question sent matches the one we wanted
+ QuestionPtr q2 = *m.beginQuestion();
+ EXPECT_EQ(q.getName(), q2->getName());
+ EXPECT_EQ(q.getType(), q2->getType());
+ EXPECT_EQ(q.getClass(), q2->getClass());
+}
+
+int
+createTestSocket()
+{
+ struct addrinfo* res_ = resolveAddress(AF_INET, IPPROTO_UDP, true);
+ int sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+ if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+ isc_throw(IOError, "failed to bind test socket");
+ }
+ return sock_;
+}
+
+int
+setSocketTimeout(int sock_, size_t tv_sec, size_t tv_usec) {
+ const struct timeval timeo = { tv_sec, tv_usec };
+ int recv_options = 0;
+ if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo))) {
+ if (errno == ENOPROTOOPT) { // see RecursiveQueryTest::recvUDP()
+ recv_options = MSG_DONTWAIT;
+ } else {
+ isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
+ }
+ }
+ return recv_options;
+}
+
+// try to read from the socket max time
+// *num is incremented for every succesfull read
+// returns true if it can read max times, false otherwise
+bool tryRead(int sock_, int recv_options, size_t max, int* num) {
+ size_t i = 0;
+ do {
+ char inbuff[512];
+ if (recv(sock_, inbuff, sizeof(inbuff), recv_options) < 0) {
+ return false;
+ } else {
+ ++i;
+ ++*num;
+ }
+ } while (i < max);
+ return true;
+}
+
+
+// Test it tries the correct amount of times before giving up
+TEST_F(RecursiveQueryTest, forwardQueryTimeout) {
+ // Prepare the service (we do not use the common setup, we do not answer
+ setDNSService();
+
+ // Prepare the socket
+ sock_ = createTestSocket();
+
+ // Prepare the server
+ bool done(true);
+ MockServerStop server(*io_service_, &done);
+
+ // Do the answer
+ const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ RecursiveQuery query(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port),
+ 10, 4000, 3000, 2);
+ Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ query.resolve(question, answer, buffer, &server);
+
+ // Run the test
+ io_service_->run();
+
+ // Read up to 3 packets. Use some ad hoc timeout to prevent an infinite
+ // block (see also recvUDP()).
+ int recv_options = setSocketTimeout(sock_, 10, 0);
+ int num = 0;
+ bool read_success = tryRead(sock_, recv_options, 3, &num);
+
+ // The query should 'succeed' with an error response
+ EXPECT_TRUE(done);
+ EXPECT_EQ(3, num);
+ EXPECT_TRUE(read_success);
+}
+
+// If we set client timeout to lower than querytimeout, we should
+// get a failure answer, but still see retries
+// (no actual answer is given here yet)
+TEST_F(RecursiveQueryTest, forwardClientTimeout) {
+ // Prepare the service (we do not use the common setup, we do not answer
+ setDNSService();
+
+ sock_ = createTestSocket();
+
+ // Prepare the server
+ bool done1(true);
+ MockServerStop server(*io_service_, &done1);
+
+ MessagePtr answer(new Message(Message::RENDER));
+
+ // Do the answer
+ const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ // Set it up to retry twice before client timeout fires
+ // Since the lookup timer has not fired, it should retry
+ // four times
+ RecursiveQuery query(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port),
+ 200, 480, 4000, 4);
+ Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ query.resolve(question, answer, buffer, &server);
+
+ // Run the test
+ io_service_->run();
+
+ // we know it'll fail, so make it a shorter timeout
+ int recv_options = setSocketTimeout(sock_, 1, 0);
+
+ // Try to read 4 times
+ int num = 0;
+ bool read_success = tryRead(sock_, recv_options, 4, &num);
+
+ // The query should fail
+ EXPECT_TRUE(done1);
+ EXPECT_EQ(3, num);
+ EXPECT_FALSE(read_success);
+}
+
+// If we set lookup timeout to lower than querytimeout*retries, we should
+// fail before the full amount of retries
+TEST_F(RecursiveQueryTest, forwardLookupTimeout) {
+ // Prepare the service (we do not use the common setup, we do not answer
+ setDNSService();
+
+ // Prepare the socket
+ sock_ = createTestSocket();
+
+ // Prepare the server
+ bool done(true);
+ MockServerStop server(*io_service_, &done);
+
+ MessagePtr answer(new Message(Message::RENDER));
+
+ // Do the answer
+ const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ // Set up the test so that it will retry 5 times, but the lookup
+ // timeout will fire after only 3 normal timeouts
+ RecursiveQuery query(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port),
+ 200, 4000, 480, 5);
+ Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ query.resolve(question, answer, buffer, &server);
+
+ // Run the test
+ io_service_->run();
+
+ int recv_options = setSocketTimeout(sock_, 1, 0);
+
+ // Try to read 5 times, should stop after 3 reads
+ int num = 0;
+ bool read_success = tryRead(sock_, recv_options, 5, &num);
+
+ // The query should fail and respond with an error
+ EXPECT_TRUE(done);
+ EXPECT_EQ(3, num);
+ EXPECT_FALSE(read_success);
+}
+
+// Set everything very low and see if this doesn't cause weird
+// behaviour
+TEST_F(RecursiveQueryTest, lowtimeouts) {
+ // Prepare the service (we do not use the common setup, we do not answer
+ setDNSService();
+
+ // Prepare the socket
+ sock_ = createTestSocket();
+
+ // Prepare the server
+ bool done(true);
+ MockServerStop server(*io_service_, &done);
+
+ MessagePtr answer(new Message(Message::RENDER));
+
+ // Do the answer
+ const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ // Set up the test so that it will retry 5 times, but the lookup
+ // timeout will fire after only 3 normal timeouts
+ RecursiveQuery query(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port),
+ 1, 1, 1, 1);
+ Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ query.resolve(question, answer, buffer, &server);
+
+ // Run the test
+ io_service_->run();
+
+ int recv_options = setSocketTimeout(sock_, 1, 0);
+
+ // Try to read 5 times, should stop after 3 reads
+ int num = 0;
+ bool read_success = tryRead(sock_, recv_options, 5, &num);
+
+ // The query should fail and respond with an error
+ EXPECT_TRUE(done);
+ EXPECT_EQ(1, num);
+ EXPECT_FALSE(read_success);
+}
+
+// as mentioned above, we need a more better framework for this,
+// in addition to that, this sends out queries into the world
+// (which we should catch somehow and fake replies for)
+// for the skeleton code, it shouldn't be too much of a problem
+// Ok so even we don't all have access to the DNS world right now,
+// so disabling these tests too.
+TEST_F(RecursiveQueryTest, DISABLED_recursiveSendOk) {
+ setDNSService(true, false);
+ bool done;
+
+ MockServerStop server(*io_service_, &done);
+ vector<pair<string, uint16_t> > empty_vector;
+ RecursiveQuery rq(*dns_service_, *nsas_, cache_, empty_vector,
+ empty_vector, 10000, 0);
+
+ Question q(Name("www.isc.org"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ rq.resolve(q, answer, buffer, &server);
+ io_service_->run();
+
+ // Check that the answer we got matches the one we wanted
+ EXPECT_EQ(Rcode::NOERROR(), answer->getRcode());
+ ASSERT_EQ(1, answer->getRRCount(Message::SECTION_ANSWER));
+ RRsetPtr a = *answer->beginSection(Message::SECTION_ANSWER);
+ EXPECT_EQ(q.getName(), a->getName());
+ EXPECT_EQ(q.getType(), a->getType());
+ EXPECT_EQ(q.getClass(), a->getClass());
+ EXPECT_EQ(1, a->getRdataCount());
+}
+
+// see comments at previous test
+TEST_F(RecursiveQueryTest, DISABLED_recursiveSendNXDOMAIN) {
+ setDNSService(true, false);
+ bool done;
+
+ MockServerStop server(*io_service_, &done);
+ vector<pair<string, uint16_t> > empty_vector;
+ RecursiveQuery rq(*dns_service_, *nsas_, cache_, empty_vector,
+ empty_vector, 10000, 0);
+
+ Question q(Name("wwwdoesnotexist.isc.org"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ rq.resolve(q, answer, buffer, &server);
+ io_service_->run();
+
+ // Check that the answer we got matches the one we wanted
+ EXPECT_EQ(Rcode::NXDOMAIN(), answer->getRcode());
+ EXPECT_EQ(0, answer->getRRCount(Message::SECTION_ANSWER));
+}
+
+// TODO: add tests that check whether the cache is updated on succesfull
+// responses, and not updated on failures.
+
+}
diff --git a/src/lib/resolve/tests/recursive_query_unittest_2.cc b/src/lib/resolve/tests/recursive_query_unittest_2.cc
new file mode 100644
index 0000000..12ff3cf
--- /dev/null
+++ b/src/lib/resolve/tests/recursive_query_unittest_2.cc
@@ -0,0 +1,656 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <algorithm>
+#include <cstdlib>
+#include <iomanip>
+#include <iostream>
+#include <string>
+
+#include <gtest/gtest.h>
+#include <boost/bind.hpp>
+
+
+#include <asio.hpp>
+
+#include <dns/buffer.h>
+#include <dns/question.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/opcode.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+
+#include <asiolink/asiolink_utilities.h>
+#include <asiolink/dns_service.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_fetch.h>
+#include <asiolink/io_service.h>
+#include <resolve/recursive_query.h>
+#include <resolve/resolver_interface.h>
+
+using namespace asio;
+using namespace asio::ip;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::resolve;
+using namespace std;
+
+/// RecursiveQuery Test - 2
+///
+/// The second part of the RecursiveQuery unit tests, this attempts to get the
+/// RecursiveQuery object to follow a set of referrals for "www.example.org" to
+/// and to invoke TCP fallback on one of the queries. In particular, we expect
+/// that the test will do the following in an attempt to resolve
+/// www.example.org:
+///
+/// - Send question over UDP to "root" - get referral to "org".
+/// - Send question over UDP to "org" - get referral to "example.org" with TC bit set.
+/// - Send question over TCP to "org" - get referral to "example.org".
+/// - Send question over UDP to "example.org" - get response for www.example.org.
+///
+/// (The order of queries is set in this way in order to also test that after a
+/// failover to TCP, queries revert to UDP).
+///
+/// By using the "test_server_" element of RecursiveQuery, all queries are
+/// directed to one or other of the "servers" in the RecursiveQueryTest2 class,
+/// regardless of the glue returned in referrals.
+
+namespace asiolink {
+
+const std::string TEST_ADDRESS = "127.0.0.1"; ///< Servers are on this address
+const uint16_t TEST_PORT = 5301; ///< ... and this port
+const size_t BUFFER_SIZE = 1024; ///< For all buffers
+const char* WWW_EXAMPLE_ORG = "192.0.2.254"; ///< Address of www.example.org
+
+// As the test is fairly long and complex, debugging "print" statements have
+// been left in although they are disabled. Set the following to "true" to
+// enable them.
+const bool DEBUG_PRINT = false;
+
+class MockResolver : public isc::resolve::ResolverInterface {
+ void resolve(const QuestionPtr& question,
+ const ResolverInterface::CallbackPtr& callback) {
+ }
+};
+
+
+
+/// \brief Test fixture for the RecursiveQuery Test
+class RecursiveQueryTest2 : public virtual ::testing::Test
+{
+public:
+
+ /// \brief Status of query
+ ///
+ /// Set before the query and then by each "server" when responding.
+ enum QueryStatus {
+ NONE = 0, ///< Default
+ UDP_ROOT = 1, ///< Query root server over UDP
+ UDP_ORG = 2, ///< Query ORG server over UDP
+ TCP_ORG = 3, ///< Query ORG server over TCP
+ UDP_EXAMPLE_ORG = 4, ///< Query EXAMPLE.ORG server over UDP
+ COMPLETE = 5 ///< Query is complete
+ };
+
+ // Common stuff
+ bool debug_; ///< Set true for debug print
+ IOService service_; ///< Service to run everything
+ DNSService dns_service_; ///< Resolver is part of "server"
+ QuestionPtr question_; ///< What to ask
+ QueryStatus last_; ///< What was the last state
+ QueryStatus expected_; ///< Expected next state
+ OutputBufferPtr question_buffer_; ///< Question we expect to receive
+ isc::nsas::NameserverAddressStore* nsas_;
+ isc::cache::ResolverCache cache_;
+
+ // Data for TCP Server
+ size_t tcp_cumulative_; ///< Cumulative TCP data received
+ tcp::endpoint tcp_endpoint_; ///< Endpoint for TCP receives
+ size_t tcp_length_; ///< Expected length value
+ uint8_t tcp_receive_buffer_[BUFFER_SIZE]; ///< Receive buffer for TCP I/O
+ OutputBufferPtr tcp_send_buffer_; ///< Send buffer for TCP I/O
+ tcp::socket tcp_socket_; ///< Socket used by TCP server
+
+ /// Data for UDP
+ udp::endpoint udp_remote_; ///< Endpoint for UDP receives
+ size_t udp_length_; ///< Expected length value
+ uint8_t udp_receive_buffer_[BUFFER_SIZE]; ///< Receive buffer for UDP I/O
+ OutputBufferPtr udp_send_buffer_; ///< Send buffer for UDP I/O
+ udp::socket udp_socket_; ///< Socket used by UDP server
+
+ /// \brief Constructor
+ RecursiveQueryTest2() :
+ debug_(DEBUG_PRINT),
+ service_(),
+ dns_service_(service_, NULL, NULL, NULL),
+ question_(new Question(Name("www.example.org"), RRClass::IN(), RRType::A())),
+ last_(NONE),
+ expected_(NONE),
+ question_buffer_(new OutputBuffer(BUFFER_SIZE)),
+ tcp_cumulative_(0),
+ tcp_endpoint_(asio::ip::address::from_string(TEST_ADDRESS), TEST_PORT),
+ tcp_length_(0),
+ tcp_receive_buffer_(),
+ tcp_send_buffer_(new OutputBuffer(BUFFER_SIZE)),
+ tcp_socket_(service_.get_io_service()),
+ udp_remote_(),
+ udp_length_(0),
+ udp_receive_buffer_(),
+ udp_send_buffer_(new OutputBuffer(BUFFER_SIZE)),
+ udp_socket_(service_.get_io_service(), udp::v4())
+ {
+ boost::shared_ptr<MockResolver>mock_resolver(new MockResolver());
+ nsas_ = new isc::nsas::NameserverAddressStore(mock_resolver);
+ }
+
+ /// \brief Set Common Message Bits
+ ///
+ /// Sets up the common bits of a response message returned by the handlers.
+ ///
+ /// \param msg Message buffer in RENDER mode.
+ /// \param qid QIT to set the message to
+ void setCommonMessage(isc::dns::Message& msg, uint16_t qid = 0) {
+ msg.setQid(qid);
+ msg.setHeaderFlag(Message::HEADERFLAG_QR);
+ msg.setOpcode(Opcode::QUERY());
+ msg.setHeaderFlag(Message::HEADERFLAG_AA);
+ msg.setRcode(Rcode::NOERROR());
+ msg.addQuestion(*question_);
+ }
+
+ /// \brief Set Referral to "org"
+ ///
+ /// Sets up the passed-in message (expected to be in "RENDER" mode to
+ /// indicate a referral to fictitious .org nameservers.
+ ///
+ /// \param msg Message to update with referral information.
+ void setReferralOrg(isc::dns::Message& msg) {
+ if (debug_) {
+ cout << "setReferralOrg(): creating referral to .org nameservers" << endl;
+ }
+
+ // Do a referral to org. We'll define all NS records as "in-zone"
+ // nameservers (and supply glue) to avoid the possibility of the
+ // resolver starting another recursive query to resolve the address of
+ // a nameserver.
+ RRsetPtr org_ns(new RRset(Name("org."), RRClass::IN(), RRType::NS(), RRTTL(300)));
+ org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns1.org."));
+ org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns2.org."));
+ msg.addRRset(Message::SECTION_AUTHORITY, org_ns);
+
+ RRsetPtr org_ns1(new RRset(Name("ns1.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
+ org_ns1->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.1"));
+ msg.addRRset(Message::SECTION_ADDITIONAL, org_ns1);
+
+ RRsetPtr org_ns2(new RRset(Name("ns2.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
+ org_ns2->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.2"));
+ msg.addRRset(Message::SECTION_ADDITIONAL, org_ns2);
+ }
+
+ /// \brief Set Referral to "example.org"
+ ///
+ /// Sets up the passed-in message (expected to be in "RENDER" mode to
+ /// indicate a referral to fictitious example.org nameservers.
+ ///
+ /// \param msg Message to update with referral information.
+ void setReferralExampleOrg(isc::dns::Message& msg) {
+ if (debug_) {
+ cout << "setReferralExampleOrg(): creating referral to example.org nameservers" << endl;
+ }
+
+ // Do a referral to example.org. As before, we'll define all NS
+ // records as "in-zone" nameservers (and supply glue) to avoid the
+ // possibility of the resolver starting another recursive query to look
+ // up the address of the nameserver.
+ RRsetPtr example_org_ns(new RRset(Name("example.org."), RRClass::IN(), RRType::NS(), RRTTL(300)));
+ example_org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns1.example.org."));
+ example_org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns2.example.org."));
+ msg.addRRset(Message::SECTION_AUTHORITY, example_org_ns);
+
+ RRsetPtr example_org_ns1(new RRset(Name("ns1.example.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
+ example_org_ns1->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.11"));
+ msg.addRRset(Message::SECTION_ADDITIONAL, example_org_ns1);
+
+ RRsetPtr example_org_ns2(new RRset(Name("ns2.example.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
+ example_org_ns2->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.21"));
+ msg.addRRset(Message::SECTION_ADDITIONAL, example_org_ns2);
+ }
+
+ /// \brief Set Answer to "www.example.org"
+ ///
+ /// Sets up the passed-in message (expected to be in "RENDER" mode) to
+ /// indicate an authoritative answer to www.example.org.
+ ///
+ /// \param msg Message to update with referral information.
+ void setAnswerWwwExampleOrg(isc::dns::Message& msg) {
+ if (debug_) {
+ cout << "setAnswerWwwExampleOrg(): creating answer for www.example.org" << endl;
+ }
+
+ // Give a response for www.example.org.
+ RRsetPtr www_example_org_a(new RRset(Name("www.example.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
+ www_example_org_a->addRdata(createRdata(RRType::A(), RRClass::IN(), WWW_EXAMPLE_ORG));
+ msg.addRRset(Message::SECTION_ANSWER, www_example_org_a);
+
+ // ... and add the Authority and Additional sections. (These are the
+ // same as in the referral to example.org from the .org nameserver.)
+ setReferralExampleOrg(msg);
+ }
+
+ /// \brief UDP Receive Handler
+ ///
+ /// This is invoked when a message is received over UDP from the
+ /// RecursiveQuery object under test. It formats an answer and sends it
+ /// asynchronously, with the UdpSendHandler method being specified as the
+ /// completion handler.
+ ///
+ /// \param ec ASIO error code, completion code of asynchronous I/O issued
+ /// by the "server" to receive data.
+ /// \param length Amount of data received.
+ void udpReceiveHandler(error_code ec = error_code(), size_t length = 0) {
+ if (debug_) {
+ cout << "udpReceiveHandler(): error = " << ec.value() <<
+ ", length = " << length << ", last state = " << last_ <<
+ ", expected state = " << expected_ << endl;
+ }
+
+ // Expected state should be one greater than the last state.
+ EXPECT_EQ(static_cast<int>(expected_), static_cast<int>(last_) + 1);
+ last_ = expected_;
+
+ // The QID in the incoming data is random so set it to 0 for the
+ // data comparison check. (It is set to 0 in the buffer containing
+ // the expected data.)
+ uint16_t qid = readUint16(udp_receive_buffer_);
+ udp_receive_buffer_[0] = udp_receive_buffer_[1] = 0;
+
+ // Check that question we received is what was expected.
+ checkReceivedPacket(udp_receive_buffer_, length);
+
+ // The message returned depends on what state we are in. Set up
+ // common stuff first: bits not mentioned are set to 0.
+ Message msg(Message::RENDER);
+ setCommonMessage(msg, qid);
+
+ // Set up state-dependent bits:
+ switch (expected_) {
+ case UDP_ROOT:
+ // Return a referral to org. We then expect to query the "org"
+ // nameservers over UDP next.
+ setReferralOrg(msg);
+ expected_ = UDP_ORG;
+ break;
+
+ case UDP_ORG:
+ // Return a referral to example.org. We explicitly set the TC bit to
+ // force a repeat query to the .org nameservers over TCP.
+ setReferralExampleOrg(msg);
+ if (debug_) {
+ cout << "udpReceiveHandler(): setting TC bit" << endl;
+ }
+ msg.setHeaderFlag(Message::HEADERFLAG_TC);
+ expected_ = TCP_ORG;
+ break;
+
+ case UDP_EXAMPLE_ORG:
+ // Return the answer to the question.
+ setAnswerWwwExampleOrg(msg);
+ expected_ = COMPLETE;
+ break;
+
+ default:
+ FAIL() << "UdpReceiveHandler called with unknown state";
+ }
+
+ // Convert to wire format
+ udp_send_buffer_->clear();
+ MessageRenderer renderer(*udp_send_buffer_);
+ msg.toWire(renderer);
+
+ // Return a message back to the IOFetch object (after setting the
+ // expected length of data for the check in the send handler).
+ udp_length_ = udp_send_buffer_->getLength();
+ udp_socket_.async_send_to(asio::buffer(udp_send_buffer_->getData(),
+ udp_send_buffer_->getLength()),
+ udp_remote_,
+ boost::bind(&RecursiveQueryTest2::udpSendHandler,
+ this, _1, _2));
+ }
+
+ /// \brief UDP Send Handler
+ ///
+ /// Called when a send operation of the UDP server (i.e. a response
+ /// being sent to the RecursiveQuery) has completed, this re-issues
+ /// a read call.
+ ///
+ /// \param ec Completion error code of the send.
+ /// \param length Actual number of bytes sent.
+ void udpSendHandler(error_code ec = error_code(), size_t length = 0) {
+ if (debug_) {
+ cout << "udpSendHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+
+ // Check send was OK
+ EXPECT_EQ(0, ec.value());
+ EXPECT_EQ(udp_length_, length);
+
+ // Reissue the receive call to await the next message.
+ udp_socket_.async_receive_from(
+ asio::buffer(udp_receive_buffer_, sizeof(udp_receive_buffer_)),
+ udp_remote_,
+ boost::bind(&RecursiveQueryTest2::udpReceiveHandler, this, _1, _2));
+ }
+
+ /// \brief Completion Handler for Accepting TCP Data
+ ///
+ /// Called when the remote system connects to the "TCP server". It issues
+ /// an asynchronous read on the socket to read data.
+ ///
+ /// \param socket Socket on which data will be received
+ /// \param ec Boost error code, value should be zero.
+ void tcpAcceptHandler(error_code ec = error_code(), size_t length = 0) {
+ if (debug_) {
+ cout << "tcpAcceptHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+
+ // Expect that the accept completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // Initiate a read on the socket, indicating that nothing has yet been
+ // received.
+ tcp_cumulative_ = 0;
+ tcp_socket_.async_receive(
+ asio::buffer(tcp_receive_buffer_, sizeof(tcp_receive_buffer_)),
+ boost::bind(&RecursiveQueryTest2::tcpReceiveHandler, this, _1, _2));
+ }
+
+ /// \brief Completion Handler for Receiving TCP Data
+ ///
+ /// Reads data from the RecursiveQuery object and loops, reissuing reads,
+ /// until all the message has been read. It then returns an appropriate
+ /// response.
+ ///
+ /// \param socket Socket to use to send the answer
+ /// \param ec ASIO error code, completion code of asynchronous I/O issued
+ /// by the "server" to receive data.
+ /// \param length Amount of data received.
+ void tcpReceiveHandler(error_code ec = error_code(), size_t length = 0) {
+ if (debug_) {
+ cout << "tcpReceiveHandler(): error = " << ec.value() <<
+ ", length = " << length <<
+ ", cumulative = " << tcp_cumulative_ << endl;
+ }
+
+ // Expect that the receive completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // Have we received all the data? We know this by checking if the two-
+ // byte length count in the message is equal to the data received.
+ tcp_cumulative_ += length;
+ bool complete = false;
+ if (tcp_cumulative_ > 2) {
+ uint16_t dns_length = readUint16(tcp_receive_buffer_);
+ complete = ((dns_length + 2) == tcp_cumulative_);
+ }
+
+ if (!complete) {
+ if (debug_) {
+ cout << "tcpReceiveHandler(): read not complete, " <<
+ "issuing another read" << endl;
+ }
+
+ // Not complete yet, issue another read.
+ tcp_socket_.async_receive(
+ asio::buffer(tcp_receive_buffer_ + tcp_cumulative_,
+ sizeof(tcp_receive_buffer_) - tcp_cumulative_),
+ boost::bind(&RecursiveQueryTest2::tcpReceiveHandler, this, _1, _2));
+ return;
+ }
+
+ // Have received a TCP message. Expected state should be one greater
+ // than the last state.
+ EXPECT_EQ(static_cast<int>(expected_), static_cast<int>(last_) + 1);
+ last_ = expected_;
+
+ // Check that question we received is what was expected. Note that we
+ // have to ignore the two-byte header in order to parse the message.
+ checkReceivedPacket(tcp_receive_buffer_ + 2, length - 2);
+
+ // Return a message back. This is a referral to example.org, which
+ // should result in another query over UDP. Note the setting of the
+ // QID in the returned message with what was in the received message.
+ Message msg(Message::RENDER);
+ setCommonMessage(msg, readUint16(tcp_receive_buffer_));
+ setReferralExampleOrg(msg);
+
+ // Convert to wire format
+ tcp_send_buffer_->clear();
+ MessageRenderer renderer(*tcp_send_buffer_);
+ msg.toWire(renderer);
+
+ // Expected next state (when checked) is the UDP query to example.org.
+ // Also, take this opportunity to clear the accumulated read count in
+ // readiness for the next read. (If any - at present, there is only
+ // one read in the test, although extensions to this test suite could
+ // change that.)
+ expected_ = UDP_EXAMPLE_ORG;
+ tcp_cumulative_ = 0;
+
+ // We'll write the message in two parts, the count and the message
+ // itself. This saves having to prepend the count onto the start of a
+ // buffer. When specifying the send handler, the expected size of the
+ // data written is passed as the first parameter so that the handler
+ // can check it.
+ uint8_t count[2];
+ writeUint16(tcp_send_buffer_->getLength(), count);
+ tcp_socket_.async_send(asio::buffer(count, 2),
+ boost::bind(&RecursiveQueryTest2::tcpSendHandler, this,
+ 2, _1, _2));
+ tcp_socket_.async_send(asio::buffer(tcp_send_buffer_->getData(),
+ tcp_send_buffer_->getLength()),
+ boost::bind(&RecursiveQueryTest2::tcpSendHandler, this,
+ tcp_send_buffer_->getLength(), _1, _2));
+ }
+
+ /// \brief Completion Handler for Sending TCP data
+ ///
+ /// Called when the asynchronous send of data back to the RecursiveQuery
+ /// by the TCP "server" in this class has completed. (This send has to
+ /// be asynchronous because control needs to return to the caller in order
+ /// for the IOService "run()" method to be called to run the handlers.)
+ ///
+ /// \param expected_length Number of bytes that were expected to have been sent.
+ /// \param ec Boost error code, value should be zero.
+ /// \param length Number of bytes sent.
+ void tcpSendHandler(size_t expected_length = 0, error_code ec = error_code(),
+ size_t length = 0)
+ {
+ if (debug_) {
+ cout << "tcpSendHandler(): error = " << ec.value() <<
+ ", length = " << length <<
+ ", (expected length = " << expected_length << ")" << endl;
+ }
+ EXPECT_EQ(0, ec.value()); // Expect no error
+ EXPECT_EQ(expected_length, length); // And that amount sent is as expected
+ }
+
+ /// \brief Check Received Packet
+ ///
+ /// Checks the packet received from the RecursiveQuery object to ensure
+ /// that the question is what is expected.
+ ///
+ /// \param data Start of data. This is the start of the received buffer in
+ /// the case of UDP data, and an offset into the buffer past the
+ /// count field for TCP data.
+ /// \param length Length of data.
+ void checkReceivedPacket(uint8_t* data, size_t length) {
+
+ // Decode the received buffer.
+ InputBuffer buffer(data, length);
+ Message message(Message::PARSE);
+ message.fromWire(buffer);
+
+ // Check the packet.
+ EXPECT_FALSE(message.getHeaderFlag(Message::HEADERFLAG_QR));
+
+ Question question = **(message.beginQuestion());
+ EXPECT_TRUE(question == *question_);
+ }
+};
+
+/// \brief Resolver Callback Object
+///
+/// Holds the success and failure callback methods for the resolver
+class ResolverCallback : public isc::resolve::ResolverInterface::Callback {
+public:
+ /// \brief Constructor
+ ResolverCallback(IOService& service) :
+ service_(service), run_(false), status_(false), debug_(DEBUG_PRINT)
+ {}
+
+ /// \brief Destructor
+ virtual ~ResolverCallback()
+ {}
+
+ /// \brief Resolver Callback Success
+ ///
+ /// Called if the resolver detects that the call has succeeded.
+ ///
+ /// \param response Answer to the question.
+ virtual void success(const isc::dns::MessagePtr response) {
+ if (debug_) {
+ cout << "ResolverCallback::success(): answer received" << endl;
+ }
+
+ // There should be one RR each in the question and answer sections, and
+ // two RRs in each of the the authority and additional sections.
+ EXPECT_EQ(1, response->getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(1, response->getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(2, response->getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(2, response->getRRCount(Message::SECTION_ADDITIONAL));
+
+ // Check the answer - that the RRset is there...
+ EXPECT_TRUE(response->hasRRset(Message::SECTION_ANSWER,
+ RRsetPtr(new RRset(Name("www.example.org."),
+ RRClass::IN(),
+ RRType::A(),
+ RRTTL(300)))));
+ const RRsetIterator rrset_i = response->beginSection(Message::SECTION_ANSWER);
+
+ // ... get iterator into the Rdata of this RRset and point to first
+ // element...
+ const RdataIteratorPtr rdata_i = (*rrset_i)->getRdataIterator();
+ rdata_i->first();
+
+ // ... and check it is what we expect.
+ EXPECT_EQ(string(WWW_EXAMPLE_ORG), rdata_i->getCurrent().toText());
+
+ // Flag completion
+ run_ = true;
+ status_ = true;
+
+ service_.stop(); // Cause run() to exit.
+ }
+
+ /// \brief Resolver Failure Completion
+ ///
+ /// Called if the resolver detects that the resolution has failed.
+ virtual void failure() {
+ if (debug_) {
+ cout << "ResolverCallback::success(): resolution failure" << endl;
+ }
+ FAIL() << "Resolver reported completion failure";
+
+ // Flag completion
+ run_ = true;
+ status_ = false;
+
+ service_.stop(); // Cause run() to exit.
+ }
+
+ /// \brief Return status of "run" flag
+ bool getRun() const {
+ return (run_);
+ }
+
+ /// \brief Return "status" flag
+ bool getStatus() const {
+ return (status_);
+ }
+
+private:
+ IOService& service_; ///< Service handling the run queue
+ bool run_; ///< Set true when completion handler run
+ bool status_; ///< Set true for success, false on error
+ bool debug_; ///< Debug flag
+};
+
+// Sets up the UDP and TCP "servers", then tries a resolution.
+
+TEST_F(RecursiveQueryTest2, Resolve) {
+
+ // Set up the UDP server and issue the first read. The endpoint from which
+ // the query is sent is put in udp_endpoint_ when the read completes, which
+ // is referenced in the callback as the place to which the response is sent.
+ udp_socket_.set_option(socket_base::reuse_address(true));
+ udp_socket_.bind(udp::endpoint(address::from_string(TEST_ADDRESS), TEST_PORT));
+ udp_socket_.async_receive_from(asio::buffer(udp_receive_buffer_,
+ sizeof(udp_receive_buffer_)),
+ udp_remote_,
+ boost::bind(&RecursiveQueryTest2::udpReceiveHandler,
+ this, _1, _2));
+
+ // Set up the TCP server and issue the accept. Acceptance will cause the
+ // read to be issued.
+ tcp::acceptor acceptor(service_.get_io_service(),
+ tcp::endpoint(tcp::v4(), TEST_PORT));
+ acceptor.async_accept(tcp_socket_,
+ boost::bind(&RecursiveQueryTest2::tcpAcceptHandler,
+ this, _1, 0));
+
+ // Set up the RecursiveQuery object.
+ std::vector<std::pair<std::string, uint16_t> > upstream; // Empty
+ std::vector<std::pair<std::string, uint16_t> > upstream_root; // Empty
+ RecursiveQuery query(dns_service_, *nsas_, cache_,
+ upstream, upstream_root);
+ query.setTestServer(TEST_ADDRESS, TEST_PORT);
+
+ // Set up callback for the tor eceive notification that the query has
+ // completed.
+ isc::resolve::ResolverInterface::CallbackPtr
+ resolver_callback(new ResolverCallback(service_));
+
+ // Kick off the resolution process. We expect the first question to go to
+ // "root".
+ expected_ = UDP_ROOT;
+ query.resolve(question_, resolver_callback);
+ service_.run();
+
+ // Check what ran. (We have to cast the callback to ResolverCallback as we
+ // lost the information on the derived class when we used a
+ // ResolverInterface::CallbackPtr to store a pointer to it.)
+ ResolverCallback* rc = static_cast<ResolverCallback*>(resolver_callback.get());
+ EXPECT_TRUE(rc->getRun());
+ EXPECT_TRUE(rc->getStatus());
+}
+
+} // namespace asiolink
diff --git a/src/lib/resolve/tests/resolve_unittest.cc b/src/lib/resolve/tests/resolve_unittest.cc
new file mode 100644
index 0000000..85d264d
--- /dev/null
+++ b/src/lib/resolve/tests/resolve_unittest.cc
@@ -0,0 +1,198 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <iostream>
+#include <gtest/gtest.h>
+
+#include <dns/message.h>
+#include <dns/question.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <resolve/resolve.h>
+
+using namespace isc::dns;
+
+namespace {
+
+class ResolveHelperFunctionsTest : public ::testing::Test {
+public:
+ ResolveHelperFunctionsTest() :
+ message_a_(new Message(Message::RENDER)),
+ message_b_(new Message(Message::RENDER)),
+ question_(new Question(Name("www.example.com"), RRClass::IN(), RRType::A()))
+ {
+ createMessageA();
+ createMessageB();
+ };
+
+ void createMessageA() {
+ message_a_->setOpcode(Opcode::QUERY());
+ message_a_->setRcode(Rcode::NOERROR());
+ message_a_->addQuestion(question_);
+ }
+
+ void createMessageB() {
+ message_b_->setOpcode(Opcode::QUERY());
+ message_b_->setRcode(Rcode::NOERROR());
+ message_b_->addQuestion(question_);
+
+ // We could reuse the same rrset in the different sections,
+ // but to be sure, we create separate ones
+ RRsetPtr answer_rrset(new RRset(Name("www.example.com"),
+ RRClass::IN(), RRType::TXT(),
+ RRTTL(3600)));
+ answer_rrset->addRdata(rdata::createRdata(RRType::TXT(),
+ RRClass::IN(),
+ "Answer"));
+ message_b_->addRRset(Message::SECTION_ANSWER, answer_rrset);
+
+ RRsetPtr auth_rrset(new RRset(Name("www.example.com"),
+ RRClass::IN(), RRType::TXT(),
+ RRTTL(3600)));
+ auth_rrset->addRdata(rdata::createRdata(RRType::TXT(),
+ RRClass::IN(),
+ "Authority"));
+ auth_rrset->addRdata(rdata::createRdata(RRType::TXT(),
+ RRClass::IN(),
+ "Rdata"));
+ message_b_->addRRset(Message::SECTION_AUTHORITY, auth_rrset);
+
+ RRsetPtr add_rrset(new RRset(Name("www.example.com"),
+ RRClass::IN(), RRType::TXT(),
+ RRTTL(3600)));
+ add_rrset->addRdata(rdata::createRdata(RRType::TXT(),
+ RRClass::IN(),
+ "Additional"));
+ add_rrset->addRdata(rdata::createRdata(RRType::TXT(),
+ RRClass::IN(),
+ "Rdata"));
+ add_rrset->addRdata(rdata::createRdata(RRType::TXT(),
+ RRClass::IN(),
+ "fields."));
+ message_b_->addRRset(Message::SECTION_ADDITIONAL, add_rrset);
+ };
+
+ MessagePtr message_a_;
+ MessagePtr message_b_;
+ QuestionPtr question_;
+};
+
+TEST_F(ResolveHelperFunctionsTest, makeErrorMessageEmptyMessage) {
+ ASSERT_EQ(Rcode::NOERROR(), message_a_->getRcode());
+ ASSERT_EQ(1, message_a_->getRRCount(Message::SECTION_QUESTION));
+ ASSERT_EQ(0, message_a_->getRRCount(Message::SECTION_ANSWER));
+ ASSERT_EQ(0, message_a_->getRRCount(Message::SECTION_AUTHORITY));
+ ASSERT_EQ(0, message_a_->getRRCount(Message::SECTION_ADDITIONAL));
+
+ isc::resolve::makeErrorMessage(message_a_, Rcode::SERVFAIL());
+ EXPECT_EQ(Rcode::SERVFAIL(), message_a_->getRcode());
+ EXPECT_EQ(1, message_a_->getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(0, message_a_->getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, message_a_->getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, message_a_->getRRCount(Message::SECTION_ADDITIONAL));
+}
+
+TEST_F(ResolveHelperFunctionsTest, makeErrorMessageNonEmptyMessage) {
+
+ ASSERT_EQ(Rcode::NOERROR(), message_b_->getRcode());
+ ASSERT_EQ(1, message_b_->getRRCount(Message::SECTION_QUESTION));
+ ASSERT_EQ(1, message_b_->getRRCount(Message::SECTION_ANSWER));
+ ASSERT_EQ(2, message_b_->getRRCount(Message::SECTION_AUTHORITY));
+ ASSERT_EQ(3, message_b_->getRRCount(Message::SECTION_ADDITIONAL));
+
+ isc::resolve::makeErrorMessage(message_b_, Rcode::FORMERR());
+ EXPECT_EQ(Rcode::FORMERR(), message_b_->getRcode());
+ EXPECT_EQ(1, message_b_->getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(0, message_b_->getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, message_b_->getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, message_b_->getRRCount(Message::SECTION_ADDITIONAL));
+}
+
+void
+compareSections(const Message& message_a, const Message& message_b,
+ Message::Section section)
+{
+ RRsetIterator rrs_a = message_a.beginSection(section);
+ RRsetIterator rrs_b = message_b.beginSection(section);
+ while (rrs_a != message_a.endSection(section) &&
+ rrs_b != message_b.endSection(section)
+ ) {
+ EXPECT_EQ(*rrs_a, *rrs_b);
+ ++rrs_a;
+ ++rrs_b;
+ }
+ // can't use EXPECT_EQ here, no eqHelper for endsection comparison
+ EXPECT_TRUE(rrs_a == message_a.endSection(section));
+ EXPECT_TRUE(rrs_b == message_b.endSection(section));
+}
+
+TEST_F(ResolveHelperFunctionsTest, initResponseMessage) {
+ Message response_parse(Message::PARSE);
+ EXPECT_THROW(isc::resolve::initResponseMessage(*message_a_,
+ response_parse),
+ isc::dns::InvalidMessageOperation);
+ EXPECT_THROW(isc::resolve::initResponseMessage(*question_,
+ response_parse),
+ isc::dns::InvalidMessageOperation);
+
+ Message response1(Message::RENDER);
+ isc::resolve::initResponseMessage(*message_a_, response1);
+ ASSERT_EQ(message_a_->getOpcode(), response1.getOpcode());
+ ASSERT_EQ(message_a_->getQid(), response1.getQid());
+ isc::dns::QuestionIterator qi = response1.beginQuestion();
+ ASSERT_EQ(*question_, **qi);
+ ASSERT_TRUE(++qi == response1.endQuestion());
+
+ Message response2(Message::RENDER);
+ isc::resolve::initResponseMessage(*question_, response2);
+ ASSERT_EQ(Opcode::QUERY(), response2.getOpcode());
+ ASSERT_EQ(0, response2.getQid());
+ qi = response2.beginQuestion();
+ ASSERT_EQ(*question_, **qi);
+ ASSERT_TRUE(++qi == response2.endQuestion());
+}
+
+TEST_F(ResolveHelperFunctionsTest, copyAnswerMessage) {
+ message_b_->setRcode(Rcode::NXDOMAIN());
+
+ ASSERT_NE(message_b_->getRcode(), message_a_->getRcode());
+ ASSERT_NE(message_b_->getRRCount(Message::SECTION_ANSWER),
+ message_a_->getRRCount(Message::SECTION_ANSWER));
+ ASSERT_NE(message_b_->getRRCount(Message::SECTION_AUTHORITY),
+ message_a_->getRRCount(Message::SECTION_AUTHORITY));
+ ASSERT_NE(message_b_->getRRCount(Message::SECTION_ADDITIONAL),
+ message_a_->getRRCount(Message::SECTION_ADDITIONAL));
+ ASSERT_EQ(0, message_a_->getRRCount(Message::SECTION_ANSWER));
+ ASSERT_EQ(0, message_a_->getRRCount(Message::SECTION_AUTHORITY));
+ ASSERT_EQ(0, message_a_->getRRCount(Message::SECTION_ADDITIONAL));
+
+ isc::resolve::copyResponseMessage(*message_b_, message_a_);
+
+ EXPECT_EQ(message_b_->getRcode(), message_a_->getRcode());
+ ASSERT_EQ(message_b_->getRRCount(Message::SECTION_ANSWER),
+ message_a_->getRRCount(Message::SECTION_ANSWER));
+ ASSERT_EQ(message_b_->getRRCount(Message::SECTION_AUTHORITY),
+ message_a_->getRRCount(Message::SECTION_AUTHORITY));
+ ASSERT_EQ(message_b_->getRRCount(Message::SECTION_ADDITIONAL),
+ message_a_->getRRCount(Message::SECTION_ADDITIONAL));
+
+
+ compareSections(*message_a_, *message_b_, Message::SECTION_ANSWER);
+ compareSections(*message_a_, *message_b_, Message::SECTION_AUTHORITY);
+ compareSections(*message_a_, *message_b_, Message::SECTION_ADDITIONAL);
+}
+
+} // Anonymous namespace
diff --git a/src/lib/resolve/tests/response_classifier_unittest.cc b/src/lib/resolve/tests/response_classifier_unittest.cc
new file mode 100644
index 0000000..23c8666
--- /dev/null
+++ b/src/lib/resolve/tests/response_classifier_unittest.cc
@@ -0,0 +1,554 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <iostream>
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+
+#include <resolve/response_classifier.h>
+
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/question.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace rdata;
+using namespace isc::dns::rdata::generic;
+using namespace isc::dns::rdata::in;
+using namespace isc::resolve;
+
+
+namespace {
+class ResponseClassifierTest : public ::testing::Test {
+public:
+ /// \brief Constructor
+ ///
+ /// The naming convention is:
+ ///
+ /// <category>_<class>_<type>_<name>
+ ///
+ /// <category> is "qu" (question), "rrs" (rrset),
+ /// <qclass> is self-explanatory
+ /// <qtype> is self-explanatory
+ /// <name> is the first part of the domain name (all expected to be in
+ /// example.com)
+ ///
+ /// Message variables
+ ///
+ /// msg_<qtype> Where <qtype> is the type of query. These are only used
+ /// in the early tests where simple messages are required.
+
+ ResponseClassifierTest() :
+ msg_a(Message::RENDER),
+ msg_any(Message::RENDER),
+ qu_ch_a_www(Name("www.example.com"), RRClass::CH(), RRType::A()),
+ qu_in_any_www(Name("www.example.com"), RRClass::IN(), RRType::ANY()),
+ qu_in_a_www2(Name("www2.example.com"), RRClass::IN(), RRType::A()),
+ qu_in_a_www(Name("www.example.com"), RRClass::IN(), RRType::A()),
+ qu_in_cname_www1(Name("www1.example.com"), RRClass::IN(), RRType::A()),
+ qu_in_ns_(Name("example.com"), RRClass::IN(), RRType::NS()),
+ qu_in_txt_www(Name("www.example.com"), RRClass::IN(), RRType::TXT()),
+ rrs_hs_txt_www(new RRset(Name("www.example.com"), RRClass::HS(),
+ RRType::TXT(), RRTTL(300))),
+ rrs_in_a_mail(new RRset(Name("mail.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_a_www(new RRset(Name("www.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(300))),
+ rrs_in_cname_www1(new RRset(Name("www1.example.com"), RRClass::IN(),
+ RRType::CNAME(), RRTTL(300))),
+ rrs_in_cname_www2(new RRset(Name("www2.example.com"), RRClass::IN(),
+ RRType::CNAME(), RRTTL(300))),
+ rrs_in_ns_(new RRset(Name("example.com"), RRClass::IN(),
+ RRType::NS(), RRTTL(300))),
+ rrs_in_soa_(new RRset(Name("example.com"), RRClass::IN(),
+ RRType::SOA(), RRTTL(300))),
+ rrs_in_txt_www(new RRset(Name("www.example.com"), RRClass::IN(),
+ RRType::TXT(), RRTTL(300))),
+ cname_target("."),
+ cname_count(0)
+ {
+ // Set up the message to indicate a successful response to the question
+ // "www.example.com A", but don't add in any response sections.
+ msg_a.setHeaderFlag(Message::HEADERFLAG_QR);
+ msg_a.setOpcode(Opcode::QUERY());
+ msg_a.setRcode(Rcode::NOERROR());
+ msg_a.addQuestion(qu_in_a_www);
+
+ // ditto for the query "www.example.com ANY"
+ msg_any.setHeaderFlag(Message::HEADERFLAG_QR);
+ msg_any.setOpcode(Opcode::QUERY());
+ msg_any.setRcode(Rcode::NOERROR());
+ msg_any.addQuestion(qu_in_any_www);
+
+ // The next set of assignments set up the following zone records
+ //
+ // example.com NS ns0.isc.org
+ // NS ns0.example.org
+ //
+ // www.example.com A 1.2.3.4
+ // TXT "An example text string"
+ //
+ // mail.example.com A 4.5.6.7
+ //
+ // www1.example.com CNAME www.example.com
+ //
+ // www2.example.com CNAME www1.example.com
+
+ // Set up an imaginary NS RRset for an authority section
+ rrs_in_ns_->addRdata(ConstRdataPtr(new NS(Name("ns0.isc.org"))));
+ rrs_in_ns_->addRdata(ConstRdataPtr(new NS(Name("ns0.example.org"))));
+
+ // And an imaginary SOA
+ rrs_in_soa_->addRdata(ConstRdataPtr(new SOA(Name("ns0.example.org"), Name("root.example.org"), 1, 2, 3, 4, 5)));
+
+ // Set up the records for the www host
+ rrs_in_a_www->addRdata(ConstRdataPtr(new A("1.2.3.4")));
+ rrs_in_txt_www->addRdata(ConstRdataPtr(
+ new TXT("An example text string")));
+
+ // ... for the mail host
+ rrs_in_a_mail->addRdata(ConstRdataPtr(new A("5.6.7.8")));
+
+ // ... the CNAME records
+ rrs_in_cname_www1->addRdata(ConstRdataPtr(
+ new CNAME("www.example.com")));
+ rrs_in_cname_www2->addRdata(ConstRdataPtr(
+ new CNAME("www1.example.com")));
+ }
+
+ Message msg_a; // Pointer to message in RENDER state
+ Message msg_any; // Pointer to message in RENDER state
+ Question qu_ch_a_www; // www.example.com CH A
+ Question qu_in_any_www; // www.example.com IN ANY
+ Question qu_in_a_www2; // www.example.com IN ANY
+ Question qu_in_a_www; // www.example.com IN A
+ Question qu_in_cname_www1; // www1.example.com IN CNAME
+ Question qu_in_ns_; // example.com IN NS
+ Question qu_in_txt_www; // www.example.com IN TXT
+ RRsetPtr rrs_hs_txt_www; // www.example.com HS TXT
+ RRsetPtr rrs_in_a_mail; // mail.example.com IN A
+ RRsetPtr rrs_in_a_www; // www.example.com IN A
+ RRsetPtr rrs_in_cname_www1; // www1.example.com IN CNAME
+ RRsetPtr rrs_in_cname_www2; // www2.example.com IN CNAME
+ RRsetPtr rrs_in_ns_; // example.com IN NS
+ RRsetPtr rrs_in_soa_; // example.com IN SOA
+ RRsetPtr rrs_in_txt_www; // www.example.com IN TXT
+ Name cname_target; // Used in response classifier to
+ // store the target of a possible
+ // CNAME chain
+ unsigned int cname_count; // Used to count cnames in a chain
+};
+
+// Test that the error() function categorises the codes correctly.
+
+TEST_F(ResponseClassifierTest, StatusCodes) {
+ EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::ANSWER));
+ EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::ANSWERCNAME));
+ EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::CNAME));
+ EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::NXDOMAIN));
+ EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::REFERRAL));
+
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::EMPTY));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::EXTRADATA));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::INVNAMCLASS));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::INVTYPE));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::MISMATQUEST));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::MULTICLASS));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::NOTONEQUEST));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::NOTRESPONSE));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::NOTSINGLE));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::OPCODE));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::RCODE));
+ EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::TRUNCATED));
+}
+
+// Test that the system will reject a message which is a query.
+
+TEST_F(ResponseClassifierTest, Query) {
+
+ // Set up message to indicate a query (QR flag = 0, one question). By
+ // default the opcode will be 0 (query)
+ msg_a.setHeaderFlag(Message::HEADERFLAG_QR, false);
+
+ // Should be rejected as it is a query, not a response
+ EXPECT_EQ(ResponseClassifier::NOTRESPONSE,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+}
+
+// Check that we get an OPCODE error on all but QUERY opcodes.
+
+TEST_F(ResponseClassifierTest, Opcode) {
+
+ uint8_t query = static_cast<uint8_t>(Opcode::QUERY().getCode());
+
+ for (uint8_t i = 0; i < (1 << 4); ++i) {
+ msg_a.setOpcode(Opcode(i));
+ if (i == query) {
+ EXPECT_NE(ResponseClassifier::OPCODE,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+ } else {
+ EXPECT_EQ(ResponseClassifier::OPCODE,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+ }
+ }
+}
+
+// Test that the system will reject a response with anything other than one
+// question.
+
+TEST_F(ResponseClassifierTest, MultipleQuestions) {
+
+ // Create a message object for this test that has no question section.
+ Message message(Message::RENDER);
+ message.setHeaderFlag(Message::HEADERFLAG_QR);
+ message.setOpcode(Opcode::QUERY());
+ message.setRcode(Rcode::NOERROR());
+
+ // Zero questions
+ EXPECT_EQ(ResponseClassifier::NOTONEQUEST,
+ ResponseClassifier::classify(qu_in_a_www, message,
+ cname_target, cname_count));
+
+ // One question
+ message.addQuestion(qu_in_a_www);
+ EXPECT_NE(ResponseClassifier::NOTONEQUEST,
+ ResponseClassifier::classify(qu_in_a_www, message,
+ cname_target, cname_count));
+
+ // Two questions
+ message.addQuestion(qu_in_ns_);
+ EXPECT_EQ(ResponseClassifier::NOTONEQUEST,
+ ResponseClassifier::classify(qu_in_a_www, message,
+ cname_target, cname_count));
+
+ // And finish the check with three questions
+ message.addQuestion(qu_in_txt_www);
+ EXPECT_EQ(ResponseClassifier::NOTONEQUEST,
+ ResponseClassifier::classify(qu_in_a_www, message,
+ cname_target, cname_count));
+}
+
+// Test that the question in the question section in the message response
+// is equal to the question supplied.
+
+TEST_F(ResponseClassifierTest, SameQuestion) {
+
+ EXPECT_EQ(ResponseClassifier::MISMATQUEST,
+ ResponseClassifier::classify(qu_in_ns_, msg_a,
+ cname_target, cname_count));
+ EXPECT_NE(ResponseClassifier::MISMATQUEST,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+}
+
+// Should get an NXDOMAIN response only on an NXDOMAIN RCODE.
+
+TEST_F(ResponseClassifierTest, NXDOMAIN) {
+
+ uint16_t nxdomain = static_cast<uint16_t>(Rcode::NXDOMAIN().getCode());
+
+ for (uint8_t i = 0; i < (1 << 4); ++i) {
+ msg_a.setRcode(Rcode(i));
+ if (i == nxdomain) {
+ EXPECT_EQ(ResponseClassifier::NXDOMAIN,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+ } else {
+ EXPECT_NE(ResponseClassifier::NXDOMAIN,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+ }
+ }
+}
+
+// Check that we get an RCODE error on all but NXDOMAIN and NOERROR responses.
+
+TEST_F(ResponseClassifierTest, RCODE) {
+
+ uint16_t nxdomain = static_cast<uint16_t>(Rcode::NXDOMAIN().getCode());
+ uint16_t noerror = static_cast<uint16_t>(Rcode::NOERROR().getCode());
+
+ for (uint8_t i = 0; i < (1 << 4); ++i) {
+ msg_a.setRcode(Rcode(i));
+ if ((i == nxdomain) || (i == noerror)) {
+ EXPECT_NE(ResponseClassifier::RCODE,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+ } else {
+ EXPECT_EQ(ResponseClassifier::RCODE,
+ ResponseClassifier::classify(qu_in_a_www, msg_a,
+ cname_target, cname_count));
+ }
+ }
+}
+
+// Test that the code will detect a truncated message. Even if nothing else
+// is wrong, we'll want to retry the query if we receive a truncated code.
+// However, we give the option to the user of the code aws to whether they
+// want to take into account the truncated bit.
+
+TEST_F(ResponseClassifierTest, Truncated) {
+
+ // Don't expect the truncated code whatever option we ask for if the TC
+ // bit is not set.
+ msg_a.setHeaderFlag(Message::HEADERFLAG_TC, false);
+ EXPECT_NE(ResponseClassifier::TRUNCATED,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count, true));
+ EXPECT_NE(ResponseClassifier::TRUNCATED,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count, false));
+
+ // Expect the truncated code if the TC bit is set, only if we don't ignore
+ // it.
+ msg_a.setHeaderFlag(Message::HEADERFLAG_TC, true);
+ EXPECT_NE(ResponseClassifier::TRUNCATED,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count, true));
+ EXPECT_EQ(ResponseClassifier::TRUNCATED,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count, false));
+}
+
+// Check for an empty packet (i.e. no error, but with the answer and additional
+// sections empty).
+
+TEST_F(ResponseClassifierTest, Empty) {
+
+ EXPECT_EQ(ResponseClassifier::EMPTY,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count));
+}
+
+// Anything where we have an empty answer section but something in the
+// authority section is a referral (if the status is NOERROR).
+
+TEST_F(ResponseClassifierTest, EmptyAnswerReferral) {
+
+ msg_a.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_);
+ EXPECT_EQ(ResponseClassifier::REFERRAL,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count));
+
+}
+
+// Test if we get a NOERROR answer that contains neither an actual
+// answer nor a delegation
+TEST_F(ResponseClassifierTest, NoErrorNoData) {
+
+ msg_a.addRRset(Message::SECTION_AUTHORITY, rrs_in_soa_);
+ EXPECT_EQ(ResponseClassifier::NXRRSET,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count));
+
+}
+
+// Check the case where we have a simple answer in the answer section. This
+// occurs when the QNAME/QTYPE/QCLASS matches one of the RRsets in the
+// answer section - expect when the QTYPE is ANY, in which case the match
+// must be on the QNAME/QCLASS alone.
+
+TEST_F(ResponseClassifierTest, SingleAnswer) {
+
+ // Check a question that matches the answer
+ msg_a.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ EXPECT_EQ(ResponseClassifier::ANSWER,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count));
+
+ // Check an ANY question that matches the answer
+ msg_any.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ EXPECT_EQ(ResponseClassifier::ANSWER,
+ ResponseClassifier::classify(qu_in_any_www, msg_any, cname_target,
+ cname_count));
+
+ // Check a CNAME response that matches the QNAME.
+ Message message_a(Message::RENDER);
+ message_a.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_a.setOpcode(Opcode::QUERY());
+ message_a.setRcode(Rcode::NOERROR());
+ message_a.addQuestion(qu_in_cname_www1);
+ message_a.addRRset(Message::SECTION_ANSWER, rrs_in_cname_www1);
+ EXPECT_EQ(ResponseClassifier::CNAME,
+ ResponseClassifier::classify(qu_in_cname_www1, message_a,
+ cname_target, cname_count));
+
+ // Check if the answer QNAME does not match the question
+ // Q: www.example.com IN A
+ // A: mail.example.com IN A
+ Message message_b(Message::RENDER);
+ message_b.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_b.setOpcode(Opcode::QUERY());
+ message_b.setRcode(Rcode::NOERROR());
+ message_b.addQuestion(qu_in_a_www);
+ message_b.addRRset(Message::SECTION_ANSWER, rrs_in_a_mail);
+ EXPECT_EQ(ResponseClassifier::INVNAMCLASS,
+ ResponseClassifier::classify(qu_in_a_www, message_b,
+ cname_target, cname_count));
+
+ // Check if the answer class does not match the question
+ // Q: www.example.com CH A
+ // A: www.example.com IN A
+ Message message_c(Message::RENDER);
+ message_c.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_c.setOpcode(Opcode::QUERY());
+ message_c.setRcode(Rcode::NOERROR());
+ message_c.addQuestion(qu_ch_a_www);
+ message_c.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ EXPECT_EQ(ResponseClassifier::INVNAMCLASS,
+ ResponseClassifier::classify(qu_ch_a_www, message_c,
+ cname_target, cname_count));
+
+ // Check if the answer type does not match the question
+ // Q: www.example.com IN A
+ // A: www.example.com IN TXT
+ Message message_d(Message::RENDER);
+ message_d.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_d.setOpcode(Opcode::QUERY());
+ message_d.setRcode(Rcode::NOERROR());
+ message_d.addQuestion(qu_in_a_www);
+ message_d.addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+ EXPECT_EQ(ResponseClassifier::INVTYPE,
+ ResponseClassifier::classify(qu_in_a_www, message_d,
+ cname_target, cname_count));
+}
+
+// Check what happens if we have multiple RRsets in the answer.
+
+TEST_F(ResponseClassifierTest, MultipleAnswerRRsets) {
+
+ // All the same QNAME but different types is only valid on an ANY query.
+ Message message_a(Message::RENDER);
+ message_a.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_a.setOpcode(Opcode::QUERY());
+ message_a.setRcode(Rcode::NOERROR());
+ message_a.addQuestion(qu_in_any_www);
+ message_a.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ message_a.addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+ EXPECT_EQ(ResponseClassifier::ANSWER,
+ ResponseClassifier::classify(qu_in_any_www, message_a,
+ cname_target, cname_count));
+
+ // On another type of query, it results in an EXTRADATA error
+ Message message_b(Message::RENDER);
+ message_b.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_b.setOpcode(Opcode::QUERY());
+ message_b.setRcode(Rcode::NOERROR());
+ message_b.addQuestion(qu_in_a_www);
+ message_b.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ message_b.addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+ EXPECT_EQ(ResponseClassifier::EXTRADATA,
+ ResponseClassifier::classify(qu_in_a_www, message_b,
+ cname_target, cname_count));
+
+ // Same QNAME on an ANY query is not valid with mixed classes
+ Message message_c(Message::RENDER);
+ message_c.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_c.setOpcode(Opcode::QUERY());
+ message_c.setRcode(Rcode::NOERROR());
+ message_c.addQuestion(qu_in_any_www);
+ message_c.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ message_c.addRRset(Message::SECTION_ANSWER, rrs_hs_txt_www);
+ EXPECT_EQ(ResponseClassifier::MULTICLASS,
+ ResponseClassifier::classify(qu_in_any_www, message_c,
+ cname_target, cname_count));
+
+ // Mixed QNAME is not valid unless QNAME requested is a CNAME.
+ Message message_d(Message::RENDER);
+ message_d.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_d.setOpcode(Opcode::QUERY());
+ message_d.setRcode(Rcode::NOERROR());
+ message_d.addQuestion(qu_in_a_www);
+ message_d.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ message_d.addRRset(Message::SECTION_ANSWER, rrs_in_a_mail);
+ EXPECT_EQ(ResponseClassifier::EXTRADATA,
+ ResponseClassifier::classify(qu_in_a_www, message_d,
+ cname_target, cname_count));
+
+ // Mixed QNAME is not valid when the query is an ANY.
+ Message message_e(Message::RENDER);
+ message_e.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_e.setOpcode(Opcode::QUERY());
+ message_e.setRcode(Rcode::NOERROR());
+ message_e.addQuestion(qu_in_any_www);
+ message_e.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ message_e.addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+ message_e.addRRset(Message::SECTION_ANSWER, rrs_in_a_mail);
+ EXPECT_EQ(ResponseClassifier::EXTRADATA,
+ ResponseClassifier::classify(qu_in_any_www, message_e,
+ cname_target, cname_count));
+}
+
+// CNAME chain is CNAME if it terminates in a CNAME, answer if it
+// does not, and error if there are RRs left over.
+TEST_F(ResponseClassifierTest, CNAMEChain) {
+
+ // Answer contains a single CNAME
+ Message message_a(Message::RENDER);
+ message_a.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_a.setOpcode(Opcode::QUERY());
+ message_a.setRcode(Rcode::NOERROR());
+ message_a.addQuestion(qu_in_a_www2);
+ message_a.addRRset(Message::SECTION_ANSWER, rrs_in_cname_www2);
+ EXPECT_EQ(ResponseClassifier::CNAME,
+ ResponseClassifier::classify(qu_in_a_www2, message_a,
+ cname_target, cname_count));
+
+ // Add a CNAME for www1, and it should still return a CNAME
+ message_a.addRRset(Message::SECTION_ANSWER, rrs_in_cname_www1);
+ EXPECT_EQ(ResponseClassifier::CNAME,
+ ResponseClassifier::classify(qu_in_a_www2, message_a,
+ cname_target, cname_count));
+
+ // Add the A record for www and it should be an answer
+ message_a.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+ EXPECT_EQ(ResponseClassifier::ANSWERCNAME,
+ ResponseClassifier::classify(qu_in_a_www2, message_a,
+ cname_target, cname_count));
+
+ // Adding an unrelated TXT record should result in EXTRADATA
+ message_a.addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+ EXPECT_EQ(ResponseClassifier::EXTRADATA,
+ ResponseClassifier::classify(qu_in_a_www2, message_a,
+ cname_target, cname_count));
+
+ // Recreate the chain, but this time end with a TXT RR and not the A
+ // record. This should return INVTYPE.
+ Message message_b(Message::RENDER);
+ message_b.setHeaderFlag(Message::HEADERFLAG_QR);
+ message_b.setOpcode(Opcode::QUERY());
+ message_b.setRcode(Rcode::NOERROR());
+ message_b.addQuestion(qu_in_a_www2);
+ message_b.addRRset(Message::SECTION_ANSWER, rrs_in_cname_www2);
+ message_b.addRRset(Message::SECTION_ANSWER, rrs_in_cname_www1);
+ message_b.addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+
+ EXPECT_EQ(ResponseClassifier::INVTYPE,
+ ResponseClassifier::classify(qu_in_a_www2, message_b,
+ cname_target, cname_count));
+}
+
+} // Anonymous namespace
diff --git a/src/lib/server_common/Makefile.am b/src/lib/server_common/Makefile.am
new file mode 100644
index 0000000..dfb3014
--- /dev/null
+++ b/src/lib/server_common/Makefile.am
@@ -0,0 +1,26 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_CLANGPP
+# clang++ complains about unused function parameters in some boost header
+# files.
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+lib_LTLIBRARIES = libserver_common.la
+libserver_common_la_SOURCES = portconfig.h portconfig.cc
+libserver_common_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+libserver_common_la_LIBADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+libserver_common_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
+libserver_common_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
+
+CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/server_common/portconfig.cc b/src/lib/server_common/portconfig.cc
new file mode 100644
index 0000000..3765f52
--- /dev/null
+++ b/src/lib/server_common/portconfig.cc
@@ -0,0 +1,119 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <server_common/portconfig.h>
+
+#include <asiolink/io_address.h>
+#include <asiolink/dns_service.h>
+#include <log/dummylog.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+using namespace std;
+using namespace isc::data;
+using namespace asiolink;
+using isc::log::dlog;
+
+namespace isc {
+namespace server_common {
+namespace portconfig {
+
+AddressList
+parseAddresses(isc::data::ConstElementPtr addresses,
+ const std::string& elemName)
+{
+ AddressList result;
+ if (addresses) {
+ if (addresses->getType() == Element::list) {
+ for (size_t i(0); i < addresses->size(); ++ i) {
+ ConstElementPtr addrPair(addresses->get(i));
+ ConstElementPtr addr(addrPair->get("address"));
+ ConstElementPtr port(addrPair->get("port"));
+ if (!addr || ! port) {
+ isc_throw(BadValue, "Address must contain both the IP"
+ "address and port");
+ }
+ try {
+ IOAddress(addr->stringValue());
+ if (port->intValue() < 0 ||
+ port->intValue() > 0xffff) {
+ isc_throw(BadValue, "Bad port value (" <<
+ port->intValue() << ")");
+ }
+ result.push_back(AddressPair(addr->stringValue(),
+ port->intValue()));
+ }
+ catch (const TypeError &e) { // Better error message
+ isc_throw(TypeError,
+ "Address must be a string and port an integer");
+ }
+ }
+ } else if (addresses->getType() != Element::null) {
+ isc_throw(TypeError, elemName + " config element must be a list");
+ }
+ }
+ return (result);
+}
+
+namespace {
+
+void
+setAddresses(DNSService& service, const AddressList& addresses) {
+ service.clearServers();
+ BOOST_FOREACH(const AddressPair &address, addresses) {
+ service.addServer(address.second, address.first);
+ }
+}
+
+}
+
+void
+installListenAddresses(const AddressList& newAddresses,
+ AddressList& addressStore,
+ asiolink::DNSService& service)
+{
+ try {
+ dlog("Setting listen addresses:");
+ BOOST_FOREACH(const AddressPair& addr, newAddresses) {
+ dlog(" " + addr.first + ":" +
+ boost::lexical_cast<string>(addr.second));
+ }
+ setAddresses(service, newAddresses);
+ addressStore = newAddresses;
+ }
+ catch (const exception& e) {
+ /*
+ * We couldn't set it. So return it back. If that fails as well,
+ * we have a problem.
+ *
+ * If that fails, bad luck, but we are useless anyway, so just die
+ * and let boss start us again.
+ */
+ dlog(string("Unable to set new address: ") + e.what(), true);
+ try {
+ setAddresses(service, addressStore);
+ }
+ catch (const exception& e2) {
+ dlog("Unable to recover from error;", true);
+ dlog(string("Rollback failed with: ") + e2.what(), true);
+ abort();
+ }
+ throw; // Let it fly a little bit further
+ }
+}
+
+}
+}
+}
diff --git a/src/lib/server_common/portconfig.h b/src/lib/server_common/portconfig.h
new file mode 100644
index 0000000..bcb8528
--- /dev/null
+++ b/src/lib/server_common/portconfig.h
@@ -0,0 +1,121 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef ISC_SERVER_COMMON_PORTCONFIG_H
+#define ISC_SERVER_COMMON_PORTCONFIG_H
+
+#include <utility>
+#include <string>
+#include <stdint.h>
+#include <vector>
+
+#include <cc/data.h>
+
+/*
+ * Some forward declarations.
+ */
+namespace asiolink {
+class DNSService;
+}
+
+namespace isc {
+namespace server_common {
+/**
+ * \brief Utilities to configure ports and addresses.
+ *
+ * Here are some utilities to help a server to parse configuration of addresses
+ * and ports and install the configuration.
+ */
+namespace portconfig {
+
+/**
+ * \brief An address-port pair.
+ *
+ * It is just a pair of string for an address and unsigned integer for port
+ * number. Anything more fancy would be an overkill, it is used only to pass
+ * the addresses and ports around as intermediate results.
+ */
+typedef std::pair<std::string, uint16_t> AddressPair;
+
+/// \brief Bunch of address pairs
+typedef std::vector<AddressPair> AddressList;
+
+/**
+ * \brief
+ *
+ * This parses a list of address-port configurations and returns them. The
+ * configuration looks like this:
+ *
+ * \verbatim
+[
+ {
+ "address": "192.0.2.1",
+ "port": 13
+ },
+ {
+ "address": "::",
+ "port": 80
+ }
+]
+ * \endverbatim
+ * \param addresses The configuration element to parse (the list). Empty list,
+ * null element and null pointer all mean empty list of addresses.
+ * \param elemName The name of the element, used to create descriptions for
+ * exceptions.
+ * \return Vector of parsed address-port pairs found in the configuration.
+ * \throw isc::data::TypeError if something in the configuration is of a wrong
+ * type (string passed to a port, element in the list that isn't hash,
+ * etc).
+ * \throw asiolink::IOError if the provided address string can't be parsed.
+ * \throw BadValue for other invalid configurations (missing port or address
+ * element in the hash, port number out of range).
+ * \throw std::bad_alloc when allocation fails.
+ */
+AddressList
+parseAddresses(isc::data::ConstElementPtr addresses,
+ const std::string& elemName);
+
+/**
+ * \brief Changes current listening addresses and ports.
+ *
+ * Removes all sockets we currently listen on and starts listening on the
+ * addresses and ports requested in newAddresses.
+ *
+ * If it fails to set up the new addresses, it attempts to roll back to the
+ * previous addresses (but it still propagates the exception). If the rollback
+ * fails as well, it aborts the application (it assumes if it can't listen
+ * on the new addresses nor on the old ones, the application is useless anyway
+ * and should be restarted by Boss, not to mention that the internal state is
+ * probably broken).
+ *
+ * \param newAddresses are the addresses you want to listen on.
+ * \param addressStore is the place you store your current addresses. It is
+ * used when there's a need for rollback. The newAddresses are copied here
+ * when the change is successful.
+ * \param dnsService is the DNSService object we use now. The requests from
+ * the new sockets are handled using this dnsService (and all current
+ * sockets on the service are closed first).
+ * \throw asiolink::IOError when initialization or closing of socket fails.
+ * \throw std::bad_alloc when allocation fails.
+ */
+void
+installListenAddresses(const AddressList& newAddresses,
+ AddressList& addressStore,
+ asiolink::DNSService& dnsService);
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/server_common/tests/Makefile.am b/src/lib/server_common/tests/Makefile.am
new file mode 100644
index 0000000..55ccc85
--- /dev/null
+++ b/src/lib/server_common/tests/Makefile.am
@@ -0,0 +1,42 @@
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/server_common
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/server_common
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+AM_LDFLAGS =
+if USE_STATIC_LINK
+AM_LDFLAGS += -static
+endif
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_CLANGPP
+# see ../Makefile.am
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += portconfig_unittest.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD = $(GTEST_LDADD)
+
+run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/server_common/tests/portconfig_unittest.cc b/src/lib/server_common/tests/portconfig_unittest.cc
new file mode 100644
index 0000000..fabdfa2
--- /dev/null
+++ b/src/lib/server_common/tests/portconfig_unittest.cc
@@ -0,0 +1,182 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <server_common/portconfig.h>
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <asiolink/asiolink.h>
+
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc::server_common::portconfig;
+using namespace isc::data;
+using namespace isc;
+using namespace std;
+using namespace asiolink;
+
+namespace {
+
+/// Testcase for parseAddresses call (struct, nobody cares about private here)
+struct ParseAddresses : public ::testing::Test {
+ AddressList result_;
+ void empty(ElementPtr config, const string& name) {
+ SCOPED_TRACE(name);
+ EXPECT_NO_THROW(result_ = parseAddresses(config, "test"));
+ EXPECT_TRUE(result_.empty());
+ }
+ template<class Exception>
+ void invalidTest(const string& json, const string& name) {
+ SCOPED_TRACE(name);
+ ElementPtr config(Element::fromJSON(json));
+ EXPECT_THROW(parseAddresses(config, "test"), Exception) <<
+ "Should throw " << typeid(Exception).name();
+ }
+};
+
+// Parse valid IPv4 address
+TEST_F(ParseAddresses, ipv4) {
+ ElementPtr config(Element::fromJSON("["
+ " {"
+ " \"address\": \"192.0.2.1\","
+ " \"port\": 53"
+ " }"
+ "]"));
+ EXPECT_NO_THROW(result_ = parseAddresses(config, "test"));
+ ASSERT_EQ(1, result_.size());
+ EXPECT_EQ("192.0.2.1", result_[0].first);
+ EXPECT_EQ(53, result_[0].second);
+}
+
+// Parse valid IPv6 address
+TEST_F(ParseAddresses, ipv6) {
+ ElementPtr config(Element::fromJSON("["
+ " {"
+ " \"address\": \"2001:db8::1\","
+ " \"port\": 53"
+ " }"
+ "]"));
+ EXPECT_NO_THROW(result_ = parseAddresses(config, "test"));
+ ASSERT_EQ(1, result_.size());
+ EXPECT_EQ("2001:db8::1", result_[0].first);
+ EXPECT_EQ(53, result_[0].second);
+}
+
+// Parse multiple addresses at once
+// (even the ports are different to see they are not mistaken)
+TEST_F(ParseAddresses, multi) {
+ ElementPtr config(Element::fromJSON("["
+ " {"
+ " \"address\": \"2001:db8::1\","
+ " \"port\": 53"
+ " },"
+ " {"
+ " \"address\": \"192.0.2.1\","
+ " \"port\": 54"
+ " }"
+ "]"));
+ EXPECT_NO_THROW(result_ = parseAddresses(config, "test"));
+ ASSERT_EQ(2, result_.size());
+ EXPECT_EQ("2001:db8::1", result_[0].first);
+ EXPECT_EQ(53, result_[0].second);
+ EXPECT_EQ("192.0.2.1", result_[1].first);
+ EXPECT_EQ(54, result_[1].second);
+}
+
+// Parse various versions of empty list
+TEST_F(ParseAddresses, empty) {
+ empty(Element::fromJSON("[]"), "Empty list");
+ empty(ElementPtr(new NullElement), "Null element");
+ empty(ElementPtr(), "Null pointer");
+}
+
+// Reject invalid configs
+TEST_F(ParseAddresses, invalid) {
+ invalidTest<TypeError>("{}", "Not a list");
+ invalidTest<BadValue>("[{}]", "Empty element");
+ invalidTest<TypeError>("[{"
+ " \"port\": 1.5,"
+ " \"address\": \"192.0.2.1\""
+ "}]", "Float port");
+ invalidTest<BadValue>("[{"
+ " \"port\": -5,"
+ " \"address\": \"192.0.2.1\""
+ "}]", "Negative port");
+ invalidTest<BadValue>("[{"
+ " \"port\": 1000000,"
+ " \"address\": \"192.0.2.1\""
+ "}]", "Port too big");
+ invalidTest<IOError>("[{"
+ " \"port\": 53,"
+ " \"address\": \"bad_address\""
+ "}]", "Bad address");
+}
+
+// Test fixture for installListenAddresses
+struct InstallListenAddresses : public ::testing::Test {
+ InstallListenAddresses() :
+ dnss_(ios_, NULL, NULL, NULL)
+ {
+ valid_.push_back(AddressPair("127.0.0.1", 5288));
+ valid_.push_back(AddressPair("::1", 5288));
+ invalid_.push_back(AddressPair("192.0.2.2", 1));
+ }
+ IOService ios_;
+ DNSService dnss_;
+ AddressList store_;
+ // We should be able to bind to these addresses
+ AddressList valid_;
+ // But this shouldn't work
+ AddressList invalid_;
+ // Check that the store_ addresses are the same as expected
+ void checkAddresses(const AddressList& expected, const string& name) {
+ SCOPED_TRACE(name);
+
+ ASSERT_EQ(expected.size(), store_.size()) <<
+ "Different amount of elements, not checking content";
+ // Run in parallel trough the vectors
+ for (AddressList::const_iterator ei(expected.begin()),
+ si(store_.begin()); ei != expected.end(); ++ei, ++si) {
+ EXPECT_EQ(ei->first, si->first);
+ EXPECT_EQ(ei->second, si->second);
+ }
+ }
+};
+
+// Try switching valid addresses
+TEST_F(InstallListenAddresses, valid) {
+ // First, bind to the valid addresses
+ EXPECT_NO_THROW(installListenAddresses(valid_, store_, dnss_));
+ checkAddresses(valid_, "Valid addresses");
+ // TODO Maybe some test to actually connect to them
+ // Try setting it back to nothing
+ EXPECT_NO_THROW(installListenAddresses(AddressList(), store_, dnss_));
+ checkAddresses(AddressList(), "No addresses");
+ // Try switching back again
+ EXPECT_NO_THROW(installListenAddresses(valid_, store_, dnss_));
+ checkAddresses(valid_, "Valid addresses");
+}
+
+// Try if rollback works
+TEST_F(InstallListenAddresses, rollback) {
+ // Set some addresses
+ EXPECT_NO_THROW(installListenAddresses(valid_, store_, dnss_));
+ checkAddresses(valid_, "Before rollback");
+ // This should not bind them, but should leave the original addresses
+ EXPECT_THROW(installListenAddresses(invalid_, store_, dnss_), IOError);
+ checkAddresses(valid_, "After rollback");
+}
+
+}
diff --git a/src/lib/server_common/tests/run_unittests.cc b/src/lib/server_common/tests/run_unittests.cc
new file mode 100644
index 0000000..7ebc985
--- /dev/null
+++ b/src/lib/server_common/tests/run_unittests.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return (RUN_ALL_TESTS());
+}
diff --git a/src/lib/testutils/Makefile.am b/src/lib/testutils/Makefile.am
index d0467ea..ae5c6da 100644
--- a/src/lib/testutils/Makefile.am
+++ b/src/lib/testutils/Makefile.am
@@ -11,4 +11,7 @@ libtestutils_la_SOURCES = srv_test.h srv_test.cc
libtestutils_la_SOURCES += dnsmessage_test.h dnsmessage_test.cc
libtestutils_la_SOURCES += mockups.h
libtestutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libtestutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libasiolink.la
endif
+
+EXTRA_DIST = portconfig.h
diff --git a/src/lib/testutils/portconfig.h b/src/lib/testutils/portconfig.h
new file mode 100644
index 0000000..8e61ffc
--- /dev/null
+++ b/src/lib/testutils/portconfig.h
@@ -0,0 +1,189 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TESTUTILS_PORTCONFIG_H
+#define TESTUTILS_PORTCONFIG_H
+
+#include <gtest/gtest.h>
+#include <cc/data.h>
+#include <server_common/portconfig.h>
+
+namespace isc {
+namespace testutils {
+/**
+ * \brief Bits of tests for server port configuration.
+ *
+ * These are bits of tests that can be reused by server classes to check if
+ * configuration of the listening addresses work.
+ *
+ * You can put any of these functions into a TEST_F test and pass the server
+ * to it.
+ *
+ * \todo There's quite a lot of common code in the basic server handling.
+ * We should refactor it, so both Resolver server and Auth server have
+ * a common base class. When this is done, the common parts would be put
+ * there and the tests would be at the base class, not here.
+ */
+namespace portconfig {
+
+/**
+ * \brief Check setting of the listening addresses directly (as a list) works.
+ *
+ * \param server The server to test against.
+ */
+template<class Server>
+void
+listenAddresses(Server& server) {
+ using namespace isc::server_common::portconfig;
+ // Default value should be fully recursive
+ EXPECT_TRUE(server.getListenAddresses().empty());
+
+ // Try putting there some addresses
+ AddressList addresses;
+ addresses.push_back(AddressPair("127.0.0.1", 53210));
+ addresses.push_back(AddressPair("::1", 53210));
+ server.setListenAddresses(addresses);
+ EXPECT_EQ(2, server.getListenAddresses().size());
+ EXPECT_EQ("::1", server.getListenAddresses()[1].first);
+
+ // Is it independent from what we do with the vector later?
+ addresses.clear();
+ EXPECT_EQ(2, server.getListenAddresses().size());
+
+ // Did it return to fully recursive?
+ server.setListenAddresses(addresses);
+ EXPECT_TRUE(server.getListenAddresses().empty());
+}
+
+/**
+ * \brief Check setting of the addresses by config value.
+ *
+ * This passes an listen_on element to the server's updateConfig function.
+ * It tries little bit of switching around. It tries both setting a presumably
+ * valid addresses and then setting something that cant be bound, rolling back
+ * back to original.
+ *
+ * \param server The server object to test against.
+ */
+template<class Server>
+void
+listenAddressConfig(Server& server) {
+ using namespace isc::data;
+ // Try putting there some address
+ ElementPtr config(Element::fromJSON("{"
+ "\"listen_on\": ["
+ " {"
+ " \"address\": \"127.0.0.1\","
+ " \"port\": 53210"
+ " }"
+ "]"
+ "}"));
+ ConstElementPtr result(server.updateConfig(config));
+ EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+ ASSERT_EQ(1, server.getListenAddresses().size());
+ EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first);
+ EXPECT_EQ(53210, server.getListenAddresses()[0].second);
+
+ // As this is example address, the machine should not have it on
+ // any interface
+ config = Element::fromJSON("{"
+ "\"listen_on\": ["
+ " {"
+ " \"address\": \"192.0.2.0\","
+ " \"port\": 53210"
+ " }"
+ "]"
+ "}");
+ result = server.updateConfig(config);
+ EXPECT_FALSE(result->equals(*isc::config::createAnswer()));
+ ASSERT_EQ(1, server.getListenAddresses().size());
+ EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first);
+ EXPECT_EQ(53210, server.getListenAddresses()[0].second);
+
+}
+
+/**
+ * \brief Check that given config is rejected.
+ *
+ * Try if given config is considered invalid by the server and is rejected.
+ * The value is converted from JSON to the data elements and passed to server's
+ * updateConfig method. It should not crash, but return a negative answer.
+ *
+ * It is used internally by invalidListenAddressConfig, but you can use it
+ * to test any other invalid configs.
+ *
+ * \todo It might be better to put it to some other namespace, as this is more
+ * generic. But right now it is used only here, so until something else
+ * needs it, it might as well stay here.
+ * \param server The server to test against.
+ * \param JSON Config to use.
+ * \param name It is used in the output if the test fails.
+ */
+template<class Server>
+void
+configRejected(Server& server, const std::string& JSON,
+ const std::string& name)
+{
+ SCOPED_TRACE(name);
+
+ using namespace isc::data;
+ ElementPtr config(Element::fromJSON(JSON));
+ EXPECT_FALSE(server.updateConfig(config)->
+ equals(*isc::config::createAnswer())) <<
+ "Accepted invalid config " << JSON;
+}
+
+/**
+ * \brief Check some invalid address configs.
+ *
+ * It tries a series of invalid listen_on configs against the server and checks
+ * it is rejected.
+ * \param server The server to check against.
+ */
+template<class Server>
+void
+invalidListenAddressConfig(Server& server) {
+ configRejected(server, "{"
+ "\"listen_on\": \"error\""
+ "}", "Wrong element type");
+ configRejected(server, "{"
+ "\"listen_on\": [{}]"
+ "}", "Empty address element");
+ configRejected(server, "{"
+ "\"listen_on\": [{"
+ " \"port\": 1.5,"
+ " \"address\": \"192.0.2.1\""
+ "}]}", "Float port");
+ configRejected(server, "{"
+ "\"listen_on\": [{"
+ " \"port\": -5,"
+ " \"address\": \"192.0.2.1\""
+ "}]}", "Negative port");
+ configRejected(server, "{"
+ "\"listen_on\": [{"
+ " \"port\": 1000000,"
+ " \"address\": \"192.0.2.1\""
+ "}]}", "Huge port");
+ configRejected(server, "{"
+ "\"listen_on\": [{"
+ " \"port\": 53,"
+ " \"address\": \"bad_address\""
+ "}]}", "Bad address");
+}
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/testutils/srv_test.cc b/src/lib/testutils/srv_test.cc
index c0d6e0f..4fec4ca 100644
--- a/src/lib/testutils/srv_test.cc
+++ b/src/lib/testutils/srv_test.cc
@@ -60,7 +60,7 @@ SrvTestBase::createDataFromFile(const char* const datafile,
delete endpoint;
endpoint = IOEndpoint::create(protocol,
- IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
+ IOAddress(DEFAULT_REMOTE_ADDRESS), 53210);
UnitTestUtil::readWireData(datafile, data);
io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
&IOSocket::getDummyTCPSocket();
@@ -76,7 +76,7 @@ SrvTestBase::createRequestPacket(Message& message,
delete io_message;
endpoint = IOEndpoint::create(protocol,
- IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
+ IOAddress(DEFAULT_REMOTE_ADDRESS), 53210);
io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
&IOSocket::getDummyTCPSocket();
io_message = new IOMessage(request_renderer.getData(),
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..d4008c0
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = system
diff --git a/tests/system/Makefile.am b/tests/system/Makefile.am
new file mode 100644
index 0000000..663258b
--- /dev/null
+++ b/tests/system/Makefile.am
@@ -0,0 +1,16 @@
+systest:
+ sh $(srcdir)/runall.sh
+
+distclean-local:
+ sh $(srcdir)/cleanall.sh
+
+# Most of the files under this directory (including test subdirectories)
+# must be listed in EXTRA_DIST.
+EXTRA_DIST = README cleanall.sh ifconfig.sh start.pl stop.pl run.sh runall.sh
+EXTRA_DIST += common/default_user.csv
+EXTRA_DIST += glue/auth.good glue/example.good glue/noglue.good glue/test.good
+EXTRA_DIST += glue/tests.sh glue/clean.sh
+EXTRA_DIST += glue/nsx1/com.db glue/nsx1/net.db glue/nsx1/root-servers.nil.db
+EXTRA_DIST += glue/nsx1/root.db
+EXTRA_DIST += bindctl/tests.sh bindctl/clean.sh bindctl/setup.sh
+EXTRA_DIST += bindctl/nsx1/root.db bindctl/nsx1/example-normalized.db
diff --git a/tests/system/README b/tests/system/README
new file mode 100644
index 0000000..a43d49e
--- /dev/null
+++ b/tests/system/README
@@ -0,0 +1,63 @@
+Copyright (C) 2004, 2010, 2011 Internet Systems Consortium, Inc. ("ISC")
+Copyright (C) 2000, 2001 Internet Software Consortium.
+See COPYRIGHT in the source root or http://isc.org/copyright.html for terms.
+
+This is a simple test environment for running BIND 10 system tests
+involving multiple name servers. It was originally developed for BIND
+9, and has been ported to test BIND 10 implementations. Ideally we
+should share the same framework for both versions, so some part of
+the original setup are kept, even though they are BIND 9 specific and
+not currently used.
+
+Also, these tests generally rely on BIND 9 programs, most commonly its
+dig, and will sometimes be its name server (named). So, the test
+environment assumes that there's a source tree of BIND 9 where its
+programs are built, and that an environment variable "BIND9_TOP" is
+set to point to the top directory of the source tree.
+
+There are multiple test suites, each in a separate subdirectory and
+involving a different DNS setup. They are:
+
+ bindctl/ Some basic management operations using the bindctl tool
+ glue/ Glue handling tests
+(the following tests are planned to be added soon)
+ dnssec/ DNSSEC tests
+ masterfile/ Master file parser
+ xfer/ Zone transfer tests
+
+Typically each test suite sets up 2-5 instances of BIND 10 (or BIND 9
+named) and then performs one or more tests against them. Within the
+test suite subdirectory, each instance has a separate subdirectory
+containing its configuration data. By convention, these
+subdirectories are named "nsx1", "nsx2", etc for BIND 10 ("x" means
+BIND 10), and "ns1", "ns2", etc. for BIND 9.
+
+The tests are completely self-contained and do not require access to
+the real DNS. Generally, one of the test servers (ns[x]1) is set up
+as a root name server and is listed in the hints file of the others.
+
+To enable all servers to run on the same machine, they bind to
+separate virtual IP address on the loopback interface. ns[x]1 runs on
+10.53.0.1, ns[x]2 on 10.53.0.2, etc. Before running any tests, you
+must set up these addresses by running "ifconfig.sh up" as root.
+
+Mac OS X:
+If you wish to make the interfaces survive across reboots
+copy org.isc.bind.system and org.isc.bind.system to
+/Library/LaunchDaemons then run
+"launchctl load /Library/LaunchDaemons/org.isc.bind.system.plist" as
+root.
+
+The servers use port 53210 instead of the usual port 53, so they can be
+run without root privileges once the interfaces have been set up.
+
+The tests can be run individually like this:
+
+ sh run.sh xfer
+ sh run.sh glue
+ etc.
+
+To run all the tests, just type "make systest" either on this directory
+or on the top source directory. Note: currently these tests cannot be
+run when built under a separate build directory. Everything must be
+run within the original source tree.
diff --git a/tests/system/bindctl/clean.sh b/tests/system/bindctl/clean.sh
new file mode 100755
index 0000000..f691512
--- /dev/null
+++ b/tests/system/bindctl/clean.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000, 2001 Internet Software Consortium.
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+rm -f */b10-config.db
+rm -f dig.out.* bindctl.out.*
+rm -f */msgq_socket */zone.sqlite3
diff --git a/tests/system/bindctl/nsx1/b10-config.db.template.in b/tests/system/bindctl/nsx1/b10-config.db.template.in
new file mode 100644
index 0000000..162329a
--- /dev/null
+++ b/tests/system/bindctl/nsx1/b10-config.db.template.in
@@ -0,0 +1,10 @@
+{"version": 2,
+ "Auth": {
+ "listen_on": [{"address": "10.53.0.1", "port": 53210}],
+ "database_file": "@abs_builddir@/zone.sqlite3",
+ "statistics-interval": 1
+ },
+ "Xfrout": {
+ "log_file": "@abs_builddir@/Xfrout.log"
+ }
+}
diff --git a/tests/system/bindctl/nsx1/example-normalized.db b/tests/system/bindctl/nsx1/example-normalized.db
new file mode 100644
index 0000000..7129522
--- /dev/null
+++ b/tests/system/bindctl/nsx1/example-normalized.db
@@ -0,0 +1,3 @@
+com. 300 IN SOA postmaster.example. ns.example.com. 2000042100 600 600 1200 600
+com. 300 IN NS ns.example.com.
+ns.example.com. 300 IN A 192.0.2.2
diff --git a/tests/system/bindctl/nsx1/root.db b/tests/system/bindctl/nsx1/root.db
new file mode 100644
index 0000000..31293de
--- /dev/null
+++ b/tests/system/bindctl/nsx1/root.db
@@ -0,0 +1,25 @@
+; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+; Copyright (C) 2000, 2001 Internet Software Consortium.
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+$TTL 300
+. IN SOA postmaster.example. a.root.servers.nil. (
+ 2000042100 ; serial
+ 600 ; refresh
+ 600 ; retry
+ 1200 ; expire
+ 600 ; minimum
+ )
+. NS ns.example.com.
+ns.example.com. A 192.0.2.1
diff --git a/tests/system/bindctl/setup.sh b/tests/system/bindctl/setup.sh
new file mode 100755
index 0000000..55afcc1
--- /dev/null
+++ b/tests/system/bindctl/setup.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+SUBTEST_TOP=${TEST_TOP}/bindctl
+
+cp ${SUBTEST_TOP}/nsx1/b10-config.db.template ${SUBTEST_TOP}/nsx1/b10-config.db
+
+rm -f ${SUBTEST_TOP}/*/zone.sqlite3
+${B10_LOADZONE} -o . -d ${SUBTEST_TOP}/nsx1/zone.sqlite3 \
+ ${SUBTEST_TOP}//nsx1/root.db
diff --git a/tests/system/bindctl/tests.sh b/tests/system/bindctl/tests.sh
new file mode 100755
index 0000000..354f349
--- /dev/null
+++ b/tests/system/bindctl/tests.sh
@@ -0,0 +1,106 @@
+#!/bin/sh
+#
+# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+#
+# Do bindctl tests.
+#
+
+status=0
+n=0
+
+echo "I:Checking b10-auth is working by default ($n)"
+$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
+# perform a simple check on the output (digcomp would be too much for this)
+grep 192.0.2.1 dig.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Checking BIND 10 statistics after a pose ($n)"
+# wait for 2sec to make sure b10-stats gets the latest statistics.
+# note that we set statistics-interval to 1.
+sleep 2
+echo 'Stats show
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
+# the server should have received 1 UDP and 1 TCP queries (TCP query was
+# sent from the server startup script)
+grep "\"auth.queries.tcp\": 1," bindctl.out.$n > /dev/null || status=1
+grep "\"auth.queries.udp\": 1," bindctl.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Stopping b10-auth and checking that ($n)"
+echo 'config set Boss/start_auth false
+config commit
+quit
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
+# dig should exit with a failure code.
+$DIG +tcp +norec @10.53.0.1 -p 53210 ns.example.com. A && status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Restarting b10-auth and checking that ($n)"
+echo 'config set Boss/start_auth true
+config commit
+quit
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
+$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
+grep 192.0.2.1 dig.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Rechecking BIND 10 statistics after a pose ($n)"
+sleep 2
+echo 'Stats show
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
+# The statistics counters should have been reset while stop/start.
+grep "\"auth.queries.tcp\": 0," bindctl.out.$n > /dev/null || status=1
+grep "\"auth.queries.udp\": 1," bindctl.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Changing the data source from sqlite3 to in-memory ($n)"
+DATASRC_SPEC='[{"type": "memory", "zones": [{"origin": "com","file":'
+DATASRC_SPEC="${DATASRC_SPEC} \"${TEST_TOP}/bindctl/nsx1/example-normalized.db\"}]}]"
+echo "config set Auth/datasources ${DATASRC_SPEC}
+config commit
+quit
+" | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
+$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
+grep 192.0.2.2 dig.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Rechecking BIND 10 statistics after changing the datasource ($n)"
+sleep 2
+echo 'Stats show
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
+# The statistics counters shouldn't be reset due to hot-swapping datasource.
+grep "\"auth.queries.tcp\": 0," bindctl.out.$n > /dev/null || status=1
+grep "\"auth.queries.udp\": 2," bindctl.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:exit status: $status"
+exit $status
diff --git a/tests/system/cleanall.sh b/tests/system/cleanall.sh
new file mode 100755
index 0000000..17c3d4a
--- /dev/null
+++ b/tests/system/cleanall.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000, 2001 Internet Software Consortium.
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+#
+# Clean up after system tests.
+#
+
+find . -type f \( \
+ -name 'K*' -o -name '*~' -o -name '*.core' -o -name '*.log' \
+ -o -name '*.pid' -o -name '*.keyset' -o -name named.run \
+ -o -name bind10.run -o -name lwresd.run -o -name ans.run \) -print | \
+ xargs rm -f
+
+status=0
+
+for d in `find . -type d -maxdepth 1 -mindepth 1 -print`
+do
+ test ! -f $d/clean.sh || ( cd $d && sh clean.sh )
+done
diff --git a/tests/system/common/default_user.csv b/tests/system/common/default_user.csv
new file mode 100644
index 0000000..e13e194
--- /dev/null
+++ b/tests/system/common/default_user.csv
@@ -0,0 +1 @@
+root,bind10
diff --git a/tests/system/conf.sh.in b/tests/system/conf.sh.in
new file mode 100755
index 0000000..66aa3f5
--- /dev/null
+++ b/tests/system/conf.sh.in
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+# Copyright (C) 2004-2011 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000-2003 Internet Software Consortium.
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+#
+# Common configuration data for system tests, to be sourced into
+# other shell scripts.
+#
+
+# Prerequisite check
+if [ @srcdir@ != @builddir@ ]; then
+ echo "Currently systest doesn't work for a separate build tree."
+ echo "Rebuild BIND 10 on the source tree and run the tests."
+ exit 1
+fi
+
+if [ -z $BIND9_TOP ]; then
+ echo "systest assumes there's a compiled tree of BIND 9 which can be"
+ echo "accessed via the BIND9_TOP environment variable."
+ echo "Please make sure this assumption is met."
+ exit 1
+fi
+
+# Find the top of the source and test trees.
+TOP=@abs_top_srcdir@
+TEST_TOP=@abs_builddir@
+
+RUN_BIND10=$TOP/src/bin/bind10/run_bind10.sh
+RUN_BINDCTL=$TOP/src/bin/bindctl/run_bindctl.sh
+BINDCTL_CSV_DIR=@abs_srcdir@/common/
+B10_LOADZONE=$TOP/src/bin/loadzone/run_loadzone.sh
+BIND9_NAMED=$BIND9_TOP/bin/named/named
+DIG=$BIND9_TOP/bin/dig/dig
+# Test tools borrowed from BIND 9's system test (without change).
+TESTSOCK=$BIND9_TOP/bin/tests/system/testsock.pl
+DIGCOMP=$BIND9_TOP/bin/tests/system/digcomp.pl
+
+SUBDIRS="bindctl glue"
+#SUBDIRS="dnssec masterfile xfer"
+
+# PERL will be an empty string if no perl interpreter was found.
+PERL=@PERL@
+
+export RUN_BIND10 BIND9_NAMED DIG SUBDIRS PERL TESTSOCK
diff --git a/tests/system/glue/auth.good b/tests/system/glue/auth.good
new file mode 100644
index 0000000..2c619f6
--- /dev/null
+++ b/tests/system/glue/auth.good
@@ -0,0 +1,15 @@
+
+; <<>> DiG 9.0 <<>> +norec @10.53.0.1 -p 5300 foo.bar.example.org. a
+;; global options: printcmd
+;; Got answer:
+;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41239
+;; flags: qr ad; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
+
+;; QUESTION SECTION:
+;foo.bar.example.org. IN A
+
+;; AUTHORITY SECTION:
+example.org. 172800 IN NS b.root-servers.nil.
+
+;; ADDITIONAL SECTION:
+b.root-servers.nil. 300 IN A 10.53.0.2
diff --git a/tests/system/glue/clean.sh b/tests/system/glue/clean.sh
new file mode 100755
index 0000000..b2c1e02
--- /dev/null
+++ b/tests/system/glue/clean.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+#
+# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000, 2001 Internet Software Consortium.
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+#
+# Clean up after glue tests.
+#
+
+rm -f dig.out.*
+rm -f */msgq_socket */zone.sqlite3
diff --git a/tests/system/glue/example.good b/tests/system/glue/example.good
new file mode 100644
index 0000000..3b7bbb8
--- /dev/null
+++ b/tests/system/glue/example.good
@@ -0,0 +1,19 @@
+
+; <<>> DiG 9.0 <<>> +norec @10.53.0.1 -p 5300 foo.bar.example. A
+;; global options: printcmd
+;; Got answer:
+;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58772
+;; flags: qr ad; QUERY: 1, ANSWER: 0, AUTHORITY: 6, ADDITIONAL: 7
+
+;; QUESTION SECTION:
+;foo.bar.example. IN A
+
+;; AUTHORITY SECTION:
+example. 172800 IN NS NS1.example.COM.
+example. 172800 IN NS NS.example.
+
+;; ADDITIONAL SECTION:
+NS.example. 172800 IN A 192.0.2.1
+NS.example. 172800 IN A 192.0.2.2
+NS1.example.COM. 172800 IN A 192.0.2.101
+NS1.example.COM. 172800 IN AAAA 2001:db8::1
diff --git a/tests/system/glue/noglue.good b/tests/system/glue/noglue.good
new file mode 100644
index 0000000..57a2211
--- /dev/null
+++ b/tests/system/glue/noglue.good
@@ -0,0 +1,14 @@
+
+; <<>> DiG 9.0 <<>> @10.53.0.1 -p 5300 example.net a
+;; global options: printcmd
+;; Got answer:
+;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29409
+;; flags: qr rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 0
+
+;; QUESTION SECTION:
+;example.net. IN A
+
+;; AUTHORITY SECTION:
+example.net. 300 IN NS ns2.example.info.
+example.net. 300 IN NS ns1.example.info.
+
diff --git a/tests/system/glue/nsx1/b10-config.db.in b/tests/system/glue/nsx1/b10-config.db.in
new file mode 100644
index 0000000..acd040c
--- /dev/null
+++ b/tests/system/glue/nsx1/b10-config.db.in
@@ -0,0 +1,9 @@
+{"version": 2,
+ "Auth": {
+ "listen_on": [{"address": "10.53.0.1", "port": 53210}],
+ "database_file": "@abs_builddir@/zone.sqlite3"
+ },
+ "Xfrout": {
+ "log_file": "@abs_builddir@/Xfrout.log"
+ }
+}
diff --git a/tests/system/glue/nsx1/com.db b/tests/system/glue/nsx1/com.db
new file mode 100644
index 0000000..c4b94e1
--- /dev/null
+++ b/tests/system/glue/nsx1/com.db
@@ -0,0 +1,31 @@
+; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+; Copyright (C) 2000, 2001 Internet Software Consortium.
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+$ORIGIN com.
+$TTL 300
+@ IN SOA root.example.com. a.root.servers.nil. (
+ 2000042100 ; serial
+ 600 ; refresh
+ 600 ; retry
+ 1200 ; expire
+ 600 ; minimum
+ )
+@ NS a.root-servers.nil.
+
+example.com. NS ns1.example.com.
+example.com. NS ns2.example.com.
+ns1.example.com. 172800 IN A 192.0.2.101
+ns1.example.com. 172800 IN AAAA 2001:db8::1
+ns2.example.com. 172800 IN A 192.0.2.102
diff --git a/tests/system/glue/nsx1/net.db b/tests/system/glue/nsx1/net.db
new file mode 100644
index 0000000..8b66521
--- /dev/null
+++ b/tests/system/glue/nsx1/net.db
@@ -0,0 +1,32 @@
+; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+; Copyright (C) 2000, 2001 Internet Software Consortium.
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+$ORIGIN net.
+$TTL 300
+@ IN SOA root.example.net. a.root.servers.nil. (
+ 2000042100 ; serial
+ 600 ; refresh
+ 600 ; retry
+ 1200 ; expire
+ 600 ; minimum
+ )
+@ NS a.root-servers.nil.
+
+; Referral outside of server authority, but with glue records present.
+; Don't hand out the glue.
+example.net. NS ns1.example.info.
+example.net. NS ns2.example.info.
+ns1.example.info. 172800 IN A 192.0.2.101
+ns2.example.info. 172800 IN A 192.0.2.102
diff --git a/tests/system/glue/nsx1/root-servers.nil.db b/tests/system/glue/nsx1/root-servers.nil.db
new file mode 100644
index 0000000..45050a9
--- /dev/null
+++ b/tests/system/glue/nsx1/root-servers.nil.db
@@ -0,0 +1,26 @@
+; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+; Copyright (C) 2000, 2001 Internet Software Consortium.
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+$TTL 300
+@ IN SOA ns hostmaster (
+ 1
+ 3600
+ 1800
+ 1814400
+ 3600
+ )
+ NS a
+a A 10.53.0.1
+b A 10.53.0.2
diff --git a/tests/system/glue/nsx1/root.db b/tests/system/glue/nsx1/root.db
new file mode 100644
index 0000000..e43f2d2
--- /dev/null
+++ b/tests/system/glue/nsx1/root.db
@@ -0,0 +1,55 @@
+; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+; Copyright (C) 2000, 2001 Internet Software Consortium.
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+$TTL 300
+. IN SOA postmaster.example. a.root.servers.nil. (
+ 2000042100 ; serial
+ 600 ; refresh
+ 600 ; retry
+ 1200 ; expire
+ 600 ; minimum
+ )
+. NS a.root-servers.nil.
+
+root-servers.nil. NS a.root-servers.nil.
+a.root-servers.nil. A 10.53.0.1
+
+; Delegate some domains that contain name servers for the sample
+; ccTLDs below.
+com. 172800 IN NS a.root-servers.nil.
+
+;
+; A sample TLD
+;
+example. 172800 IN NS NS.example.
+example. 172800 IN NS NS1.example.COM.
+NS.example. 172800 IN A 192.0.2.1
+NS.example. 172800 IN A 192.0.2.2
+; this "glue" is below a zone cut for com. BIND 9 still uses it for
+; the delegation to example. BIND 10 (with sqlite3 data source) doesn't.
+NS1.example.COM. 172800 IN A 192.0.2.3
+
+;
+;
+;
+test. 172800 IN NS ns.test.
+test. 172800 IN NS ns1.example.net.
+ns.test. 172800 IN A 192.0.2.200
+ns1.example.net. 172800 IN A 192.0.2.201
+
+;
+; A hypothetical ccTLD where we are authoritative for the NS glue.
+;
+example.org 172800 IN NS b.root-servers.nil.
diff --git a/tests/system/glue/setup.sh.in b/tests/system/glue/setup.sh.in
new file mode 100755
index 0000000..dc5b28a
--- /dev/null
+++ b/tests/system/glue/setup.sh.in
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+rm -f */zone.sqlite3
+${B10_LOADZONE} -o . -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/root.db
+${B10_LOADZONE} -o root-servers.nil -d @builddir@/nsx1/zone.sqlite3 \
+ @builddir@/nsx1/root-servers.nil.db
+${B10_LOADZONE} -o com -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/com.db
+${B10_LOADZONE} -o net -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/net.db
diff --git a/tests/system/glue/test.good b/tests/system/glue/test.good
new file mode 100644
index 0000000..b9b4719
--- /dev/null
+++ b/tests/system/glue/test.good
@@ -0,0 +1,19 @@
+
+; <<>> DiG 9.8.0 <<>> @127.0.0.1 -p 5300 foo.bar.test
+; (1 server found)
+;; global options: +cmd
+;; Got answer:
+;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 55069
+;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 2
+;; WARNING: recursion requested but not available
+
+;; QUESTION SECTION:
+;foo.bar.test. IN A
+
+;; AUTHORITY SECTION:
+test. 172800 IN NS ns.test.
+test. 172800 IN NS ns1.example.net.
+
+;; ADDITIONAL SECTION:
+ns.test. 172800 IN A 192.0.2.200
+ns1.example.net. 172800 IN A 192.0.2.201
diff --git a/tests/system/glue/tests.sh b/tests/system/glue/tests.sh
new file mode 100755
index 0000000..50b2330
--- /dev/null
+++ b/tests/system/glue/tests.sh
@@ -0,0 +1,66 @@
+#!/bin/sh
+#
+# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000, 2001, 2003 Internet Software Consortium.
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+#
+# Do glue tests.
+#
+
+status=0
+n=0
+
+# This query should result in a delegation with two NS; one in the delegated
+# zone and one in a so called out-of-bailiwick zone for which the auth server
+# has authority, too. For the former, the server should return glue in the
+# parent zone. For the latter, BIND 9 and BIND 10 behave differently; BIND 9
+# uses "glue" in the parent zone (since this is the root zone everything can
+# be considered a valid glue). BIND 10 (using sqlite3 data source) searches
+# the other zone and uses the authoritative data in that zone (which is
+# intentionally different from the glue in the root zone).
+echo "I:testing that a TLD referral gets a full glue set from the root zone ($n)"
+$DIG +norec @10.53.0.1 -p 53210 foo.bar.example. A >dig.out.$n || status=1
+$PERL $DIGCOMP example.good dig.out.$n || status=1
+n=`expr $n + 1`
+
+echo "I:testing that we find glue A RRs we are authoritative for ($n)"
+$DIG +norec @10.53.0.1 -p 53210 foo.bar.example.org. a >dig.out.$n || status=1
+$PERL $DIGCOMP auth.good dig.out.$n || status=1
+n=`expr $n + 1`
+
+# We cannot do this test for BIND 10 because b10-auth doesn't act as a
+# recursive (caching) server (by design)
+# echo "I:testing that we find glue A/AAAA RRs in the cache ($n)"
+# $DIG +norec @10.53.0.1 -p 53210 foo.bar.yy. a >dig.out.$n || status=1
+# $PERL $DIGCOMP yy.good dig.out.$n || status=1
+# n=`expr $n + 1`
+
+echo "I:testing that we don't find out-of-zone glue ($n)"
+$DIG +norec @10.53.0.1 -p 53210 example.net. a > dig.out.$n || status=1
+$PERL $DIGCOMP noglue.good dig.out.$n || status=1
+n=`expr $n + 1`
+
+# This test currently fails (additional section will be empty, which is
+# incorrect). See Trac ticket #646.
+#echo "I:testing that we are finding partial out-of-zone glue ($n)"
+#$DIG +norec @10.53.0.1 -p 53210 foo.bar.test. a >dig.out.$n || status=1
+#$PERL $DIGCOMP test.good dig.out.$n || status=1
+#n=`expr $n + 1`
+
+echo "I:exit status: $status"
+exit $status
diff --git a/tests/system/ifconfig.sh b/tests/system/ifconfig.sh
new file mode 100755
index 0000000..c0c365a
--- /dev/null
+++ b/tests/system/ifconfig.sh
@@ -0,0 +1,226 @@
+#!/bin/sh
+#
+# Copyright (C) 2004, 2007-2010 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000-2003 Internet Software Consortium.
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+#
+# Set up interface aliases for bind9 system tests.
+#
+# IPv4: 10.53.0.{1..7} RFC 1918
+# IPv6: fd92:7065:b8e:ffff::{1..7} ULA
+#
+
+config_guess=""
+for f in ./config.guess ../../config.guess
+do
+ if test -f $f
+ then
+ config_guess=$f
+ fi
+done
+
+if test "X$config_guess" = "X"
+then
+ cat <<EOF >&2
+$0: must be run from the top level source directory or the
+bin/tests/system directory
+EOF
+ exit 1
+fi
+
+# If running on hp-ux, don't even try to run config.guess.
+# It will try to create a temporary file in the current directory,
+# which fails when running as root with the current directory
+# on a NFS mounted disk.
+
+case `uname -a` in
+ *HP-UX*) sys=hpux ;;
+ *) sys=`sh $config_guess` ;;
+esac
+
+case "$2" in
+[0-9]|[1-9][0-9]|[1-9][0-9][0-9]) base=$2;;
+*) base=""
+esac
+
+case "$3" in
+[0-9]|[1-9][0-9]|[1-9][0-9][0-9]) base6=$2;;
+*) base6=""
+esac
+
+case "$1" in
+
+ start|up)
+ for ns in 1 2 3 4 5 6 7
+ do
+ if test -n "$base"
+ then
+ int=`expr $ns + $base - 1`
+ else
+ int=$ns
+ fi
+ if test -n "$base6"
+ then
+ int6=`expr $ns + $base6 - 1`
+ else
+ int6=$ns
+ fi
+ case "$sys" in
+ *-pc-solaris2.5.1)
+ ifconfig lo0:$int 10.53.0.$ns netmask 0xffffffff up
+ ;;
+ *-sun-solaris2.[6-7])
+ ifconfig lo0:$int 10.53.0.$ns netmask 0xffffffff up
+ ;;
+ *-*-solaris2.[8-9]|*-*-solaris2.1[0-9])
+ /sbin/ifconfig lo0:$int plumb
+ /sbin/ifconfig lo0:$int 10.53.0.$ns up
+ if test -n "$int6"
+ then
+ /sbin/ifconfig lo0:$int6 inet6 plumb
+ /sbin/ifconfig lo0:$int6 \
+ inet6 fd92:7065:b8e:ffff::$ns up
+ fi
+ ;;
+ *-*-linux*)
+ ifconfig lo:$int 10.53.0.$ns up netmask 255.255.255.0
+ ifconfig lo inet6 add fd92:7065:b8e:ffff::$ns/64
+ ;;
+ *-unknown-freebsd*)
+ ifconfig lo0 10.53.0.$ns alias netmask 0xffffffff
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
+ ;;
+ *-unknown-netbsd*)
+ ifconfig lo0 10.53.0.$ns alias netmask 255.255.255.0
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
+ ;;
+ *-unknown-openbsd*)
+ ifconfig lo0 10.53.0.$ns alias netmask 255.255.255.0
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
+ ;;
+ *-*-bsdi[3-5].*)
+ ifconfig lo0 add 10.53.0.$ns netmask 255.255.255.0
+ ;;
+ *-dec-osf[4-5].*)
+ ifconfig lo0 alias 10.53.0.$ns
+ ;;
+ *-sgi-irix6.*)
+ ifconfig lo0 alias 10.53.0.$ns
+ ;;
+ *-*-sysv5uw7*|*-*-sysv*UnixWare*|*-*-sysv*OpenUNIX*)
+ ifconfig lo0 10.53.0.$ns alias netmask 0xffffffff
+ ;;
+ *-ibm-aix4.*|*-ibm-aix5.*)
+ ifconfig lo0 alias 10.53.0.$ns
+ ifconfig lo0 inet6 alias -dad fd92:7065:b8e:ffff::$ns/64
+ ;;
+ hpux)
+ ifconfig lo0:$int 10.53.0.$ns netmask 255.255.255.0 up
+ ifconfig lo0:$int inet6 fd92:7065:b8e:ffff::$ns up
+ ;;
+ *-sco3.2v*)
+ ifconfig lo0 alias 10.53.0.$ns
+ ;;
+ *-darwin*)
+ ifconfig lo0 alias 10.53.0.$ns
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
+ ;;
+ *)
+ echo "Don't know how to set up interface. Giving up."
+ exit 1
+ esac
+ done
+ ;;
+
+ stop|down)
+ for ns in 7 6 5 4 3 2 1
+ do
+ if test -n "$base"
+ then
+ int=`expr $ns + $base - 1`
+ else
+ int=$ns
+ fi
+ case "$sys" in
+ *-pc-solaris2.5.1)
+ ifconfig lo0:$int 0.0.0.0 down
+ ;;
+ *-sun-solaris2.[6-7])
+ ifconfig lo0:$int 10.53.0.$ns down
+ ;;
+ *-*-solaris2.[8-9]|*-*-solaris2.1[0-9])
+ ifconfig lo0:$int 10.53.0.$ns down
+ ifconfig lo0:$int 10.53.0.$ns unplumb
+ if test -n "$int6"
+ then
+ ifconfig lo0:$int6 inet6 down
+ ifconfig lo0:$int6 inet6 unplumb
+ fi
+ ;;
+ *-*-linux*)
+ ifconfig lo:$int 10.53.0.$ns down
+ ifconfig lo inet6 del fd92:7065:b8e:ffff::$ns/64
+ ;;
+ *-unknown-freebsd*)
+ ifconfig lo0 10.53.0.$ns delete
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
+ ;;
+ *-unknown-netbsd*)
+ ifconfig lo0 10.53.0.$ns delete
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
+ ;;
+ *-unknown-openbsd*)
+ ifconfig lo0 10.53.0.$ns delete
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
+ ;;
+ *-*-bsdi[3-5].*)
+ ifconfig lo0 remove 10.53.0.$ns
+ ;;
+ *-dec-osf[4-5].*)
+ ifconfig lo0 -alias 10.53.0.$ns
+ ;;
+ *-sgi-irix6.*)
+ ifconfig lo0 -alias 10.53.0.$ns
+ ;;
+ *-*-sysv5uw7*|*-*-sysv*UnixWare*|*-*-sysv*OpenUNIX*)
+ ifconfig lo0 -alias 10.53.0.$ns
+ ;;
+ *-ibm-aix4.*|*-ibm-aix5.*)
+ ifconfig lo0 delete 10.53.0.$ns
+ ifconfig lo0 delete inet6 fd92:7065:b8e:ffff::$ns/64
+ ;;
+ hpux)
+ ifconfig lo0:$int 0.0.0.0
+ ifconfig lo0:$int inet6 ::
+ ;;
+ *-sco3.2v*)
+ ifconfig lo0 -alias 10.53.0.$ns
+ ;;
+ *darwin*)
+ ifconfig lo0 -alias 10.53.0.$ns
+ ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
+ ;;
+ *)
+ echo "Don't know how to destroy interface. Giving up."
+ exit 1
+ esac
+ done
+
+ ;;
+
+ *)
+ echo "Usage: $0 { up | down } [base]"
+ exit 1
+esac
diff --git a/tests/system/run.sh b/tests/system/run.sh
new file mode 100755
index 0000000..4f852f4
--- /dev/null
+++ b/tests/system/run.sh
@@ -0,0 +1,125 @@
+#!/bin/sh
+#
+# Copyright (C) 2004, 2007, 2010 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000, 2001 Internet Software Consortium.
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+#
+# Run a system test.
+#
+
+SYSTEMTESTTOP=.
+. $SYSTEMTESTTOP/conf.sh
+
+stopservers=true
+
+case $1 in
+ --keep) stopservers=false; shift ;;
+esac
+
+test $# -gt 0 || { echo "usage: $0 [--keep] test-directory" >&2; exit 1; }
+
+test=$1
+shift
+
+test -d $test || { echo "$0: $test: no such test" >&2; exit 1; }
+
+echo "S:$test:`date`" >&2
+echo "T:$test:1:A" >&2
+echo "A:System test $test" >&2
+
+if [ x$PERL = x ]
+then
+ echo "I:Perl not available. Skipping test." >&2
+ echo "R:UNTESTED" >&2
+ echo "E:$test:`date`" >&2
+ exit 0;
+fi
+
+$PERL $TESTSOCK || {
+ echo "I:Network interface aliases not set up. Skipping test." >&2;
+ echo "R:UNTESTED" >&2;
+ echo "E:$test:`date`" >&2;
+ exit 0;
+}
+
+
+# Check for test-specific prerequisites.
+test ! -f $test/prereq.sh || ( cd $test && sh prereq.sh "$@" )
+result=$?
+
+if [ $result -eq 0 ]; then
+ : prereqs ok
+else
+ echo "I:Prerequisites for $test missing, skipping test." >&2
+ [ $result -eq 255 ] && echo "R:SKIPPED" || echo "R:UNTESTED"
+ echo "E:$test:`date`" >&2
+ exit 0
+fi
+
+# Check for PKCS#11 support
+if
+ test ! -f $test/usepkcs11 || sh cleanpkcs11.sh
+then
+ : pkcs11 ok
+else
+ echo "I:Need PKCS#11 for $test, skipping test." >&2
+ echo "R:PKCS11ONLY" >&2
+ echo "E:$test:`date`" >&2
+ exit 0
+fi
+
+# Set up any dynamically generated test data
+if test -f $test/setup.sh
+then
+ ( cd $test && sh setup.sh "$@" )
+fi
+
+# Start name servers running
+$PERL start.pl $test || exit 1
+
+# Run the tests
+( cd $test ; sh tests.sh )
+
+status=$?
+
+if $stopservers
+then
+ :
+else
+ exit $status
+fi
+
+# Shutdown
+$PERL stop.pl $test
+
+status=`expr $status + $?`
+
+if [ $status != 0 ]; then
+ echo "R:FAIL"
+ # Don't clean up - we need the evidence.
+ find . -name core -exec chmod 0644 '{}' \;
+else
+ echo "R:PASS"
+
+ # Clean up.
+ if test -f $test/clean.sh
+ then
+ ( cd $test && sh clean.sh "$@" )
+ fi
+fi
+
+echo "E:$test:`date`"
+
+exit $status
diff --git a/tests/system/runall.sh b/tests/system/runall.sh
new file mode 100755
index 0000000..5d0fe9b
--- /dev/null
+++ b/tests/system/runall.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# Copyright (C) 2004, 2007, 2010 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2000, 2001 Internet Software Consortium.
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+#
+# Run all the system tests.
+#
+
+SYSTEMTESTTOP=.
+. $SYSTEMTESTTOP/conf.sh
+
+status=0
+
+for d in $SUBDIRS
+do
+ sh run.sh $d || status=1
+done
+
+$PERL $TESTSOCK || {
+ cat <<EOF >&2
+I:
+I:NOTE: Many of the tests were skipped because they require that
+I: the IP addresses 10.53.0.1 through 10.53.0.7 are configured
+I: as alias addresses on the loopback interface. Please run
+I: "tests/system/ifconfig.sh up" as root to configure them
+I: and rerun the tests.
+EOF
+ exit 0;
+}
+
+exit $status
diff --git a/tests/system/start.pl b/tests/system/start.pl
new file mode 100755
index 0000000..56f00c4
--- /dev/null
+++ b/tests/system/start.pl
@@ -0,0 +1,226 @@
+#!/usr/bin/perl -w
+#
+# Copyright (C) 2004-2008, 2010 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2001 Internet Software Consortium.
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# Framework for starting test servers.
+# Based on the type of server specified, check for port availability, remove
+# temporary files, start the server, and verify that the server is running.
+# If a server is specified, start it. Otherwise, start all servers for test.
+
+use strict;
+use Cwd 'abs_path';
+use Getopt::Long;
+
+# Option handling
+# --noclean test [server [options]]
+#
+# --noclean - Do not cleanup files in server directory
+# test - name of the test directory
+# server - name of the server directory
+# options - alternate options for the server
+
+my $usage = "usage: $0 [--noclean] test-directory [server-directory [server-options]]";
+my $noclean;
+GetOptions('noclean' => \$noclean);
+my $test = $ARGV[0];
+my $server = $ARGV[1];
+my $options = $ARGV[2];
+
+if (!$test) {
+ print "$usage\n";
+}
+if (!-d $test) {
+ print "No test directory: \"$test\"\n";
+}
+if ($server && !-d "$test/$server") {
+ print "No server directory: \"$test/$server\"\n";
+}
+
+# Global variables
+my $topdir = abs_path("$test/..");
+my $testdir = abs_path("$test");
+my $RUN_BIND10 = $ENV{'RUN_BIND10'};
+my $NAMED = $ENV{'NAMED'};
+my $LWRESD = $ENV{'LWRESD'};
+my $DIG = $ENV{'DIG'};
+my $PERL = $ENV{'PERL'};
+my $TESTSOCK = $ENV{'TESTSOCK'};
+
+# Start the server(s)
+
+if ($server) {
+ if ($server =~ /^ns/) {
+ &check_ports($server);
+ }
+ &start_server($server, $options);
+ if ($server =~ /^ns/) {
+ &verify_server($server);
+ }
+} else {
+ # Determine which servers need to be started for this test.
+ opendir DIR, $testdir;
+ my @files = sort readdir DIR;
+ closedir DIR;
+
+ my @ns = grep /^nsx?[0-9]*$/, @files;
+ my @lwresd = grep /^lwresd[0-9]*$/, @files;
+ my @ans = grep /^ans[0-9]*$/, @files;
+
+ # Start the servers we found.
+ &check_ports();
+ foreach my $s (@ns, @lwresd, @ans) {
+ &start_server($s);
+ }
+ foreach my $s (@ns) {
+ &verify_server($s);
+ }
+}
+
+# Subroutines
+
+sub check_ports {
+ my $server = shift;
+ my $options = "";
+
+ if ($server && $server =~ /(\d+)$/) {
+ $options = "-i $1";
+ }
+
+ my $tries = 0;
+ while (1) {
+ my $return = system("$PERL $TESTSOCK -p 53210 $options");
+ last if ($return == 0);
+ if (++$tries > 4) {
+ print "$0: could not bind to server addresses, still running?\n";
+ print "I:server sockets not available\n";
+ print "R:FAIL\n";
+ system("$PERL $topdir/stop.pl $testdir"); # Is this the correct behavior?
+ exit 1;
+ }
+ print "I:Couldn't bind to socket (yet)\n";
+ sleep 2;
+ }
+}
+
+sub start_server {
+ my $server = shift;
+ my $options = shift;
+
+ my $cleanup_files;
+ my $command;
+ my $pid_file;
+
+ if ($server =~ /^nsx/) {
+ $cleanup_files = "{bind10.run}";
+ $command = "B10_FROM_SOURCE_LOCALSTATEDIR=$testdir/$server/ ";
+ $command .= "$RUN_BIND10 ";
+ if ($options) {
+ $command .= "$options";
+ } else {
+ $command .= "--msgq-socket-file=$testdir/$server/msgq_socket ";
+ $command .= "--pid-file=$testdir/$server/bind10.pid ";
+ $command .= "-v";
+ }
+ $command .= " >bind10.run 2>&1 &";
+ $pid_file = "bind10.pid";
+ } elsif ($server =~ /^ns/) {
+ $cleanup_files = "{*.jnl,*.bk,*.st,named.run}";
+ $command = "$NAMED ";
+ if ($options) {
+ $command .= "$options";
+ } else {
+ $command .= "-m record,size,mctx ";
+ $command .= "-T clienttest ";
+ $command .= "-T nosoa "
+ if (-e "$testdir/$server/named.nosoa");
+ $command .= "-T noaa "
+ if (-e "$testdir/$server/named.noaa");
+ $command .= "-c named.conf -d 99 -g";
+ }
+ $command .= " >named.run 2>&1 &";
+ $pid_file = "named.pid";
+ } elsif ($server =~ /^lwresd/) {
+ $cleanup_files = "{lwresd.run}";
+ $command = "$LWRESD ";
+ if ($options) {
+ $command .= "$options";
+ } else {
+ $command .= "-m record,size,mctx ";
+ $command .= "-T clienttest ";
+ $command .= "-C resolv.conf -d 99 -g ";
+ $command .= "-i lwresd.pid -P 9210 -p 53210";
+ }
+ $command .= " >lwresd.run 2>&1 &";
+ $pid_file = "lwresd.pid";
+ } elsif ($server =~ /^ans/) {
+ $cleanup_files = "{ans.run}";
+ $command = "$PERL ./ans.pl ";
+ if ($options) {
+ $command .= "$options";
+ } else {
+ $command .= "";
+ }
+ $command .= " >ans.run 2>&1 &";
+ $pid_file = "ans.pid";
+ } else {
+ print "I:Unknown server type $server\n";
+ print "R:FAIL\n";
+ system "$PERL $topdir/stop.pl $testdir";
+ exit 1;
+ }
+
+ # print "I:starting server $server\n";
+
+ chdir "$testdir/$server";
+
+ unless ($noclean) {
+ unlink glob $cleanup_files;
+ }
+
+ system "$command";
+
+ my $tries = 0;
+ while (!-f $pid_file) {
+ if (++$tries > 14) {
+ print "I:Couldn't start server $server\n";
+ print "R:FAIL\n";
+ system "$PERL $topdir/stop.pl $testdir";
+ exit 1;
+ }
+ sleep 1;
+ }
+}
+
+sub verify_server {
+ my $server = shift;
+ my $n = $server;
+ $n =~ s/^nsx?//;
+
+ my $tries = 0;
+ while (1) {
+ my $return = system("$DIG +tcp +noadd +nosea +nostat +noquest +nocomm +nocmd -p 53210 version.bind. chaos txt \@10.53.0.$n > dig.out");
+ last if ($return == 0);
+ print `grep ";" dig.out`;
+ if (++$tries >= 30) {
+ print "I:no response from $server\n";
+ print "R:FAIL\n";
+ system("$PERL $topdir/stop.pl $testdir");
+ exit 1;
+ }
+ sleep 2;
+ }
+ unlink "dig.out";
+}
diff --git a/tests/system/stop.pl b/tests/system/stop.pl
new file mode 100755
index 0000000..a803f52
--- /dev/null
+++ b/tests/system/stop.pl
@@ -0,0 +1,188 @@
+#!/usr/bin/perl -w
+#
+# Copyright (C) 2004-2007 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2001 Internet Software Consortium.
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# Framework for stopping test servers
+# Based on the type of server specified, signal the server to stop, wait
+# briefly for it to die, and then kill it if it is still alive.
+# If a server is specified, stop it. Otherwise, stop all servers for test.
+
+use strict;
+use Cwd 'abs_path';
+
+# Option handling
+# [--use-rndc] test [server]
+#
+# test - name of the test directory
+# server - name of the server directory
+
+my $usage = "usage: $0 [--use-rndc] test-directory [server-directory]";
+my $use_rndc;
+
+while (@ARGV && $ARGV[0] =~ /^-/) {
+ my $opt = shift @ARGV;
+ if ($opt eq '--use-rndc') {
+ $use_rndc = 1;
+ } else {
+ die "$usage\n";
+ }
+}
+
+my $test = $ARGV[0];
+my $server = $ARGV[1];
+
+my $errors = 0;
+
+die "$usage\n" unless defined($test);
+die "No test directory: \"$test\"\n" unless (-d $test);
+die "No server directory: \"$server\"\n" if (defined($server) && !-d "$test/$server");
+
+# Global variables
+my $testdir = abs_path($test);
+my @servers;
+
+
+# Determine which servers need to be stopped.
+if (defined $server) {
+ @servers = ($server);
+} else {
+ local *DIR;
+ opendir DIR, $testdir or die "$testdir: $!\n";
+ my @files = sort readdir DIR;
+ closedir DIR;
+
+ my @ns = grep /^nsx?[0-9]*$/, @files;
+ my @lwresd = grep /^lwresd[0-9]*$/, @files;
+ my @ans = grep /^ans[0-9]*$/, @files;
+
+ push @servers, @ns, @lwresd, @ans;
+}
+
+
+# Stop the server(s), pass 1: rndc.
+if ($use_rndc) {
+ foreach my $server (grep /^ns/, @servers) {
+ stop_rndc($server);
+ }
+
+ wait_for_servers(30, grep /^ns/, @servers);
+}
+
+
+# Pass 2: SIGTERM
+foreach my $server (@servers) {
+ stop_signal($server, "TERM");
+}
+
+wait_for_servers(60, @servers);
+
+# Pass 3: SIGABRT
+foreach my $server (@servers) {
+ stop_signal($server, "ABRT");
+}
+
+exit($errors ? 1 : 0);
+
+# Subroutines
+
+# Return the full path to a given server's PID file.
+sub server_pid_file {
+ my($server) = @_;
+
+ my $pid_file;
+ if ($server =~ /^nsx/) {
+ $pid_file = "bind10.pid";
+ } elsif ($server =~ /^ns/) {
+ $pid_file = "named.pid";
+ } elsif ($server =~ /^lwresd/) {
+ $pid_file = "lwresd.pid";
+ } elsif ($server =~ /^ans/) {
+ $pid_file = "ans.pid";
+ } else {
+ print "I:Unknown server type $server\n";
+ exit 1;
+ }
+ $pid_file = "$testdir/$server/$pid_file";
+}
+
+# Read a PID.
+sub read_pid {
+ my($pid_file) = @_;
+
+ local *FH;
+ my $result = open FH, "< $pid_file";
+ if (!$result) {
+ print "I:$pid_file: $!\n";
+ unlink $pid_file;
+ return;
+ }
+
+ my $pid = <FH>;
+ chomp($pid);
+ return $pid;
+}
+
+# Stop a named process with rndc.
+sub stop_rndc {
+ my($server) = @_;
+
+ return unless ($server =~ /^ns(\d+)$/);
+ my $ip = "10.53.0.$1";
+
+ # Ugly, but should work.
+ system("$ENV{RNDC} -c $testdir/../common/rndc.conf -s $ip -p 9953 stop | sed 's/^/I:$server /'");
+ return;
+}
+
+# Stop a server by sending a signal to it.
+sub stop_signal {
+ my($server, $sig) = @_;
+
+ my $pid_file = server_pid_file($server);
+ return unless -f $pid_file;
+
+ my $pid = read_pid($pid_file);
+ return unless defined($pid);
+
+ if ($sig eq 'ABRT') {
+ print "I:$server didn't die when sent a SIGTERM\n";
+ $errors++;
+ }
+
+ my $result = kill $sig, $pid;
+ if (!$result) {
+ print "I:$server died before a SIG$sig was sent\n";
+ unlink $pid_file;
+ $errors++;
+ }
+
+ return;
+}
+
+sub wait_for_servers {
+ my($timeout, @servers) = @_;
+
+ my @pid_files = grep { defined($_) }
+ map { server_pid_file($_) } @servers;
+
+ while ($timeout > 0 && @pid_files > 0) {
+ @pid_files = grep { -f $_ } @pid_files;
+ sleep 1 if (@pid_files > 0);
+ $timeout--;
+ }
+
+ return;
+}
diff --git a/tools/README b/tools/README
index a18b38a..ce8ddea 100644
--- a/tools/README
+++ b/tools/README
@@ -1,3 +1,4 @@
The "tools" directory contains scripts for helping the BIND 10
-developers maintain the source tree. These are not intended
-to be built nor installed by the build system.
+developers with various tasks, eg. maintaining the source tree,
+running some tests. These are not intended to be built nor
+installed by the build system.
diff --git a/tools/import_boost.sh b/tools/import_boost.sh
deleted file mode 100755
index 07abe8c..0000000
--- a/tools/import_boost.sh
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/bin/sh
-
-# given a directory, copy all needed parts from boost into the
-# current branch
-
-# only run this to update boost! (i.e. almost never)
-
-# usage example:
-# cd /tmp
-# tar xzvf /location/of/boost/tarball
-# cd /home/user/svn/bind10/trunk
-# tools/import_boost.sh /tmp/boost-version
-# svn commit
-
-# need new boost stuff?
-# TODO: LICENSE_1_0.txt
-# add files to list 'ere
-FILES="
-boost/*.hpp
-boost/algorithm
-boost/asio
-boost/assign/list_inserter.hpp
-boost/assign/std/vector.hpp
-boost/bind
-boost/config
-boost/concept
-boost/detail
-boost/exception
-boost/function
-boost/iterator
-boost/mpl
-boost/preprocessor
-boost/python
-boost/range
-boost/smart_ptr
-boost/type_traits
-boost/utility
-"
-
-TARGET="ext"
-
-if [ $# -ne 1 ]
-then
- echo "Usage: boost_import.sh <boost directory>"
- exit
-fi
-
-if [ ! -d $TARGET/boost ]
-then
- echo "This does not appear to be the main trunk/branch directory"
- exit
-fi
-
-
-DIR=$1
-
-do_cmd()
-{
- echo $@
- $@
-}
-
-
-#echo "cp ${DIR}/boost/shared_ptr.hpp boost/"
-for FILE in ${FILES}
-do
-TGT=`echo ${FILE} | sed 's/[^\/]*$//'`
-cmd="mkdir -p ${TARGET}/${TGT}"
-do_cmd ${cmd}
-cmd="cp -r ${DIR}/${FILE} ${TARGET}/${TGT}"
-do_cmd ${cmd}
-done
-
-
diff --git a/tools/tests_in_valgrind.sh b/tools/tests_in_valgrind.sh
new file mode 100755
index 0000000..14e91ba
--- /dev/null
+++ b/tools/tests_in_valgrind.sh
@@ -0,0 +1,75 @@
+#!/bin/sh
+
+###########################################
+# This script runs all tests in valgrind. Configure and compile bind the way
+# you want it to be tested (you should use --with-gtest, however, or you get
+# no tests). Then run this script from the top build directory.
+#
+# Note that the test isn't what you would call "production quality" (it is
+# expected to be used by the bind10 developers, not end user) and might break,
+# some ways of breaking it are known.
+#
+# There are two variables that modify it's behaviour.
+# * VALGRIND_FLAGS are the flag passed to valgrind. There are some, hopefully
+# reasonable defaults which you can overwrite. Note that the variable is
+# used unmodified inside a sed pattern with # as a modifier, which can
+# easily break it. There was no motivation to fix this.
+# * VALGRIND_FILE is the file to store the output into. Default is valgrind.log
+###########################################
+
+# First, make sure the tests are up to date
+make
+
+if [ $? = 2 ] ; then
+ echo "Did you run configure? Or maybe you're running the script from the tools directory? (you need to run it from the top bind10 build directory)"
+ exit 1
+fi
+
+set -e
+
+# Some configuration
+# TODO Escape for sed, this might break
+LOGFILE="${VALGRIND_FILE:-`pwd`/valgrind.log}"
+FLAGS="${VALGRIND_FLAGS:---leak-check=full --track-fds=yes}"
+FLAGS="$FLAGS --log-file=$LOGFILE.%p"
+
+FOUND_ANY=false
+FAILED=
+
+# Find all the tests (yes, doing it by a name is a nasty hack)
+# Since the while runs in a subprocess, we need to get the assignments out, done by the eval
+eval $(find . -type f -name run_unittests -print | grep -v '\.libs/run_unittests$' | while read testname ; do
+ sed -e 's#exec "#exec valgrind '"$FLAGS"' "#' "$testname" > "$testname.valgrind"
+ chmod +x "$testname.valgrind"
+ echo "$testname" >>"$LOGFILE"
+ echo "===============" >>"$LOGFILE"
+ OLDDIR="`pwd`"
+ cd $(dirname "$testname")
+ ./run_unittests.valgrind >&2 &
+ PID="$!"
+ set +e
+ wait "$PID"
+ CODE="$?"
+ set -e
+ cd "$OLDDIR"
+ if [ "$CODE" != 0 ] ; then
+ echo 'FAILED="$FAILED
+'"$testname"'"'
+ fi
+ NAME="$LOGFILE.$PID"
+ rm "$testname.valgrind"
+ # Remove the ones from death tests
+ grep "==$PID==" "$NAME" >>"$LOGFILE"
+ rm "$NAME"
+ echo 'FOUND_ANY=true'
+done)
+
+if test -n "$FAILED"; then
+ echo "These tests failed:" >&2
+ echo "$FAILED" >&2
+fi
+
+if ! $FOUND_ANY ; then
+ echo "No test was found. It is possible you configured without --with-gtest or you run it from wrong directory" >&2
+ exit 1
+fi
diff --git a/tools/valgrind_test_cleaner.pl b/tools/valgrind_test_cleaner.pl
new file mode 100755
index 0000000..9928e9f
--- /dev/null
+++ b/tools/valgrind_test_cleaner.pl
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# This script can be used on a valgrind output of the tests (from
+# tests_in_valgrind.sh) to remove some uninteresting error reports.
+# Since we care about the tested application not leaking/crashing, not
+# the tests itself, memory leaks that are caused only by the tests
+# (eg. unreleased test data), we don't want to have logs full of them.
+#
+# This script does some heuristics to eliminate some of such error
+# reports. Currently, the memory lost reports whose stack contains
+# no call from the real application are suppressed.
+#
+# Of course, the rest still can contain many uninteresting entries.
+
+# Yes, it's perl even when we use python. I wrote it for myself when
+# I needed to clean the outputs and after it proved useful to me, I
+# thought it might be for others too, so I just included it. It's not
+# that we would be switching to perl. If it should grow in future to
+# include more heuristics and do something more fancy, we should probably
+# rewrite it in python instead.
+
+my ($block, $blockOK);
+
+sub endBlock(_) {
+ return unless $block;
+ if ($blockOK) {
+ print @$block;
+ }
+ undef $block;
+ undef $blockOK;
+}
+
+sub startBlock(_) {
+ $block = [@_];
+}
+
+sub addToBlock(_) {
+ my ($line) = @_;
+ push @$block, $line;
+ return unless $line =~ /^==\d+==\s+(at|by) 0x[0-9A-F]+: (.*) \(.+:\d+\)$/;
+ $_ = $2;
+ return $blockOK = 1 if /^isc::/;
+ return $blockOK = 1 if /^asiolink:/;
+ return if /^main \(/;
+ return if /^testing::/;
+ return if /^\(anonymous namespace\)::/;
+ $blockOK = 1;
+}
+
+while(<>) {
+ if (/^==\d+==\s*$/) {
+ print;
+ endBlock;
+ } elsif (/^==\d+==\s+\d+bytes.*lost in loss record/) {
+ startBlock;
+ } elsif ($block) {
+ addToBlock;
+ } else {
+ print;
+ }
+}
+endBlock;
More information about the bind10-changes
mailing list