BIND 10 trac1213, updated. 044381e03b7f178c7c322861960b79c8a27bb4b1 [1213] Merge branch 'master' into trac1213
BIND 10 source code commits
bind10-changes at lists.isc.org
Wed Oct 12 13:24:21 UTC 2011
The branch, trac1213 has been updated
via 044381e03b7f178c7c322861960b79c8a27bb4b1 (commit)
via 625e9b719947e894ad7369d8ca61df23ea31b243 (commit)
via 8a40bdd1108f37caacd6bc5f367ecc1587ee53cc (commit)
via ad1161678c25ee35b1cb7d657d1aba411939efdd (commit)
via b0e43dae72cf709cb01627eb9e3095cc48989f4e (commit)
via 0c88eb0d723fa43865e185b201aba2685173f378 (commit)
via 9ec6d23aa2ca58dd13a45821c92a926a0780591c (commit)
via 46230c83bb8d70e170fe77e9e936765014b762d2 (commit)
via 594dc98507783efcec6781077467885990094ec6 (commit)
via 656f891efdbb6cda87d10a06f7c2ac883e17fb7b (commit)
via 691a23f33ccde30a0d741b98bf0439228336af01 (commit)
via 1c16d6d7fe6253041362ff994e7594805c297b89 (commit)
via 8eb6232b0094778b4c195a870fa2c06cd1b7d0ab (commit)
via f6445b024942629726daeb591f99af090aa43c28 (commit)
via 146934075349f94ee27f23bf9ff01711b94e369e (commit)
via 911cb21ff76c1b887d8ce5e52a3059eaba9ec7e0 (commit)
via 794cb37669e1a0566c6435e38e247ded643fa96c (commit)
via de9778c0c9db5a2e6ca3cfa384ae5a7b84120281 (commit)
via 14c73702aebaea61c543730e4aec2608b842b5c6 (commit)
via 625d818594e468ebd8bf89a6c09a97208b58071e (commit)
via 81240b14097c5311ba5585f01f344b18b2048fcf (commit)
via 4a4d7bbde30de5eec9cd7753e3f44c92c2c057d7 (commit)
via ff571bb13401ce21184923c973ee2cb67b85cade (commit)
via 3a7f572d5a406e294373ba56b1a0357252fb30cd (commit)
via 4e99a42d3634690c74963ec9fc5c45ae21431775 (commit)
via 2e74ee9f329249738ddf00599090f94ef80eecc1 (commit)
via bc1c9342a382378d6d659e3fcf87d6730ea71e81 (commit)
via 38e4a2c44f8f73f81b56e54a7436bb9662b9851f (commit)
via af3b17472694f58b3d6a56d0baf64601b0f6a6a1 (commit)
via 0e4960761e5bd30e5157e45711da1013d825764e (commit)
via f00e85fff2018483303ccc3dbf7d85b4052cae1c (commit)
via ab1f7bea793d2435080e5cc018f115169ddf07f0 (commit)
via c5753d1c96374bfdf2c8e9fc0773ac036082cfa5 (commit)
via ff329082790af7572016f64a90f62c7be87f593c (commit)
via 32007ad7c992f395895eb8f27343003cf4f94a20 (commit)
via 5d1dc735923493b057014df7fefa8c8d7b04349e (commit)
via 5fb87e69f26c800823be33e81f99e1cb2143e067 (commit)
via d903fe92287645e9701890b0953bd84529665776 (commit)
via ecb3b76489bf838fe32030517e3c8b23000d59bd (commit)
via df1298668ac3e758576b8b2bd6475c70cff7a57f (commit)
via f3f87eb305123de57135aaa96c12190f3bf1951b (commit)
via 0a149e0c7faf8fc0db56d4804acfb3df99dcebb4 (commit)
via 5ca7b409bccc815cee58c804236504fda1c1c147 (commit)
via 1e6ee8d85fb949a2e39eb0f130b6a2d4a6470958 (commit)
via a903d1aae9ab0ab3095144b9d2db7d5fc606b187 (commit)
via dcbc2db0a515208db5cbfc5a2ba88c14120ba1bb (commit)
via be1f9361884f15155c20fc8f8979d9ee32102713 (commit)
via 4f423f99cb3b73d75a736c9610f3faf30cc3d837 (commit)
via 982f6e4d7e7a2ffb0d17add0df1e5643aa38c092 (commit)
via 98104aa8ac64b6602fa6c1c7c7eb08e9b43f0fa6 (commit)
via 5220fc55e92504899d102b5704382382a4e450c1 (commit)
via 21d7a1b1870466cd8b9f6203d509d9a9601e5c87 (commit)
via bb1400f97e377247cda132a14cdcb5dcc3f456e1 (commit)
via 1d007555e13f0e148014b4582f6fbd8b6b7fd386 (commit)
via 9163208c660f8ef8c4b1dbdae6c0c785c516bb1a (commit)
via f5c9c2f489e84de596aff390c498ec31fe44a5b0 (commit)
via 56bd0746aef2a0b789402844e041a268b889b081 (commit)
via c4949d3d2b74f62824b670cf8d07cfe9e965a661 (commit)
via d76b95edce86919636ee0e458f0b9def08a9d2ea (commit)
via d4405856fd2e088fbc7cd4caa9b2e9a6c66e8e83 (commit)
via 99fbf7cc5eacc8c0ec65a19a1eb83b4e0a329cd1 (commit)
via ff4a86af49e629a83e40f35c78c7616a208659c4 (commit)
via 47ea557d9d8a9782e4d576c45c545913bbaac4ea (commit)
via 006133b49eb5d44eeacb1d79593b97ae2212bbca (commit)
via 261656de6d4fbe2f6979162c978023f8859d2289 (commit)
via 419768f653e098ab566c9f94771552e2bfe3cc99 (commit)
via affa93f31bbc9719ac4e2ccc0e44d9a09c2b6a3b (commit)
via b6465a25eb8106081484d17a48c75031c14c50d2 (commit)
via a6222db2c3da815eb23c6deab6390066b0969670 (commit)
via 6117a5805ef05672b9e05e961f7fa0f8aa5fca0e (commit)
via 7e8fc92cb83d984188bd1556ead421bee39d9581 (commit)
via 929daeade2b98ad56b156eadd22308bd6d7f265a (commit)
via 64ba30803ae7a87f1c6bc21eb1a45c413fb6ce43 (commit)
via 6588fc2759e5901f61327f170bb9ce0ec3d0bfcd (commit)
via 1f77f60db465b53342d88b4280b1889f1888b411 (commit)
via 7ae9a73236a4bb7eed9f02b30563b7f4e040863f (commit)
via 35f2bd564e1e0311e3440f09bf81aac822d65a1c (commit)
via f5bb60e5636d908de8534d35b5f06142ae2a8c3a (commit)
via b8d12c83236964f6bbb5cd3910b0960abd0117c1 (commit)
via c260476dc19056181931668db6316055526f4daf (commit)
via 60765d3c47eedd4bf412b53c2ce47c5de41be8a8 (commit)
via b26befde4983f01b37f7e29bc8ebb8dbc7f6c1de (commit)
via d178a34c2798221f7cee90d07bfced84df4908d6 (commit)
via da9206de5ccdb4ff074c0246856ac8de487eff40 (commit)
via 6aa910d6307f825013e2e0d7b5b1e4599a634f1b (commit)
via 9bbc2ac61f19fe7d27f3268fb4de7dd727a59bb0 (commit)
via ff23bfe6d68eeb0972e9b01a45b950e6ae781b01 (commit)
via 0fd60764e65b270cafc1b3b573e5ac14b3c633d6 (commit)
via a3c0c4cffe519c5d70185ec989fe707102a71836 (commit)
via d119c3155e617e120c69abebcf8d22e500dcffca (commit)
via c80a7b8a53dc04e66b55431e2d4c637618293dae (commit)
via 31830397285a50d1636622b58d04fffc7ca883ae (commit)
via c96f735cd5bbbd8be3c32e7d40f264ebfa889be5 (commit)
via 973fc74ac4c030a350f436e96d37a582565e41dc (commit)
via 95cbc4efbaab12b66852ede318cb9af0d3f8780b (commit)
via 90ab0a155bc5e42ef2ad35569968dd3db9c501bb (commit)
via 1fe148279b130dc4c8c072ab3bd1006cdacfc9f6 (commit)
via 137d1b29b6063f4d1983bde07f6ec5404f67dcee (commit)
via 8f3c0649785d7fb0df37a9ba9e0e20c978044bb7 (commit)
via 2a2aa4ccfb548b2a18b10e97acd80df324c5d4a8 (commit)
via 02acd96cff43650110f4af6d2fb2a8143887ac00 (commit)
via a6790c80bfcefde81e032db9d3a45c7a9e48faad (commit)
via 2342cdf1ff5563c6afa1901104fe4cda849ad345 (commit)
via 5b302edc6302331a2c39ae1ac5a18759e47340c0 (commit)
via 85071d50cf5e1a569b447ba00e118db04293475a (commit)
via 70f720080190f2ec3536bd5c15c7ada18a7a7fa7 (commit)
via 3898b36a132fe44e51cc99674104d9e1f0d35d36 (commit)
via ed7eecab42af0064d261d9c9dafd701250bbc1d3 (commit)
via 2adf4a90ad79754d52126e7988769580d20501c3 (commit)
via d6616e7ef66b3904e2d585e7b4946900f67d3b70 (commit)
via c4344fadc93b62af473a8e05fc3a453256e4ce13 (commit)
via a9b140ed88b9a25f47e5649b635c8a19e81bfdee (commit)
via f5d7359a945241edf986b7c91c0ad6c7bcf113e3 (commit)
via 6b206d435a3dd92ef4a18f1c4558da147016fe4f (commit)
via cf136247fad510f55ba230f746558274fada1de6 (commit)
via 5f5b2d7a98eff5dc74f74b7018f50e286ae82c2d (commit)
via 7209be736accd15885ad7eafc23b36eec18c2213 (commit)
via 703cd3ed5855e673443e898d427fdc7768c5bceb (commit)
via 1a035954731fce34faf09705bc61b7eb0ad34ac6 (commit)
via ae43bdedcfaabacbc8e4455313e6a5b4d98a68cd (commit)
via 017b4e1bcc7fe62f11650444518f422934c925ff (commit)
via e9e29a281b0b8b9d91fe9097e51c7e5df6d3ff78 (commit)
via fcd39b6e84665a033d7ee4c06bd904e2b416c53a (commit)
via ce00497088209db82fbbabb80381acf92039763c (commit)
via 0fbdaf01b0fc3d7031b51d542b91f6f758f033fa (commit)
via b3a1ea108d3df58dcd2d247fdc87b3d1fbd953cf (commit)
via 2de8b71f8c0e7d02e25aa7ec6fa13f9933c8b534 (commit)
via 4edd9c38112db5161f46533ffb3886c85880ee03 (commit)
via bff7aa9429b7e0a9f26f69dd24c8aa7efc64ffc6 (commit)
via 1e0d70a994d9cf9cabe10d1205c40b74af2a2bc4 (commit)
via 738afedababcfc874fe107d9bc408d69d213813e (commit)
via 8ed59723a5ae90dedcbf741254b65f88a4c98ca1 (commit)
via 3f2d29d0dc92606fac3ba306c34a32a0bec8159e (commit)
via 3bfaa404624697f5e2f08076c78f94a8438e851c (commit)
via f85f868171956abcc1996235a26a276da2ca6209 (commit)
via 1982c235382043d87737ec24779d10da216101a6 (commit)
via f6c675c19790d3715445a7877cc8d1d193f17071 (commit)
via 7225bbf8e6e3c892159124e7795f7396b5764bb8 (commit)
via 2056251f56e4c5e3ff785b924061fecfe1ac21e4 (commit)
via a5eeb73116cbc74f6bb3fb4a06b99396a8ceebcb (commit)
via 743dad9408b0a86052156e6a3d4fec1001600017 (commit)
via af927e2c390b49012b276c11991a3f7ef3a592a9 (commit)
via d267c0511a07c41cd92e3b0b9ee9bf693743a7cf (commit)
via 42968abbd4edf489d4d667089033d11e4045f463 (commit)
via 33c0d21361655c08b274c75736b7bcbe99dd3d2d (commit)
via e114429f15c0ff8b5eb77728985281afcfc0d37a (commit)
via 6dbe35be17827ccf8bfc904be707aea01fb4ef94 (commit)
via a8a8ceb589f9f3bf4da29717eec446cb2766032c (commit)
via 1f6c32ac6941c3c2ec456017e73ea74ca5944e1c (commit)
via 71488ea628a1d791eeba41cb2eed3025c6311565 (commit)
via 956e210239d46bebe4574c5ca38b3b51b1bb7c65 (commit)
via fe76209cd8ad96144f0e2fc9522f5fda1d52d9c3 (commit)
via cadfcca91ef5bdb2c72c9db4e918ff6ac7b10e65 (commit)
via eb4917aea94d78ea64fa90f0c70501bbb6d48b37 (commit)
via a01b47d66272166135c20bf15a958bed023ff009 (commit)
via 461acc1e4b464611411ae77b7a72d65c744a740e (commit)
via 9163b3833877225c8b9bd8e59eb7159ea65d3867 (commit)
via e451eade196bc7cc43102412a73faa397253c841 (commit)
via 5a2d0b61afe86668613cbb83a75708b760aae76f (commit)
via 43bbfab2cc57a08da6d2d6ffe8da92efcae9c2ec (commit)
via 38e530b762f7d05caaf06aec41c6df432f0800cf (commit)
via e8e1bd309d449b23dae2b472b650a130300aa760 (commit)
via a5adb8e45ee8c66a19c46bd1bf5f752630619be8 (commit)
via 85ac49c5282c231c71b8d2046889d22b0061db08 (commit)
via ebeb5ead60c5c0d7b16478498b78a8f1ef3b71c3 (commit)
via e38010819247006d20532d24de8dd6c37e0ca664 (commit)
via 00f4c38428153bb5ad99ba1cc40e9a204266dace (commit)
via f7bb760f4d8290d52959ea83b090d1877e4ac9ee (commit)
via b29c5e5221b8e6a9ff65a0c39f14c04afaed5c44 (commit)
via 9e17bd49b426ffba00312cf90ec80d178a20b964 (commit)
via 519720d9c6eb354a2e31089f1c7b8fd0760053f9 (commit)
via c6babcd3e44bc42fdb090d3a4837848d8c7c149c (commit)
via bb444bae93e8e87d1e62214b1819fb73fd7634e4 (commit)
via d36eda71276b43e4281ae53fd558155725f4d4eb (commit)
via 32f075fa288dc5ea049cbf72657386889144bd12 (commit)
via 471edfd7a86d91f04536bc7c7fb42ad7239e1731 (commit)
via feeddd7e5b966c9445fc4ac97a6526fa792413cd (commit)
via d801b1e2ebb6c9cf35e3475040b013784f3e6e41 (commit)
via e4c78a2739dddade3aaaa12528afff944458f777 (commit)
via 48bec4c73b92679e91f0cc72fc63bdba9c593e87 (commit)
via dc5aa6284fe6b6f51d85270969f0befd8db1f838 (commit)
via 15e60f1f54722c32c9977f00e49c211f047ee08f (commit)
via 7ab4a102ee610f36b4362897431e4fbbeac735c5 (commit)
via 4fda2b6eefe81f1c197d32a0c8eb14ca1a7d9108 (commit)
via 106e9a793499c81698cf5a938d48933f5e909af4 (commit)
via 26691e282b76d74959e63524b280e77b09ac89df (commit)
via 763a994cb14bb11ba823831f54d64071319bfac0 (commit)
via b86d51b24e7d1bb4980426c9a74962628c096ba7 (commit)
via 48d5ac59277e2e8b43f697a0d1d4b0991a40caa0 (commit)
via c191f23dfc2b0179ec0a010a1ff00fa3ae1d9398 (commit)
via 8d2c46f19c1b4f435d7b9180ff6c2e8daf78ab2b (commit)
via 80319933903fbdb359ef9472573bfaceda7c8cd5 (commit)
via 8c838cf57adef3c004b910b086513d9620147692 (commit)
via 1378551aa74712c929a79964ae18d9962ce73787 (commit)
via fdf1c88a53f5970aa4e6d55da42303ce7d4730f7 (commit)
via 33ee923f7139cbda7a616a83d572a4358f456e16 (commit)
via 9b23d60d6f58b18da3995dc3e090d7fd63233bcc (commit)
via 4bb4081381b39c563707c03818a0f9d16ef7846f (commit)
via eef5b0eb5defdd22ef5e351213ab66531f788c5d (commit)
via e7f1ead205f2dc13d6fd6e2a28b121794ca281be (commit)
via 638674c480d47cf957a8b4f7d61dda3320c881ff (commit)
via 0a22b98c05bf5032c190fbfdf9fefceac3597411 (commit)
via f59415a8b5ee951dd298eaf8eecaa21e8955851c (commit)
via 4e458fc15b5c236e1cc44565f6af313753e87a26 (commit)
via e2eca96f1876a72fc8c121c9204d49cb7e9eaeb7 (commit)
via 85455b6e2f7063b10bae9938de1b70f5d319911e (commit)
via 66e1420d30f8e71e867a3b5b0a73ead1156d5660 (commit)
via 16cc75f764b6ea509f386c261b472e282cd606ed (commit)
via b1f197c6102ae31ded2e4b61103308dcdfa72a89 (commit)
via 5e14c4caafaa44b92134c5df01b726f435f46845 (commit)
via 0b46c391a973bb8d3f0a1681eb0a79e8a196f0f0 (commit)
via 5e5743ecb40da81c4e8ad27ac8b158c9a7aaff87 (commit)
via 9c95bf79406ae791e2f8c7263ff4fddb19d0eda4 (commit)
via 58b843554162e6599ba895c8325985f74adef734 (commit)
via 98cb905a5852321204499985efb42c5a76b9da6e (commit)
via f7a92e4b0336f3c64eb429947657952178b7d76f (commit)
via 3ff9c6c215faa2e1419d4cb67906a1f7772b355a (commit)
via 90b3952ff515f8746ffc6b227695836921bc046d (commit)
via 0372723794501908ae94be9330dcd8577d951f68 (commit)
via 6b27a7ba1c0343725e3d2e9ea7d97426a8f73f0d (commit)
via a8b5aabeb7b56702a85344434d7822a034ff140c (commit)
via 87a3c86e7e132a1ee80bf29b418ad4b61cefc7d8 (commit)
via 8b4f53f245ab45bf07be9b1108fca951133b836a (commit)
via 07b6398dbd11037eb553fc6fcf56dc8051e71150 (commit)
via f0ef6c88066961a038ea1b80face4feaa9a2d17d (commit)
via 8f9f4ece764df4607f695f3f7eb4c421e8ac4c9d (commit)
via 7751d0ac43f1b7186a53ba5dd5cf2eeca6f7dc46 (commit)
via b6dd72042939ca62d9ceeb80385eedc7c5f0560d (commit)
via 31e010330189f489c624b7cdb812ef3f33f8e280 (commit)
via efe8aa23b59448214ef826a5910e52bdf0ce0015 (commit)
via f8b10842465d60483e3bc9827e06115ea8081bfc (commit)
via a4ff990c9b0136c97b101f42dd5498a453fbdf25 (commit)
via 06341cb6cdbd5ff57c376f7b0b25aba4a35bab86 (commit)
via 54aad8af04350eb3a45a4bd6623681efa2f8d2fb (commit)
via 749e1c9c0627c0a20dc824ecc8c475ecee613d8a (commit)
via 8d8d6bd981771edb3011afedc5e62a59d78d7826 (commit)
via eefc291d240bc1fe15d131df9d463343b0333d3a (commit)
via fb3d0e1146e9e5a36a9402690a09e7629408c677 (commit)
via 7e857cbcbd5dfa64552d15dee5ed01ca39bf8937 (commit)
via fa4c8fa8acbff7f4defc768e50a453bc376c56de (commit)
via 9b0785b11da612abf0e60f39950ebed9977b2e65 (commit)
via 8e5b40643255bd93c6edda9cabed39f46b074b0d (commit)
via 3a11f2fd5bbe98fc555bfdf1cdf9019f7222e3e9 (commit)
via 21bac503aa78b1d0cbb6993edc083fbc508dad16 (commit)
via ee826c177bef06f22cdbbf82044085972bfd8737 (commit)
via 4111989bb81641ee36fa94bf5cb181aa18f5477f (commit)
via 68653f1c822916ceade94511168f87adff74c235 (commit)
via f6463fa6e74a32e3fb28f150247e11d0fe073782 (commit)
via d63056e7cff35f58898a9bdc8d5cad589689590c (commit)
via 51a7361aef92b8c6caad857ed09f0bea0f210db6 (commit)
via b51f0cfcedc2499aa1c0b85aaebf2fecf244c291 (commit)
via ecffd3a7f26c9a1590994bb176494ed4f4ca7a64 (commit)
via 0edf51aca0ea2d75ed9d96fb612c1005965ec64f (commit)
via f83a47d3c260982be4918a3d9f5d0b480503b131 (commit)
via 8c84a6865c4b09eccf41c9d2e91a030c941bffab (commit)
via a9ddfa91f81e00400f04548e71ab9519892a6dea (commit)
via 04749187843604f51ddcab4f53811dac9a9ed8a0 (commit)
via a03d7d9aae8ac258d266c66c62c63e03ff5d2558 (commit)
via d14895917e4841ee53c46f7ab3f46c3f19489069 (commit)
via eb5023d2a38e0862e2d9a5f1ca4a3788fc131405 (commit)
via 4102716ab6f3cfaa979151029c2859701dfe2ac6 (commit)
via 8975d286a6de827a02b073de32570602cd9cffbc (commit)
via b2cab7978ff20eff1d4fcb4cf60fc8a4421fc24c (commit)
via f4620596bd798f3c0e1d4b7738a5c4ca1730cf89 (commit)
via 600d77cc8af4625a30dceda2033c4aadbbbe71ff (commit)
via 3b1a604abf5709bfda7271fa94213f7d823de69d (commit)
via 0caae46ef006c8322d489d6b140c0aee91928803 (commit)
via 70d50df5bc495661463ff19885b9a4112270bafa (commit)
via d3ef96824420d7f089b28e6521790191e39949bf (commit)
from f2b5473fc2f2dfa13485fe9822e84fadd69ac950 (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 044381e03b7f178c7c322861960b79c8a27bb4b1
Merge: f2b5473fc2f2dfa13485fe9822e84fadd69ac950 625e9b719947e894ad7369d8ca61df23ea31b243
Author: Stephen Morris <stephen at isc.org>
Date: Wed Oct 12 14:23:57 2011 +0100
[1213] Merge branch 'master' into trac1213
Conflicts:
tests/system/cleanall.sh
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 41 +-
Makefile.am | 4 +
configure.ac | 6 +
doc/Doxyfile | 2 +-
doc/guide/bind10-guide.xml | 56 +-
src/bin/auth/Makefile.am | 6 +
src/bin/auth/benchmarks/Makefile.am | 6 +
src/bin/auth/tests/Makefile.am | 9 +-
src/bin/dhcp6/Makefile.am | 9 +-
src/bin/dhcp6/b10-dhcp6.8 | 4 +-
src/bin/dhcp6/dhcp6.h | 73 +-
src/bin/dhcp6/dhcp6_srv.cc | 55 +
src/bin/dhcp6/dhcp6_srv.h | 40 +
src/bin/dhcp6/iface_mgr.cc | 581 +++++++++
src/bin/dhcp6/iface_mgr.h | 103 ++
src/bin/dhcp6/interfaces.txt | 10 +
src/bin/dhcp6/main.cc | 36 +-
src/bin/dhcp6/pkt6.cc | 46 +
src/bin/dhcp6/pkt6.h | 62 +
src/bin/dhcp6/tests/Makefile.am | 45 +
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 53 +
src/bin/dhcp6/tests/dhcp6_unittests.cc | 28 +
src/bin/dhcp6/tests/iface_mgr_unittest.cc | 263 ++++
src/bin/dhcp6/tests/pkt6_unittest.cc | 44 +
src/bin/resolver/tests/Makefile.am | 4 +-
src/bin/sockcreator/tests/Makefile.am | 2 +-
src/bin/xfrin/b10-xfrin.xml | 15 +-
src/bin/xfrin/tests/Makefile.am | 7 +
src/bin/xfrin/tests/testdata/Makefile.am | 2 +
src/bin/xfrin/tests/testdata/example.com | 17 +
src/bin/xfrin/tests/testdata/example.com.sqlite3 | Bin 0 -> 11264 bytes
src/bin/xfrin/tests/xfrin_test.py | 1266 ++++++++++++++++++--
src/bin/xfrin/xfrin.py.in | 561 ++++++++-
src/bin/xfrin/xfrin_messages.mes | 50 +-
src/cppcheck-suppress.lst | 1 +
src/lib/acl/tests/Makefile.am | 2 +-
src/lib/asiodns/tests/Makefile.am | 2 +-
src/lib/asiolink/io_address.cc | 5 +
src/lib/asiolink/io_address.h | 8 +
src/lib/asiolink/tests/Makefile.am | 2 +-
src/lib/asiolink/tests/io_address_unittest.cc | 2 +
src/lib/asiolink/tests/io_endpoint_unittest.cc | 2 +-
src/lib/bench/tests/Makefile.am | 2 +-
src/lib/cache/tests/Makefile.am | 2 +-
src/lib/cc/tests/Makefile.am | 2 +-
src/lib/config/tests/Makefile.am | 2 +-
src/lib/cryptolink/tests/Makefile.am | 2 +-
src/lib/datasrc/Makefile.am | 16 +-
src/lib/datasrc/client.h | 44 +
src/lib/datasrc/data_source.h | 12 +-
src/lib/datasrc/factory.cc | 95 ++
src/lib/datasrc/factory.h | 170 +++
src/lib/datasrc/memory_datasrc.cc | 151 +++-
src/lib/datasrc/memory_datasrc.h | 36 +-
src/lib/datasrc/sqlite3_accessor.cc | 95 ++-
src/lib/datasrc/sqlite3_accessor.h | 39 +-
src/lib/datasrc/tests/Makefile.am | 19 +-
src/lib/datasrc/tests/database_unittest.cc | 3 +-
src/lib/datasrc/tests/factory_unittest.cc | 175 +++
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc | 40 +-
src/lib/dns/Makefile.am | 4 +
src/lib/dns/python/message_python.cc | 8 +-
src/lib/dns/rdata/generic/detail/ds_like.h | 225 ++++
src/lib/dns/rdata/generic/detail/txt_like.h | 56 +-
src/lib/dns/rdata/generic/dlv_32769.cc | 121 ++
src/lib/dns/rdata/generic/dlv_32769.h | 77 ++
src/lib/dns/rdata/generic/ds_43.cc | 109 +--
src/lib/dns/rdata/generic/ds_43.h | 33 +-
src/lib/dns/rdata/generic/spf_99.cc | 44 +
src/lib/dns/rdata/generic/spf_99.h | 26 +
src/lib/dns/rdata/in_1/dhcid_49.cc | 8 +-
src/lib/dns/rdata/in_1/dhcid_49.h | 2 +-
src/lib/dns/tests/Makefile.am | 8 +-
src/lib/dns/tests/rdata_dhcid_unittest.cc | 111 ++
src/lib/dns/tests/rdata_ds_like_unittest.cc | 171 +++
src/lib/dns/tests/rdata_ds_unittest.cc | 99 --
src/lib/dns/tests/rdata_txt_like_unittest.cc | 261 ++++
src/lib/dns/tests/rdata_txt_unittest.cc | 166 ---
src/lib/dns/tests/testdata/Makefile.am | 1 +
src/lib/dns/tests/testdata/rdata_dhcid_fromWire | 12 +
src/lib/dns/tests/testdata/rdata_dhcid_toWire | 7 +
src/lib/exceptions/tests/Makefile.am | 2 +-
src/lib/log/tests/Makefile.am | 2 +-
src/lib/nsas/tests/Makefile.am | 2 +-
src/lib/python/isc/Makefile.am | 2 +-
src/lib/python/isc/config/ccsession.py | 4 +-
src/lib/python/isc/config/tests/ccsession_test.py | 3 +
src/lib/python/isc/datasrc/Makefile.am | 1 -
src/lib/python/isc/datasrc/__init__.py | 14 +
src/lib/python/isc/datasrc/client_inc.cc | 17 +-
src/lib/python/isc/datasrc/client_python.cc | 57 +-
src/lib/python/isc/datasrc/datasrc.cc | 83 +-
src/lib/python/isc/datasrc/finder_inc.cc | 22 +
src/lib/python/isc/datasrc/finder_python.cc | 54 +-
src/lib/python/isc/datasrc/finder_python.h | 10 +-
src/lib/python/isc/datasrc/iterator_python.cc | 19 +-
src/lib/python/isc/datasrc/iterator_python.h | 10 +-
src/lib/python/isc/datasrc/tests/Makefile.am | 7 +-
src/lib/python/isc/datasrc/tests/datasrc_test.py | 100 ++-
src/lib/python/isc/datasrc/updater_python.cc | 63 +-
src/lib/python/isc/datasrc/updater_python.h | 10 +-
src/lib/python/isc/dns/Makefile.am | 1 +
src/lib/python/isc/log_messages/Makefile.am | 2 +
.../python/isc/log_messages/libxfrin_messages.py | 1 +
src/lib/python/isc/xfrin/Makefile.am | 23 +
src/lib/python/isc/{bind10 => xfrin}/__init__.py | 0
src/lib/python/isc/xfrin/diff.py | 237 ++++
src/lib/python/isc/xfrin/libxfrin_messages.mes | 21 +
src/lib/python/isc/xfrin/tests/Makefile.am | 24 +
src/lib/python/isc/xfrin/tests/diff_tests.py | 446 +++++++
src/lib/resolve/tests/Makefile.am | 2 +-
src/lib/server_common/tests/Makefile.am | 2 +-
src/lib/util/pyunittests/Makefile.am | 7 +-
src/lib/util/tests/Makefile.am | 2 +-
src/lib/util/unittests/Makefile.am | 2 +-
tests/system/cleanall.sh | 5 +-
tests/system/ixfr/clean_ns.sh | 3 +-
tests/tools/badpacket/Makefile.am | 2 +-
tests/tools/badpacket/tests/Makefile.am | 2 +-
119 files changed, 6354 insertions(+), 894 deletions(-)
create mode 100644 src/bin/dhcp6/dhcp6_srv.cc
create mode 100644 src/bin/dhcp6/dhcp6_srv.h
create mode 100644 src/bin/dhcp6/iface_mgr.cc
create mode 100644 src/bin/dhcp6/iface_mgr.h
create mode 100644 src/bin/dhcp6/interfaces.txt
create mode 100644 src/bin/dhcp6/pkt6.cc
create mode 100644 src/bin/dhcp6/pkt6.h
create mode 100644 src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
create mode 100644 src/bin/dhcp6/tests/dhcp6_unittests.cc
create mode 100644 src/bin/dhcp6/tests/iface_mgr_unittest.cc
create mode 100644 src/bin/dhcp6/tests/pkt6_unittest.cc
create mode 100644 src/bin/xfrin/tests/testdata/Makefile.am
create mode 100644 src/bin/xfrin/tests/testdata/example.com
create mode 100644 src/bin/xfrin/tests/testdata/example.com.sqlite3
create mode 100644 src/lib/datasrc/factory.cc
create mode 100644 src/lib/datasrc/factory.h
create mode 100644 src/lib/datasrc/tests/factory_unittest.cc
create mode 100644 src/lib/dns/rdata/generic/detail/ds_like.h
create mode 100644 src/lib/dns/rdata/generic/dlv_32769.cc
create mode 100644 src/lib/dns/rdata/generic/dlv_32769.h
create mode 100644 src/lib/dns/tests/rdata_dhcid_unittest.cc
create mode 100644 src/lib/dns/tests/rdata_ds_like_unittest.cc
delete mode 100644 src/lib/dns/tests/rdata_ds_unittest.cc
create mode 100644 src/lib/dns/tests/rdata_txt_like_unittest.cc
delete mode 100644 src/lib/dns/tests/rdata_txt_unittest.cc
create mode 100644 src/lib/dns/tests/testdata/rdata_dhcid_fromWire
create mode 100644 src/lib/dns/tests/testdata/rdata_dhcid_toWire
create mode 100644 src/lib/python/isc/log_messages/libxfrin_messages.py
create mode 100644 src/lib/python/isc/xfrin/Makefile.am
copy src/lib/python/isc/{bind10 => xfrin}/__init__.py (100%)
create mode 100644 src/lib/python/isc/xfrin/diff.py
create mode 100644 src/lib/python/isc/xfrin/libxfrin_messages.mes
create mode 100644 src/lib/python/isc/xfrin/tests/Makefile.am
create mode 100644 src/lib/python/isc/xfrin/tests/diff_tests.py
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 82e920b..2db865e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,42 @@
+297. [func] dvv
+ Implement the SPF rrtype according to RFC4408.
+ (Trac #1140, git 146934075349f94ee27f23bf9ff01711b94e369e)
+
+296. [build] jreed
+ Do not install the unittest libraries. At this time, they
+ are not useful without source tree (and they may or may
+ not have googletest support). Also, convert several makefiles
+ to build tests at "check" time and not build time.
+ (Trac #1091, git 2adf4a90ad79754d52126e7988769580d20501c3)
+
+295. [bug] jinmei
+ __init__.py for isc.dns was installed in the wrong directory,
+ which would now make xfrin fail to start. It was also bad
+ in that it replaced any existing __init__.py in th public
+ site-packages directory. After applying this fix You may want to
+ check if the wrong init file is in the wrong place, in which
+ case it should be removed.
+ (Trac #1285, git af3b17472694f58b3d6a56d0baf64601b0f6a6a1)
+
+294. [func] jelte, jinmei, vorner
+ b10-xfrin now supports incoming IXFR. See BIND 10 Guide for
+ how to configure it and operational notes.
+ (Trac #1212, multiple git merges)
+
+293. [func]* tomek
+ b10-dhcp6: Implemented DHCPv6 echo server. It joins DHCPv6
+ multicast groups and listens to incoming DHCPv6 client messages.
+ Received messages are then echoed back to clients. This
+ functionality is limited, but it can be used to test out client
+ resiliency to unexpected messages. Note that network interface
+ detection routines are not implemented yet, so interface name
+ and its address must be specified in interfaces.txt.
+ (Trac #878, git 3b1a604abf5709bfda7271fa94213f7d823de69d)
+
+292. [func] dvv
+ Implement the DLV rrtype according to RFC4431.
+ (Trac #1144, git d267c0511a07c41cd92e3b0b9ee9bf693743a7cf)
+
291. [func] naokikambe
Statistics items are specified by each module's spec file.
Stats module can read these through the config manager. Stats
@@ -31,7 +70,7 @@
configuration.
(Trac #1165, git 698176eccd5d55759fe9448b2c249717c932ac31)
-288. [bug] stephen
+288. [bug] stephen
Fixed problem whereby the order in which component files appeared in
rdataclass.cc was system dependent, leading to problems on some
systems where data types were used before the header file in which
diff --git a/Makefile.am b/Makefile.am
index b07ef0f..50aa6b9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,12 +2,16 @@ SUBDIRS = doc src tests
USE_LCOV=@USE_LCOV@
LCOV=@LCOV@
GENHTML=@GENHTML@
+DISTCHECK_GTEST_CONFIGURE_FLAG=@DISTCHECK_GTEST_CONFIGURE_FLAG@
DISTCLEANFILES = config.report
# When running distcheck target, do not install the configurations
DISTCHECK_CONFIGURE_FLAGS = --disable-install-configurations
+# Use same --with-gtest flag if set
+DISTCHECK_CONFIGURE_FLAGS += $(DISTCHECK_GTEST_CONFIGURE_FLAG)
+
clean-cpp-coverage:
@if [ $(USE_LCOV) = yes ] ; then \
$(LCOV) --directory . --zerocounters; \
diff --git a/configure.ac b/configure.ac
index b5b9256..664ce48 100644
--- a/configure.ac
+++ b/configure.ac
@@ -650,6 +650,7 @@ fi
#
if test "$gtest_path" != "no"
then
+ DISTCHECK_GTEST_CONFIGURE_FLAG="--with-gtest=\"$gtest_path\""
if test "$gtest_path" != "yes"; then
GTEST_PATHS=$gtest_path
if test -x "${gtest_path}/bin/gtest-config" ; then
@@ -690,8 +691,10 @@ else
GTEST_INCLUDES=
GTEST_LDFLAGS=
GTEST_LDADD=
+ DISTCHECK_GTEST_CONFIGURE_FLAG=
fi
AM_CONDITIONAL(HAVE_GTEST, test $gtest_path != "no")
+AC_SUBST(DISTCHECK_GTEST_CONFIGURE_FLAG)
AC_SUBST(GTEST_INCLUDES)
AC_SUBST(GTEST_LDFLAGS)
AC_SUBST(GTEST_LDADD)
@@ -813,6 +816,7 @@ AC_CONFIG_FILES([Makefile
src/bin/sockcreator/tests/Makefile
src/bin/xfrin/Makefile
src/bin/xfrin/tests/Makefile
+ src/bin/xfrin/tests/testdata/Makefile
src/bin/xfrout/Makefile
src/bin/xfrout/tests/Makefile
src/bin/zonemgr/Makefile
@@ -855,6 +859,8 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/testutils/Makefile
src/lib/python/isc/bind10/Makefile
src/lib/python/isc/bind10/tests/Makefile
+ src/lib/python/isc/xfrin/Makefile
+ src/lib/python/isc/xfrin/tests/Makefile
src/lib/config/Makefile
src/lib/config/tests/Makefile
src/lib/config/tests/testdata/Makefile
diff --git a/doc/Doxyfile b/doc/Doxyfile
index 71b0738..8be9098 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -574,7 +574,7 @@ INPUT = ../src/lib/exceptions ../src/lib/cc \
../src/lib/log/compiler ../src/lib/asiolink/ ../src/lib/nsas \
../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
../src/bin/sockcreator/ ../src/lib/util/ \
- ../src/lib/resolve ../src/lib/acl
+ ../src/lib/resolve ../src/lib/acl ../src/bin/dhcp6
# 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.xml b/doc/guide/bind10-guide.xml
index 00ffee6..34607e9 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -1257,21 +1257,65 @@ TODO
<para>
Incoming zones are transferred using the <command>b10-xfrin</command>
process which is started by <command>bind10</command>.
- When received, the zone is stored in the BIND 10
- data store, and its records can be served by
+ When received, the zone is stored in the corresponding BIND 10
+ data source, and its records can be served by
<command>b10-auth</command>.
In combination with <command>b10-zonemgr</command> (for
automated SOA checks), this allows the BIND 10 server to
provide <quote>secondary</quote> service.
</para>
+ <para>
+ The <command>b10-xfrin</command> process supports both AXFR and
+ IXFR. Due to some implementation limitations of the current
+ development release, however, it only tries AXFR by default,
+ and care should be taken to enable IXFR.
+ </para>
+
<note><simpara>
- The current development release of BIND 10 only supports
- AXFR. (IXFR is not supported.)
+ In the current development release of BIND 10, incoming zone
+ transfers are only available for SQLite3-based data sources,
+ that is, they don't work for an in-memory data source.
+ </simpara></note>
-<!-- TODO: sqlite3 data source only? -->
+ <para>
+ To enable IXFR, you need to
+ configure <command>b10-xfrin</command> with an explicit zone
+ configuration for the zone.
+ For example, to enable IXFR for a zone named "example.com"
+ (whose master address is assumed to be 2001:db8::53 here),
+ run the following at the <command>bindctl</command> prompt:
+
+ <screen>> <userinput>config add Xfrin/zones</userinput>
+> <userinput>config set Xfrin/zones[0]/name "<option>example.com</option>"</userinput>
+> <userinput>config set Xfrin/zones[0]/master_addr "<option>2001:db8::53</option>"</userinput>
+> <userinput>config commit</userinput></screen>
+
+ (We assume there has been no zone configuration before).
+ Note that you do NOT have to explicitly enable IXFR in the zone
+ configuration; once it's defined, IXFR is enabled by default.
+ This also means if you specify a zone configuration for some
+ other reason but don't want to use IXFR for that zone, you need
+ to disable it explicitly:
+
+ <screen>> <userinput>config set Xfrin/zones[0]/ixfr_disabled true</userinput></screen>
+ </para>
- </simpara></note>
+ <para>
+ One reason why IXFR is disabled by default in the current
+ release is because it does not support automatic fallback from IXFR to
+ AXFR when it encounters a primary server that doesn't support
+ outbound IXFR (and, not many existing implementations support
+ it). Another, related reason is that it does not use AXFR even
+ if it has no knowledge about the zone (like at the very first
+ time the secondary server is set up). IXFR requires the
+ "current version" of the zone, so obviously it doesn't work
+ in this situation and AXFR is the only workable choice.
+ The current release of <command>b10-xfrin</command> does not
+ make this selection automatically.
+ These features will be implemented in a near future
+ version, at which point we will enable IXFR by default.
+ </para>
<!-- TODO:
diff --git a/src/bin/auth/Makefile.am b/src/bin/auth/Makefile.am
index e3128b5..4d8ec83 100644
--- a/src/bin/auth/Makefile.am
+++ b/src/bin/auth/Makefile.am
@@ -50,6 +50,12 @@ b10_auth_SOURCES += command.cc command.h
b10_auth_SOURCES += common.h common.cc
b10_auth_SOURCES += statistics.cc statistics.h
b10_auth_SOURCES += main.cc
+# This is a temporary workaround for #1206, where the InMemoryClient has been
+# moved to an ldopened library. We could add that library to LDADD, but that
+# is nonportable. When #1207 is done this becomes moot anyway, and the
+# specific workaround is not needed anymore, so we can then remove this
+# line again.
+b10_auth_SOURCES += ${top_srcdir}/src/lib/datasrc/memory_datasrc.cc
nodist_b10_auth_SOURCES = auth_messages.h auth_messages.cc
EXTRA_DIST += auth_messages.mes
diff --git a/src/bin/auth/benchmarks/Makefile.am b/src/bin/auth/benchmarks/Makefile.am
index d51495b..53c019f 100644
--- a/src/bin/auth/benchmarks/Makefile.am
+++ b/src/bin/auth/benchmarks/Makefile.am
@@ -13,6 +13,12 @@ query_bench_SOURCES += ../auth_srv.h ../auth_srv.cc
query_bench_SOURCES += ../auth_config.h ../auth_config.cc
query_bench_SOURCES += ../statistics.h ../statistics.cc
query_bench_SOURCES += ../auth_log.h ../auth_log.cc
+# This is a temporary workaround for #1206, where the InMemoryClient has been
+# moved to an ldopened library. We could add that library to LDADD, but that
+# is nonportable. When #1207 is done this becomes moot anyway, and the
+# specific workaround is not needed anymore, so we can then remove this
+# line again.
+query_bench_SOURCES += ${top_srcdir}/src/lib/datasrc/memory_datasrc.cc
nodist_query_bench_SOURCES = ../auth_messages.h ../auth_messages.cc
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index 5cd2f5a..a4bd6fa 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -37,6 +37,13 @@ run_unittests_SOURCES += query_unittest.cc
run_unittests_SOURCES += change_user_unittest.cc
run_unittests_SOURCES += statistics_unittest.cc
run_unittests_SOURCES += run_unittests.cc
+# This is a temporary workaround for #1206, where the InMemoryClient has been
+# moved to an ldopened library. We could add that library to LDADD, but that
+# is nonportable. When #1207 is done this becomes moot anyway, and the
+# specific workaround is not needed anymore, so we can then remove this
+# line again.
+run_unittests_SOURCES += ${top_srcdir}/src/lib/datasrc/memory_datasrc.cc
+
nodist_run_unittests_SOURCES = ../auth_messages.h ../auth_messages.cc
@@ -60,4 +67,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am
index 824e8a8..805d6bb 100644
--- a/src/bin/dhcp6/Makefile.am
+++ b/src/bin/dhcp6/Makefile.am
@@ -19,7 +19,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
CLEANFILES = *.gcno *.gcda spec_config.h
man_MANS = b10-dhcp6.8
-EXTRA_DIST = $(man_MANS) dhcp6.spec
+EXTRA_DIST = $(man_MANS) dhcp6.spec interfaces.txt
#if ENABLE_MAN
#b10-dhcp6.8: b10-dhcp6.xml
@@ -31,8 +31,8 @@ spec_config.h: spec_config.h.pre
BUILT_SOURCES = spec_config.h
pkglibexec_PROGRAMS = b10-dhcp6
-b10_dhcp6_SOURCES = main.cc
-b10_dhcp6_SOURCES += dhcp6.h
+b10_dhcp6_SOURCES = main.cc iface_mgr.cc pkt6.cc dhcp6_srv.cc
+b10_dhcp6_SOURCES += iface_mgr.h pkt6.h dhcp6_srv.h dhcp6.h
b10_dhcp6_LDADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/util/libutil.la
@@ -49,5 +49,4 @@ b10_dhcp6_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_dhcp6dir = $(pkgdatadir)
-b10_dhcp6_DATA = dhcp6.spec
-
+b10_dhcp6_DATA = dhcp6.spec interfaces.txt
diff --git a/src/bin/dhcp6/b10-dhcp6.8 b/src/bin/dhcp6/b10-dhcp6.8
index 14a5621..a05bf71 100644
--- a/src/bin/dhcp6/b10-dhcp6.8
+++ b/src/bin/dhcp6/b10-dhcp6.8
@@ -21,8 +21,8 @@
.SH "NAME"
b10-dhcp6 \- DHCPv6 daemon in BIND10 architecture
.SH "SYNOPSIS"
-.HP \w'\fBb10\-dhcp6\fR\ 'u
-\fBb10\-dhcp6\fR [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR]
+.HP \w'\fBb10\-dhcp6
+\fBb10\-dhcp6\fR [\fB\-v\fR]
.SH "DESCRIPTION"
.PP
The
diff --git a/src/bin/dhcp6/dhcp6.h b/src/bin/dhcp6/dhcp6.h
index 322b06c..b5512f3 100644
--- a/src/bin/dhcp6/dhcp6.h
+++ b/src/bin/dhcp6/dhcp6.h
@@ -1,29 +1,19 @@
-/* dhcp6.h
-
- DHCPv6 Protocol structures... */
-
-/*
- * Copyright (c) 2006-2011 by Internet Systems Consortium, Inc. ("ISC")
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
- * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- *
- * Internet Systems Consortium, Inc.
- * 950 Charter Street
- * Redwood City, CA 94063
- * <info at isc.org>
- * https://www.isc.org/
- */
-
+// Copyright (C) 2006-2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 DHCP6_H
+#define DHCP6_H
/* DHCPv6 Option codes: */
@@ -136,8 +126,11 @@ extern const int dhcpv6_type_name_max;
/*
* DHCPv6 well-known multicast addressess, from section 5.1 of RFC 3315
*/
-#define All_DHCP_Relay_Agents_and_Servers "FF02::1:2"
-#define All_DHCP_Servers "FF05::1:3"
+#define ALL_DHCP_RELAY_AGENTS_AND_SERVERS "ff02::1:2"
+#define ALL_DHCP_SERVERS "ff05::1:3"
+
+#define DHCP6_CLIENT_PORT 546
+#define DHCP6_SERVER_PORT 547
/*
* DHCPv6 Retransmission Constants (RFC3315 section 5.5, RFC 5007)
@@ -171,29 +164,6 @@ extern const int dhcpv6_type_name_max;
#define LQ6_MAX_RT 10
#define LQ6_MAX_RC 5
-/*
- * Normal packet format, defined in section 6 of RFC 3315
- */
-struct dhcpv6_packet {
- unsigned char msg_type;
- unsigned char transaction_id[3];
- unsigned char options[FLEXIBLE_ARRAY_MEMBER];
-};
-
-/* Offset into DHCPV6 Reply packets where Options spaces commence. */
-#define REPLY_OPTIONS_INDEX 4
-
-/*
- * Relay packet format, defined in section 7 of RFC 3315
- */
-struct dhcpv6_relay_packet {
- unsigned char msg_type;
- unsigned char hop_count;
- unsigned char link_address[16];
- unsigned char peer_address[16];
- unsigned char options[FLEXIBLE_ARRAY_MEMBER];
-};
-
/* Leasequery query-types (RFC 5007) */
#define LQ6QT_BY_ADDRESS 1
@@ -211,3 +181,4 @@ struct dhcpv6_relay_packet {
#define IRT_DEFAULT 86400
#define IRT_MINIMUM 600
+#endif
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
new file mode 100644
index 0000000..4d9244f
--- /dev/null
+++ b/src/bin/dhcp6/dhcp6_srv.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 "dhcp6/pkt6.h"
+#include "dhcp6/iface_mgr.h"
+#include "dhcp6/dhcp6_srv.h"
+
+using namespace std;
+using namespace isc;
+
+Dhcpv6Srv::Dhcpv6Srv() {
+ cout << "Initialization" << endl;
+
+ // first call to instance() will create IfaceMgr (it's a singleton)
+ // it may throw something if things go wrong
+ IfaceMgr::instance();
+}
+
+Dhcpv6Srv::~Dhcpv6Srv() {
+ cout << "DHCPv6 Srv shutdown." << endl;
+}
+
+bool
+Dhcpv6Srv::run() {
+ while (true) {
+ Pkt6* pkt;
+
+ pkt = IfaceMgr::instance().receive();
+
+ if (pkt) {
+ cout << "Received " << pkt->data_len_ << " bytes, echoing back."
+ << endl;
+ IfaceMgr::instance().send(*pkt);
+ delete pkt;
+ }
+
+ // TODO add support for config session (see src/bin/auth/main.cc)
+ // so this daemon can be controlled from bob
+ sleep(1);
+
+ }
+
+ return (true);
+}
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
new file mode 100644
index 0000000..a02f5f6
--- /dev/null
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -0,0 +1,40 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 DHCPV6_SRV_H
+#define DHCPV6_SRV_H
+
+#include <iostream>
+
+namespace isc {
+ class Dhcpv6Srv {
+ private:
+ // defined private on purpose. We don't want to have more than
+ // one copy
+ Dhcpv6Srv(const Dhcpv6Srv& src);
+ Dhcpv6Srv& operator=(const Dhcpv6Srv& src);
+
+ public:
+ // default constructor
+ Dhcpv6Srv();
+ ~Dhcpv6Srv();
+
+ bool run();
+
+ protected:
+ bool shutdown;
+ };
+};
+
+#endif // DHCP6_SRV_H
diff --git a/src/bin/dhcp6/iface_mgr.cc b/src/bin/dhcp6/iface_mgr.cc
new file mode 100644
index 0000000..1e2551a
--- /dev/null
+++ b/src/bin/dhcp6/iface_mgr.cc
@@ -0,0 +1,581 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN 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 <fstream>
+#include <string.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "dhcp6/iface_mgr.h"
+#include "dhcp6/dhcp6.h"
+#include "exceptions/exceptions.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+
+namespace isc {
+
+// IfaceMgr is a singleton implementation
+IfaceMgr* IfaceMgr::instance_ = 0;
+
+void
+IfaceMgr::instanceCreate() {
+ if (instance_) {
+ // no need to do anything. Instance is already created.
+ // Who called it again anyway? Uh oh. Had to be us, as
+ // this is private method.
+ return;
+ }
+ instance_ = new IfaceMgr();
+}
+
+IfaceMgr&
+IfaceMgr::instance() {
+ if (instance_ == 0)
+ instanceCreate();
+ return (*instance_);
+}
+
+IfaceMgr::Iface::Iface(const std::string& name, int ifindex)
+ :name_(name), ifindex_(ifindex), mac_len_(0) {
+ memset(mac_, 0, 20);
+}
+
+std::string
+IfaceMgr::Iface::getFullName() const {
+ ostringstream tmp;
+ tmp << name_ << "/" << ifindex_;
+ return (tmp.str());
+}
+
+std::string
+IfaceMgr::Iface::getPlainMac() const {
+ ostringstream tmp;
+ for (int i=0; i<mac_len_; i++) {
+ tmp.fill('0');
+ tmp.width(2);
+ tmp << (hex) << (int) mac_[i];
+ if (i<mac_len_-1) {
+ tmp << ":";
+ }
+ }
+ return (tmp.str());
+}
+
+IfaceMgr::IfaceMgr() {
+
+ cout << "IfaceMgr initialization." << endl;
+
+ try {
+ // required for sending/receiving packets
+ // let's keep it in front, just in case someone
+ // wants to send anything during initialization
+ control_buf_len_ = CMSG_SPACE(sizeof(struct in6_pktinfo));
+ control_buf_ = new char[control_buf_len_];
+
+ detectIfaces();
+
+ if (!openSockets()) {
+ isc_throw(Unexpected, "Failed to open/bind sockets.");
+ }
+ } catch (const std::exception& ex) {
+ cout << "IfaceMgr creation failed:" << ex.what() << endl;
+
+ // TODO Uncomment this (or call LOG_FATAL) once
+ // interface detection is implemented. Otherwise
+ // it is not possible to run tests in a portable
+ // way (see detectIfaces() method).
+ // throw ex;
+ }
+}
+
+IfaceMgr::~IfaceMgr() {
+ if (control_buf_) {
+ delete [] control_buf_;
+ control_buf_ = 0;
+ control_buf_len_ = 0;
+ }
+}
+
+void
+IfaceMgr::detectIfaces() {
+ string ifaceName, linkLocal;
+
+ // TODO do the actual detection. Currently interface detection is faked
+ // by reading a text file.
+
+ cout << "Interface detection is not implemented yet. "
+ << "Reading interfaces.txt file instead." << endl;
+ cout << "Please use format: interface-name link-local-address" << endl;
+
+ try {
+ ifstream interfaces("interfaces.txt");
+
+ if (!interfaces.good()) {
+ cout << "Failed to read interfaces.txt file." << endl;
+ isc_throw(Unexpected, "Failed to read interfaces.txt");
+ }
+ interfaces >> ifaceName;
+ interfaces >> linkLocal;
+
+ cout << "Detected interface " << ifaceName << "/" << linkLocal << endl;
+
+ Iface iface(ifaceName, if_nametoindex( ifaceName.c_str() ) );
+ IOAddress addr(linkLocal);
+ iface.addrs_.push_back(addr);
+ ifaces_.push_back(iface);
+ interfaces.close();
+ } catch (const std::exception& ex) {
+ // TODO: deallocate whatever memory we used
+ // not that important, since this function is going to be
+ // thrown away as soon as we get proper interface detection
+ // implemented
+
+ // TODO Do LOG_FATAL here
+ std::cerr << "Interface detection failed." << std::endl;
+ throw ex;
+ }
+}
+
+bool
+IfaceMgr::openSockets() {
+ int sock;
+
+ for (IfaceLst::iterator iface=ifaces_.begin();
+ iface!=ifaces_.end();
+ ++iface) {
+
+ for (Addr6Lst::iterator addr=iface->addrs_.begin();
+ addr!=iface->addrs_.end();
+ ++addr) {
+
+ sock = openSocket(iface->name_, *addr,
+ DHCP6_SERVER_PORT);
+ if (sock<0) {
+ cout << "Failed to open unicast socket." << endl;
+ return (false);
+ }
+ sendsock_ = sock;
+
+ sock = openSocket(iface->name_,
+ IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
+ DHCP6_SERVER_PORT);
+ if (sock<0) {
+ cout << "Failed to open multicast socket." << endl;
+ close(sendsock_);
+ return (false);
+ }
+ recvsock_ = sock;
+ }
+ }
+
+ return (true);
+}
+
+void
+IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
+ for (IfaceLst::const_iterator iface=ifaces_.begin();
+ iface!=ifaces_.end();
+ ++iface) {
+ out << "Detected interface " << iface->getFullName() << endl;
+ out << " " << iface->addrs_.size() << " addr(s):" << endl;
+ for (Addr6Lst::const_iterator addr=iface->addrs_.begin();
+ addr != iface->addrs_.end();
+ ++addr) {
+ out << " " << addr->toText() << endl;
+ }
+ out << " mac: " << iface->getPlainMac() << endl;
+ }
+}
+
+IfaceMgr::Iface*
+IfaceMgr::getIface(int ifindex) {
+ for (IfaceLst::iterator iface=ifaces_.begin();
+ iface!=ifaces_.end();
+ ++iface) {
+ if (iface->ifindex_ == ifindex)
+ return (&(*iface));
+ }
+
+ return (NULL); // not found
+}
+
+IfaceMgr::Iface*
+IfaceMgr::getIface(const std::string& ifname) {
+ for (IfaceLst::iterator iface=ifaces_.begin();
+ iface!=ifaces_.end();
+ ++iface) {
+ if (iface->name_ == ifname)
+ return (&(*iface));
+ }
+
+ return (NULL); // not found
+}
+
+
+/**
+ * Opens UDP/IPv6 socket and binds it to specific address, interface and port.
+ *
+ * @param ifname name of the interface
+ * @param addr address to be bound.
+ * @param port UDP port.
+ * @param mcast Should multicast address also be bound?
+ *
+ * @return socket descriptor, if socket creation, binding and multicast
+ * group join were all successful. -1 otherwise.
+ */
+int
+IfaceMgr::openSocket(const std::string& ifname,
+ const IOAddress& addr,
+ int port) {
+ struct sockaddr_in6 addr6;
+
+ cout << "Creating socket on " << ifname << "/" << addr.toText()
+ << "/port=" << port << endl;
+
+ memset(&addr6, 0, sizeof(addr6));
+ addr6.sin6_family = AF_INET6;
+ addr6.sin6_port = htons(port);
+ addr6.sin6_scope_id = if_nametoindex(ifname.c_str());
+
+ memcpy(&addr6.sin6_addr,
+ addr.getAddress().to_v6().to_bytes().data(),
+ sizeof(addr6.sin6_addr));
+#ifdef HAVE_SA_LEN
+ addr6->sin6_len = sizeof(addr6);
+#endif
+
+ // TODO: use sockcreator once it becomes available
+
+ // make a socket
+ int sock = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ cout << "Failed to create UDP6 socket." << endl;
+ return (-1);
+ }
+
+ /* Set the REUSEADDR option so that we don't fail to start if
+ we're being restarted. */
+ int flag = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+ (char *)&flag, sizeof(flag)) < 0) {
+ cout << "Can't set SO_REUSEADDR option on dhcpv6 socket." << endl;
+ close(sock);
+ return (-1);
+ }
+
+ if (bind(sock, (struct sockaddr *)&addr6, sizeof(addr6)) < 0) {
+ cout << "Failed to bind socket " << sock << " to " << addr.toText()
+ << "/port=" << port << endl;
+ close(sock);
+ return (-1);
+ }
+#ifdef IPV6_RECVPKTINFO
+ /* RFC3542 - a new way */
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ &flag, sizeof(flag)) != 0) {
+ cout << "setsockopt: IPV6_RECVPKTINFO failed." << endl;
+ close(sock);
+ return (-1);
+ }
+#else
+ /* RFC2292 - an old way */
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO,
+ &flag, sizeof(flag)) != 0) {
+ cout << "setsockopt: IPV6_PKTINFO: failed." << endl;
+ close(sock);
+ return (-1);
+ }
+#endif
+
+ // multicast stuff
+
+ if (addr.getAddress().to_v6().is_multicast()) {
+ // both mcast (ALL_DHCP_RELAY_AGENTS_AND_SERVERS and ALL_DHCP_SERVERS)
+ // are link and site-scoped, so there is no sense to join those groups
+ // with global addresses.
+
+ if ( !joinMcast( sock, ifname,
+ string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS) ) ) {
+ close(sock);
+ return (-1);
+ }
+ }
+
+ cout << "Created socket " << sock << " on " << ifname << "/" <<
+ addr.toText() << "/port=" << port << endl;
+
+ return (sock);
+}
+
+/**
+ * joins multicast group
+ *
+ * @param sock socket file descriptor
+ * @param ifname name of the interface (DHCPv6 uses link-scoped mc groups)
+ * @param mcast multicast address to join (string)
+ *
+ * @return true if joined successfully, false otherwise
+ */
+bool
+IfaceMgr::joinMcast(int sock, const std::string& ifname,
+const std::string & mcast) {
+
+ struct ipv6_mreq mreq;
+
+ if (inet_pton(AF_INET6, mcast.c_str(),
+ &mreq.ipv6mr_multiaddr) <= 0) {
+ cout << "Failed to convert " << ifname
+ << " to IPv6 multicast address." << endl;
+ return (false);
+ }
+
+ mreq.ipv6mr_interface = if_nametoindex(ifname.c_str());
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq, sizeof(mreq)) < 0) {
+ cout << "Failed to join " << mcast << " multicast group." << endl;
+ return (false);
+ }
+
+ cout << "Joined multicast " << mcast << " group." << endl;
+
+ return (true);
+}
+
+/**
+ * Sends UDP packet over IPv6.
+ *
+ * All parameters for actual transmission are specified in
+ * Pkt6 structure itself. That includes destination address,
+ * src/dst port and interface over which data will be sent.
+ *
+ * @param pkt A packet object that is going to be sent.
+ *
+ * @return True, if transmission was successful. False otherwise.
+ */
+bool
+IfaceMgr::send(Pkt6 &pkt) {
+ struct msghdr m;
+ struct iovec v;
+ int result;
+ struct in6_pktinfo *pktinfo;
+ struct cmsghdr *cmsg;
+ memset(control_buf_, 0, control_buf_len_);
+
+ /*
+ * Initialize our message header structure.
+ */
+ memset(&m, 0, sizeof(m));
+
+ /*
+ * Set the target address we're sending to.
+ */
+ sockaddr_in6 to;
+ memset(&to, 0, sizeof(to));
+ to.sin6_family = AF_INET6;
+ to.sin6_port = htons(pkt.remote_port_);
+ memcpy(&to.sin6_addr,
+ pkt.remote_addr_.getAddress().to_v6().to_bytes().data(),
+ 16);
+ to.sin6_scope_id = pkt.ifindex_;
+
+ m.msg_name = &to;
+ m.msg_namelen = sizeof(to);
+
+ /*
+ * Set the data buffer we're sending. (Using this wacky
+ * "scatter-gather" stuff... we only have a single chunk
+ * of data to send, so we declare a single vector entry.)
+ */
+ v.iov_base = (char *) &pkt.data_[0];
+ v.iov_len = pkt.data_len_;
+ m.msg_iov = &v;
+ m.msg_iovlen = 1;
+
+ /*
+ * Setting the interface is a bit more involved.
+ *
+ * We have to create a "control message", and set that to
+ * define the IPv6 packet information. We could set the
+ * source address if we wanted, but we can safely let the
+ * kernel decide what that should be.
+ */
+ m.msg_control = control_buf_;
+ m.msg_controllen = control_buf_len_;
+ cmsg = CMSG_FIRSTHDR(&m);
+ cmsg->cmsg_level = IPPROTO_IPV6;
+ cmsg->cmsg_type = IPV6_PKTINFO;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo));
+ pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+ memset(pktinfo, 0, sizeof(*pktinfo));
+ pktinfo->ipi6_ifindex = pkt.ifindex_;
+ m.msg_controllen = cmsg->cmsg_len;
+
+ result = sendmsg(sendsock_, &m, 0);
+ if (result < 0) {
+ cout << "Send packet failed." << endl;
+ }
+ cout << "Sent " << result << " bytes." << endl;
+
+ cout << "Sent " << pkt.data_len_ << " bytes over "
+ << pkt.iface_ << "/" << pkt.ifindex_ << " interface: "
+ << " dst=" << pkt.remote_addr_.toText()
+ << ", src=" << pkt.local_addr_.toText()
+ << endl;
+
+ return (result);
+}
+
+
+/**
+ * Attempts to receive UDP/IPv6 packet over open sockets.
+ *
+ * TODO Start using select() and add timeout to be able
+ * to not wait infinitely, but rather do something useful
+ * (e.g. remove expired leases)
+ *
+ * @return Object prepresenting received packet.
+ */
+Pkt6*
+IfaceMgr::receive() {
+ struct msghdr m;
+ struct iovec v;
+ int result;
+ struct cmsghdr* cmsg;
+ struct in6_pktinfo* pktinfo;
+ struct sockaddr_in6 from;
+ struct in6_addr to_addr;
+ Pkt6* pkt;
+ char addr_str[INET6_ADDRSTRLEN];
+
+ try {
+ // RFC3315 states that server responses may be
+ // fragmented if they are over MTU. There is no
+ // text whether client's packets may be larger
+ // than 1500. Nevertheless to be on the safe side
+ // we use larger buffer. This buffer limit is checked
+ // during reception (see iov_len below), so we are
+ // safe
+ pkt = new Pkt6(65536);
+ } catch (const std::exception& ex) {
+ cout << "Failed to create new packet." << endl;
+ return (0);
+ }
+
+ memset(control_buf_, 0, control_buf_len_);
+
+ memset(&from, 0, sizeof(from));
+ memset(&to_addr, 0, sizeof(to_addr));
+
+ /*
+ * Initialize our message header structure.
+ */
+ memset(&m, 0, sizeof(m));
+
+ /*
+ * Point so we can get the from address.
+ */
+ m.msg_name = &from;
+ m.msg_namelen = sizeof(from);
+
+ /*
+ * Set the data buffer we're receiving. (Using this wacky
+ * "scatter-gather" stuff... but we that doesn't really make
+ * sense for us, so we use a single vector entry.)
+ */
+ v.iov_base = (void*)&pkt->data_[0];
+ v.iov_len = pkt->data_len_;
+ m.msg_iov = &v;
+ m.msg_iovlen = 1;
+
+ /*
+ * Getting the interface is a bit more involved.
+ *
+ * We set up some space for a "control message". We have
+ * previously asked the kernel to give us packet
+ * information (when we initialized the interface), so we
+ * should get the destination address from that.
+ */
+ m.msg_control = control_buf_;
+ m.msg_controllen = control_buf_len_;
+
+ result = recvmsg(recvsock_, &m, 0);
+
+ if (result >= 0) {
+ /*
+ * If we did read successfully, then we need to loop
+ * through the control messages we received and
+ * find the one with our destination address.
+ *
+ * We also keep a flag to see if we found it. If we
+ * didn't, then we consider this to be an error.
+ */
+ int found_pktinfo = 0;
+ cmsg = CMSG_FIRSTHDR(&m);
+ while (cmsg != NULL) {
+ if ((cmsg->cmsg_level == IPPROTO_IPV6) &&
+ (cmsg->cmsg_type == IPV6_PKTINFO)) {
+ pktinfo = (struct in6_pktinfo*)CMSG_DATA(cmsg);
+ to_addr = pktinfo->ipi6_addr;
+ pkt->ifindex_ = pktinfo->ipi6_ifindex;
+ found_pktinfo = 1;
+ }
+ cmsg = CMSG_NXTHDR(&m, cmsg);
+ }
+ if (!found_pktinfo) {
+ cout << "Unable to find pktinfo" << endl;
+ delete pkt;
+ return (0);
+ }
+ } else {
+ cout << "Failed to receive data." << endl;
+ delete pkt;
+ return (0);
+ }
+
+ // That's ugly.
+ // TODO add IOAddress constructor that will take struct in6_addr*
+ inet_ntop(AF_INET6, &to_addr, addr_str,INET6_ADDRSTRLEN);
+ pkt->local_addr_ = IOAddress(string(addr_str));
+
+ inet_ntop(AF_INET6, &from.sin6_addr, addr_str, INET6_ADDRSTRLEN);
+ pkt->remote_addr_ = IOAddress(string(addr_str));
+
+ pkt->remote_port_ = ntohs(from.sin6_port);
+
+ Iface* received = getIface(pkt->ifindex_);
+ if (received) {
+ pkt->iface_ = received->name_;
+ } else {
+ cout << "Received packet over unknown interface (ifindex="
+ << pkt->ifindex_ << ")." << endl;
+ delete pkt;
+ return (0);
+ }
+
+ pkt->data_len_ = result;
+
+ // TODO Move this to LOG_DEBUG
+ cout << "Received " << pkt->data_len_ << " bytes over "
+ << pkt->iface_ << "/" << pkt->ifindex_ << " interface: "
+ << " src=" << pkt->remote_addr_.toText()
+ << ", dst=" << pkt->local_addr_.toText()
+ << endl;
+
+ return (pkt);
+}
+
+}
diff --git a/src/bin/dhcp6/iface_mgr.h b/src/bin/dhcp6/iface_mgr.h
new file mode 100644
index 0000000..39061da
--- /dev/null
+++ b/src/bin/dhcp6/iface_mgr.h
@@ -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.
+
+#ifndef IFACE_MGR_H
+#define IFACE_MGR_H
+
+#include <list>
+#include "io_address.h"
+#include "dhcp6/pkt6.h"
+
+namespace isc {
+
+ /**
+ * IfaceMgr is an interface manager class that detects available network
+ * interfaces, configured addresses, link-local addresses, and provides
+ * API for using sockets.
+ *
+ */
+ class IfaceMgr {
+ public:
+ typedef std::list<isc::asiolink::IOAddress> Addr6Lst;
+ struct Iface { // TODO: could be a class as well
+ std::string name_; // network interface name
+ int ifindex_; // interface index (a value that uniquely indentifies
+ // an interface
+ Addr6Lst addrs_;
+ char mac_[20]; // Infiniband used 20 bytes indentifiers
+ int mac_len_;
+
+ Iface(const std::string& name, int ifindex);
+ std::string getFullName() const;
+ std::string getPlainMac() const;
+
+ int sendsock_; // socket used to sending data
+ int recvsock_; // socket used for receiving data
+
+ // next field is not needed, let's keep it in cointainers
+ };
+
+ // TODO performance improvement: we may change this into
+ // 2 maps (ifindex-indexed and name-indexed) and
+ // also hide it (make it public make tests easier for now)
+ typedef std::list<Iface> IfaceLst;
+
+ static IfaceMgr& instance();
+
+ Iface * getIface(int ifindex);
+ Iface * getIface(const std::string& ifname);
+
+ void printIfaces(std::ostream& out = std::cout);
+
+ bool send(Pkt6& pkt);
+ Pkt6* receive();
+
+ // don't use private, we need derived classes in tests
+ protected:
+ IfaceMgr(); // don't create IfaceMgr directly, use instance() method
+ ~IfaceMgr();
+
+ void detectIfaces();
+
+ int openSocket(const std::string& ifname,
+ const isc::asiolink::IOAddress& addr,
+ int port);
+
+ // TODO: having 2 maps (ifindex->iface and ifname->iface would)
+ // probably be better for performance reasons
+ IfaceLst ifaces_;
+
+ static IfaceMgr * instance_;
+
+ // TODO: Also keep this interface on Iface once interface detection
+ // is implemented. We may need it e.g. to close all sockets on
+ // specific interface
+ int recvsock_; // TODO: should be fd_set eventually, but we have only
+ int sendsock_; // 2 sockets for now. Will do for until next release
+ // we can't use the same socket, as receiving socket
+ // is bound to multicast address. And we all know what happens
+ // to people who try to use multicast as source address.
+
+ char * control_buf_;
+ int control_buf_len_;
+
+ private:
+ bool openSockets();
+ static void instanceCreate();
+ bool joinMcast(int sock, const std::string& ifname,
+ const std::string& mcast);
+ };
+};
+
+#endif
diff --git a/src/bin/dhcp6/interfaces.txt b/src/bin/dhcp6/interfaces.txt
new file mode 100644
index 0000000..6a64309
--- /dev/null
+++ b/src/bin/dhcp6/interfaces.txt
@@ -0,0 +1,10 @@
+eth0 fe80::21e:8cff:fe9b:7349
+
+#
+# only first line is read.
+# please use following format:
+# interface-name link-local-ipv6-address
+#
+# This file will become obsolete once proper interface detection
+# is implemented.
+#
diff --git a/src/bin/dhcp6/main.cc b/src/bin/dhcp6/main.cc
index 75af3d9..95d2261 100644
--- a/src/bin/dhcp6/main.cc
+++ b/src/bin/dhcp6/main.cc
@@ -33,7 +33,7 @@
#include <log/dummylog.h>
#include <dhcp6/spec_config.h>
-
+#include "dhcp6/dhcp6_srv.h"
using namespace std;
using namespace isc::util;
@@ -42,15 +42,16 @@ using namespace isc::cc;
using namespace isc::config;
using namespace isc::util;
+using namespace isc;
+
namespace {
bool verbose_mode = false;
void
usage() {
- cerr << "Usage: b10-dhcp6 [-u user] [-v]"
+ cerr << "Usage: b10-dhcp6 [-v]"
<< endl;
- cerr << "\t-u: change process UID to the specified user" << endl;
cerr << "\t-v: verbose output" << endl;
exit(1);
}
@@ -59,40 +60,32 @@ usage() {
int
main(int argc, char* argv[]) {
int ch;
- const char* uid = NULL;
- bool cache = true;
- while ((ch = getopt(argc, argv, ":nu:v")) != -1) {
+ while ((ch = getopt(argc, argv, ":v")) != -1) {
switch (ch) {
- case 'n':
- cache = false;
- break;
- case 'u':
- uid = optarg;
- break;
case 'v':
verbose_mode = true;
isc::log::denabled = true;
break;
- case '?':
+ case ':':
default:
usage();
}
}
+ cout << "My pid=" << getpid() << endl;
+
if (argc - optind > 0) {
usage();
}
int ret = 0;
- // XXX: we should eventually pass io_service here.
+ // TODO remainder of auth to dhcp6 code copy. We need to enable this in
+ // dhcp6 eventually
#if 0
Session* cc_session = NULL;
- Session* xfrin_session = NULL;
Session* statistics_session = NULL;
- bool xfrin_session_established = false; // XXX (see Trac #287)
- bool statistics_session_established = false; // XXX (see Trac #287)
ModuleCCSession* config_session = NULL;
#endif
try {
@@ -108,15 +101,14 @@ main(int argc, char* argv[]) {
// auth_server->setVerbose(verbose_mode);
cout << "[b10-dhcp6] Initiating DHCPv6 operation." << endl;
+ Dhcpv6Srv* srv = new Dhcpv6Srv();
+
+ srv->run();
+
} catch (const std::exception& ex) {
cerr << "[b10-dhcp6] Server failed: " << ex.what() << endl;
ret = 1;
}
- while (true) {
- sleep(10);
- cout << "[b10-dhcp6] I'm alive." << endl;
- }
-
return (ret);
}
diff --git a/src/bin/dhcp6/pkt6.cc b/src/bin/dhcp6/pkt6.cc
new file mode 100644
index 0000000..5dcab86
--- /dev/null
+++ b/src/bin/dhcp6/pkt6.cc
@@ -0,0 +1,46 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+
+#include "dhcp6/dhcp6.h"
+#include "dhcp6/pkt6.h"
+#include <iostream>
+
+namespace isc {
+
+///
+/// constructor
+///
+/// \param dataLen - length of the data to be allocated
+///
+Pkt6::Pkt6(int dataLen)
+ :local_addr_("::"),
+ remote_addr_("::") {
+ try {
+ data_ = boost::shared_array<char>(new char[dataLen]);
+ data_len_ = dataLen;
+ } catch (const std::exception& ex) {
+ // TODO move to LOG_FATAL()
+ // let's continue with empty pkt for now
+ std::cout << "Failed to allocate " << dataLen << " bytes."
+ << std::endl;
+ data_len_ = 0;
+ }
+}
+
+Pkt6::~Pkt6() {
+ // no need to delete anything shared_ptr will take care of data_
+}
+
+};
diff --git a/src/bin/dhcp6/pkt6.h b/src/bin/dhcp6/pkt6.h
new file mode 100644
index 0000000..9a14d92
--- /dev/null
+++ b/src/bin/dhcp6/pkt6.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 PKT6_H
+#define PKT6_H
+
+#include <iostream>
+#include <boost/shared_array.hpp>
+#include "io_address.h"
+
+namespace isc {
+
+ class Pkt6 {
+ public:
+ Pkt6(int len);
+ ~Pkt6();
+
+ // XXX: probably need getter/setter wrappers
+ // and hide fields as protected
+ // buffer that holds memory. It is shared_array as options may
+ // share pointer to this buffer
+ boost::shared_array<char> data_;
+
+ // length of the data
+ int data_len_;
+
+ // local address (destination if receiving packet, source if sending packet)
+ isc::asiolink::IOAddress local_addr_;
+
+ // remote address (source if receiving packet, destination if sending packet)
+ isc::asiolink::IOAddress remote_addr_;
+
+ // name of the network interface the packet was received/to be sent over
+ std::string iface_;
+
+ // interface index (each network interface has assigned unique ifindex
+ // it is functional equvalent of name, but sometimes more useful, e.g.
+ // when using crazy systems that allow spaces in interface names (Windows)
+ int ifindex_;
+
+ // local TDP or UDP port
+ int local_port_;
+
+ // remote TCP or UDP port
+ int remote_port_;
+
+ // XXX: add *a lot* here
+ };
+}
+
+#endif
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index 231a3d9..ae9d8e3 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -20,3 +20,48 @@ check-local:
BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
+
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/asiolink
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/tests\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+CLEANFILES = $(builddir)/interfaces.txt
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+TESTS =
+if HAVE_GTEST
+
+TESTS += dhcp6_unittests
+
+dhcp6_unittests_SOURCES = ../pkt6.h ../pkt6.cc
+dhcp6_unittests_SOURCES += ../iface_mgr.h ../iface_mgr.cc
+dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
+dhcp6_unittests_SOURCES += dhcp6_unittests.cc
+dhcp6_unittests_SOURCES += pkt6_unittest.cc
+dhcp6_unittests_SOURCES += iface_mgr_unittest.cc
+dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
+
+dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+dhcp6_unittests_LDADD = $(GTEST_LDADD)
+dhcp6_unittests_LDADD += $(SQLITE_LIBS)
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
new file mode 100644
index 0000000..96c767e
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -0,0 +1,53 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+
+#include "dhcp6/dhcp6_srv.h"
+
+using namespace std;
+using namespace isc;
+
+namespace {
+class Dhcpv6SrvTest : public ::testing::Test {
+public:
+ Dhcpv6SrvTest() {
+ }
+};
+
+TEST_F(Dhcpv6SrvTest, basic) {
+ // there's almost no code now. What's there provides echo capability
+ // that is just a proof of concept and will be removed soon
+ // No need to thoroughly test it
+
+ // srv has stubbed interface detection. It will read
+ // interfaces.txt instead. It will pretend to have detected
+ // fe80::1234 link-local address on eth0 interface. Obviously
+
+ // an attempt to bind this socket will fail.
+ EXPECT_NO_THROW( {
+ Dhcpv6Srv * srv = new Dhcpv6Srv();
+
+ delete srv;
+ });
+
+}
+
+}
diff --git a/src/bin/dhcp6/tests/dhcp6_unittests.cc b/src/bin/dhcp6/tests/dhcp6_unittests.cc
new file mode 100644
index 0000000..360fb71
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_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 <stdio.h>
+#include <gtest/gtest.h>
+#include <log/logger_support.h>
+
+int
+main(int argc, char* argv[]) {
+
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return result;
+}
diff --git a/src/bin/dhcp6/tests/iface_mgr_unittest.cc b/src/bin/dhcp6/tests/iface_mgr_unittest.cc
new file mode 100644
index 0000000..c9a9d72
--- /dev/null
+++ b/src/bin/dhcp6/tests/iface_mgr_unittest.cc
@@ -0,0 +1,263 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include "io_address.h"
+#include "dhcp6/pkt6.h"
+#include "dhcp6/iface_mgr.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+
+namespace {
+const char* const INTERFACE_FILE = TEST_DATA_BUILDDIR "/interfaces.txt";
+
+class NakedIfaceMgr: public IfaceMgr {
+ // "naked" Interface Manager, exposes internal fields
+public:
+ NakedIfaceMgr() { }
+ IfaceLst & getIfacesLst() { return ifaces_; }
+ void setSendSock(int sock) { sendsock_ = sock; }
+ void setRecvSock(int sock) { recvsock_ = sock; }
+
+ int openSocket(const std::string& ifname,
+ const isc::asiolink::IOAddress& addr,
+ int port) {
+ return IfaceMgr::openSocket(ifname, addr, port);
+ }
+
+};
+
+// dummy class for now, but this will be expanded when needed
+class IfaceMgrTest : public ::testing::Test {
+public:
+ IfaceMgrTest() {
+ }
+};
+
+TEST_F(IfaceMgrTest, basic) {
+ // checks that IfaceManager can be instantiated
+
+ IfaceMgr & ifacemgr = IfaceMgr::instance();
+ ASSERT_TRUE(&ifacemgr != 0);
+}
+
+TEST_F(IfaceMgrTest, ifaceClass) {
+ // basic tests for Iface inner class
+
+ IfaceMgr::Iface * iface = new IfaceMgr::Iface("eth5", 7);
+
+ EXPECT_STREQ("eth5/7", iface->getFullName().c_str());
+
+ delete iface;
+
+}
+
+// TODO: Implement getPlainMac() test as soon as interface detection is implemented.
+TEST_F(IfaceMgrTest, getIface) {
+
+ cout << "Interface checks. Please ignore socket binding errors." << endl;
+ NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+ // interface name, ifindex
+ IfaceMgr::Iface iface1("lo", 1);
+ IfaceMgr::Iface iface2("eth5", 2);
+ IfaceMgr::Iface iface3("en3", 5);
+ IfaceMgr::Iface iface4("e1000g0", 3);
+
+ ifacemgr->getIfacesLst().push_back(iface1);
+ ifacemgr->getIfacesLst().push_back(iface2);
+ ifacemgr->getIfacesLst().push_back(iface3);
+ ifacemgr->getIfacesLst().push_back(iface4);
+
+ // check that interface can be retrieved by ifindex
+ IfaceMgr::Iface * tmp = ifacemgr->getIface(5);
+ // ASSERT_NE(NULL, tmp); is not supported. hmmmm.
+ ASSERT_TRUE( tmp != NULL );
+
+ EXPECT_STREQ( "en3", tmp->name_.c_str() );
+ EXPECT_EQ(5, tmp->ifindex_);
+
+ // check that interface can be retrieved by name
+ tmp = ifacemgr->getIface("lo");
+ ASSERT_TRUE( tmp != NULL );
+
+ EXPECT_STREQ( "lo", tmp->name_.c_str() );
+ EXPECT_EQ(1, tmp->ifindex_);
+
+ // check that non-existing interfaces are not returned
+ EXPECT_EQ(static_cast<void*>(NULL), ifacemgr->getIface("wifi0") );
+
+ delete ifacemgr;
+}
+
+TEST_F(IfaceMgrTest, detectIfaces) {
+
+ // test detects that interfaces can be detected
+ // there is no code for that now, but interfaces are
+ // read from file
+ fstream fakeifaces(INTERFACE_FILE, ios::out|ios::trunc);
+ fakeifaces << "eth0 fe80::1234";
+ fakeifaces.close();
+
+ // this is not usable on systems that don't have eth0
+ // interfaces. Nevertheless, this fake interface should
+ // be on list, but if_nametoindex() will fail.
+
+ NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+ ASSERT_TRUE( ifacemgr->getIface("eth0") != NULL );
+
+ IfaceMgr::Iface * eth0 = ifacemgr->getIface("eth0");
+
+ // there should be one address
+ EXPECT_EQ(1, eth0->addrs_.size());
+
+ IOAddress * addr = &(*eth0->addrs_.begin());
+ ASSERT_TRUE( addr != NULL );
+
+ EXPECT_STREQ( "fe80::1234", addr->toText().c_str() );
+
+ delete ifacemgr;
+}
+
+// TODO: disabled due to other naming on various systems
+// (lo in Linux, lo0 in BSD systems)
+// Fix for this is available on 1186 branch, will reenable
+// this test once 1186 is merged
+TEST_F(IfaceMgrTest, DISABLED_sockets) {
+ // testing socket operation in a portable way is tricky
+ // without interface detection implemented
+
+ NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+ IOAddress loAddr("::1");
+
+ // bind multicast socket to port 10547
+ int socket1 = ifacemgr->openSocket("lo", loAddr, 10547);
+ EXPECT_GT(socket1, 0); // socket > 0
+
+ // bind unicast socket to port 10548
+ int socket2 = ifacemgr->openSocket("lo", loAddr, 10548);
+ EXPECT_GT(socket2, 0);
+
+ // expect success. This address/port is already bound, but
+ // we are using SO_REUSEADDR, so we can bind it twice
+ int socket3 = ifacemgr->openSocket("lo", loAddr, 10547);
+ EXPECT_GT(socket3, 0); // socket > 0
+
+ // we now have 3 sockets open at the same time. Looks good.
+
+ close(socket1);
+ close(socket2);
+ close(socket3);
+
+ delete ifacemgr;
+}
+
+// TODO: disabled due to other naming on various systems
+// (lo in Linux, lo0 in BSD systems)
+TEST_F(IfaceMgrTest, DISABLED_socketsMcast) {
+ // testing socket operation in a portable way is tricky
+ // without interface detection implemented
+
+ NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+ IOAddress loAddr("::1");
+ IOAddress mcastAddr("ff02::1:2");
+
+ // bind multicast socket to port 10547
+ int socket1 = ifacemgr->openSocket("lo", mcastAddr, 10547);
+ EXPECT_GT(socket1, 0); // socket > 0
+
+ // expect success. This address/port is already bound, but
+ // we are using SO_REUSEADDR, so we can bind it twice
+ int socket2 = ifacemgr->openSocket("lo", mcastAddr, 10547);
+ EXPECT_GT(socket2, 0);
+
+ // there's no good way to test negative case here.
+ // we would need non-multicast interface. We will be able
+ // to iterate thru available interfaces and check if there
+ // are interfaces without multicast-capable flag.
+
+ close(socket1);
+ close(socket2);
+
+ delete ifacemgr;
+}
+
+// TODO: disabled due to other naming on various systems
+// (lo in Linux, lo0 in BSD systems)
+// Fix for this is available on 1186 branch, will reenable
+// this test once 1186 is merged
+TEST_F(IfaceMgrTest, DISABLED_sendReceive) {
+ // testing socket operation in a portable way is tricky
+ // without interface detection implemented
+
+ fstream fakeifaces(INTERFACE_FILE, ios::out|ios::trunc);
+ fakeifaces << "lo ::1";
+ fakeifaces.close();
+
+ NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+ // let's assume that every supported OS have lo interface
+ IOAddress loAddr("::1");
+ int socket1 = ifacemgr->openSocket("lo", loAddr, 10547);
+ int socket2 = ifacemgr->openSocket("lo", loAddr, 10546);
+
+ ifacemgr->setSendSock(socket2);
+ ifacemgr->setRecvSock(socket1);
+
+ Pkt6 sendPkt(128);
+
+ // prepare dummy payload
+ for (int i=0;i<128; i++) {
+ sendPkt.data_[i] = i;
+ }
+
+ sendPkt.remote_port_ = 10547;
+ sendPkt.remote_addr_ = IOAddress("::1");
+ sendPkt.ifindex_ = 1;
+ sendPkt.iface_ = "lo";
+
+ Pkt6 * rcvPkt;
+
+ EXPECT_EQ(true, ifacemgr->send(sendPkt));
+
+ rcvPkt = ifacemgr->receive();
+
+ ASSERT_TRUE( rcvPkt != NULL ); // received our own packet
+
+ // let's check that we received what was sent
+ EXPECT_EQ(sendPkt.data_len_, rcvPkt->data_len_);
+ EXPECT_EQ(0, memcmp(&sendPkt.data_[0], &rcvPkt->data_[0],
+ rcvPkt->data_len_) );
+
+ EXPECT_EQ(sendPkt.remote_addr_.toText(), rcvPkt->remote_addr_.toText());
+ EXPECT_EQ(rcvPkt->remote_port_, 10546);
+
+ delete rcvPkt;
+
+ delete ifacemgr;
+}
+
+}
diff --git a/src/bin/dhcp6/tests/pkt6_unittest.cc b/src/bin/dhcp6/tests/pkt6_unittest.cc
new file mode 100644
index 0000000..5054c45
--- /dev/null
+++ b/src/bin/dhcp6/tests/pkt6_unittest.cc
@@ -0,0 +1,44 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+
+#include "dhcp6/pkt6.h"
+
+using namespace std;
+using namespace isc;
+
+namespace {
+// empty class for now, but may be extended once Addr6 becomes bigger
+class Pkt6Test : public ::testing::Test {
+public:
+ Pkt6Test() {
+ }
+};
+
+TEST_F(Pkt6Test, constructor) {
+ Pkt6 * pkt1 = new Pkt6(17);
+
+ ASSERT_EQ(pkt1->data_len_, 17);
+
+ delete pkt1;
+}
+
+}
diff --git a/src/bin/resolver/tests/Makefile.am b/src/bin/resolver/tests/Makefile.am
index 97a2ba6..14d92fe 100644
--- a/src/bin/resolver/tests/Makefile.am
+++ b/src/bin/resolver/tests/Makefile.am
@@ -60,6 +60,4 @@ run_unittests_CXXFLAGS += -Wno-unused-parameter
endif
endif
-
-
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
diff --git a/src/bin/sockcreator/tests/Makefile.am b/src/bin/sockcreator/tests/Makefile.am
index 223e761..b3ca344 100644
--- a/src/bin/sockcreator/tests/Makefile.am
+++ b/src/bin/sockcreator/tests/Makefile.am
@@ -21,4 +21,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.
run_unittests_LDADD += $(top_builddir)/src/lib/util/io/libutil_io.la
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
diff --git a/src/bin/xfrin/b10-xfrin.xml b/src/bin/xfrin/b10-xfrin.xml
index d45e15f..824d5fa 100644
--- a/src/bin/xfrin/b10-xfrin.xml
+++ b/src/bin/xfrin/b10-xfrin.xml
@@ -59,7 +59,7 @@
<citerefentry><refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum></citerefentry>
boss process.
When triggered it can request and receive a zone transfer and store
- the zone in a BIND 10 zone data store.
+ the zone in a BIND 10 zone data source.
</para>
<!-- TODO:
@@ -68,9 +68,13 @@ The logic for handling transfer triggers or zone management is handled
in separate zonemgr process.
-->
- <note><simpara>
- This prototype release only supports AXFR. IXFR is not implemented.
- </simpara></note>
+ <para>
+ The <command>b10-xfrin</command> daemon supports both AXFR and
+ IXFR. Due to some implementation limitations of the current
+ development release, however, it only tries AXFR by default,
+ and care should be taken to enable IXFR.
+ See the BIND 10 Guide for more details.
+ </para>
<para>
This daemon communicates with BIND 10 over a
@@ -105,7 +109,8 @@ in separate zonemgr process.
<varname>name</varname> (the zone name),
<varname>class</varname> (defaults to <quote>IN</quote>),
<varname>master_addr</varname> (the zone master to transfer from),
- <varname>master_port</varname> (defaults to 53), and
+ <varname>master_port</varname> (defaults to 53),
+ <varname>ixfr_disabled</varname> (defaults to false), and
<varname>tsig_key</varname> (optional TSIG key to use).
The <varname>tsig_key</varname> is specified using a full string
colon-delimited name:key:algorithm representation (e.g.
diff --git a/src/bin/xfrin/tests/Makefile.am b/src/bin/xfrin/tests/Makefile.am
index 3d56009..8f4fa91 100644
--- a/src/bin/xfrin/tests/Makefile.am
+++ b/src/bin/xfrin/tests/Makefile.am
@@ -1,3 +1,5 @@
+SUBDIRS = testdata .
+
PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
PYTESTS = xfrin_test.py
EXTRA_DIST = $(PYTESTS)
@@ -7,6 +9,9 @@ EXTRA_DIST = $(PYTESTS)
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+else
+# sunstudio needs the ds path even if not all paths are necessary
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs
endif
# test using command-line arguments, so use check-local target instead of TESTS
@@ -20,5 +25,7 @@ endif
echo Running test: $$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/bin/xfrin:$(COMMON_PYTHON_PATH) \
+ TESTDATASRCDIR=$(abs_top_srcdir)/src/bin/xfrin/tests/testdata/ \
+ TESTDATAOBJDIR=$(abs_top_builddir)/src/bin/xfrin/tests/testdata/ \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/xfrin/tests/testdata/Makefile.am b/src/bin/xfrin/tests/testdata/Makefile.am
new file mode 100644
index 0000000..5e325cb
--- /dev/null
+++ b/src/bin/xfrin/tests/testdata/Makefile.am
@@ -0,0 +1,2 @@
+EXTRA_DIST = example.com # not necessarily needed, but for reference
+EXTRA_DIST += example.com.sqlite3
diff --git a/src/bin/xfrin/tests/testdata/example.com b/src/bin/xfrin/tests/testdata/example.com
new file mode 100644
index 0000000..2afcd28
--- /dev/null
+++ b/src/bin/xfrin/tests/testdata/example.com
@@ -0,0 +1,17 @@
+;; This is a simplest form of zone file for 'example.com', which is the
+;; source of the corresponding sqlite3 DB file. This file is provided
+;; for reference purposes only; it's not actually used anywhere.
+
+example.com. 3600 IN SOA master.example.com. admin.example.com. (
+ 1230 ; serial
+ 3600 ; refresh (1 hour)
+ 1800 ; retry (30 minutes)
+ 2419200 ; expire (4 weeks)
+ 7200 ; minimum (2 hours)
+ )
+ 3600 NS dns01.example.com.
+ 3600 NS dns02.example.com.
+ 3600 NS dns03.example.com.
+dns01.example.com. 3600 IN A 192.0.2.1
+dns02.example.com. 3600 IN A 192.0.2.2
+dns03.example.com. 3600 IN A 192.0.2.3
diff --git a/src/bin/xfrin/tests/testdata/example.com.sqlite3 b/src/bin/xfrin/tests/testdata/example.com.sqlite3
new file mode 100644
index 0000000..ed241c3
Binary files /dev/null and b/src/bin/xfrin/tests/testdata/example.com.sqlite3 differ
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index 05cce98..5d3a16e 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -14,10 +14,12 @@
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import unittest
+import shutil
import socket
import io
from isc.testutils.tsigctx_mock import MockTSIGContext
from xfrin import *
+from isc.xfrin.diff import Diff
import isc.log
#
@@ -36,28 +38,64 @@ TEST_MASTER_IPV6_ADDRESS = '::1'
TEST_MASTER_IPV6_ADDRINFO = (socket.AF_INET6, socket.SOCK_STREAM,
socket.IPPROTO_TCP, '',
(TEST_MASTER_IPV6_ADDRESS, 53))
+
+TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
+TESTDATA_OBJDIR = os.getenv("TESTDATAOBJDIR")
# XXX: This should be a non priviledge port that is unlikely to be used.
# If some other process uses this port test will fail.
TEST_MASTER_PORT = '53535'
TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
+# SOA intended to be used for the new SOA as a result of transfer.
soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
'master.example.com. admin.example.com ' +
'1234 3600 1800 2419200 7200')
-soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(),
- RRTTL(3600))
+soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(), RRTTL(3600))
soa_rrset.add_rdata(soa_rdata)
-example_axfr_question = Question(TEST_ZONE_NAME, TEST_RRCLASS,
- RRType.AXFR())
-example_soa_question = Question(TEST_ZONE_NAME, TEST_RRCLASS,
- RRType.SOA())
+
+# SOA intended to be used for the current SOA at the secondary side.
+# Note that its serial is smaller than that of soa_rdata.
+begin_soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
+ 'master.example.com. admin.example.com ' +
+ '1230 3600 1800 2419200 7200')
+begin_soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(), RRTTL(3600))
+begin_soa_rrset.add_rdata(begin_soa_rdata)
+example_axfr_question = Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.AXFR())
+example_soa_question = Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA())
default_questions = [example_axfr_question]
default_answers = [soa_rrset]
+def check_diffs(assert_fn, expected, actual):
+ '''A helper function checking the differences made in the XFR session.
+
+ This is expected called from some subclass of unittest.TestCase and
+ assert_fn is generally expected to be 'self.assertEqual' of that class.
+
+ '''
+ assert_fn(len(expected), len(actual))
+ for (diffs_exp, diffs_actual) in zip(expected, actual):
+ assert_fn(len(diffs_exp), len(diffs_actual))
+ for (diff_exp, diff_actual) in zip(diffs_exp, diffs_actual):
+ # operation should match
+ assert_fn(diff_exp[0], diff_actual[0])
+ # The diff as RRset should be equal (for simplicity we assume
+ # all RRsets contain exactly one RDATA)
+ assert_fn(diff_exp[1].get_name(), diff_actual[1].get_name())
+ assert_fn(diff_exp[1].get_type(), diff_actual[1].get_type())
+ assert_fn(diff_exp[1].get_class(), diff_actual[1].get_class())
+ assert_fn(diff_exp[1].get_rdata_count(),
+ diff_actual[1].get_rdata_count())
+ assert_fn(1, diff_exp[1].get_rdata_count())
+ assert_fn(diff_exp[1].get_rdata()[0],
+ diff_actual[1].get_rdata()[0])
+
class XfrinTestException(Exception):
pass
+class XfrinTestTimeoutException(Exception):
+ pass
+
class MockCC():
def get_default_value(self, identifier):
if identifier == "zones/master_port":
@@ -65,6 +103,81 @@ class MockCC():
if identifier == "zones/class":
return TEST_RRCLASS_STR
+class MockDataSourceClient():
+ '''A simple mock data source client.
+
+ This class provides a minimal set of wrappers related the data source
+ API that would be used by Diff objects. For our testing purposes they
+ only keep truck of the history of the changes.
+
+ '''
+ def __init__(self):
+ self.force_fail = False # if True, raise an exception on commit
+ self.committed_diffs = []
+ self.diffs = []
+
+ def get_class(self):
+ '''Mock version of get_class().
+
+ We simply return the commonly used constant RR class. If and when
+ we use this mock for a different RR class we need to adjust it
+ accordingly.
+
+ '''
+ return TEST_RRCLASS
+
+ def find_zone(self, zone_name):
+ '''Mock version of find_zone().
+
+ It returns itself (subsequently acting as a mock ZoneFinder) for
+ some test zone names. For some others it returns either NOTFOUND
+ or PARTIALMATCH.
+
+ '''
+ if zone_name == TEST_ZONE_NAME or \
+ zone_name == Name('no-soa.example') or \
+ zone_name == Name('dup-soa.example'):
+ return (isc.datasrc.DataSourceClient.SUCCESS, self)
+ elif zone_name == Name('no-such-zone.example'):
+ return (DataSourceClient.NOTFOUND, None)
+ elif zone_name == Name('partial-match-zone.example'):
+ return (DataSourceClient.PARTIALMATCH, self)
+ raise ValueError('Unexpected input to mock client: bug in test case?')
+
+ def find(self, name, rrtype, target, options):
+ '''Mock ZoneFinder.find().
+
+ It returns the predefined SOA RRset to queries for SOA of the common
+ test zone name. It also emulates some unusual cases for special
+ zone names.
+
+ '''
+ if name == TEST_ZONE_NAME and rrtype == RRType.SOA():
+ return (ZoneFinder.SUCCESS, begin_soa_rrset)
+ if name == Name('no-soa.example'):
+ return (ZoneFinder.NXDOMAIN, None)
+ if name == Name('dup-soa.example'):
+ dup_soa_rrset = RRset(name, TEST_RRCLASS, RRType.SOA(), RRTTL(0))
+ dup_soa_rrset.add_rdata(begin_soa_rdata)
+ dup_soa_rrset.add_rdata(soa_rdata)
+ return (ZoneFinder.SUCCESS, dup_soa_rrset)
+ raise ValueError('Unexpected input to mock finder: bug in test case?')
+
+ def get_updater(self, zone_name, replace):
+ return self
+
+ def add_rrset(self, rrset):
+ self.diffs.append(('add', rrset))
+
+ def delete_rrset(self, rrset):
+ self.diffs.append(('delete', rrset))
+
+ def commit(self):
+ if self.force_fail:
+ raise isc.datasrc.Error('Updater.commit() failed')
+ self.committed_diffs.append(self.diffs)
+ self.diffs = []
+
class MockXfrin(Xfrin):
# This is a class attribute of a callable object that specifies a non
# default behavior triggered in _cc_check_command(). Specific test methods
@@ -87,20 +200,21 @@ class MockXfrin(Xfrin):
MockXfrin.check_command_hook()
def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo,
- tsig_key, check_soa=True):
+ tsig_key, request_type, check_soa=True):
# store some of the arguments for verification, then call this
# method in the superclass
self.xfrin_started_master_addr = master_addrinfo[2][0]
self.xfrin_started_master_port = master_addrinfo[2][1]
- return Xfrin.xfrin_start(self, zone_name, rrclass, db_file,
+ self.xfrin_started_request_type = request_type
+ return Xfrin.xfrin_start(self, zone_name, rrclass, None,
master_addrinfo, tsig_key,
- check_soa)
+ request_type, check_soa)
class MockXfrinConnection(XfrinConnection):
- def __init__(self, sock_map, zone_name, rrclass, db_file, shutdown_event,
+ def __init__(self, sock_map, zone_name, rrclass, shutdown_event,
master_addr):
- super().__init__(sock_map, zone_name, rrclass, db_file, shutdown_event,
- master_addr)
+ super().__init__(sock_map, zone_name, rrclass, MockDataSourceClient(),
+ shutdown_event, master_addr)
self.query_data = b''
self.reply_data = b''
self.force_time_out = False
@@ -121,8 +235,11 @@ class MockXfrinConnection(XfrinConnection):
def recv(self, size):
data = self.reply_data[:size]
self.reply_data = self.reply_data[size:]
+ if len(data) == 0:
+ raise XfrinTestTimeoutException('Emulated timeout')
if len(data) < size:
- raise XfrinTestException('cannot get reply data')
+ raise XfrinTestException('cannot get reply data (' + str(size) +
+ ' bytes)')
return data
def send(self, data):
@@ -174,14 +291,296 @@ class MockXfrinConnection(XfrinConnection):
return reply_data
+class TestXfrinState(unittest.TestCase):
+ def setUp(self):
+ self.sock_map = {}
+ self.conn = MockXfrinConnection(self.sock_map, TEST_ZONE_NAME,
+ TEST_RRCLASS, threading.Event(),
+ TEST_MASTER_IPV4_ADDRINFO)
+ self.begin_soa = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(),
+ RRTTL(3600))
+ self.begin_soa.add_rdata(Rdata(RRType.SOA(), TEST_RRCLASS,
+ 'm. r. 1230 0 0 0 0'))
+ self.ns_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.NS(),
+ RRTTL(3600))
+ self.ns_rrset.add_rdata(Rdata(RRType.NS(), TEST_RRCLASS,
+ 'ns.example.com'))
+ self.a_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.A(),
+ RRTTL(3600))
+ self.a_rrset.add_rdata(Rdata(RRType.A(), TEST_RRCLASS, '192.0.2.1'))
+
+ self.conn._datasrc_client = MockDataSourceClient()
+ self.conn._diff = Diff(self.conn._datasrc_client, TEST_ZONE_NAME)
+
+class TestXfrinStateBase(TestXfrinState):
+ def setUp(self):
+ super().setUp()
+
+ def test_handle_rr_on_base(self):
+ # The base version of handle_rr() isn't supposed to be called
+ # directly (the argument doesn't matter in this test)
+ self.assertRaises(XfrinException, XfrinState().handle_rr, None)
+
+class TestXfrinInitialSOA(TestXfrinState):
+ def setUp(self):
+ super().setUp()
+ self.state = XfrinInitialSOA()
+
+ def test_handle_rr(self):
+ # normal case
+ self.assertTrue(self.state.handle_rr(self.conn, soa_rrset))
+ self.assertEqual(type(XfrinFirstData()),
+ type(self.conn.get_xfrstate()))
+ self.assertEqual(1234, self.conn._end_serial)
+
+ def test_handle_not_soa(self):
+ # The given RR is not of SOA
+ self.assertRaises(XfrinProtocolError, self.state.handle_rr, self.conn,
+ self.ns_rrset)
+
+ def test_finish_message(self):
+ self.assertTrue(self.state.finish_message(self.conn))
+
+class TestXfrinFirstData(TestXfrinState):
+ def setUp(self):
+ super().setUp()
+ self.state = XfrinFirstData()
+ self.conn._request_type = RRType.IXFR()
+ self.conn._request_serial = 1230 # arbitrary chosen serial < 1234
+ self.conn._diff = None # should be replaced in the AXFR case
+
+ def test_handle_ixfr_begin_soa(self):
+ self.conn._request_type = RRType.IXFR()
+ self.assertFalse(self.state.handle_rr(self.conn, self.begin_soa))
+ self.assertEqual(type(XfrinIXFRDeleteSOA()),
+ type(self.conn.get_xfrstate()))
+
+ def test_handle_axfr(self):
+ # If the original type is AXFR, other conditions aren't considered,
+ # and AXFR processing will continue
+ self.conn._request_type = RRType.AXFR()
+ self.assertFalse(self.state.handle_rr(self.conn, self.begin_soa))
+ self.assertEqual(type(XfrinAXFR()), type(self.conn.get_xfrstate()))
+
+ def test_handle_ixfr_to_axfr(self):
+ # Detecting AXFR-compatible IXFR response by seeing a non SOA RR after
+ # the initial SOA. Should switch to AXFR.
+ self.assertFalse(self.state.handle_rr(self.conn, self.ns_rrset))
+ self.assertEqual(type(XfrinAXFR()), type(self.conn.get_xfrstate()))
+ # The Diff for AXFR should be created at this point
+ self.assertNotEqual(None, self.conn._diff)
+
+ def test_handle_ixfr_to_axfr_by_different_soa(self):
+ # An unusual case: Response contains two consecutive SOA but the
+ # serial of the second does not match the requested one. See
+ # the documentation for XfrinFirstData.handle_rr().
+ self.assertFalse(self.state.handle_rr(self.conn, soa_rrset))
+ self.assertEqual(type(XfrinAXFR()), type(self.conn.get_xfrstate()))
+ self.assertNotEqual(None, self.conn._diff)
+
+ def test_finish_message(self):
+ self.assertTrue(self.state.finish_message(self.conn))
+
+class TestXfrinIXFRDeleteSOA(TestXfrinState):
+ def setUp(self):
+ super().setUp()
+ self.state = XfrinIXFRDeleteSOA()
+ # In this state a new Diff object is expected to be created. To
+ # confirm it, we nullify it beforehand.
+ self.conn._diff = None
+
+ def test_handle_rr(self):
+ self.assertTrue(self.state.handle_rr(self.conn, self.begin_soa))
+ self.assertEqual(type(XfrinIXFRDelete()),
+ type(self.conn.get_xfrstate()))
+ self.assertEqual([('delete', self.begin_soa)],
+ self.conn._diff.get_buffer())
+
+ def test_handle_non_soa(self):
+ self.assertRaises(XfrinException, self.state.handle_rr, self.conn,
+ self.ns_rrset)
+
+ def test_finish_message(self):
+ self.assertTrue(self.state.finish_message(self.conn))
+
+class TestXfrinIXFRDelete(TestXfrinState):
+ def setUp(self):
+ super().setUp()
+ # We need record the state in 'conn' to check the case where the
+ # state doesn't change.
+ XfrinIXFRDelete().set_xfrstate(self.conn, XfrinIXFRDelete())
+ self.state = self.conn.get_xfrstate()
+
+ def test_handle_delete_rr(self):
+ # Non SOA RRs are simply (goting to be) deleted in this state
+ self.assertTrue(self.state.handle_rr(self.conn, self.ns_rrset))
+ self.assertEqual([('delete', self.ns_rrset)],
+ self.conn._diff.get_buffer())
+ # The state shouldn't change
+ self.assertEqual(type(XfrinIXFRDelete()),
+ type(self.conn.get_xfrstate()))
+
+ def test_handle_soa(self):
+ # SOA in this state means the beginning of added RRs. This SOA
+ # should also be added in the next state, so handle_rr() should return
+ # false.
+ self.assertFalse(self.state.handle_rr(self.conn, soa_rrset))
+ self.assertEqual([], self.conn._diff.get_buffer())
+ self.assertEqual(1234, self.conn._current_serial)
+ self.assertEqual(type(XfrinIXFRAddSOA()),
+ type(self.conn.get_xfrstate()))
+
+ def test_finish_message(self):
+ self.assertTrue(self.state.finish_message(self.conn))
+
+class TestXfrinIXFRAddSOA(TestXfrinState):
+ def setUp(self):
+ super().setUp()
+ self.state = XfrinIXFRAddSOA()
+
+ def test_handle_rr(self):
+ self.assertTrue(self.state.handle_rr(self.conn, soa_rrset))
+ self.assertEqual(type(XfrinIXFRAdd()), type(self.conn.get_xfrstate()))
+ self.assertEqual([('add', soa_rrset)],
+ self.conn._diff.get_buffer())
+
+ def test_handle_non_soa(self):
+ self.assertRaises(XfrinException, self.state.handle_rr, self.conn,
+ self.ns_rrset)
+
+ def test_finish_message(self):
+ self.assertTrue(self.state.finish_message(self.conn))
+
+class TestXfrinIXFRAdd(TestXfrinState):
+ def setUp(self):
+ super().setUp()
+ # We need record the state in 'conn' to check the case where the
+ # state doesn't change.
+ XfrinIXFRAdd().set_xfrstate(self.conn, XfrinIXFRAdd())
+ self.conn._current_serial = 1230
+ self.state = self.conn.get_xfrstate()
+
+ def test_handle_add_rr(self):
+ # Non SOA RRs are simply (goting to be) added in this state
+ self.assertTrue(self.state.handle_rr(self.conn, self.ns_rrset))
+ self.assertEqual([('add', self.ns_rrset)],
+ self.conn._diff.get_buffer())
+ # The state shouldn't change
+ self.assertEqual(type(XfrinIXFRAdd()), type(self.conn.get_xfrstate()))
+
+ def test_handle_end_soa(self):
+ self.conn._end_serial = 1234
+ self.conn._diff.add_data(self.ns_rrset) # put some dummy change
+ self.assertTrue(self.state.handle_rr(self.conn, soa_rrset))
+ self.assertEqual(type(XfrinIXFREnd()), type(self.conn.get_xfrstate()))
+ # handle_rr should have caused commit, and the buffer should now be
+ # empty.
+ self.assertEqual([], self.conn._diff.get_buffer())
+
+ def test_handle_new_delete(self):
+ self.conn._end_serial = 1234
+ # SOA RR whose serial is the current one means we are going to a new
+ # difference, starting with removing that SOA.
+ self.conn._diff.add_data(self.ns_rrset) # put some dummy change
+ self.assertFalse(self.state.handle_rr(self.conn, self.begin_soa))
+ self.assertEqual([], self.conn._diff.get_buffer())
+ self.assertEqual(type(XfrinIXFRDeleteSOA()),
+ type(self.conn.get_xfrstate()))
+
+ def test_handle_out_of_sync(self):
+ # getting SOA with an inconsistent serial. This is an error.
+ self.conn._end_serial = 1235
+ self.assertRaises(XfrinProtocolError, self.state.handle_rr,
+ self.conn, soa_rrset)
+
+ def test_finish_message(self):
+ self.assertTrue(self.state.finish_message(self.conn))
+
+class TestXfrinIXFREnd(TestXfrinState):
+ def setUp(self):
+ super().setUp()
+ self.state = XfrinIXFREnd()
+
+ def test_handle_rr(self):
+ self.assertRaises(XfrinProtocolError, self.state.handle_rr, self.conn,
+ self.ns_rrset)
+
+ def test_finish_message(self):
+ self.assertFalse(self.state.finish_message(self.conn))
+
+class TestXfrinAXFR(TestXfrinState):
+ def setUp(self):
+ super().setUp()
+ self.state = XfrinAXFR()
+ self.conn._end_serial = 1234
+
+ def test_handle_rr(self):
+ """
+ Test we can put data inside.
+ """
+ # Put some data inside
+ self.assertTrue(self.state.handle_rr(self.conn, self.a_rrset))
+ # This test uses internal Diff structure to check the behaviour of
+ # XfrinAXFR. Maybe there could be a cleaner way, but it would be more
+ # complicated.
+ self.assertEqual([('add', self.a_rrset)], self.conn._diff.get_buffer())
+ # This SOA terminates the transfer
+ self.assertTrue(self.state.handle_rr(self.conn, soa_rrset))
+ # It should have changed the state
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ # At this point, the data haven't been committed yet
+ self.assertEqual([('add', self.a_rrset), ('add', soa_rrset)],
+ self.conn._diff.get_buffer())
+
+ def test_handle_rr_mismatch_soa(self):
+ """ SOA with inconsistent serial - unexpected, but we accept it.
+
+ """
+ self.assertTrue(self.state.handle_rr(self.conn, begin_soa_rrset))
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+
+ def test_finish_message(self):
+ """
+ Check normal end of message.
+ """
+ # When a message ends, nothing happens usually
+ self.assertTrue(self.state.finish_message(self.conn))
+
+class TestXfrinAXFREnd(TestXfrinState):
+ def setUp(self):
+ super().setUp()
+ self.state = XfrinAXFREnd()
+
+ def test_handle_rr(self):
+ self.assertRaises(XfrinProtocolError, self.state.handle_rr, self.conn,
+ self.ns_rrset)
+
+ def test_finish_message(self):
+ self.conn._diff.add_data(self.a_rrset)
+ self.conn._diff.add_data(soa_rrset)
+ self.assertFalse(self.state.finish_message(self.conn))
+
+ # The data should have been committed
+ self.assertEqual([], self.conn._diff.get_buffer())
+ check_diffs(self.assertEqual, [[('add', self.a_rrset),
+ ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
+ self.assertRaises(ValueError, self.conn._diff.commit)
+
class TestXfrinConnection(unittest.TestCase):
+ '''Convenient parent class for XFR-protocol tests.
+
+ This class provides common setups and helper methods for protocol related
+ tests on AXFR and IXFR.
+
+ '''
+
def setUp(self):
if os.path.exists(TEST_DB_FILE):
os.remove(TEST_DB_FILE)
self.sock_map = {}
- self.conn = MockXfrinConnection(self.sock_map, 'example.com.',
- TEST_RRCLASS, TEST_DB_FILE,
- threading.Event(),
+ self.conn = MockXfrinConnection(self.sock_map, TEST_ZONE_NAME,
+ TEST_RRCLASS, threading.Event(),
TEST_MASTER_IPV4_ADDRINFO)
self.soa_response_params = {
'questions': [example_soa_question],
@@ -192,6 +591,10 @@ class TestXfrinConnection(unittest.TestCase):
'axfr_after_soa': self._create_normal_response_data
}
self.axfr_response_params = {
+ 'question_1st': default_questions,
+ 'question_2nd': default_questions,
+ 'answer_1st': [soa_rrset, self._create_ns()],
+ 'answer_2nd': default_answers,
'tsig_1st': None,
'tsig_2nd': None
}
@@ -201,6 +604,82 @@ class TestXfrinConnection(unittest.TestCase):
if os.path.exists(TEST_DB_FILE):
os.remove(TEST_DB_FILE)
+ def _create_normal_response_data(self):
+ # This helper method creates a simple sequence of DNS messages that
+ # forms a valid AXFR transaction. It consists of two messages: the
+ # first one containing SOA, NS, the second containing the trailing SOA.
+ question_1st = self.axfr_response_params['question_1st']
+ question_2nd = self.axfr_response_params['question_2nd']
+ answer_1st = self.axfr_response_params['answer_1st']
+ answer_2nd = self.axfr_response_params['answer_2nd']
+ tsig_1st = self.axfr_response_params['tsig_1st']
+ tsig_2nd = self.axfr_response_params['tsig_2nd']
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=question_1st, answers=answer_1st,
+ tsig_ctx=tsig_1st)
+ self.conn.reply_data += \
+ self.conn.create_response_data(questions=question_2nd,
+ answers=answer_2nd,
+ tsig_ctx=tsig_2nd)
+
+ def _create_soa_response_data(self):
+ # This helper method creates a DNS message that is supposed to be
+ # used a valid response to SOA queries prior to XFR.
+ # If tsig is True, it tries to verify the query with a locally
+ # created TSIG context (which may or may not succeed) so that the
+ # response will include a TSIG.
+ # If axfr_after_soa is True, it resets the response_generator so that
+ # a valid XFR messages will follow.
+
+ verify_ctx = None
+ if self.soa_response_params['tsig']:
+ # xfrin (currently) always uses TCP. strip off the length field.
+ query_data = self.conn.query_data[2:]
+ query_message = Message(Message.PARSE)
+ query_message.from_wire(query_data)
+ verify_ctx = TSIGContext(TSIG_KEY)
+ verify_ctx.verify(query_message.get_tsig_record(), query_data)
+
+ self.conn.reply_data = self.conn.create_response_data(
+ bad_qid=self.soa_response_params['bad_qid'],
+ response=self.soa_response_params['response'],
+ rcode=self.soa_response_params['rcode'],
+ questions=self.soa_response_params['questions'],
+ tsig_ctx=verify_ctx)
+ if self.soa_response_params['axfr_after_soa'] != None:
+ self.conn.response_generator = \
+ self.soa_response_params['axfr_after_soa']
+
+ def _create_broken_response_data(self):
+ # This helper method creates a bogus "DNS message" that only contains
+ # 4 octets of data. The DNS message parser will raise an exception.
+ bogus_data = b'xxxx'
+ self.conn.reply_data = struct.pack('H', socket.htons(len(bogus_data)))
+ self.conn.reply_data += bogus_data
+
+ def _create_a(self, address):
+ rrset = RRset(Name('a.example.com'), TEST_RRCLASS, RRType.A(),
+ RRTTL(3600))
+ rrset.add_rdata(Rdata(RRType.A(), TEST_RRCLASS, address))
+ return rrset
+
+ def _create_soa(self, serial):
+ rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(),
+ RRTTL(3600))
+ rdata_str = 'm. r. ' + serial + ' 3600 1800 2419200 7200'
+ rrset.add_rdata(Rdata(RRType.SOA(), TEST_RRCLASS, rdata_str))
+ return rrset
+
+ def _create_ns(self, nsname='ns.'+TEST_ZONE_NAME_STR):
+ rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.NS(), RRTTL(3600))
+ rrset.add_rdata(Rdata(RRType.NS(), TEST_RRCLASS, nsname))
+ return rrset
+
+class TestAXFR(TestXfrinConnection):
+ def setUp(self):
+ super().setUp()
+ XfrinInitialSOA().set_xfrstate(self.conn, XfrinInitialSOA())
+
def __create_mock_tsig(self, key, error):
# This helper function creates a MockTSIGContext for a given key
# and TSIG error to be used as a result of verify (normally faked
@@ -236,31 +715,81 @@ class TestXfrinConnection(unittest.TestCase):
# to confirm an AF_INET6 socket has been created. A naive application
# tends to assume it's IPv4 only and hardcode AF_INET. This test
# uncovers such a bug.
- c = MockXfrinConnection({}, 'example.com.', TEST_RRCLASS, TEST_DB_FILE,
- threading.Event(),
- TEST_MASTER_IPV6_ADDRINFO)
+ c = MockXfrinConnection({}, TEST_ZONE_NAME, TEST_RRCLASS,
+ threading.Event(), TEST_MASTER_IPV6_ADDRINFO)
c.bind(('::', 0))
c.close()
def test_init_chclass(self):
- c = XfrinConnection({}, 'example.com.', RRClass.CH(), TEST_DB_FILE,
- threading.Event(), TEST_MASTER_IPV4_ADDRINFO)
+ c = MockXfrinConnection({}, TEST_ZONE_NAME, RRClass.CH(),
+ threading.Event(), TEST_MASTER_IPV4_ADDRINFO)
axfrmsg = c._create_query(RRType.AXFR())
self.assertEqual(axfrmsg.get_question()[0].get_class(),
RRClass.CH())
c.close()
- def test_send_query(self):
- def create_msg(query_type):
- msg = Message(Message.RENDER)
- query_id = 0x1035
- msg.set_qid(query_id)
- msg.set_opcode(Opcode.QUERY())
- msg.set_rcode(Rcode.NOERROR())
- query_question = Question(Name("example.com."), RRClass.IN(), query_type)
- msg.add_question(query_question)
- return msg
+ def test_create_query(self):
+ def check_query(expected_qtype, expected_auth):
+ '''Helper method to repeat the same pattern of tests'''
+ self.assertEqual(Opcode.QUERY(), msg.get_opcode())
+ self.assertEqual(Rcode.NOERROR(), msg.get_rcode())
+ self.assertEqual(1, msg.get_rr_count(Message.SECTION_QUESTION))
+ self.assertEqual(TEST_ZONE_NAME, msg.get_question()[0].get_name())
+ self.assertEqual(expected_qtype, msg.get_question()[0].get_type())
+ self.assertEqual(0, msg.get_rr_count(Message.SECTION_ANSWER))
+ self.assertEqual(0, msg.get_rr_count(Message.SECTION_ADDITIONAL))
+ if expected_auth is None:
+ self.assertEqual(0,
+ msg.get_rr_count(Message.SECTION_AUTHORITY))
+ else:
+ self.assertEqual(1,
+ msg.get_rr_count(Message.SECTION_AUTHORITY))
+ auth_rr = msg.get_section(Message.SECTION_AUTHORITY)[0]
+ self.assertEqual(expected_auth.get_name(), auth_rr.get_name())
+ self.assertEqual(expected_auth.get_type(), auth_rr.get_type())
+ self.assertEqual(expected_auth.get_class(),
+ auth_rr.get_class())
+ # In our test scenario RDATA must be 1
+ self.assertEqual(1, expected_auth.get_rdata_count())
+ self.assertEqual(1, auth_rr.get_rdata_count())
+ self.assertEqual(expected_auth.get_rdata()[0],
+ auth_rr.get_rdata()[0])
+
+ # Actual tests start here
+ # SOA query
+ msg = self.conn._create_query(RRType.SOA())
+ check_query(RRType.SOA(), None)
+
+ # AXFR query
+ msg = self.conn._create_query(RRType.AXFR())
+ check_query(RRType.AXFR(), None)
+
+ # IXFR query
+ msg = self.conn._create_query(RRType.IXFR())
+ check_query(RRType.IXFR(), begin_soa_rrset)
+ self.assertEqual(1230, self.conn._request_serial)
+
+ def test_create_ixfr_query_fail(self):
+ # In these cases _create_query() will fail to find a valid SOA RR to
+ # insert in the IXFR query, and should raise an exception.
+
+ self.conn._zone_name = Name('no-such-zone.example')
+ self.assertRaises(XfrinException, self.conn._create_query,
+ RRType.IXFR())
+
+ self.conn._zone_name = Name('partial-match-zone.example')
+ self.assertRaises(XfrinException, self.conn._create_query,
+ RRType.IXFR())
+
+ self.conn._zone_name = Name('no-soa.example')
+ self.assertRaises(XfrinException, self.conn._create_query,
+ RRType.IXFR())
+
+ self.conn._zone_name = Name('dup-soa.example')
+ self.assertRaises(XfrinException, self.conn._create_query,
+ RRType.IXFR())
+ def test_send_query(self):
def message_has_tsig(data):
# a simple check if the actual data contains a TSIG RR.
# At our level this simple check should suffice; other detailed
@@ -269,14 +798,6 @@ class TestXfrinConnection(unittest.TestCase):
msg.from_wire(data)
return msg.get_tsig_record() is not None
- self.conn._create_query = create_msg
- # soa request
- self.conn._send_query(RRType.SOA())
- self.assertEqual(self.conn.query_data, b'\x00\x1d\x105\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\x06\x00\x01')
- # axfr request
- self.conn._send_query(RRType.AXFR())
- self.assertEqual(self.conn.query_data, b'\x00\x1d\x105\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\xfc\x00\x01')
-
# soa request with tsig
self.conn._tsig_key = TSIG_KEY
self.conn._send_query(RRType.SOA())
@@ -288,24 +809,28 @@ class TestXfrinConnection(unittest.TestCase):
def test_response_with_invalid_msg(self):
self.conn.reply_data = b'aaaxxxx'
- self.assertRaises(XfrinTestException, self._handle_xfrin_response)
+ self.assertRaises(XfrinTestException,
+ self.conn._handle_xfrin_responses)
def test_response_with_tsigfail(self):
self.conn._tsig_key = TSIG_KEY
# server tsig check fail, return with RCODE 9 (NOTAUTH)
self.conn._send_query(RRType.SOA())
self.conn.reply_data = self.conn.create_response_data(rcode=Rcode.NOTAUTH())
- self.assertRaises(XfrinException, self._handle_xfrin_response)
+ self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
def test_response_without_end_soa(self):
self.conn._send_query(RRType.AXFR())
self.conn.reply_data = self.conn.create_response_data()
- self.assertRaises(XfrinTestException, self._handle_xfrin_response)
+ # This should result in timeout in the asyncore loop. We emulate
+ # that situation in recv() by emptying the reply data buffer.
+ self.assertRaises(XfrinTestTimeoutException,
+ self.conn._handle_xfrin_responses)
def test_response_bad_qid(self):
self.conn._send_query(RRType.AXFR())
- self.conn.reply_data = self.conn.create_response_data(bad_qid = True)
- self.assertRaises(XfrinException, self._handle_xfrin_response)
+ self.conn.reply_data = self.conn.create_response_data(bad_qid=True)
+ self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
def test_response_error_code_bad_sig(self):
self.conn._tsig_key = TSIG_KEY
@@ -318,7 +843,7 @@ class TestXfrinConnection(unittest.TestCase):
# validate log message for XfrinException
self.__match_exception(XfrinException,
"TSIG verify fail: BADSIG",
- self._handle_xfrin_response)
+ self.conn._handle_xfrin_responses)
def test_response_bad_qid_bad_key(self):
self.conn._tsig_key = TSIG_KEY
@@ -330,36 +855,29 @@ class TestXfrinConnection(unittest.TestCase):
# validate log message for XfrinException
self.__match_exception(XfrinException,
"TSIG verify fail: BADKEY",
- self._handle_xfrin_response)
+ self.conn._handle_xfrin_responses)
def test_response_non_response(self):
self.conn._send_query(RRType.AXFR())
- self.conn.reply_data = self.conn.create_response_data(response = False)
- self.assertRaises(XfrinException, self._handle_xfrin_response)
+ self.conn.reply_data = self.conn.create_response_data(response=False)
+ self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
def test_response_error_code(self):
self.conn._send_query(RRType.AXFR())
self.conn.reply_data = self.conn.create_response_data(
rcode=Rcode.SERVFAIL())
- self.assertRaises(XfrinException, self._handle_xfrin_response)
+ self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
def test_response_multi_question(self):
self.conn._send_query(RRType.AXFR())
self.conn.reply_data = self.conn.create_response_data(
questions=[example_axfr_question, example_axfr_question])
- self.assertRaises(XfrinException, self._handle_xfrin_response)
-
- def test_response_empty_answer(self):
- self.conn._send_query(RRType.AXFR())
- self.conn.reply_data = self.conn.create_response_data(answers=[])
- # Should an empty answer trigger an exception? Even though it's very
- # unusual it's not necessarily invalid. Need to revisit.
- self.assertRaises(XfrinException, self._handle_xfrin_response)
+ self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
def test_response_non_response(self):
self.conn._send_query(RRType.AXFR())
self.conn.reply_data = self.conn.create_response_data(response = False)
- self.assertRaises(XfrinException, self._handle_xfrin_response)
+ self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
def test_soacheck(self):
# we need to defer the creation until we know the QID, which is
@@ -450,30 +968,155 @@ class TestXfrinConnection(unittest.TestCase):
self.conn.response_generator = self._create_normal_response_data
self.conn._shutdown_event.set()
self.conn._send_query(RRType.AXFR())
- self.assertRaises(XfrinException, self._handle_xfrin_response)
+ self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
def test_response_timeout(self):
self.conn.response_generator = self._create_normal_response_data
self.conn.force_time_out = True
- self.assertRaises(XfrinException, self._handle_xfrin_response)
+ self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
def test_response_remote_close(self):
self.conn.response_generator = self._create_normal_response_data
self.conn.force_close = True
- self.assertRaises(XfrinException, self._handle_xfrin_response)
+ self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
def test_response_bad_message(self):
self.conn.response_generator = self._create_broken_response_data
self.conn._send_query(RRType.AXFR())
- self.assertRaises(Exception, self._handle_xfrin_response)
+ self.assertRaises(Exception, self.conn._handle_xfrin_responses)
+
+ def test_axfr_response(self):
+ # A simple normal case: AXFR consists of SOA, NS, then trailing SOA.
+ self.conn.response_generator = self._create_normal_response_data
+ self.conn._send_query(RRType.AXFR())
+ self.conn._handle_xfrin_responses()
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ check_diffs(self.assertEqual,
+ [[('add', self._create_ns()), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
+
+ def test_response_empty_answer(self):
+ '''Test with an empty AXFR answer section.
+
+ This is an unusual response, but there is no reason to reject it.
+ The second message is a complete AXFR response, and transfer should
+ succeed just like the normal case.
+
+ '''
+
+ self.axfr_response_params['answer_1st'] = []
+ self.axfr_response_params['answer_2nd'] = [soa_rrset,
+ self._create_ns(),
+ soa_rrset]
+ self.conn.response_generator = self._create_normal_response_data
+ self.conn._send_query(RRType.AXFR())
+ self.conn._handle_xfrin_responses()
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ check_diffs(self.assertEqual,
+ [[('add', self._create_ns()), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
+
+ def test_axfr_response_soa_mismatch(self):
+ '''AXFR response whose begin/end SOAs are not same.
+
+ What should we do this is moot, for now we accept it, so does BIND 9.
+
+ '''
+ ns_rr = self._create_ns()
+ a_rr = self._create_a('192.0.2.1')
+ self.conn._send_query(RRType.AXFR())
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+ RRType.AXFR())],
+ # begin serial=1230, end serial=1234. end will be used.
+ answers=[begin_soa_rrset, ns_rr, a_rr, soa_rrset])
+ self.conn._handle_xfrin_responses()
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ check_diffs(self.assertEqual,
+ [[('add', ns_rr), ('add', a_rr), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
+
+ def test_axfr_response_extra(self):
+ '''Test with an extra RR after the end of AXFR session.
+
+ The session should be rejected, and nothing should be committed.
+
+ '''
+ ns_rr = self._create_ns()
+ a_rr = self._create_a('192.0.2.1')
+ self.conn._send_query(RRType.AXFR())
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+ RRType.AXFR())],
+ answers=[soa_rrset, ns_rr, a_rr, soa_rrset, a_rr])
+ self.assertRaises(XfrinProtocolError,
+ self.conn._handle_xfrin_responses)
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ self.assertEqual([], self.conn._datasrc_client.committed_diffs)
+
+ def test_axfr_response_qname_mismatch(self):
+ '''AXFR response with a mismatch question name.
+
+ Our implementation accepts that, so does BIND 9.
+
+ '''
+ self.axfr_response_params['question_1st'] = \
+ [Question(Name('mismatch.example'), TEST_RRCLASS, RRType.AXFR())]
+ self.conn.response_generator = self._create_normal_response_data
+ self.conn._send_query(RRType.AXFR())
+ self.conn._handle_xfrin_responses()
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ check_diffs(self.assertEqual,
+ [[('add', self._create_ns()), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
+
+ def test_axfr_response_qclass_mismatch(self):
+ '''AXFR response with a mismatch RR class.
+
+ Our implementation accepts that, so does BIND 9.
+
+ '''
+ self.axfr_response_params['question_1st'] = \
+ [Question(TEST_ZONE_NAME, RRClass.CH(), RRType.AXFR())]
+ self.conn.response_generator = self._create_normal_response_data
+ self.conn._send_query(RRType.AXFR())
+ self.conn._handle_xfrin_responses()
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ check_diffs(self.assertEqual,
+ [[('add', self._create_ns()), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
+
+ def test_axfr_response_qtype_mismatch(self):
+ '''AXFR response with a mismatch RR type.
+
+ Our implementation accepts that, so does BIND 9.
+
+ '''
+ # returning IXFR in question to AXFR query
+ self.axfr_response_params['question_1st'] = \
+ [Question(TEST_ZONE_NAME, RRClass.CH(), RRType.IXFR())]
+ self.conn.response_generator = self._create_normal_response_data
+ self.conn._send_query(RRType.AXFR())
+ self.conn._handle_xfrin_responses()
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ check_diffs(self.assertEqual,
+ [[('add', self._create_ns()), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
- def test_response(self):
- # normal case.
+ def test_axfr_response_empty_question(self):
+ '''AXFR response with an empty question.
+
+ Our implementation accepts that, so does BIND 9.
+
+ '''
+ self.axfr_response_params['question_1st'] = []
self.conn.response_generator = self._create_normal_response_data
self.conn._send_query(RRType.AXFR())
- # two SOAs, and only these have been transfered. the 2nd SOA is just
- # a marker, so only 1 RR has been provided in the iteration.
- self.assertEqual(self._handle_xfrin_response(), 1)
+ self.conn._handle_xfrin_responses()
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ check_diffs(self.assertEqual,
+ [[('add', self._create_ns()), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
def test_do_xfrin(self):
self.conn.response_generator = self._create_normal_response_data
@@ -487,9 +1130,10 @@ class TestXfrinConnection(unittest.TestCase):
lambda key: self.__create_mock_tsig(key, TSIGError.NOERROR)
self.conn.response_generator = self._create_normal_response_data
self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
- # We use two messages in the tests. The same context should have been
- # usef for both.
- self.assertEqual(2, self.conn._tsig_ctx.verify_called)
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ check_diffs(self.assertEqual,
+ [[('add', self._create_ns()), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
def test_do_xfrin_with_tsig_fail(self):
# TSIG verify will fail for the first message. xfrin should fail
@@ -569,10 +1213,10 @@ class TestXfrinConnection(unittest.TestCase):
self.conn.response_generator = self._create_broken_response_data
self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
- def test_do_xfrin_dberror(self):
- # DB file is under a non existent directory, so its creation will fail,
- # which will make the transfer fail.
- self.conn._db_file = "not_existent/" + TEST_DB_FILE
+ def test_do_xfrin_datasrc_error(self):
+ # Emulate failure in the data source client on commit.
+ self.conn._datasrc_client.force_fail = True
+ self.conn.response_generator = self._create_normal_response_data
self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
def test_do_soacheck_and_xfrin(self):
@@ -598,10 +1242,7 @@ class TestXfrinConnection(unittest.TestCase):
def test_do_soacheck_broken_response(self):
self.conn.response_generator = self._create_broken_response_data
- # XXX: TODO: this test failed here, should xfr not raise an
- # exception but simply drop and return FAIL?
- #self.assertEqual(self.conn.do_xfrin(True), XFRIN_FAIL)
- self.assertRaises(MessageTooShort, self.conn.do_xfrin, True)
+ self.assertEqual(self.conn.do_xfrin(True), XFRIN_FAIL)
def test_do_soacheck_badqid(self):
# the QID mismatch would internally trigger a XfrinException exception,
@@ -610,59 +1251,396 @@ class TestXfrinConnection(unittest.TestCase):
self.conn.response_generator = self._create_soa_response_data
self.assertEqual(self.conn.do_xfrin(True), XFRIN_FAIL)
- def _handle_xfrin_response(self):
- # This helper methods iterates over all RRs (excluding the ending SOA)
- # transferred, and simply returns the number of RRs. The return value
- # may be used an assertion value for test cases.
- rrs = 0
- for rr in self.conn._handle_xfrin_response():
- rrs += 1
- return rrs
-
- def _create_normal_response_data(self):
- # This helper method creates a simple sequence of DNS messages that
- # forms a valid XFR transaction. It consists of two messages, each
- # containing just a single SOA RR.
- tsig_1st = self.axfr_response_params['tsig_1st']
- tsig_2nd = self.axfr_response_params['tsig_2nd']
- self.conn.reply_data = self.conn.create_response_data(tsig_ctx=tsig_1st)
- self.conn.reply_data += \
- self.conn.create_response_data(tsig_ctx=tsig_2nd)
+class TestIXFRResponse(TestXfrinConnection):
+ def setUp(self):
+ super().setUp()
+ self.conn._query_id = self.conn.qid = 1035
+ self.conn._request_serial = 1230
+ self.conn._request_type = RRType.IXFR()
+ self._zone_name = TEST_ZONE_NAME
+ self.conn._datasrc_client = MockDataSourceClient()
+ XfrinInitialSOA().set_xfrstate(self.conn, XfrinInitialSOA())
- def _create_soa_response_data(self):
- # This helper method creates a DNS message that is supposed to be
- # used a valid response to SOA queries prior to XFR.
- # If tsig is True, it tries to verify the query with a locally
- # created TSIG context (which may or may not succeed) so that the
- # response will include a TSIG.
- # If axfr_after_soa is True, it resets the response_generator so that
- # a valid XFR messages will follow.
+ def test_ixfr_response(self):
+ '''A simplest form of IXFR response.
- verify_ctx = None
- if self.soa_response_params['tsig']:
- # xfrin (curreently) always uses TCP. strip off the length field.
- query_data = self.conn.query_data[2:]
- query_message = Message(Message.PARSE)
- query_message.from_wire(query_data)
- verify_ctx = TSIGContext(TSIG_KEY)
- verify_ctx.verify(query_message.get_tsig_record(), query_data)
+ It simply updates the zone's SOA one time.
+ '''
self.conn.reply_data = self.conn.create_response_data(
- bad_qid=self.soa_response_params['bad_qid'],
- response=self.soa_response_params['response'],
- rcode=self.soa_response_params['rcode'],
- questions=self.soa_response_params['questions'],
- tsig_ctx=verify_ctx)
- if self.soa_response_params['axfr_after_soa'] != None:
- self.conn.response_generator = \
- self.soa_response_params['axfr_after_soa']
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+ answers=[soa_rrset, begin_soa_rrset, soa_rrset, soa_rrset])
+ self.conn._handle_xfrin_responses()
+ self.assertEqual(type(XfrinIXFREnd()), type(self.conn.get_xfrstate()))
+ self.assertEqual([], self.conn._datasrc_client.diffs)
+ check_diffs(self.assertEqual,
+ [[('delete', begin_soa_rrset), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
+
+ def test_ixfr_response_multi_sequences(self):
+ '''Similar to the previous case, but with multiple diff seqs.
+
+ '''
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+ answers=[soa_rrset,
+ # removing one A in serial 1230
+ begin_soa_rrset, self._create_a('192.0.2.1'),
+ # adding one A in serial 1231
+ self._create_soa('1231'), self._create_a('192.0.2.2'),
+ # removing one A in serial 1231
+ self._create_soa('1231'), self._create_a('192.0.2.3'),
+ # adding one A in serial 1232
+ self._create_soa('1232'), self._create_a('192.0.2.4'),
+ # removing one A in serial 1232
+ self._create_soa('1232'), self._create_a('192.0.2.5'),
+ # adding one A in serial 1234
+ soa_rrset, self._create_a('192.0.2.6'),
+ soa_rrset])
+ self.conn._handle_xfrin_responses()
+ self.assertEqual(type(XfrinIXFREnd()), type(self.conn.get_xfrstate()))
+ self.assertEqual([], self.conn._datasrc_client.diffs)
+ check_diffs(self.assertEqual,
+ [[('delete', begin_soa_rrset),
+ ('delete', self._create_a('192.0.2.1')),
+ ('add', self._create_soa('1231')),
+ ('add', self._create_a('192.0.2.2'))],
+ [('delete', self._create_soa('1231')),
+ ('delete', self._create_a('192.0.2.3')),
+ ('add', self._create_soa('1232')),
+ ('add', self._create_a('192.0.2.4'))],
+ [('delete', self._create_soa('1232')),
+ ('delete', self._create_a('192.0.2.5')),
+ ('add', soa_rrset),
+ ('add', self._create_a('192.0.2.6'))]],
+ self.conn._datasrc_client.committed_diffs)
+
+ def test_ixfr_response_multi_messages(self):
+ '''Similar to the first case, but RRs span over multiple messages.
+
+ '''
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+ answers=[soa_rrset, begin_soa_rrset, soa_rrset])
+ self.conn.reply_data += self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+ answers=[soa_rrset])
+ self.conn._handle_xfrin_responses()
+ self.assertEqual(type(XfrinIXFREnd()), type(self.conn.get_xfrstate()))
+ check_diffs(self.assertEqual,
+ [[('delete', begin_soa_rrset), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
+
+ def test_ixfr_response_broken(self):
+ '''Test with a broken response.
+
+ '''
+ # SOA sequence is out-of-sync
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+ answers=[soa_rrset, begin_soa_rrset, soa_rrset,
+ self._create_soa('1235')])
+ self.assertRaises(XfrinProtocolError,
+ self.conn._handle_xfrin_responses)
+ # no diffs should have been committed
+ check_diffs(self.assertEqual,
+ [], self.conn._datasrc_client.committed_diffs)
+
+ def test_ixfr_response_extra(self):
+ '''Test with an extra RR after the end of IXFR diff sequences.
+
+ IXFR should be rejected, but complete diff sequences should be
+ committed; it's not clear whether it's compliant to the protocol
+ specification, but it is how BIND 9 works and we do the same.
+ '''
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+ answers=[soa_rrset, begin_soa_rrset, soa_rrset, soa_rrset,
+ self._create_a('192.0.2.1')])
+ self.assertRaises(XfrinProtocolError,
+ self.conn._handle_xfrin_responses)
+ check_diffs(self.assertEqual,
+ [[('delete', begin_soa_rrset), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
+
+ def test_ixfr_to_axfr_response(self):
+ '''AXFR-style IXFR response.
+
+ It simply updates the zone's SOA one time.
+
+ '''
+ ns_rr = self._create_ns()
+ a_rr = self._create_a('192.0.2.1')
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+ answers=[soa_rrset, ns_rr, a_rr, soa_rrset])
+ self.conn._handle_xfrin_responses()
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ self.assertEqual([], self.conn._datasrc_client.diffs)
+ # The SOA should be added exactly once, and in our implementation
+ # it should be added at the end of the sequence.
+ check_diffs(self.assertEqual,
+ [[('add', ns_rr), ('add', a_rr), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
+
+ def test_ixfr_to_axfr_response_mismatch_soa(self):
+ '''AXFR-style IXFR response, but the two SOA are not the same.
+
+ In the current implementation, we accept it and use the second SOA.
+
+ '''
+ ns_rr = self._create_ns()
+ a_rr = self._create_a('192.0.2.1')
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+ answers=[soa_rrset, ns_rr, a_rr, begin_soa_rrset])
+ self.conn._handle_xfrin_responses()
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ self.assertEqual([], self.conn._datasrc_client.diffs)
+ check_diffs(self.assertEqual,
+ [[('add', ns_rr), ('add', a_rr),
+ ('add', begin_soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
+
+ def test_ixfr_to_axfr_response_extra(self):
+ '''Test with an extra RR after the end of AXFR-style IXFR session.
+
+ The session should be rejected, and nothing should be committed.
+
+ '''
+ ns_rr = self._create_ns()
+ a_rr = self._create_a('192.0.2.1')
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+ answers=[soa_rrset, ns_rr, a_rr, soa_rrset, a_rr])
+ self.assertRaises(XfrinProtocolError,
+ self.conn._handle_xfrin_responses)
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ self.assertEqual([], self.conn._datasrc_client.committed_diffs)
+
+class TestIXFRSession(TestXfrinConnection):
+ '''Tests for a full IXFR session (query and response).
+
+ Detailed corner cases should have been covered in test_create_query()
+ and TestIXFRResponse, so we'll only check some typical cases to confirm
+ the general logic flow.
+ '''
+ def setUp(self):
+ super().setUp()
- def _create_broken_response_data(self):
- # This helper method creates a bogus "DNS message" that only contains
- # 4 octets of data. The DNS message parser will raise an exception.
- bogus_data = b'xxxx'
- self.conn.reply_data = struct.pack('H', socket.htons(len(bogus_data)))
- self.conn.reply_data += bogus_data
+ def test_do_xfrin(self):
+ def create_ixfr_response():
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+ RRType.IXFR())],
+ answers=[soa_rrset, begin_soa_rrset, soa_rrset, soa_rrset])
+ self.conn.response_generator = create_ixfr_response
+ self.assertEqual(XFRIN_OK, self.conn.do_xfrin(False, RRType.IXFR()))
+
+ # Check some details of the IXFR protocol processing
+ self.assertEqual(type(XfrinIXFREnd()), type(self.conn.get_xfrstate()))
+ check_diffs(self.assertEqual,
+ [[('delete', begin_soa_rrset), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
+
+ # Check if the query was IXFR.
+ qdata = self.conn.query_data[2:]
+ qmsg = Message(Message.PARSE)
+ qmsg.from_wire(qdata, len(qdata))
+ self.assertEqual(1, qmsg.get_rr_count(Message.SECTION_QUESTION))
+ self.assertEqual(TEST_ZONE_NAME, qmsg.get_question()[0].get_name())
+ self.assertEqual(RRType.IXFR(), qmsg.get_question()[0].get_type())
+
+ def test_do_xfrin_fail(self):
+ '''IXFR fails due to a protocol error.
+
+ '''
+ def create_ixfr_response():
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+ RRType.IXFR())],
+ answers=[soa_rrset, begin_soa_rrset, soa_rrset,
+ self._create_soa('1235')])
+ self.conn.response_generator = create_ixfr_response
+ self.assertEqual(XFRIN_FAIL, self.conn.do_xfrin(False, RRType.IXFR()))
+
+ def test_do_xfrin_fail(self):
+ '''IXFR fails due to a bogus DNS message.
+
+ '''
+ self._create_broken_response_data()
+ self.assertEqual(XFRIN_FAIL, self.conn.do_xfrin(False, RRType.IXFR()))
+
+class TestXFRSessionWithSQLite3(TestXfrinConnection):
+ '''Tests for XFR sessions using an SQLite3 DB.
+
+ These are provided mainly to confirm the implementation actually works
+ in an environment closer to actual operational environments. So we
+ only check a few common cases; other details are tested using mock
+ data sources.
+
+ '''
+ def setUp(self):
+ self.sqlite3db_src = TESTDATA_SRCDIR + '/example.com.sqlite3'
+ self.sqlite3db_obj = TESTDATA_OBJDIR + '/example.com.sqlite3.copy'
+ self.empty_sqlite3db_obj = TESTDATA_OBJDIR + '/empty.sqlite3'
+ self.sqlite3db_cfg = "{ \"database_file\": \"" +\
+ self.sqlite3db_obj + "\"}"
+ super().setUp()
+ if os.path.exists(self.sqlite3db_obj):
+ os.unlink(self.sqlite3db_obj)
+ if os.path.exists(self.empty_sqlite3db_obj):
+ os.unlink(self.empty_sqlite3db_obj)
+ shutil.copyfile(self.sqlite3db_src, self.sqlite3db_obj)
+ self.conn._datasrc_client = DataSourceClient("sqlite3",
+ self.sqlite3db_cfg)
+
+ def tearDown(self):
+ if os.path.exists(self.sqlite3db_obj):
+ os.unlink(self.sqlite3db_obj)
+ if os.path.exists(self.empty_sqlite3db_obj):
+ os.unlink(self.empty_sqlite3db_obj)
+
+ def get_zone_serial(self):
+ result, finder = self.conn._datasrc_client.find_zone(TEST_ZONE_NAME)
+ self.assertEqual(DataSourceClient.SUCCESS, result)
+ result, soa = finder.find(TEST_ZONE_NAME, RRType.SOA(),
+ None, ZoneFinder.FIND_DEFAULT)
+ self.assertEqual(ZoneFinder.SUCCESS, result)
+ self.assertEqual(1, soa.get_rdata_count())
+ return get_soa_serial(soa.get_rdata()[0])
+
+ def record_exist(self, name, type):
+ result, finder = self.conn._datasrc_client.find_zone(TEST_ZONE_NAME)
+ self.assertEqual(DataSourceClient.SUCCESS, result)
+ result, soa = finder.find(name, type, None, ZoneFinder.FIND_DEFAULT)
+ return result == ZoneFinder.SUCCESS
+
+ def test_do_ixfrin_sqlite3(self):
+ def create_ixfr_response():
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+ RRType.IXFR())],
+ answers=[soa_rrset, begin_soa_rrset, soa_rrset, soa_rrset])
+ self.conn.response_generator = create_ixfr_response
+
+ # Confirm xfrin succeeds and SOA is updated
+ self.assertEqual(1230, self.get_zone_serial())
+ self.assertEqual(XFRIN_OK, self.conn.do_xfrin(False, RRType.IXFR()))
+ self.assertEqual(1234, self.get_zone_serial())
+
+ def test_do_ixfrin_sqlite3_fail(self):
+ '''Similar to the previous test, but xfrin fails due to error.
+
+ Check the DB is not changed.
+
+ '''
+ def create_ixfr_response():
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+ RRType.IXFR())],
+ answers=[soa_rrset, begin_soa_rrset, soa_rrset,
+ self._create_soa('1235')])
+ self.conn.response_generator = create_ixfr_response
+
+ self.assertEqual(1230, self.get_zone_serial())
+ self.assertEqual(XFRIN_FAIL, self.conn.do_xfrin(False, RRType.IXFR()))
+ self.assertEqual(1230, self.get_zone_serial())
+
+ def test_do_ixfrin_nozone_sqlite3(self):
+ self.conn._zone_name = Name('nosuchzone.example')
+ self.assertEqual(XFRIN_FAIL, self.conn.do_xfrin(False, RRType.IXFR()))
+ # This should fail even before starting state transition
+ self.assertEqual(None, self.conn.get_xfrstate())
+
+ def axfr_check(self, type):
+ '''Common checks for AXFR and AXFR-style IXFR
+
+ '''
+ def create_response():
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, type)],
+ answers=[soa_rrset, self._create_ns(), soa_rrset])
+ self.conn.response_generator = create_response
+
+ # Confirm xfrin succeeds and SOA is updated, A RR is deleted.
+ self.assertEqual(1230, self.get_zone_serial())
+ self.assertTrue(self.record_exist(Name('dns01.example.com'),
+ RRType.A()))
+ self.assertEqual(XFRIN_OK, self.conn.do_xfrin(False, type))
+ self.assertEqual(1234, self.get_zone_serial())
+ self.assertFalse(self.record_exist(Name('dns01.example.com'),
+ RRType.A()))
+
+ def test_do_ixfrin_axfr_sqlite3(self):
+ '''AXFR-style IXFR.
+
+ '''
+ self.axfr_check(RRType.IXFR())
+
+ def test_do_axfrin_sqlite3(self):
+ '''AXFR.
+
+ '''
+ self.axfr_check(RRType.AXFR())
+
+ def axfr_failure_check(self, type):
+ '''Similar to the previous two tests, but xfrin fails due to error.
+
+ Check the DB is not changed.
+
+ '''
+ def create_response():
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, type)],
+ answers=[soa_rrset, self._create_ns(), soa_rrset, soa_rrset])
+ self.conn.response_generator = create_response
+
+ self.assertEqual(1230, self.get_zone_serial())
+ self.assertTrue(self.record_exist(Name('dns01.example.com'),
+ RRType.A()))
+ self.assertEqual(XFRIN_FAIL, self.conn.do_xfrin(False, type))
+ self.assertEqual(1230, self.get_zone_serial())
+ self.assertTrue(self.record_exist(Name('dns01.example.com'),
+ RRType.A()))
+
+ def test_do_xfrin_axfr_sqlite3_fail(self):
+ '''Failure case for AXFR-style IXFR.
+
+ '''
+ self.axfr_failure_check(RRType.IXFR())
+
+ def test_do_axfrin_sqlite3_fail(self):
+ '''Failure case for AXFR.
+
+ '''
+ self.axfr_failure_check(RRType.AXFR())
+
+ def test_do_axfrin_nozone_sqlite3(self):
+ '''AXFR test with an empty SQLite3 DB file, thus no target zone there.
+
+ For now, we provide backward compatible behavior: xfrin will create
+ the zone (after even setting up the entire schema) in the zone.
+ Note: a future version of this test will make it fail.
+
+ '''
+ self.conn._db_file = self.empty_sqlite3db_obj
+ self.conn._datasrc_client = DataSourceClient(
+ "sqlite3",
+ "{ \"database_file\": \"" + self.empty_sqlite3db_obj + "\"}")
+ def create_response():
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+ RRType.AXFR())],
+ answers=[soa_rrset, self._create_ns(), soa_rrset])
+ self.conn.response_generator = create_response
+ self.conn._zone_name = Name('example.com')
+ self.assertEqual(XFRIN_OK, self.conn.do_xfrin(False, RRType.AXFR()))
+ self.assertEqual(type(XfrinAXFREnd()),
+ type(self.conn.get_xfrstate()))
+ self.assertEqual(1234, self.get_zone_serial())
+ self.assertFalse(self.record_exist(Name('dns01.example.com'),
+ RRType.A()))
class TestXfrinRecorder(unittest.TestCase):
def setUp(self):
@@ -789,6 +1767,8 @@ class TestXfrin(unittest.TestCase):
self.args)['result'][0], 0)
self.assertEqual(self.args['master'], self.xfr.xfrin_started_master_addr)
self.assertEqual(int(self.args['port']), self.xfr.xfrin_started_master_port)
+ # By default we use AXFR (for now)
+ self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
def test_command_handler_retransfer_short_command1(self):
# try it when only specifying the zone name (of unknown zone)
@@ -901,6 +1881,8 @@ class TestXfrin(unittest.TestCase):
self.xfr.xfrin_started_master_addr)
self.assertEqual(int(TEST_MASTER_PORT),
self.xfr.xfrin_started_master_port)
+ # By default we use AXFR (for now)
+ self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
def test_command_handler_notify(self):
# at this level, refresh is no different than retransfer.
@@ -1090,6 +2072,38 @@ class TestXfrin(unittest.TestCase):
# since this has failed, we should still have the previous config
self._check_zones_config(config2)
+ def common_ixfr_setup(self, xfr_mode, ixfr_disabled):
+ # This helper method explicitly sets up a zone configuration with
+ # ixfr_disabled, and invokes either retransfer or refresh.
+ # Shared by some of the following test cases.
+ config = {'zones': [
+ {'name': 'example.com.',
+ 'master_addr': '192.0.2.1',
+ 'ixfr_disabled': ixfr_disabled}]}
+ self.assertEqual(self.xfr.config_handler(config)['result'][0], 0)
+ self.assertEqual(self.xfr.command_handler(xfr_mode,
+ self.args)['result'][0], 0)
+
+ def test_command_handler_retransfer_ixfr_enabled(self):
+ # If IXFR is explicitly enabled in config, IXFR will be used
+ self.common_ixfr_setup('retransfer', False)
+ self.assertEqual(RRType.IXFR(), self.xfr.xfrin_started_request_type)
+
+ def test_command_handler_refresh_ixfr_enabled(self):
+ # Same for refresh
+ self.common_ixfr_setup('refresh', False)
+ self.assertEqual(RRType.IXFR(), self.xfr.xfrin_started_request_type)
+
+ def test_command_handler_retransfer_ixfr_disabled(self):
+ # Similar to the previous case, but explicitly disabled. AXFR should
+ # be used.
+ self.common_ixfr_setup('retransfer', True)
+ self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
+
+ def test_command_handler_refresh_ixfr_disabled(self):
+ # Same for refresh
+ self.common_ixfr_setup('refresh', True)
+ self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
def raise_interrupt():
raise KeyboardInterrupt()
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index a77a383..28d5d50 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -28,7 +28,9 @@ from optparse import OptionParser, OptionValueError
from isc.config.ccsession import *
from isc.notify import notify_out
import isc.util.process
+from isc.datasrc import DataSourceClient, ZoneFinder
import isc.net.parse
+from isc.xfrin.diff import Diff
from isc.log_messages.xfrin_messages import *
isc.log.init("b10-xfrin")
@@ -62,6 +64,9 @@ ZONE_MANAGER_MODULE_NAME = 'Zonemgr'
REFRESH_FROM_ZONEMGR = 'refresh_from_zonemgr'
ZONE_XFRIN_FAILED = 'zone_xfrin_failed'
+# Constants for debug levels, to be removed when we have #1074.
+DBG_XFRIN_TRACE = 3
+
# These two default are currently hard-coded. For config this isn't
# necessary, but we need these defaults for optional command arguments
# (TODO: have similar support to get default values for command
@@ -77,6 +82,11 @@ XFRIN_FAIL = 1
class XfrinException(Exception):
pass
+class XfrinProtocolError(Exception):
+ '''An exception raised for errors encountered in xfrin protocol handling.
+ '''
+ pass
+
class XfrinZoneInfoException(Exception):
"""This exception is raised if there is an error in the given
configuration (part), or when a command does not have a required
@@ -112,29 +122,358 @@ def _check_zone_class(zone_class_str):
except InvalidRRClass as irce:
raise XfrinZoneInfoException("bad zone class: " + zone_class_str + " (" + str(irce) + ")")
+def get_soa_serial(soa_rdata):
+ '''Extract the serial field of an SOA RDATA and returns it as an intger.
+
+ We don't have to be very efficient here, so we first dump the entire RDATA
+ as a string and convert the first corresponding field. This should be
+ sufficient in practice, but may not always work when the MNAME or RNAME
+ contains an (escaped) space character in their labels. Ideally there
+ should be a more direct and convenient way to get access to the SOA
+ fields.
+ '''
+ return int(soa_rdata.to_text().split()[2])
+
+class XfrinState:
+ '''
+ The states of the incomding *XFR state machine.
+
+ We (will) handle both IXFR and AXFR with a single integrated state
+ machine because they cannot be distinguished immediately - an AXFR
+ response to an IXFR request can only be detected when the first two (2)
+ response RRs have already been received.
+
+ The following diagram summarizes the state transition. After sending
+ the query, xfrin starts the process with the InitialSOA state (all
+ IXFR/AXFR response begins with an SOA). When it reaches IXFREnd
+ or AXFREnd, the process successfully completes.
+
+ (AXFR or
+ (recv SOA) AXFR-style IXFR) (SOA, add)
+ InitialSOA------->FirstData------------->AXFR--------->AXFREnd
+ | | ^ (post xfr
+ | | | checks, then
+ | +--+ commit)
+ | (non SOA, add)
+ |
+ | (non SOA, delete)
+ (pure IXFR,| +-------+
+ keep handling)| (Delete SOA) V |
+ + ->IXFRDeleteSOA------>IXFRDelete--+
+ ^ |
+ (see SOA, not end, | (see SOA)|
+ commit, keep handling) | |
+ | V
+ +---------IXFRAdd<----------+IXFRAddSOA
+ (non SOA, add)| ^ | (Add SOA)
+ ----------+ |
+ |(see SOA w/ end serial, commit changes)
+ V
+ IXFREnd
+
+ Note that changes are committed for every "difference sequence"
+ (i.e. changes for one SOA update). This means when an IXFR response
+ contains multiple difference sequences and something goes wrong
+ after several commits, these changes have been published and visible
+ to clients even if the IXFR session is subsequently aborted.
+ It is not clear if this is valid in terms of the protocol specification.
+ Section 4 of RFC 1995 states:
+
+ An IXFR client, should only replace an older version with a newer
+ version after all the differences have been successfully processed.
+
+ If this "replacement" is for the changes of one difference sequence
+ and "all the differences" mean the changes for that sequence, this
+ implementation strictly follows what RFC states. If this is for
+ the entire IXFR response (that may contain multiple sequences),
+ we should implement it with one big transaction and one final commit
+ at the very end.
+
+ For now, we implement it with multiple smaller commits for two
+ reasons. First, this is what BIND 9 does, and we generally port
+ the implementation logic here. BIND 9 has been supporting IXFR
+ for many years, so the fact that it still behaves this way
+ probably means it at least doesn't cause a severe operational
+ problem in practice. Second, especially because BIND 10 would
+ often uses a database backend, a larger transaction could cause an
+ undesirable effects, e.g. suspending normal lookups for a longer
+ period depending on the characteristics of the database. Even if
+ we find something wrong in a later sequeunce and abort the
+ session, we can start another incremental update from what has
+ been validated, or we can switch to AXFR to replace the zone
+ completely.
+
+ This implementation uses the state design pattern, where each state
+ is represented as a subclass of the base XfrinState class. Each concrete
+ subclass of XfrinState is assumed to define two methods: handle_rr() and
+ finish_message(). These methods handle specific part of XFR protocols
+ and (if necessary) perform the state transition.
+
+ Conceptually, XfrinState and its subclasses are a "friend" of
+ XfrinConnection and are assumed to be allowed to access its internal
+ information (even though Python does not have a strict access control
+ between different classes).
+
+ The XfrinState and its subclasses are designed to be stateless, and
+ can be used as singleton objects. For now, however, we always instantiate
+ a new object for every state transition, partly because the introduction
+ of singleton will make a code bit complicated, and partly because
+ the overhead of object instantiotion wouldn't be significant for xfrin.
+
+ '''
+ def set_xfrstate(self, conn, new_state):
+ '''Set the XfrConnection to a given new state.
+
+ As a "friend" class, this method intentionally gets access to the
+ connection's "private" method.
+
+ '''
+ conn._XfrinConnection__set_xfrstate(new_state)
+
+ def handle_rr(self, conn):
+ '''Handle one RR of an XFR response message.
+
+ Depending on the state, the RR is generally added or deleted in the
+ corresponding data source, or in some special cases indicates
+ a specifi transition, such as starting a new IXFR difference
+ sequence or completing the session.
+
+ All subclass has their specific behaviors for this method, so
+ there is no default definition. If the base class version
+ is called, it's a bug of the caller, and it's notified via
+ an XfrinException exception.
+
+ This method returns a boolean value: True if the given RR was
+ fully handled and the caller should go to the next RR; False
+ if the caller needs to call this method with the (possibly) new
+ state for the same RR again.
+
+ '''
+ raise XfrinException("Internal bug: " +
+ "XfrinState.handle_rr() called directly")
+
+ def finish_message(self, conn):
+ '''Perform any final processing after handling all RRs of a response.
+
+ This method then returns a boolean indicating whether to continue
+ receiving the message. Unless it's in the end of the entire XFR
+ session, we should continue, so this default method simply returns
+ True.
+
+ '''
+ return True
+
+class XfrinInitialSOA(XfrinState):
+ def handle_rr(self, conn, rr):
+ if rr.get_type() != RRType.SOA():
+ raise XfrinProtocolError('First RR in zone transfer must be SOA ('
+ + rr.get_type().to_text() + ' received)')
+ conn._end_serial = get_soa_serial(rr.get_rdata()[0])
+
+ # FIXME: we need to check the serial is actually greater than ours.
+ # To do so, however, we need to implement serial number arithmetic.
+ # Although it wouldn't be a big task, we'll leave it for a separate
+ # task for now. (Always performing xfr could be inefficient, but
+ # shouldn't do any harm otherwise)
+
+ self.set_xfrstate(conn, XfrinFirstData())
+ return True
+
+class XfrinFirstData(XfrinState):
+ def handle_rr(self, conn, rr):
+ '''Handle the first RR after initial SOA in an XFR session.
+
+ This state happens exactly once in an XFR session, where
+ we decide whether it's incremental update ("real" IXFR) or
+ non incremental update (AXFR or AXFR-style IXFR).
+ If we initiated IXFR and the transfer begins with two SOAs
+ (the serial of the second one being equal to our serial),
+ it's incremental; otherwise it's non incremental.
+
+ This method always return False (unlike many other handle_rr()
+ methods) because this first RR must be examined again in the
+ determined update context.
+
+ Note that in the non incremental case the RR should normally be
+ something other SOA, but it's still possible it's an SOA with a
+ different serial than ours. The only possible interpretation at
+ this point is that it's non incremental update that only consists
+ of the SOA RR. It will result in broken zone (for example, it
+ wouldn't even contain an apex NS) and should be rejected at post
+ XFR processing, but in terms of the XFR session processing we
+ accept it and move forward.
+
+ Note further that, in the half-broken SOA-only transfer case,
+ these two SOAs are supposed to be the same as stated in Section 2.2
+ of RFC 5936. We don't check that condition here, either; we'll
+ leave whether and how to deal with that situation to the end of
+ the processing of non incremental update. See also a related
+ discussion at the IETF dnsext wg:
+ http://www.ietf.org/mail-archive/web/dnsext/current/msg07908.html
+
+ '''
+ if conn._request_type == RRType.IXFR() and \
+ rr.get_type() == RRType.SOA() and \
+ conn._request_serial == get_soa_serial(rr.get_rdata()[0]):
+ logger.debug(DBG_XFRIN_TRACE, XFRIN_GOT_INCREMENTAL_RESP,
+ conn.zone_str())
+ self.set_xfrstate(conn, XfrinIXFRDeleteSOA())
+ else:
+ logger.debug(DBG_XFRIN_TRACE, XFRIN_GOT_NONINCREMENTAL_RESP,
+ conn.zone_str())
+ # We are now going to add RRs to the new zone. We need create
+ # a Diff object. It will be used throughtout the XFR session.
+ conn._diff = Diff(conn._datasrc_client, conn._zone_name, True)
+ self.set_xfrstate(conn, XfrinAXFR())
+ return False
+
+class XfrinIXFRDeleteSOA(XfrinState):
+ def handle_rr(self, conn, rr):
+ if rr.get_type() != RRType.SOA():
+ # this shouldn't happen; should this occur it means an internal
+ # bug.
+ raise XfrinException(rr.get_type().to_text() +
+ ' RR is given in IXFRDeleteSOA state')
+ # This is the beginning state of one difference sequence (changes
+ # for one SOA update). We need to create a new Diff object now.
+ conn._diff = Diff(conn._datasrc_client, conn._zone_name)
+ conn._diff.delete_data(rr)
+ self.set_xfrstate(conn, XfrinIXFRDelete())
+ return True
+
+class XfrinIXFRDelete(XfrinState):
+ def handle_rr(self, conn, rr):
+ if rr.get_type() == RRType.SOA():
+ # This is the only place where current_serial is set
+ conn._current_serial = get_soa_serial(rr.get_rdata()[0])
+ self.set_xfrstate(conn, XfrinIXFRAddSOA())
+ return False
+ conn._diff.delete_data(rr)
+ return True
+
+class XfrinIXFRAddSOA(XfrinState):
+ def handle_rr(self, conn, rr):
+ if rr.get_type() != RRType.SOA():
+ # this shouldn't happen; should this occur it means an internal
+ # bug.
+ raise XfrinException(rr.get_type().to_text() +
+ ' RR is given in IXFRAddSOA state')
+ conn._diff.add_data(rr)
+ self.set_xfrstate(conn, XfrinIXFRAdd())
+ return True
+
+class XfrinIXFRAdd(XfrinState):
+ def handle_rr(self, conn, rr):
+ if rr.get_type() == RRType.SOA():
+ soa_serial = get_soa_serial(rr.get_rdata()[0])
+ if soa_serial == conn._end_serial:
+ conn._diff.commit()
+ self.set_xfrstate(conn, XfrinIXFREnd())
+ return True
+ elif soa_serial != conn._current_serial:
+ raise XfrinProtocolError('IXFR out of sync: expected ' +
+ 'serial ' +
+ str(conn._current_serial) +
+ ', got ' + str(soa_serial))
+ else:
+ conn._diff.commit()
+ self.set_xfrstate(conn, XfrinIXFRDeleteSOA())
+ return False
+ conn._diff.add_data(rr)
+ return True
+
+class XfrinIXFREnd(XfrinState):
+ def handle_rr(self, conn, rr):
+ raise XfrinProtocolError('Extra data after the end of IXFR diffs: ' +
+ rr.to_text())
+
+ def finish_message(self, conn):
+ '''Final processing after processing an entire IXFR session.
+
+ There will be more actions here, but for now we simply return False,
+ indicating there will be no more message to receive.
+
+ '''
+ return False
+
+class XfrinAXFR(XfrinState):
+ def handle_rr(self, conn, rr):
+ """
+ Handle the RR by putting it into the zone.
+ """
+ conn._diff.add_data(rr)
+ if rr.get_type() == RRType.SOA():
+ # SOA means end. Don't commit it yet - we need to perform
+ # post-transfer checks
+
+ soa_serial = get_soa_serial(rr.get_rdata()[0])
+ if conn._end_serial != soa_serial:
+ logger.warn(XFRIN_AXFR_INCONSISTENT_SOA, conn.zone_str(),
+ conn._end_serial, soa_serial)
+
+ self.set_xfrstate(conn, XfrinAXFREnd())
+ # Yes, we've eaten this RR.
+ return True
+
+class XfrinAXFREnd(XfrinState):
+ def handle_rr(self, conn, rr):
+ raise XfrinProtocolError('Extra data after the end of AXFR: ' +
+ rr.to_text())
+
+ def finish_message(self, conn):
+ """
+ Final processing after processing an entire AXFR session.
+
+ In this process all the AXFR changes are committed to the
+ data source.
+
+ There might be more actions here, but for now we simply return False,
+ indicating there will be no more message to receive.
+
+ """
+ conn._diff.commit()
+ return False
+
class XfrinConnection(asyncore.dispatcher):
'''Do xfrin in this class. '''
def __init__(self,
- sock_map, zone_name, rrclass, db_file, shutdown_event,
- master_addrinfo, tsig_key = None, verbose = False,
- idle_timeout = 60):
- ''' idle_timeout: max idle time for read data from socket.
- db_file: specify the data source file.
- check_soa: when it's true, check soa first before sending xfr query
+ sock_map, zone_name, rrclass, datasrc_client,
+ shutdown_event, master_addrinfo, tsig_key=None,
+ idle_timeout=60):
+ '''Constructor of the XfirnConnection class.
+
+ idle_timeout: max idle time for read data from socket.
+ datasrc_client: the data source client object used for the XFR session.
+ This will eventually replace db_file completely.
+
'''
asyncore.dispatcher.__init__(self, map=sock_map)
- self.create_socket(master_addrinfo[0], master_addrinfo[1])
+
+ # The XFR state. Conceptually this is purely private, so we emphasize
+ # the fact by the double underscore. Other classes are assumed to
+ # get access to this via get_xfrstate(), and only XfrinState classes
+ # are assumed to be allowed to modify it via __set_xfrstate().
+ self.__state = None
+
+ # Requested transfer type (RRType.AXFR or RRType.IXFR). The actual
+ # transfer type may differ due to IXFR->AXFR fallback:
+ self._request_type = None
+
+ # Zone parameters
self._zone_name = zone_name
- self._sock_map = sock_map
self._rrclass = rrclass
- self._db_file = db_file
+
+ # Data source handler
+ self._datasrc_client = datasrc_client
+
+ self.create_socket(master_addrinfo[0], master_addrinfo[1])
+ self._sock_map = sock_map
self._soa_rr_count = 0
self._idle_timeout = idle_timeout
self.setblocking(1)
self._shutdown_event = shutdown_event
- self._verbose = verbose
self._master_address = master_addrinfo[2]
self._tsig_key = tsig_key
self._tsig_ctx = None
@@ -145,6 +484,16 @@ class XfrinConnection(asyncore.dispatcher):
def __create_tsig_ctx(self, key):
return TSIGContext(key)
+ def __set_xfrstate(self, new_state):
+ self.__state = new_state
+
+ def get_xfrstate(self):
+ return self.__state
+
+ def zone_str(self):
+ '''A convenient function for logging to include zone name and class'''
+ return self._zone_name.to_text() + '/' + str(self._rrclass)
+
def connect_to_master(self):
'''Connect to master in TCP.'''
@@ -155,17 +504,67 @@ class XfrinConnection(asyncore.dispatcher):
logger.error(XFRIN_CONNECT_MASTER, self._master_address, str(e))
return False
+ def _get_zone_soa(self):
+ result, finder = self._datasrc_client.find_zone(self._zone_name)
+ if result != DataSourceClient.SUCCESS:
+ raise XfrinException('Zone not found in the given data ' +
+ 'source: ' + self.zone_str())
+ result, soa_rrset = finder.find(self._zone_name, RRType.SOA(),
+ None, ZoneFinder.FIND_DEFAULT)
+ if result != ZoneFinder.SUCCESS:
+ raise XfrinException('SOA RR not found in zone: ' +
+ self.zone_str())
+ # Especially for database-based zones, a working zone may be in
+ # a broken state where it has more than one SOA RR. We proactively
+ # check the condition and abort the xfr attempt if we identify it.
+ if soa_rrset.get_rdata_count() != 1:
+ raise XfrinException('Invalid number of SOA RRs for ' +
+ self.zone_str() + ': ' +
+ str(soa_rrset.get_rdata_count()))
+ return soa_rrset
+
def _create_query(self, query_type):
- '''Create dns query message. '''
+ '''Create an XFR-related query message.
+
+ query_type is either SOA, AXFR or IXFR. For type IXFR, it searches
+ the associated data source for the current SOA record to include
+ it in the query. If the corresponding zone or the SOA record
+ cannot be found, it raises an XfrinException exception. Note that
+ this may not necessarily a broken configuration; for the first attempt
+ of transfer the secondary may not have any boot-strap zone
+ information, in which case IXFR simply won't work. The xfrin
+ should then fall back to AXFR. _request_serial is recorded for
+ later use.
+ '''
msg = Message(Message.RENDER)
query_id = random.randint(0, 0xFFFF)
self._query_id = query_id
msg.set_qid(query_id)
msg.set_opcode(Opcode.QUERY())
msg.set_rcode(Rcode.NOERROR())
- query_question = Question(Name(self._zone_name), self._rrclass, query_type)
- msg.add_question(query_question)
+ msg.add_question(Question(self._zone_name, self._rrclass, query_type))
+ if query_type == RRType.IXFR():
+ # get the zone finder. this must be SUCCESS (not even
+ # PARTIALMATCH) because we are specifying the zone origin name.
+ zone_soa_rr = self._get_zone_soa()
+ msg.add_rrset(Message.SECTION_AUTHORITY, zone_soa_rr)
+ self._request_serial = get_soa_serial(zone_soa_rr.get_rdata()[0])
+ else:
+ # For AXFR, we temporarily provide backward compatible behavior
+ # where xfrin is responsible for creating zone in the corresponding
+ # DB table. Note that the code below uses the old data source
+ # API and assumes SQLite3 in an ugly manner. We'll have to
+ # develop a better way of managing zones in a generic way and
+ # eliminate the code like the one here.
+ try:
+ self._get_zone_soa()
+ except XfrinException:
+ def empty_rr_generator():
+ return []
+ isc.datasrc.sqlite3_ds.load(self._db_file,
+ self._zone_name.to_text(),
+ empty_rr_generator)
return msg
def _send_data(self, data):
@@ -256,39 +655,49 @@ class XfrinConnection(asyncore.dispatcher):
# now.
return XFRIN_OK
- def do_xfrin(self, check_soa, ixfr_first = False):
- '''Do xfr by sending xfr request and parsing response. '''
+ def do_xfrin(self, check_soa, request_type=RRType.AXFR()):
+ '''Do an xfr session by sending xfr request and parsing responses.'''
try:
ret = XFRIN_OK
+ self._request_type = request_type
+ # Right now RRType.[IA]XFR().to_text() is 'TYPExxx', so we need
+ # to hardcode here.
+ request_str = 'IXFR' if request_type == RRType.IXFR() else 'AXFR'
if check_soa:
- logstr = 'SOA check for \'%s\' ' % self._zone_name
ret = self._check_soa_serial()
if ret == XFRIN_OK:
- logger.info(XFRIN_AXFR_TRANSFER_STARTED, self._zone_name)
- self._send_query(RRType.AXFR())
- isc.datasrc.sqlite3_ds.load(self._db_file, self._zone_name,
- self._handle_xfrin_response)
-
- logger.info(XFRIN_AXFR_TRANSFER_SUCCESS, self._zone_name)
-
- except XfrinException as e:
- logger.error(XFRIN_AXFR_TRANSFER_FAILURE, self._zone_name, str(e))
- ret = XFRIN_FAIL
- #TODO, recover data source.
- except isc.datasrc.sqlite3_ds.Sqlite3DSError as e:
- logger.error(XFRIN_AXFR_DATABASE_FAILURE, self._zone_name, str(e))
+ logger.info(XFRIN_XFR_TRANSFER_STARTED, request_str,
+ self.zone_str())
+ self._send_query(self._request_type)
+ self.__state = XfrinInitialSOA()
+ self._handle_xfrin_responses()
+ logger.info(XFRIN_XFR_TRANSFER_SUCCESS, request_str,
+ self.zone_str())
+
+ except (XfrinException, XfrinProtocolError) as e:
+ logger.error(XFRIN_XFR_TRANSFER_FAILURE, request_str,
+ self.zone_str(), str(e))
ret = XFRIN_FAIL
- except UserWarning as e:
- # XXX: this is an exception from our C++ library via the
- # Boost.Python binding. It would be better to have more more
- # specific exceptions, but at this moment this is the finest
- # granularity.
- logger.error(XFRIN_AXFR_INTERNAL_FAILURE, self._zone_name, str(e))
+ except Exception as e:
+ # Catching all possible exceptions like this is generally not a
+ # good practice, but handling an xfr session could result in
+ # so many types of exceptions, including ones from the DNS library
+ # or from the data source library. Eventually we'd introduce a
+ # hierarchy for exception classes from a base "ISC exception" and
+ # catch it here, but until then we need broadest coverage so that
+ # we won't miss anything.
+
+ logger.error(XFRIN_XFR_OTHER_FAILURE, request_str,
+ self.zone_str(), str(e))
ret = XFRIN_FAIL
finally:
- self.close()
+ # Make sure any remaining transaction in the diff is closed
+ # (if not yet - possible in case of xfr-level exception) as soon
+ # as possible
+ self._diff = None
+ self.close()
return ret
@@ -318,9 +727,6 @@ class XfrinConnection(asyncore.dispatcher):
self._check_response_header(msg)
- if msg.get_rr_count(Message.SECTION_ANSWER) == 0:
- raise XfrinException('answer section is empty')
-
if msg.get_rr_count(Message.SECTION_QUESTION) > 1:
raise XfrinException('query section count greater than 1')
@@ -351,14 +757,14 @@ class XfrinConnection(asyncore.dispatcher):
yield (rrset_name, rrset_ttl, rrset_class, rrset_type,
rdata_text)
- def _handle_xfrin_response(self):
- '''Return a generator for the response to a zone transfer. '''
- while True:
+ def _handle_xfrin_responses(self):
+ read_next_msg = True
+ while read_next_msg:
data_len = self._get_request_response(2)
msg_len = socket.htons(struct.unpack('H', data_len)[0])
recvdata = self._get_request_response(msg_len)
msg = Message(Message.PARSE)
- msg.from_wire(recvdata)
+ msg.from_wire(recvdata, Message.PRESERVE_ORDER)
# TSIG related checks, including an unexpected signed response
self._check_response_tsig(msg, recvdata)
@@ -366,12 +772,12 @@ class XfrinConnection(asyncore.dispatcher):
# Perform response status validation
self._check_response_status(msg)
- answer_section = msg.get_section(Message.SECTION_ANSWER)
- for rr in self._handle_answer_section(answer_section):
- yield rr
+ for rr in msg.get_section(Message.SECTION_ANSWER):
+ rr_handled = False
+ while not rr_handled:
+ rr_handled = self.__state.handle_rr(self, rr)
- if self._soa_rr_count == 2:
- break
+ read_next_msg = self.__state.finish_message(self)
if self._shutdown_event.is_set():
raise XfrinException('xfrin is forced to stop')
@@ -393,16 +799,35 @@ class XfrinConnection(asyncore.dispatcher):
pass
def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file,
- shutdown_event, master_addrinfo, check_soa, verbose,
- tsig_key):
+ shutdown_event, master_addrinfo, check_soa, tsig_key,
+ request_type):
xfrin_recorder.increment(zone_name)
+
+ # Create a data source client used in this XFR session. Right now we
+ # still assume an sqlite3-based data source, and use both the old and new
+ # data source APIs. We also need to use a mock client for tests.
+ # For a temporary workaround to deal with these situations, we skip the
+ # creation when the given file is none (the test case). Eventually
+ # this code will be much cleaner.
+ datasrc_client = None
+ if db_file is not None:
+ # temporary hardcoded sqlite initialization. Once we decide on
+ # the config specification, we need to update this (TODO)
+ # this may depend on #1207, or any followup ticket created for #1207
+ datasrc_type = "sqlite3"
+ datasrc_config = "{ \"database_file\": \"" + db_file + "\"}"
+ datasrc_client = DataSourceClient(datasrc_type, datasrc_config)
+
+ # Create a TCP connection for the XFR session and perform the operation.
sock_map = {}
- conn = XfrinConnection(sock_map, zone_name, rrclass, db_file,
- shutdown_event, master_addrinfo,
- tsig_key, verbose)
+ conn = XfrinConnection(sock_map, zone_name, rrclass, datasrc_client,
+ shutdown_event, master_addrinfo, tsig_key)
+ # XXX: We still need _db_file for temporary workaround in _create_query().
+ # This should be removed when we eliminate the need for the workaround.
+ conn._db_file = db_file
ret = XFRIN_FAIL
if conn.connect_to_master():
- ret = conn.do_xfrin(check_soa)
+ ret = conn.do_xfrin(check_soa, request_type)
# Publish the zone transfer result news, so zonemgr can reset the
# zone timer, and xfrout can notify the zone's slaves if the result
@@ -541,13 +966,12 @@ class ZoneInfo:
(str(self.master_addr), self.master_port))
class Xfrin:
- def __init__(self, verbose = False):
+ def __init__(self):
self._max_transfers_in = 10
self._zones = {}
self._cc_setup()
self.recorder = XfrinRecorder()
self._shutdown_event = threading.Event()
- self._verbose = verbose
def _cc_setup(self):
'''This method is used only as part of initialization, but is
@@ -646,7 +1070,7 @@ class Xfrin:
rrclass,
self._get_db_file(),
master_addr,
- zone_info.tsig_key,
+ zone_info.tsig_key, RRType.AXFR(),
True)
answer = create_answer(ret[0], ret[1])
@@ -659,14 +1083,17 @@ class Xfrin:
rrclass)
zone_info = self._get_zone_info(zone_name, rrclass)
tsig_key = None
+ request_type = RRType.AXFR()
if zone_info:
tsig_key = zone_info.tsig_key
+ if not zone_info.ixfr_disabled:
+ request_type = RRType.IXFR()
db_file = args.get('db_file') or self._get_db_file()
ret = self.xfrin_start(zone_name,
rrclass,
db_file,
master_addr,
- tsig_key,
+ tsig_key, request_type,
(False if command == 'retransfer' else True))
answer = create_answer(ret[0], ret[1])
@@ -746,7 +1173,8 @@ class Xfrin:
news(command: zone_new_data_ready) to zone manager and xfrout.
if xfrin failed, just tell the bad news to zone manager, so that
it can reset the refresh timer for that zone. '''
- param = {'zone_name': zone_name, 'zone_class': zone_class.to_text()}
+ param = {'zone_name': zone_name.to_text(),
+ 'zone_class': zone_class.to_text()}
if xfr_result == XFRIN_OK:
msg = create_command(notify_out.ZONE_NEW_DATA_READY_CMD, param)
# catch the exception, in case msgq has been killed.
@@ -783,8 +1211,8 @@ class Xfrin:
while not self._shutdown_event.is_set():
self._cc_check_command()
- def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo, tsig_key,
- check_soa = True):
+ def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo,
+ tsig_key, request_type, check_soa=True):
if "pydnspp" not in sys.modules:
return (1, "xfrin failed, can't load dns message python library: 'pydnspp'")
@@ -798,13 +1226,12 @@ class Xfrin:
xfrin_thread = threading.Thread(target = process_xfrin,
args = (self,
self.recorder,
- zone_name.to_text(),
+ zone_name,
rrclass,
db_file,
self._shutdown_event,
master_addrinfo, check_soa,
- self._verbose,
- tsig_key))
+ tsig_key, request_type))
xfrin_thread.start()
return (0, 'zone xfrin is started')
@@ -823,9 +1250,9 @@ def set_signal_handler():
def set_cmd_options(parser):
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
- help="display more about what is going on")
+ help="This option is obsolete and has no effect.")
-def main(xfrin_class, use_signal = True):
+def main(xfrin_class, use_signal=True):
"""The main loop of the Xfrin daemon.
@param xfrin_class: A class of the Xfrin object. This is normally Xfrin,
@@ -842,7 +1269,7 @@ def main(xfrin_class, use_signal = True):
if use_signal:
set_signal_handler()
- xfrind = xfrin_class(verbose = options.verbose)
+ xfrind = xfrin_class()
xfrind.startup()
except KeyboardInterrupt:
logger.info(XFRIN_STOPPED_BY_KEYBOARD)
diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes
index 80a0be3..a5bbdf7 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -15,25 +15,26 @@
# No namespace declaration - these constants go in the global namespace
# of the xfrin messages python module.
-% XFRIN_AXFR_INTERNAL_FAILURE AXFR transfer of zone %1 failed: %2
-The AXFR transfer for the given zone has failed due to an internal
-problem in the bind10 python wrapper library.
-The error is shown in the log message.
+% XFRIN_XFR_OTHER_FAILURE %1 transfer of zone %2 failed: %3
+The XFR transfer for the given zone has failed due to a problem outside
+of the xfrin module. Possible reasons are a broken DNS message or failure
+in database connection. The error is shown in the log message.
% XFRIN_AXFR_DATABASE_FAILURE AXFR transfer of zone %1 failed: %2
The AXFR transfer for the given zone has failed due to a database problem.
-The error is shown in the log message.
+The error is shown in the log message. Note: due to the code structure
+this can only happen for AXFR.
-% XFRIN_AXFR_TRANSFER_FAILURE AXFR transfer of zone %1 failed: %2
-The AXFR transfer for the given zone has failed due to a protocol error.
+% XFRIN_XFR_TRANSFER_FAILURE %1 transfer of zone %2 failed: %3
+The XFR transfer for the given zone has failed due to a protocol error.
The error is shown in the log message.
-% XFRIN_AXFR_TRANSFER_STARTED AXFR transfer of zone %1 started
+% XFRIN_XFR_TRANSFER_STARTED %1 transfer of zone %2 started
A connection to the master server has been made, the serial value in
the SOA record has been checked, and a zone transfer has been started.
-% XFRIN_AXFR_TRANSFER_SUCCESS AXFR transfer of zone %1 succeeded
-The AXFR transfer of the given zone was successfully completed.
+% XFRIN_XFR_TRANSFER_SUCCESS %1 transfer of zone %2 succeeded
+The XFR transfer of the given zone was successfully completed.
% XFRIN_BAD_MASTER_ADDR_FORMAT bad format for master address: %1
The given master address is not a valid IP address.
@@ -89,3 +90,32 @@ daemon will now shut down.
% XFRIN_UNKNOWN_ERROR unknown error: %1
An uncaught exception was raised while running the xfrin daemon. The
exception message is printed in the log message.
+
+% XFRIN_GOT_INCREMENTAL_RESP got incremental response for %1
+In an attempt of IXFR processing, the begenning SOA of the first difference
+(following the initial SOA that specified the final SOA for all the
+differences) was found. This means a connection for xfrin tried IXFR
+and really aot a response for incremental updates.
+
+% XFRIN_GOT_NONINCREMENTAL_RESP got nonincremental response for %1
+Non incremental transfer was detected at the "first data" of a transfer,
+which is the RR following the initial SOA. Non incremental transfer is
+either AXFR or AXFR-style IXFR. In the latter case, it means that
+in a response to IXFR query the first data is not SOA or its SOA serial
+is not equal to the requested SOA serial.
+
+% XFRIN_AXFR_INCONSISTENT_SOA AXFR SOAs are inconsistent for %1: %2 expected, %3 received
+The serial fields of the first and last SOAs of AXFR (including AXFR-style
+IXFR) are not the same. According to RFC 5936 these two SOAs must be the
+"same" (not only for the serial), but it is still not clear what the
+receiver should do if this condition does not hold. There was a discussion
+about this at the IETF dnsext wg:
+http://www.ietf.org/mail-archive/web/dnsext/current/msg07908.html
+and the general feeling seems that it would be better to reject the
+transfer if a mismatch is detected. On the other hand, also as noted
+in that email thread, neither BIND 9 nor NSD performs any comparison
+on the SOAs. For now, we only check the serials (ignoring other fields)
+and only leave a warning log message when a mismatch is found. If it
+turns out to happen with a real world primary server implementation
+and that server actually feeds broken data (e.g. mixed versions of
+zone), we can consider a stricter action.
diff --git a/src/cppcheck-suppress.lst b/src/cppcheck-suppress.lst
index 8a4c7c1..1020ffe 100644
--- a/src/cppcheck-suppress.lst
+++ b/src/cppcheck-suppress.lst
@@ -8,3 +8,4 @@ unreadVariable:src/lib/dns/rdata/template.cc:61
selfAssignment:src/lib/dns/tests/name_unittest.cc:293
selfAssignment:src/lib/dns/tests/rdata_unittest.cc:228
selfAssignment:src/lib/dns/tests/tsigkey_unittest.cc:137
+selfAssignment:src/lib/dns/tests/rdata_txt_like_unittest.cc:222
diff --git a/src/lib/acl/tests/Makefile.am b/src/lib/acl/tests/Makefile.am
index 6369511..2074c64 100644
--- a/src/lib/acl/tests/Makefile.am
+++ b/src/lib/acl/tests/Makefile.am
@@ -37,4 +37,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
run_unittests_LDADD += $(top_builddir)/src/lib/acl/libdnsacl.la
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
diff --git a/src/lib/asiodns/tests/Makefile.am b/src/lib/asiodns/tests/Makefile.am
index f49d485..5a45430 100644
--- a/src/lib/asiodns/tests/Makefile.am
+++ b/src/lib/asiodns/tests/Makefile.am
@@ -47,4 +47,4 @@ run_unittests_CXXFLAGS += -Wno-error
endif
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
diff --git a/src/lib/asiolink/io_address.cc b/src/lib/asiolink/io_address.cc
index 7f7a6fc..7e2f5d4 100644
--- a/src/lib/asiolink/io_address.cc
+++ b/src/lib/asiolink/io_address.cc
@@ -63,5 +63,10 @@ IOAddress::getFamily() const {
}
}
+const asio::ip::address&
+IOAddress::getAddress() const {
+ return asio_address_;
+}
+
} // namespace asiolink
} // namespace isc
diff --git a/src/lib/asiolink/io_address.h b/src/lib/asiolink/io_address.h
index 655b727..1b488fa 100644
--- a/src/lib/asiolink/io_address.h
+++ b/src/lib/asiolink/io_address.h
@@ -74,6 +74,14 @@ public:
/// \return A string representation of the address.
std::string toText() const;
+ /// \brief Returns const reference to the underlying address object.
+ ///
+ /// This is useful, when access to interface offerted by
+ // asio::ip::address_v4 and asio::ip::address_v6 is beneficial.
+ ///
+ /// \return A const reference to asio::ip::address object
+ const asio::ip::address& getAddress() const;
+
/// \brief Returns the address family
///
/// \return AF_INET for IPv4 or AF_INET6 for IPv6.
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index 984cf07..94643c0 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -53,4 +53,4 @@ run_unittests_CXXFLAGS += -Wno-error
endif
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
diff --git a/src/lib/asiolink/tests/io_address_unittest.cc b/src/lib/asiolink/tests/io_address_unittest.cc
index 18b181e..56368a1 100644
--- a/src/lib/asiolink/tests/io_address_unittest.cc
+++ b/src/lib/asiolink/tests/io_address_unittest.cc
@@ -18,6 +18,8 @@
#include <asiolink/io_error.h>
#include <asiolink/io_address.h>
+#include <cstring>
+
using namespace isc::asiolink;
TEST(IOAddressTest, fromText) {
diff --git a/src/lib/asiolink/tests/io_endpoint_unittest.cc b/src/lib/asiolink/tests/io_endpoint_unittest.cc
index f0279d1..c7283ec 100644
--- a/src/lib/asiolink/tests/io_endpoint_unittest.cc
+++ b/src/lib/asiolink/tests/io_endpoint_unittest.cc
@@ -219,7 +219,7 @@ sockAddrMatch(const struct sockaddr& actual_sa,
res->ai_addr->sa_len = actual_sa.sa_len;
#endif
EXPECT_EQ(0, memcmp(res->ai_addr, &actual_sa, res->ai_addrlen));
- free(res);
+ freeaddrinfo(res);
}
TEST(IOEndpointTest, getSockAddr) {
diff --git a/src/lib/bench/tests/Makefile.am b/src/lib/bench/tests/Makefile.am
index 3f8a678..2a6e2c6 100644
--- a/src/lib/bench/tests/Makefile.am
+++ b/src/lib/bench/tests/Makefile.am
@@ -22,6 +22,6 @@ run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
run_unittests_LDADD += $(GTEST_LDADD)
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
EXTRA_DIST = testdata/query.txt
diff --git a/src/lib/cache/tests/Makefile.am b/src/lib/cache/tests/Makefile.am
index a215c56..0b95036 100644
--- a/src/lib/cache/tests/Makefile.am
+++ b/src/lib/cache/tests/Makefile.am
@@ -62,7 +62,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
EXTRA_DIST = testdata/message_cname_referral.wire
EXTRA_DIST += testdata/message_example_com_soa.wire
diff --git a/src/lib/cc/tests/Makefile.am b/src/lib/cc/tests/Makefile.am
index 4760855..eebd103 100644
--- a/src/lib/cc/tests/Makefile.am
+++ b/src/lib/cc/tests/Makefile.am
@@ -32,4 +32,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
diff --git a/src/lib/config/tests/Makefile.am b/src/lib/config/tests/Makefile.am
index 7153e09..4fb147d 100644
--- a/src/lib/config/tests/Makefile.am
+++ b/src/lib/config/tests/Makefile.am
@@ -31,4 +31,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
diff --git a/src/lib/cryptolink/tests/Makefile.am b/src/lib/cryptolink/tests/Makefile.am
index fbdd13f..2861edc 100644
--- a/src/lib/cryptolink/tests/Makefile.am
+++ b/src/lib/cryptolink/tests/Makefile.am
@@ -24,4 +24,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index 6b71388..bf1171e 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -9,7 +9,7 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
-lib_LTLIBRARIES = libdatasrc.la
+lib_LTLIBRARIES = libdatasrc.la sqlite3_ds.la memory_ds.la
libdatasrc_la_SOURCES = data_source.h data_source.cc
libdatasrc_la_SOURCES += static_datasrc.h static_datasrc.cc
libdatasrc_la_SOURCES += sqlite3_datasrc.h sqlite3_datasrc.cc
@@ -17,15 +17,25 @@ libdatasrc_la_SOURCES += query.h query.cc
libdatasrc_la_SOURCES += cache.h cache.cc
libdatasrc_la_SOURCES += rbtree.h
libdatasrc_la_SOURCES += zonetable.h zonetable.cc
-libdatasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc
libdatasrc_la_SOURCES += zone.h
libdatasrc_la_SOURCES += result.h
libdatasrc_la_SOURCES += logger.h logger.cc
libdatasrc_la_SOURCES += client.h iterator.h
libdatasrc_la_SOURCES += database.h database.cc
-libdatasrc_la_SOURCES += sqlite3_accessor.h sqlite3_accessor.cc
+libdatasrc_la_SOURCES += factory.h factory.cc
nodist_libdatasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
+sqlite3_ds_la_SOURCES = sqlite3_accessor.h sqlite3_accessor.cc
+sqlite3_ds_la_LDFLAGS = -module
+sqlite3_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+sqlite3_ds_la_LIBADD += libdatasrc.la
+sqlite3_ds_la_LIBADD += $(SQLITE_LIBS)
+
+memory_ds_la_SOURCES = memory_datasrc.h memory_datasrc.cc
+memory_ds_la_LDFLAGS = -module
+memory_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+memory_ds_la_LIBADD += libdatasrc.la
+
libdatasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
libdatasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libdns++.la
libdatasrc_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h
index 6a7ae04..40b7a3f 100644
--- a/src/lib/datasrc/client.h
+++ b/src/lib/datasrc/client.h
@@ -22,6 +22,47 @@
#include <datasrc/zone.h>
+/// \file
+/// Datasource clients
+///
+/// The data source client API is specified in client.h, and provides the
+/// functionality to query and modify data in the data sources. There are
+/// multiple datasource implementations, and by subclassing DataSourceClient or
+/// DatabaseClient, more can be added.
+///
+/// All datasources are implemented as loadable modules, with a name of the
+/// form "<type>_ds.so". This has been chosen intentionally, to minimize
+/// confusion and potential mistakes.
+///
+/// In order to use a datasource client backend, the class
+/// DataSourceClientContainer is provided in factory.h; this will load the
+/// library, set up the instance, and clean everything up once it is destroyed.
+///
+/// Access to the actual instance is provided with the getInstance() method
+/// in DataSourceClientContainer
+///
+/// \note Depending on actual usage, we might consider making the container
+/// a transparent abstraction layer, so it can be used as a DataSourceClient
+/// directly. This has some other implications though so for now the only access
+/// provided is through getInstance()).
+///
+/// For datasource backends, we use a dynamically loaded library system (with
+/// dlopen()). This library must contain the following things;
+/// - A subclass of DataSourceClient or DatabaseClient (which itself is a
+/// subclass of DataSourceClient)
+/// - A creator function for an instance of that subclass, of the form:
+/// \code
+/// extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr cfg);
+/// \endcode
+/// - A destructor for said instance, of the form:
+/// \code
+/// extern "C" void destroyInstance(isc::data::DataSourceClient* instance);
+/// \endcode
+///
+/// See the documentation for the \link DataSourceClient \endlink class for
+/// more information on implementing subclasses of it.
+///
+
namespace isc {
namespace datasrc {
@@ -39,6 +80,9 @@ typedef boost::shared_ptr<ZoneIterator> ZoneIteratorPtr;
/// operations to other classes; in general methods of this class act as
/// factories of these other classes.
///
+/// See \link datasrc/client.h datasrc/client.h \endlink for more information
+/// on adding datasource implementations.
+///
/// The following derived classes are currently (expected to be) provided:
/// - \c InMemoryClient: A client of a conceptual data source that stores
/// all necessary data in memory for faster lookups
diff --git a/src/lib/datasrc/data_source.h b/src/lib/datasrc/data_source.h
index ff695da..a7a15a9 100644
--- a/src/lib/datasrc/data_source.h
+++ b/src/lib/datasrc/data_source.h
@@ -184,9 +184,9 @@ public:
void setClass(isc::dns::RRClass& c) { rrclass = c; }
void setClass(const isc::dns::RRClass& c) { rrclass = c; }
- Result init() { return (NOT_IMPLEMENTED); }
- Result init(isc::data::ConstElementPtr config);
- Result close() { return (NOT_IMPLEMENTED); }
+ virtual Result init() { return (NOT_IMPLEMENTED); }
+ virtual Result init(isc::data::ConstElementPtr config);
+ virtual Result close() { return (NOT_IMPLEMENTED); }
virtual Result findRRset(const isc::dns::Name& qname,
const isc::dns::RRClass& qclass,
@@ -351,7 +351,7 @@ public:
/// \brief Returns the best enclosing zone name found for the given
// name and RR class so far.
- ///
+ ///
/// \return A pointer to the zone apex \c Name, NULL if none found yet.
///
/// This method never throws an exception.
@@ -413,6 +413,6 @@ private:
#endif
-// Local Variables:
+// Local Variables:
// mode: c++
-// End:
+// End:
diff --git a/src/lib/datasrc/factory.cc b/src/lib/datasrc/factory.cc
new file mode 100644
index 0000000..1818c70
--- /dev/null
+++ b/src/lib/datasrc/factory.cc
@@ -0,0 +1,95 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "factory.h"
+
+#include "data_source.h"
+#include "database.h"
+#include "sqlite3_accessor.h"
+#include "memory_datasrc.h"
+
+#include <datasrc/logger.h>
+
+#include <dlfcn.h>
+
+using namespace isc::data;
+using namespace isc::datasrc;
+
+namespace isc {
+namespace datasrc {
+
+LibraryContainer::LibraryContainer(const std::string& name) {
+ // use RTLD_GLOBAL so that shared symbols (e.g. exceptions)
+ // are recognized as such
+ ds_lib_ = dlopen(name.c_str(), RTLD_NOW | RTLD_GLOBAL);
+ if (ds_lib_ == NULL) {
+ isc_throw(DataSourceLibraryError, dlerror());
+ }
+}
+
+LibraryContainer::~LibraryContainer() {
+ dlclose(ds_lib_);
+}
+
+void*
+LibraryContainer::getSym(const char* name) {
+ // Since dlsym can return NULL on success, we check for errors by
+ // first clearing any existing errors with dlerror(), then calling dlsym,
+ // and finally checking for errors with dlerror()
+ dlerror();
+
+ void *sym = dlsym(ds_lib_, name);
+
+ const char* dlsym_error = dlerror();
+ if (dlsym_error != NULL) {
+ isc_throw(DataSourceLibrarySymbolError, dlsym_error);
+ }
+
+ return (sym);
+}
+
+DataSourceClientContainer::DataSourceClientContainer(const std::string& type,
+ ConstElementPtr config)
+: ds_lib_(type + "_ds.so")
+{
+ // We are casting from a data to a function pointer here
+ // Some compilers (rightfully) complain about that, but
+ // c-style casts are accepted the most here. If we run
+ // into any that also don't like this, we might need to
+ // use some form of union cast or memory copy to get
+ // from the void* to the function pointer.
+ ds_creator* ds_create = (ds_creator*)ds_lib_.getSym("createInstance");
+ destructor_ = (ds_destructor*)ds_lib_.getSym("destroyInstance");
+
+ std::string error;
+ try {
+ instance_ = ds_create(config, error);
+ if (instance_ == NULL) {
+ isc_throw(DataSourceError, error);
+ }
+ } catch (const std::exception& exc) {
+ isc_throw(DataSourceError, "Unknown uncaught exception from " + type +
+ " createInstance: " + exc.what());
+ } catch (...) {
+ isc_throw(DataSourceError, "Unknown uncaught exception from " + type);
+ }
+}
+
+DataSourceClientContainer::~DataSourceClientContainer() {
+ destructor_(instance_);
+}
+
+} // end namespace datasrc
+} // end namespace isc
+
diff --git a/src/lib/datasrc/factory.h b/src/lib/datasrc/factory.h
new file mode 100644
index 0000000..0284067
--- /dev/null
+++ b/src/lib/datasrc/factory.h
@@ -0,0 +1,170 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 __DATA_SOURCE_FACTORY_H
+#define __DATA_SOURCE_FACTORY_H 1
+
+#include <boost/noncopyable.hpp>
+
+#include <datasrc/data_source.h>
+#include <datasrc/client.h>
+#include <exceptions/exceptions.h>
+
+#include <cc/data.h>
+
+namespace isc {
+namespace datasrc {
+
+
+/// \brief Raised if there is an error loading the datasource implementation
+/// library
+class DataSourceLibraryError : public DataSourceError {
+public:
+ DataSourceLibraryError(const char* file, size_t line, const char* what) :
+ DataSourceError(file, line, what) {}
+};
+
+/// \brief Raised if there is an error reading a symbol from the datasource
+/// implementation library
+class DataSourceLibrarySymbolError : public DataSourceError {
+public:
+ DataSourceLibrarySymbolError(const char* file, size_t line,
+ const char* what) :
+ DataSourceError(file, line, what) {}
+};
+
+typedef DataSourceClient* ds_creator(isc::data::ConstElementPtr config,
+ std::string& error);
+typedef void ds_destructor(DataSourceClient* instance);
+
+/// \brief Container class for dynamically loaded libraries
+///
+/// This class is used to dlopen() a library, provides access to dlsym(),
+/// and cleans up the dlopened library when the instance of this class is
+/// destroyed.
+///
+/// Its main function is to provide RAII-style access to dlopen'ed libraries.
+///
+/// \note Currently it is Datasource-backend specific. If we have need for this
+/// in other places than for dynamically loading datasources, then, apart
+/// from moving it to another location, we also need to make the
+/// exceptions raised more general.
+class LibraryContainer : boost::noncopyable {
+public:
+ /// \brief Constructor
+ ///
+ /// \param name The name of the library (.so) file. This file must be in
+ /// the library path.
+ ///
+ /// \exception DataSourceLibraryError If the library cannot be found or
+ /// cannot be loaded.
+ LibraryContainer(const std::string& name);
+
+ /// \brief Destructor
+ ///
+ /// Cleans up the library by calling dlclose()
+ ~LibraryContainer();
+
+ /// \brief Retrieve a symbol
+ ///
+ /// This retrieves a symbol from the loaded library.
+ ///
+ /// \exception DataSourceLibrarySymbolError if the symbol cannot be found,
+ /// or if another error (as reported by dlerror() occurs.
+ ///
+ /// \param name The name of the symbol to retrieve
+ /// \return A pointer to the symbol. This may be NULL, and if so, indicates
+ /// the symbol does indeed exist, but has the value NULL itself.
+ /// If the symbol does not exist, a DataSourceLibrarySymbolError is
+ /// raised.
+ ///
+ /// \note The argument is a const char* (and not a std::string like the
+ /// argument in the constructor). This argument is always a fixed
+ /// string in the code, while the other can be read from
+ /// configuration, and needs modification
+ void* getSym(const char* name);
+private:
+ /// Pointer to the dynamically loaded library structure
+ void *ds_lib_;
+};
+
+
+/// \brief Container for a specific instance of a dynamically loaded
+/// DataSourceClient implementation
+///
+/// Given a datasource type and a type-specific set of configuration data,
+/// the corresponding dynamic library is loaded (if it hadn't been already),
+/// and an instance is created. This instance is stored within this structure,
+/// and can be accessed through getInstance(). Upon destruction of this
+/// container, the stored instance of the DataSourceClient is deleted with
+/// the destructor function provided by the loaded library.
+///
+/// The 'type' is actually the name of the library, minus the '_ds.so' postfix
+/// Datasource implementation libraries therefore have a fixed name, both for
+/// easy recognition and to reduce potential mistakes.
+/// For example, the sqlite3 implementation has the type 'sqlite3', and the
+/// derived filename 'sqlite3_ds.so'
+///
+/// There are of course some demands to an implementation, not all of which
+/// can be verified compile-time. It must provide a creator and destructor
+/// functions. The creator function must return an instance of a subclass of
+/// DataSourceClient. The prototypes of these functions are as follows:
+/// \code
+/// extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr cfg);
+///
+/// extern "C" void destroyInstance(isc::data::DataSourceClient* instance);
+/// \endcode
+class DataSourceClientContainer : boost::noncopyable {
+public:
+ /// \brief Constructor
+ ///
+ /// \exception DataSourceLibraryError if there is an error loading the
+ /// backend library
+ /// \exception DataSourceLibrarySymbolError if the library does not have
+ /// the needed symbols, or if there is an error reading them
+ /// \exception DataError if the given config is not correct
+ /// for the given type, or if there was a problem during
+ /// initialization
+ ///
+ /// \param type The type of the datasource client. Based on the value of
+ /// type, a specific backend library is used, by appending the
+ /// string '_ds.so' to the given type, and loading that as the
+ /// implementation library
+ /// \param config Type-specific configuration data, see the documentation
+ /// of the datasource backend type for information on what
+ /// configuration data to pass.
+ DataSourceClientContainer(const std::string& type,
+ isc::data::ConstElementPtr config);
+
+ /// \brief Destructor
+ ~DataSourceClientContainer();
+
+ /// \brief Accessor to the instance
+ ///
+ /// \return Reference to the DataSourceClient instance contained in this
+ /// container.
+ DataSourceClient& getInstance() { return *instance_; }
+
+private:
+ DataSourceClient* instance_;
+ ds_destructor* destructor_;
+ LibraryContainer ds_lib_;
+};
+
+} // end namespace datasrc
+} // end namespace isc
+#endif // DATA_SOURCE_FACTORY_H
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index 2e94b67..2b556ab 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -16,6 +16,7 @@
#include <cassert>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
#include <exceptions/exceptions.h>
@@ -29,9 +30,13 @@
#include <datasrc/logger.h>
#include <datasrc/iterator.h>
#include <datasrc/data_source.h>
+#include <datasrc/factory.h>
+
+#include <cc/data.h>
using namespace std;
using namespace isc::dns;
+using namespace isc::data;
namespace isc {
namespace datasrc {
@@ -805,5 +810,149 @@ ZoneUpdaterPtr
InMemoryClient::getUpdater(const isc::dns::Name&, bool) const {
isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
}
+
+
+namespace {
+// convencience function to add an error message to a list of those
+// (TODO: move functions like these to some util lib?)
+void
+addError(ElementPtr errors, const std::string& error) {
+ if (errors != ElementPtr() && errors->getType() == Element::list) {
+ errors->add(Element::create(error));
+ }
+}
+
+/// Check if the given element exists in the map, and if it is a string
+bool
+checkConfigElementString(ConstElementPtr config, const std::string& name,
+ ElementPtr errors)
+{
+ if (!config->contains(name)) {
+ addError(errors,
+ "Config for memory backend does not contain a '"
+ +name+
+ "' value");
+ return false;
+ } else if (!config->get(name) ||
+ config->get(name)->getType() != Element::string) {
+ addError(errors, "value of " + name +
+ " in memory backend config is not a string");
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool
+checkZoneConfig(ConstElementPtr config, ElementPtr errors) {
+ bool result = true;
+ if (!config || config->getType() != Element::map) {
+ addError(errors, "Elements in memory backend's zone list must be maps");
+ result = false;
+ } else {
+ if (!checkConfigElementString(config, "origin", errors)) {
+ result = false;
+ }
+ if (!checkConfigElementString(config, "file", errors)) {
+ result = false;
+ }
+ // we could add some existence/readabilty/parsability checks here
+ // if we want
+ }
+ return result;
+}
+
+bool
+checkConfig(ConstElementPtr config, ElementPtr errors) {
+ /* Specific configuration is under discussion, right now this accepts
+ * the 'old' configuration, see [TODO]
+ * So for memory datasource, we get a structure like this:
+ * { "type": string ("memory"),
+ * "class": string ("IN"/"CH"/etc),
+ * "zones": list
+ * }
+ * Zones list is a list of maps:
+ * { "origin": string,
+ * "file": string
+ * }
+ *
+ * At this moment we cannot be completely sure of the contents of the
+ * structure, so we have to do some more extensive tests than should
+ * strictly be necessary (e.g. existence and type of elements)
+ */
+ bool result = true;
+
+ if (!config || config->getType() != Element::map) {
+ addError(errors, "Base config for memory backend must be a map");
+ result = false;
+ } else {
+ if (!checkConfigElementString(config, "type", errors)) {
+ result = false;
+ } else {
+ if (config->get("type")->stringValue() != "memory") {
+ addError(errors,
+ "Config for memory backend is not of type \"memory\"");
+ result = false;
+ }
+ }
+ if (!checkConfigElementString(config, "class", errors)) {
+ result = false;
+ } else {
+ try {
+ RRClass rrc(config->get("class")->stringValue());
+ } catch (const isc::Exception& rrce) {
+ addError(errors,
+ "Error parsing class config for memory backend: " +
+ std::string(rrce.what()));
+ result = false;
+ }
+ }
+ if (!config->contains("zones")) {
+ addError(errors, "No 'zones' element in memory backend config");
+ result = false;
+ } else if (!config->get("zones") ||
+ config->get("zones")->getType() != Element::list) {
+ addError(errors, "'zones' element in memory backend config is not a list");
+ result = false;
+ } else {
+ BOOST_FOREACH(ConstElementPtr zone_config,
+ config->get("zones")->listValue()) {
+ if (!checkZoneConfig(zone_config, errors)) {
+ result = false;
+ }
+ }
+ }
+ }
+
+ return (result);
+ return true;
+}
+
+} // end anonymous namespace
+
+DataSourceClient *
+createInstance(isc::data::ConstElementPtr config, std::string& error) {
+ ElementPtr errors(Element::createList());
+ if (!checkConfig(config, errors)) {
+ error = "Configuration error: " + errors->str();
+ return (NULL);
+ }
+ try {
+ return (new InMemoryClient());
+ } catch (const std::exception& exc) {
+ error = std::string("Error creating memory datasource: ") + exc.what();
+ return (NULL);
+ } catch (...) {
+ error = std::string("Error creating memory datasource, "
+ "unknown exception");
+ return (NULL);
+ }
+}
+
+void destroyInstance(DataSourceClient* instance) {
+ delete instance;
+}
+
+
} // end of namespace datasrc
-} // end of namespace dns
+} // end of namespace isc
diff --git a/src/lib/datasrc/memory_datasrc.h b/src/lib/datasrc/memory_datasrc.h
index 95f589a..610deff 100644
--- a/src/lib/datasrc/memory_datasrc.h
+++ b/src/lib/datasrc/memory_datasrc.h
@@ -22,6 +22,8 @@
#include <datasrc/zonetable.h>
#include <datasrc/client.h>
+#include <cc/data.h>
+
namespace isc {
namespace dns {
class Name;
@@ -219,7 +221,7 @@ private:
/// while it wouldn't be safe to delete unnecessary zones inside the dedicated
/// backend.
///
-/// The findZone() method takes a domain name and returns the best matching
+/// The findZone() method takes a domain name and returns the best matching
/// \c InMemoryZoneFinder in the form of (Boost) shared pointer, so that it can
/// provide the general interface for all data sources.
class InMemoryClient : public DataSourceClient {
@@ -289,6 +291,38 @@ private:
class InMemoryClientImpl;
InMemoryClientImpl* impl_;
};
+
+/// \brief Creates an instance of the Memory datasource client
+///
+/// Currently the configuration passed here must be a MapElement, formed as
+/// follows:
+/// \code
+/// { "type": string ("memory"),
+/// "class": string ("IN"/"CH"/etc),
+/// "zones": list
+/// }
+/// Zones list is a list of maps:
+/// { "origin": string,
+/// "file": string
+/// }
+/// \endcode
+/// (i.e. the configuration that was used prior to the datasource refactor)
+///
+/// This configuration setup is currently under discussion and will change in
+/// the near future.
+///
+/// \param config The configuration for the datasource instance
+/// \param error This string will be set to an error message if an error occurs
+/// during initialization
+/// \return An instance of the memory datasource client, or NULL if there was
+/// an error
+extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr config,
+ std::string& error);
+
+/// \brief Destroy the instance created by createInstance()
+extern "C" void destroyInstance(DataSourceClient* instance);
+
+
}
}
#endif // __DATA_SOURCE_MEMORY_H
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index 69d5649..6d6dbba 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -22,12 +22,16 @@
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/logger.h>
#include <datasrc/data_source.h>
+#include <datasrc/factory.h>
#include <util/filename.h>
using namespace std;
+using namespace isc::data;
#define SQLITE_SCHEMA_VERSION 1
+#define CONFIG_ITEM_DATABASE_FILE "database_file"
+
namespace isc {
namespace datasrc {
@@ -134,19 +138,6 @@ private:
};
SQLite3Accessor::SQLite3Accessor(const std::string& filename,
- const isc::dns::RRClass& rrclass) :
- dbparameters_(new SQLite3Parameters),
- filename_(filename),
- class_(rrclass.toText()),
- database_name_("sqlite3_" +
- isc::util::Filename(filename).nameAndExtension())
-{
- LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_NEWCONN);
-
- open(filename);
-}
-
-SQLite3Accessor::SQLite3Accessor(const std::string& filename,
const string& rrclass) :
dbparameters_(new SQLite3Parameters),
filename_(filename),
@@ -637,8 +628,12 @@ doUpdate(SQLite3Parameters& dbparams, StatementID stmt_id,
const size_t column_count =
sizeof(update_params) / sizeof(update_params[0]);
for (int i = 0; i < column_count; ++i) {
- if (sqlite3_bind_text(stmt, ++param_id, update_params[i].c_str(), -1,
- SQLITE_TRANSIENT) != SQLITE_OK) {
+ // The old sqlite3 data source API assumes NULL for an empty column.
+ // We need to provide compatibility at least for now.
+ if (sqlite3_bind_text(stmt, ++param_id,
+ update_params[i].empty() ? NULL :
+ update_params[i].c_str(),
+ -1, SQLITE_TRANSIENT) != SQLITE_OK) {
isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
sqlite3_errmsg(dbparams.db_));
}
@@ -711,5 +706,75 @@ SQLite3Accessor::findPreviousName(int zone_id, const std::string& rname)
return (result);
}
+namespace {
+void
+addError(ElementPtr errors, const std::string& error) {
+ if (errors != ElementPtr() && errors->getType() == Element::list) {
+ errors->add(Element::create(error));
+ }
+}
+
+bool
+checkConfig(ConstElementPtr config, ElementPtr errors) {
+ /* Specific configuration is under discussion, right now this accepts
+ * the 'old' configuration, see header file
+ */
+ bool result = true;
+
+ if (!config || config->getType() != Element::map) {
+ addError(errors, "Base config for SQlite3 backend must be a map");
+ result = false;
+ } else {
+ if (!config->contains(CONFIG_ITEM_DATABASE_FILE)) {
+ addError(errors,
+ "Config for SQlite3 backend does not contain a '"
+ CONFIG_ITEM_DATABASE_FILE
+ "' value");
+ result = false;
+ } else if (!config->get(CONFIG_ITEM_DATABASE_FILE) ||
+ config->get(CONFIG_ITEM_DATABASE_FILE)->getType() !=
+ Element::string) {
+ addError(errors, "value of " CONFIG_ITEM_DATABASE_FILE
+ " in SQLite3 backend is not a string");
+ result = false;
+ } else if (config->get(CONFIG_ITEM_DATABASE_FILE)->stringValue() ==
+ "") {
+ addError(errors, "value of " CONFIG_ITEM_DATABASE_FILE
+ " in SQLite3 backend is empty");
+ result = false;
+ }
+ }
+
+ return (result);
+}
+
+} // end anonymous namespace
+
+DataSourceClient *
+createInstance(isc::data::ConstElementPtr config, std::string& error) {
+ ElementPtr errors(Element::createList());
+ if (!checkConfig(config, errors)) {
+ error = "Configuration error: " + errors->str();
+ return (NULL);
+ }
+ std::string dbfile = config->get(CONFIG_ITEM_DATABASE_FILE)->stringValue();
+ try {
+ boost::shared_ptr<DatabaseAccessor> sqlite3_accessor(
+ new SQLite3Accessor(dbfile, "IN")); // XXX: avoid hardcode RR class
+ return (new DatabaseClient(isc::dns::RRClass::IN(), sqlite3_accessor));
+ } catch (const std::exception& exc) {
+ error = std::string("Error creating sqlite3 datasource: ") + exc.what();
+ return (NULL);
+ } catch (...) {
+ error = std::string("Error creating sqlite3 datasource, "
+ "unknown exception");
+ return (NULL);
+ }
}
+
+void destroyInstance(DataSourceClient* instance) {
+ delete instance;
}
+
+} // end of namespace datasrc
+} // end of namespace isc
diff --git a/src/lib/datasrc/sqlite3_accessor.h b/src/lib/datasrc/sqlite3_accessor.h
index c4bacad..8b74309 100644
--- a/src/lib/datasrc/sqlite3_accessor.h
+++ b/src/lib/datasrc/sqlite3_accessor.h
@@ -24,6 +24,8 @@
#include <boost/scoped_ptr.hpp>
#include <string>
+#include <cc/data.h>
+
namespace isc {
namespace dns {
class RRClass;
@@ -65,20 +67,10 @@ public:
* doesn't work (it is broken, doesn't exist and can't be created, etc).
*
* \param filename The database file to be used.
- * \param rrclass Which class of data it should serve (while the database
- * file can contain multiple classes of data, single database can
- * provide only one class).
- */
- SQLite3Accessor(const std::string& filename,
- const isc::dns::RRClass& rrclass);
-
- /**
- * \brief Constructor
- *
- * Same as the other version, but takes rrclass as a bare string.
- * we should obsolete the other version and unify the constructor to
- * this version; the SQLite3Accessor is expected to be "dumb" and
- * shouldn't care about DNS specific information such as RRClass.
+ * \param rrclass Textual representation of RR class ("IN", "CH", etc),
+ * specifying which class of data it should serve (while the database
+ * file can contain multiple classes of data, a single accessor can
+ * work with only one class).
*/
SQLite3Accessor(const std::string& filename, const std::string& rrclass);
@@ -191,6 +183,25 @@ private:
const std::string database_name_;
};
+/// \brief Creates an instance of the SQlite3 datasource client
+///
+/// Currently the configuration passed here must be a MapElement, containing
+/// one item called "database_file", whose value is a string
+///
+/// This configuration setup is currently under discussion and will change in
+/// the near future.
+///
+/// \param config The configuration for the datasource instance
+/// \param error This string will be set to an error message if an error occurs
+/// during initialization
+/// \return An instance of the sqlite3 datasource client, or NULL if there was
+/// an error
+extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr config,
+ std::string& error);
+
+/// \brief Destroy the instance created by createInstance()
+extern "C" void destroyInstance(DataSourceClient* instance);
+
}
}
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 48cbc76..0be8cd2 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = . testdata
+SUBDIRS = testdata
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/lib/dns
@@ -29,12 +29,23 @@ run_unittests_SOURCES += query_unittest.cc
run_unittests_SOURCES += cache_unittest.cc
run_unittests_SOURCES += test_datasrc.h test_datasrc.cc
run_unittests_SOURCES += rbtree_unittest.cc
-run_unittests_SOURCES += zonetable_unittest.cc
-run_unittests_SOURCES += memory_datasrc_unittest.cc
+#run_unittests_SOURCES += zonetable_unittest.cc
+#run_unittests_SOURCES += memory_datasrc_unittest.cc
run_unittests_SOURCES += logger_unittest.cc
run_unittests_SOURCES += database_unittest.cc
run_unittests_SOURCES += client_unittest.cc
run_unittests_SOURCES += sqlite3_accessor_unittest.cc
+if !USE_STATIC_LINK
+# This test uses dynamically loadable module. It will cause various
+# troubles with static link such as "missing" symbols in the static object
+# for the module. As a workaround we disable this particualr test
+# in this case.
+run_unittests_SOURCES += factory_unittest.cc
+endif
+# for the dlopened types we have tests for, we also need to include the
+# sources
+run_unittests_SOURCES += $(top_srcdir)/src/lib/datasrc/sqlite3_accessor.cc
+#run_unittests_SOURCES += $(top_srcdir)/src/lib/datasrc/memory_datasrc.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
@@ -51,7 +62,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
EXTRA_DIST = testdata/brokendb.sqlite3
EXTRA_DIST += testdata/example.com.signed
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index fe57185..8cd70ef 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -830,8 +830,7 @@ public:
class TestSQLite3Accessor : public SQLite3Accessor {
public:
TestSQLite3Accessor() : SQLite3Accessor(
- TEST_DATA_BUILDDIR "/rwtest.sqlite3.copied",
- RRClass::IN())
+ TEST_DATA_BUILDDIR "/rwtest.sqlite3.copied", "IN")
{
startUpdateZone("example.org.", true);
string columns[ADD_COLUMN_COUNT];
diff --git a/src/lib/datasrc/tests/factory_unittest.cc b/src/lib/datasrc/tests/factory_unittest.cc
new file mode 100644
index 0000000..0133508
--- /dev/null
+++ b/src/lib/datasrc/tests/factory_unittest.cc
@@ -0,0 +1,175 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <boost/scoped_ptr.hpp>
+
+#include <datasrc/factory.h>
+#include <datasrc/data_source.h>
+#include <datasrc/sqlite3_accessor.h>
+
+#include <dns/rrclass.h>
+#include <cc/data.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::datasrc;
+using namespace isc::data;
+
+std::string SQLITE_DBFILE_EXAMPLE_ORG = TEST_DATA_DIR "/example.org.sqlite3";
+
+namespace {
+
+TEST(FactoryTest, sqlite3ClientBadConfig) {
+ // We start out by building the configuration data bit by bit,
+ // testing each form of 'bad config', until we have a good one.
+ // Then we do some very basic operation on the client (detailed
+ // tests are left to the implementation-specific backends)
+ ElementPtr config;
+ ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+ DataSourceError);
+
+ config = Element::create("asdf");
+ ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+ DataSourceError);
+
+ config = Element::createMap();
+ ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+ DataSourceError);
+
+ config->set("class", ElementPtr());
+ ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+ DataSourceError);
+
+ config->set("class", Element::create(1));
+ ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+ DataSourceError);
+
+ config->set("class", Element::create("FOO"));
+ ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+ DataSourceError);
+
+ config->set("class", Element::create("IN"));
+ ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+ DataSourceError);
+
+ config->set("database_file", ElementPtr());
+ ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+ DataSourceError);
+
+ config->set("database_file", Element::create(1));
+ ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+ DataSourceError);
+
+ config->set("database_file", Element::create("/foo/bar/doesnotexist"));
+ ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+ DataSourceError);
+
+ config->set("database_file", Element::create(SQLITE_DBFILE_EXAMPLE_ORG));
+ DataSourceClientContainer dsc("sqlite3", config);
+
+ DataSourceClient::FindResult result1(
+ dsc.getInstance().findZone(isc::dns::Name("example.org.")));
+ ASSERT_EQ(result::SUCCESS, result1.code);
+
+ DataSourceClient::FindResult result2(
+ dsc.getInstance().findZone(isc::dns::Name("no.such.zone.")));
+ ASSERT_EQ(result::NOTFOUND, result2.code);
+
+ ZoneIteratorPtr iterator(dsc.getInstance().getIterator(
+ isc::dns::Name("example.org.")));
+
+ ZoneUpdaterPtr updater(dsc.getInstance().getUpdater(
+ isc::dns::Name("example.org."), false));
+}
+
+TEST(FactoryTest, memoryClient) {
+ // We start out by building the configuration data bit by bit,
+ // testing each form of 'bad config', until we have a good one.
+ // Then we do some very basic operation on the client (detailed
+ // tests are left to the implementation-specific backends)
+ ElementPtr config;
+ ASSERT_THROW(DataSourceClientContainer client("memory", config),
+ DataSourceError);
+
+ config = Element::create("asdf");
+ ASSERT_THROW(DataSourceClientContainer("memory", config),
+ DataSourceError);
+
+ config = Element::createMap();
+ ASSERT_THROW(DataSourceClientContainer("memory", config),
+ DataSourceError);
+
+ config->set("type", ElementPtr());
+ ASSERT_THROW(DataSourceClientContainer("memory", config),
+ DataSourceError);
+
+ config->set("type", Element::create(1));
+ ASSERT_THROW(DataSourceClientContainer("memory", config),
+ DataSourceError);
+
+ config->set("type", Element::create("FOO"));
+ ASSERT_THROW(DataSourceClientContainer("memory", config),
+ DataSourceError);
+
+ config->set("type", Element::create("memory"));
+ ASSERT_THROW(DataSourceClientContainer("memory", config),
+ DataSourceError);
+
+ config->set("class", ElementPtr());
+ ASSERT_THROW(DataSourceClientContainer("memory", config),
+ DataSourceError);
+
+ config->set("class", Element::create(1));
+ ASSERT_THROW(DataSourceClientContainer("memory", config),
+ DataSourceError);
+
+ config->set("class", Element::create("FOO"));
+ ASSERT_THROW(DataSourceClientContainer("memory", config),
+ DataSourceError);
+
+ config->set("class", Element::create("IN"));
+ ASSERT_THROW(DataSourceClientContainer("memory", config),
+ DataSourceError);
+
+ config->set("zones", ElementPtr());
+ ASSERT_THROW(DataSourceClientContainer("memory", config),
+ DataSourceError);
+
+ config->set("zones", Element::create(1));
+ ASSERT_THROW(DataSourceClientContainer("memory", config),
+ DataSourceError);
+
+ config->set("zones", Element::createList());
+ DataSourceClientContainer dsc("memory", config);
+
+ // Once it is able to load some zones, we should add a few tests
+ // here to see that it does.
+ DataSourceClient::FindResult result(
+ dsc.getInstance().findZone(isc::dns::Name("no.such.zone.")));
+ ASSERT_EQ(result::NOTFOUND, result.code);
+
+ ASSERT_THROW(dsc.getInstance().getIterator(isc::dns::Name("example.org.")),
+ DataSourceError);
+
+ ASSERT_THROW(dsc.getInstance().getUpdater(isc::dns::Name("no.such.zone."),
+ false), isc::NotImplemented);
+}
+
+TEST(FactoryTest, badType) {
+ ASSERT_THROW(DataSourceClientContainer("foo", ElementPtr()),
+ DataSourceError);
+}
+
+} // end anonymous namespace
+
diff --git a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
index 3974977..62fa3c3 100644
--- a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
@@ -57,36 +57,34 @@ const char* SQLITE_NEW_DBFILE = TEST_DATA_BUILDDIR "/newdb.sqlite3";
// Opening works (the content is tested in different tests)
TEST(SQLite3Open, common) {
- EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE,
- RRClass::IN()));
+ EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE, "IN"));
}
// The file can't be opened
TEST(SQLite3Open, notExist) {
- EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_NOTEXIST,
- RRClass::IN()), SQLite3Error);
+ EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_NOTEXIST, "IN"),
+ SQLite3Error);
}
// It rejects broken DB
TEST(SQLite3Open, brokenDB) {
- EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_BROKENDB,
- RRClass::IN()), SQLite3Error);
+ EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_BROKENDB, "IN"),
+ SQLite3Error);
}
// Test we can create the schema on the fly
TEST(SQLite3Open, memoryDB) {
- EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_MEMORY,
- RRClass::IN()));
+ EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_MEMORY, "IN"));
}
// Test fixture for querying the db
class SQLite3AccessorTest : public ::testing::Test {
public:
SQLite3AccessorTest() {
- initAccessor(SQLITE_DBFILE_EXAMPLE, RRClass::IN());
+ initAccessor(SQLITE_DBFILE_EXAMPLE, "IN");
}
// So it can be re-created with different data
- void initAccessor(const std::string& filename, const RRClass& rrclass) {
+ void initAccessor(const std::string& filename, const string& rrclass) {
accessor.reset(new SQLite3Accessor(filename, rrclass));
}
// The tested accessor
@@ -112,14 +110,14 @@ TEST_F(SQLite3AccessorTest, noZone) {
// This zone is there, but in different class
TEST_F(SQLite3AccessorTest, noClass) {
- initAccessor(SQLITE_DBFILE_EXAMPLE, RRClass::CH());
+ initAccessor(SQLITE_DBFILE_EXAMPLE, "CH");
EXPECT_FALSE(accessor->getZone("example.com.").first);
}
// This tests the iterator context
TEST_F(SQLite3AccessorTest, iterator) {
// Our test zone is conveniently small, but not empty
- initAccessor(SQLITE_DBFILE_EXAMPLE_ORG, RRClass::IN());
+ initAccessor(SQLITE_DBFILE_EXAMPLE_ORG, "IN");
const std::pair<bool, int> zone_info(accessor->getZone("example.org."));
ASSERT_TRUE(zone_info.first);
@@ -207,12 +205,12 @@ TEST_F(SQLite3AccessorTest, iterator) {
}
TEST(SQLite3Open, getDBNameExample2) {
- SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
+ SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE2, "IN");
EXPECT_EQ(SQLITE_DBNAME_EXAMPLE2, accessor.getDBName());
}
TEST(SQLite3Open, getDBNameExampleROOT) {
- SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE_ROOT, RRClass::IN());
+ SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE_ROOT, "IN");
EXPECT_EQ(SQLITE_DBNAME_EXAMPLE_ROOT, accessor.getDBName());
}
@@ -410,7 +408,7 @@ bool isReadable(const char* filename) {
TEST_F(SQLite3Create, creationtest) {
ASSERT_FALSE(isReadable(SQLITE_NEW_DBFILE));
// Should simply be created
- SQLite3Accessor accessor(SQLITE_NEW_DBFILE, RRClass::IN());
+ SQLite3Accessor accessor(SQLITE_NEW_DBFILE, "IN");
ASSERT_TRUE(isReadable(SQLITE_NEW_DBFILE));
}
@@ -422,12 +420,12 @@ TEST_F(SQLite3Create, emptytest) {
ASSERT_EQ(SQLITE_OK, sqlite3_open(SQLITE_NEW_DBFILE, &db));
// empty, but not locked, so creating it now should work
- SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, RRClass::IN());
+ SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, "IN");
sqlite3_close(db);
// should work now that we closed it
- SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
+ SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, "IN");
}
TEST_F(SQLite3Create, lockedtest) {
@@ -439,13 +437,13 @@ TEST_F(SQLite3Create, lockedtest) {
sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL, NULL);
// should not be able to open it
- EXPECT_THROW(SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, RRClass::IN()),
+ EXPECT_THROW(SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, "IN"),
SQLite3Error);
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
// should work now that we closed it
- SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
+ SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, "IN");
}
TEST_F(SQLite3AccessorTest, clone) {
@@ -508,11 +506,11 @@ protected:
isc_throw(isc::Exception,
"Error setting up; command failed: " << install_cmd);
};
- initAccessor(TEST_DATA_BUILDDIR "/test.sqlite3.copied", RRClass::IN());
+ initAccessor(TEST_DATA_BUILDDIR "/test.sqlite3.copied", "IN");
zone_id = accessor->getZone("example.com.").second;
another_accessor.reset(new SQLite3Accessor(
TEST_DATA_BUILDDIR "/test.sqlite3.copied",
- RRClass::IN()));
+ "IN"));
expected_stored.push_back(common_expected_data);
}
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index 3d4a663..0d2bffd 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -24,6 +24,9 @@ EXTRA_DIST += rdata/generic/cname_5.h
EXTRA_DIST += rdata/generic/detail/nsec_bitmap.cc
EXTRA_DIST += rdata/generic/detail/nsec_bitmap.h
EXTRA_DIST += rdata/generic/detail/txt_like.h
+EXTRA_DIST += rdata/generic/detail/ds_like.h
+EXTRA_DIST += rdata/generic/dlv_32769.cc
+EXTRA_DIST += rdata/generic/dlv_32769.h
EXTRA_DIST += rdata/generic/dname_39.cc
EXTRA_DIST += rdata/generic/dname_39.h
EXTRA_DIST += rdata/generic/dnskey_48.cc
@@ -107,6 +110,7 @@ libdns___la_SOURCES += character_string.h character_string.cc
libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc
libdns___la_SOURCES += rdata/generic/detail/txt_like.h
+libdns___la_SOURCES += rdata/generic/detail/ds_like.h
libdns___la_CPPFLAGS = $(AM_CPPFLAGS)
# Most applications of libdns++ will only implicitly rely on libcryptolink,
diff --git a/src/lib/dns/python/message_python.cc b/src/lib/dns/python/message_python.cc
index 6de0925..2349401 100644
--- a/src/lib/dns/python/message_python.cc
+++ b/src/lib/dns/python/message_python.cc
@@ -78,7 +78,7 @@ PyObject* Message_makeResponse(s_Message* self);
PyObject* Message_toText(s_Message* self);
PyObject* Message_str(PyObject* self);
PyObject* Message_toWire(s_Message* self, PyObject* args);
-PyObject* Message_fromWire(PyObject* const pyself, PyObject* args);
+PyObject* Message_fromWire(PyObject* pyself, PyObject* args);
// This list contains the actual set of functions we have in
// python. Each entry has
@@ -160,7 +160,7 @@ PyMethodDef Message_methods[] = {
"If the given message is not in RENDER mode, an "
"InvalidMessageOperation is raised.\n"
},
- { "from_wire", reinterpret_cast<PyCFunction>(Message_fromWire), METH_VARARGS, Message_fromWire_doc },
+ { "from_wire", Message_fromWire, METH_VARARGS, Message_fromWire_doc },
{ NULL, NULL, 0, NULL }
};
@@ -642,8 +642,8 @@ Message_toWire(s_Message* self, PyObject* args) {
}
PyObject*
-Message_fromWire(PyObject* const pyself, PyObject* args) {
- s_Message* self = static_cast<s_Message*>(pyself);
+Message_fromWire(PyObject* pyself, PyObject* args) {
+ s_Message* const self = static_cast<s_Message*>(pyself);
const char* b;
Py_ssize_t len;
unsigned int options = Message::PARSE_DEFAULT;
diff --git a/src/lib/dns/rdata/generic/detail/ds_like.h b/src/lib/dns/rdata/generic/detail/ds_like.h
new file mode 100644
index 0000000..b5a35cd
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/ds_like.h
@@ -0,0 +1,225 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 __DS_LIKE_H
+#define __DS_LIKE_H 1
+
+#include <stdint.h>
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+/// \brief \c rdata::DSLikeImpl class represents the DS-like RDATA for DS
+/// and DLV types.
+///
+/// This class implements the basic interfaces inherited by the DS and DLV
+/// classes from the abstract \c rdata::Rdata class, and provides trivial
+/// accessors to DS-like RDATA.
+template <class Type, uint16_t typeCode> class DSLikeImpl {
+ // Common sequence of toWire() operations used for the two versions of
+ // toWire().
+ template <typename Output>
+ void
+ toWireCommon(Output& output) const {
+ output.writeUint16(tag_);
+ output.writeUint8(algorithm_);
+ output.writeUint8(digest_type_);
+ output.writeData(&digest_[0], digest_.size());
+ }
+
+public:
+ /// \brief Constructor from string.
+ ///
+ /// <b>Exceptions</b>
+ ///
+ /// \c InvalidRdataText is thrown if the method cannot process the
+ /// parameter data for any of the number of reasons.
+ DSLikeImpl(const std::string& ds_str) {
+ std::istringstream iss(ds_str);
+ // peekc should be of iss's char_type for isspace to work
+ std::istringstream::char_type peekc;
+ std::stringbuf digestbuf;
+ uint32_t tag, algorithm, digest_type;
+
+ iss >> tag >> algorithm >> digest_type;
+ if (iss.bad() || iss.fail()) {
+ isc_throw(InvalidRdataText,
+ "Invalid " << RRType(typeCode) << " text");
+ }
+ if (tag > 0xffff) {
+ isc_throw(InvalidRdataText,
+ RRType(typeCode) << " tag out of range");
+ }
+ if (algorithm > 0xff) {
+ isc_throw(InvalidRdataText,
+ RRType(typeCode) << " algorithm out of range");
+ }
+ if (digest_type > 0xff) {
+ isc_throw(InvalidRdataText,
+ RRType(typeCode) << " digest type out of range");
+ }
+
+ iss.read(&peekc, 1);
+ if (!iss.good() || !isspace(peekc, iss.getloc())) {
+ isc_throw(InvalidRdataText,
+ RRType(typeCode) << " presentation format error");
+ }
+
+ iss >> &digestbuf;
+
+ tag_ = tag;
+ algorithm_ = algorithm;
+ digest_type_ = digest_type;
+ decodeHex(digestbuf.str(), digest_);
+ }
+
+ /// \brief Constructor from wire-format data.
+ ///
+ /// \param buffer A buffer storing the wire format data.
+ /// \param rdata_len The length of the RDATA in bytes, normally expected
+ /// to be the value of the RDLENGTH field of the corresponding RR.
+ ///
+ /// <b>Exceptions</b>
+ ///
+ /// \c InvalidRdataLength is thrown if the input data is too short for the
+ /// type.
+ DSLikeImpl(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len < 4) {
+ isc_throw(InvalidRdataLength, RRType(typeCode) << " too short");
+ }
+
+ tag_ = buffer.readUint16();
+ algorithm_ = buffer.readUint8();
+ digest_type_ = buffer.readUint8();
+
+ rdata_len -= 4;
+ digest_.resize(rdata_len);
+ buffer.readData(&digest_[0], rdata_len);
+ }
+
+ /// \brief The copy constructor.
+ ///
+ /// Trivial for now, we could've used the default one.
+ DSLikeImpl(const DSLikeImpl& source) {
+ digest_ = source.digest_;
+ tag_ = source.tag_;
+ algorithm_ = source.algorithm_;
+ digest_type_ = source.digest_type_;
+ }
+
+ /// \brief Convert the DS-like data to a string.
+ ///
+ /// \return A \c string object that represents the DS-like data.
+ std::string
+ toText() const {
+ using namespace boost;
+ return (lexical_cast<string>(static_cast<int>(tag_)) +
+ " " + lexical_cast<string>(static_cast<int>(algorithm_)) +
+ " " + lexical_cast<string>(static_cast<int>(digest_type_)) +
+ " " + encodeHex(digest_));
+ }
+
+ /// \brief Render the DS-like data in the wire format to an OutputBuffer
+ /// object.
+ ///
+ /// \param buffer An output buffer to store the wire data.
+ void
+ toWire(OutputBuffer& buffer) const {
+ toWireCommon(buffer);
+ }
+
+ /// \brief Render the DS-like data in the wire format to an
+ /// AbstractMessageRenderer object.
+ ///
+ /// \param renderer A renderer object to send the wire data to.
+ void
+ toWire(AbstractMessageRenderer& renderer) const {
+ toWireCommon(renderer);
+ }
+
+ /// \brief Compare two instances of DS-like RDATA.
+ ///
+ /// It is up to the caller to make sure that \c other is an object of the
+ /// same \c DSLikeImpl class.
+ ///
+ /// \param other the right-hand operand to compare against.
+ /// \return < 0 if \c this would be sorted before \c other.
+ /// \return 0 if \c this is identical to \c other in terms of sorting
+ /// order.
+ /// \return > 0 if \c this would be sorted after \c other.
+ int
+ compare(const DSLikeImpl& other_ds) const {
+ if (tag_ != other_ds.tag_) {
+ return (tag_ < other_ds.tag_ ? -1 : 1);
+ }
+ if (algorithm_ != other_ds.algorithm_) {
+ return (algorithm_ < other_ds.algorithm_ ? -1 : 1);
+ }
+ if (digest_type_ != other_ds.digest_type_) {
+ return (digest_type_ < other_ds.digest_type_ ? -1 : 1);
+ }
+
+ size_t this_len = digest_.size();
+ size_t other_len = other_ds.digest_.size();
+ size_t cmplen = min(this_len, other_len);
+ int cmp = memcmp(&digest_[0], &other_ds.digest_[0], cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return ((this_len == other_len)
+ ? 0 : (this_len < other_len) ? -1 : 1);
+ }
+ }
+
+ /// \brief Accessors
+ uint16_t
+ getTag() const {
+ return (tag_);
+ }
+
+private:
+ // straightforward representation of DS RDATA fields
+ uint16_t tag_;
+ uint8_t algorithm_;
+ uint8_t digest_type_;
+ std::vector<uint8_t> digest_;
+};
+
+}
+}
+}
+}
+}
+#endif // __DS_LIKE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/detail/txt_like.h b/src/lib/dns/rdata/generic/detail/txt_like.h
index 392a8ce..a0ab7ac 100644
--- a/src/lib/dns/rdata/generic/detail/txt_like.h
+++ b/src/lib/dns/rdata/generic/detail/txt_like.h
@@ -23,8 +23,24 @@
using namespace std;
using namespace isc::util;
+/// \brief \c rdata::TXTLikeImpl class represents the TXT-like RDATA for TXT
+/// and SPF types.
+///
+/// This class implements the basic interfaces inherited by the TXT and SPF
+/// classes from the abstract \c rdata::Rdata class, and provides trivial
+/// accessors to TXT-like RDATA.
template<class Type, uint16_t typeCode>class TXTLikeImpl {
public:
+ /// \brief Constructor from wire-format data.
+ ///
+ /// \param buffer A buffer storing the wire format data.
+ /// \param rdata_len The length of the RDATA in bytes, normally expected
+ /// to be the value of the RDLENGTH field of the corresponding RR.
+ ///
+ /// <b>Exceptions</b>
+ ///
+ /// \c InvalidRdataLength is thrown if rdata_len exceeds the maximum.
+ /// \c DNSMessageFORMERR is thrown if the RR is misformed.
TXTLikeImpl(InputBuffer& buffer, size_t rdata_len) {
if (rdata_len > MAX_RDLENGTH) {
isc_throw(InvalidRdataLength, "RDLENGTH too large: " << rdata_len);
@@ -52,6 +68,14 @@ public:
} while (rdata_len > 0);
}
+ /// \brief Constructor from string.
+ ///
+ /// <b>Exceptions</b>
+ ///
+ /// \c CharStringTooLong is thrown if the parameter string length exceeds
+ /// maximum.
+ /// \c InvalidRdataText is thrown if the method cannot process the
+ /// parameter data.
explicit TXTLikeImpl(const std::string& txtstr) {
// TBD: this is a simple, incomplete implementation that only supports
// a single character-string.
@@ -86,10 +110,17 @@ public:
string_list_.push_back(data);
}
+ /// \brief The copy constructor.
+ ///
+ /// Trivial for now, we could've used the default one.
TXTLikeImpl(const TXTLikeImpl& other) :
string_list_(other.string_list_)
{}
+ /// \brief Render the TXT-like data in the wire format to an OutputBuffer
+ /// object.
+ ///
+ /// \param buffer An output buffer to store the wire data.
void
toWire(OutputBuffer& buffer) const {
for (vector<vector<uint8_t> >::const_iterator it =
@@ -101,6 +132,11 @@ public:
}
}
+ /// \brief Render the TXT-like data in the wire format to an
+ /// AbstractMessageRenderer object.
+ ///
+ /// \param buffer An output AbstractMessageRenderer to send the wire data
+ /// to.
void
toWire(AbstractMessageRenderer& renderer) const {
for (vector<vector<uint8_t> >::const_iterator it =
@@ -112,6 +148,9 @@ public:
}
}
+ /// \brief Convert the TXT-like data to a string.
+ ///
+ /// \return A \c string object that represents the TXT-like data.
string
toText() const {
string s;
@@ -134,20 +173,33 @@ public:
return (s);
}
+ /// \brief Compare two instances of TXT-like RDATA.
+ ///
+ /// It is up to the caller to make sure that \c other is an object of the
+ /// same \c TXTLikeImpl class.
+ ///
+ /// \param other the right-hand operand to compare against.
+ /// \return < 0 if \c this would be sorted before \c other.
+ /// \return 0 if \c this is identical to \c other in terms of sorting
+ /// order.
+ /// \return > 0 if \c this would be sorted after \c other.
int
compare(const TXTLikeImpl& other) const {
// This implementation is not efficient. Revisit this (TBD).
OutputBuffer this_buffer(0);
toWire(this_buffer);
+ uint8_t const* const this_data = (uint8_t const*)this_buffer.getData();
size_t this_len = this_buffer.getLength();
OutputBuffer other_buffer(0);
other.toWire(other_buffer);
+ uint8_t const* const other_data
+ = (uint8_t const*)other_buffer.getData();
const size_t other_len = other_buffer.getLength();
const size_t cmplen = min(this_len, other_len);
- const int cmp = memcmp(this_buffer.getData(), other_buffer.getData(),
- cmplen);
+ const int cmp = memcmp(this_data, other_data, cmplen);
+
if (cmp != 0) {
return (cmp);
} else {
diff --git a/src/lib/dns/rdata/generic/dlv_32769.cc b/src/lib/dns/rdata/generic/dlv_32769.cc
new file mode 100644
index 0000000..9887aa8
--- /dev/null
+++ b/src/lib/dns/rdata/generic/dlv_32769.cc
@@ -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.
+
+#include <string>
+
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/ds_like.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns::rdata::generic::detail;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief Constructor from string.
+///
+/// A copy of the implementation object is allocated and constructed.
+DLV::DLV(const string& ds_str) :
+ impl_(new DLVImpl(ds_str))
+{}
+
+/// \brief Constructor from wire-format data.
+///
+/// A copy of the implementation object is allocated and constructed.
+DLV::DLV(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new DLVImpl(buffer, rdata_len))
+{}
+
+/// \brief Copy constructor
+///
+/// A copy of the implementation object is allocated and constructed.
+DLV::DLV(const DLV& source) :
+ Rdata(), impl_(new DLVImpl(*source.impl_))
+{}
+
+/// \brief Assignment operator
+///
+/// PIMPL-induced logic
+DLV&
+DLV::operator=(const DLV& source) {
+ if (impl_ == source.impl_) {
+ return (*this);
+ }
+
+ DLVImpl* newimpl = new DLVImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+/// \brief Destructor
+///
+/// Deallocates an internal resource.
+DLV::~DLV() {
+ delete impl_;
+}
+
+/// \brief Convert the \c DLV to a string.
+///
+/// A pass-thru to the corresponding implementation method.
+string
+DLV::toText() const {
+ return (impl_->toText());
+}
+
+/// \brief Render the \c DLV in the wire format to a OutputBuffer object
+///
+/// A pass-thru to the corresponding implementation method.
+void
+DLV::toWire(OutputBuffer& buffer) const {
+ impl_->toWire(buffer);
+}
+
+/// \brief Render the \c DLV in the wire format to a AbstractMessageRenderer
+/// object
+///
+/// A pass-thru to the corresponding implementation method.
+void
+DLV::toWire(AbstractMessageRenderer& renderer) const {
+ impl_->toWire(renderer);
+}
+
+/// \brief Compare two instances of \c DLV RDATA.
+///
+/// The type check is performed here. Otherwise, a pass-thru to the
+/// corresponding implementation method.
+int
+DLV::compare(const Rdata& other) const {
+ const DLV& other_ds = dynamic_cast<const DLV&>(other);
+
+ return (impl_->compare(*other_ds.impl_));
+}
+
+/// \brief Tag accessor
+uint16_t
+DLV::getTag() const {
+ return (impl_->getTag());
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/dlv_32769.h b/src/lib/dns/rdata/generic/dlv_32769.h
new file mode 100644
index 0000000..86cd98c
--- /dev/null
+++ b/src/lib/dns/rdata/generic/dlv_32769.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.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+namespace detail {
+template <class Type, uint16_t typeCode> class DSLikeImpl;
+}
+
+/// \brief \c rdata::generic::DLV class represents the DLV RDATA as defined in
+/// RFC4431.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// DLV RDATA.
+class DLV : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ /// \brief Assignment operator.
+ ///
+ /// It internally allocates a resource, and if it fails a corresponding
+ /// standard exception will be thrown.
+ /// This operator never throws an exception otherwise.
+ ///
+ /// This operator provides the strong exception guarantee: When an
+ /// exception is thrown the content of the assignment target will be
+ /// intact.
+ DLV& operator=(const DLV& source);
+
+ /// \brief The destructor.
+ ~DLV();
+
+ /// \brief Return the value of the Tag field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getTag() const;
+private:
+ typedef detail::DSLikeImpl<DLV, 32769> DLVImpl;
+ DLVImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/ds_43.cc b/src/lib/dns/rdata/generic/ds_43.cc
index 1b48456..20b62dc 100644
--- a/src/lib/dns/rdata/generic/ds_43.cc
+++ b/src/lib/dns/rdata/generic/ds_43.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,87 +12,32 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <iostream>
#include <string>
-#include <sstream>
-#include <vector>
-
-#include <boost/lexical_cast.hpp>
#include <util/buffer.h>
#include <util/encode/hex.h>
#include <dns/messagerenderer.h>
-#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
-#include <stdio.h>
-#include <time.h>
+#include <dns/rdata/generic/detail/ds_like.h>
using namespace std;
using namespace isc::util;
using namespace isc::util::encode;
+using namespace isc::dns::rdata::generic::detail;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-struct DSImpl {
- // straightforward representation of DS RDATA fields
- DSImpl(uint16_t tag, uint8_t algorithm, uint8_t digest_type,
- const vector<uint8_t>& digest) :
- tag_(tag), algorithm_(algorithm), digest_type_(digest_type),
- digest_(digest)
- {}
-
- uint16_t tag_;
- uint8_t algorithm_;
- uint8_t digest_type_;
- const vector<uint8_t> digest_;
-};
-
DS::DS(const string& ds_str) :
- impl_(NULL)
-{
- istringstream iss(ds_str);
- unsigned int tag, algorithm, digest_type;
- stringbuf digestbuf;
-
- iss >> tag >> algorithm >> digest_type >> &digestbuf;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid DS text");
- }
- if (tag > 0xffff) {
- isc_throw(InvalidRdataText, "DS tag out of range");
- }
- if (algorithm > 0xff) {
- isc_throw(InvalidRdataText, "DS algorithm out of range");
- }
- if (digest_type > 0xff) {
- isc_throw(InvalidRdataText, "DS digest type out of range");
- }
-
- vector<uint8_t> digest;
- decodeHex(digestbuf.str(), digest);
-
- impl_ = new DSImpl(tag, algorithm, digest_type, digest);
-}
-
-DS::DS(InputBuffer& buffer, size_t rdata_len) {
- if (rdata_len < 4) {
- isc_throw(InvalidRdataLength, "DS too short");
- }
-
- uint16_t tag = buffer.readUint16();
- uint16_t algorithm = buffer.readUint8();
- uint16_t digest_type = buffer.readUint8();
-
- rdata_len -= 4;
- vector<uint8_t> digest(rdata_len);
- buffer.readData(&digest[0], rdata_len);
+ impl_(new DSImpl(ds_str))
+{}
- impl_ = new DSImpl(tag, algorithm, digest_type, digest);
-}
+DS::DS(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new DSImpl(buffer, rdata_len))
+{}
DS::DS(const DS& source) :
Rdata(), impl_(new DSImpl(*source.impl_))
@@ -117,57 +62,29 @@ DS::~DS() {
string
DS::toText() const {
- using namespace boost;
- return (lexical_cast<string>(static_cast<int>(impl_->tag_)) +
- " " + lexical_cast<string>(static_cast<int>(impl_->algorithm_)) +
- " " + lexical_cast<string>(static_cast<int>(impl_->digest_type_)) +
- " " + encodeHex(impl_->digest_));
+ return (impl_->toText());
}
void
DS::toWire(OutputBuffer& buffer) const {
- buffer.writeUint16(impl_->tag_);
- buffer.writeUint8(impl_->algorithm_);
- buffer.writeUint8(impl_->digest_type_);
- buffer.writeData(&impl_->digest_[0], impl_->digest_.size());
+ impl_->toWire(buffer);
}
void
DS::toWire(AbstractMessageRenderer& renderer) const {
- renderer.writeUint16(impl_->tag_);
- renderer.writeUint8(impl_->algorithm_);
- renderer.writeUint8(impl_->digest_type_);
- renderer.writeData(&impl_->digest_[0], impl_->digest_.size());
+ impl_->toWire(renderer);
}
int
DS::compare(const Rdata& other) const {
const DS& other_ds = dynamic_cast<const DS&>(other);
- if (impl_->tag_ != other_ds.impl_->tag_) {
- return (impl_->tag_ < other_ds.impl_->tag_ ? -1 : 1);
- }
- if (impl_->algorithm_ != other_ds.impl_->algorithm_) {
- return (impl_->algorithm_ < other_ds.impl_->algorithm_ ? -1 : 1);
- }
- if (impl_->digest_type_ != other_ds.impl_->digest_type_) {
- return (impl_->digest_type_ < other_ds.impl_->digest_type_ ? -1 : 1);
- }
-
- size_t this_len = impl_->digest_.size();
- size_t other_len = other_ds.impl_->digest_.size();
- size_t cmplen = min(this_len, other_len);
- int cmp = memcmp(&impl_->digest_[0], &other_ds.impl_->digest_[0], cmplen);
- if (cmp != 0) {
- return (cmp);
- } else {
- return ((this_len == other_len) ? 0 : (this_len < other_len) ? -1 : 1);
- }
+ return (impl_->compare(*other_ds.impl_));
}
uint16_t
DS::getTag() const {
- return (impl_->tag_);
+ return (impl_->getTag());
}
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/ds_43.h b/src/lib/dns/rdata/generic/ds_43.h
index 03b19a0..2697f51 100644
--- a/src/lib/dns/rdata/generic/ds_43.h
+++ b/src/lib/dns/rdata/generic/ds_43.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,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+// BEGIN_HEADER_GUARD
+
#include <stdint.h>
#include <string>
@@ -21,8 +23,6 @@
#include <dns/rrttl.h>
#include <dns/rdata.h>
-// BEGIN_HEADER_GUARD
-
// BEGIN_ISC_NAMESPACE
// BEGIN_COMMON_DECLARATIONS
@@ -30,20 +30,41 @@
// BEGIN_RDATA_NAMESPACE
-struct DSImpl;
+namespace detail {
+template <class Type, uint16_t typeCode> class DSLikeImpl;
+}
+/// \brief \c rdata::generic::DS class represents the DS RDATA as defined in
+/// RFC3658.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// DS RDATA.
class DS : public Rdata {
public:
// BEGIN_COMMON_MEMBERS
// END_COMMON_MEMBERS
+
+ /// \brief Assignment operator.
+ ///
+ /// It internally allocates a resource, and if it fails a corresponding
+ /// standard exception will be thrown.
+ /// This operator never throws an exception otherwise.
+ ///
+ /// This operator provides the strong exception guarantee: When an
+ /// exception is thrown the content of the assignment target will be
+ /// intact.
DS& operator=(const DS& source);
+
+ /// \brief The destructor.
~DS();
+ /// \brief Return the value of the Tag field.
///
- /// Specialized methods
- ///
+ /// This method never throws an exception.
uint16_t getTag() const;
private:
+ typedef detail::DSLikeImpl<DS, 43> DSImpl;
DSImpl* impl_;
};
diff --git a/src/lib/dns/rdata/generic/spf_99.cc b/src/lib/dns/rdata/generic/spf_99.cc
index 492de98..aa3e4a1 100644
--- a/src/lib/dns/rdata/generic/spf_99.cc
+++ b/src/lib/dns/rdata/generic/spf_99.cc
@@ -30,8 +30,17 @@ using namespace isc::util;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class. The semantics of the class is provided by
+/// a copy of instantiated TXTLikeImpl class common to both TXT and SPF.
+
#include <dns/rdata/generic/detail/txt_like.h>
+/// \brief The assignment operator
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
+/// This method never throws an exception otherwise.
SPF&
SPF::operator=(const SPF& source) {
if (impl_ == source.impl_) {
@@ -45,37 +54,72 @@ SPF::operator=(const SPF& source) {
return (*this);
}
+/// \brief The destructor
SPF::~SPF() {
delete impl_;
}
+/// \brief Constructor from wire-format data.
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
SPF::SPF(InputBuffer& buffer, size_t rdata_len) :
impl_(new SPFImpl(buffer, rdata_len))
{}
+/// \brief Constructor from string.
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
SPF::SPF(const std::string& txtstr) :
impl_(new SPFImpl(txtstr))
{}
+/// \brief Copy constructor
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
SPF::SPF(const SPF& other) :
Rdata(), impl_(new SPFImpl(*other.impl_))
{}
+/// \brief Render the \c SPF in the wire format to a OutputBuffer object
+///
+/// \return is the return of the corresponding implementation method.
void
SPF::toWire(OutputBuffer& buffer) const {
impl_->toWire(buffer);
}
+/// \brief Render the \c SPF in the wire format to an AbstractMessageRenderer
+/// object
+///
+/// \return is the return of the corresponding implementation method.
void
SPF::toWire(AbstractMessageRenderer& renderer) const {
impl_->toWire(renderer);
}
+/// \brief Convert the \c SPF to a string.
+///
+/// \return is the return of the corresponding implementation method.
string
SPF::toText() const {
return (impl_->toText());
}
+/// \brief Compare two instances of \c SPF RDATA.
+///
+/// This method compares \c this and the \c other \c SPF objects.
+///
+/// This method is expected to be used in a polymorphic way, and the
+/// parameter to compare against is therefore of the abstract \c Rdata class.
+/// However, comparing two \c Rdata objects of different RR types
+/// is meaningless, and \c other must point to a \c SPF object;
+/// otherwise, the standard \c bad_cast exception will be thrown.
+///
+/// \param other the right-hand operand to compare against.
+/// \return is the return of the corresponding implementation method.
int
SPF::compare(const Rdata& other) const {
const SPF& other_txt = dynamic_cast<const SPF&>(other);
diff --git a/src/lib/dns/rdata/generic/spf_99.h b/src/lib/dns/rdata/generic/spf_99.h
index 956adb9..04ac99b 100644
--- a/src/lib/dns/rdata/generic/spf_99.h
+++ b/src/lib/dns/rdata/generic/spf_99.h
@@ -30,14 +30,40 @@
template<class Type, uint16_t typeCode> class TXTLikeImpl;
+/// \brief \c rdata::SPF class represents the SPF RDATA as defined %in
+/// RFC4408.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class. The semantics of the class is provided by
+/// a copy of instantiated TXTLikeImpl class common to both TXT and SPF.
class SPF : public Rdata {
public:
// BEGIN_COMMON_MEMBERS
// END_COMMON_MEMBERS
+ /// \brief Assignment operator.
+ ///
+ /// It internally allocates a resource, and if it fails a corresponding
+ /// standard exception will be thrown.
+ /// This operator never throws an exception otherwise.
+ ///
+ /// This operator provides the strong exception guarantee: When an
+ /// exception is thrown the content of the assignment target will be
+ /// intact.
SPF& operator=(const SPF& source);
+
+ /// \brief The destructor.
~SPF();
+ ///
+ /// Specialized methods
+ ///
+
+ /// \brief Return a reference to the data strings
+ ///
+ /// This method never throws an exception.
+ const std::vector<std::vector<uint8_t> >& getString() const;
+
private:
typedef TXTLikeImpl<SPF, 99> SPFImpl;
SPFImpl* impl_;
diff --git a/src/lib/dns/rdata/in_1/dhcid_49.cc b/src/lib/dns/rdata/in_1/dhcid_49.cc
index 0a9a23c..f0c4aca 100644
--- a/src/lib/dns/rdata/in_1/dhcid_49.cc
+++ b/src/lib/dns/rdata/in_1/dhcid_49.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
@@ -20,7 +20,7 @@
#include <exceptions/exceptions.h>
#include <util/buffer.h>
-#include <util/encode/hex.h>
+#include <util/encode/base64.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
@@ -52,7 +52,7 @@ DHCID::DHCID(const string& dhcid_str) {
stringbuf digestbuf;
iss >> &digestbuf;
- isc::util::encode::decodeHex(digestbuf.str(), digest_);
+ isc::util::encode::decodeBase64(digestbuf.str(), digest_);
// RFC4701 states DNS software should consider the RDATA section to
// be opaque, but there must be at least three bytes in the data:
@@ -112,7 +112,7 @@ DHCID::toWire(AbstractMessageRenderer& renderer) const {
/// \return A string representation of \c DHCID.
string
DHCID::toText() const {
- return (isc::util::encode::encodeHex(digest_));
+ return (isc::util::encode::encodeBase64(digest_));
}
/// \brief Compare two instances of \c DHCID RDATA.
diff --git a/src/lib/dns/rdata/in_1/dhcid_49.h b/src/lib/dns/rdata/in_1/dhcid_49.h
index 919395f..90f5fab 100644
--- a/src/lib/dns/rdata/in_1/dhcid_49.h
+++ b/src/lib/dns/rdata/in_1/dhcid_49.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index 5f90cea..e4c577e 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -29,13 +29,15 @@ run_unittests_SOURCES += rdata_unittest.h rdata_unittest.cc
run_unittests_SOURCES += rdatafields_unittest.cc
run_unittests_SOURCES += rdata_in_a_unittest.cc rdata_in_aaaa_unittest.cc
run_unittests_SOURCES += rdata_ns_unittest.cc rdata_soa_unittest.cc
-run_unittests_SOURCES += rdata_txt_unittest.cc rdata_mx_unittest.cc
+run_unittests_SOURCES += rdata_txt_like_unittest.cc
+run_unittests_SOURCES += rdata_mx_unittest.cc
run_unittests_SOURCES += rdata_ptr_unittest.cc rdata_cname_unittest.cc
run_unittests_SOURCES += rdata_dname_unittest.cc
run_unittests_SOURCES += rdata_afsdb_unittest.cc
run_unittests_SOURCES += rdata_opt_unittest.cc
+run_unittests_SOURCES += rdata_dhcid_unittest.cc
run_unittests_SOURCES += rdata_dnskey_unittest.cc
-run_unittests_SOURCES += rdata_ds_unittest.cc
+run_unittests_SOURCES += rdata_ds_like_unittest.cc
run_unittests_SOURCES += rdata_nsec_unittest.cc
run_unittests_SOURCES += rdata_nsec3_unittest.cc
run_unittests_SOURCES += rdata_nsecbitmap_unittest.cc
@@ -71,4 +73,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
diff --git a/src/lib/dns/tests/rdata_dhcid_unittest.cc b/src/lib/dns/tests/rdata_dhcid_unittest.cc
new file mode 100644
index 0000000..9df7043
--- /dev/null
+++ b/src/lib/dns/tests/rdata_dhcid_unittest.cc
@@ -0,0 +1,111 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/buffer.h>
+#include <dns/rdataclass.h>
+#include <util/encode/base64.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns::rdata;
+
+namespace {
+
+const string string_dhcid(
+ "0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=");
+
+const in::DHCID rdata_dhcid(string_dhcid);
+
+class Rdata_DHCID_Test : public RdataTest {
+};
+
+TEST_F(Rdata_DHCID_Test, createFromString) {
+ const in::DHCID rdata_dhcid2(string_dhcid);
+ EXPECT_EQ(0, rdata_dhcid2.compare(rdata_dhcid));
+}
+
+TEST_F(Rdata_DHCID_Test, badBase64) {
+ EXPECT_THROW(const in::DHCID rdata_dhcid_bad("00"), isc::BadValue);
+}
+
+TEST_F(Rdata_DHCID_Test, badLength) {
+ EXPECT_THROW(const in::DHCID rdata_dhcid_bad("MDA="), InvalidRdataLength);
+}
+
+TEST_F(Rdata_DHCID_Test, copy) {
+ const in::DHCID rdata_dhcid2(rdata_dhcid);
+ EXPECT_EQ(0, rdata_dhcid.compare(rdata_dhcid2));
+}
+
+TEST_F(Rdata_DHCID_Test, createFromWire) {
+ EXPECT_EQ(0, rdata_dhcid.compare(
+ *rdataFactoryFromFile(RRType("DHCID"), RRClass("IN"),
+ "rdata_dhcid_fromWire")));
+ // TBD: more tests
+}
+
+TEST_F(Rdata_DHCID_Test, toWireRenderer) {
+ rdata_dhcid.toWire(renderer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_dhcid_toWire", data);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+ renderer.getLength(), &data[0], data.size());
+}
+
+TEST_F(Rdata_DHCID_Test, toWireBuffer) {
+ rdata_dhcid.toWire(obuffer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_dhcid_toWire", data);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
+ obuffer.getLength(), &data[0], data.size());
+}
+
+TEST_F(Rdata_DHCID_Test, toText) {
+ EXPECT_EQ(string_dhcid, rdata_dhcid.toText());
+}
+
+TEST_F(Rdata_DHCID_Test, getDHCIDDigest) {
+ const string string_dhcid1(encodeBase64(rdata_dhcid.getDigest()));
+
+ EXPECT_EQ(string_dhcid, string_dhcid1);
+}
+
+TEST_F(Rdata_DHCID_Test, compare) {
+ // trivial case: self equivalence
+ EXPECT_EQ(0, rdata_dhcid.compare(rdata_dhcid));
+
+ in::DHCID rdata_dhcid1("0YLQvtC/0L7Qu9GPINC00LLQsCDRgNGD0LHQu9GP");
+ in::DHCID rdata_dhcid2("0YLQvtC/0L7Qu9GPINGC0YDQuCDRgNGD0LHQu9GP");
+ in::DHCID rdata_dhcid3("0YLQvtC/0L7Qu9GPINGH0LXRgtGL0YDQtSDRgNGD0LHQu9GP");
+
+ EXPECT_LT(rdata_dhcid1.compare(rdata_dhcid2), 0);
+ EXPECT_GT(rdata_dhcid2.compare(rdata_dhcid1), 0);
+
+ EXPECT_LT(rdata_dhcid2.compare(rdata_dhcid3), 0);
+ EXPECT_GT(rdata_dhcid3.compare(rdata_dhcid2), 0);
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(rdata_dhcid.compare(*rdata_nomatch), bad_cast);
+}
+}
diff --git a/src/lib/dns/tests/rdata_ds_like_unittest.cc b/src/lib/dns/tests/rdata_ds_like_unittest.cc
new file mode 100644
index 0000000..9b29446
--- /dev/null
+++ b/src/lib/dns/tests/rdata_ds_like_unittest.cc
@@ -0,0 +1,171 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <algorithm>
+#include <string>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+namespace {
+// hacks to make templates work
+template <class T>
+class RRTYPE : public RRType {
+public:
+ RRTYPE();
+};
+
+template<> RRTYPE<generic::DS>::RRTYPE() : RRType(RRType::DS()) {}
+template<> RRTYPE<generic::DLV>::RRTYPE() : RRType(RRType::DLV()) {}
+
+template <class DS_LIKE>
+class Rdata_DS_LIKE_Test : public RdataTest {
+protected:
+ static DS_LIKE const rdata_ds_like;
+};
+
+string ds_like_txt("12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+
+template <class DS_LIKE>
+DS_LIKE const Rdata_DS_LIKE_Test<DS_LIKE>::rdata_ds_like(ds_like_txt);
+
+// The list of types we want to test.
+typedef testing::Types<generic::DS, generic::DLV> Implementations;
+
+TYPED_TEST_CASE(Rdata_DS_LIKE_Test, Implementations);
+
+TYPED_TEST(Rdata_DS_LIKE_Test, toText_DS_LIKE) {
+ EXPECT_EQ(ds_like_txt, this->rdata_ds_like.toText());
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, badText_DS_LIKE) {
+ EXPECT_THROW(const TypeParam ds_like2("99999 5 2 BEEF"), InvalidRdataText);
+ EXPECT_THROW(const TypeParam ds_like2("11111 555 2 BEEF"),
+ InvalidRdataText);
+ EXPECT_THROW(const TypeParam ds_like2("11111 5 22222 BEEF"),
+ InvalidRdataText);
+ EXPECT_THROW(const TypeParam ds_like2("11111 5 2"), InvalidRdataText);
+ EXPECT_THROW(const TypeParam ds_like2("GARBAGE IN"), InvalidRdataText);
+ // no space between the digest type and the digest.
+ EXPECT_THROW(const TypeParam ds_like2(
+ "12892 5 2F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5"), InvalidRdataText);
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, createFromWire_DS_LIKE) {
+ EXPECT_EQ(0, this->rdata_ds_like.compare(
+ *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass::IN(),
+ "rdata_ds_fromWire")));
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, assignment_DS_LIKE) {
+ TypeParam copy((string(ds_like_txt)));
+ copy = this->rdata_ds_like;
+ EXPECT_EQ(0, copy.compare(this->rdata_ds_like));
+
+ // Check if the copied data is valid even after the original is deleted
+ TypeParam* copy2 = new TypeParam(this->rdata_ds_like);
+ TypeParam copy3((string(ds_like_txt)));
+ copy3 = *copy2;
+ delete copy2;
+ EXPECT_EQ(0, copy3.compare(this->rdata_ds_like));
+
+ // Self assignment
+ copy = copy;
+ EXPECT_EQ(0, copy.compare(this->rdata_ds_like));
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, getTag_DS_LIKE) {
+ EXPECT_EQ(12892, this->rdata_ds_like.getTag());
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, toWireRenderer) {
+ Rdata_DS_LIKE_Test<TypeParam>::renderer.skip(2);
+ TypeParam rdata_ds_like(ds_like_txt);
+ rdata_ds_like.toWire(this->renderer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_ds_fromWire", data);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ static_cast<const uint8_t*>
+ (this->obuffer.getData()) + 2,
+ this->obuffer.getLength() - 2,
+ &data[2], data.size() - 2);
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, toWireBuffer) {
+ TypeParam rdata_ds_like(ds_like_txt);
+ rdata_ds_like.toWire(this->obuffer);
+}
+
+string ds_like_txt1("12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+// different tag
+string ds_like_txt2("12893 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+// different algorithm
+string ds_like_txt3("12892 6 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+// different digest type
+string ds_like_txt4("12892 5 3 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+// different digest
+string ds_like_txt5("12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+// different digest length
+string ds_like_txt6("12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B555");
+
+TYPED_TEST(Rdata_DS_LIKE_Test, compare) {
+ // trivial case: self equivalence
+ EXPECT_EQ(0, TypeParam(ds_like_txt).compare(TypeParam(ds_like_txt)));
+
+ // non-equivalence tests
+ EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt2)), 0);
+ EXPECT_GT(TypeParam(ds_like_txt2).compare(TypeParam(ds_like_txt1)), 0);
+
+ EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt3)), 0);
+ EXPECT_GT(TypeParam(ds_like_txt3).compare(TypeParam(ds_like_txt1)), 0);
+
+ EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt4)), 0);
+ EXPECT_GT(TypeParam(ds_like_txt4).compare(TypeParam(ds_like_txt1)), 0);
+
+ EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt5)), 0);
+ EXPECT_GT(TypeParam(ds_like_txt5).compare(TypeParam(ds_like_txt1)), 0);
+
+ EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt6)), 0);
+ EXPECT_GT(TypeParam(ds_like_txt6).compare(TypeParam(ds_like_txt1)), 0);
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(this->rdata_ds_like.compare(*this->rdata_nomatch),
+ bad_cast);
+}
+
+}
diff --git a/src/lib/dns/tests/rdata_ds_unittest.cc b/src/lib/dns/tests/rdata_ds_unittest.cc
deleted file mode 100644
index 5988620..0000000
--- a/src/lib/dns/tests/rdata_ds_unittest.cc
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <string>
-
-#include <util/buffer.h>
-#include <dns/messagerenderer.h>
-#include <dns/rdata.h>
-#include <dns/rdataclass.h>
-#include <dns/rrclass.h>
-#include <dns/rrtype.h>
-
-#include <gtest/gtest.h>
-
-#include <dns/tests/unittest_util.h>
-#include <dns/tests/rdata_unittest.h>
-
-using isc::UnitTestUtil;
-using namespace std;
-using namespace isc::dns;
-using namespace isc::util;
-using namespace isc::dns::rdata;
-
-namespace {
-class Rdata_DS_Test : public RdataTest {
- // there's nothing to specialize
-};
-
-string ds_txt("12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
- "5F0EB5C777586DE18DA6B5");
-const generic::DS rdata_ds(ds_txt);
-
-TEST_F(Rdata_DS_Test, toText_DS) {
- EXPECT_EQ(ds_txt, rdata_ds.toText());
-}
-
-TEST_F(Rdata_DS_Test, badText_DS) {
- EXPECT_THROW(const generic::DS ds2("99999 5 2 BEEF"), InvalidRdataText);
- EXPECT_THROW(const generic::DS ds2("11111 555 2 BEEF"), InvalidRdataText);
- EXPECT_THROW(const generic::DS ds2("11111 5 22222 BEEF"), InvalidRdataText);
- EXPECT_THROW(const generic::DS ds2("11111 5 2"), InvalidRdataText);
- EXPECT_THROW(const generic::DS ds2("GARBAGE IN"), InvalidRdataText);
-}
-
-// this test currently fails; we must fix it, and then migrate the test to
-// badText_DS
-TEST_F(Rdata_DS_Test, DISABLED_badText_DS) {
- // no space between the digest type and the digest.
- EXPECT_THROW(const generic::DS ds2(
- "12892 5 2F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
- "5F0EB5C777586DE18DA6B5"), InvalidRdataText);
-}
-
-TEST_F(Rdata_DS_Test, createFromWire_DS) {
- EXPECT_EQ(0, rdata_ds.compare(
- *rdataFactoryFromFile(RRType::DS(), RRClass::IN(),
- "rdata_ds_fromWire")));
-}
-
-TEST_F(Rdata_DS_Test, getTag_DS) {
- EXPECT_EQ(12892, rdata_ds.getTag());
-}
-
-TEST_F(Rdata_DS_Test, toWireRenderer) {
- renderer.skip(2);
- generic::DS rdata_ds(ds_txt);
- rdata_ds.toWire(renderer);
-
- vector<unsigned char> data;
- UnitTestUtil::readWireData("rdata_ds_fromWire", data);
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- static_cast<const uint8_t *>(obuffer.getData()) + 2,
- obuffer.getLength() - 2, &data[2], data.size() - 2);
-}
-
-TEST_F(Rdata_DS_Test, toWireBuffer) {
- generic::DS rdata_ds(ds_txt);
- rdata_ds.toWire(obuffer);
-}
-
-TEST_F(Rdata_DS_Test, compare) {
- // trivial case: self equivalence
- EXPECT_EQ(0, generic::DS(ds_txt).compare(generic::DS(ds_txt)));
-
- // TODO: need more tests
-}
-
-}
diff --git a/src/lib/dns/tests/rdata_txt_like_unittest.cc b/src/lib/dns/tests/rdata_txt_like_unittest.cc
new file mode 100644
index 0000000..981265e
--- /dev/null
+++ b/src/lib/dns/tests/rdata_txt_like_unittest.cc
@@ -0,0 +1,261 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 common code for TXT and SPF tests.
+
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/rdataclass.h>
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+
+template<class T>
+class RRTYPE : public RRType {
+public:
+ RRTYPE();
+};
+
+template<> RRTYPE<generic::TXT>::RRTYPE() : RRType(RRType::TXT()) {}
+template<> RRTYPE<generic::SPF>::RRTYPE() : RRType(RRType::SPF()) {}
+
+namespace {
+const uint8_t wiredata_txt_like[] = {
+ sizeof("Test String") - 1,
+ 'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+};
+
+const uint8_t wiredata_nulltxt[] = { 0 };
+vector<uint8_t> wiredata_longesttxt(256, 'a');
+
+template<class TXT_LIKE>
+class Rdata_TXT_LIKE_Test : public RdataTest {
+protected:
+ Rdata_TXT_LIKE_Test() {
+ wiredata_longesttxt[0] = 255; // adjust length
+ }
+
+ static const TXT_LIKE rdata_txt_like;
+ static const TXT_LIKE rdata_txt_like_empty;
+ static const TXT_LIKE rdata_txt_like_quoted;
+};
+
+template<class TXT_LIKE>
+const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like("Test String");
+
+template<class TXT_LIKE>
+const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like_empty("");
+
+template<class TXT_LIKE>
+const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like_quoted
+ ("\"Test String\"");
+
+// The list of types we want to test.
+typedef testing::Types<generic::TXT, generic::SPF> Implementations;
+
+TYPED_TEST_CASE(Rdata_TXT_LIKE_Test, Implementations);
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, createFromText) {
+ // normal case is covered in toWireBuffer.
+
+ // surrounding double-quotes shouldn't change the result.
+ EXPECT_EQ(0, this->rdata_txt_like.compare(this->rdata_txt_like_quoted));
+
+ // Null character-string.
+ this->obuffer.clear();
+ TypeParam(string("")).toWire(this->obuffer);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ this->obuffer.getData(),
+ this->obuffer.getLength(),
+ wiredata_nulltxt, sizeof(wiredata_nulltxt));
+
+ // Longest possible character-string.
+ this->obuffer.clear();
+ TypeParam(string(255, 'a')).toWire(this->obuffer);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ this->obuffer.getData(),
+ this->obuffer.getLength(),
+ &wiredata_longesttxt[0], wiredata_longesttxt.size());
+
+ // Too long text for a valid character-string.
+ EXPECT_THROW(TypeParam(string(256, 'a')), CharStringTooLong);
+
+ // The escape character makes the double quote a part of character-string,
+ // so this is invalid input and should be rejected.
+ EXPECT_THROW(TypeParam("\"Test String\\\""), InvalidRdataText);
+
+ // Terminating double-quote is provided, so this is valid, but in this
+ // version of implementation we reject escaped characters.
+ EXPECT_THROW(TypeParam("\"Test String\\\"\""), InvalidRdataText);
+}
+
+void
+makeLargest(vector<uint8_t>& data) {
+ uint8_t ch = 0;
+
+ // create 255 sets of character-strings, each of which has the longest
+ // length (255bytes string + 1-byte length field)
+ for (int i = 0; i < 255; ++i, ++ch) {
+ data.push_back(255);
+ data.insert(data.end(), 255, ch);
+ }
+ // the last character-string should be 255 bytes (including the one-byte
+ // length field) in length so that the total length should be in the range
+ // of 16-bit integers.
+ data.push_back(254);
+ data.insert(data.end(), 254, ch);
+
+ assert(data.size() == 65535);
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) {
+ EXPECT_EQ(0, this->rdata_txt_like.compare(
+ *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
+ "rdata_txt_fromWire1")));
+
+ // Empty character string
+ EXPECT_EQ(0, this->rdata_txt_like_empty.compare(
+ *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
+ "rdata_txt_fromWire2.wire")));
+
+ // Multiple character strings
+ this->obuffer.clear();
+ this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
+ "rdata_txt_fromWire3.wire")->toWire(this->obuffer);
+ // the result should be 'wiredata_txt' repeated twice
+ vector<uint8_t> expected_data(wiredata_txt_like, wiredata_txt_like +
+ sizeof(wiredata_txt_like));
+ expected_data.insert(expected_data.end(), wiredata_txt_like,
+ wiredata_txt_like + sizeof(wiredata_txt_like));
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ this->obuffer.getData(),
+ this->obuffer.getLength(),
+ &expected_data[0], expected_data.size());
+
+ // Largest length of data. There's nothing special, but should be
+ // constructed safely, and the content should be identical to the original
+ // data.
+ vector<uint8_t> largest_txt_like_data;
+ makeLargest(largest_txt_like_data);
+ InputBuffer ibuffer(&largest_txt_like_data[0],
+ largest_txt_like_data.size());
+ TypeParam largest_txt_like(ibuffer, largest_txt_like_data.size());
+ this->obuffer.clear();
+ largest_txt_like.toWire(this->obuffer);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ this->obuffer.getData(),
+ this->obuffer.getLength(),
+ &largest_txt_like_data[0],
+ largest_txt_like_data.size());
+
+ // rdlen parameter is out of range. This is a rare event because we'd
+ // normally call the constructor via a polymorphic wrapper, where the
+ // length is validated. But this should be checked explicitly.
+ InputBuffer ibuffer2(&largest_txt_like_data[0],
+ largest_txt_like_data.size());
+ EXPECT_THROW(TypeParam(ibuffer2, 65536), InvalidRdataLength);
+
+ // RDATA is empty, which is invalid for TXT_LIKE.
+ EXPECT_THROW(this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
+ "rdata_txt_fromWire4.wire"),
+ DNSMessageFORMERR);
+
+ // character-string length is too large, which could cause overrun.
+ EXPECT_THROW(this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
+ "rdata_txt_fromWire5.wire"),
+ DNSMessageFORMERR);
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, toWireBuffer) {
+ this->rdata_txt_like.toWire(this->obuffer);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ this->obuffer.getData(),
+ this->obuffer.getLength(),
+ wiredata_txt_like, sizeof(wiredata_txt_like));
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, toWireRenderer) {
+ this->rdata_txt_like.toWire(this->renderer);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ this->renderer.getData(),
+ this->renderer.getLength(),
+ wiredata_txt_like, sizeof(wiredata_txt_like));
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, toText) {
+ EXPECT_EQ("\"Test String\"", this->rdata_txt_like.toText());
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, assignment) {
+ TypeParam rdata1("assignment1");
+ TypeParam rdata2("assignment2");
+ rdata1 = rdata2;
+ EXPECT_EQ(0, rdata2.compare(rdata1));
+
+ // Check if the copied data is valid even after the original is deleted
+ TypeParam* rdata3 = new TypeParam(rdata1);
+ TypeParam rdata4("assignment3");
+ rdata4 = *rdata3;
+ delete rdata3;
+ EXPECT_EQ(0, rdata4.compare(rdata1));
+
+ // Self assignment
+ rdata2 = rdata2;
+ EXPECT_EQ(0, rdata2.compare(rdata1));
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, compare) {
+ string const txt1("aaaaaaaa");
+ string const txt2("aaaaaaaaaa");
+ string const txt3("bbbbbbbb");
+ string const txt4(129, 'a');
+ string const txt5(128, 'b');
+
+ EXPECT_EQ(TypeParam(txt1).compare(TypeParam(txt1)), 0);
+
+ EXPECT_LT(TypeParam("").compare(TypeParam(txt1)), 0);
+ EXPECT_GT(TypeParam(txt1).compare(TypeParam("")), 0);
+
+ EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt2)), 0);
+ EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt1)), 0);
+
+ EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt3)), 0);
+ EXPECT_GT(TypeParam(txt3).compare(TypeParam(txt1)), 0);
+
+ // we're comparing the data raw, starting at the length octet, so a shorter
+ // string sorts before a longer one no matter the lexicopraphical order
+ EXPECT_LT(TypeParam(txt3).compare(TypeParam(txt2)), 0);
+ EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt3)), 0);
+
+ // to make sure the length octet compares unsigned
+ EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt4)), 0);
+ EXPECT_GT(TypeParam(txt4).compare(TypeParam(txt1)), 0);
+
+ EXPECT_LT(TypeParam(txt5).compare(TypeParam(txt4)), 0);
+ EXPECT_GT(TypeParam(txt4).compare(TypeParam(txt5)), 0);
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(TypeParam(txt1).compare(*this->rdata_nomatch),
+ bad_cast);
+}
+
+}
diff --git a/src/lib/dns/tests/rdata_txt_unittest.cc b/src/lib/dns/tests/rdata_txt_unittest.cc
deleted file mode 100644
index e5f8ac9..0000000
--- a/src/lib/dns/tests/rdata_txt_unittest.cc
+++ /dev/null
@@ -1,166 +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 <util/buffer.h>
-#include <dns/exceptions.h>
-#include <dns/messagerenderer.h>
-#include <dns/rdata.h>
-#include <dns/rdataclass.h>
-#include <dns/rrclass.h>
-#include <dns/rrtype.h>
-
-#include <gtest/gtest.h>
-
-#include <dns/tests/unittest_util.h>
-#include <dns/tests/rdata_unittest.h>
-
-using isc::UnitTestUtil;
-using namespace std;
-using namespace isc::dns;
-using namespace isc::util;
-using namespace isc::dns::rdata;
-
-namespace {
-const generic::TXT rdata_txt("Test String");
-const generic::TXT rdata_txt_empty("");
-const generic::TXT rdata_txt_quoated("\"Test String\"");
-const uint8_t wiredata_txt[] = {
- sizeof("Test String") - 1,
- 'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
-};
-const uint8_t wiredata_nulltxt[] = { 0 };
-vector<uint8_t> wiredata_longesttxt(256, 'a');
-
-class Rdata_TXT_Test : public RdataTest {
-protected:
- Rdata_TXT_Test() {
- wiredata_longesttxt[0] = 255; // adjust length
- }
-};
-
-TEST_F(Rdata_TXT_Test, createFromText) {
- // normal case is covered in toWireBuffer.
-
- // surrounding double-quotes shouldn't change the result.
- EXPECT_EQ(0, rdata_txt.compare(rdata_txt_quoated));
-
- // Null character-string.
- obuffer.clear();
- generic::TXT(string("")).toWire(obuffer);
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
- wiredata_nulltxt, sizeof(wiredata_nulltxt));
-
- // Longest possible character-string.
- obuffer.clear();
- generic::TXT(string(255, 'a')).toWire(obuffer);
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
- &wiredata_longesttxt[0], wiredata_longesttxt.size());
-
- // Too long text for a valid character-string.
- EXPECT_THROW(generic::TXT(string(256, 'a')), CharStringTooLong);
-
- // The escape character makes the double quote a part of character-string,
- // so this is invalid input and should be rejected.
- EXPECT_THROW(generic::TXT("\"Test String\\\""), InvalidRdataText);
-
- // Terminating double-quote is provided, so this is valid, but in this
- // version of implementation we reject escaped characters.
- EXPECT_THROW(generic::TXT("\"Test String\\\"\""), InvalidRdataText);
-}
-
-void
-makeLargest(vector<uint8_t>& data) {
- uint8_t ch = 0;
-
- // create 255 sets of character-strings, each of which has the longest
- // length (255bytes string + 1-byte length field)
- for (int i = 0; i < 255; ++i, ++ch) {
- data.push_back(255);
- data.insert(data.end(), 255, ch);
- }
- // the last character-string should be 255 bytes (including the one-byte
- // length field) in length so that the total length should be in the range
- // of 16-bit integers.
- data.push_back(254);
- data.insert(data.end(), 254, ch);
-
- assert(data.size() == 65535);
-}
-
-TEST_F(Rdata_TXT_Test, createFromWire) {
- EXPECT_EQ(0, rdata_txt.compare(
- *rdataFactoryFromFile(RRType("TXT"), RRClass("IN"),
- "rdata_txt_fromWire1")));
-
- // Empty character string
- EXPECT_EQ(0, rdata_txt_empty.compare(
- *rdataFactoryFromFile(RRType("TXT"), RRClass("IN"),
- "rdata_txt_fromWire2.wire")));
-
- // Multiple character strings
- obuffer.clear();
- rdataFactoryFromFile(RRType("TXT"), RRClass("IN"),
- "rdata_txt_fromWire3.wire")->toWire(obuffer);
- // the result should be 'wiredata_txt' repeated twice
- vector<uint8_t> expected_data(wiredata_txt, wiredata_txt +
- sizeof(wiredata_txt));
- expected_data.insert(expected_data.end(), wiredata_txt,
- wiredata_txt + sizeof(wiredata_txt));
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
- &expected_data[0], expected_data.size());
-
- // Largest length of data. There's nothing special, but should be
- // constructed safely, and the content should be identical to the original
- // data.
- vector<uint8_t> largest_txt_data;
- makeLargest(largest_txt_data);
- InputBuffer ibuffer(&largest_txt_data[0], largest_txt_data.size());
- generic::TXT largest_txt(ibuffer, largest_txt_data.size());
- obuffer.clear();
- largest_txt.toWire(obuffer);
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
- &largest_txt_data[0], largest_txt_data.size());
-
- // rdlen parameter is out of range. This is a rare event because we'd
- // normally call the constructor via a polymorphic wrapper, where the
- // length is validated. But this should be checked explicitly.
- InputBuffer ibuffer2(&largest_txt_data[0], largest_txt_data.size());
- EXPECT_THROW(generic::TXT(ibuffer2, 65536), InvalidRdataLength);
-
- // RDATA is empty, which is invalid for TXT.
- EXPECT_THROW(rdataFactoryFromFile(RRType("TXT"), RRClass("IN"),
- "rdata_txt_fromWire4.wire"),
- DNSMessageFORMERR);
-
- // character-string length is too large, which could cause overrun.
- EXPECT_THROW(rdataFactoryFromFile(RRType("TXT"), RRClass("IN"),
- "rdata_txt_fromWire5.wire"),
- DNSMessageFORMERR);
-}
-
-TEST_F(Rdata_TXT_Test, toWireBuffer) {
- rdata_txt.toWire(obuffer);
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- obuffer.getData(), obuffer.getLength(),
- wiredata_txt, sizeof(wiredata_txt));
-}
-
-TEST_F(Rdata_TXT_Test, toText) {
- EXPECT_EQ("\"Test String\"", rdata_txt.toText());
-}
-}
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index d8f0d1c..27edf5f 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -90,6 +90,7 @@ EXTRA_DIST += question_fromWire question_toWire1 question_toWire2
EXTRA_DIST += rdatafields1.spec rdatafields2.spec rdatafields3.spec
EXTRA_DIST += rdatafields4.spec rdatafields5.spec rdatafields6.spec
EXTRA_DIST += rdata_cname_fromWire rdata_dname_fromWire rdata_dnskey_fromWire
+EXTRA_DIST += rdata_dhcid_fromWire rdata_dhcid_toWire
EXTRA_DIST += rdata_ds_fromWire rdata_in_a_fromWire rdata_in_aaaa_fromWire
EXTRA_DIST += rdata_mx_fromWire rdata_mx_toWire1 rdata_mx_toWire2
EXTRA_DIST += rdata_ns_fromWire
diff --git a/src/lib/dns/tests/testdata/rdata_dhcid_fromWire b/src/lib/dns/tests/testdata/rdata_dhcid_fromWire
new file mode 100644
index 0000000..0c8d56a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_dhcid_fromWire
@@ -0,0 +1,12 @@
+#
+# DHCID RDATA stored in an input buffer
+#
+# Valid RDATA for 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=
+#
+# RDLENGHT=41 bytes
+# 0 1
+ 00 29
+# 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=
+d0 b2 20 d0 bb d0 b5 d1 81 d1 83 20 d1 80 d0 be
+d0 b4 d0 b8 d0 bb d0 b0 d1 81 d1 8c 20 d1 91 d0
+bb d0 be d1 87 d0 ba d0 b0
diff --git a/src/lib/dns/tests/testdata/rdata_dhcid_toWire b/src/lib/dns/tests/testdata/rdata_dhcid_toWire
new file mode 100644
index 0000000..99ec229
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_dhcid_toWire
@@ -0,0 +1,7 @@
+#
+# DHCID RDATA stored in an output buffer
+#
+# 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=
+d0 b2 20 d0 bb d0 b5 d1 81 d1 83 20 d1 80 d0 be
+d0 b4 d0 b8 d0 bb d0 b0 d1 81 d1 8c 20 d1 91 d0
+bb d0 be d1 87 d0 ba d0 b0
diff --git a/src/lib/exceptions/tests/Makefile.am b/src/lib/exceptions/tests/Makefile.am
index 2444b02..35161a1 100644
--- a/src/lib/exceptions/tests/Makefile.am
+++ b/src/lib/exceptions/tests/Makefile.am
@@ -20,4 +20,4 @@ run_unittests_LDADD = $(GTEST_LDADD)
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
diff --git a/src/lib/log/tests/Makefile.am b/src/lib/log/tests/Makefile.am
index 069a7b4..8ca561d 100644
--- a/src/lib/log/tests/Makefile.am
+++ b/src/lib/log/tests/Makefile.am
@@ -61,7 +61,7 @@ init_logger_test_LDADD = $(top_builddir)/src/lib/log/liblog.la
init_logger_test_LDADD += $(top_builddir)/src/lib/util/libutil.la
init_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS += $(TESTS)
# Additional test using the shell. These are principally tests
# where the global logging environment is affected, and where the
diff --git a/src/lib/nsas/tests/Makefile.am b/src/lib/nsas/tests/Makefile.am
index 420e897..8845187 100644
--- a/src/lib/nsas/tests/Makefile.am
+++ b/src/lib/nsas/tests/Makefile.am
@@ -60,4 +60,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
diff --git a/src/lib/python/isc/Makefile.am b/src/lib/python/isc/Makefile.am
index f90f7b6..a3e74c5 100644
--- a/src/lib/python/isc/Makefile.am
+++ b/src/lib/python/isc/Makefile.am
@@ -1,5 +1,5 @@
SUBDIRS = datasrc cc config dns log net notify util testutils acl bind10
-SUBDIRS += log_messages
+SUBDIRS += xfrin log_messages
python_PYTHON = __init__.py
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index d07df1e..11a13ec 100644
--- a/src/lib/python/isc/config/ccsession.py
+++ b/src/lib/python/isc/config/ccsession.py
@@ -510,10 +510,10 @@ class UIModuleCCSession(MultiConfigData):
def _remove_value_from_list(self, identifier, value):
if value is None:
- # we are directly removing an list index
+ # we are directly removing a list index
id, list_indices = isc.cc.data.split_identifier_list_indices(identifier)
if list_indices is None:
- raise DataTypeError("identifier in remove_value() does not contain a list index, and no value to remove")
+ raise isc.cc.data.DataTypeError("identifier in remove_value() does not contain a list index, and no value to remove")
else:
self.set_value(identifier, None)
else:
diff --git a/src/lib/python/isc/config/tests/ccsession_test.py b/src/lib/python/isc/config/tests/ccsession_test.py
index 351c8e6..1c63957 100644
--- a/src/lib/python/isc/config/tests/ccsession_test.py
+++ b/src/lib/python/isc/config/tests/ccsession_test.py
@@ -747,6 +747,9 @@ class TestUIModuleCCSession(unittest.TestCase):
self.assertEqual({'Spec2': {'item5': []}}, uccs._local_changes)
uccs.add_value("Spec2/item5", None);
self.assertEqual({'Spec2': {'item5': ['']}}, uccs._local_changes)
+ # Intending to empty a list element, but forget specifying the index.
+ self.assertRaises(isc.cc.data.DataTypeError,
+ uccs.remove_value, "Spec2/item5", None)
def test_add_remove_value_named_set(self):
fake_conn = fakeUIConn()
diff --git a/src/lib/python/isc/datasrc/Makefile.am b/src/lib/python/isc/datasrc/Makefile.am
index ca2d9b4..60282d9 100644
--- a/src/lib/python/isc/datasrc/Makefile.am
+++ b/src/lib/python/isc/datasrc/Makefile.am
@@ -24,7 +24,6 @@ datasrc_la_LDFLAGS += -module
datasrc_la_LIBADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/python/libpydnspp.la
datasrc_la_LIBADD += $(PYTHON_LIB)
-#datasrc_la_LIBADD += $(SQLITE_LIBS)
EXTRA_DIST = client_inc.cc
EXTRA_DIST += finder_inc.cc
diff --git a/src/lib/python/isc/datasrc/__init__.py b/src/lib/python/isc/datasrc/__init__.py
index 0b4ed98..7ebd918 100644
--- a/src/lib/python/isc/datasrc/__init__.py
+++ b/src/lib/python/isc/datasrc/__init__.py
@@ -1,6 +1,16 @@
import sys
import os
+# The datasource factory loader uses dlopen, as does python
+# for its modules. Some dynamic linkers do not play nice if
+# modules are not loaded with RTLD_GLOBAL, a symptom of which
+# is that exceptions are not recognized by type. So to make
+# sure this doesn't happen, we temporarily set RTLD_GLOBAL
+# during the loading of the datasource wrappers.
+import ctypes
+flags = sys.getdlopenflags()
+sys.setdlopenflags(flags | ctypes.RTLD_GLOBAL)
+
# this setup is a temporary workaround to deal with the problem of
# having both 'normal' python modules and a wrapper module
# Once all programs use the new interface, we should remove the
@@ -16,6 +26,10 @@ if intree:
from datasrc import *
else:
from isc.datasrc.datasrc import *
+
+# revert to the default dlopen flags
+sys.setdlopenflags(flags)
+
from isc.datasrc.sqlite3_ds import *
from isc.datasrc.master import *
diff --git a/src/lib/python/isc/datasrc/client_inc.cc b/src/lib/python/isc/datasrc/client_inc.cc
index 1eba488..b81f48d 100644
--- a/src/lib/python/isc/datasrc/client_inc.cc
+++ b/src/lib/python/isc/datasrc/client_inc.cc
@@ -7,7 +7,20 @@ This is the python wrapper for the abstract base class that defines\n\
the common interface for various types of data source clients. A data\n\
source client is a top level access point to a data source, allowing \n\
various operations on the data source such as lookups, traversing or \n\
-updates. The client class itself has limited focus and delegates \n\
+updates.\n\
+This class serves as both the factory and the main interface to those \n\
+classes.\n\
+\n\
+The constructor takes two arguments; a type (string), and\n\
+configuration data for a datasource client of that type. The configuration\n\
+data is currently passed as a JSON in string form, and its contents depend\n\
+on the type of datasource from the first argument. For instance, a\n\
+datasource of type \"sqlite3\" takes the config \n\
+{ \"database_file\": \"/var/example.org\" }\n\
+We may in the future add support for passing configuration data,\n\
+but right now we limit it to a JSON-formatted string\n\
+\n\
+The client class itself has limited focus and delegates \n\
the responsibility for these specific operations to other (c++) classes;\n\
in general methods of this class act as factories of these other classes.\n\
\n\
@@ -110,7 +123,7 @@ Return an updater to make updates to a specific zone.\n\
The RR class of the zone is the one that the client is expected to\n\
handle (see the detailed description of this class).\n\
\n\
-If the specified zone is not found via the client, a NULL pointer will\n\
+If the specified zone is not found via the client, a None object will\n\
be returned; in other words a completely new zone cannot be created\n\
using an updater. It must be created beforehand (even if it's an empty\n\
placeholder) in a way specific to the underlying data source.\n\
diff --git a/src/lib/python/isc/datasrc/client_python.cc b/src/lib/python/isc/datasrc/client_python.cc
index 984eabf..caebd25 100644
--- a/src/lib/python/isc/datasrc/client_python.cc
+++ b/src/lib/python/isc/datasrc/client_python.cc
@@ -23,6 +23,7 @@
#include <util/python/pycppwrapper_util.h>
#include <datasrc/client.h>
+#include <datasrc/factory.h>
#include <datasrc/database.h>
#include <datasrc/data_source.h>
#include <datasrc/sqlite3_accessor.h>
@@ -50,13 +51,9 @@ namespace {
class s_DataSourceClient : public PyObject {
public:
s_DataSourceClient() : cppobj(NULL) {};
- DataSourceClient* cppobj;
+ DataSourceClientContainer* cppobj;
};
-// Shortcut type which would be convenient for adding class variables safely.
-typedef CPPPyObjectContainer<s_DataSourceClient, DataSourceClient>
- DataSourceClientContainer;
-
PyObject*
DataSourceClient_findZone(PyObject* po_self, PyObject* args) {
s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
@@ -64,12 +61,12 @@ DataSourceClient_findZone(PyObject* po_self, PyObject* args) {
if (PyArg_ParseTuple(args, "O!", &name_type, &name)) {
try {
DataSourceClient::FindResult find_result(
- self->cppobj->findZone(PyName_ToName(name)));
+ self->cppobj->getInstance().findZone(PyName_ToName(name)));
result::Result r = find_result.code;
ZoneFinderPtr zfp = find_result.zone_finder;
// Use N instead of O so refcount isn't increased twice
- return (Py_BuildValue("IN", r, createZoneFinderObject(zfp)));
+ return (Py_BuildValue("IN", r, createZoneFinderObject(zfp, po_self)));
} catch (const std::exception& exc) {
PyErr_SetString(getDataSourceException("Error"), exc.what());
return (NULL);
@@ -90,7 +87,8 @@ DataSourceClient_getIterator(PyObject* po_self, PyObject* args) {
if (PyArg_ParseTuple(args, "O!", &name_type, &name_obj)) {
try {
return (createZoneIteratorObject(
- self->cppobj->getIterator(PyName_ToName(name_obj))));
+ self->cppobj->getInstance().getIterator(PyName_ToName(name_obj)),
+ po_self));
} catch (const isc::NotImplemented& ne) {
PyErr_SetString(getDataSourceException("NotImplemented"),
ne.what());
@@ -120,9 +118,13 @@ DataSourceClient_getUpdater(PyObject* po_self, PyObject* args) {
PyBool_Check(replace_obj)) {
bool replace = (replace_obj != Py_False);
try {
- return (createZoneUpdaterObject(
- self->cppobj->getUpdater(PyName_ToName(name_obj),
- replace)));
+ ZoneUpdaterPtr updater =
+ self->cppobj->getInstance().getUpdater(PyName_ToName(name_obj),
+ replace);
+ if (!updater) {
+ return (Py_None);
+ }
+ return (createZoneUpdaterObject(updater, po_self));
} catch (const isc::NotImplemented& ne) {
PyErr_SetString(getDataSourceException("NotImplemented"),
ne.what());
@@ -162,22 +164,33 @@ PyMethodDef DataSourceClient_methods[] = {
int
DataSourceClient_init(s_DataSourceClient* self, PyObject* args) {
- // TODO: we should use the factory function which hasn't been written
- // yet. For now we hardcode the sqlite3 initialization, and pass it one
- // string for the database file. (similar to how the 'old direct'
- // sqlite3_ds code works)
+ char* ds_type_str;
+ char* ds_config_str;
try {
- char* db_file_name;
- if (PyArg_ParseTuple(args, "s", &db_file_name)) {
- boost::shared_ptr<DatabaseAccessor> sqlite3_accessor(
- new SQLite3Accessor(db_file_name, isc::dns::RRClass::IN()));
- self->cppobj = new DatabaseClient(isc::dns::RRClass::IN(),
- sqlite3_accessor);
+ // Turn the given argument into config Element; then simply call
+ // factory class to do its magic
+
+ // for now, ds_config must be JSON string
+ if (PyArg_ParseTuple(args, "ss", &ds_type_str, &ds_config_str)) {
+ isc::data::ConstElementPtr ds_config =
+ isc::data::Element::fromJSON(ds_config_str);
+ self->cppobj = new DataSourceClientContainer(ds_type_str,
+ ds_config);
return (0);
} else {
return (-1);
}
-
+ } catch (const isc::data::JSONError& je) {
+ const string ex_what = "JSON parse error in data source configuration "
+ "data for type " +
+ string(ds_type_str) + ":" + je.what();
+ PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
+ return (-1);
+ } catch (const DataSourceError& dse) {
+ const string ex_what = "Failed to create DataSourceClient of type " +
+ string(ds_type_str) + ":" + dse.what();
+ PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
+ return (-1);
} catch (const exception& ex) {
const string ex_what = "Failed to construct DataSourceClient object: " +
string(ex.what());
diff --git a/src/lib/python/isc/datasrc/datasrc.cc b/src/lib/python/isc/datasrc/datasrc.cc
index 4b0324a..7676104 100644
--- a/src/lib/python/isc/datasrc/datasrc.cc
+++ b/src/lib/python/isc/datasrc/datasrc.cc
@@ -77,14 +77,26 @@ initModulePart_DataSourceClient(PyObject* mod) {
}
Py_INCREF(&datasourceclient_type);
- addClassVariable(datasourceclient_type, "SUCCESS",
- Py_BuildValue("I", result::SUCCESS));
- addClassVariable(datasourceclient_type, "EXIST",
- Py_BuildValue("I", result::EXIST));
- addClassVariable(datasourceclient_type, "NOTFOUND",
- Py_BuildValue("I", result::NOTFOUND));
- addClassVariable(datasourceclient_type, "PARTIALMATCH",
- Py_BuildValue("I", result::PARTIALMATCH));
+ try {
+ installClassVariable(datasourceclient_type, "SUCCESS",
+ Py_BuildValue("I", result::SUCCESS));
+ installClassVariable(datasourceclient_type, "EXIST",
+ Py_BuildValue("I", result::EXIST));
+ installClassVariable(datasourceclient_type, "NOTFOUND",
+ Py_BuildValue("I", result::NOTFOUND));
+ installClassVariable(datasourceclient_type, "PARTIALMATCH",
+ Py_BuildValue("I", result::PARTIALMATCH));
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in DataSourceClient initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in DataSourceClient initialization");
+ return (false);
+ }
return (true);
}
@@ -103,26 +115,41 @@ initModulePart_ZoneFinder(PyObject* mod) {
}
Py_INCREF(&zonefinder_type);
- addClassVariable(zonefinder_type, "SUCCESS",
- Py_BuildValue("I", ZoneFinder::SUCCESS));
- addClassVariable(zonefinder_type, "DELEGATION",
- Py_BuildValue("I", ZoneFinder::DELEGATION));
- addClassVariable(zonefinder_type, "NXDOMAIN",
- Py_BuildValue("I", ZoneFinder::NXDOMAIN));
- addClassVariable(zonefinder_type, "NXRRSET",
- Py_BuildValue("I", ZoneFinder::NXRRSET));
- addClassVariable(zonefinder_type, "CNAME",
- Py_BuildValue("I", ZoneFinder::CNAME));
- addClassVariable(zonefinder_type, "DNAME",
- Py_BuildValue("I", ZoneFinder::DNAME));
-
- addClassVariable(zonefinder_type, "FIND_DEFAULT",
- Py_BuildValue("I", ZoneFinder::FIND_DEFAULT));
- addClassVariable(zonefinder_type, "FIND_GLUE_OK",
- Py_BuildValue("I", ZoneFinder::FIND_GLUE_OK));
- addClassVariable(zonefinder_type, "FIND_DNSSEC",
- Py_BuildValue("I", ZoneFinder::FIND_DNSSEC));
-
+ try {
+ installClassVariable(zonefinder_type, "SUCCESS",
+ Py_BuildValue("I", ZoneFinder::SUCCESS));
+ installClassVariable(zonefinder_type, "DELEGATION",
+ Py_BuildValue("I", ZoneFinder::DELEGATION));
+ installClassVariable(zonefinder_type, "NXDOMAIN",
+ Py_BuildValue("I", ZoneFinder::NXDOMAIN));
+ installClassVariable(zonefinder_type, "NXRRSET",
+ Py_BuildValue("I", ZoneFinder::NXRRSET));
+ installClassVariable(zonefinder_type, "CNAME",
+ Py_BuildValue("I", ZoneFinder::CNAME));
+ installClassVariable(zonefinder_type, "DNAME",
+ Py_BuildValue("I", ZoneFinder::DNAME));
+ installClassVariable(zonefinder_type, "WILDCARD",
+ Py_BuildValue("I", ZoneFinder::WILDCARD));
+ installClassVariable(zonefinder_type, "WILDCARD_NXRRSET",
+ Py_BuildValue("I", ZoneFinder::WILDCARD_NXRRSET));
+
+ installClassVariable(zonefinder_type, "FIND_DEFAULT",
+ Py_BuildValue("I", ZoneFinder::FIND_DEFAULT));
+ installClassVariable(zonefinder_type, "FIND_GLUE_OK",
+ Py_BuildValue("I", ZoneFinder::FIND_GLUE_OK));
+ installClassVariable(zonefinder_type, "FIND_DNSSEC",
+ Py_BuildValue("I", ZoneFinder::FIND_DNSSEC));
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in ZoneFinder initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in ZoneFinder initialization");
+ return (false);
+ }
return (true);
}
diff --git a/src/lib/python/isc/datasrc/finder_inc.cc b/src/lib/python/isc/datasrc/finder_inc.cc
index 2b47d02..bc8e62c 100644
--- a/src/lib/python/isc/datasrc/finder_inc.cc
+++ b/src/lib/python/isc/datasrc/finder_inc.cc
@@ -62,6 +62,10 @@ Search the zone for a given pair of domain name and RR type.\n\
and the code of SUCCESS will be returned.\n\
- If the search name matches a delegation point of DNAME, it returns\n\
the code of DNAME and that DNAME RR.\n\
+- If the result was synthesized by a wildcard match, it returns the\n\
+ code WILDCARD and the synthesized RRset\n\
+- If the query matched a wildcard name, but not its type, it returns the\n\
+ code WILDCARD_NXRRSET, and None\n\
- If the target is a list, all RRsets under the domain are inserted\n\
there and SUCCESS (or NXDOMAIN, in case of empty domain) is returned\n\
instead of normall processing. This is intended to handle ANY query.\n\
@@ -93,4 +97,22 @@ Parameters:\n\
Return Value(s): A tuple of a result code an a FindResult object enclosing\n\
the search result (see above).\n\
";
+
+const char* const ZoneFinder_find_previous_name_doc = "\
+find_previous_name(isc.dns.Name) -> isc.dns.Name\n\
+\n\
+Gets the previous name in the DNSSEC order. This can be used\n\
+to find the correct NSEC records for proving nonexistence\n\
+of domains.\n\
+\n\
+This method does not include under-zone-cut data (glue data).\n\
+\n\
+Raises isc.datasrc.NotImplemented in case the data source backend\n\
+doesn't support DNSSEC or there is no previous in the zone (NSEC\n\
+records might be missing in the DB, the queried name is less or\n\
+equal to the apex).\n\
+\n\
+Raises isc.datasrc.Error for low-level or internal datasource errors\n\
+(like broken connection to database, wrong data living there).\n\
+";
} // unnamed namespace
diff --git a/src/lib/python/isc/datasrc/finder_python.cc b/src/lib/python/isc/datasrc/finder_python.cc
index 598d300..cb02724 100644
--- a/src/lib/python/isc/datasrc/finder_python.cc
+++ b/src/lib/python/isc/datasrc/finder_python.cc
@@ -103,8 +103,14 @@ namespace {
// The s_* Class simply covers one instantiation of the object
class s_ZoneFinder : public PyObject {
public:
- s_ZoneFinder() : cppobj(ZoneFinderPtr()) {};
+ s_ZoneFinder() : cppobj(ZoneFinderPtr()), base_obj(NULL) {};
ZoneFinderPtr cppobj;
+ // This is a reference to a base object; if the object of this class
+ // depends on another object to be in scope during its lifetime,
+ // we use INCREF the base object upon creation, and DECREF it at
+ // the end of the destructor
+ // This is an optional argument to createXXX(). If NULL, it is ignored.
+ PyObject* base_obj;
};
// Shortcut type which would be convenient for adding class variables safely.
@@ -125,6 +131,9 @@ ZoneFinder_destroy(s_ZoneFinder* const self) {
// cppobj is a shared ptr, but to make sure things are not destroyed in
// the wrong order, we reset it here.
self->cppobj.reset();
+ if (self->base_obj != NULL) {
+ Py_DECREF(self->base_obj);
+ }
Py_TYPE(self)->tp_free(self);
}
@@ -160,6 +169,31 @@ ZoneFinder_find(PyObject* po_self, PyObject* args) {
return (isc_datasrc_internal::ZoneFinder_helper(self->cppobj.get(), args));
}
+PyObject*
+ZoneFinder_findPreviousName(PyObject* po_self, PyObject* args) {
+ s_ZoneFinder* const self = static_cast<s_ZoneFinder*>(po_self);
+ PyObject* name_obj;
+ if (PyArg_ParseTuple(args, "O!", &name_type, &name_obj)) {
+ try {
+ return (createNameObject(
+ self->cppobj->findPreviousName(PyName_ToName(name_obj))));
+ } catch (const isc::NotImplemented& nie) {
+ PyErr_SetString(getDataSourceException("NotImplemented"),
+ nie.what());
+ return (NULL);
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unexpected exception");
+ return (NULL);
+ }
+ } else {
+ return (NULL);
+ }
+}
+
// This list contains the actual set of functions we have in
// python. Each entry has
// 1. Python method name
@@ -167,12 +201,12 @@ ZoneFinder_find(PyObject* po_self, PyObject* args) {
// 3. Argument type
// 4. Documentation
PyMethodDef ZoneFinder_methods[] = {
- { "get_origin", reinterpret_cast<PyCFunction>(ZoneFinder_getOrigin),
- METH_NOARGS, ZoneFinder_getOrigin_doc },
- { "get_class", reinterpret_cast<PyCFunction>(ZoneFinder_getClass),
- METH_NOARGS, ZoneFinder_getClass_doc },
- { "find", reinterpret_cast<PyCFunction>(ZoneFinder_find), METH_VARARGS,
- ZoneFinder_find_doc },
+ { "get_origin", ZoneFinder_getOrigin, METH_NOARGS,
+ ZoneFinder_getOrigin_doc },
+ { "get_class", ZoneFinder_getClass, METH_NOARGS, ZoneFinder_getClass_doc },
+ { "find", ZoneFinder_find, METH_VARARGS, ZoneFinder_find_doc },
+ { "find_previous_name", ZoneFinder_findPreviousName, METH_VARARGS,
+ ZoneFinder_find_previous_name_doc },
{ NULL, NULL, 0, NULL }
};
@@ -233,11 +267,15 @@ PyTypeObject zonefinder_type = {
};
PyObject*
-createZoneFinderObject(isc::datasrc::ZoneFinderPtr source) {
+createZoneFinderObject(isc::datasrc::ZoneFinderPtr source, PyObject* base_obj) {
s_ZoneFinder* py_zi = static_cast<s_ZoneFinder*>(
zonefinder_type.tp_alloc(&zonefinder_type, 0));
if (py_zi != NULL) {
py_zi->cppobj = source;
+ py_zi->base_obj = base_obj;
+ }
+ if (base_obj != NULL) {
+ Py_INCREF(base_obj);
}
return (py_zi);
}
diff --git a/src/lib/python/isc/datasrc/finder_python.h b/src/lib/python/isc/datasrc/finder_python.h
index 5f2404e..23bc457 100644
--- a/src/lib/python/isc/datasrc/finder_python.h
+++ b/src/lib/python/isc/datasrc/finder_python.h
@@ -24,7 +24,15 @@ namespace python {
extern PyTypeObject zonefinder_type;
-PyObject* createZoneFinderObject(isc::datasrc::ZoneFinderPtr source);
+/// \brief Create a ZoneFinder python object
+///
+/// \param source The zone iterator pointer to wrap
+/// \param base_obj An optional PyObject that this ZoneFinder depends on
+/// Its refcount is increased, and will be decreased when
+/// this zone iterator is destroyed, making sure that the
+/// base object is never destroyed before this zonefinder.
+PyObject* createZoneFinderObject(isc::datasrc::ZoneFinderPtr source,
+ PyObject* base_obj = NULL);
} // namespace python
} // namespace datasrc
diff --git a/src/lib/python/isc/datasrc/iterator_python.cc b/src/lib/python/isc/datasrc/iterator_python.cc
index b482ea6..c52ab4a 100644
--- a/src/lib/python/isc/datasrc/iterator_python.cc
+++ b/src/lib/python/isc/datasrc/iterator_python.cc
@@ -45,8 +45,14 @@ namespace {
// The s_* Class simply covers one instantiation of the object
class s_ZoneIterator : public PyObject {
public:
- s_ZoneIterator() : cppobj(ZoneIteratorPtr()) {};
+ s_ZoneIterator() : cppobj(ZoneIteratorPtr()), base_obj(NULL) {};
ZoneIteratorPtr cppobj;
+ // This is a reference to a base object; if the object of this class
+ // depends on another object to be in scope during its lifetime,
+ // we use INCREF the base object upon creation, and DECREF it at
+ // the end of the destructor
+ // This is an optional argument to createXXX(). If NULL, it is ignored.
+ PyObject* base_obj;
};
// Shortcut type which would be convenient for adding class variables safely.
@@ -68,6 +74,9 @@ ZoneIterator_destroy(s_ZoneIterator* const self) {
// cppobj is a shared ptr, but to make sure things are not destroyed in
// the wrong order, we reset it here.
self->cppobj.reset();
+ if (self->base_obj != NULL) {
+ Py_DECREF(self->base_obj);
+ }
Py_TYPE(self)->tp_free(self);
}
@@ -187,11 +196,17 @@ PyTypeObject zoneiterator_type = {
};
PyObject*
-createZoneIteratorObject(isc::datasrc::ZoneIteratorPtr source) {
+createZoneIteratorObject(isc::datasrc::ZoneIteratorPtr source,
+ PyObject* base_obj)
+{
s_ZoneIterator* py_zi = static_cast<s_ZoneIterator*>(
zoneiterator_type.tp_alloc(&zoneiterator_type, 0));
if (py_zi != NULL) {
py_zi->cppobj = source;
+ py_zi->base_obj = base_obj;
+ }
+ if (base_obj != NULL) {
+ Py_INCREF(base_obj);
}
return (py_zi);
}
diff --git a/src/lib/python/isc/datasrc/iterator_python.h b/src/lib/python/isc/datasrc/iterator_python.h
index b457740..7c1b0eb 100644
--- a/src/lib/python/isc/datasrc/iterator_python.h
+++ b/src/lib/python/isc/datasrc/iterator_python.h
@@ -25,7 +25,15 @@ namespace python {
extern PyTypeObject zoneiterator_type;
-PyObject* createZoneIteratorObject(isc::datasrc::ZoneIteratorPtr source);
+/// \brief Create a ZoneIterator python object
+///
+/// \param source The zone iterator pointer to wrap
+/// \param base_obj An optional PyObject that this ZoneIterator depends on
+/// Its refcount is increased, and will be decreased when
+/// this zone iterator is destroyed, making sure that the
+/// base object is never destroyed before this zone iterator.
+PyObject* createZoneIteratorObject(isc::datasrc::ZoneIteratorPtr source,
+ PyObject* base_obj = NULL);
} // namespace python
diff --git a/src/lib/python/isc/datasrc/tests/Makefile.am b/src/lib/python/isc/datasrc/tests/Makefile.am
index be30dfa..411b5cc 100644
--- a/src/lib/python/isc/datasrc/tests/Makefile.am
+++ b/src/lib/python/isc/datasrc/tests/Makefile.am
@@ -10,9 +10,14 @@ CLEANFILES = $(abs_builddir)/rwtest.sqlite3.copied
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
-LIBRARY_PATH_PLACEHOLDER =
+# We always add one, the location of the data source modules
+# We may want to add an API method for this to the ds factory, but that is out
+# of scope for this ticket
+LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs:
if SET_ENV_LIBRARY_PATH
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+else
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/datasrc/tests/datasrc_test.py b/src/lib/python/isc/datasrc/tests/datasrc_test.py
index 15ceb80..75a0cfb 100644
--- a/src/lib/python/isc/datasrc/tests/datasrc_test.py
+++ b/src/lib/python/isc/datasrc/tests/datasrc_test.py
@@ -19,14 +19,16 @@ import isc.dns
import unittest
import os
import shutil
+import json
TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
TESTDATA_WRITE_PATH = os.environ['TESTDATA_WRITE_PATH'] + os.sep
READ_ZONE_DB_FILE = TESTDATA_PATH + "example.com.sqlite3"
-BROKEN_DB_FILE = TESTDATA_PATH + "brokendb.sqlite3"
WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "rwtest.sqlite3.copied"
-NEW_DB_FILE = TESTDATA_WRITE_PATH + "new_db.sqlite3"
+
+READ_ZONE_DB_CONFIG = "{ \"database_file\": \"" + READ_ZONE_DB_FILE + "\" }"
+WRITE_ZONE_DB_CONFIG = "{ \"database_file\": \"" + WRITE_ZONE_DB_FILE + "\"}"
def add_rrset(rrset_list, name, rrclass, rrtype, ttl, rdatas):
rrset_to_add = isc.dns.RRset(name, rrclass, rrtype, ttl)
@@ -59,13 +61,27 @@ def check_for_rrset(expected_rrsets, rrset):
class DataSrcClient(unittest.TestCase):
- def test_construct(self):
+ def test_constructors(self):
# can't construct directly
self.assertRaises(TypeError, isc.datasrc.ZoneIterator)
+ self.assertRaises(TypeError, isc.datasrc.DataSourceClient, 1, "{}")
+ self.assertRaises(TypeError, isc.datasrc.DataSourceClient, "sqlite3", 1)
+ self.assertRaises(isc.datasrc.Error,
+ isc.datasrc.DataSourceClient, "foo", "{}")
+ self.assertRaises(isc.datasrc.Error,
+ isc.datasrc.DataSourceClient, "sqlite3", "")
+ self.assertRaises(isc.datasrc.Error,
+ isc.datasrc.DataSourceClient, "sqlite3", "{}")
+ self.assertRaises(isc.datasrc.Error,
+ isc.datasrc.DataSourceClient, "sqlite3",
+ "{ \"foo\": 1 }")
+ self.assertRaises(isc.datasrc.Error,
+ isc.datasrc.DataSourceClient, "memory",
+ "{ \"foo\": 1 }")
def test_iterate(self):
- dsc = isc.datasrc.DataSourceClient(READ_ZONE_DB_FILE)
+ dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
# for RRSIGS, the TTL's are currently modified. This test should
# start failing when we fix that.
@@ -176,7 +192,7 @@ class DataSrcClient(unittest.TestCase):
self.assertRaises(TypeError, isc.datasrc.ZoneFinder)
def test_find(self):
- dsc = isc.datasrc.DataSourceClient(READ_ZONE_DB_FILE)
+ dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
result, finder = dsc.find_zone(isc.dns.Name("example.com"))
self.assertEqual(finder.SUCCESS, result)
@@ -231,6 +247,21 @@ class DataSrcClient(unittest.TestCase):
"cname-ext.example.com. 3600 IN CNAME www.sql1.example.com.\n",
rrset.to_text())
+ result, rrset = finder.find(isc.dns.Name("foo.wild.example.com"),
+ isc.dns.RRType.A(),
+ None,
+ finder.FIND_DEFAULT)
+ self.assertEqual(finder.WILDCARD, result)
+ self.assertEqual("foo.wild.example.com. 3600 IN A 192.0.2.255\n",
+ rrset.to_text())
+
+ result, rrset = finder.find(isc.dns.Name("foo.wild.example.com"),
+ isc.dns.RRType.TXT(),
+ None,
+ finder.FIND_DEFAULT)
+ self.assertEqual(finder.WILDCARD_NXRRSET, result)
+ self.assertEqual(None, rrset)
+
self.assertRaises(TypeError, finder.find,
"foo",
isc.dns.RRType.A(),
@@ -247,6 +278,24 @@ class DataSrcClient(unittest.TestCase):
None,
"foo")
+ def test_find_previous(self):
+ dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
+
+ result, finder = dsc.find_zone(isc.dns.Name("example.com"))
+ self.assertEqual(finder.SUCCESS, result)
+
+ prev = finder.find_previous_name(isc.dns.Name("bbb.example.com"))
+ self.assertEqual("example.com.", prev.to_text())
+
+ prev = finder.find_previous_name(isc.dns.Name("zzz.example.com"))
+ self.assertEqual("www.example.com.", prev.to_text())
+
+ prev = finder.find_previous_name(prev)
+ self.assertEqual("*.wild.example.com.", prev.to_text())
+
+ self.assertRaises(isc.datasrc.NotImplemented,
+ finder.find_previous_name,
+ isc.dns.Name("com"))
class DataSrcUpdater(unittest.TestCase):
@@ -260,7 +309,7 @@ class DataSrcUpdater(unittest.TestCase):
def test_update_delete_commit(self):
- dsc = isc.datasrc.DataSourceClient(WRITE_ZONE_DB_FILE)
+ dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
# first make sure, through a separate finder, that some record exists
result, finder = dsc.find_zone(isc.dns.Name("example.com"))
@@ -333,8 +382,40 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
rrset.to_text())
+ def test_two_modules(self):
+ # load two modules, and check if they don't interfere
+ mem_cfg = { "type": "memory", "class": "IN", "zones": [] };
+ dsc_mem = isc.datasrc.DataSourceClient("memory", json.dumps(mem_cfg))
+ dsc_sql = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
+
+ # check if exceptions are working
+ self.assertRaises(isc.datasrc.Error, isc.datasrc.DataSourceClient,
+ "memory", "{}")
+ self.assertRaises(isc.datasrc.Error, isc.datasrc.DataSourceClient,
+ "sqlite3", "{}")
+
+ # see if a lookup succeeds in sqlite3 ds
+ result, finder = dsc_sql.find_zone(isc.dns.Name("example.com"))
+ self.assertEqual(finder.SUCCESS, result)
+ self.assertEqual(isc.dns.RRClass.IN(), finder.get_class())
+ self.assertEqual("example.com.", finder.get_origin().to_text())
+ result, rrset = finder.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRType.A(),
+ None,
+ finder.FIND_DEFAULT)
+ self.assertEqual(finder.SUCCESS, result)
+ self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
+ rrset.to_text())
+
+ # see if a lookup fails in mem ds
+ result, finder = dsc_mem.find_zone(isc.dns.Name("example.com"))
+ self.assertEqual(finder.NXDOMAIN, result)
+
+
def test_update_delete_abort(self):
- dsc = isc.datasrc.DataSourceClient(WRITE_ZONE_DB_FILE)
+ # we don't do enything with this one, just making sure loading two
+ # datasources
+ dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
# first make sure, through a separate finder, that some record exists
result, finder = dsc.find_zone(isc.dns.Name("example.com"))
@@ -383,6 +464,11 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
rrset.to_text())
+ def test_update_for_no_zone(self):
+ dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
+ self.assertEqual(None,
+ dsc.get_updater(isc.dns.Name("notexistent.example"),
+ True))
if __name__ == "__main__":
isc.log.init("bind10")
diff --git a/src/lib/python/isc/datasrc/updater_python.cc b/src/lib/python/isc/datasrc/updater_python.cc
index a9dc581..e447622 100644
--- a/src/lib/python/isc/datasrc/updater_python.cc
+++ b/src/lib/python/isc/datasrc/updater_python.cc
@@ -54,8 +54,14 @@ namespace {
// The s_* Class simply covers one instantiation of the object
class s_ZoneUpdater : public PyObject {
public:
- s_ZoneUpdater() : cppobj(ZoneUpdaterPtr()) {};
+ s_ZoneUpdater() : cppobj(ZoneUpdaterPtr()), base_obj(NULL) {};
ZoneUpdaterPtr cppobj;
+ // This is a reference to a base object; if the object of this class
+ // depends on another object to be in scope during its lifetime,
+ // we use INCREF the base object upon creation, and DECREF it at
+ // the end of the destructor
+ // This is an optional argument to createXXX(). If NULL, it is ignored.
+ PyObject* base_obj;
};
// Shortcut type which would be convenient for adding class variables safely.
@@ -81,6 +87,9 @@ ZoneUpdater_destroy(s_ZoneUpdater* const self) {
// cppobj is a shared ptr, but to make sure things are not destroyed in
// the wrong order, we reset it here.
self->cppobj.reset();
+ if (self->base_obj != NULL) {
+ Py_DECREF(self->base_obj);
+ }
Py_TYPE(self)->tp_free(self);
}
@@ -176,51 +185,6 @@ ZoneUpdater_find(PyObject* po_self, PyObject* args) {
args));
}
-PyObject*
-AZoneUpdater_find(PyObject* po_self, PyObject* args) {
- s_ZoneUpdater* const self = static_cast<s_ZoneUpdater*>(po_self);
- PyObject *name;
- PyObject *rrtype;
- PyObject *target;
- int options_int;
- if (PyArg_ParseTuple(args, "O!O!OI", &name_type, &name,
- &rrtype_type, &rrtype,
- &target, &options_int)) {
- try {
- ZoneFinder::FindOptions options =
- static_cast<ZoneFinder::FindOptions>(options_int);
- ZoneFinder::FindResult find_result(
- self->cppobj->getFinder().find(PyName_ToName(name),
- PyRRType_ToRRType(rrtype),
- NULL,
- options
- ));
- ZoneFinder::Result r = find_result.code;
- isc::dns::ConstRRsetPtr rrsp = find_result.rrset;
- if (rrsp) {
- // Use N instead of O so the refcount isn't increased twice
- return Py_BuildValue("IN", r, createRRsetObject(*rrsp));
- } else {
- return Py_BuildValue("IO", r, Py_None);
- }
- } catch (const DataSourceError& dse) {
- PyErr_SetString(getDataSourceException("Error"), dse.what());
- return (NULL);
- } catch (const std::exception& exc) {
- PyErr_SetString(getDataSourceException("Error"), exc.what());
- return (NULL);
- } catch (...) {
- PyErr_SetString(getDataSourceException("Error"),
- "Unexpected exception");
- return (NULL);
- }
- } else {
- return (NULL);
- }
- return Py_BuildValue("I", 1);
-}
-
-
// This list contains the actual set of functions we have in
// python. Each entry has
// 1. Python method name
@@ -303,12 +267,17 @@ PyTypeObject zoneupdater_type = {
};
PyObject*
-createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source) {
+createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
+ PyObject* base_obj)
+{
s_ZoneUpdater* py_zi = static_cast<s_ZoneUpdater*>(
zoneupdater_type.tp_alloc(&zoneupdater_type, 0));
if (py_zi != NULL) {
py_zi->cppobj = source;
}
+ if (base_obj != NULL) {
+ Py_INCREF(base_obj);
+ }
return (py_zi);
}
diff --git a/src/lib/python/isc/datasrc/updater_python.h b/src/lib/python/isc/datasrc/updater_python.h
index 3886aa3..8228578 100644
--- a/src/lib/python/isc/datasrc/updater_python.h
+++ b/src/lib/python/isc/datasrc/updater_python.h
@@ -26,7 +26,15 @@ namespace python {
extern PyTypeObject zoneupdater_type;
-PyObject* createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source);
+/// \brief Create a ZoneUpdater python object
+///
+/// \param source The zone iterator pointer to wrap
+/// \param base_obj An optional PyObject that this ZoneUpdater depends on
+/// It's refcount is increased, and will be decreased when
+/// this zone iterator is destroyed, making sure that the
+/// base object is never destroyed before this zone updater.
+PyObject* createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
+ PyObject* base_obj = NULL);
} // namespace python
diff --git a/src/lib/python/isc/dns/Makefile.am b/src/lib/python/isc/dns/Makefile.am
index 161c2a5..b31da93 100644
--- a/src/lib/python/isc/dns/Makefile.am
+++ b/src/lib/python/isc/dns/Makefile.am
@@ -1,4 +1,5 @@
python_PYTHON = __init__.py
+pythondir = $(pyexecdir)/isc/dns
CLEANDIRS = __pycache__
diff --git a/src/lib/python/isc/log_messages/Makefile.am b/src/lib/python/isc/log_messages/Makefile.am
index b9bc4c8..30f8374 100644
--- a/src/lib/python/isc/log_messages/Makefile.am
+++ b/src/lib/python/isc/log_messages/Makefile.am
@@ -11,6 +11,7 @@ EXTRA_DIST += zonemgr_messages.py
EXTRA_DIST += cfgmgr_messages.py
EXTRA_DIST += config_messages.py
EXTRA_DIST += notify_out_messages.py
+EXTRA_DIST += libxfrin_messages.py
CLEANFILES = __init__.pyc
CLEANFILES += bind10_messages.pyc
@@ -23,6 +24,7 @@ CLEANFILES += zonemgr_messages.pyc
CLEANFILES += cfgmgr_messages.pyc
CLEANFILES += config_messages.pyc
CLEANFILES += notify_out_messages.pyc
+CLEANFILES += libxfrin_messages.pyc
CLEANDIRS = __pycache__
diff --git a/src/lib/python/isc/log_messages/libxfrin_messages.py b/src/lib/python/isc/log_messages/libxfrin_messages.py
new file mode 100644
index 0000000..74da329
--- /dev/null
+++ b/src/lib/python/isc/log_messages/libxfrin_messages.py
@@ -0,0 +1 @@
+from work.libxfrin_messages import *
diff --git a/src/lib/python/isc/xfrin/Makefile.am b/src/lib/python/isc/xfrin/Makefile.am
new file mode 100644
index 0000000..5804de6
--- /dev/null
+++ b/src/lib/python/isc/xfrin/Makefile.am
@@ -0,0 +1,23 @@
+SUBDIRS = . tests
+
+python_PYTHON = __init__.py diff.py
+BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/libxfrin_messages.py
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/libxfrin_messages.py
+pylogmessagedir = $(pyexecdir)/isc/log_messages/
+
+EXTRA_DIST = libxfrin_messages.mes
+
+CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/libxfrin_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/libxfrin_messages.pyc
+
+# Define rule to build logging source files from message file
+$(PYTHON_LOGMSGPKG_DIR)/work/libxfrin_messages.py: libxfrin_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message \
+ -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/libxfrin_messages.mes
+
+pythondir = $(pyexecdir)/isc/xfrin
+
+CLEANDIRS = __pycache__
+
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/xfrin/__init__.py b/src/lib/python/isc/xfrin/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/lib/python/isc/xfrin/diff.py b/src/lib/python/isc/xfrin/diff.py
new file mode 100644
index 0000000..a2d9a7d
--- /dev/null
+++ b/src/lib/python/isc/xfrin/diff.py
@@ -0,0 +1,237 @@
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+This helps the XFR in process with accumulating parts of diff and applying
+it to the datasource.
+
+The name of the module is not yet fully decided. We might want to move it
+under isc.datasrc or somewhere else, because we might want to reuse it with
+future DDNS process. But until then, it lives here.
+"""
+
+import isc.dns
+import isc.log
+from isc.log_messages.libxfrin_messages import *
+
+class NoSuchZone(Exception):
+ """
+ This is raised if a diff for non-existant zone is being created.
+ """
+ pass
+
+"""
+This is the amount of changes we accumulate before calling Diff.apply
+automatically.
+
+The number 100 is just taken from BIND 9. We don't know the rationale
+for exactly this amount, but we think it is just some randomly chosen
+number.
+"""
+# If changing this, modify the tests accordingly as well.
+DIFF_APPLY_TRESHOLD = 100
+
+logger = isc.log.Logger('libxfrin')
+
+class Diff:
+ """
+ The class represents a diff against current state of datasource on
+ one zone. The usual way of working with it is creating it, then putting
+ bunch of changes in and commiting at the end.
+
+ If you change your mind, you can just stop using the object without
+ really commiting it. In that case no changes will happen in the data
+ sounce.
+
+ The class works as a kind of a buffer as well, it does not direct
+ the changes to underlying data source right away, but keeps them for
+ a while.
+ """
+ def __init__(self, ds_client, zone, replace=False):
+ """
+ Initializes the diff to a ready state. It checks the zone exists
+ in the datasource and if not, NoSuchZone is raised. This also creates
+ a transaction in the data source.
+
+ The ds_client is the datasource client containing the zone. Zone is
+ isc.dns.Name object representing the name of the zone (its apex).
+ If replace is true, the content of the whole zone is wiped out before
+ applying the diff.
+
+ You can also expect isc.datasrc.Error or isc.datasrc.NotImplemented
+ exceptions.
+ """
+ self.__updater = ds_client.get_updater(zone, replace)
+ if self.__updater is None:
+ # The no such zone case
+ raise NoSuchZone("Zone " + str(zone) +
+ " does not exist in the data source " +
+ str(ds_client))
+ self.__buffer = []
+
+ def __check_commited(self):
+ """
+ This checks if the diff is already commited or broken. If it is, it
+ raises ValueError. This check is for methods that need to work only on
+ yet uncommited diffs.
+ """
+ if self.__updater is None:
+ raise ValueError("The diff is already commited or it has raised " +
+ "an exception, you come late")
+
+ def __data_common(self, rr, operation):
+ """
+ Schedules an operation with rr.
+
+ It does all the real work of add_data and delete_data, including
+ all checks.
+ """
+ self.__check_commited()
+ if rr.get_rdata_count() != 1:
+ raise ValueError('The rrset must contain exactly 1 Rdata, but ' +
+ 'it holds ' + str(rr.get_rdata_count()))
+ if rr.get_class() != self.__updater.get_class():
+ raise ValueError("The rrset's class " + str(rr.get_class()) +
+ " does not match updater's " +
+ str(self.__updater.get_class()))
+ self.__buffer.append((operation, rr))
+ if len(self.__buffer) >= DIFF_APPLY_TRESHOLD:
+ # Time to auto-apply, so the data don't accumulate too much
+ self.apply()
+
+ def add_data(self, rr):
+ """
+ Schedules addition of an RR into the zone in this diff.
+
+ The rr is of isc.dns.RRset type and it must contain only one RR.
+ If this is not the case or if the diff was already commited, this
+ raises the ValueError exception.
+
+ The rr class must match the one of the datasource client. If
+ it does not, ValueError is raised.
+ """
+ self.__data_common(rr, 'add')
+
+ def delete_data(self, rr):
+ """
+ Schedules deleting an RR from the zone in this diff.
+
+ The rr is of isc.dns.RRset type and it must contain only one RR.
+ If this is not the case or if the diff was already commited, this
+ raises the ValueError exception.
+
+ The rr class must match the one of the datasource client. If
+ it does not, ValueError is raised.
+ """
+ self.__data_common(rr, 'delete')
+
+ def compact(self):
+ """
+ Tries to compact the operations in buffer a little by putting some of
+ the operations together, forming RRsets with more than one RR.
+
+ This is called by apply before putting the data into datasource. You
+ may, but not have to, call this manually.
+
+ Currently it merges consecutive same operations on the same
+ domain/type. We could do more fancy things, like sorting by the domain
+ and do more merging, but such diffs should be rare in practice anyway,
+ so we don't bother and do it this simple way.
+ """
+ buf = []
+ for (op, rrset) in self.__buffer:
+ old = buf[-1][1] if len(buf) > 0 else None
+ if old is None or op != buf[-1][0] or \
+ rrset.get_name() != old.get_name() or \
+ rrset.get_type() != old.get_type():
+ buf.append((op, isc.dns.RRset(rrset.get_name(),
+ rrset.get_class(),
+ rrset.get_type(),
+ rrset.get_ttl())))
+ if rrset.get_ttl() != buf[-1][1].get_ttl():
+ logger.warn(LIBXFRIN_DIFFERENT_TTL, rrset.get_ttl(),
+ buf[-1][1].get_ttl())
+ for rdatum in rrset.get_rdata():
+ buf[-1][1].add_rdata(rdatum)
+ self.__buffer = buf
+
+ def apply(self):
+ """
+ Push the buffered changes inside this diff down into the data source.
+ This does not stop you from adding more changes later through this
+ diff and it does not close the datasource transaction, so the changes
+ will not be shown to others yet. It just means the internal memory
+ buffer is flushed.
+
+ This is called from time to time automatically, but you can call it
+ manually if you really want to.
+
+ This raises ValueError if the diff was already commited.
+
+ It also can raise isc.datasrc.Error. If that happens, you should stop
+ using this object and abort the modification.
+ """
+ self.__check_commited()
+ # First, compact the data
+ self.compact()
+ try:
+ # Then pass the data inside the data source
+ for (operation, rrset) in self.__buffer:
+ if operation == 'add':
+ self.__updater.add_rrset(rrset)
+ elif operation == 'delete':
+ self.__updater.delete_rrset(rrset)
+ else:
+ raise ValueError('Unknown operation ' + operation)
+ # As everything is already in, drop the buffer
+ except:
+ # If there's a problem, we can't continue.
+ self.__updater = None
+ raise
+
+ self.__buffer = []
+
+ def commit(self):
+ """
+ Writes all the changes into the data source and makes them visible.
+ This closes the diff, you may not use it any more. If you try to use
+ it, you'll get ValueError.
+
+ This might raise isc.datasrc.Error.
+ """
+ self.__check_commited()
+ # Push the data inside the data source
+ self.apply()
+ # Make sure they are visible.
+ try:
+ self.__updater.commit()
+ finally:
+ # Remove the updater. That will free some resources for one, but
+ # mark this object as already commited, so we can check
+
+ # We delete it even in case the commit failed, as that makes us
+ # unusable.
+ self.__updater = None
+
+ def get_buffer(self):
+ """
+ Returns the current buffer of changes not yet passed into the data
+ source. It is in a form like [('add', rrset), ('delete', rrset),
+ ('delete', rrset), ...].
+
+ Probably useful only for testing and introspection purposes. Don't
+ modify the list.
+ """
+ return self.__buffer
diff --git a/src/lib/python/isc/xfrin/libxfrin_messages.mes b/src/lib/python/isc/xfrin/libxfrin_messages.mes
new file mode 100644
index 0000000..be943c8
--- /dev/null
+++ b/src/lib/python/isc/xfrin/libxfrin_messages.mes
@@ -0,0 +1,21 @@
+# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# No namespace declaration - these constants go in the global namespace
+# of the libxfrin_messages python module.
+
+% LIBXFRIN_DIFFERENT_TTL multiple data with different TTLs (%1, %2) on %3/%4. Adjusting %2 -> %1.
+The xfrin module received an update containing multiple rdata changes for the
+same RRset. But the TTLs of these don't match each other. As we combine them
+together, the later one get's overwritten to the earlier one in the sequence.
diff --git a/src/lib/python/isc/xfrin/tests/Makefile.am b/src/lib/python/isc/xfrin/tests/Makefile.am
new file mode 100644
index 0000000..416d62b
--- /dev/null
+++ b/src/lib/python/isc/xfrin/tests/Makefile.am
@@ -0,0 +1,24 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = diff_tests.py
+EXTRA_DIST = $(PYTESTS)
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+LIBRARY_PATH_PLACEHOLDER =
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
diff --git a/src/lib/python/isc/xfrin/tests/diff_tests.py b/src/lib/python/isc/xfrin/tests/diff_tests.py
new file mode 100644
index 0000000..9fab890
--- /dev/null
+++ b/src/lib/python/isc/xfrin/tests/diff_tests.py
@@ -0,0 +1,446 @@
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import isc.log
+import unittest
+from isc.dns import Name, RRset, RRClass, RRType, RRTTL, Rdata
+from isc.xfrin.diff import Diff, NoSuchZone
+
+class TestError(Exception):
+ """
+ Just to have something to be raised during the tests.
+ Not used outside.
+ """
+ pass
+
+class DiffTest(unittest.TestCase):
+ """
+ Tests for the isc.xfrin.diff.Diff class.
+
+ It also plays role of a data source and an updater, so it can manipulate
+ some test variables while being called.
+ """
+ def setUp(self):
+ """
+ This sets internal variables so we can see nothing was called yet.
+
+ It also creates some variables used in multiple tests.
+ """
+ # Track what was called already
+ self.__updater_requested = False
+ self.__compact_called = False
+ self.__data_operations = []
+ self.__apply_called = False
+ self.__commit_called = False
+ self.__broken_called = False
+ self.__warn_called = False
+ self.__should_replace = False
+ # Some common values
+ self.__rrclass = RRClass.IN()
+ self.__type = RRType.A()
+ self.__ttl = RRTTL(3600)
+ # And RRsets
+ # Create two valid rrsets
+ self.__rrset1 = RRset(Name('a.example.org.'), self.__rrclass,
+ self.__type, self.__ttl)
+ self.__rdata = Rdata(self.__type, self.__rrclass, '192.0.2.1')
+ self.__rrset1.add_rdata(self.__rdata)
+ self.__rrset2 = RRset(Name('b.example.org.'), self.__rrclass,
+ self.__type, self.__ttl)
+ self.__rrset2.add_rdata(self.__rdata)
+ # And two invalid
+ self.__rrset_empty = RRset(Name('empty.example.org.'), self.__rrclass,
+ self.__type, self.__ttl)
+ self.__rrset_multi = RRset(Name('multi.example.org.'), self.__rrclass,
+ self.__type, self.__ttl)
+ self.__rrset_multi.add_rdata(self.__rdata)
+ self.__rrset_multi.add_rdata(Rdata(self.__type, self.__rrclass,
+ '192.0.2.2'))
+
+ def __mock_compact(self):
+ """
+ This can be put into the diff to hook into its compact method and see
+ if it gets called.
+ """
+ self.__compact_called = True
+
+ def __mock_apply(self):
+ """
+ This can be put into the diff to hook into its apply method and see
+ it gets called.
+ """
+ self.__apply_called = True
+
+ def __broken_operation(self, *args):
+ """
+ This can be used whenever an operation should fail. It raises TestError.
+ It should take whatever amount of parameters needed, so it can be put
+ quite anywhere.
+ """
+ self.__broken_called = True
+ raise TestError("Test error")
+
+ def warn(self, *args):
+ """
+ This is for checking the warn function was called, we replace the logger
+ in the tested module.
+ """
+ self.__warn_called = True
+
+ def commit(self):
+ """
+ This is part of pretending to be a zone updater. This notes the commit
+ was called.
+ """
+ self.__commit_called = True
+
+ def add_rrset(self, rrset):
+ """
+ This one is part of pretending to be a zone updater. It writes down
+ addition of an rrset was requested.
+ """
+ self.__data_operations.append(('add', rrset))
+
+ def delete_rrset(self, rrset):
+ """
+ This one is part of pretending to be a zone updater. It writes down
+ removal of an rrset was requested.
+ """
+ self.__data_operations.append(('delete', rrset))
+
+ def get_class(self):
+ """
+ This one is part of pretending to be a zone updater. It returns
+ the IN class.
+ """
+ return self.__rrclass
+
+ def get_updater(self, zone_name, replace):
+ """
+ This one pretends this is the data source client and serves
+ getting an updater.
+
+ If zone_name is 'none.example.org.', it returns None, otherwise
+ it returns self.
+ """
+ # The diff should not delete the old data.
+ self.assertEqual(self.__should_replace, replace)
+ self.__updater_requested = True
+ # Pretend this zone doesn't exist
+ if zone_name == Name('none.example.org.'):
+ return None
+ else:
+ return self
+
+ def test_create(self):
+ """
+ This test the case when the diff is successfuly created. It just
+ tries it does not throw and gets the updater.
+ """
+ diff = Diff(self, Name('example.org.'))
+ self.assertTrue(self.__updater_requested)
+ self.assertEqual([], diff.get_buffer())
+
+ def test_create_nonexist(self):
+ """
+ Try to create a diff on a zone that doesn't exist. This should
+ raise a correct exception.
+ """
+ self.assertRaises(NoSuchZone, Diff, self, Name('none.example.org.'))
+ self.assertTrue(self.__updater_requested)
+
+ def __data_common(self, diff, method, operation):
+ """
+ Common part of test for test_add and test_delte.
+ """
+ # Try putting there the bad data first
+ self.assertRaises(ValueError, method, self.__rrset_empty)
+ self.assertRaises(ValueError, method, self.__rrset_multi)
+ # They were not added
+ self.assertEqual([], diff.get_buffer())
+ # Put some proper data into the diff
+ method(self.__rrset1)
+ method(self.__rrset2)
+ dlist = [(operation, self.__rrset1), (operation, self.__rrset2)]
+ self.assertEqual(dlist, diff.get_buffer())
+ # Check the data are not destroyed by raising an exception because of
+ # bad data
+ self.assertRaises(ValueError, method, self.__rrset_empty)
+ self.assertEqual(dlist, diff.get_buffer())
+
+ def test_add(self):
+ """
+ Try to add few items into the diff and see they are stored in there.
+
+ Also try passing an rrset that has differnt amount of RRs than 1.
+ """
+ diff = Diff(self, Name('example.org.'))
+ self.__data_common(diff, diff.add_data, 'add')
+
+ def test_delete(self):
+ """
+ Try scheduling removal of few items into the diff and see they are
+ stored in there.
+
+ Also try passing an rrset that has different amount of RRs than 1.
+ """
+ diff = Diff(self, Name('example.org.'))
+ self.__data_common(diff, diff.delete_data, 'delete')
+
+ def test_apply(self):
+ """
+ Schedule few additions and check the apply works by passing the
+ data into the updater.
+ """
+ # Prepare the diff
+ diff = Diff(self, Name('example.org.'))
+ diff.add_data(self.__rrset1)
+ diff.delete_data(self.__rrset2)
+ dlist = [('add', self.__rrset1), ('delete', self.__rrset2)]
+ self.assertEqual(dlist, diff.get_buffer())
+ # Do the apply, hook the compact method
+ diff.compact = self.__mock_compact
+ diff.apply()
+ # It should call the compact
+ self.assertTrue(self.__compact_called)
+ # And pass the data. Our local history of what happened is the same
+ # format, so we can check the same way
+ self.assertEqual(dlist, self.__data_operations)
+ # And the buffer in diff should become empty, as everything
+ # got inside.
+ self.assertEqual([], diff.get_buffer())
+
+ def test_commit(self):
+ """
+ If we call a commit, it should first apply whatever changes are
+ left (we hook into that instead of checking the effect) and then
+ the commit on the updater should have been called.
+
+ Then we check it raises value error for whatever operation we try.
+ """
+ diff = Diff(self, Name('example.org.'))
+ diff.add_data(self.__rrset1)
+ orig_apply = diff.apply
+ diff.apply = self.__mock_apply
+ diff.commit()
+ self.assertTrue(self.__apply_called)
+ self.assertTrue(self.__commit_called)
+ # The data should be handled by apply which we replaced.
+ self.assertEqual([], self.__data_operations)
+ # Now check all range of other methods raise ValueError
+ self.assertRaises(ValueError, diff.commit)
+ self.assertRaises(ValueError, diff.add_data, self.__rrset2)
+ self.assertRaises(ValueError, diff.delete_data, self.__rrset1)
+ diff.apply = orig_apply
+ self.assertRaises(ValueError, diff.apply)
+ # This one does not state it should raise, so check it doesn't
+ # But it is NOP in this situation anyway
+ diff.compact()
+
+ def test_autoapply(self):
+ """
+ Test the apply is called all by itself after 100 tasks are added.
+ """
+ diff = Diff(self, Name('example.org.'))
+ # A method to check the apply is called _after_ the 100th element
+ # is added. We don't use it anywhere else, so we define it locally
+ # as lambda function
+ def check():
+ self.assertEqual(100, len(diff.get_buffer()))
+ self.__mock_apply()
+ orig_apply = diff.apply
+ diff.apply = check
+ # If we put 99, nothing happens yet
+ for i in range(0, 99):
+ diff.add_data(self.__rrset1)
+ expected = [('add', self.__rrset1)] * 99
+ self.assertEqual(expected, diff.get_buffer())
+ self.assertFalse(self.__apply_called)
+ # Now we push the 100th and it should call the apply method
+ # This will _not_ flush the data yet, as we replaced the method.
+ # It, however, would in the real life.
+ diff.add_data(self.__rrset1)
+ # Now the apply method (which is replaced by our check) should
+ # have been called. If it wasn't, this is false. If it was, but
+ # still with 99 elements, the check would complain
+ self.assertTrue(self.__apply_called)
+ # Reset the buffer by calling the original apply.
+ orig_apply()
+ self.assertEqual([], diff.get_buffer())
+ # Similar with delete
+ self.__apply_called = False
+ for i in range(0, 99):
+ diff.delete_data(self.__rrset2)
+ expected = [('delete', self.__rrset2)] * 99
+ self.assertEqual(expected, diff.get_buffer())
+ self.assertFalse(self.__apply_called)
+ diff.delete_data(self.__rrset2)
+ self.assertTrue(self.__apply_called)
+
+ def test_compact(self):
+ """
+ Test the compaction works as expected, eg. it compacts only consecutive
+ changes of the same operation and on the same domain/type.
+
+ The test case checks that it does merge them, but also puts some
+ different operations "in the middle", changes the type and name and
+ places the same kind of change further away of each other to see they
+ are not merged in that case.
+ """
+ diff = Diff(self, Name('example.org.'))
+ # Check we can do a compact on empty data, it shouldn't break
+ diff.compact()
+ self.assertEqual([], diff.get_buffer())
+ # This data is the way it should look like after the compact
+ # ('operation', 'domain.prefix', 'type', ['rdata', 'rdata'])
+ # The notes say why the each of consecutive can't be merged
+ data = [
+ ('add', 'a', 'A', ['192.0.2.1', '192.0.2.2']),
+ # Different type.
+ ('add', 'a', 'AAAA', ['2001:db8::1', '2001:db8::2']),
+ # Different operation
+ ('delete', 'a', 'AAAA', ['2001:db8::3']),
+ # Different domain
+ ('delete', 'b', 'AAAA', ['2001:db8::4']),
+ # This does not get merged with the first, even if logically
+ # possible. We just don't do this.
+ ('add', 'a', 'A', ['192.0.2.3'])
+ ]
+ # Now, fill the data into the diff, in a "flat" way, one by one
+ for (op, nprefix, rrtype, rdata) in data:
+ name = Name(nprefix + '.example.org.')
+ rrtype_obj = RRType(rrtype)
+ for rdatum in rdata:
+ rrset = RRset(name, self.__rrclass, rrtype_obj, self.__ttl)
+ rrset.add_rdata(Rdata(rrtype_obj, self.__rrclass, rdatum))
+ if op == 'add':
+ diff.add_data(rrset)
+ else:
+ diff.delete_data(rrset)
+ # Compact it
+ diff.compact()
+ # Now check they got compacted. They should be in the same order as
+ # pushed inside. So it should be the same as data modulo being in
+ # the rrsets and isc.dns objects.
+ def check():
+ buf = diff.get_buffer()
+ self.assertEqual(len(data), len(buf))
+ for (expected, received) in zip(data, buf):
+ (eop, ename, etype, edata) = expected
+ (rop, rrrset) = received
+ self.assertEqual(eop, rop)
+ ename_obj = Name(ename + '.example.org.')
+ self.assertEqual(ename_obj, rrrset.get_name())
+ # We check on names to make sure they are printed nicely
+ self.assertEqual(etype, str(rrrset.get_type()))
+ rdata = rrrset.get_rdata()
+ self.assertEqual(len(edata), len(rdata))
+ # It should also preserve the order
+ for (edatum, rdatum) in zip(edata, rdata):
+ self.assertEqual(edatum, str(rdatum))
+ check()
+ # Try another compact does nothing, but survives
+ diff.compact()
+ check()
+
+ def test_wrong_class(self):
+ """
+ Test a wrong class of rrset is rejected.
+ """
+ diff = Diff(self, Name('example.org.'))
+ rrset = RRset(Name('a.example.org.'), RRClass.CH(), RRType.NS(),
+ self.__ttl)
+ rrset.add_rdata(Rdata(RRType.NS(), RRClass.CH(), 'ns.example.org.'))
+ self.assertRaises(ValueError, diff.add_data, rrset)
+ self.assertRaises(ValueError, diff.delete_data, rrset)
+
+ def __do_raise_test(self):
+ """
+ Do a raise test. Expects that one of the operations is exchanged for
+ broken version.
+ """
+ diff = Diff(self, Name('example.org.'))
+ diff.add_data(self.__rrset1)
+ diff.delete_data(self.__rrset2)
+ self.assertRaises(TestError, diff.commit)
+ self.assertTrue(self.__broken_called)
+ self.assertRaises(ValueError, diff.add_data, self.__rrset1)
+ self.assertRaises(ValueError, diff.delete_data, self.__rrset2)
+ self.assertRaises(ValueError, diff.commit)
+ self.assertRaises(ValueError, diff.apply)
+
+ def test_raise_add(self):
+ """
+ Test the exception from add_rrset is propagated and the diff can't be
+ used afterwards.
+ """
+ self.add_rrset = self.__broken_operation
+ self.__do_raise_test()
+
+ def test_raise_delete(self):
+ """
+ Test the exception from delete_rrset is propagated and the diff can't be
+ used afterwards.
+ """
+ self.delete_rrset = self.__broken_operation
+ self.__do_raise_test()
+
+ def test_raise_commit(self):
+ """
+ Test the exception from updater's commit gets propagated and it can't be
+ used afterwards.
+ """
+ self.commit = self.__broken_operation
+ self.__do_raise_test()
+
+ def test_ttl(self):
+ """
+ Test the TTL handling. A warn function should have been called if they
+ differ, but that's all, it should not crash or raise.
+ """
+ orig_logger = isc.xfrin.diff.logger
+ try:
+ isc.xfrin.diff.logger = self
+ diff = Diff(self, Name('example.org.'))
+ diff.add_data(self.__rrset1)
+ rrset2 = RRset(Name('a.example.org.'), self.__rrclass,
+ self.__type, RRTTL(120))
+ rrset2.add_rdata(Rdata(self.__type, self.__rrclass, '192.10.2.2'))
+ diff.add_data(rrset2)
+ rrset2 = RRset(Name('a.example.org.'), self.__rrclass,
+ self.__type, RRTTL(6000))
+ rrset2.add_rdata(Rdata(self.__type, self.__rrclass, '192.10.2.3'))
+ diff.add_data(rrset2)
+ # They should get compacted together and complain.
+ diff.compact()
+ self.assertEqual(1, len(diff.get_buffer()))
+ # The TTL stays on the first value, no matter if smaller or bigger
+ # ones come later.
+ self.assertEqual(self.__ttl, diff.get_buffer()[0][1].get_ttl())
+ self.assertTrue(self.__warn_called)
+ finally:
+ isc.xfrin.diff.logger = orig_logger
+
+ def test_relpace(self):
+ """
+ Test that when we want to replace the whole zone, it is propagated.
+ """
+ self.__should_replace = True
+ diff = Diff(self, "example.org.", True)
+ self.assertTrue(self.__updater_requested)
+
+if __name__ == "__main__":
+ isc.log.init("bind10")
+ unittest.main()
diff --git a/src/lib/resolve/tests/Makefile.am b/src/lib/resolve/tests/Makefile.am
index cf05d9b..7c42c36 100644
--- a/src/lib/resolve/tests/Makefile.am
+++ b/src/lib/resolve/tests/Makefile.am
@@ -38,4 +38,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
diff --git a/src/lib/server_common/tests/Makefile.am b/src/lib/server_common/tests/Makefile.am
index d7e113a..d07be52 100644
--- a/src/lib/server_common/tests/Makefile.am
+++ b/src/lib/server_common/tests/Makefile.am
@@ -49,6 +49,6 @@ run_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
run_unittests_LDADD += $(top_builddir)/src/lib/config/tests/libfake_session.la
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
EXTRA_DIST = testdata/spec.spec
diff --git a/src/lib/util/pyunittests/Makefile.am b/src/lib/util/pyunittests/Makefile.am
index 63ccf2a..02fb8c2 100644
--- a/src/lib/util/pyunittests/Makefile.am
+++ b/src/lib/util/pyunittests/Makefile.am
@@ -2,7 +2,8 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
-pyexec_LTLIBRARIES = pyunittests_util.la
+check_LTLIBRARIES = pyunittests_util.la
+
pyunittests_util_la_SOURCES = pyunittests_util.cc
pyunittests_util_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
pyunittests_util_la_LDFLAGS = $(PYTHON_LDFLAGS)
@@ -15,3 +16,7 @@ pyunittests_util_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
pyunittests_util_la_LDFLAGS += -module
pyunittests_util_la_LIBADD = $(top_builddir)/src/lib/util/libutil.la
pyunittests_util_la_LIBADD += $(PYTHON_LIB)
+
+# hack to trigger libtool to not create a convenience archive,
+# resulting in shared modules
+pyunittests_util_la_LDFLAGS += -rpath /nowhere
diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am
index 47243f8..b61804e 100644
--- a/src/lib/util/tests/Makefile.am
+++ b/src/lib/util/tests/Makefile.am
@@ -40,4 +40,4 @@ run_unittests_LDADD += \
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
diff --git a/src/lib/util/unittests/Makefile.am b/src/lib/util/unittests/Makefile.am
index 83235f2..8006262 100644
--- a/src/lib/util/unittests/Makefile.am
+++ b/src/lib/util/unittests/Makefile.am
@@ -1,7 +1,7 @@
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CXXFLAGS = $(B10_CXXFLAGS)
-lib_LTLIBRARIES = libutil_unittests.la
+check_LTLIBRARIES = libutil_unittests.la
libutil_unittests_la_SOURCES = fork.h fork.cc resolver.h
libutil_unittests_la_SOURCES += newhook.h newhook.cc
libutil_unittests_la_SOURCES += testdata.h testdata.cc
diff --git a/tests/system/cleanall.sh b/tests/system/cleanall.sh
index f067696..434c6b1 100755
--- a/tests/system/cleanall.sh
+++ b/tests/system/cleanall.sh
@@ -27,7 +27,10 @@ find . -type f \( \
status=0
-for d in `find . -maxdepth 2 -mindepth 1 -type d -print`
+for d in ./.* ./* ./*/*
do
+ case $d in ./.|./..) continue ;; esac
+ test -d $d || continue
+
test ! -f $d/clean.sh || ( cd $d && sh clean.sh )
done
diff --git a/tests/system/ixfr/clean_ns.sh b/tests/system/ixfr/clean_ns.sh
index 8f6634f..88f4ff1 100644
--- a/tests/system/ixfr/clean_ns.sh
+++ b/tests/system/ixfr/clean_ns.sh
@@ -17,10 +17,11 @@
# Clean up nameserver directories after zone transfer tests.
rm -f ns1/named.conf
-rm -f ns1/db.example
+rm -f ns1/db.example*
rm -f ns1/named.memstats
rm -f nsx2/bind10.run
+rm -f nsx2/b10-config.db
rm -f ../zone.sqlite3
rm -f client.dig
diff --git a/tests/tools/badpacket/Makefile.am b/tests/tools/badpacket/Makefile.am
index fcba404..61b76b2 100644
--- a/tests/tools/badpacket/Makefile.am
+++ b/tests/tools/badpacket/Makefile.am
@@ -12,7 +12,7 @@ endif
CLEANFILES = *.gcno *.gcda
-noinst_PROGRAMS = badpacket
+check_PROGRAMS = badpacket
badpacket_SOURCES = badpacket.cc
badpacket_SOURCES += command_options.cc command_options.h
badpacket_SOURCES += header_flags.h
diff --git a/tests/tools/badpacket/tests/Makefile.am b/tests/tools/badpacket/tests/Makefile.am
index 2daa664..a110e11 100644
--- a/tests/tools/badpacket/tests/Makefile.am
+++ b/tests/tools/badpacket/tests/Makefile.am
@@ -29,4 +29,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
endif
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
More information about the bind10-changes
mailing list