BIND 10 trac542, updated. 5519b6cb34e655c96dca4ec8be2a3eabda941f3c [trac542] Merge branch 'master' into trac542
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue May 24 09:09:06 UTC 2011
The branch, trac542 has been updated
via 5519b6cb34e655c96dca4ec8be2a3eabda941f3c (commit)
via c37ebedf94c5dbbed3c685272a0cdc4bea67fb04 (commit)
via b0dfec1a5000ad22ef4dfa9c21c89e90b0e68673 (commit)
via 496a4378cfcc8430dd1155bb3ae5448f27ebfdfa (commit)
via 6e6eda44e9b1b7236e3baccc25e008eb674440aa (commit)
via 27e122bfdd9edcc04e39cbe8a033d89ef36207d3 (commit)
via d04505cee4d9df2cb9cf9a2909a948589be4ff09 (commit)
via 77d792c9d7c1a3f95d3e6a8b721ac79002cd7db1 (commit)
via 82ea1075a4ae1ef8f6c0ba3cca5d6a1968a77f3f (commit)
via 358e2303c565cb387aca660c2e55967b683792ff (commit)
via 2ddb97d633c1721d74615b84d2c6ebd360b9bda3 (commit)
via 74567fe1e92c8f10da3a639d235cdbc4c4d5cc9d (commit)
via cc3ccc1f474407507118ae0c53af307d0dd7bdf2 (commit)
via 41521402ab646288ff1a4a12c5110d52dddeb4b1 (commit)
via 5cb80378e05cc0ec208adfbe09e15efd04431267 (commit)
via 0c7670c04135ba2ade5ffe241b66094aeb891f16 (commit)
via f0645fdb5d7be3c7d7090159847853f5a06ed7e4 (commit)
via df354bdb4031c1f67c03d71527105da4e35d1a6e (commit)
via b30c6c0843ae18a275f65e7ad5ab86bd78c82678 (commit)
via 30d350e8cae28631a083c04a9ccf8d5bcaf69adb (commit)
via 6c86102687c491bdac96577cabf98a0640ad8a60 (commit)
via 37035a073be57c9f6d00f2558f74ce69b7067295 (commit)
via 765bfaf3ccf1a96e981210c429d5beacb229b4ce (commit)
via 938eb7ec8ac6bc670fcdc95a2189f4b13390e9cb (commit)
via 50c678a05ecc181f4a63fd930bd345bce72dfc68 (commit)
via b5dc1d86b2a763f98d8414589a545fcbda09a36c (commit)
via bf4d941c69ed13ec03a2e179009780eb1ad11359 (commit)
via 71ff1ab09693f1a90e46ba74cd5bbadf449161ea (commit)
via d2ee006cd5d5f3cf5ff56594fbf3d8955685d8a1 (commit)
via 74a60943da1619dc598bbf4b440b78914ebe98a9 (commit)
via 71d57323f77270278f75a57f63499c82f8f4ddd0 (commit)
via 6f8d74e83a1c8073dada7288be92c2976c638e27 (commit)
via 0034051f94a5e0e91e2574ec07f9b5374d39d6cd (commit)
via 0346449357648e45f5b68f792cbf6dc914b16d9c (commit)
via 89e3ffaa1fb56bcc76f626a64afcc25e506d8b54 (commit)
via fc97ca7de4ecd731aa6e470a7c68f5b26cbda7b0 (commit)
via 78502c021478d97672232015b7df06a7d52e531b (commit)
via 71eb80242fd144bddc06c98b3bdaa91341a65f26 (commit)
via 0555ab65d0e43d03b2d40c95d833dd050eea6c23 (commit)
via 23d63caf0cc09f19a01794458bf2457a67caac05 (commit)
via 031eda633fe45496f47fad9d4b656a0100144f75 (commit)
via 77def12e8d0d957d9fe587816b27b6df4f88ce90 (commit)
via 080db7e6245ebf63f0b3104e92b99142c87fe291 (commit)
via b50e03318eeb6464f7143eb524eb7b8ed2a5d02a (commit)
via 88504d121c5e08fff947b92e698a54d24d14c375 (commit)
via 4bd053d4e1a4165c7b4b5d91a6f674e40250301a (commit)
via a2936cc155f8b5ce2afaa82820fa377a037f2be3 (commit)
via 94cfeebfec6574350fb4980c2b0cc6a7d84ba4f7 (commit)
via 47e5578077fa332f8476a13aa4ad6ba29e003a1a (commit)
via 830c0ba6c96b009d1c9c4fa31dea7cf4f6de4e37 (commit)
via 9e84764e4911a4ecf9b82df3beb6cd289eb68ada (commit)
via 28465ec39050a2779fe98503c690ed4df2711e98 (commit)
via bcea2b3da4bca69194a2154b92f9f734edbe8322 (commit)
via a251c4f239e7b42856314412142cd9777f91dbf1 (commit)
via 2360fc72c24eb39d32b0afd6a389cfe8f10cb2f9 (commit)
via fc9c42d22de8c2c5555573a1a3e29b2d30146a29 (commit)
via 3303478b6f9943fd5514268bb1c0c42a638d107a (commit)
via d158640b970e5d8f0e5d4f8c6c278f03ee0e47e7 (commit)
via d11b5e89203a5340d4e5ca51c4c02db17c33dc1f (commit)
via 7a1c4cc8e1baaf51a2058edd0a4179bd586345f3 (commit)
via de0694d2f4c7dba909eb922e3fa1a1269d3cbc78 (commit)
via bc0688b0e7846ec9bb38e4e014ed23be84876948 (commit)
via b8a7bf58c974c9ba4518fd894963cd66a19baf7e (commit)
via 3a54f64afc94e9394a527872b957609d14002ff1 (commit)
via 7c390c1149baab7bd33e741afcdefb827275d5cd (commit)
via b6982ea32af206b6ef661b492eea7b274af97bd2 (commit)
via 62168918aceee04418765089cdc5fdfb34bf66a3 (commit)
via 929a9cae2b351e67ac1953514a63b0c54095361c (commit)
via 04ea273e2988cc405ae3ee7555a0f028258c17a5 (commit)
via 58a5aabf65705e4107cc59ad25c76bbbcedc52bf (commit)
via 43f5888605c770c012421a420766b01e1ad8a96b (commit)
via d1b5154a7c17058ff49aa67f389f52496228e4b6 (commit)
via 5b495e880e559c413a80d0dcc741a5076f3f7eb4 (commit)
via 141bf465a7b13f1e4c92a76ca2df208c8d375385 (commit)
via 7a87432e627715d9062367e2321427a4510d5822 (commit)
via ac2e283bd92dcc5af494938d6cfed82e4074abde (commit)
via 03fd43339b3ffd2537a1621d628d504cee13c9d5 (commit)
via 6ed8870ee7ac32e786030403de4423b8d7647679 (commit)
via 7a6f36fc9073def2a531a4090b97d223d5a5c69d (commit)
via 7c576f2e3d986b0f58883776822323ff57535a3c (commit)
via 201bae76d11706e9fe9c09491ed216c225a02e9d (commit)
via be4334aa0b66fd01f9b3f859c309545459dfab00 (commit)
via 1440e71559ad3565dff46e9cac31f31e6748d8f2 (commit)
via a407cb3d58c78b93afe294be13e7b360b11fd542 (commit)
via 1cccbc6845d795f8d8e2b0c7ba637fb28e179e71 (commit)
via 0c81e90348e53e673a05e92e150cec0f598a2d4e (commit)
via 882cc4d6b2b9f391e72fdc0bf8eb82bdb846ca61 (commit)
via 081891b38f05f9a186814ab7d1cd5c572b8f777f (commit)
via 4472e3c7cbfb5fd33ce575bd2666036d363b2ce2 (commit)
via 04c0fbf9fa780b4a281c5982ec33e5632852cfa1 (commit)
via cd79c2fae07f7b1a8d2e2f501488de7a2d11eac5 (commit)
via 76039741c19fa58e404879b334475b9ae01cd8dd (commit)
via dc19181b46dec42bc5db83861731656a5b45b899 (commit)
via 224c7b9aeb3000e11790a2c667f0ee45c9481f17 (commit)
via 94d43a69237b5d2bf671e384ff8b2b9a5ce445b4 (commit)
via c3e1e45419104bdb01dd385b22eae85bb8799611 (commit)
via 946b527467c19236814cae6e35191ce19db3284a (commit)
via 02e2f3a3c2ae669824d595bc9b42f37d9624b22c (commit)
via 54210ba456b4e0822c5e333fd1f996bb35c6bee9 (commit)
via 7a52a9a3618fd19ea9779eb0cef1a3e4f1c3d444 (commit)
via fd08a0dc40846f58aaa8a7df7726ac83e5e4c038 (commit)
via 98f5e0e604e60fead409c28645b064510b8fc8de (commit)
via 46629b17783d3ddf624002893af955d525f453a8 (commit)
via a1a58a7382e82256f3f6785b7bebfa4643cced67 (commit)
via 928a439496c6040392cc03615c38aa3de45bfe87 (commit)
via 8e28187a582820857ef2dae9b13637a3881f13ba (commit)
via 4b3e621a4497ac99978d40b316b11581cf80d088 (commit)
via 75340e4d2906762ecd088180087c9229a253e4ed (commit)
via 1ce058332d84de9bc2f37344d0b49c24d3c30551 (commit)
via 4e0adcf35398ec9f5c3a7a4b14e9a1aedcf9f66a (commit)
via 87d5f96fe04dab6de30c733230669703a2429701 (commit)
via 324cf344f3e0cd9e35e50076911fe7801a7d4690 (commit)
via d3da4f1f4f00d13962e6f23ff1272269569be5d4 (commit)
via 71315bc901882a8bbb35a95c19781528560fcb82 (commit)
via 3336f575aee4072d1d88b05f7db79712384ef2d8 (commit)
via f6e9a1186d00e023021f5853e5a7a1d7c3f838cf (commit)
via 3bb68553bb39502b749265542c5b6d36ad80e32d (commit)
via ade0b1a890f1fe21c075d4fef332e3e35407f086 (commit)
via ff20711233b8bd6a5df5a896c0d2855222291a9e (commit)
via 3e8ef1595dd2c7095540b22cca77e69991ad8ee1 (commit)
via c6827475447b07375cfdd2902c08519cd1cc9dde (commit)
via f15bdf8420351828159c0d7847d4f653388bf53c (commit)
via a234b20dc6617392deb8a1e00eb0eed0ff353c0a (commit)
via 97a0938cfafb96358649b9a538b00fbb3ee42ea0 (commit)
via 023527c23812b2f999c938ff1ee5b30434209297 (commit)
via 86632c12308c3ed099d75eb828f740c526dd7ec0 (commit)
via dafe7a6038499cc0432f4dd6f968b7fb1ffb119b (commit)
via e93cfe3f6812d8a1fef103e34d9d1a6ee88dd8d7 (commit)
via a1420dabc6ace739cb21044184654717a32604a5 (commit)
via cd689c463a59fa5fee72d3f977835e0369eb3650 (commit)
via d1fdfd86c9b8e0120e557e2c7baaab542f9c2719 (commit)
via 744fe91ac965c576cbe916ca39a0bef54afdcd3f (commit)
via c3f769ce6f0e3be367f7e0079a97a11e3f344761 (commit)
via ff36bea0314636d978f61923cc7292364c1808fa (commit)
via d686e00a5dd1efae29891e8056af324ea983d187 (commit)
via da57d4a08a2e14e5e6589138692674894f667f54 (commit)
via 5aaa6c0f628ed7c2093ecdbac93a2c8cf6c94349 (commit)
via 345b5986ff26015475b5e17506b3ca6cafaea8bd (commit)
via 7b25152b5bbbaff6840f88ad7ff2684f8b8062a6 (commit)
via 76a81d33b95b12341515cc6ca5718d72a242ab59 (commit)
via 8339141a4a64043e82685f0e6793b592b7ac820a (commit)
via 25c5c37094f9845be826f91bf5901068415891f6 (commit)
via 9df5e254832c5c65ddf4a0895c09256e8386820f (commit)
via 36af50328be059026859a09d402ff2d591789dfd (commit)
via f8c763ef2c9eb8343ead481093e2d0024e3f1cd2 (commit)
via f5278f2ac4becb33b93dc4c2fa7e6558a7daad8f (commit)
via c47f23e502e998d7885d4d3d679c1eacb1dab790 (commit)
via f80cdaf115a8ad80184240b283b243ac07bb9739 (commit)
via c2693bb84ea22e3b44617d729d8d2e8dbd99c51c (commit)
via 2b521f9674a0d9aea7fb06d71b008e64c40922aa (commit)
via 2f25f64f9f7a1021b67b9c37cfca41e86f5ae032 (commit)
via 14f8767d89d62e0d573a1e6b31fbeea9272e7011 (commit)
via 32872f1e745f45d1c0e84943cf57ad985a925f3e (commit)
via a82432e09cc7f3b22920d9baecba1f8e89332f37 (commit)
via fd51a1883a332c305fa4015a6210971d3956fc12 (commit)
via 04611df9f5e34b2a8d6949b5b00d25a065c7c920 (commit)
via 9eddc07cd32918f3b8e9ebd114d9c8f8f39a359b (commit)
via 143b2c6769c64eb55d2f34305ad8e2b7ce681aa6 (commit)
via 4569c1b87f5d04f663e1c8e2813d090a78dd9e40 (commit)
via 76a1d7e547e9eecdc4aecfa3cf6cc4ba940ad725 (commit)
via ee53746391bcfdaa75bba0db87add3dc7becb84d (commit)
via 92c0c95057cd98a5b8cf8099367a122684cf329b (commit)
via 296ec6be00c208aa9ab8cbaa20376749e8b6ab49 (commit)
via 82ead9ea724a5482c44e8a6235bcd4634eacce2c (commit)
via 7deb4955ef8816885376e558e7f0eaf65e22b4d4 (commit)
via 4a78ffbf8a2b9d1171415d7f0af1b7adc4e53481 (commit)
via b916d130e0799f457634cfba2b06fc1250db54a0 (commit)
via 1ea71dc16928433c1243375bd9210d5fceb28fe6 (commit)
via e7a49bee8af14cf5d4e16e14cb5bdd3ba83385b9 (commit)
via ed62a7f4dde0264cd60e4b739c41be9c98bbe3e4 (commit)
via 38932d7e76c4e35aca7382640e9e86360a389545 (commit)
via 55b3152ebb03fbe946986a52732f79a68d926163 (commit)
via 29d36377ec206cbe52274ee9a5a6e88ef27921d5 (commit)
via 25b33e62902b5bb181405e2177d2fe935cfe6c75 (commit)
via a892818fb13a1839c82104523cb6cb359c970e88 (commit)
via df1ab797a9d1a2a188060c70071bb8fa7d6f1a01 (commit)
via 6fc6ecda47f8556fc0a4daabc042b0b1a2d7d592 (commit)
via b653f950b906ecf4194d2a2cd07fa92ae47dd546 (commit)
via ceef68cd1223ae14d8412adbe18af2812ade8c2d (commit)
via 879d981650dff5ee577712aa45aa9b363b039a04 (commit)
via e595402348d7766d7e3f7fd8d9f32bd8b144f747 (commit)
via 33cd7d9232078f70aa380309a3615a1a9d743988 (commit)
via fb26d933e7af0cc07fe686ef88ef95b28cd10d2b (commit)
via 48d2369e3a7c0e55f5c94973ab0191ddfbaa02c1 (commit)
via fe59cd140491dbc685932bd22440e28c703c1053 (commit)
via afd75a89d7aeba622b53c5b37e3f76572ef68c3a (commit)
via 8d9debda3a88cb20d19d23dbdfe106b3132a4ead (commit)
via 6eba2b45ef54d671c674d7d840dc99208eb5d00c (commit)
via 4787c281a351b04b9570b8bb2d863db706a9d9c9 (commit)
via 9bd9fa819f30aff560da9412ffa14d87e557ab69 (commit)
via d53bbaf239de6059dd0d8872f84d1a0db6ccb90c (commit)
via 7b0d9b1dc03d9343f3ccfed2fb5876d7d9f048d5 (commit)
via d65686925bbd04cbbfa3ca2705f54696b76beb45 (commit)
via 4f6b51bc00d3f37b8938951217cd1f3564bedcd8 (commit)
via b395258c708b49a5da8d0cffcb48d83294354ba3 (commit)
via 569b2b6e5d84277316bce84c0e118ffd116ffe5e (commit)
via e5687a75c06966ab68ad453093fb6c7b9d8fe761 (commit)
via 27a4c936d87d31952f6a8377e04cae1fb90edffd (commit)
via 7f642b767c8274a6f3344e4a5e1141e9e7f38924 (commit)
via 7efc144e0eacc123544507d635630b7949052993 (commit)
via 43db6eac145a6a9a59f6d892a9b9fd2384a12d51 (commit)
via ad6d052794693ec22f430109de624b8fd7ea212d (commit)
via a4f08f816c968fd9dc614f171b8925b5b7998da7 (commit)
via a7a3c9f8cc85f04267a5606f4413afcde7af1864 (commit)
via ea37ca8caa7c56fea957fc7aac066d5708675f28 (commit)
via e93f054a2d0cf1c21d00112ccaa93d5d2432a677 (commit)
via b734c55b73941125af327595ba39252add032791 (commit)
via daab0dcd59baee4b1e56eab640e29c9e8e947e80 (commit)
via 00a15b4b06f9de757b30cd320fd56431980f8458 (commit)
via 8bfccfb1fead0448e959126e07dad1a800880575 (commit)
via a09ced080782388e389ee6c669b9d36da65f9dc5 (commit)
via b21c74c08fbdd350bcd01b083fc3cf29a98e5a3a (commit)
via cabd3b127cf5ab2b916c5717992338316496de8b (commit)
via 80c0a2c3eec3ac5d3ac862f9b4c5926f0eece6ed (commit)
via 11439c030741ce402c52ad9d3cd1a407d45a443e (commit)
via e564debcb683454296d3e3cf478748daa0b08cbc (commit)
via fba4ec1d7269284b48042b1a7ea084b531bf8d9c (commit)
via cccc20cac1d91951bd9b707c3dfb532e9ad8686b (commit)
via 68c9545bb7d57933e514ef55779abfd06c38145b (commit)
via 2cf390147f147dfab2c8c63c470177189799ed20 (commit)
via 287ee379ad62f9043699c936dc1204b6a906f52c (commit)
via a64f778b468ff7bd95461d1228aec66565602d57 (commit)
via eef87432f746406d467c647dd7302fc4cac8d53f (commit)
via 313464eed2b54281b8b2357002cd7dfb9231ddf6 (commit)
via 4fff42d6c9ae24effcae3f6eaa73a8a4eda5a38f (commit)
via df8ef3fffa7a4186d7ade60b514bb78acbb9b33c (commit)
via 79b087eab39177799e38611661fd86125d9b8a67 (commit)
via efdf126e6e0fe75afa26d4d7cdcce663892a840c (commit)
via 35a7c63a23646994af69a1a6d325ad11b4bc8354 (commit)
via a91fc737dd4c982194392b467b68c6c8383cec55 (commit)
via 34c2d5bbe08e41b0802eb6ca58a8084595064803 (commit)
via c83d4bea0e9abc94b84315eb42c8ed7a879c8899 (commit)
via 7e9ea719c8fda75f346eb87daae58cf06c8070e0 (commit)
via e4f2823d8b564c5b2c2f7029e5eb81be6b058d3c (commit)
via 1b967a5870088c3f4c7b5a3b8202e1b2e9572690 (commit)
via 04c515e17a08b41c9dbf241e3983148596353d2c (commit)
via 45417c48a7d32fb6e3f7fdccc70ec2faa8441135 (commit)
via 9c3195a542df8d7e747e28d49d7abdc707781632 (commit)
via 96d1613a474602b833af953a364fb045fcfee776 (commit)
via 6f8490fe9ce1d6dbef326d75f9eab79c09e83462 (commit)
via 38cddfdc23f6d2aa31be24173f9d7c1df2e3c4bc (commit)
via e2e13d0bd0856496a3571ba6b7f35f41e6534859 (commit)
via ea1f2d6e96715f0821df5d096be46b2454e6073a (commit)
via 04f7331a44e0bfd48f52d06f6fd463c6652dd38f (commit)
via 5b4feea19e2cc1bdf18ce52cf0062828ad9067c7 (commit)
via 1ce2874170c85de79a896b1cc3077d9f3032d9e0 (commit)
via 1151e49bcec0ac69fe01d084e10f106481337dea (commit)
via b92cca798d702273d63d56d9197782edeb927c8c (commit)
via abdf624576745fa0a4f37319e3c7dbd76047e2d2 (commit)
via 47dc61713d6bcfc16410fdb657c41dbbef85fb2c (commit)
via 9713a50748616587ca32f17e9931c752aa1e5d3d (commit)
via fe141c14e201993b5e0efe848c2a47e0a4b553bc (commit)
via 81b2d1ceaf680d740f66250b848eea1db05c4a3b (commit)
via 583203a8ca082541bc2982752b20294bb972bdcc (commit)
via 051d8bf74c500b8e1f1d2eabb9bb384d01104d10 (commit)
from de552586600ecd4123b904e65d09c3ef0717fc2a (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 5519b6cb34e655c96dca4ec8be2a3eabda941f3c
Merge: de552586600ecd4123b904e65d09c3ef0717fc2a c37ebedf94c5dbbed3c685272a0cdc4bea67fb04
Author: Stephen Morris <stephen at isc.org>
Date: Tue May 24 10:08:46 2011 +0100
[trac542] Merge branch 'master' into trac542
Conflicts:
src/lib/Makefile.am
src/lib/config/tests/Makefile.am
src/lib/config/tests/run_unittests.cc
src/lib/datasrc/tests/Makefile.am
src/lib/nsas/tests/Makefile.am
src/lib/nsas/tests/run_unittests.cc
src/lib/server_common/tests/Makefile.am
src/lib/util/Makefile.am
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 118 ++++-
README | 4 +-
configure.ac | 8 +-
doc/Doxyfile | 3 +-
doc/guide/bind10-guide.html | 44 +-
doc/guide/bind10-guide.xml | 16 +-
src/bin/cfgmgr/b10-cfgmgr.py.in | 23 +-
src/bin/cfgmgr/plugins/Makefile.am | 6 +-
src/bin/cfgmgr/plugins/tests/Makefile.am | 27 +
src/bin/cfgmgr/plugins/tests/tsig_keys_test.py | 103 ++++
src/bin/cfgmgr/plugins/tsig_keys.py | 50 ++
src/bin/cfgmgr/plugins/tsig_keys.spec | 21 +
src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in | 3 +
src/bin/msgq/tests/msgq_test.py | 2 +-
src/bin/resolver/resolver.cc | 16 +-
src/bin/stats/Makefile.am | 7 +-
src/bin/stats/b10-stats-httpd.8 | 4 +
src/bin/stats/b10-stats-httpd.xml | 6 +
src/bin/stats/b10-stats.8 | 8 +-
src/bin/stats/b10-stats.xml | 16 +-
src/bin/stats/stats-schema.spec.in | 87 +++
src/bin/stats/stats.py.in | 19 +-
src/bin/stats/stats.spec.in | 81 +---
src/bin/stats/stats_httpd.py.in | 4 +-
src/bin/stats/tests/b10-stats_test.py | 9 +
src/bin/xfrin/b10-xfrin.8 | 43 ++-
src/bin/xfrin/b10-xfrin.xml | 46 ++-
src/bin/xfrin/tests/xfrin_test.py | 555 ++++++++++++++++++--
src/bin/xfrin/xfrin.py.in | 418 +++++++++++----
src/bin/xfrin/xfrin.spec | 45 ++-
src/bin/zonemgr/b10-zonemgr.8 | 37 +-
src/bin/zonemgr/b10-zonemgr.xml | 66 ++-
src/bin/zonemgr/tests/zonemgr_test.py | 151 ++++--
src/bin/zonemgr/zonemgr.py.in | 89 +++-
src/cppcheck-suppress.lst | 2 +-
src/lib/Makefile.am | 4 +-
src/lib/asiodns/Makefile.am | 6 +-
src/lib/asiodns/asiodef.mes | 56 ++
src/lib/asiodns/asiodef.msg | 56 --
src/lib/asiodns/io_fetch.cc | 73 ++-
src/lib/asiodns/io_fetch.h | 24 +
src/lib/asiolink/tests/Makefile.am | 6 +
src/lib/asiolink/tests/interval_timer_unittest.cc | 26 +-
src/lib/config/Makefile.am | 19 +-
src/lib/config/ccsession.cc | 82 ++-
src/lib/config/ccsession.h | 55 ++-
src/lib/config/config_log.cc | 26 +
src/lib/config/config_log.h | 38 ++
src/lib/config/configdef.mes | 50 ++
src/lib/config/tests/Makefile.am | 3 +-
src/lib/config/tests/ccsession_unittests.cc | 122 +++++-
src/lib/config/tests/run_unittests.cc | 2 +
src/lib/cryptolink/crypto_hmac.cc | 48 ++-
src/lib/cryptolink/cryptolink.h | 12 +-
src/lib/cryptolink/tests/crypto_unittests.cc | 377 +++++++++++---
src/lib/datasrc/Makefile.am | 15 +-
src/lib/datasrc/cache.cc | 23 +-
src/lib/datasrc/data_source.cc | 76 +++-
src/lib/datasrc/logger.cc | 23 +
src/lib/datasrc/logger.h | 46 ++
src/lib/datasrc/memory_datasrc.cc | 60 +++
src/lib/datasrc/messagedef.mes | 498 ++++++++++++++++++
src/lib/datasrc/sqlite3_datasrc.cc | 37 ++-
src/lib/datasrc/static_datasrc.cc | 5 +
src/lib/datasrc/tests/Makefile.am | 6 +-
src/lib/datasrc/tests/logger_unittest.cc | 31 ++
src/lib/dns/message.h | 1 +
src/lib/dns/python/Makefile.am | 17 +-
src/lib/dns/python/edns_python.cc | 6 +-
src/lib/dns/python/message_python.cc | 50 ++-
src/lib/dns/python/messagerenderer_python.cc | 189 ++++----
src/lib/dns/python/messagerenderer_python.h | 52 ++
src/lib/dns/python/name_python.cc | 479 +++++++++--------
src/lib/dns/python/name_python.h | 84 +++
src/lib/dns/python/pydnspp.cc | 51 ++-
src/lib/dns/python/pydnspp_common.cc | 18 +-
src/lib/dns/python/pydnspp_common.h | 29 +-
src/lib/dns/python/pydnspp_towire.h | 127 +++++
src/lib/dns/python/question_python.cc | 4 +-
src/lib/dns/python/rcode_python.cc | 157 +++---
src/lib/dns/python/rcode_python.h | 57 ++
src/lib/dns/python/rrset_python.cc | 8 +-
src/lib/dns/python/tests/Makefile.am | 5 +-
src/lib/dns/python/tests/message_python_test.py | 49 ++-
src/lib/dns/python/tests/tsig_python_test.py | 535 +++++++++++++++++++-
src/lib/dns/python/tests/tsig_rdata_python_test.py | 30 +
src/lib/dns/python/tests/tsigerror_python_test.py | 97 ++++
src/lib/dns/python/tests/tsigkey_python_test.py | 34 +-
src/lib/dns/python/tests/tsigrecord_python_test.py | 44 ++
src/lib/dns/python/tsig_python.cc | 311 +++++++++--
src/lib/dns/python/tsig_python.h | 47 ++
src/lib/dns/python/tsig_rdata_python.cc | 369 +++++++++++++
src/lib/dns/python/tsig_rdata_python.h | 57 ++
src/lib/dns/python/tsigerror_python.cc | 370 +++++++++++++
src/lib/dns/python/tsigerror_python.h | 52 ++
src/lib/dns/python/tsigerror_python_inc.cc | 83 +++
src/lib/dns/python/tsigkey_python.cc | 388 ++++++++-------
src/lib/dns/python/tsigkey_python.h | 53 ++
src/lib/dns/python/tsigrecord_python.cc | 311 +++++++++++
src/lib/dns/python/tsigrecord_python.h | 53 ++
src/lib/dns/rdata/any_255/tsig_250.cc | 2 +-
src/lib/dns/tests/testdata/Makefile.am | 8 +
src/lib/dns/tests/testdata/gen-wiredata.py.in | 13 +-
src/lib/dns/tests/testdata/tsig_verify1.spec | 19 +
src/lib/dns/tests/testdata/tsig_verify10.spec | 22 +
src/lib/dns/tests/testdata/tsig_verify2.spec | 32 ++
src/lib/dns/tests/testdata/tsig_verify3.spec | 26 +
src/lib/dns/tests/testdata/tsig_verify4.spec | 27 +
src/lib/dns/tests/testdata/tsig_verify5.spec | 26 +
src/lib/dns/tests/testdata/tsig_verify6.spec | 21 +
src/lib/dns/tests/testdata/tsig_verify7.spec | 21 +
src/lib/dns/tests/testdata/tsig_verify8.spec | 23 +
src/lib/dns/tests/testdata/tsig_verify9.spec | 21 +
src/lib/dns/tests/tsig_unittest.cc | 534 +++++++++++++++++---
src/lib/dns/tests/tsigerror_unittest.cc | 14 +
src/lib/dns/tests/tsigkey_unittest.cc | 100 +++-
src/lib/dns/tests/tsigrecord_unittest.cc | 9 +-
src/lib/dns/tsig.cc | 380 +++++++++++---
src/lib/dns/tsig.h | 198 +++++++-
src/lib/dns/tsigerror.cc | 11 +
src/lib/dns/tsigerror.h | 24 +-
src/lib/dns/tsigkey.cc | 53 ++-
src/lib/dns/tsigkey.h | 45 ++-
src/lib/dns/tsigrecord.cc | 6 +-
src/lib/dns/tsigrecord.h | 43 ++-
src/lib/log/Makefile.am | 2 +-
src/lib/log/README | 115 +++--
src/lib/log/compiler/message.cc | 36 +-
src/lib/log/log_formatter.h | 6 +-
src/lib/log/logger_support.cc | 79 +++-
src/lib/log/logger_support.h | 31 ++
src/lib/log/message_exception.cc | 26 -
src/lib/log/message_exception.h | 37 +-
src/lib/log/message_reader.cc | 182 ++++---
src/lib/log/message_reader.h | 19 +-
src/lib/log/messagedef.cc | 70 ++--
src/lib/log/messagedef.h | 14 +-
src/lib/log/messagedef.mes | 202 ++++----
src/lib/log/tests/logger_support_test.cc | 17 +-
src/lib/log/tests/message_dictionary_unittest.cc | 4 +-
src/lib/log/tests/message_reader_unittest.cc | 47 +-
src/lib/log/tests/run_time_init_test.sh.in | 41 +-
src/lib/nsas/Makefile.am | 31 +-
src/lib/nsas/nameserver_address_store.cc | 7 +
src/lib/nsas/nameserver_entry.cc | 24 +-
src/lib/nsas/nsas_log.cc | 26 +
src/lib/nsas/nsas_log.h | 53 ++
src/lib/nsas/nsasdef.mes | 61 +++
src/lib/nsas/tests/Makefile.am | 2 +
src/lib/nsas/tests/run_unittests.cc | 7 +-
src/lib/python/Makefile.am | 1 +
src/lib/python/bind10_config.py.in | 38 ++-
src/lib/python/isc/config/config_data.py | 9 +
.../python/isc/config/tests/config_data_test.py | 21 +
src/lib/python/isc/notify/notify_out.py | 8 +-
src/lib/python/isc/notify/tests/notify_out_test.py | 50 ++-
src/lib/python/isc/testutils/Makefile.am | 2 +-
src/lib/python/isc/testutils/tsigctx_mock.py | 53 ++
src/lib/python/isc/util/Makefile.am | 2 +-
src/lib/python/isc/util/file.py | 29 +
src/lib/python/isc/util/tests/Makefile.am | 2 +-
src/lib/python/isc/util/tests/file_test.py | 32 ++
src/lib/resolve/recursive_query.cc | 252 ++++++++--
src/lib/resolve/recursive_query.h | 14 +
src/lib/resolve/tests/recursive_query_unittest.cc | 134 +++---
src/lib/server_common/Makefile.am | 3 +
src/lib/server_common/keyring.cc | 61 +++
src/lib/server_common/keyring.h | 96 ++++
src/lib/server_common/tests/Makefile.am | 7 +-
src/lib/server_common/tests/data_path.h.in | 16 +
src/lib/server_common/tests/keyring_test.cc | 132 +++++
src/lib/server_common/tests/testdata/spec.spec | 6 +
src/lib/util/Makefile.am | 4 +-
src/lib/util/python/mkpywrapper.py.in | 100 ++++
src/lib/util/python/pycppwrapper_util.h | 308 +++++++++++
src/lib/util/python/wrapper_template.cc | 309 +++++++++++
src/lib/util/python/wrapper_template.h | 59 ++
src/lib/util/pyunittests/Makefile.am | 14 +
src/lib/util/pyunittests/pyunittests_util.cc | 84 +++
179 files changed, 10856 insertions(+), 2066 deletions(-)
create mode 100644 src/bin/cfgmgr/plugins/tests/Makefile.am
create mode 100644 src/bin/cfgmgr/plugins/tests/tsig_keys_test.py
create mode 100644 src/bin/cfgmgr/plugins/tsig_keys.py
create mode 100644 src/bin/cfgmgr/plugins/tsig_keys.spec
create mode 100644 src/bin/stats/stats-schema.spec.in
create mode 100644 src/lib/asiodns/asiodef.mes
delete mode 100644 src/lib/asiodns/asiodef.msg
create mode 100644 src/lib/config/config_log.cc
create mode 100644 src/lib/config/config_log.h
create mode 100644 src/lib/config/configdef.mes
create mode 100644 src/lib/datasrc/logger.cc
create mode 100644 src/lib/datasrc/logger.h
create mode 100644 src/lib/datasrc/messagedef.mes
create mode 100644 src/lib/datasrc/tests/logger_unittest.cc
create mode 100644 src/lib/dns/python/messagerenderer_python.h
create mode 100644 src/lib/dns/python/name_python.h
create mode 100644 src/lib/dns/python/pydnspp_towire.h
create mode 100644 src/lib/dns/python/rcode_python.h
create mode 100644 src/lib/dns/python/tests/tsig_rdata_python_test.py
create mode 100644 src/lib/dns/python/tests/tsigerror_python_test.py
create mode 100644 src/lib/dns/python/tests/tsigrecord_python_test.py
create mode 100644 src/lib/dns/python/tsig_python.h
create mode 100644 src/lib/dns/python/tsig_rdata_python.cc
create mode 100644 src/lib/dns/python/tsig_rdata_python.h
create mode 100644 src/lib/dns/python/tsigerror_python.cc
create mode 100644 src/lib/dns/python/tsigerror_python.h
create mode 100644 src/lib/dns/python/tsigerror_python_inc.cc
create mode 100644 src/lib/dns/python/tsigkey_python.h
create mode 100644 src/lib/dns/python/tsigrecord_python.cc
create mode 100644 src/lib/dns/python/tsigrecord_python.h
create mode 100644 src/lib/dns/tests/testdata/tsig_verify1.spec
create mode 100644 src/lib/dns/tests/testdata/tsig_verify10.spec
create mode 100644 src/lib/dns/tests/testdata/tsig_verify2.spec
create mode 100644 src/lib/dns/tests/testdata/tsig_verify3.spec
create mode 100644 src/lib/dns/tests/testdata/tsig_verify4.spec
create mode 100644 src/lib/dns/tests/testdata/tsig_verify5.spec
create mode 100644 src/lib/dns/tests/testdata/tsig_verify6.spec
create mode 100644 src/lib/dns/tests/testdata/tsig_verify7.spec
create mode 100644 src/lib/dns/tests/testdata/tsig_verify8.spec
create mode 100644 src/lib/dns/tests/testdata/tsig_verify9.spec
delete mode 100644 src/lib/log/message_exception.cc
create mode 100644 src/lib/nsas/nsas_log.cc
create mode 100644 src/lib/nsas/nsas_log.h
create mode 100644 src/lib/nsas/nsasdef.mes
create mode 100644 src/lib/python/isc/testutils/tsigctx_mock.py
create mode 100644 src/lib/python/isc/util/file.py
create mode 100644 src/lib/python/isc/util/tests/file_test.py
create mode 100644 src/lib/server_common/keyring.cc
create mode 100644 src/lib/server_common/keyring.h
create mode 100644 src/lib/server_common/tests/data_path.h.in
create mode 100644 src/lib/server_common/tests/keyring_test.cc
create mode 100644 src/lib/server_common/tests/testdata/spec.spec
create mode 100755 src/lib/util/python/mkpywrapper.py.in
create mode 100644 src/lib/util/python/pycppwrapper_util.h
create mode 100644 src/lib/util/python/wrapper_template.cc
create mode 100644 src/lib/util/python/wrapper_template.h
create mode 100644 src/lib/util/pyunittests/Makefile.am
create mode 100644 src/lib/util/pyunittests/pyunittests_util.cc
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index d5de903..e44caa2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,9 +1,95 @@
-231. [func]*
- The logging interface changed slightly. We use
- logger.foo(MESSAGE_ID).arg(bar); instead of logger.foo(MESSAGE_ID, bar);
- internally. The message definitions use '%1,%2,...' instead of '%s,%d',
- which allows us to cope better with mismatched placeholders and allows
- reordering of them in case of translation.
+243. [func]* feng
+ Add optional hmac algorithm SHA224/384/812.
+ (Trac#782,git 77d792c9d7c1a3f95d3e6a8b721ac79002cd7db1).
+
+bind10-devel-20110519 released on May 19, 2011
+
+242. [func] jinmei
+ xfrin: added support for TSIG verify. This change completes TSIG
+ support in b10-xfrin.
+ (Trac #914, git 78502c021478d97672232015b7df06a7d52e531b)
+
+241. [func] jinmei
+ pydnspp: added python extension for the TSIG API introduced in
+ change 235.
+ (Trac #905, git 081891b38f05f9a186814ab7d1cd5c572b8f777f)
+ (Trac #915, git 0555ab65d0e43d03b2d40c95d833dd050eea6c23)
+
+240. [func]* jelte
+ Updated configuration options to Xfrin, so that you can specify
+ a master address, port, and TSIG key per zone. Still only one per
+ zone at this point, and TSIG keys are (currently) only specified
+ by their full string representation. This replaces the
+ Xfrin/master_addr, Xfrin/master_port, and short-lived
+ Xfrin/tsig_key configurations with a Xfrin/zones list.
+ (Trac #811, git 88504d121c5e08fff947b92e698a54d24d14c375)
+
+239. [bug] jerry
+ src/bin/xfrout: If a zone doesn't have notify slaves (only has
+ one apex ns record - the primary master name server) will cause
+ b10-xfrout uses 100% of CPU.
+ (Trac #684, git d11b5e89203a5340d4e5ca51c4c02db17c33dc1f)
+
+238. [func] zhang likun
+ Implement the simplest forwarder, which pass everything through
+ except QID, port number. The response will not be cached.
+ (Trac #598_new, git 8e28187a582820857ef2dae9b13637a3881f13ba)
+
+237. [bug] naokikambe
+ Resolved that the stats module wasn't configurable in bindctl in
+ spite of its having configuration items. The configuration part
+ was removed from the original spec file "stats.spec" and was
+ placed in a new spec file "stats-schema.spec". Because it means
+ definitions of statistics items. The command part is still
+ there. Thus stats module currently has no its own configuration,
+ and the items in "stats-schema.spec" are neither visible nor
+ configurable through bindctl. "stats-schema.spec" is shared with
+ stats module and stats-httpd module, and maybe with other
+ statistical modules in future. "stats.spec" has own configuration
+ and commands of stats module, if it requires.
+ (Trac#719, git a234b20dc6617392deb8a1e00eb0eed0ff353c0a)
+
+236. [func] jelte
+ C++ client side of configuration now uses BIND10 logging system.
+ It also has improved error handling when communicating with the
+ rest of the system.
+ (Trac #743, git 86632c12308c3ed099d75eb828f740c526dd7ec0)
+
+235. [func] jinmei
+ libdns++: added support for TSIG signing and verification. It can
+ be done using a newly introduced TSIGContext class.
+ Note: we temporarily disabled support for truncated signature
+ and modified some part of the code introduced in #226 accordingly.
+ We plan to fix this pretty soon.
+ (Trac #812, git ebe0c4b1e66d359227bdd1bd47395fee7b957f14)
+ (Trac #871, git 7c54055c0e47c7a0e36fcfab4b47ff180c0ca8c8)
+ (Trac #813, git ffa2f0672084c1f16e5784cdcdd55822f119feaa)
+ (Trac #893, git 5aaa6c0f628ed7c2093ecdbac93a2c8cf6c94349)
+
+234. [func] jerry
+ src/bin/xfrin: update xfrin to use TSIG. Currently it only supports
+ sending a signed TSIG request or SOA request.
+ (Trac #815, git a892818fb13a1839c82104523cb6cb359c970e88)
+
+233. [func] stephen
+ Added new-style logging statements to the NSAS code.
+ (Trac #745, git ceef68cd1223ae14d8412adbe18af2812ade8c2d)
+
+232. [func] stephen
+ To facilitate the writing of extended descriptions in
+ message files, altered the message file format. The message
+ is now flagged with a "%" as the first non-blank character
+ in the line and the lines in the extended description are
+ no longer preceded by a "+".
+ (Trac #900, git b395258c708b49a5da8d0cffcb48d83294354ba3)
+
+231. [func]* vorner
+ The logging interface changed slightly. We use
+ logger.foo(MESSAGE_ID).arg(bar); instead of logger.foo(MESSAGE_ID,
+ bar); internally. The message definitions use '%1,%2,...'
+ instead of '%s,%d', which allows us to cope better with
+ mismatched placeholders and allows reordering of them in
+ case of translation.
(Trac901, git 4903410e45670b30d7283f5d69dc28c2069237d6)
230. [bug] naokikambe
@@ -40,14 +126,14 @@
(Trac #781, git 9df42279a47eb617f586144dce8cce680598558a)
225. [func] naokikambe
- Added the HTTP/XML interface(b10-stats-httpd) to the
+ Added the HTTP/XML interface (b10-stats-httpd) to the
statistics feature in BIND 10. b10-stats-httpd is a standalone
HTTP server and it requests statistics data to the stats
- daemon(b10-stats) and sends it to HTTP clients in XML
+ daemon (b10-stats) and sends it to HTTP clients in XML
format. Items of the data collected via b10-stats-httpd
are almost equivalent to ones which are collected via
- bindctl. Since it also can send XSL(Extensible Stylessheet
- Language) document and XSD(XML Schema definition) document,
+ bindctl. Since it also can send XSL (Extensible Stylesheet
+ Language) document and XSD (XML Schema definition) document,
XML document is human-friendly to view through web browsers
and its data types are strictly defined.
(Trac #547, git 1cbd51919237a6e65983be46e4f5a63d1877b1d3)
@@ -66,11 +152,13 @@
reconfigure them.
(Trac #775, git 572ac2cf62e18f7eb69d670b890e2a3443bfd6e7)
-222. [bug] jerry
- src/lib/zonemgr: Fix a bug that xfrin not checking for new copy of
- zone on startup. Imposes some random jitters to avoid many zones
- need to do refresh at the same time.
- (Trac #387, svn 9140fab9bab5f6502bd15d391fd51ac078b0b89b)
+222. [bug]* jerry
+ src/lib/zonemgr: Fix a bug that xfrin not checking for new
+ copy of zone on startup. Imposes some random jitters to
+ avoid many zones need to do refresh at the same time. This
+ removed the Zonemgr/jitter_scope setting and introduced
+ Zonemgr/refresh_jitter and Zonemgr/reload_jitter.
+ (Trac #387, git 1241ddcffa16285d0a7bb01d6a8526e19fbb70cb)
221. [func]* jerry
src/lib/util: Create C++ utility library.
diff --git a/README b/README
index 5320a6e..a6509da 100644
--- a/README
+++ b/README
@@ -19,7 +19,9 @@ backends), b10-resolver recursive or forwarding DNS server, b10-cmdctl
remote control daemon, b10-cfgmgr configuration manager, b10-xfrin
AXFR inbound service, b10-xfrout outgoing AXFR service, b10-zonemgr
secondary manager, b10-stats statistics collection and reporting
-daemon, and a new libdns++ library for C++ with a python wrapper.
+daemon, b10-stats-httpd for HTTP access to XML-formatted stats,
+b10-host DNS lookup utility, and a new libdns++ library for C++
+with a python wrapper.
Documentation is included and also available via the BIND 10
website at http://bind10.isc.org/
diff --git a/configure.ac b/configure.ac
index 784bf45..93b9304 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.59])
-AC_INIT(bind10-devel, 20110322, bind10-dev at isc.org)
+AC_INIT(bind10-devel, 20110519, bind10-dev at isc.org)
AC_CONFIG_SRCDIR(README)
AM_INIT_AUTOMAKE
AC_CONFIG_HEADERS([config.h])
@@ -686,6 +686,7 @@ AC_CONFIG_FILES([Makefile
src/bin/bindctl/tests/Makefile
src/bin/cfgmgr/Makefile
src/bin/cfgmgr/plugins/Makefile
+ src/bin/cfgmgr/plugins/tests/Makefile
src/bin/cfgmgr/tests/Makefile
src/bin/host/Makefile
src/bin/loadzone/Makefile
@@ -775,6 +776,7 @@ AC_CONFIG_FILES([Makefile
src/lib/util/Makefile
src/lib/util/io/Makefile
src/lib/util/unittests/Makefile
+ src/lib/util/pyunittests/Makefile
src/lib/util/tests/Makefile
tests/Makefile
tests/system/Makefile
@@ -806,6 +808,7 @@ AC_OUTPUT([doc/version.ent
src/bin/stats/stats.py
src/bin/stats/stats_httpd.py
src/bin/stats/stats.spec
+ src/bin/stats/stats-schema.spec
src/bin/stats/stats-httpd.spec
src/bin/stats/stats-httpd-xml.tpl
src/bin/stats/stats-httpd-xsd.tpl
@@ -839,6 +842,8 @@ AC_OUTPUT([doc/version.ent
src/lib/cc/session_config.h.pre
src/lib/cc/tests/session_unittests_config.h
src/lib/log/tests/run_time_init_test.sh
+ src/lib/util/python/mkpywrapper.py
+ src/lib/server_common/tests/data_path.h
tests/system/conf.sh
tests/system/glue/setup.sh
tests/system/glue/nsx1/b10-config.db
@@ -864,6 +869,7 @@ AC_OUTPUT([doc/version.ent
chmod +x src/lib/dns/gen-rdatacode.py
chmod +x src/lib/dns/tests/testdata/gen-wiredata.py
chmod +x src/lib/log/tests/run_time_init_test.sh
+ chmod +x src/lib/util/python/mkpywrapper.py
chmod +x tests/system/conf.sh
])
AC_OUTPUT
diff --git a/doc/Doxyfile b/doc/Doxyfile
index a57d275..05b6ef3 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -574,6 +574,7 @@ INPUT = ../src/lib/cc ../src/lib/config \
../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas \
../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
../src/bin/sockcreator/ ../src/lib/util/
+ ../src/lib/resolve
# 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
@@ -1164,7 +1165,7 @@ XML_DTD =
# and cross-referencing information) to the XML output. Note that
# enabling this will significantly increase the size of the XML output.
-XML_PROGRAMLISTING = YES
+XML_PROGRAMLISTING = NO
#---------------------------------------------------------------------------
# configuration options for the AutoGen Definitions output
diff --git a/doc/guide/bind10-guide.html b/doc/guide/bind10-guide.html
index a631a9c..069f508 100644
--- a/doc/guide/bind10-guide.html
+++ b/doc/guide/bind10-guide.html
@@ -1,19 +1,19 @@
-<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>BIND 10 Guide</title><link rel="stylesheet" href="./bind10-guide.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.75.2"><meta name="description" content="BIND 10 is a Domain Name System (DNS) suite managed by Internet Systems Consortium (ISC). It includes DNS libraries and modular components for controlling authoritative and recursive DNS servers. This is the reference guide for BIND 10 version 20110322. The most up-to-date version of this document, along with other documents for BIND 10, can be found at ."></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="book" title="BIND 10 Guide"><div class="titlepage"><div><div><h1 class="title"><a name="id1168230298903"></a>BIND 10 Guide</h1></div><div><h2 class="subtitle">Administrator Reference for BIND 10</h2></div><div><p class="releaseinfo">This is the referenc
e guide for BIND 10 version
- 20110322.</p></div><div><p class="copyright">Copyright © 2010 Internet Systems Consortium, Inc.</p></div><div><div class="abstract" title="Abstract"><p class="title"><b>Abstract</b></p><p>BIND 10 is a Domain Name System (DNS) suite managed by
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>BIND 10 Guide</title><link rel="stylesheet" href="./bind10-guide.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.75.2"><meta name="description" content="BIND 10 is a Domain Name System (DNS) suite managed by Internet Systems Consortium (ISC). It includes DNS libraries and modular components for controlling authoritative and recursive DNS servers. This is the reference guide for BIND 10 version 20110519. The most up-to-date version of this document, along with other documents for BIND 10, can be found at ."></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="book" title="BIND 10 Guide"><div class="titlepage"><div><div><h1 class="title"><a name="id1168230298903"></a>BIND 10 Guide</h1></div><div><h2 class="subtitle">Administrator Reference for BIND 10</h2></div><div><p class="releaseinfo">This is the referenc
e guide for BIND 10 version
+ 20110519.</p></div><div><p class="copyright">Copyright © 2010 Internet Systems Consortium, Inc.</p></div><div><div class="abstract" title="Abstract"><p class="title"><b>Abstract</b></p><p>BIND 10 is a Domain Name System (DNS) suite managed by
Internet Systems Consortium (ISC). It includes DNS libraries
and modular components for controlling authoritative and
recursive DNS servers.
</p><p>
- This is the reference guide for BIND 10 version 20110322.
+ This is the reference guide for BIND 10 version 20110519.
The most up-to-date version of this document, along with
- other documents for BIND 10, can be found at <a class="ulink" href="http://bind10.isc.org/docs" target="_top">http://bind10.isc.org/docs</a>. </p></div></div></div><hr></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="chapter"><a href="#intro">1. Introduction</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230299038">Supported Platforms</a></span></dt><dt><span class="section"><a href="#id1168230299065">Required Software</a></span></dt><dt><span class="section"><a href="#starting_stopping">Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">Managing BIND 10</a></span></dt></dl></dd><dt><span class="chapter"><a href="#installation">2. Installation</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230284842">Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">Quick start</a></span></dt><dt><span class="section"><a href="#install">In
stallation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285021">Download Tar File</a></span></dt><dt><span class="section"><a href="#id1168230285041">Retrieve from Git</a></span></dt><dt><span class="section"><a href="#id1168230285101">Configure before the build</a></span></dt><dt><span class="section"><a href="#id1168230285198">Build</a></span></dt><dt><span class="section"><a href="#id1168230285214">Install</a></span></dt><dt><span class="section"><a href="#id1168230285238">Install Hierarchy</a></span></dt></dl></dd></dl></dd><dt><span class="chapter"><a href="#bind10">3. Starting BIND10 with <span class="command"><strong>bind10</strong></span></a></span></dt><dd><dl><dt><span class="section"><a href="#start">Starting BIND 10</a></span></dt></dl></dd><dt><span class="chapter"><a href="#msgq">4. Command channel</a></span></dt><dt><span class="chapter"><a href="#cfgmgr">5. Configuration manager</a></span></dt><dt><span class="chapter"><a hr
ef="#cmdctl">6. Remote control daemon</a></span></dt><dd><dl><dt><span class="section"><a href="#cmdctl.spec">Configuration specification for b10-cmdctl</a></span></dt></dl></dd><dt><span class="chapter"><a href="#bindctl">7. Control and configure user interface</a></span></dt><dt><span class="chapter"><a href="#authserver">8. Authoritative Server</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285812">Server Configurations</a></span></dt><dt><span class="section"><a href="#id1168230285877">Data Source Backends</a></span></dt><dt><span class="section"><a href="#id1168230285908">Loading Master Zones Files</a></span></dt></dl></dd><dt><span class="chapter"><a href="#xfrin">9. Incoming Zone Transfers</a></span></dt><dt><span class="chapter"><a href="#xfrout">10. Outbound Zone Transfers</a></span></dt><dt><span class="chapter"><a href="#zonemgr">11. Secondary Manager</a></span></dt><dt><span class="chapter"><a href="#resolverserver">12. Recursive Name Server<
/a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230286296">Forwarding</a></span></dt></dl></dd><dt><span class="chapter"><a href="#statistics">13. Statistics</a></span></dt></dl></div><div class="chapter" title="Chapter 1. Introduction"><div class="titlepage"><div><div><h2 class="title"><a name="intro"></a>Chapter 1. Introduction</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230299038">Supported Platforms</a></span></dt><dt><span class="section"><a href="#id1168230299065">Required Software</a></span></dt><dt><span class="section"><a href="#starting_stopping">Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">Managing BIND 10</a></span></dt></dl></div><p>
+ other documents for BIND 10, can be found at <a class="ulink" href="http://bind10.isc.org/docs" target="_top">http://bind10.isc.org/docs</a>. </p></div></div></div><hr></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="chapter"><a href="#intro">1. Introduction</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230299038">Supported Platforms</a></span></dt><dt><span class="section"><a href="#id1168230299065">Required Software</a></span></dt><dt><span class="section"><a href="#starting_stopping">Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">Managing BIND 10</a></span></dt></dl></dd><dt><span class="chapter"><a href="#installation">2. Installation</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230284846">Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">Quick start</a></span></dt><dt><span class="section"><a href="#install">In
stallation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285026">Download Tar File</a></span></dt><dt><span class="section"><a href="#id1168230285045">Retrieve from Git</a></span></dt><dt><span class="section"><a href="#id1168230285106">Configure before the build</a></span></dt><dt><span class="section"><a href="#id1168230285203">Build</a></span></dt><dt><span class="section"><a href="#id1168230285219">Install</a></span></dt><dt><span class="section"><a href="#id1168230285242">Install Hierarchy</a></span></dt></dl></dd></dl></dd><dt><span class="chapter"><a href="#bind10">3. Starting BIND10 with <span class="command"><strong>bind10</strong></span></a></span></dt><dd><dl><dt><span class="section"><a href="#start">Starting BIND 10</a></span></dt></dl></dd><dt><span class="chapter"><a href="#msgq">4. Command channel</a></span></dt><dt><span class="chapter"><a href="#cfgmgr">5. Configuration manager</a></span></dt><dt><span class="chapter"><a hr
ef="#cmdctl">6. Remote control daemon</a></span></dt><dd><dl><dt><span class="section"><a href="#cmdctl.spec">Configuration specification for b10-cmdctl</a></span></dt></dl></dd><dt><span class="chapter"><a href="#bindctl">7. Control and configure user interface</a></span></dt><dt><span class="chapter"><a href="#authserver">8. Authoritative Server</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285816">Server Configurations</a></span></dt><dt><span class="section"><a href="#id1168230285881">Data Source Backends</a></span></dt><dt><span class="section"><a href="#id1168230285912">Loading Master Zones Files</a></span></dt></dl></dd><dt><span class="chapter"><a href="#xfrin">9. Incoming Zone Transfers</a></span></dt><dt><span class="chapter"><a href="#xfrout">10. Outbound Zone Transfers</a></span></dt><dt><span class="chapter"><a href="#zonemgr">11. Secondary Manager</a></span></dt><dt><span class="chapter"><a href="#resolverserver">12. Recursive Name Server<
/a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230286300">Forwarding</a></span></dt></dl></dd><dt><span class="chapter"><a href="#statistics">13. Statistics</a></span></dt></dl></div><div class="chapter" title="Chapter 1. Introduction"><div class="titlepage"><div><div><h2 class="title"><a name="intro"></a>Chapter 1. Introduction</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230299038">Supported Platforms</a></span></dt><dt><span class="section"><a href="#id1168230299065">Required Software</a></span></dt><dt><span class="section"><a href="#starting_stopping">Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">Managing BIND 10</a></span></dt></dl></div><p>
BIND is the popular implementation of a DNS server, developer
interfaces, and DNS tools.
BIND 10 is a rewrite of BIND 9. BIND 10 is written in C++ and Python
and provides a modular environment for serving and maintaining DNS.
</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
This guide covers the experimental prototype of
- BIND 10 version 20110322.
+ BIND 10 version 20110519.
</p></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
BIND 10 provides a EDNS0- and DNSSEC-capable
authoritative DNS server and a caching recursive name server
@@ -31,12 +31,16 @@
</p></div><div class="section" title="Required Software"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230299065"></a>Required Software</h2></div></div></div><p>
BIND 10 requires Python 3.1. Later versions may work, but Python
3.1 is the minimum version which will work.
- </p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
+ </p><p>
+ BIND 10 uses the Botan crypto library for C++. It requires
+ at least Botan version 1.8. To build BIND 10, install the
+ Botan libraries and development include headers.
+ </p><p>
The authoritative server requires SQLite 3.3.9 or newer.
The <span class="command"><strong>b10-xfrin</strong></span>, <span class="command"><strong>b10-xfrout</strong></span>,
and <span class="command"><strong>b10-zonemgr</strong></span> modules require the
libpython3 library and the Python _sqlite3.so module.
- </p></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
+ </p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
Some operating systems do not provide these dependencies
in their default installation nor standard packages
collections.
@@ -132,7 +136,7 @@
and, of course, DNS. These include detailed developer
documentation and code examples.
- </p></div><div class="chapter" title="Chapter 2. Installation"><div class="titlepage"><div><div><h2 class="title"><a name="installation"></a>Chapter 2. Installation</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230284842">Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">Quick start</a></span></dt><dt><span class="section"><a href="#install">Installation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285021">Download Tar File</a></span></dt><dt><span class="section"><a href="#id1168230285041">Retrieve from Git</a></span></dt><dt><span class="section"><a href="#id1168230285101">Configure before the build</a></span></dt><dt><span class="section"><a href="#id1168230285198">Build</a></span></dt><dt><span class="section"><a href="#id1168230285214">Install</a></span></dt><dt><span class="section"><a href="#id1168230285238">Install Hierarchy<
/a></span></dt></dl></dd></dl></div><div class="section" title="Building Requirements"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230284842"></a>Building Requirements</h2></div></div></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
+ </p></div><div class="chapter" title="Chapter 2. Installation"><div class="titlepage"><div><div><h2 class="title"><a name="installation"></a>Chapter 2. Installation</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230284846">Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">Quick start</a></span></dt><dt><span class="section"><a href="#install">Installation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285026">Download Tar File</a></span></dt><dt><span class="section"><a href="#id1168230285045">Retrieve from Git</a></span></dt><dt><span class="section"><a href="#id1168230285106">Configure before the build</a></span></dt><dt><span class="section"><a href="#id1168230285203">Build</a></span></dt><dt><span class="section"><a href="#id1168230285219">Install</a></span></dt><dt><span class="section"><a href="#id1168230285242">Install Hierarchy<
/a></span></dt></dl></dd></dl></div><div class="section" title="Building Requirements"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230284846"></a>Building Requirements</h2></div></div></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
Some operating systems have split their distribution packages into
a run-time and a development package. You will need to install
the development package versions, which include header files and
@@ -188,14 +192,14 @@
the Git code revision control system or as a downloadable
tar file. It may also be available in pre-compiled ready-to-use
packages from operating system vendors.
- </p><div class="section" title="Download Tar File"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285021"></a>Download Tar File</h3></div></div></div><p>
+ </p><div class="section" title="Download Tar File"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285026"></a>Download Tar File</h3></div></div></div><p>
Downloading a release tar file is the recommended method to
obtain the source code.
</p><p>
The BIND 10 releases are available as tar file downloads from
<a class="ulink" href="ftp://ftp.isc.org/isc/bind10/" target="_top">ftp://ftp.isc.org/isc/bind10/</a>.
Periodic development snapshots may also be available.
- </p></div><div class="section" title="Retrieve from Git"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285041"></a>Retrieve from Git</h3></div></div></div><p>
+ </p></div><div class="section" title="Retrieve from Git"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285045"></a>Retrieve from Git</h3></div></div></div><p>
Downloading this "bleeding edge" code is recommended only for
developers or advanced users. Using development code in a production
environment is not recommended.
@@ -229,7 +233,7 @@
<span class="command"><strong>autoheader</strong></span>,
<span class="command"><strong>automake</strong></span>,
and related commands.
- </p></div><div class="section" title="Configure before the build"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285101"></a>Configure before the build</h3></div></div></div><p>
+ </p></div><div class="section" title="Configure before the build"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285106"></a>Configure before the build</h3></div></div></div><p>
BIND 10 uses the GNU Build System to discover build environment
details.
To generate the makefiles using the defaults, simply run:
@@ -260,16 +264,16 @@
</p><p>
If the configure fails, it may be due to missing or old
dependencies.
- </p></div><div class="section" title="Build"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285198"></a>Build</h3></div></div></div><p>
+ </p></div><div class="section" title="Build"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285203"></a>Build</h3></div></div></div><p>
After the configure step is complete, to build the executables
from the C++ code and prepare the Python scripts, run:
</p><pre class="screen">$ <strong class="userinput"><code>make</code></strong></pre><p>
- </p></div><div class="section" title="Install"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285214"></a>Install</h3></div></div></div><p>
+ </p></div><div class="section" title="Install"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285219"></a>Install</h3></div></div></div><p>
To install the BIND 10 executables, support files,
and documentation, run:
</p><pre class="screen">$ <strong class="userinput"><code>make install</code></strong></pre><p>
- </p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>The install step may require superuser privileges.</p></div></div><div class="section" title="Install Hierarchy"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285238"></a>Install Hierarchy</h3></div></div></div><p>
+ </p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>The install step may require superuser privileges.</p></div></div><div class="section" title="Install Hierarchy"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285242"></a>Install Hierarchy</h3></div></div></div><p>
The following is the layout of the complete BIND 10 installation:
</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem">
<code class="filename">bin/</code> —
@@ -486,12 +490,12 @@ shutdown
the details and relays (over a <span class="command"><strong>b10-msgq</strong></span> command
channel) the configuration on to the specified module.
</p><p>
- </p></div><div class="chapter" title="Chapter 8. Authoritative Server"><div class="titlepage"><div><div><h2 class="title"><a name="authserver"></a>Chapter 8. Authoritative Server</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230285812">Server Configurations</a></span></dt><dt><span class="section"><a href="#id1168230285877">Data Source Backends</a></span></dt><dt><span class="section"><a href="#id1168230285908">Loading Master Zones Files</a></span></dt></dl></div><p>
+ </p></div><div class="chapter" title="Chapter 8. Authoritative Server"><div class="titlepage"><div><div><h2 class="title"><a name="authserver"></a>Chapter 8. Authoritative Server</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230285816">Server Configurations</a></span></dt><dt><span class="section"><a href="#id1168230285881">Data Source Backends</a></span></dt><dt><span class="section"><a href="#id1168230285912">Loading Master Zones Files</a></span></dt></dl></div><p>
The <span class="command"><strong>b10-auth</strong></span> is the authoritative DNS server.
It supports EDNS0 and DNSSEC. It supports IPv6.
Normally it is started by the <span class="command"><strong>bind10</strong></span> master
process.
- </p><div class="section" title="Server Configurations"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285812"></a>Server Configurations</h2></div></div></div><p>
+ </p><div class="section" title="Server Configurations"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285816"></a>Server Configurations</h2></div></div></div><p>
<span class="command"><strong>b10-auth</strong></span> is configured via the
<span class="command"><strong>b10-cfgmgr</strong></span> configuration manager.
The module name is <span class="quote">“<span class="quote">Auth</span>”</span>.
@@ -511,7 +515,7 @@ This may be a temporary setting until then.
</p><div class="variablelist"><dl><dt><span class="term">shutdown</span></dt><dd>Stop the authoritative DNS server.
</dd></dl></div><p>
- </p></div><div class="section" title="Data Source Backends"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285877"></a>Data Source Backends</h2></div></div></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
+ </p></div><div class="section" title="Data Source Backends"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285881"></a>Data Source Backends</h2></div></div></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
For the development prototype release, <span class="command"><strong>b10-auth</strong></span>
supports a SQLite3 data source backend and in-memory data source
backend.
@@ -525,7 +529,7 @@ This may be a temporary setting until then.
The default is <code class="filename">/usr/local/var/</code>.)
This data file location may be changed by defining the
<span class="quote">“<span class="quote">database_file</span>”</span> configuration.
- </p></div><div class="section" title="Loading Master Zones Files"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285908"></a>Loading Master Zones Files</h2></div></div></div><p>
+ </p></div><div class="section" title="Loading Master Zones Files"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285912"></a>Loading Master Zones Files</h2></div></div></div><p>
RFC 1035 style DNS master zone files may imported
into a BIND 10 data source by using the
<span class="command"><strong>b10-loadzone</strong></span> utility.
@@ -603,7 +607,7 @@ This may be a temporary setting until then.
</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
Access control (such as allowing notifies) is not yet provided.
The primary/secondary service is not yet complete.
- </p></div></div><div class="chapter" title="Chapter 12. Recursive Name Server"><div class="titlepage"><div><div><h2 class="title"><a name="resolverserver"></a>Chapter 12. Recursive Name Server</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230286296">Forwarding</a></span></dt></dl></div><p>
+ </p></div></div><div class="chapter" title="Chapter 12. Recursive Name Server"><div class="titlepage"><div><div><h2 class="title"><a name="resolverserver"></a>Chapter 12. Recursive Name Server</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230286300">Forwarding</a></span></dt></dl></div><p>
The <span class="command"><strong>b10-resolver</strong></span> process is started by
<span class="command"><strong>bind10</strong></span>.
@@ -632,7 +636,7 @@ This may be a temporary setting until then.
> <strong class="userinput"><code>config set Resolver/listen_on [{ "address": "127.0.0.1", "port": 53 }]</code></strong>
> <strong class="userinput"><code>config commit</code></strong>
</pre><p>
- </p><div class="section" title="Forwarding"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230286296"></a>Forwarding</h2></div></div></div><p>
+ </p><div class="section" title="Forwarding"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230286300"></a>Forwarding</h2></div></div></div><p>
To enable forwarding, the upstream address and port must be
configured to forward queries to, such as:
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index c020f11..5eb4dc7 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -79,12 +79,24 @@
3.1 is the minimum version which will work.
</para>
- <note><para>
+ <para>
+ BIND 10 uses the Botan crypto library for C++. It requires
+ at least Botan version 1.8. To build BIND 10, install the
+ Botan libraries and development include headers.
+ </para>
+
+<!--
+TODO
+Debian and Ubuntu:
+ libgmp3-dev and libbz2-dev required for botan too
+-->
+
+ <para>
The authoritative server requires SQLite 3.3.9 or newer.
The <command>b10-xfrin</command>, <command>b10-xfrout</command>,
and <command>b10-zonemgr</command> modules require the
libpython3 library and the Python _sqlite3.so module.
- </para></note>
+ </para>
<!-- TODO: this will change ... -->
<!-- TODO: list where to get these from -->
diff --git a/src/bin/cfgmgr/b10-cfgmgr.py.in b/src/bin/cfgmgr/b10-cfgmgr.py.in
index 16c8f76..d91dfca 100755
--- a/src/bin/cfgmgr/b10-cfgmgr.py.in
+++ b/src/bin/cfgmgr/b10-cfgmgr.py.in
@@ -18,6 +18,7 @@
import sys; sys.path.append ('@@PYTHONPATH@@')
from isc.config.cfgmgr import ConfigManager, ConfigManagerDataReadError
+import bind10_config
from isc.cc import SessionError
import isc.util.process
import signal
@@ -28,24 +29,10 @@ import os.path
isc.util.process.rename()
-# If B10_FROM_SOURCE is set in the environment, we use data files
-# from a directory relative to the value of that variable, or, if defined,
-# relative to the value of B10_FROM_SOURCE_LOCALSTATEDIR. Otherwise
-# we use the ones installed on the system.
-# B10_FROM_SOURCE_LOCALSTATEDIR is specifically intended to be used for
-# tests where we want to use variuos types of configuration within the test
-# environment. (We may want to make it even more generic so that the path is
-# passed from the boss process)
-if "B10_FROM_SOURCE" in os.environ:
- if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
- DATA_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
- else:
- DATA_PATH = os.environ["B10_FROM_SOURCE"]
- PLUGIN_PATHS = [DATA_PATH + '/src/bin/cfgmgr/plugins']
-else:
- PREFIX = "@prefix@"
- DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
- PLUGIN_PATHS = ["@prefix@/share/@PACKAGE@/config_plugins"]
+# Import some paths from our configuration
+DATA_PATH = bind10_config.DATA_PATH
+PLUGIN_PATHS = bind10_config.PLUGIN_PATHS
+PREFIX = bind10_config.PREFIX
DEFAULT_CONFIG_FILE = "b10-config.db"
cm = None
diff --git a/src/bin/cfgmgr/plugins/Makefile.am b/src/bin/cfgmgr/plugins/Makefile.am
index 952fde6..d83c2bb 100644
--- a/src/bin/cfgmgr/plugins/Makefile.am
+++ b/src/bin/cfgmgr/plugins/Makefile.am
@@ -1 +1,5 @@
-EXTRA_DIST = README
+SUBDIRS = tests
+EXTRA_DIST = README tsig_keys.py tsig_keys.spec
+
+config_plugindir = @prefix@/share/@PACKAGE@/config_plugins
+config_plugin_DATA = tsig_keys.py tsig_keys.spec
diff --git a/src/bin/cfgmgr/plugins/tests/Makefile.am b/src/bin/cfgmgr/plugins/tests/Makefile.am
new file mode 100644
index 0000000..896dab7
--- /dev/null
+++ b/src/bin/cfgmgr/plugins/tests/Makefile.am
@@ -0,0 +1,27 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = tsig_keys_test.py
+
+EXTRA_DIST = $(PYTESTS)
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+LIBRARY_PATH_PLACEHOLDER =
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs
+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 ; \
+ env B10_TEST_PLUGIN_DIR=$(abs_srcdir)/..:$(abs_builddir)/.. \
+ env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/cfgmgr:$(abs_top_builddir)/src/lib/dns/python/.libs \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
+
diff --git a/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py b/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py
new file mode 100644
index 0000000..be2921c
--- /dev/null
+++ b/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py
@@ -0,0 +1,103 @@
+# 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.
+
+# Make sure we can load the module, put it into path
+import sys
+import os
+sys.path.extend(os.environ["B10_TEST_PLUGIN_DIR"].split(':'))
+
+import tsig_keys
+import unittest
+import isc.config.module_spec
+
+class TSigKeysTest(unittest.TestCase):
+ def test_load(self):
+ """
+ Checks the entry point returns the correct values.
+ """
+ (spec, check) = tsig_keys.load()
+ # It returns the checking function
+ self.assertEqual(check, tsig_keys.check)
+ # The plugin stores it's spec
+ self.assertEqual(spec, tsig_keys.spec)
+
+ def test_spec(self):
+ """
+ Checks the spec is looking sane (doesn't do really deep check here).
+ """
+ spec = tsig_keys.spec
+ # In python, we don't generally check the type of something, because
+ # of the duck typing.
+ # But this is unittest, so we check it does what we intend and
+ # supplying that's behaving the same but is different is not our
+ # intention
+ self.assertTrue(isinstance(spec, isc.config.module_spec.ModuleSpec))
+ # Correct name
+ self.assertEqual("tsig_keys", spec.get_module_name())
+ # There are no commands, nobody would handle them anyway
+ self.assertEqual([], spec.get_commands_spec())
+ # There's some nonempty configuration
+ self.assertNotEqual({}, spec.get_config_spec())
+
+ def test_missing_keys(self):
+ """
+ Test that missing keys doesn't kill us. There are just no keys there.
+ """
+ self.assertEqual(None, tsig_keys.check({}))
+
+ def test_data_empty(self):
+ """Check we accept valid config with empty set of tsig keys."""
+ self.assertEqual(None, tsig_keys.check({'keys': []}))
+
+ def test_keys_valid(self):
+ """
+ Check we accept some valid keys (we don't check all the algorithms,
+ that's the job of isc.dns.TSIGKey).
+ """
+ self.assertEqual(None, tsig_keys.check({'keys':
+ ['testkey:QklORCAxMCBpcyBjb29sCg==',
+ 'test.key:QklORCAxMCBpcyBjb29sCg==:hmac-sha1']}))
+
+ def test_keys_same_name(self):
+ """
+ Test we reject when we have multiple keys with the same name.
+ """
+ self.assertEqual("Multiple TSIG keys with name 'test.key.'",
+ tsig_keys.check({'keys':
+ ['test.key:QklORCAxMCBpcyBjb29sCg==',
+ 'test.key:b3RoZXIK']}))
+
+ def test_invalid_key(self):
+ """
+ Test we reject invalid key.
+ """
+ self.assertEqual("TSIG: Invalid TSIG key string: invalid.key",
+ tsig_keys.check({'keys': ['invalid.key']}))
+ self.assertEqual(
+ "TSIG: attempt to decode a value not in base64 char set",
+ tsig_keys.check({'keys': ['invalid.key:123']}))
+
+ def test_bad_format(self):
+ """
+ Test we fail on bad format. We don't really care much how here, though,
+ as this should not get in trough config manager anyway.
+ """
+ self.assertNotEqual(None, tsig_keys.check({'bad_name': {}}))
+ self.assertNotEqual(None, tsig_keys.check({'keys': 'not_list'}))
+ self.assertNotEqual(None, tsig_keys.check({'keys': 42}))
+ self.assertNotEqual(None, tsig_keys.check({'keys': {}}))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/bin/cfgmgr/plugins/tsig_keys.py b/src/bin/cfgmgr/plugins/tsig_keys.py
new file mode 100644
index 0000000..d57e645
--- /dev/null
+++ b/src/bin/cfgmgr/plugins/tsig_keys.py
@@ -0,0 +1,50 @@
+# 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 is the plugin for tsig_keys configuration section. The TSIG keyring
+# lives there (eg. all the shared secrets, with some exceptions where there
+# are some TSIG keys elsewhere, but these should be removed soon). We do
+# sanity checking of user configuration here, simply by trying to construct
+# all the keys here.
+
+from isc.config.module_spec import module_spec_from_file
+from isc.util.file import path_search
+from pydnspp import TSIGKey, InvalidParameter
+from bind10_config import PLUGIN_PATHS
+spec = module_spec_from_file(path_search('tsig_keys.spec', PLUGIN_PATHS))
+
+def check(config):
+ # Check the data layout first
+ errors=[]
+ if not spec.validate_config(False, config, errors):
+ return ' '.join(errors)
+ # Get the list of keys, if any
+ keys = config.get('keys', [])
+ # Run through them, check they can be constructed and there are no
+ # duplicates
+ keyNames = set()
+ for key in keys:
+ try:
+ name = str(TSIGKey(key).get_key_name())
+ except InvalidParameter as e:
+ return "TSIG: " + str(e)
+ if name in keyNames:
+ return "Multiple TSIG keys with name '" + name + "'"
+ keyNames.add(name)
+ # No error found, so let's assume it's OK
+ return None
+
+def load():
+ return (spec, check)
diff --git a/src/bin/cfgmgr/plugins/tsig_keys.spec b/src/bin/cfgmgr/plugins/tsig_keys.spec
new file mode 100644
index 0000000..e558dd2
--- /dev/null
+++ b/src/bin/cfgmgr/plugins/tsig_keys.spec
@@ -0,0 +1,21 @@
+{
+ "module_spec": {
+ "module_name": "tsig_keys",
+ "module_description": "The TSIG keyring is stored here",
+ "config_data": [
+ {
+ "item_name": "keys",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec": {
+ "item_name": "key",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }
+ }
+ ],
+ "commands": []
+ }
+}
diff --git a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
index 37cd0f5..ea5fc8b 100644
--- a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
+++ b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
@@ -20,6 +20,7 @@
import unittest
import os
import sys
+import bind10_config
from isc.testutils.parse_args import OptsError, TestOptParser
class MyConfigManager:
@@ -110,6 +111,7 @@ class TestConfigManagerStartup(unittest.TestCase):
env_var = os.environ["B10_FROM_SOURCE"]
os.environ["B10_FROM_SOURCE"] = tmp_env_var
+ bind10_config.reload()
b = __import__("b10-cfgmgr", globals(), locals())
b.PLUGIN_PATH = [] # It's enough to test plugins in one test
b.ConfigManager = MyConfigManager
@@ -117,6 +119,7 @@ class TestConfigManagerStartup(unittest.TestCase):
if env_var != None:
os.environ["B10_FROM_SOURCE"] = env_var
+ bind10_config.reload()
sys.modules.pop("b10-cfgmgr")
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index 26878f7..fe4f7d4 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -202,7 +202,7 @@ class SendNonblock(unittest.TestCase):
try:
def killall(signum, frame):
os.kill(queue_pid, signal.SIGTERM)
- sys.exit(1)
+ os._exit(1)
signal.signal(signal.SIGALRM, killall)
msg = msgq.preparemsg({"type" : "ping"}, data)
now = time.clock()
diff --git a/src/bin/resolver/resolver.cc b/src/bin/resolver/resolver.cc
index 591e214..e43b48e 100644
--- a/src/bin/resolver/resolver.cc
+++ b/src/bin/resolver/resolver.cc
@@ -144,7 +144,7 @@ public:
void resolve(const isc::dns::QuestionPtr& question,
const isc::resolve::ResolverInterface::CallbackPtr& callback);
- void processNormalQuery(const Question& question,
+ void processNormalQuery(ConstMessagePtr query_message,
MessagePtr answer_message,
OutputBufferPtr buffer,
DNSServer* server);
@@ -468,7 +468,7 @@ Resolver::processMessage(const IOMessage& io_message,
// The RecursiveQuery object will post the "resume" event to the
// DNSServer when an answer arrives, so we don't have to do it now.
sendAnswer = false;
- impl_->processNormalQuery(*question, answer_message,
+ impl_->processNormalQuery(query_message, answer_message,
buffer, server);
}
}
@@ -486,13 +486,19 @@ ResolverImpl::resolve(const QuestionPtr& question,
}
void
-ResolverImpl::processNormalQuery(const Question& question,
+ResolverImpl::processNormalQuery(ConstMessagePtr query_message,
MessagePtr answer_message,
OutputBufferPtr buffer,
DNSServer* server)
{
- dlog("Processing normal query");
- rec_query_->resolve(question, answer_message, buffer, server);
+ if (upstream_.empty()) {
+ dlog("Processing normal query");
+ ConstQuestionPtr question = *query_message->beginQuestion();
+ rec_query_->resolve(*question, answer_message, buffer, server);
+ } else {
+ dlog("Processing forward query");
+ rec_query_->forward(query_message, answer_message, buffer, server);
+ }
}
ConstElementPtr
diff --git a/src/bin/stats/Makefile.am b/src/bin/stats/Makefile.am
index 485bc05..e4a4f92 100644
--- a/src/bin/stats/Makefile.am
+++ b/src/bin/stats/Makefile.am
@@ -5,7 +5,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-stats b10-stats-httpd
b10_statsdir = $(pkgdatadir)
-b10_stats_DATA = stats.spec stats-httpd.spec
+b10_stats_DATA = stats.spec stats-httpd.spec stats-schema.spec
b10_stats_DATA += stats-httpd-xml.tpl stats-httpd-xsd.tpl stats-httpd-xsl.tpl
CLEANFILES = b10-stats stats.pyc
@@ -13,7 +13,7 @@ CLEANFILES += b10-stats-httpd stats_httpd.pyc
man_MANS = b10-stats.8 b10-stats-httpd.8
EXTRA_DIST = $(man_MANS) b10-stats.xml b10-stats-httpd.xml
-EXTRA_DIST += stats.spec stats-httpd.spec
+EXTRA_DIST += stats.spec stats-httpd.spec stats-schema.spec
EXTRA_DIST += stats-httpd-xml.tpl stats-httpd-xsd.tpl stats-httpd-xsl.tpl
if ENABLE_MAN
@@ -28,8 +28,7 @@ endif
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-stats: stats.py
- $(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
- -e "s|.*#@@REMOVED@@$$||" stats.py >$@
+ $(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" stats.py >$@
chmod a+x $@
b10-stats-httpd: stats_httpd.py
diff --git a/src/bin/stats/b10-stats-httpd.8 b/src/bin/stats/b10-stats-httpd.8
index c066f91..ed4aafa 100644
--- a/src/bin/stats/b10-stats-httpd.8
+++ b/src/bin/stats/b10-stats-httpd.8
@@ -66,6 +66,10 @@ bindctl(1)\&. Please see the manual of
bindctl(1)
about how to configure the settings\&.
.PP
+/usr/local/share/bind10\-devel/stats\-schema\&.spec
+\(em This is a spec file for data schema of of BIND 10 statistics\&. This schema cannot be configured via
+bindctl(1)\&.
+.PP
/usr/local/share/bind10\-devel/stats\-httpd\-xml\&.tpl
\(em the template file of XML document\&.
diff --git a/src/bin/stats/b10-stats-httpd.xml b/src/bin/stats/b10-stats-httpd.xml
index 5cf3b4b..34c704f 100644
--- a/src/bin/stats/b10-stats-httpd.xml
+++ b/src/bin/stats/b10-stats-httpd.xml
@@ -112,6 +112,12 @@
of <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum> about
how to configure the settings.
</para>
+ <para><filename>/usr/local/share/bind10-devel/stats-schema.spec</filename>
+ <!--TODO: The filename should be computed from prefix-->
+ — This is a spec file for data schema of
+ of BIND 10 statistics. This schema cannot be configured
+ via <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>.
+ </para>
<para>
<filename>/usr/local/share/bind10-devel/stats-httpd-xml.tpl</filename>
<!--TODO: The filename should be computed from prefix-->
diff --git a/src/bin/stats/b10-stats.8 b/src/bin/stats/b10-stats.8
index 5714234..f69e4d3 100644
--- a/src/bin/stats/b10-stats.8
+++ b/src/bin/stats/b10-stats.8
@@ -63,11 +63,17 @@ switches to verbose mode\&. It sends verbose messages to STDOUT\&.
.PP
/usr/local/share/bind10\-devel/stats\&.spec
\(em This is a spec file for
-\fBb10\-stats\fR\&. It contains definitions of statistics items of BIND 10 and commands received via
+\fBb10\-stats\fR\&. It contains commands for
+\fBb10\-stats\fR\&. They can be invoked via
+bindctl(1)\&.
+.PP
+/usr/local/share/bind10\-devel/stats\-schema\&.spec
+\(em This is a spec file for data schema of of BIND 10 statistics\&. This schema cannot be configured via
bindctl(1)\&.
.SH "SEE ALSO"
.PP
+\fBb10-stats-httpd\fR(8),
\fBbind10\fR(8),
\fBbindctl\fR(1),
\fBb10-auth\fR(8),
diff --git a/src/bin/stats/b10-stats.xml b/src/bin/stats/b10-stats.xml
index 7ec58dd..f0c472d 100644
--- a/src/bin/stats/b10-stats.xml
+++ b/src/bin/stats/b10-stats.xml
@@ -89,10 +89,17 @@
<refsect1>
<title>FILES</title>
<para><filename>/usr/local/share/bind10-devel/stats.spec</filename>
+ <!--TODO: The filename should be computed from prefix-->
— This is a spec file for <command>b10-stats</command>. It
- contains definitions of statistics items of BIND 10 and commands
- received via
- <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>.
+ contains commands for <command>b10-stats</command>. They can be
+ invoked
+ via <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>.
+ </para>
+ <para><filename>/usr/local/share/bind10-devel/stats-schema.spec</filename>
+ <!--TODO: The filename should be computed from prefix-->
+ — This is a spec file for data schema of
+ of BIND 10 statistics. This schema cannot be configured
+ via <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>.
</para>
</refsect1>
@@ -100,6 +107,9 @@
<title>SEE ALSO</title>
<para>
<citerefentry>
+ <refentrytitle>b10-stats-httpd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
<refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
</citerefentry>,
<citerefentry>
diff --git a/src/bin/stats/stats-schema.spec.in b/src/bin/stats/stats-schema.spec.in
new file mode 100644
index 0000000..37e9c1a
--- /dev/null
+++ b/src/bin/stats/stats-schema.spec.in
@@ -0,0 +1,87 @@
+{
+ "module_spec": {
+ "module_name": "Stats",
+ "module_description": "Statistics data schema",
+ "config_data": [
+ {
+ "item_name": "report_time",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "1970-01-01T00:00:00Z",
+ "item_title": "Report time",
+ "item_description": "A date time when stats module reports",
+ "item_format": "date-time"
+ },
+ {
+ "item_name": "bind10.boot_time",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "1970-01-01T00:00:00Z",
+ "item_title": "bind10.BootTime",
+ "item_description": "A date time when bind10 process starts initially",
+ "item_format": "date-time"
+ },
+ {
+ "item_name": "stats.boot_time",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "1970-01-01T00:00:00Z",
+ "item_title": "stats.BootTime",
+ "item_description": "A date time when the stats module starts initially or when the stats module restarts",
+ "item_format": "date-time"
+ },
+ {
+ "item_name": "stats.start_time",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "1970-01-01T00:00:00Z",
+ "item_title": "stats.StartTime",
+ "item_description": "A date time when the stats module starts collecting data or resetting values last time",
+ "item_format": "date-time"
+ },
+ {
+ "item_name": "stats.last_update_time",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "1970-01-01T00:00:00Z",
+ "item_title": "stats.LastUpdateTime",
+ "item_description": "The latest date time when the stats module receives from other modules like auth server or boss process and so on",
+ "item_format": "date-time"
+ },
+ {
+ "item_name": "stats.timestamp",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 0.0,
+ "item_title": "stats.Timestamp",
+ "item_description": "A current time stamp since epoch time (1970-01-01T00:00:00Z)",
+ "item_format": "second"
+ },
+ {
+ "item_name": "stats.lname",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ "item_title": "stats.LocalName",
+ "item_description": "A localname of stats module given via CC protocol"
+ },
+ {
+ "item_name": "auth.queries.tcp",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "auth.queries.tcp",
+ "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially"
+ },
+ {
+ "item_name": "auth.queries.udp",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "auth.queries.udp",
+ "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially"
+ }
+ ],
+ "commands": []
+ }
+}
diff --git a/src/bin/stats/stats.py.in b/src/bin/stats/stats.py.in
index 821a719..969676e 100644
--- a/src/bin/stats/stats.py.in
+++ b/src/bin/stats/stats.py.in
@@ -24,12 +24,6 @@ from optparse import OptionParser, OptionValueError
from collections import defaultdict
from isc.config.ccsession import ModuleCCSession, create_answer
from isc.cc import Session, SessionError
-# Note: Following lines are removed in b10-stats #@@REMOVED@@
-if __name__ == 'stats': #@@REMOVED@@
- try: #@@REMOVED@@
- from fake_time import time, strftime, gmtime #@@REMOVED@@
- except ImportError: #@@REMOVED@@
- pass #@@REMOVED@@
# for setproctitle
import isc.util.process
@@ -39,13 +33,15 @@ isc.util.process.rename()
# from a directory relative to that, otherwise we use the ones
# installed on the system
if "B10_FROM_SOURCE" in os.environ:
- SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + os.sep + \
- "src" + os.sep + "bin" + os.sep + "stats" + os.sep + "stats.spec"
+ BASE_LOCATION = os.environ["B10_FROM_SOURCE"] + os.sep + \
+ "src" + os.sep + "bin" + os.sep + "stats"
else:
PREFIX = "@prefix@"
DATAROOTDIR = "@datarootdir@"
- SPECFILE_LOCATION = "@datadir@" + os.sep + "@PACKAGE@" + os.sep + "stats.spec"
- SPECFILE_LOCATION = SPECFILE_LOCATION.replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+ BASE_LOCATION = "@datadir@" + os.sep + "@PACKAGE@"
+ BASE_LOCATION = BASE_LOCATION.replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats.spec"
+SCHEMA_SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats-schema.spec"
class Singleton(type):
"""
@@ -184,8 +180,7 @@ class CCSessionListener(Listener):
self.session = self.subject.session = self.cc_session._session
# initialize internal data
- self.config_spec = self.cc_session.get_module_spec().get_config_spec()
- self.stats_spec = self.config_spec
+ self.stats_spec = isc.config.module_spec_from_file(SCHEMA_SPECFILE_LOCATION).get_config_spec()
self.stats_data = self.initialize_data(self.stats_spec)
# add event handler invoked via SessionSubject object
diff --git a/src/bin/stats/stats.spec.in b/src/bin/stats/stats.spec.in
index 4d42ebf..25f6b54 100644
--- a/src/bin/stats/stats.spec.in
+++ b/src/bin/stats/stats.spec.in
@@ -2,86 +2,7 @@
"module_spec": {
"module_name": "Stats",
"module_description": "Stats daemon",
- "config_data": [
- {
- "item_name": "report_time",
- "item_type": "string",
- "item_optional": false,
- "item_default": "1970-01-01T00:00:00Z",
- "item_title": "Report time",
- "item_description": "A date time when stats module reports",
- "item_format": "date-time"
- },
- {
- "item_name": "bind10.boot_time",
- "item_type": "string",
- "item_optional": false,
- "item_default": "1970-01-01T00:00:00Z",
- "item_title": "bind10.BootTime",
- "item_description": "A date time when bind10 process starts initially",
- "item_format": "date-time"
- },
- {
- "item_name": "stats.boot_time",
- "item_type": "string",
- "item_optional": false,
- "item_default": "1970-01-01T00:00:00Z",
- "item_title": "stats.BootTime",
- "item_description": "A date time when the stats module starts initially or when the stats module restarts",
- "item_format": "date-time"
- },
- {
- "item_name": "stats.start_time",
- "item_type": "string",
- "item_optional": false,
- "item_default": "1970-01-01T00:00:00Z",
- "item_title": "stats.StartTime",
- "item_description": "A date time when the stats module starts collecting data or resetting values last time",
- "item_format": "date-time"
- },
- {
- "item_name": "stats.last_update_time",
- "item_type": "string",
- "item_optional": false,
- "item_default": "1970-01-01T00:00:00Z",
- "item_title": "stats.LastUpdateTime",
- "item_description": "The latest date time when the stats module receives from other modules like auth server or boss process and so on",
- "item_format": "date-time"
- },
- {
- "item_name": "stats.timestamp",
- "item_type": "real",
- "item_optional": false,
- "item_default": 0.0,
- "item_title": "stats.Timestamp",
- "item_description": "A current time stamp since epoch time (1970-01-01T00:00:00Z)",
- "item_format": "second"
- },
- {
- "item_name": "stats.lname",
- "item_type": "string",
- "item_optional": false,
- "item_default": "",
- "item_title": "stats.LocalName",
- "item_description": "A localname of stats module given via CC protocol"
- },
- {
- "item_name": "auth.queries.tcp",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "auth.queries.tcp",
- "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially"
- },
- {
- "item_name": "auth.queries.udp",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "auth.queries.udp",
- "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially"
- }
- ],
+ "config_data": [],
"commands": [
{
"command_name": "status",
diff --git a/src/bin/stats/stats_httpd.py.in b/src/bin/stats/stats_httpd.py.in
index dd9220e..97e9c78 100644
--- a/src/bin/stats/stats_httpd.py.in
+++ b/src/bin/stats/stats_httpd.py.in
@@ -46,7 +46,7 @@ else:
BASE_LOCATION = "@datadir@" + os.sep + "@PACKAGE@"
BASE_LOCATION = BASE_LOCATION.replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd.spec"
-STATS_SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats.spec"
+SCHEMA_SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats-schema.spec"
XML_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xml.tpl"
XSD_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xsd.tpl"
XSL_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xsl.tpl"
@@ -175,7 +175,7 @@ class StatsHttpd:
SPECFILE_LOCATION, self.config_handler, self.command_handler)
self.cc_session = self.mccs._session
# read spec file of stats module and subscribe 'Stats'
- self.stats_module_spec = isc.config.module_spec_from_file(STATS_SPECFILE_LOCATION)
+ self.stats_module_spec = isc.config.module_spec_from_file(SCHEMA_SPECFILE_LOCATION)
self.stats_config_spec = self.stats_module_spec.get_config_spec()
self.stats_module_name = self.stats_module_spec.get_module_name()
diff --git a/src/bin/stats/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py
index 818b67a..eccabdc 100644
--- a/src/bin/stats/tests/b10-stats_test.py
+++ b/src/bin/stats/tests/b10-stats_test.py
@@ -23,7 +23,11 @@ import unittest
import imp
from isc.cc.session import Session, SessionError
from isc.config.ccsession import ModuleCCSession, ModuleCCSessionError
+from fake_time import time, strftime, gmtime
import stats
+stats.time = time
+stats.strftime = strftime
+stats.gmtime = gmtime
from stats import SessionSubject, CCSessionListener, get_timestamp, get_datetime
from fake_time import _TEST_TIME_SECS, _TEST_TIME_STRF
@@ -540,9 +544,14 @@ class TestStats2(unittest.TestCase):
os.environ["B10_FROM_SOURCE"] + os.sep + \
"src" + os.sep + "bin" + os.sep + "stats" + \
os.sep + "stats.spec")
+ self.assertEqual(stats.SCHEMA_SPECFILE_LOCATION,
+ os.environ["B10_FROM_SOURCE"] + os.sep + \
+ "src" + os.sep + "bin" + os.sep + "stats" + \
+ os.sep + "stats-schema.spec")
imp.reload(stats)
# change path of SPECFILE_LOCATION
stats.SPECFILE_LOCATION = TEST_SPECFILE_LOCATION
+ stats.SCHEMA_SPECFILE_LOCATION = TEST_SPECFILE_LOCATION
self.assertEqual(stats.SPECFILE_LOCATION, TEST_SPECFILE_LOCATION)
self.subject = stats.SessionSubject(session=self.session, verbose=True)
self.session = self.subject.session
diff --git a/src/bin/xfrin/b10-xfrin.8 b/src/bin/xfrin/b10-xfrin.8
index d0723b5..3ea2293 100644
--- a/src/bin/xfrin/b10-xfrin.8
+++ b/src/bin/xfrin/b10-xfrin.8
@@ -2,12 +2,12 @@
.\" Title: b10-xfrin
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: September 8, 2010
+.\" Date: May 19, 2011
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-XFRIN" "8" "September 8, 2010" "BIND10" "BIND10"
+.TH "B10\-XFRIN" "8" "May 19, 2011" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -43,7 +43,7 @@ boss process\&. When triggered it can request and receive a zone transfer and st
.ps -1
.br
.sp
-The Y1 prototype release only supports AXFR\&. IXFR is not implemented\&.
+This prototype release only supports AXFR\&. IXFR is not implemented\&.
.sp .5v
.RE
.PP
@@ -61,15 +61,34 @@ receives its configurations from
.PP
The configurable settings are:
.PP
-\fImaster_addr\fR
-The default is 127\&.0\&.0\&.1\&.
-.PP
-\fImaster_port\fR
-The default is 53\&.
-.PP
\fItransfers\-in\fR
defines the maximum number of inbound zone transfers that can run concurrently\&. The default is 10\&.
.PP
+
+\fIzones\fR
+is a list of zones known to the
+\fBb10\-xfrin\fR
+daemon\&. The list items are:
+\fIname\fR
+(the zone name),
+\fImaster_addr\fR
+(the zone master to transfer from),
+\fImaster_port\fR
+(defaults to 53), and
+\fItsig_key\fR
+(optional TSIG key to use)\&. The
+\fItsig_key\fR
+is specified using a full string colon\-delimited name:key:algorithm representation (e\&.g\&.
+\(lqfoo\&.example\&.org:EvABsfU2h7uofnmqaRCrhHunGsd=:hmac\-sha1\(rq)\&.
+.PP
+(The site\-wide
+\fImaster_addr\fR
+and
+\fImaster_port\fR
+configurations are deprecated; use the
+\fIzones\fR
+list configuration instead\&.)
+.PP
The configuration commands are:
.PP
@@ -106,7 +125,9 @@ to define the class (defaults to
\fImaster\fR
to define the IP address of the authoritative server to transfer from, and
\fIport\fR
-to define the port number on the authoritative server (defaults to 53)\&.
+to define the port number on the authoritative server (defaults to 53)\&. If the address or port is not specified, it will use the values previously defined in the
+\fIzones\fR
+configuration\&.
.PP
\fBshutdown\fR
@@ -143,5 +164,5 @@ The
daemon was implemented in March 2010 by Zhang Likun of CNNIC for the ISC BIND 10 project\&.
.SH "COPYRIGHT"
.br
-Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")
+Copyright \(co 2010-2011 Internet Systems Consortium, Inc. ("ISC")
.br
diff --git a/src/bin/xfrin/b10-xfrin.xml b/src/bin/xfrin/b10-xfrin.xml
index fdfe1ef..ea4c724 100644
--- a/src/bin/xfrin/b10-xfrin.xml
+++ b/src/bin/xfrin/b10-xfrin.xml
@@ -2,7 +2,7 @@
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[<!ENTITY mdash "—">]>
<!--
- - Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2010-2011 Internet Systems Consortium, Inc. ("ISC")
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>September 8, 2010</date>
+ <date>May 19, 2011</date>
</refentryinfo>
<refmeta>
@@ -36,7 +36,7 @@
<docinfo>
<copyright>
- <year>2010</year>
+ <year>2010-2011</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
@@ -62,6 +62,12 @@
the zone in a BIND 10 zone data store.
</para>
+<!-- TODO:
+xfrin only does the transfer to make it as simple as possible.
+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>
@@ -86,20 +92,33 @@
The configurable settings are:
</para>
- <para><varname>master_addr</varname>
-<!-- TODO: how can there be a single setting for this? -->
- The default is 127.0.0.1.
+ <para><varname>transfers-in</varname>
+ defines the maximum number of inbound zone transfers
+ that can run concurrently. The default is 10.
</para>
- <para><varname>master_port</varname>
-<!-- TODO: what if custom is needed per zone? -->
- The default is 53.
+<!-- TODO: is name okay for master_addr or just IP? -->
+ <para>
+ <varname>zones</varname> is a list of zones known to the
+ <command>b10-xfrin</command> daemon.
+ The list items are:
+ <varname>name</varname> (the zone name),
+ <varname>master_addr</varname> (the zone master to transfer from),
+ <varname>master_port</varname> (defaults to 53), 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.
+ <quote>foo.example.org:EvABsfU2h7uofnmqaRCrhHunGsd=:hmac-sha1</quote>).
</para>
+<!-- TODO: document this better -->
+<!-- TODO: the tsig_key format may change -->
- <para><varname>transfers-in</varname>
- defines the maximum number of inbound zone transfers
- that can run concurrently. The default is 10.
+ <para>
+ (The site-wide <varname>master_addr</varname> and
+ <varname>master_port</varname> configurations are deprecated;
+ use the <varname>zones</varname> list configuration instead.)
</para>
+<!-- NOTE: also tsig_key but not mentioning since so short lived. -->
<!-- TODO: formating -->
<para>
@@ -148,6 +167,9 @@
the authoritative server to transfer from,
and <varname>port</varname> to define the port number on the
authoritative server (defaults to 53).
+ If the address or port is not specified, it will use the
+ values previously defined in the <varname>zones</varname>
+ configuration.
</para>
<!-- TODO: later hostname for master? -->
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index 04d04a6..f3e2ee4 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 Internet Systems Consortium.
+# Copyright (C) 2009-2011 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -15,13 +15,16 @@
import unittest
import socket
+from isc.testutils.tsigctx_mock import MockTSIGContext
from xfrin import *
#
# Commonly used (mostly constant) test parameters
#
-TEST_ZONE_NAME = "example.com"
+TEST_ZONE_NAME_STR = "example.com."
+TEST_ZONE_NAME = Name(TEST_ZONE_NAME_STR)
TEST_RRCLASS = RRClass.IN()
+TEST_RRCLASS_STR = 'IN'
TEST_DB_FILE = 'db_file'
TEST_MASTER_IPV4_ADDRESS = '127.0.0.1'
TEST_MASTER_IPV4_ADDRINFO = (socket.AF_INET, socket.SOCK_STREAM,
@@ -35,15 +38,17 @@ TEST_MASTER_IPV6_ADDRINFO = (socket.AF_INET6, socket.SOCK_STREAM,
# If some other process uses this port test will fail.
TEST_MASTER_PORT = '53535'
+TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
+
soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
'master.example.com. admin.example.com ' +
'1234 3600 1800 2419200 7200')
-soa_rrset = RRset(Name(TEST_ZONE_NAME), TEST_RRCLASS, RRType.SOA(),
+soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(),
RRTTL(3600))
soa_rrset.add_rdata(soa_rdata)
-example_axfr_question = Question(Name(TEST_ZONE_NAME), TEST_RRCLASS,
+example_axfr_question = Question(TEST_ZONE_NAME, TEST_RRCLASS,
RRType.AXFR())
-example_soa_question = Question(Name(TEST_ZONE_NAME), TEST_RRCLASS,
+example_soa_question = Question(TEST_ZONE_NAME, TEST_RRCLASS,
RRType.SOA())
default_questions = [example_axfr_question]
default_answers = [soa_rrset]
@@ -51,6 +56,13 @@ default_answers = [soa_rrset]
class XfrinTestException(Exception):
pass
+class MockCC():
+ def get_default_value(self, identifier):
+ if identifier == "zones/master_port":
+ return TEST_MASTER_PORT
+ if identifier == "zones/class":
+ return TEST_RRCLASS_STR
+
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
@@ -60,6 +72,8 @@ class MockXfrin(Xfrin):
check_command_hook = None
def _cc_setup(self):
+ self._tsig_key = None
+ self._module_cc = MockCC()
pass
def _get_db_file(self):
@@ -70,6 +84,16 @@ class MockXfrin(Xfrin):
if MockXfrin.check_command_hook:
MockXfrin.check_command_hook()
+ def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo,
+ tsig_key, 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,
+ master_addrinfo, tsig_key,
+ check_soa)
+
class MockXfrinConnection(XfrinConnection):
def __init__(self, sock_map, zone_name, rrclass, db_file, shutdown_event,
master_addr):
@@ -121,10 +145,11 @@ class MockXfrinConnection(XfrinConnection):
self.response_generator()
return len(data)
- def create_response_data(self, response = True, bad_qid = False,
- rcode = Rcode.NOERROR(),
- questions = default_questions,
- answers = default_answers):
+ def create_response_data(self, response=True, bad_qid=False,
+ rcode=Rcode.NOERROR(),
+ questions=default_questions,
+ answers=default_answers,
+ tsig_ctx=None):
resp = Message(Message.RENDER)
qid = self.qid
if bad_qid:
@@ -138,7 +163,10 @@ class MockXfrinConnection(XfrinConnection):
[resp.add_rrset(Message.SECTION_ANSWER, a) for a in answers]
renderer = MessageRenderer()
- resp.to_wire(renderer)
+ if tsig_ctx is not None:
+ resp.to_wire(renderer, tsig_ctx)
+ else:
+ resp.to_wire(renderer)
reply_data = struct.pack('H', socket.htons(renderer.get_length()))
reply_data += renderer.get_data()
@@ -153,20 +181,32 @@ class TestXfrinConnection(unittest.TestCase):
TEST_RRCLASS, TEST_DB_FILE,
threading.Event(),
TEST_MASTER_IPV4_ADDRINFO)
- self.axfr_after_soa = False
self.soa_response_params = {
'questions': [example_soa_question],
'bad_qid': False,
'response': True,
'rcode': Rcode.NOERROR(),
+ 'tsig': False,
'axfr_after_soa': self._create_normal_response_data
}
+ self.axfr_response_params = {
+ 'tsig_1st': None,
+ 'tsig_2nd': None
+ }
def tearDown(self):
self.conn.close()
if os.path.exists(TEST_DB_FILE):
os.remove(TEST_DB_FILE)
+ 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
+ # one)
+ mock_ctx = MockTSIGContext(key)
+ mock_ctx.error = error
+ return mock_ctx
+
def test_close(self):
# we shouldn't be using the global asyncore map.
self.assertEqual(len(asyncore.socket_map), 0)
@@ -196,10 +236,53 @@ class TestXfrinConnection(unittest.TestCase):
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 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
+ # tests regarding the TSIG protocol are done in pydnspp.
+ msg = Message(Message.PARSE)
+ 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())
+ self.assertTrue(message_has_tsig(self.conn.query_data[2:]))
+
+ # axfr request with tsig
+ self.conn._send_query(RRType.AXFR())
+ self.assertTrue(message_has_tsig(self.conn.query_data[2:]))
+
def test_response_with_invalid_msg(self):
self.conn.reply_data = b'aaaxxxx'
self.assertRaises(XfrinTestException, self._handle_xfrin_response)
+ 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)
+
def test_response_without_end_soa(self):
self.conn._send_query(RRType.AXFR())
self.conn.reply_data = self.conn.create_response_data()
@@ -264,6 +347,54 @@ class TestXfrinConnection(unittest.TestCase):
self.conn.response_generator = self._create_soa_response_data
self.assertRaises(XfrinException, self.conn._check_soa_serial)
+ def test_soacheck_with_tsig(self):
+ # Use a mock tsig context emulating a validly signed response
+ self.conn._tsig_key = TSIG_KEY
+ self.conn._tsig_ctx_creator = \
+ lambda key: self.__create_mock_tsig(key, TSIGError.NOERROR)
+ self.conn.response_generator = self._create_soa_response_data
+ self.assertEqual(self.conn._check_soa_serial(), XFRIN_OK)
+ self.assertEqual(self.conn._tsig_ctx.get_error(), TSIGError.NOERROR)
+
+ def test_soacheck_with_tsig_notauth(self):
+ # emulate a valid error response
+ self.conn._tsig_key = TSIG_KEY
+ self.conn._tsig_ctx_creator = \
+ lambda key: self.__create_mock_tsig(key, TSIGError.BAD_SIG)
+ self.soa_response_params['rcode'] = Rcode.NOTAUTH()
+ self.conn.response_generator = self._create_soa_response_data
+
+ self.assertRaises(XfrinException, self.conn._check_soa_serial)
+
+ def test_soacheck_with_tsig_noerror_badsig(self):
+ self.conn._tsig_key = TSIG_KEY
+ self.conn._tsig_ctx_creator = \
+ lambda key: self.__create_mock_tsig(key, TSIGError.BAD_SIG)
+
+ # emulate a normal response bad verification failure due to BADSIG.
+ # According RFC2845, in this case we should ignore it and keep
+ # waiting for a valid response until a timeout. But we immediately
+ # treat this as a final failure (just as BIND 9 does).
+ self.conn.response_generator = self._create_soa_response_data
+
+ self.assertRaises(XfrinException, self.conn._check_soa_serial)
+
+ def test_soacheck_with_tsig_unsigned_response(self):
+ # we can use a real TSIGContext for this. the response doesn't
+ # contain a TSIG while we sent a signed query. RFC2845 states
+ # we should wait for a valid response in this case, but we treat
+ # it as a fatal transaction failure, too.
+ self.conn._tsig_key = TSIG_KEY
+ self.conn.response_generator = self._create_soa_response_data
+ self.assertRaises(XfrinException, self.conn._check_soa_serial)
+
+ def test_soacheck_with_unexpected_tsig_response(self):
+ # we reject unexpected TSIG in responses (following BIND 9's
+ # behavior)
+ self.soa_response_params['tsig'] = True
+ self.conn.response_generator = self._create_soa_response_data
+ self.assertRaises(XfrinException, self.conn._check_soa_serial)
+
def test_response_shutdown(self):
self.conn.response_generator = self._create_normal_response_data
self.conn._shutdown_event.set()
@@ -297,6 +428,88 @@ class TestXfrinConnection(unittest.TestCase):
self.conn.response_generator = self._create_normal_response_data
self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
+ def test_do_xfrin_with_tsig(self):
+ # use TSIG with a mock context. we fake all verify results to
+ # emulate successful verification.
+ self.conn._tsig_key = TSIG_KEY
+ self.conn._tsig_ctx_creator = \
+ 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)
+
+ def test_do_xfrin_with_tsig_fail(self):
+ # TSIG verify will fail for the first message. xfrin should fail
+ # immediately.
+ self.conn._tsig_key = TSIG_KEY
+ self.conn._tsig_ctx_creator = \
+ lambda key: self.__create_mock_tsig(key, TSIGError.BAD_SIG)
+ self.conn.response_generator = self._create_normal_response_data
+ self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+ self.assertEqual(1, self.conn._tsig_ctx.verify_called)
+
+ def test_do_xfrin_with_tsig_fail_for_second_message(self):
+ # Similar to the previous test, but first verify succeeds. There
+ # should be a second verify attempt, which will fail, which should
+ # make xfrin fail.
+ def fake_tsig_error(ctx):
+ if self.conn._tsig_ctx.verify_called == 1:
+ return TSIGError.NOERROR
+ return TSIGError.BAD_SIG
+ self.conn._tsig_key = TSIG_KEY
+ self.conn._tsig_ctx_creator = \
+ lambda key: self.__create_mock_tsig(key, fake_tsig_error)
+ self.conn.response_generator = self._create_normal_response_data
+ self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+ self.assertEqual(2, self.conn._tsig_ctx.verify_called)
+
+ def test_do_xfrin_with_missing_tsig(self):
+ # XFR request sent with TSIG, but the response doesn't have TSIG.
+ # xfr should fail.
+ self.conn._tsig_key = TSIG_KEY
+ self.conn._tsig_ctx_creator = \
+ lambda key: self.__create_mock_tsig(key, None)
+ self.conn._tsig_ctx = MockTSIGContext(TSIG_KEY)
+ self.conn.response_generator = self._create_normal_response_data
+ self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+ self.assertEqual(1, self.conn._tsig_ctx.verify_called)
+
+ def test_do_xfrin_with_missing_tsig_for_second_message(self):
+ # Similar to the previous test, but firt one contains TSIG and verify
+ # succeeds (due to fake). The second message lacks TSIG.
+ #
+ # Note: this test case is actually not that trivial: Skipping
+ # intermediate TSIG is allowed. In this case, however, the second
+ # message is the last one, which must contain TSIG anyway, so the
+ # expected result is correct. If/when we support skipping
+ # intermediate TSIGs, we'll need additional test cases.
+ def fake_tsig_error(ctx):
+ if self.conn._tsig_ctx.verify_called == 1:
+ return TSIGError.NOERROR
+ return TSIGError.FORMERR
+ self.conn._tsig_key = TSIG_KEY
+ self.conn._tsig_ctx_creator = \
+ lambda key: self.__create_mock_tsig(key, fake_tsig_error)
+ self.conn.response_generator = self._create_normal_response_data
+ self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+ self.assertEqual(2, self.conn._tsig_ctx.verify_called)
+
+ def test_do_xfrin_with_unexpected_tsig(self):
+ # XFR request wasn't signed, but response includes TSIG. Like BIND 9,
+ # we reject that.
+ self.axfr_response_params['tsig_1st'] = TSIGContext(TSIG_KEY)
+ self.conn.response_generator = self._create_normal_response_data
+ self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+
+ def test_do_xfrin_with_unexpected_tsig_for_second_message(self):
+ # similar to the previous test, but the first message is normal.
+ # the second one contains an unexpected TSIG. should be rejected.
+ self.axfr_response_params['tsig_2nd'] = TSIGContext(TSIG_KEY)
+ self.conn.response_generator = self._create_normal_response_data
+ self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+
def test_do_xfrin_empty_response(self):
# skipping the creation of response data, so the transfer will fail.
self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
@@ -315,6 +528,23 @@ class TestXfrinConnection(unittest.TestCase):
self.conn.response_generator = self._create_soa_response_data
self.assertEqual(self.conn.do_xfrin(True), XFRIN_OK)
+ def test_do_soacheck_and_xfrin_with_tsig(self):
+ # We are going to have a SOA query/response transaction, followed by
+ # AXFR, all TSIG signed. xfrin should use a new TSIG context for
+ # AXFR. We are not interested in whether verify works correctly in
+ # this test, so we simply fake the results (they need to succeed for
+ # this test)
+ self.conn._tsig_key = TSIG_KEY
+ self.conn._tsig_ctx_creator = \
+ lambda key: self.__create_mock_tsig(key, TSIGError.NOERROR)
+ self.soa_response_params['tsig'] = True
+ self.conn.response_generator = self._create_soa_response_data
+ self.assertEqual(self.conn.do_xfrin(True), XFRIN_OK)
+ # We should've got 3 response messages: 1 SOA and two AXFR, but
+ # the context should be replaced for AXFR, so verify() should be
+ # called only twice for the latest context.
+ self.assertEqual(2, self.conn._tsig_ctx.verify_called)
+
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
@@ -342,21 +572,39 @@ class TestXfrinConnection(unittest.TestCase):
# 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.
- self.conn.reply_data = self.conn.create_response_data()
- self.conn.reply_data += self.conn.create_response_data()
+ 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)
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 (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)
+
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'])
+ 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']
+ 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
@@ -399,21 +647,28 @@ class TestXfrinRecorder(unittest.TestCase):
class TestXfrin(unittest.TestCase):
def setUp(self):
+ # redirect output
+ self.stderr_backup = sys.stderr
+ sys.stderr = open(os.devnull, 'w')
self.xfr = MockXfrin()
self.args = {}
- self.args['zone_name'] = TEST_ZONE_NAME
+ self.args['zone_name'] = TEST_ZONE_NAME_STR
+ self.args['class'] = TEST_RRCLASS_STR
self.args['port'] = TEST_MASTER_PORT
self.args['master'] = TEST_MASTER_IPV4_ADDRESS
self.args['db_file'] = TEST_DB_FILE
+ self.args['tsig_key'] = ''
def tearDown(self):
self.xfr.shutdown()
+ sys.stderr= self.stderr_backup
def _do_parse_zone_name_class(self):
return self.xfr._parse_zone_name_and_class(self.args)
def _do_parse_master_port(self):
- return self.xfr._parse_master_and_port(self.args)
+ name, rrclass = self._do_parse_zone_name_class()
+ return self.xfr._parse_master_and_port(self.args, name, rrclass)
def test_parse_cmd_params(self):
name, rrclass = self._do_parse_zone_name_class()
@@ -441,7 +696,7 @@ class TestXfrin(unittest.TestCase):
def test_parse_cmd_params_bogusclass(self):
self.args['zone_class'] = 'XXX'
- self.assertRaises(XfrinException, self._do_parse_zone_name_class)
+ self.assertRaises(XfrinZoneInfoException, self._do_parse_zone_name_class)
def test_parse_cmd_params_nozone(self):
# zone name is mandatory.
@@ -451,8 +706,7 @@ class TestXfrin(unittest.TestCase):
def test_parse_cmd_params_nomaster(self):
# master address is mandatory.
del self.args['master']
- master_addrinfo = self._do_parse_master_port()
- self.assertEqual(master_addrinfo[2][0], DEFAULT_MASTER)
+ self.assertRaises(XfrinException, self._do_parse_master_port)
def test_parse_cmd_params_bad_ip4(self):
self.args['master'] = '3.3.3.3.3'
@@ -482,6 +736,77 @@ class TestXfrin(unittest.TestCase):
def test_command_handler_retransfer(self):
self.assertEqual(self.xfr.command_handler("retransfer",
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)
+
+ def test_command_handler_retransfer_short_command1(self):
+ # try it when only specifying the zone name (of unknown zone)
+ # this should fail because master address is not specified.
+ short_args = {}
+ short_args['zone_name'] = TEST_ZONE_NAME_STR
+ self.assertEqual(self.xfr.command_handler("retransfer",
+ short_args)['result'][0], 1)
+
+ def test_command_handler_retransfer_short_command2(self):
+ # try it when only specifying the zone name (of known zone)
+ short_args = {}
+ short_args['zone_name'] = TEST_ZONE_NAME_STR
+
+ zones = { 'zones': [
+ { 'name': TEST_ZONE_NAME_STR,
+ 'master_addr': TEST_MASTER_IPV4_ADDRESS,
+ 'master_port': TEST_MASTER_PORT
+ }
+ ]}
+ self.xfr.config_handler(zones)
+ self.assertEqual(self.xfr.command_handler("retransfer",
+ short_args)['result'][0], 0)
+ self.assertEqual(TEST_MASTER_IPV4_ADDRESS,
+ self.xfr.xfrin_started_master_addr)
+ self.assertEqual(int(TEST_MASTER_PORT),
+ self.xfr.xfrin_started_master_port)
+
+ def test_command_handler_retransfer_short_command3(self):
+ # try it when only specifying the zone name (of known zone)
+ short_args = {}
+ # test it without the trailing root dot
+ short_args['zone_name'] = TEST_ZONE_NAME_STR[:-1]
+
+ zones = { 'zones': [
+ { 'name': TEST_ZONE_NAME_STR,
+ 'master_addr': TEST_MASTER_IPV4_ADDRESS,
+ 'master_port': TEST_MASTER_PORT
+ }
+ ]}
+ self.xfr.config_handler(zones)
+ self.assertEqual(self.xfr.command_handler("retransfer",
+ short_args)['result'][0], 0)
+ self.assertEqual(TEST_MASTER_IPV4_ADDRESS,
+ self.xfr.xfrin_started_master_addr)
+ self.assertEqual(int(TEST_MASTER_PORT),
+ self.xfr.xfrin_started_master_port)
+
+ def test_command_handler_retransfer_short_command4(self):
+ # try it when only specifying the zone name (of known zone, with
+ # different case)
+ short_args = {}
+
+ # swap the case of the zone name in our command
+ short_args['zone_name'] = TEST_ZONE_NAME_STR.swapcase()
+
+ zones = { 'zones': [
+ { 'name': TEST_ZONE_NAME_STR,
+ 'master_addr': TEST_MASTER_IPV4_ADDRESS,
+ 'master_port': TEST_MASTER_PORT
+ }
+ ]}
+ self.xfr.config_handler(zones)
+ self.assertEqual(self.xfr.command_handler("retransfer",
+ short_args)['result'][0], 0)
+ self.assertEqual(TEST_MASTER_IPV4_ADDRESS,
+ self.xfr.xfrin_started_master_addr)
+ self.assertEqual(int(TEST_MASTER_PORT),
+ self.xfr.xfrin_started_master_port)
def test_command_handler_retransfer_badcommand(self):
self.args['master'] = 'invalid'
@@ -489,13 +814,15 @@ class TestXfrin(unittest.TestCase):
self.args)['result'][0], 1)
def test_command_handler_retransfer_quota(self):
+ self.args['master'] = TEST_MASTER_IPV4_ADDRESS
+
for i in range(self.xfr._max_transfers_in - 1):
- self.xfr.recorder.increment(str(i) + TEST_ZONE_NAME)
+ self.xfr.recorder.increment(Name(str(i) + TEST_ZONE_NAME_STR))
# there can be one more outstanding transfer.
self.assertEqual(self.xfr.command_handler("retransfer",
self.args)['result'][0], 0)
# make sure the # xfrs would excceed the quota
- self.xfr.recorder.increment(str(self.xfr._max_transfers_in) + TEST_ZONE_NAME)
+ self.xfr.recorder.increment(Name(str(self.xfr._max_transfers_in) + TEST_ZONE_NAME_STR))
# this one should fail
self.assertEqual(self.xfr.command_handler("retransfer",
self.args)['result'][0], 1)
@@ -519,14 +846,43 @@ class TestXfrin(unittest.TestCase):
self.args['master'] = TEST_MASTER_IPV6_ADDRESS
self.assertEqual(self.xfr.command_handler("refresh",
self.args)['result'][0], 0)
+ self.assertEqual(TEST_MASTER_IPV6_ADDRESS,
+ self.xfr.xfrin_started_master_addr)
+ self.assertEqual(int(TEST_MASTER_PORT),
+ self.xfr.xfrin_started_master_port)
def test_command_handler_notify(self):
# at this level, refresh is no different than retransfer.
self.args['master'] = TEST_MASTER_IPV6_ADDRESS
- # ...but right now we disable the feature due to security concerns.
+ # ...but the zone is unknown so this would return an error
+ self.assertEqual(self.xfr.command_handler("notify",
+ self.args)['result'][0], 1)
+
+ def test_command_handler_notify_known_zone(self):
+ # try it with a known zone
+ self.args['master'] = TEST_MASTER_IPV6_ADDRESS
+
+ # but use a different address in the actual command
+ zones = { 'zones': [
+ { 'name': TEST_ZONE_NAME_STR,
+ 'master_addr': TEST_MASTER_IPV4_ADDRESS,
+ 'master_port': TEST_MASTER_PORT
+ }
+ ]}
+ self.xfr.config_handler(zones)
self.assertEqual(self.xfr.command_handler("notify",
self.args)['result'][0], 0)
+ # and see if we used the address from the command, and not from
+ # the config
+ # This is actually NOT the address given in the command, which
+ # would at this point not make sense, see the TODO in
+ # xfrin.py.in Xfrin.command_handler())
+ self.assertEqual(TEST_MASTER_IPV4_ADDRESS,
+ self.xfr.xfrin_started_master_addr)
+ self.assertEqual(int(TEST_MASTER_PORT),
+ self.xfr.xfrin_started_master_port)
+
def test_command_handler_unknown(self):
self.assertEqual(self.xfr.command_handler("xxx", None)['result'][0], 1)
@@ -535,20 +891,145 @@ class TestXfrin(unittest.TestCase):
self.assertEqual(self.xfr.config_handler({'transfers_in': 3})['result'][0], 0)
self.assertEqual(self.xfr._max_transfers_in, 3)
- def test_command_handler_masters(self):
- master_info = {'master_addr': '1.1.1.1', 'master_port':53}
- self.assertEqual(self.xfr.config_handler(master_info)['result'][0], 0)
-
- master_info = {'master_addr': '1111.1.1.1', 'master_port':53 }
- self.assertEqual(self.xfr.config_handler(master_info)['result'][0], 1)
-
- master_info = {'master_addr': '2.2.2.2', 'master_port':530000 }
- self.assertEqual(self.xfr.config_handler(master_info)['result'][0], 1)
-
- master_info = {'master_addr': '2.2.2.2', 'master_port':53 }
- self.xfr.config_handler(master_info)
- self.assertEqual(self.xfr._master_addr, '2.2.2.2')
- self.assertEqual(self.xfr._master_port, 53)
+ def _check_zones_config(self, config_given):
+ if 'transfers_in' in config_given:
+ self.assertEqual(config_given['transfers_in'],
+ self.xfr._max_transfers_in)
+ for zone_config in config_given['zones']:
+ zone_name = zone_config['name']
+ zone_info = self.xfr._get_zone_info(Name(zone_name), RRClass.IN())
+ self.assertEqual(str(zone_info.master_addr), zone_config['master_addr'])
+ self.assertEqual(zone_info.master_port, zone_config['master_port'])
+ if 'tsig_key' in zone_config:
+ self.assertEqual(zone_info.tsig_key.to_text(), TSIGKey(zone_config['tsig_key']).to_text())
+ else:
+ self.assertIsNone(zone_info.tsig_key)
+
+ def test_command_handler_zones(self):
+ config1 = { 'transfers_in': 3,
+ 'zones': [
+ { 'name': 'test.example.',
+ 'master_addr': '192.0.2.1',
+ 'master_port': 53
+ }
+ ]}
+ self.assertEqual(self.xfr.config_handler(config1)['result'][0], 0)
+ self._check_zones_config(config1)
+
+ config2 = { 'transfers_in': 4,
+ 'zones': [
+ { 'name': 'test.example.',
+ 'master_addr': '192.0.2.2',
+ 'master_port': 53,
+ 'tsig_key': "example.com:SFuWd/q99SzF8Yzd1QbB9g=="
+ }
+ ]}
+ self.assertEqual(self.xfr.config_handler(config2)['result'][0], 0)
+ self._check_zones_config(config2)
+
+ # test that configuring the zone multiple times fails
+ zones = { 'transfers_in': 5,
+ 'zones': [
+ { 'name': 'test.example.',
+ 'master_addr': '192.0.2.1',
+ 'master_port': 53
+ },
+ { 'name': 'test.example.',
+ 'master_addr': '192.0.2.2',
+ 'master_port': 53
+ }
+ ]}
+ self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+ # since this has failed, we should still have the previous config
+ self._check_zones_config(config2)
+
+ zones = { 'zones': [
+ { 'name': 'test.example.',
+ 'master_addr': '192.0.2.3',
+ 'master_port': 53,
+ 'class': 'BADCLASS'
+ }
+ ]}
+ self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+ self._check_zones_config(config2)
+
+ zones = { 'zones': [
+ { 'master_addr': '192.0.2.4',
+ 'master_port': 53
+ }
+ ]}
+ self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+ # since this has failed, we should still have the previous config
+ self._check_zones_config(config2)
+
+ zones = { 'zones': [
+ { 'name': 'bad..zone.',
+ 'master_addr': '192.0.2.5',
+ 'master_port': 53
+ }
+ ]}
+ self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+ # since this has failed, we should still have the previous config
+ self._check_zones_config(config2)
+
+ zones = { 'zones': [
+ { 'name': '',
+ 'master_addr': '192.0.2.6',
+ 'master_port': 53
+ }
+ ]}
+ self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+ # since this has failed, we should still have the previous config
+ self._check_zones_config(config2)
+
+ zones = { 'zones': [
+ { 'name': 'test.example',
+ 'master_addr': 'badaddress',
+ 'master_port': 53
+ }
+ ]}
+ self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+ # since this has failed, we should still have the previous config
+ self._check_zones_config(config2)
+
+ zones = { 'zones': [
+ { 'name': 'test.example',
+ 'master_addr': '192.0.2.7',
+ 'master_port': 'bad_port'
+ }
+ ]}
+ self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+ # since this has failed, we should still have the previous config
+ self._check_zones_config(config2)
+
+ zones = { 'zones': [
+ { 'name': 'test.example',
+ 'master_addr': '192.0.2.7',
+ 'master_port': 53,
+ # using a bad TSIG key spec
+ 'tsig_key': "bad..example.com:SFuWd/q99SzF8Yzd1QbB9g=="
+ }
+ ]}
+ self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+ # since this has failed, we should still have the previous config
+ self._check_zones_config(config2)
+
+ # let's also add a zone that is correct too, and make sure
+ # that the new config is not partially taken
+ zones = { 'zones': [
+ { 'name': 'test.example.',
+ 'master_addr': '192.0.2.8',
+ 'master_port': 53
+ },
+ { 'name': 'test2.example.',
+ 'master_addr': '192.0.2.9',
+ 'master_port': 53,
+ 'tsig_key': 'badkey'
+ }
+ ]}
+ self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+ # since this has failed, we should still have the previous config
+ self._check_zones_config(config2)
def raise_interrupt():
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index 10a866e..7758a37 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -1,6 +1,6 @@
#!@PYTHON@
-# Copyright (C) 2010 Internet Systems Consortium.
+# Copyright (C) 2009-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
@@ -56,26 +56,67 @@ XFROUT_MODULE_NAME = 'Xfrout'
ZONE_MANAGER_MODULE_NAME = 'Zonemgr'
REFRESH_FROM_ZONEMGR = 'refresh_from_zonemgr'
ZONE_XFRIN_FAILED = 'zone_xfrin_failed'
+
+# 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
+# arguments as we do for config options)
+DEFAULT_MASTER_PORT = 53
+DEFAULT_ZONE_CLASS = RRClass.IN()
+
__version__ = 'BIND10'
# define xfrin rcode
XFRIN_OK = 0
XFRIN_FAIL = 1
-DEFAULT_MASTER_PORT = '53'
-DEFAULT_MASTER = '127.0.0.1'
-
def log_error(msg):
sys.stderr.write("[b10-xfrin] %s\n" % str(msg))
-class XfrinException(Exception):
+class XfrinException(Exception):
+ 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
+ argument or has bad arguments, for instance when the zone's master
+ address is not a valid IP address, when the zone does not
+ have a name, or when multiple settings are given for the same
+ zone."""
pass
+def _check_zone_name(zone_name_str):
+ """Checks if the given zone name is a valid domain name, and returns
+ it as a Name object. Raises an XfrinException if it is not."""
+ try:
+ # In the _zones dict, part of the key is the zone name,
+ # but due to a limitation in the Name class, we
+ # cannot directly use it as a dict key, and we use to_text()
+ #
+ # Downcase the name here for that reason.
+ return Name(zone_name_str, True)
+ except (EmptyLabel, TooLongLabel, BadLabelType, BadEscape,
+ TooLongName, IncompleteName) as ne:
+ raise XfrinZoneInfoException("bad zone name: " + zone_name_str + " (" + str(ne) + ")")
+
+def _check_zone_class(zone_class_str):
+ """If the given argument is a string: checks if the given class is
+ a valid one, and returns an RRClass object if so.
+ Raises XfrinZoneInfoException if not.
+ If it is None, this function returns the default RRClass.IN()"""
+ if zone_class_str is None:
+ return DEFAULT_ZONE_CLASS
+ try:
+ return RRClass(zone_class_str)
+ except InvalidRRClass as irce:
+ raise XfrinZoneInfoException("bad zone class: " + zone_class_str + " (" + str(irce) + ")")
+
class XfrinConnection(asyncore.dispatcher):
- '''Do xfrin in this class. '''
+ '''Do xfrin in this class. '''
def __init__(self,
sock_map, zone_name, rrclass, db_file, shutdown_event,
- master_addrinfo, verbose = False, idle_timeout = 60):
+ 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
@@ -93,6 +134,14 @@ class XfrinConnection(asyncore.dispatcher):
self._shutdown_event = shutdown_event
self._verbose = verbose
self._master_address = master_addrinfo[2]
+ self._tsig_key = tsig_key
+ self._tsig_ctx = None
+ # tsig_ctx_creator is introduced to allow tests to use a mock class for
+ # easier tests (in normal case we always use the default)
+ self._tsig_ctx_creator = self.__create_tsig_ctx
+
+ def __create_tsig_ctx(self, key):
+ return TSIGContext(key)
def connect_to_master(self):
'''Connect to master in TCP.'''
@@ -130,9 +179,15 @@ class XfrinConnection(asyncore.dispatcher):
msg = self._create_query(query_type)
render = MessageRenderer()
- msg.to_wire(render)
- header_len = struct.pack('H', socket.htons(render.get_length()))
+ # XXX Currently, python wrapper doesn't accept 'None' parameter in this case,
+ # we should remove the if statement and use a universal interface later.
+ if self._tsig_key is not None:
+ self._tsig_ctx = self._tsig_ctx_creator(self._tsig_key)
+ msg.to_wire(render, self._tsig_ctx)
+ else:
+ msg.to_wire(render)
+ header_len = struct.pack('H', socket.htons(render.get_length()))
self._send_data(header_len)
self._send_data(render.get_data())
@@ -142,7 +197,7 @@ class XfrinConnection(asyncore.dispatcher):
_get_request_response so that we can test the rest of the code without
involving actual communication with a remote server.'''
asyncore.loop(self._idle_timeout, map=self._sock_map, count=1)
-
+
def _get_request_response(self, size):
recv_size = 0
data = b''
@@ -158,6 +213,22 @@ class XfrinConnection(asyncore.dispatcher):
return data
+ def _check_response_tsig(self, msg, response_data):
+ tsig_record = msg.get_tsig_record()
+ if self._tsig_ctx is not None:
+ tsig_error = self._tsig_ctx.verify(tsig_record, response_data)
+ if tsig_error != TSIGError.NOERROR:
+ raise XfrinException('TSIG verify fail: %s' % str(tsig_error))
+ elif tsig_record is not None:
+ # If the response includes a TSIG while we didn't sign the query,
+ # we treat it as an error. RFC doesn't say anything about this
+ # case, but it clearly states the server must not sign a response
+ # to an unsigned request. Although we could be flexible, no sane
+ # implementation would return such a response, and since this is
+ # part of security mechanism, it's probably better to be more
+ # strict.
+ raise XfrinException('Unexpected TSIG in response')
+
def _check_soa_serial(self):
''' Compare the soa serial, if soa serial in master is less than
the soa serial in local, Finish xfrin.
@@ -165,7 +236,7 @@ class XfrinConnection(asyncore.dispatcher):
True: soa serial in master is bigger
'''
- self._send_query(RRType("SOA"))
+ self._send_query(RRType.SOA())
data_len = self._get_request_response(2)
msg_len = socket.htons(struct.unpack('H', data_len)[0])
soa_response = self._get_request_response(msg_len)
@@ -176,7 +247,10 @@ class XfrinConnection(asyncore.dispatcher):
# strict we should be (see the comment in _check_response_header())
self._check_response_header(msg)
- # TODO, need select soa record from data source then compare the two
+ # TSIG related checks, including an unexpected signed response
+ self._check_response_tsig(msg, soa_response)
+
+ # TODO, need select soa record from data source then compare the two
# serial, current just return OK, since this function hasn't been used
# now.
return XFRIN_OK
@@ -193,8 +267,7 @@ class XfrinConnection(asyncore.dispatcher):
logstr = 'transfer of \'%s\': AXFR ' % self._zone_name
if ret == XFRIN_OK:
self.log_msg(logstr + 'started')
- # TODO: .AXFR() RRType.AXFR()
- self._send_query(RRType(252))
+ self._send_query(RRType.AXFR())
isc.datasrc.sqlite3_ds.load(self._db_file, self._zone_name,
self._handle_xfrin_response)
@@ -265,7 +338,7 @@ class XfrinConnection(asyncore.dispatcher):
for rdata in rrset.get_rdata():
# Count the soa record count
- if rrset.get_type() == RRType("SOA"):
+ if rrset.get_type() == RRType.SOA():
self._soa_rr_count += 1
# XXX: the current DNS message parser can't preserve the
@@ -290,14 +363,17 @@ class XfrinConnection(asyncore.dispatcher):
msg = Message(Message.PARSE)
msg.from_wire(recvdata)
self._check_response_status(msg)
-
+
+ # TSIG related checks, including an unexpected signed response
+ self._check_response_tsig(msg, recvdata)
+
answer_section = msg.get_section(Message.SECTION_ANSWER)
for rr in self._handle_answer_section(answer_section):
yield rr
if self._soa_rr_count == 2:
break
-
+
if self._shutdown_event.is_set():
raise XfrinException('xfrin is forced to stop')
@@ -322,16 +398,18 @@ class XfrinConnection(asyncore.dispatcher):
sys.stdout.write('[b10-xfrin] %s\n' % str(msg))
-def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file,
- shutdown_event, master_addrinfo, check_soa, verbose):
+def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file,
+ shutdown_event, master_addrinfo, check_soa, verbose,
+ tsig_key):
xfrin_recorder.increment(zone_name)
sock_map = {}
conn = XfrinConnection(sock_map, zone_name, rrclass, db_file,
- shutdown_event, master_addrinfo, verbose)
+ shutdown_event, master_addrinfo,
+ tsig_key, verbose)
ret = XFRIN_FAIL
if conn.connect_to_master():
ret = conn.do_xfrin(check_soa)
-
+
# Publish the zone transfer result news, so zonemgr can reset the
# zone timer, and xfrout can notify the zone's slaves if the result
# is success.
@@ -367,23 +445,111 @@ class XfrinRecorder:
self._lock.release()
return ret
+class ZoneInfo:
+ def __init__(self, config_data, module_cc):
+ """Creates a zone_info with the config data element as
+ specified by the 'zones' list in xfrin.spec. Module_cc is
+ needed to get the defaults from the specification"""
+ self._module_cc = module_cc
+ self.set_name(config_data.get('name'))
+ self.set_master_addr(config_data.get('master_addr'))
+
+ self.set_master_port(config_data.get('master_port'))
+ self.set_zone_class(config_data.get('class'))
+ self.set_tsig_key(config_data.get('tsig_key'))
+
+ def set_name(self, name_str):
+ """Set the name for this zone given a name string.
+ Raises XfrinZoneInfoException if name_str is None or if it
+ cannot be parsed."""
+ if name_str is None:
+ raise XfrinZoneInfoException("Configuration zones list "
+ "element does not contain "
+ "'name' attribute")
+ else:
+ self.name = _check_zone_name(name_str)
+
+ def set_master_addr(self, master_addr_str):
+ """Set the master address for this zone given an IP address
+ string. Raises XfrinZoneInfoException if master_addr_str is
+ None or if it cannot be parsed."""
+ if master_addr_str is None:
+ raise XfrinZoneInfoException("master address missing from config data")
+ else:
+ try:
+ self.master_addr = isc.net.parse.addr_parse(master_addr_str)
+ except ValueError:
+ errmsg = "bad format for zone's master: " + master_addr_str
+ log_error(errmsg)
+ raise XfrinZoneInfoException(errmsg)
+
+ def set_master_port(self, master_port_str):
+ """Set the master port given a port number string. If
+ master_port_str is None, the default from the specification
+ for this module will be used. Raises XfrinZoneInfoException if
+ the string contains an invalid port number"""
+ if master_port_str is None:
+ self.master_port = self._module_cc.get_default_value("zones/master_port")
+ else:
+ try:
+ self.master_port = isc.net.parse.port_parse(master_port_str)
+ except ValueError:
+ errmsg = "bad format for zone's master port: " + master_port_str
+ log_error(errmsg)
+ raise XfrinZoneInfoException(errmsg)
+
+ def set_zone_class(self, zone_class_str):
+ """Set the zone class given an RR class str (e.g. "IN"). If
+ zone_class_str is None, it will default to what is specified
+ in the specification file for this module. Raises
+ XfrinZoneInfoException if the string cannot be parsed."""
+ # TODO: remove _str
+ self.class_str = zone_class_str or self._module_cc.get_default_value("zones/class")
+ if zone_class_str == None:
+ #TODO rrclass->zone_class
+ self.rrclass = RRClass(self._module_cc.get_default_value("zones/class"))
+ else:
+ try:
+ self.rrclass = RRClass(zone_class_str)
+ except InvalidRRClass:
+ errmsg = "invalid zone class: " + zone_class_str
+ log_error(errmsg)
+ raise XfrinZoneInfoException(errmsg)
+
+ def set_tsig_key(self, tsig_key_str):
+ """Set the tsig_key for this zone, given a TSIG key string
+ representation. If tsig_key_str is None, no TSIG key will
+ be set. Raises XfrinZoneInfoException if tsig_key_str cannot
+ be parsed."""
+ if tsig_key_str is None:
+ self.tsig_key = None
+ else:
+ try:
+ self.tsig_key = TSIGKey(tsig_key_str)
+ except InvalidParameter as ipe:
+ errmsg = "bad TSIG key string: " + tsig_key_str
+ log_error(errmsg)
+ raise XfrinZoneInfoException(errmsg)
+
+ def get_master_addr_info(self):
+ return (self.master_addr.family, socket.SOCK_STREAM,
+ (str(self.master_addr), self.master_port))
+
class Xfrin:
def __init__(self, verbose = False):
self._max_transfers_in = 10
- #TODO, this is the temp way to set the zone's master.
- self._master_addr = DEFAULT_MASTER
- self._master_port = DEFAULT_MASTER_PORT
+ 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
- implemented separately for convenience of unit tests; by letting
- the test code override this method we can test most of this class
+ '''This method is used only as part of initialization, but is
+ implemented separately for convenience of unit tests; by letting
+ the test code override this method we can test most of this class
without requiring a command channel.'''
- # Create one session for sending command to other modules, because the
+ # Create one session for sending command to other modules, because the
# listening session will block the send operation.
self._send_cc_session = isc.cc.Session()
self._module_cc = isc.config.ModuleCCSession(SPECFILE_LOCATION,
@@ -391,36 +557,55 @@ class Xfrin:
self.command_handler)
self._module_cc.start()
config_data = self._module_cc.get_full_config()
- self._max_transfers_in = config_data.get("transfers_in")
- self._master_addr = config_data.get('master_addr') or self._master_addr
- self._master_port = config_data.get('master_port') or self._master_port
+ self.config_handler(config_data)
def _cc_check_command(self):
- '''This is a straightforward wrapper for cc.check_command,
- but provided as a separate method for the convenience
+ '''This is a straightforward wrapper for cc.check_command,
+ but provided as a separate method for the convenience
of unit tests.'''
self._module_cc.check_command(False)
+ def _get_zone_info(self, name, rrclass):
+ """Returns the ZoneInfo object containing the configured data
+ for the given zone name. If the zone name did not have any
+ data, returns None"""
+ return self._zones.get((name.to_text(), rrclass.to_text()))
+
+ def _add_zone_info(self, zone_info):
+ """Add the zone info. Raises a XfrinZoneInfoException if a zone
+ with the same name and class is already configured"""
+ key = (zone_info.name.to_text(), zone_info.class_str)
+ if key in self._zones:
+ raise XfrinZoneInfoException("zone " + str(key) +
+ " configured multiple times")
+ self._zones[key] = zone_info
+
+ def _clear_zone_info(self):
+ self._zones = {}
+
def config_handler(self, new_config):
+ # backup all config data (should there be a problem in the new
+ # data)
+ old_max_transfers_in = self._max_transfers_in
+ old_zones = self._zones
+
self._max_transfers_in = new_config.get("transfers_in") or self._max_transfers_in
- if ('master_addr' in new_config) or ('master_port' in new_config):
- # User should change the port and address together.
- try:
- addr = new_config.get('master_addr') or self._master_addr
- port = new_config.get('master_port') or self._master_port
- isc.net.parse.addr_parse(addr)
- isc.net.parse.port_parse(port)
- self._master_addr = addr
- self._master_port = port
- except ValueError:
- errmsg = "bad format for zone's master: " + str(new_config)
- log_error(errmsg)
- return create_answer(1, errmsg)
+
+ if 'zones' in new_config:
+ self._clear_zone_info()
+ for zone_config in new_config.get('zones'):
+ try:
+ zone_info = ZoneInfo(zone_config, self._module_cc)
+ self._add_zone_info(zone_info)
+ except XfrinZoneInfoException as xce:
+ self._zones = old_zones
+ self._max_transfers_in = old_max_transfers_in
+ return create_answer(1, str(xce))
return create_answer(0)
def shutdown(self):
- ''' shutdown the xfrin process. the thread which is doing xfrin should be
+ ''' shutdown the xfrin process. the thread which is doing xfrin should be
terminated.
'''
self._shutdown_event.set()
@@ -436,30 +621,47 @@ class Xfrin:
if command == 'shutdown':
self._shutdown_event.set()
elif command == 'notify' or command == REFRESH_FROM_ZONEMGR:
- # Xfrin receives the refresh/notify command from zone manager.
- # notify command maybe has the parameters which
+ # Xfrin receives the refresh/notify command from zone manager.
+ # notify command maybe has the parameters which
# specify the notifyfrom address and port, according the RFC1996, zone
# transfer should starts first from the notifyfrom, but now, let 'TODO' it.
+ # (using the value now, while we can only set one master address, would be
+ # a security hole. Once we add the ability to have multiple master addresses,
+ # we should check if it matches one of them, and then use it.)
(zone_name, rrclass) = self._parse_zone_name_and_class(args)
- (master_addr) = build_addr_info(self._master_addr, self._master_port)
- ret = self.xfrin_start(zone_name,
- rrclass,
- self._get_db_file(),
- master_addr,
- True)
- answer = create_answer(ret[0], ret[1])
+ zone_info = self._get_zone_info(zone_name, rrclass)
+ if zone_info is None:
+ # TODO what to do? no info known about zone. defaults?
+ errmsg = "Got notification to retransfer unknown zone " + zone_name.to_text()
+ log_error(errmsg)
+ answer = create_answer(1, errmsg)
+ else:
+ master_addr = zone_info.get_master_addr_info()
+ ret = self.xfrin_start(zone_name,
+ rrclass,
+ self._get_db_file(),
+ master_addr,
+ zone_info.tsig_key,
+ True)
+ answer = create_answer(ret[0], ret[1])
elif command == 'retransfer' or command == 'refresh':
# Xfrin receives the retransfer/refresh from cmdctl(sent by bindctl).
- # If the command has specified master address, do transfer from the
- # master address, or else do transfer from the configured masters.
+ # If the command has specified master address, do transfer from the
+ # master address, or else do transfer from the configured masters.
(zone_name, rrclass) = self._parse_zone_name_and_class(args)
- master_addr = self._parse_master_and_port(args)
+ master_addr = self._parse_master_and_port(args, zone_name,
+ rrclass)
+ zone_info = self._get_zone_info(zone_name, rrclass)
+ tsig_key = None
+ if zone_info:
+ tsig_key = zone_info.tsig_key
db_file = args.get('db_file') or self._get_db_file()
- ret = self.xfrin_start(zone_name,
- rrclass,
- db_file,
+ ret = self.xfrin_start(zone_name,
+ rrclass,
+ db_file,
master_addr,
+ tsig_key,
(False if command == 'retransfer' else True))
answer = create_answer(ret[0], ret[1])
@@ -471,26 +673,52 @@ class Xfrin:
return answer
def _parse_zone_name_and_class(self, args):
- zone_name = args.get('zone_name')
- if not zone_name:
+ zone_name_str = args.get('zone_name')
+ if zone_name_str is None:
raise XfrinException('zone name should be provided')
- rrclass = args.get('zone_class')
- if not rrclass:
- rrclass = RRClass.IN()
+ return (_check_zone_name(zone_name_str), _check_zone_class(args.get('zone_class')))
+
+ def _parse_master_and_port(self, args, zone_name, zone_class):
+ """
+ Return tuple (family, socktype, sockaddr) for address and port in given
+ args dict.
+ IPv4 and IPv6 are the only supported addresses now, so sockaddr will be
+ (address, port). The socktype is socket.SOCK_STREAM for now.
+ """
+ # check if we have configured info about this zone, in case
+ # port or master are not specified
+ zone_info = self._get_zone_info(zone_name, zone_class)
+
+ addr_str = args.get('master')
+ if addr_str is None:
+ if zone_info is not None:
+ addr = zone_info.master_addr
+ else:
+ raise XfrinException("Master address not given or "
+ "configured for " + zone_name.to_text())
+ else:
+ try:
+ addr = isc.net.parse.addr_parse(addr_str)
+ except ValueError as err:
+ raise XfrinException("failed to resolve master address %s: %s" %
+ (addr_str, str(err)))
+
+ port_str = args.get('port')
+ if port_str is None:
+ if zone_info is not None:
+ port = zone_info.master_port
+ else:
+ port = DEFAULT_MASTER_PORT
else:
try:
- rrclass = RRClass(rrclass)
- except InvalidRRClass as e:
- raise XfrinException('invalid RRClass: ' + rrclass)
-
- return zone_name, rrclass
-
- def _parse_master_and_port(self, args):
- port = args.get('port') or self._master_port
- master = args.get('master') or self._master_addr
- return build_addr_info(master, port)
-
+ port = isc.net.parse.port_parse(port_str)
+ except ValueError as err:
+ raise XfrinException("failed to parse port=%s: %s" %
+ (port_str, str(err)))
+
+ return (addr.family, socket.SOCK_STREAM, (str(addr), port))
+
def _get_db_file(self):
#TODO, the db file path should be got in auth server's configuration
# if we need access to this configuration more often, we
@@ -506,12 +734,12 @@ class Xfrin:
db_file = os.environ["B10_FROM_BUILD"] + os.sep + "bind10_zones.sqlite3"
self._module_cc.remove_remote_config(AUTH_SPECFILE_LOCATION)
return db_file
-
+
def publish_xfrin_news(self, zone_name, zone_class, xfr_result):
'''Send command to xfrout/zone manager module.
- If xfrin has finished successfully for one zone, tell the good
+ If xfrin has finished successfully for one zone, tell the good
news(command: zone_new_data_ready) to zone manager and xfrout.
- if xfrin failed, just tell the bad news to zone manager, so that
+ 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()}
if xfr_result == XFRIN_OK:
@@ -531,8 +759,8 @@ class Xfrin:
seq)
except isc.cc.session.SessionTimeout:
pass # for now we just ignore the failure
- except socket.error as err:
- log_error("Fail to send message to %s and %s, msgq may has been killed"
+ except socket.error as err:
+ log_error("Fail to send message to %s and %s, msgq may has been killed"
% (XFROUT_MODULE_NAME, ZONE_MANAGER_MODULE_NAME))
else:
msg = create_command(ZONE_XFRIN_FAILED, param)
@@ -545,14 +773,14 @@ class Xfrin:
except isc.cc.session.SessionTimeout:
pass # for now we just ignore the failure
except socket.error as err:
- log_error("Fail to send message to %s, msgq may has been killed"
+ log_error("Fail to send message to %s, msgq may has been killed"
% ZONE_MANAGER_MODULE_NAME)
def startup(self):
while not self._shutdown_event.is_set():
self._cc_check_command()
- def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo,
+ def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo, tsig_key,
check_soa = True):
if "pydnspp" not in sys.modules:
return (1, "xfrin failed, can't load dns message python library: 'pydnspp'")
@@ -567,11 +795,13 @@ class Xfrin:
xfrin_thread = threading.Thread(target = process_xfrin,
args = (self,
self.recorder,
- zone_name, rrclass,
+ zone_name.to_text(),
+ rrclass,
db_file,
self._shutdown_event,
master_addrinfo, check_soa,
- self._verbose))
+ self._verbose,
+ tsig_key))
xfrin_thread.start()
return (0, 'zone xfrin is started')
@@ -588,20 +818,6 @@ def set_signal_handler():
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
-def build_addr_info(addrstr, portstr):
- """
- Return tuple (family, socktype, sockaddr) for given address and port.
- IPv4 and IPv6 are the only supported addresses now, so sockaddr will be
- (address, port). The socktype is socket.SOCK_STREAM for now.
- """
- try:
- port = isc.net.parse.port_parse(portstr)
- addr = isc.net.parse.addr_parse(addrstr)
- return (addr.family, socket.SOCK_STREAM, (addrstr, port))
- except ValueError as err:
- raise XfrinException("failed to resolve master address/port=%s/%s: %s" %
- (addrstr, portstr, str(err)))
-
def set_cmd_options(parser):
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
help="display more about what is going on")
diff --git a/src/bin/xfrin/xfrin.spec b/src/bin/xfrin/xfrin.spec
index 61ddaad..a3e62ce 100644
--- a/src/bin/xfrin/xfrin.spec
+++ b/src/bin/xfrin/xfrin.spec
@@ -9,16 +9,43 @@
"item_optional": false,
"item_default": 10
},
- {
- "item_name": "master_addr",
- "item_type": "string",
- "item_optional": false,
- "item_default": ""
- },
- { "item_name": "master_port",
- "item_type": "integer",
+ { "item_name": "zones",
+ "item_type": "list",
"item_optional": false,
- "item_default": 53
+ "item_default": [],
+ "list_item_spec":
+ { "item_type": "map",
+ "item_name": "zone_info",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ { "item_name": "class",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "IN"
+ },
+ {
+ "item_name": "master_addr",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ { "item_name": "master_port",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 53
+ },
+ { "item_name": "tsig_key",
+ "item_type": "string",
+ "item_optional": true
+ }
+ ]
+ }
}
],
"commands": [
diff --git a/src/bin/zonemgr/b10-zonemgr.8 b/src/bin/zonemgr/b10-zonemgr.8
index fbd0602..bfc0a7b 100644
--- a/src/bin/zonemgr/b10-zonemgr.8
+++ b/src/bin/zonemgr/b10-zonemgr.8
@@ -2,12 +2,12 @@
.\" Title: b10-zonemgr
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: October 18, 2010
+.\" Date: May 19, 2011
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-ZONEMGR" "8" "October 18, 2010" "BIND10" "BIND10"
+.TH "B10\-ZONEMGR" "8" "May 19, 2011" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -46,11 +46,6 @@ receives its configurations from
The configurable settings are:
.PP
-\fIjitter_scope\fR
-defines the random jitter range subtracted from the refresh and retry timers to avoid many zones from refreshing at the same time\&. The refresh or retry time actually used is a random time between the defined refresh or retry time and it multiplied by the
-\fIjitter_scope\fR\&. This is re\-evaluated after each refresh or retry\&. This value is a real number and the maximum is 0\&.5 (half of the refresh or retry time)\&. The default is 0\&.25\&. Set to 0 to disable the jitter\&.
-.PP
-
\fIlowerbound_refresh\fR
defines the minimum SOA REFRESH time in seconds\&. The default is 10\&.
.PP
@@ -59,10 +54,36 @@ defines the minimum SOA REFRESH time in seconds\&. The default is 10\&.
defines the minimum SOA RETRY time in seconds\&. The default is 5\&.
.PP
+\fIrefresh_jitter\fR
+This value is a real number\&. The maximum amount is 0\&.5\&. The default is 0\&.25\&.
+.PP
+
+\fIreload_jitter\fR
+This value is a real number\&. The default is 0\&.75\&.
+.PP
+
\fImax_transfer_timeout\fR
defines the maximum amount of time in seconds for a transfer\&.
The default is 14400 (4 hours)\&.
.PP
+
+\fIsecondary_zones\fR
+is a list of slave zones that the
+\fBb10\-zonemgr\fR
+should keep timers for\&. The list items include the
+\fIname\fR
+(which defines the zone name) and the
+\fIclass\fR
+(which defaults to
+\(lqIN\(rq)\&.
+.PP
+(A deprecated configuration is
+\fIjitter_scope\fR
+which is superceded by
+\fIrefresh_jitter\fR
+and
+\fIreload_jitter\fR\&.)
+.PP
The configuration commands are:
.PP
@@ -107,5 +128,5 @@ The
daemon was designed in July 2010 by CNNIC for the ISC BIND 10 project\&.
.SH "COPYRIGHT"
.br
-Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")
+Copyright \(co 2010-2011 Internet Systems Consortium, Inc. ("ISC")
.br
diff --git a/src/bin/zonemgr/b10-zonemgr.xml b/src/bin/zonemgr/b10-zonemgr.xml
index 4d796ee..00f5d04 100644
--- a/src/bin/zonemgr/b10-zonemgr.xml
+++ b/src/bin/zonemgr/b10-zonemgr.xml
@@ -2,7 +2,7 @@
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[<!ENTITY mdash "—">]>
<!--
- - Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2010-2011 Internet Systems Consortium, Inc. ("ISC")
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>October 18, 2010</date>
+ <date>May 19, 2011</date>
</refentryinfo>
<refmeta>
@@ -36,7 +36,7 @@
<docinfo>
<copyright>
- <year>2010</year>
+ <year>2010-2011</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
@@ -92,6 +92,39 @@
<para>
The configurable settings are:
</para>
+
+ <para>
+ <varname>lowerbound_refresh</varname>
+ defines the minimum SOA REFRESH time in seconds.
+ The default is 10.
+ </para>
+
+ <para>
+ <varname>lowerbound_retry</varname>
+ defines the minimum SOA RETRY time in seconds.
+ The default is 5.
+ </para>
+
+ <para>
+ <varname>refresh_jitter</varname>
+ This value is a real number.
+ The maximum amount is 0.5.
+ The default is 0.25.
+ </para>
+<!-- TODO: needs to be documented -->
+<!-- TODO: Set to 0 to disable the jitter. -->
+
+ <para>
+ <varname>reload_jitter</varname>
+ This value is a real number.
+ The default is 0.75.
+ </para>
+<!-- TODO: needs to be documented -->
+<!-- TODO: Set to 0 to disable the jitter. -->
+<!-- what does 0 do? -->
+<!-- TODO: no max? -->
+
+<!-- TODO: remove this. This is old removed config
<para>
<varname>jitter_scope</varname>
defines the random jitter range subtracted from the refresh
@@ -106,16 +139,8 @@
The default is 0.25.
Set to 0 to disable the jitter.
</para>
- <para>
- <varname>lowerbound_refresh</varname>
- defines the minimum SOA REFRESH time in seconds.
- The default is 10.
- </para>
- <para>
- <varname>lowerbound_retry</varname>
- defines the minimum SOA RETRY time in seconds.
- The default is 5.
- </para>
+-->
+
<para>
<varname>max_transfer_timeout</varname>
defines the maximum amount of time in seconds for a transfer.
@@ -123,6 +148,21 @@
The default is 14400 (4 hours).
</para>
+<!-- TODO: this duplicates list in Xfrin too -->
+ <para>
+ <varname>secondary_zones</varname> is a list of slave zones
+ that the <command>b10-zonemgr</command> should keep timers for.
+ The list items include the <varname>name</varname> (which
+ defines the zone name) and the <varname>class</varname>
+ (which defaults to <quote>IN</quote>).
+ </para>
+
+ <para>
+ (A deprecated configuration is <varname>jitter_scope</varname>
+ which is superceded by <varname>refresh_jitter</varname>
+ and <varname>reload_jitter</varname>.)
+ </para>
+
<!-- TODO: formating -->
<para>
The configuration commands are:
diff --git a/src/bin/zonemgr/tests/zonemgr_test.py b/src/bin/zonemgr/tests/zonemgr_test.py
index c6d151d..496ce6b 100644
--- a/src/bin/zonemgr/tests/zonemgr_test.py
+++ b/src/bin/zonemgr/tests/zonemgr_test.py
@@ -21,11 +21,12 @@ import os
import tempfile
from zonemgr import *
-ZONE_NAME_CLASS1_IN = ("sd.cn.", "IN")
-ZONE_NAME_CLASS2_CH = ("tw.cn.", "CH")
-ZONE_NAME_CLASS3_IN = ("example.com", "IN")
-ZONE_NAME_CLASS1_CH = ("sd.cn.", "CH")
-ZONE_NAME_CLASS2_IN = ("tw.cn.", "IN")
+ZONE_NAME_CLASS1_IN = ("example.net.", "IN")
+ZONE_NAME_CLASS1_CH = ("example.net.", "CH")
+ZONE_NAME_CLASS2_IN = ("example.org.", "IN")
+ZONE_NAME_CLASS2_CH = ("example.org.", "CH")
+ZONE_NAME_CLASS3_IN = ("example.com.", "IN")
+ZONE_NAME_CLASS3_CH = ("example.com.", "CH")
MAX_TRANSFER_TIMEOUT = 14400
LOWERBOUND_REFRESH = 10
@@ -80,12 +81,12 @@ class MyZonemgrRefresh(ZonemgrRefresh):
self._refresh_jitter = 0.25
def get_zone_soa(zone_name, db_file):
- if zone_name == 'sd.cn.':
- return (1, 2, 'sd.cn.', 'cn.sd.', 21600, 'SOA', None,
- 'a.dns.cn. root.cnnic.cn. 2009073106 7200 3600 2419200 21600')
- elif zone_name == 'tw.cn.':
- return (1, 2, 'tw.cn.', 'cn.sd.', 21600, 'SOA', None,
- 'a.dns.cn. root.cnnic.cn. 2009073112 7200 3600 2419200 21600')
+ if zone_name == 'example.net.':
+ return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
+ 'a.example.net. root.example.net. 2009073106 7200 3600 2419200 21600')
+ elif zone_name == 'example.org.':
+ return (1, 2, 'example.org.', 'example.org.sd.', 21600, 'SOA', None,
+ 'a.example.org. root.example.org. 2009073112 7200 3600 2419200 21600')
else:
return None
sqlite3_ds.get_zone_soa = get_zone_soa
@@ -94,15 +95,15 @@ class MyZonemgrRefresh(ZonemgrRefresh):
self._slave_socket, FakeConfig())
current_time = time.time()
self._zonemgr_refresh_info = {
- ('sd.cn.', 'IN'): {
+ ('example.net.', 'IN'): {
'last_refresh_time': current_time,
'next_refresh_time': current_time + 6500,
- 'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600',
+ 'zone_soa_rdata': 'a.example.net. root.example.net. 2009073105 7200 3600 2419200 21600',
'zone_state': 0},
- ('tw.cn.', 'CH'): {
+ ('example.org.', 'CH'): {
'last_refresh_time': current_time,
'next_refresh_time': current_time + 6900,
- 'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073112 7200 3600 2419200 21600',
+ 'zone_soa_rdata': 'a.example.org. root.example.org. 2009073112 7200 3600 2419200 21600',
'zone_state': 0}
}
@@ -157,6 +158,7 @@ class TestZonemgrRefresh(unittest.TestCase):
self.assertFalse(self.zone_refresh._zone_not_exist(ZONE_NAME_CLASS2_CH))
self.assertTrue(self.zone_refresh._zone_not_exist(ZONE_NAME_CLASS2_IN))
self.assertTrue(self.zone_refresh._zone_not_exist(ZONE_NAME_CLASS3_IN))
+ self.assertTrue(self.zone_refresh._zone_not_exist(ZONE_NAME_CLASS3_CH))
def test_set_zone_notify_timer(self):
time1 = time.time()
@@ -179,20 +181,20 @@ class TestZonemgrRefresh(unittest.TestCase):
self.assertTrue(self.zone_refresh._zone_is_expired(ZONE_NAME_CLASS1_IN))
def test_get_zone_soa_rdata(self):
- soa_rdata1 = 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600'
- soa_rdata2 = 'a.dns.cn. root.cnnic.cn. 2009073112 7200 3600 2419200 21600'
+ soa_rdata1 = 'a.example.net. root.example.net. 2009073105 7200 3600 2419200 21600'
+ soa_rdata2 = 'a.example.org. root.example.org. 2009073112 7200 3600 2419200 21600'
self.assertEqual(soa_rdata1, self.zone_refresh._get_zone_soa_rdata(ZONE_NAME_CLASS1_IN))
self.assertRaises(KeyError, self.zone_refresh._get_zone_soa_rdata, ZONE_NAME_CLASS1_CH)
self.assertEqual(soa_rdata2, self.zone_refresh._get_zone_soa_rdata(ZONE_NAME_CLASS2_CH))
self.assertRaises(KeyError, self.zone_refresh._get_zone_soa_rdata, ZONE_NAME_CLASS2_IN)
def test_zonemgr_reload_zone(self):
- soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600'
+ soa_rdata = 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600'
# We need to restore this not to harm other tests
old_get_zone_soa = sqlite3_ds.get_zone_soa
def get_zone_soa(zone_name, db_file):
- return (1, 2, 'sd.cn.', 'cn.sd.', 21600, 'SOA', None,
- 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600')
+ return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
+ 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600')
sqlite3_ds.get_zone_soa = get_zone_soa
self.zone_refresh.zonemgr_reload_zone(ZONE_NAME_CLASS1_IN)
@@ -274,15 +276,15 @@ class TestZonemgrRefresh(unittest.TestCase):
self.assertTrue(self.zone_refresh._zone_mgr_is_empty())
def test_zonemgr_add_zone(self):
- soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600'
+ soa_rdata = 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600'
# This needs to be restored. The following test actually failed if we left
# this unclean
old_get_zone_soa = sqlite3_ds.get_zone_soa
time1 = time.time()
def get_zone_soa(zone_name, db_file):
- return (1, 2, 'sd.cn.', 'cn.sd.', 21600, 'SOA', None,
- 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600')
+ return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
+ 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600')
sqlite3_ds.get_zone_soa = get_zone_soa
@@ -314,15 +316,15 @@ class TestZonemgrRefresh(unittest.TestCase):
current_time = time.time()
self.assertTrue(zone_timeout <= current_time)
self.assertRaises(ZonemgrException, self.zone_refresh.zone_handle_notify,\
- ("org.cn.", "IN"), "127.0.0.1")
+ ZONE_NAME_CLASS3_CH, "127.0.0.1")
self.assertRaises(ZonemgrException, self.zone_refresh.zone_handle_notify,\
ZONE_NAME_CLASS3_IN, "127.0.0.1")
def test_zone_refresh_success(self):
- soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600'
+ soa_rdata = 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600'
def get_zone_soa(zone_name, db_file):
- return (1, 2, 'sd.cn.', 'cn.sd.', 21600, 'SOA', None,
- 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600')
+ return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
+ 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600')
sqlite3_ds.get_zone_soa = get_zone_soa
time1 = time.time()
self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"] = ZONE_REFRESHING
@@ -337,11 +339,11 @@ class TestZonemgrRefresh(unittest.TestCase):
last_refresh_time = self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["last_refresh_time"]
self.assertTrue(time1 <= last_refresh_time)
self.assertTrue(last_refresh_time <= time2)
- self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_success, ("org.cn.", "CH"))
+ self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_success, ("example.test.", "CH"))
self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_success, ZONE_NAME_CLASS3_IN)
def test_zone_refresh_fail(self):
- soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600'
+ soa_rdata = 'a.example.net. root.example.net. 2009073105 7200 3600 2419200 21600'
time1 = time.time()
self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"] = ZONE_REFRESHING
self.zone_refresh.zone_refresh_fail(ZONE_NAME_CLASS1_IN)
@@ -357,22 +359,22 @@ class TestZonemgrRefresh(unittest.TestCase):
self.zone_refresh.zone_refresh_fail(ZONE_NAME_CLASS1_IN)
self.assertEqual(ZONE_EXPIRED, self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"])
- self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_fail, ("org.cn.", "CH"))
+ self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_fail, ZONE_NAME_CLASS3_CH)
self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_fail, ZONE_NAME_CLASS3_IN)
def test_find_need_do_refresh_zone(self):
time1 = time.time()
self.zone_refresh._zonemgr_refresh_info = {
- ("sd.cn.","IN"):{
+ ("example.net.","IN"):{
'last_refresh_time': time1,
'next_refresh_time': time1 + 7200,
- 'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600',
+ 'zone_soa_rdata': 'a.example.net. root.example.net. 2009073105 7200 3600 2419200 21600',
'zone_state': ZONE_OK},
- ("tw.cn.","CH"):{
+ ("example.org.","CH"):{
'last_refresh_time': time1 - 7200,
'next_refresh_time': time1,
'refresh_timeout': time1 + MAX_TRANSFER_TIMEOUT,
- 'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073112 7200 3600 2419200 21600',
+ 'zone_soa_rdata': 'a.example.org. root.example.org. 2009073112 7200 3600 2419200 21600',
'zone_state': ZONE_REFRESHING}
}
zone_need_refresh = self.zone_refresh._find_need_do_refresh_zone()
@@ -385,10 +387,10 @@ class TestZonemgrRefresh(unittest.TestCase):
def test_do_refresh(self):
time1 = time.time()
self.zone_refresh._zonemgr_refresh_info = {
- ("sd.cn.", "IN"):{
+ ("example.net.", "IN"):{
'last_refresh_time': time1 - 7200,
'next_refresh_time': time1 - 1,
- 'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600',
+ 'zone_soa_rdata': 'a.example.net. root.example.net. 2009073105 7200 3600 2419200 21600',
'zone_state': ZONE_OK}
}
self.zone_refresh._do_refresh(ZONE_NAME_CLASS1_IN)
@@ -416,10 +418,10 @@ class TestZonemgrRefresh(unittest.TestCase):
"""
time1 = time.time()
self.zone_refresh._zonemgr_refresh_info = {
- ("sd.cn.", "IN"):{
+ ("example.net.", "IN"):{
'last_refresh_time': time1 - 7200,
'next_refresh_time': time1 - 1,
- 'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600',
+ 'zone_soa_rdata': 'a.example.net. root.example.net. 2009073105 7200 3600 2419200 21600',
'zone_state': ZONE_OK}
}
self.zone_refresh._check_sock = self.zone_refresh._master_socket
@@ -432,6 +434,14 @@ class TestZonemgrRefresh(unittest.TestCase):
self.assertTrue(zone_state == ZONE_REFRESHING)
def test_update_config_data(self):
+ # make sure it doesn't fail if we only provide secondary zones
+ config_data = {
+ "secondary_zones": [ { "name": "example.net.",
+ "class": "IN" } ]
+ }
+ self.zone_refresh.update_config_data(config_data)
+
+ # update all values
config_data = {
"lowerbound_refresh" : 60,
"lowerbound_retry" : 30,
@@ -447,6 +457,53 @@ class TestZonemgrRefresh(unittest.TestCase):
self.assertEqual(0.25, self.zone_refresh._refresh_jitter)
self.assertEqual(0.75, self.zone_refresh._reload_jitter)
+ # make sure they are not reset when we only update one
+ config_data = {
+ "reload_jitter" : 0.35,
+ }
+ self.zone_refresh.update_config_data(config_data)
+ self.assertEqual(60, self.zone_refresh._lowerbound_refresh)
+ self.assertEqual(30, self.zone_refresh._lowerbound_retry)
+ self.assertEqual(19800, self.zone_refresh._max_transfer_timeout)
+ self.assertEqual(0.25, self.zone_refresh._refresh_jitter)
+ self.assertEqual(0.35, self.zone_refresh._reload_jitter)
+
+ # and make sure we restore the previous config if something
+ # goes wrong
+ config_data = {
+ "lowerbound_refresh" : 61,
+ "lowerbound_retry" : 31,
+ "max_transfer_timeout" : 19801,
+ "refresh_jitter" : 0.21,
+ "reload_jitter" : 0.71,
+ "secondary_zones": [ { "name": "doesnotexist",
+ "class": "IN" } ]
+ }
+ self.assertRaises(ZonemgrException,
+ self.zone_refresh.update_config_data,
+ config_data)
+ self.assertEqual(60, self.zone_refresh._lowerbound_refresh)
+ self.assertEqual(30, self.zone_refresh._lowerbound_retry)
+ self.assertEqual(19800, self.zone_refresh._max_transfer_timeout)
+ self.assertEqual(0.25, self.zone_refresh._refresh_jitter)
+ self.assertEqual(0.35, self.zone_refresh._reload_jitter)
+
+ # Make sure we accept 0 as a value
+ config_data = {
+ "lowerbound_refresh" : 60,
+ "lowerbound_retry" : 30,
+ "max_transfer_timeout" : 19800,
+ "refresh_jitter" : 0,
+ "reload_jitter" : 0.75,
+ "secondary_zones": []
+ }
+ self.zone_refresh.update_config_data(config_data)
+ self.assertEqual(60, self.zone_refresh._lowerbound_refresh)
+ self.assertEqual(30, self.zone_refresh._lowerbound_retry)
+ self.assertEqual(19800, self.zone_refresh._max_transfer_timeout)
+ self.assertEqual(0, self.zone_refresh._refresh_jitter)
+ self.assertEqual(0.75, self.zone_refresh._reload_jitter)
+
def test_shutdown(self):
self.zone_refresh._check_sock = self.zone_refresh._master_socket
listener = self.zone_refresh.run_timer()
@@ -465,19 +522,19 @@ class TestZonemgrRefresh(unittest.TestCase):
# Put something in
config.set_zone_list_from_name_classes([ZONE_NAME_CLASS1_IN])
self.zone_refresh.update_config_data(config)
- self.assertTrue(("sd.cn.", "IN") in
+ self.assertTrue(("example.net.", "IN") in
self.zone_refresh._zonemgr_refresh_info)
# This one does not exist
config.set_zone_list_from_name_classes(["example.net", "CH"])
self.assertRaises(ZonemgrException,
self.zone_refresh.update_config_data, config)
# So it should not affect the old ones
- self.assertTrue(("sd.cn.", "IN") in
+ self.assertTrue(("example.net.", "IN") in
self.zone_refresh._zonemgr_refresh_info)
# Make sure it works even when we "accidentally" forget the final dot
- config.set_zone_list_from_name_classes([("sd.cn", "IN")])
+ config.set_zone_list_from_name_classes([("example.net", "IN")])
self.zone_refresh.update_config_data(config)
- self.assertTrue(("sd.cn.", "IN") in
+ self.assertTrue(("example.net.", "IN") in
self.zone_refresh._zonemgr_refresh_info)
def tearDown(self):
@@ -532,7 +589,7 @@ class TestZonemgr(unittest.TestCase):
self.assertEqual(self.zonemgr.config_handler(config_data1),
{"result": [0]})
self.assertEqual(config_data1, self.zonemgr._config_data)
- config_data2 = {"zone_name" : "sd.cn.", "port" : "53", "master" : "192.168.1.1"}
+ config_data2 = {"zone_name" : "example.net.", "port" : "53", "master" : "192.168.1.1"}
self.zonemgr.config_handler(config_data2)
self.assertEqual(config_data1, self.zonemgr._config_data)
# jitter should not be bigger than half of the original value
@@ -553,11 +610,11 @@ class TestZonemgr(unittest.TestCase):
self.assertEqual("initdb.file", self.zonemgr.get_db_file())
def test_parse_cmd_params(self):
- params1 = {"zone_name" : "org.cn", "zone_class" : "CH", "master" : "127.0.0.1"}
- answer1 = (("org.cn", "CH"), "127.0.0.1")
+ params1 = {"zone_name" : "example.com.", "zone_class" : "CH", "master" : "127.0.0.1"}
+ answer1 = (ZONE_NAME_CLASS3_CH, "127.0.0.1")
self.assertEqual(answer1, self.zonemgr._parse_cmd_params(params1, ZONE_NOTIFY_COMMAND))
- params2 = {"zone_name" : "org.cn", "zone_class" : "CH"}
- answer2 = ("org.cn", "CH")
+ params2 = {"zone_name" : "example.com.", "zone_class" : "IN"}
+ answer2 = ZONE_NAME_CLASS3_IN
self.assertEqual(answer2, self.zonemgr._parse_cmd_params(params2, ZONE_XFRIN_SUCCESS_COMMAND))
self.assertRaises(ZonemgrException, self.zonemgr._parse_cmd_params, params2, ZONE_NOTIFY_COMMAND)
params1 = {"zone_class" : "CH"}
diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
index cc6d7b9..c6e3163 100755
--- a/src/bin/zonemgr/zonemgr.py.in
+++ b/src/bin/zonemgr/zonemgr.py.in
@@ -101,6 +101,11 @@ class ZonemgrRefresh:
self._check_sock = slave_socket
self._db_file = db_file
self._zonemgr_refresh_info = {}
+ self._lowerbound_refresh = None
+ self._lowerbound_retry = None
+ self._max_transfer_timeout = None
+ self._refresh_jitter = None
+ self._reload_jitter = None
self.update_config_data(config_data)
self._running = False
@@ -404,37 +409,73 @@ class ZonemgrRefresh:
def update_config_data(self, new_config):
""" update ZonemgrRefresh config """
+ # TODO: we probably want to store all this info in a nice
+ # class, so that we don't have to backup and restore every
+ # single value.
+ # TODO2: We also don't use get_default_value yet
backup = self._zonemgr_refresh_info.copy()
+
+ # Get a new value, but only if it is defined (commonly used below)
+ # We don't use "value or default", because if value would be
+ # 0, we would take default
+ def val_or_default(value, default):
+ if value is not None:
+ return value
+ else:
+ return default
+
+ # store the values so we can restore them if there is a problem
+ lowerbound_refresh_backup = self._lowerbound_refresh
+ self._lowerbound_refresh = val_or_default(
+ new_config.get('lowerbound_refresh'), self._lowerbound_refresh)
+
+ lowerbound_retry_backup = self._lowerbound_retry
+ self._lowerbound_retry = val_or_default(
+ new_config.get('lowerbound_retry'), self._lowerbound_retry)
+
+ max_transfer_timeout_backup = self._max_transfer_timeout
+ self._max_transfer_timeout = val_or_default(
+ new_config.get('max_transfer_timeout'), self._max_transfer_timeout)
+
+ refresh_jitter_backup = self._refresh_jitter
+ self._refresh_jitter = val_or_default(
+ new_config.get('refresh_jitter'), self._refresh_jitter)
+
+ reload_jitter_backup = self._reload_jitter
+ self._reload_jitter = val_or_default(
+ new_config.get('reload_jitter'), self._reload_jitter)
try:
required = {}
- # Add new zones
- for secondary_zone in new_config.get('secondary_zones'):
- name = secondary_zone['name']
- # Be tolerant to sclerotic users who forget the final dot
- if name[-1] != '.':
- name = name + '.'
- name_class = (name, secondary_zone['class'])
- required[name_class] = True
- # Add it only if it isn't there already
- if not name_class in self._zonemgr_refresh_info:
- self.zonemgr_add_zone(name_class)
- # Drop the zones that are no longer there
- # Do it in two phases, python doesn't like deleting while iterating
- to_drop = []
- for old_zone in self._zonemgr_refresh_info:
- if not old_zone in required:
- to_drop.append(old_zone)
- for drop in to_drop:
- del self._zonemgr_refresh_info[drop]
+ secondary_zones = new_config.get('secondary_zones')
+ if secondary_zones is not None:
+ # Add new zones
+ for secondary_zone in new_config.get('secondary_zones'):
+ name = secondary_zone['name']
+ # Be tolerant to sclerotic users who forget the final dot
+ if name[-1] != '.':
+ name = name + '.'
+ name_class = (name, secondary_zone['class'])
+ required[name_class] = True
+ # Add it only if it isn't there already
+ if not name_class in self._zonemgr_refresh_info:
+ self.zonemgr_add_zone(name_class)
+ # Drop the zones that are no longer there
+ # Do it in two phases, python doesn't like deleting while iterating
+ to_drop = []
+ for old_zone in self._zonemgr_refresh_info:
+ if not old_zone in required:
+ to_drop.append(old_zone)
+ for drop in to_drop:
+ del self._zonemgr_refresh_info[drop]
# If we are not able to find it in database, restore the original
except:
self._zonemgr_refresh_info = backup
+ self._lowerbound_refresh = lowerbound_refresh_backup
+ self._lowerbound_retry = lowerbound_retry_backup
+ self._max_transfer_timeout = max_transfer_timeout_backup
+ self._refresh_jitter = refresh_jitter_backup
+ self._reload_jitter = reload_jitter_backup
raise
- self._lowerbound_refresh = new_config.get('lowerbound_refresh')
- self._lowerbound_retry = new_config.get('lowerbound_retry')
- self._max_transfer_timeout = new_config.get('max_transfer_timeout')
- self._refresh_jitter = new_config.get('refresh_jitter')
- self._reload_jitter = new_config.get('reload_jitter')
class Zonemgr:
"""Zone manager class."""
diff --git a/src/cppcheck-suppress.lst b/src/cppcheck-suppress.lst
index 5e6d81f..36b8e4c 100644
--- a/src/cppcheck-suppress.lst
+++ b/src/cppcheck-suppress.lst
@@ -12,4 +12,4 @@ functionConst:src/lib/cache/rrset_cache.h
// Intentional self assignment tests. Suppress warning about them.
selfAssignment:src/lib/dns/tests/name_unittest.cc:293
selfAssignment:src/lib/dns/tests/rdata_unittest.cc:228
-selfAssignment:src/lib/dns/tests/tsigkey_unittest.cc:120
+selfAssignment:src/lib/dns/tests/tsigkey_unittest.cc:137
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 4fc3905..81ddd18 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -1,3 +1,3 @@
-SUBDIRS = exceptions util log cryptolink dns cc config python xfr bench \
- asiolink asiodns nsas cache resolve testutils datasrc \
+SUBDIRS = exceptions util log cryptolink dns cc config python xfr \
+ bench asiolink asiodns nsas cache resolve testutils datasrc \
server_common
diff --git a/src/lib/asiodns/Makefile.am b/src/lib/asiodns/Makefile.am
index 7beaaa3..2a6c3ac 100644
--- a/src/lib/asiodns/Makefile.am
+++ b/src/lib/asiodns/Makefile.am
@@ -11,8 +11,8 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
CLEANFILES = *.gcno *.gcda asiodef.h asiodef.cc
# Define rule to build logging source files from message file
-asiodef.h asiodef.cc: asiodef.msg
- $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/asiodns/asiodef.msg
+asiodef.h asiodef.cc: asiodef.mes
+ $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/asiodns/asiodef.mes
BUILT_SOURCES = asiodef.h asiodef.cc
@@ -28,7 +28,7 @@ libasiodns_la_SOURCES += io_fetch.cc io_fetch.h
nodist_libasiodns_la_SOURCES = asiodef.cc asiodef.h
-EXTRA_DIST = asiodef.msg
+EXTRA_DIST = asiodef.mes
# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS)
diff --git a/src/lib/asiodns/asiodef.mes b/src/lib/asiodns/asiodef.mes
new file mode 100644
index 0000000..3f2e80c
--- /dev/null
+++ b/src/lib/asiodns/asiodef.mes
@@ -0,0 +1,56 @@
+# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$PREFIX ASIODNS_
+$NAMESPACE isc::asiodns
+
+% FETCHCOMP upstream fetch to %1(%2) has now completed
+A debug message, this records the the upstream fetch (a query made by the
+resolver on behalf of its client) to the specified address has completed.
+
+% FETCHSTOP upstream fetch to %1(%2) has been stopped
+An external component has requested the halting of an upstream fetch. This
+is an allowed operation, and the message should only appear if debug is
+enabled.
+
+% OPENSOCK error %1 opening %2 socket to %3(%4)
+The asynchronous I/O code encountered an error when trying to open a socket
+of the specified protocol in order to send a message to the target address.
+The the number of the system error that cause the problem is given in the
+message.
+
+% RECVSOCK error %1 reading %2 data from %3(%4)
+The asynchronous I/O code encountered an error when trying read data from
+the specified address on the given protocol. The the number of the system
+error that cause the problem is given in the message.
+
+% SENDSOCK error %1 sending data using %2 to %3(%4)
+The asynchronous I/O code encountered an error when trying send data to
+the specified address on the given protocol. The the number of the system
+error that cause the problem is given in the message.
+
+% RECVTMO receive timeout while waiting for data from %1(%2)
+An upstream fetch from the specified address timed out. This may happen for
+any number of reasons and is most probably a problem at the remote server
+or a problem on the network. The message will only appear if debug is
+enabled.
+
+% UNKORIGIN unknown origin for ASIO error code %1 (protocol: %2, address %3)
+This message should not appear and indicates an internal error if it does.
+Please enter a bug report.
+
+% UNKRESULT unknown result (%1) when IOFetch::stop() was executed for I/O to %2(%3)
+The termination method of the resolver's upstream fetch class was called with
+an unknown result code (which is given in the message). This message should
+not appear and may indicate an internal error. Please enter a bug report.
diff --git a/src/lib/asiodns/asiodef.msg b/src/lib/asiodns/asiodef.msg
deleted file mode 100644
index ea3ec2f..0000000
--- a/src/lib/asiodns/asiodef.msg
+++ /dev/null
@@ -1,56 +0,0 @@
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-$PREFIX ASIODNS_
-$NAMESPACE isc::asiodns
-
-FETCHCOMP upstream fetch to %1(%2) has now completed
-+ A debug message, this records the the upstream fetch (a query made by the
-+ resolver on behalf of its client) to the specified address has completed.
-
-FETCHSTOP upstream fetch to %1(%2) has been stopped
-+ An external component has requested the halting of an upstream fetch. This
-+ is an allowed operation, and the message should only appear if debug is
-+ enabled.
-
-OPENSOCK error %1 opening %2 socket to %3(%4)
-+ The asynchronous I/O code encountered an error when trying to open a socket
-+ of the specified protocol in order to send a message to the target address.
-+ The the number of the system error that cause the problem is given in the
-+ message.
-
-RECVSOCK error %1 reading %2 data from %3(%4)
-+ The asynchronous I/O code encountered an error when trying read data from
-+ the specified address on the given protocol. The the number of the system
-+ error that cause the problem is given in the message.
-
-SENDSOCK error %1 sending data using %2 to %3(%4)
-+ The asynchronous I/O code encountered an error when trying send data to
-+ the specified address on the given protocol. The the number of the system
-+ error that cause the problem is given in the message.
-
-RECVTMO receive timeout while waiting for data from %1(%2)
-+ An upstream fetch from the specified address timed out. This may happen for
-+ any number of reasons and is most probably a problem at the remote server
-+ or a problem on the network. The message will only appear if debug is
-+ enabled.
-
-UNKORIGIN unknown origin for ASIO error code %1 (protocol: %2, address %3)
-+ This message should not appear and indicates an internal error if it does.
-+ Please enter a bug report.
-
-UNKRESULT unknown result (%1) when IOFetch::stop() was executed for I/O to %2(%3)
-+ The termination method of the resolver's upstream fetch class was called with
-+ an unknown result code (which is given in the message). This message should
-+ not appear and may indicate an internal error. Please enter a bug report.
diff --git a/src/lib/asiodns/io_fetch.cc b/src/lib/asiodns/io_fetch.cc
index cc8bd11..e535381 100644
--- a/src/lib/asiodns/io_fetch.cc
+++ b/src/lib/asiodns/io_fetch.cc
@@ -35,7 +35,6 @@
#include <asiolink/udp_endpoint.h>
#include <asiolink/udp_socket.h>
-#include <dns/message.h>
#include <dns/messagerenderer.h>
#include <dns/opcode.h>
#include <dns/rcode.h>
@@ -91,7 +90,6 @@ struct IOFetchData {
///< Socket to use for I/O
boost::scoped_ptr<IOEndpoint> remote_snd;///< Where the fetch is sent
boost::scoped_ptr<IOEndpoint> remote_rcv;///< Where the response came from
- isc::dns::Question question; ///< Question to be asked
OutputBufferPtr msgbuf; ///< Wire buffer for question
OutputBufferPtr received; ///< Received data put here
IOFetch::Callback* callback; ///< Called on I/O Completion
@@ -121,7 +119,6 @@ struct IOFetchData {
/// \param proto Either IOFetch::TCP or IOFetch::UDP.
/// \param service I/O Service object to handle the asynchronous
/// operations.
- /// \param query DNS question to send to the upstream server.
/// \param address IP address of upstream server
/// \param port Port to use for the query
/// \param buff Output buffer into which the response (in wire format)
@@ -133,8 +130,8 @@ struct IOFetchData {
///
/// TODO: May need to alter constructor (see comment 4 in Trac ticket #554)
IOFetchData(IOFetch::Protocol proto, IOService& service,
- const isc::dns::Question& query, const IOAddress& address,
- uint16_t port, OutputBufferPtr& buff, IOFetch::Callback* cb, int wait)
+ const IOAddress& address, uint16_t port, OutputBufferPtr& buff,
+ IOFetch::Callback* cb, int wait)
:
socket((proto == IOFetch::UDP) ?
static_cast<IOAsioSocket<IOFetch>*>(
@@ -150,7 +147,6 @@ struct IOFetchData {
static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
),
- question(query),
msgbuf(new OutputBuffer(512)),
received(buff),
callback(cb),
@@ -185,10 +181,10 @@ struct IOFetchData {
IOFetch::IOFetch(Protocol protocol, IOService& service,
const isc::dns::Question& question, const IOAddress& address, uint16_t port,
OutputBufferPtr& buff, Callback* cb, int wait)
- :
- data_(new IOFetchData(protocol, service, question, address,
- port, buff, cb, wait))
{
+ MessagePtr query_msg(new Message(Message::RENDER));
+ initIOFetch(query_msg, protocol, service, question, address, port, buff,
+ cb, wait);
}
IOFetch::IOFetch(Protocol protocol, IOService& service,
@@ -196,14 +192,56 @@ IOFetch::IOFetch(Protocol protocol, IOService& service,
OutputBufferPtr& buff, Callback* cb, int wait)
:
data_(new IOFetchData(protocol, service,
- isc::dns::Question(isc::dns::Name("dummy.example.org"),
- isc::dns::RRClass::IN(), isc::dns::RRType::A()),
address, port, buff, cb, wait))
{
data_->msgbuf = outpkt;
data_->packet = true;
}
+IOFetch::IOFetch(Protocol protocol, IOService& service,
+ ConstMessagePtr query_message, const IOAddress& address, uint16_t port,
+ OutputBufferPtr& buff, Callback* cb, int wait)
+{
+ MessagePtr msg(new Message(Message::RENDER));
+
+ msg->setHeaderFlag(Message::HEADERFLAG_RD,
+ query_message->getHeaderFlag(Message::HEADERFLAG_RD));
+ msg->setHeaderFlag(Message::HEADERFLAG_CD,
+ query_message->getHeaderFlag(Message::HEADERFLAG_CD));
+
+ ConstEDNSPtr edns(query_message->getEDNS());
+ const bool dnssec_ok = edns && edns->getDNSSECAwareness();
+ if (edns) {
+ EDNSPtr edns_response(new EDNS());
+ edns_response->setDNSSECAwareness(dnssec_ok);
+ // TODO: We should make our own edns bufsize length configurable
+ edns_response->setUDPSize(Message::DEFAULT_MAX_EDNS0_UDPSIZE);
+ msg->setEDNS(edns_response);
+ }
+
+ initIOFetch(msg, protocol, service,
+ **(query_message->beginQuestion()),
+ address, port, buff, cb, wait);
+}
+
+void
+IOFetch::initIOFetch(MessagePtr& query_msg, Protocol protocol, IOService& service,
+ const isc::dns::Question& question,
+ const IOAddress& address, uint16_t port,
+ OutputBufferPtr& buff, Callback* cb, int wait)
+{
+ data_ = boost::shared_ptr<IOFetchData>(new IOFetchData(
+ protocol, service, address, port, buff, cb, wait));
+
+ query_msg->setQid(data_->qid);
+ query_msg->setOpcode(Opcode::QUERY());
+ query_msg->setRcode(Rcode::NOERROR());
+ query_msg->setHeaderFlag(Message::HEADERFLAG_RD);
+ query_msg->addQuestion(question);
+ MessageRenderer renderer(*data_->msgbuf);
+ query_msg->toWire(renderer);
+}
+
// Return protocol in use.
IOFetch::Protocol
@@ -235,17 +273,7 @@ IOFetch::operator()(asio::error_code ec, size_t length) {
// first two bytes of the packet).
data_->msgbuf->writeUint16At(data_->qid, 0);
- } else {
- // A question was given, construct the packet
- Message msg(Message::RENDER);
- msg.setQid(data_->qid);
- msg.setOpcode(Opcode::QUERY());
- msg.setRcode(Rcode::NOERROR());
- msg.setHeaderFlag(Message::HEADERFLAG_RD);
- msg.addQuestion(data_->question);
- MessageRenderer renderer(*data_->msgbuf);
- msg.toWire(renderer);
- }
+ }
}
// If we timeout, we stop, which will can cancel outstanding I/Os and
@@ -406,4 +434,3 @@ void IOFetch::logIOFailure(asio::error_code ec) {
} // namespace asiodns
} // namespace isc {
-
diff --git a/src/lib/asiodns/io_fetch.h b/src/lib/asiodns/io_fetch.h
index 98c917d..9626ffe 100644
--- a/src/lib/asiodns/io_fetch.h
+++ b/src/lib/asiodns/io_fetch.h
@@ -29,6 +29,7 @@
#include <util/buffer.h>
#include <dns/question.h>
+#include <dns/message.h>
namespace isc {
namespace asiodns {
@@ -136,6 +137,20 @@ public:
uint16_t port, isc::util::OutputBufferPtr& buff, Callback* cb,
int wait = -1);
+ /// \brief Constructor
+ /// This constructor has one parameter "query_message", which
+ /// is the shared_ptr to a full query message. It's different
+ /// with above contructor which has only question section. All
+ /// other parameters are same.
+ ///
+ /// \param query_message the shared_ptr to a full query message
+ /// got from a query client.
+ IOFetch(Protocol protocol, isc::asiolink::IOService& service,
+ isc::dns::ConstMessagePtr query_message,
+ const isc::asiolink::IOAddress& address,
+ uint16_t port, isc::util::OutputBufferPtr& buff, Callback* cb,
+ int wait = -1);
+
/// \brief Constructor.
///
/// Creates the object that will handle the upstream fetch.
@@ -184,6 +199,15 @@ public:
void stop(Result reason = STOPPED);
private:
+ /// \brief IOFetch Initialization Function.
+ /// All the parameters are same with the constructor, except
+ /// parameter "query_message"
+ /// \param query_message the message to be sent out.
+ void initIOFetch(isc::dns::MessagePtr& query_message, Protocol protocol,
+ isc::asiolink::IOService& service, const isc::dns::Question& question,
+ const isc::asiolink::IOAddress& address, uint16_t port,
+ isc::util::OutputBufferPtr& buff, Callback* cb, int wait);
+
/// \brief Log I/O Failure
///
/// Records an I/O failure to the log file
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index 26a24ef..af4f679 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -10,6 +10,12 @@ if USE_STATIC_LINK
AM_LDFLAGS = -static
endif
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
CLEANFILES = *.gcno *.gcda
TESTS =
diff --git a/src/lib/asiolink/tests/interval_timer_unittest.cc b/src/lib/asiolink/tests/interval_timer_unittest.cc
index c24e60e..8e8ef81 100644
--- a/src/lib/asiolink/tests/interval_timer_unittest.cc
+++ b/src/lib/asiolink/tests/interval_timer_unittest.cc
@@ -18,7 +18,7 @@
#include <asio.hpp>
#include <asiolink/asiolink.h>
-#include <boost/date_time/posix_time/posix_time_types.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
namespace {
// TODO: Consider this margin
@@ -166,16 +166,22 @@ TEST_F(IntervalTimerTest, startIntervalTimer) {
io_service_.run();
// reaches here after timer expired
// delta: difference between elapsed time and 100 milliseconds.
+ boost::posix_time::time_duration test_runtime =
+ boost::posix_time::microsec_clock::universal_time() - start;
+ EXPECT_FALSE(test_runtime.is_negative()) <<
+ "test duration " << test_runtime <<
+ " negative - clock skew?";
boost::posix_time::time_duration delta =
- (boost::posix_time::microsec_clock::universal_time() - start)
- - boost::posix_time::millisec(100);
+ test_runtime - boost::posix_time::milliseconds(100);
if (delta.is_negative()) {
delta.invert_sign();
}
// expect TimerCallBack is called; timer_called_ is true
EXPECT_TRUE(timer_called_);
// expect interval is 100 milliseconds +/- TIMER_MARGIN_MSEC.
- EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+ EXPECT_TRUE(delta < TIMER_MARGIN_MSEC) <<
+ "delta " << delta.total_milliseconds() << "msec " <<
+ ">= " << TIMER_MARGIN_MSEC.total_milliseconds();
}
TEST_F(IntervalTimerTest, destructIntervalTimer) {
@@ -283,14 +289,20 @@ TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
// + 400 milliseconds for TimerCallBackOverwriter (stop)
// = 800 milliseconds.
// delta: difference between elapsed time and 400 + 100 milliseconds
+ boost::posix_time::time_duration test_runtime =
+ boost::posix_time::microsec_clock::universal_time() - start;
+ EXPECT_FALSE(test_runtime.is_negative()) <<
+ "test duration " << test_runtime <<
+ " negative - clock skew?";
boost::posix_time::time_duration delta =
- (boost::posix_time::microsec_clock::universal_time() - start)
- - boost::posix_time::millisec(400 + 100);
+ test_runtime - boost::posix_time::milliseconds(400 + 100);
if (delta.is_negative()) {
delta.invert_sign();
}
// expect callback function is updated: TimerCallBack is called
EXPECT_TRUE(timer_called_);
// expect interval is updated
- EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+ EXPECT_TRUE(delta < TIMER_MARGIN_MSEC) <<
+ "delta " << delta.total_milliseconds() << " msec " <<
+ ">= " << TIMER_MARGIN_MSEC.total_milliseconds();
}
diff --git a/src/lib/config/Makefile.am b/src/lib/config/Makefile.am
index 99e8c86..52337ad 100644
--- a/src/lib/config/Makefile.am
+++ b/src/lib/config/Makefile.am
@@ -2,9 +2,24 @@ SUBDIRS = . tests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
AM_CPPFLAGS += $(BOOST_INCLUDES)
+# Define rule to build logging source files from message file
+configdef.h configdef.cc: configdef.mes
+ $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/config/configdef.mes
+
+BUILT_SOURCES = configdef.h configdef.cc
+
lib_LTLIBRARIES = libcfgclient.la
-libcfgclient_la_SOURCES = config_data.h config_data.cc module_spec.h module_spec.cc ccsession.cc ccsession.h
+libcfgclient_la_SOURCES = config_data.h config_data.cc
+libcfgclient_la_SOURCES += module_spec.h module_spec.cc
+libcfgclient_la_SOURCES += ccsession.cc ccsession.h
+libcfgclient_la_SOURCES += config_log.h config_log.cc
+
+nodist_libcfgclient_la_SOURCES = configdef.h configdef.cc
+
+# The message file should be in the distribution.
+EXTRA_DIST = configdef.mes
-CLEANFILES = *.gcno *.gcda
+CLEANFILES = *.gcno *.gcda configdef.h configdef.cc
diff --git a/src/lib/config/ccsession.cc b/src/lib/config/ccsession.cc
index 69621a4..45710e3 100644
--- a/src/lib/config/ccsession.cc
+++ b/src/lib/config/ccsession.cc
@@ -12,12 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-//
-// todo: generalize this and make it into a specific API for all modules
-// to use (i.e. connect to cc, send config and commands, get config,
-// react on config change announcements)
-//
-
#include <config.h>
#include <stdexcept>
@@ -38,6 +32,7 @@
#include <cc/session.h>
#include <exceptions/exceptions.h>
+#include <config/config_log.h>
#include <config/ccsession.h>
using namespace std;
@@ -164,18 +159,18 @@ ModuleCCSession::readModuleSpecification(const std::string& filename) {
// this file should be declared in a @something@ directive
file.open(filename.c_str());
if (!file) {
- cout << "error opening " << filename << ": " << strerror(errno) << endl;
- exit(1);
+ LOG_ERROR(config_logger, CONFIG_FOPEN_ERR).arg(filename).arg(strerror(errno));
+ isc_throw(CCSessionInitError, strerror(errno));
}
try {
module_spec = moduleSpecFromFile(file, true);
} catch (const JSONError& pe) {
- cout << "Error parsing module specification file: " << pe.what() << endl;
- exit(1);
+ LOG_ERROR(config_logger, CONFIG_JSON_PARSE).arg(filename).arg(pe.what());
+ isc_throw(CCSessionInitError, pe.what());
} catch (const ModuleSpecError& dde) {
- cout << "Error reading module specification file: " << dde.what() << endl;
- exit(1);
+ LOG_ERROR(config_logger, CONFIG_MODULE_SPEC).arg(filename).arg(dde.what());
+ isc_throw(CCSessionInitError, dde.what());
}
file.close();
return (module_spec);
@@ -223,7 +218,8 @@ ModuleCCSession::ModuleCCSession(
int rcode;
ConstElementPtr err = parseAnswer(rcode, answer);
if (rcode != 0) {
- std::cerr << "[" << module_name_ << "] Error in specification: " << answer << std::endl;
+ LOG_ERROR(config_logger, CONFIG_MANAGER_MOD_SPEC).arg(answer->str());
+ isc_throw(CCSessionInitError, answer->str());
}
setLocalConfig(Element::fromJSON("{}"));
@@ -236,7 +232,8 @@ ModuleCCSession::ModuleCCSession(
if (rcode == 0) {
handleConfigUpdate(new_config);
} else {
- std::cerr << "[" << module_name_ << "] Error getting config: " << new_config << std::endl;
+ LOG_ERROR(config_logger, CONFIG_MANAGER_CONFIG).arg(new_config->str());
+ isc_throw(CCSessionInitError, answer->str());
}
}
@@ -348,24 +345,51 @@ ModuleCCSession::checkCommand() {
answer = checkModuleCommand(cmd_str, target_module, arg);
}
} catch (const CCSessionError& re) {
- // TODO: Once we have logging and timeouts, we should not
- // answer here (potential interference)
- answer = createAnswer(1, re.what());
+ LOG_ERROR(config_logger, CONFIG_CCSESSION_MSG).arg(re.what());
}
if (!isNull(answer)) {
session_.reply(routing, answer);
}
}
-
+
return (0);
}
std::string
-ModuleCCSession::addRemoteConfig(const std::string& spec_file_name) {
- ModuleSpec rmod_spec = readModuleSpecification(spec_file_name);
- std::string module_name = rmod_spec.getFullSpec()->get("module_name")->stringValue();
+ModuleCCSession::addRemoteConfig(const std::string& spec_name,
+ void (*handler)(const std::string& module,
+ ConstElementPtr),
+ bool spec_is_filename)
+{
+ std::string module_name;
+ ModuleSpec rmod_spec;
+ if (spec_is_filename) {
+ // It's a file name, so load it
+ rmod_spec = readModuleSpecification(spec_name);
+ module_name =
+ rmod_spec.getFullSpec()->get("module_name")->stringValue();
+ } else {
+ // It's module name, request it from config manager
+ ConstElementPtr cmd = Element::fromJSON("{ \"command\": ["
+ "\"get_module_spec\","
+ "{\"module_name\": \"" +
+ module_name + "\"} ] }");
+ unsigned int seq = session_.group_sendmsg(cmd, "ConfigManager");
+ ConstElementPtr env, answer;
+ session_.group_recvmsg(env, answer, false, seq);
+ int rcode;
+ ConstElementPtr spec_data = parseAnswer(rcode, answer);
+ if (rcode == 0 && spec_data) {
+ rmod_spec = ModuleSpec(spec_data);
+ module_name = spec_name;
+ if (module_name != rmod_spec.getModuleName()) {
+ isc_throw(CCSessionError, "Module name mismatch");
+ }
+ } else {
+ isc_throw(CCSessionError, "Error getting config for " + module_name + ": " + answer->str());
+ }
+ }
ConfigData rmod_config = ConfigData(rmod_spec);
- session_.subscribe(module_name);
// Get the current configuration values for that module
ConstElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name + "\"} ] }");
@@ -375,8 +399,9 @@ ModuleCCSession::addRemoteConfig(const std::string& spec_file_name) {
session_.group_recvmsg(env, answer, false, seq);
int rcode;
ConstElementPtr new_config = parseAnswer(rcode, answer);
+ ElementPtr local_config;
if (rcode == 0 && new_config) {
- ElementPtr local_config = rmod_config.getLocalConfig();
+ local_config = rmod_config.getLocalConfig();
isc::data::merge(local_config, new_config);
rmod_config.setLocalConfig(local_config);
} else {
@@ -385,6 +410,11 @@ ModuleCCSession::addRemoteConfig(const std::string& spec_file_name) {
// all ok, add it
remote_module_configs_[module_name] = rmod_config;
+ if (handler) {
+ remote_module_handlers_[module_name] = handler;
+ handler(module_name, local_config);
+ }
+ session_.subscribe(module_name);
return (module_name);
}
@@ -395,6 +425,7 @@ ModuleCCSession::removeRemoteConfig(const std::string& module_name) {
it = remote_module_configs_.find(module_name);
if (it != remote_module_configs_.end()) {
remote_module_configs_.erase(it);
+ remote_module_handlers_.erase(module_name);
session_.unsubscribe(module_name);
}
}
@@ -424,6 +455,11 @@ ModuleCCSession::updateRemoteConfig(const std::string& module_name,
if (it != remote_module_configs_.end()) {
ElementPtr rconf = (*it).second.getLocalConfig();
isc::data::merge(rconf, new_config);
+ std::map<std::string, RemoteHandler>::iterator hit =
+ remote_module_handlers_.find(module_name);
+ if (hit != remote_module_handlers_.end()) {
+ hit->second(module_name, new_config);
+ }
}
}
diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h
index d8d1eeb..c845b8f 100644
--- a/src/lib/config/ccsession.h
+++ b/src/lib/config/ccsession.h
@@ -135,6 +135,15 @@ public:
};
///
+/// \brief This exception is thrown if the constructor fails
+///
+class CCSessionInitError : public isc::Exception {
+public:
+ CCSessionInitError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+///
/// \brief This module keeps a connection to the command channel,
/// holds configuration information, and handles messages from
/// the command channel
@@ -154,6 +163,11 @@ public:
* AbstractSession without establishing the session.
* Note: the design decision on who is responsible for establishing the
* session is in flux, and may change in near future.
+ *
+ * \exception CCSessionInitError when the initialization fails,
+ * either because the file cannot be read or there is
+ * a communication problem with the config manager.
+ *
* @param command_handler A callback function pointer to be called when
* a control command from a remote agent needs to be performed on the
* local module.
@@ -220,24 +234,43 @@ public:
/**
* Gives access to the configuration values of a different module
* Once this function has been called with the name of the specification
- * file of the module you want the configuration of, you can use
+ * file or the module you want the configuration of, you can use
* \c getRemoteConfigValue() to get a specific setting.
- * Changes are automatically updated, but you cannot specify handlers
- * for those changes, must use \c getRemoteConfigValue() to get a value
- * This function will subscribe to the relevant module channel.
+ * Changes are automatically updated, and you can specify handlers
+ * for those changes. This function will subscribe to the relevant module
+ * channel.
*
- * \param spec_file_name The path to the specification file of
- * the module we want to have configuration
- * values from
+ * \param spec_name This specifies the module to add. It is either a
+ * filename of the spec file to use or a name of module
+ * (in case it's a module name, the spec data is
+ * downloaded from the configuration manager, therefore
+ * the configuration manager must know it). If
+ * spec_is_filenabe is true (the default), then a
+ * filename is assumed, otherwise a module name.
+ * \param handler The handler function called whenever there's a change.
+ * Called once initally from this function. May be NULL
+ * if you don't want any handler to be called and you're
+ * fine with requesting the data through
+ * getRemoteConfigValue() each time.
+ *
+ * The handler should not throw, or it'll fall trough and
+ * the exception will get into strange places, probably
+ * aborting the application.
+ * \param spec_is_filename Says if spec_name is filename or module name.
* \return The name of the module specified in the given specification
* file
*/
- std::string addRemoteConfig(const std::string& spec_file_name);
+ std::string addRemoteConfig(const std::string& spec_name,
+ void (*handler)(const std::string& module_name,
+ isc::data::ConstElementPtr
+ update) = NULL,
+ bool spec_is_filename = true);
/**
* Removes the module with the given name from the remote config
* settings. If the module was not added with \c addRemoteConfig(),
- * nothing happens.
+ * nothing happens. If there was a handler for this config, it is
+ * removed as well.
*/
void removeRemoteConfig(const std::string& module_name);
@@ -282,7 +315,11 @@ private:
const std::string& command,
isc::data::ConstElementPtr args);
+ typedef void (*RemoteHandler)(const std::string&,
+ isc::data::ConstElementPtr);
std::map<std::string, ConfigData> remote_module_configs_;
+ std::map<std::string, RemoteHandler> remote_module_handlers_;
+
void updateRemoteConfig(const std::string& module_name,
isc::data::ConstElementPtr new_config);
};
diff --git a/src/lib/config/config_log.cc b/src/lib/config/config_log.cc
new file mode 100644
index 0000000..672b9f1
--- /dev/null
+++ b/src/lib/config/config_log.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// Defines the logger used by the config lib
+
+#include "config/config_log.h"
+
+namespace isc {
+namespace config {
+
+isc::log::Logger config_logger("config");
+
+} // namespace nsas
+} // namespace isc
+
diff --git a/src/lib/config/config_log.h b/src/lib/config/config_log.h
new file mode 100644
index 0000000..22e5a5c
--- /dev/null
+++ b/src/lib/config/config_log.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 __CONFIG_LOG__H
+#define __CONFIG_LOG__H
+
+#include <log/macros.h>
+#include "configdef.h"
+
+namespace isc {
+namespace config {
+
+/// \brief Config Logging
+///
+/// Defines logger object for config log messages
+
+/// \brief Config Logger
+///
+/// Define the logger used to log messages. We could define it in multiple
+/// modules, but defining in a single module and linking to it saves time and
+/// space.
+extern isc::log::Logger config_logger; // isc::config::config_logger is the CONFIG logger
+
+} // namespace config
+} // namespace isc
+
+#endif // __CONFIG_LOG__H
diff --git a/src/lib/config/configdef.mes b/src/lib/config/configdef.mes
new file mode 100644
index 0000000..4c3c991
--- /dev/null
+++ b/src/lib/config/configdef.mes
@@ -0,0 +1,50 @@
+# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$PREFIX CONFIG_
+$NAMESPACE isc::config
+
+% FOPEN_ERR error opening %1: %2
+There was an error opening the given file.
+
+% JSON_PARSE JSON parse error in %1: %2
+There was a parse error in the JSON file. The given file does not appear
+to be in valid JSON format. Please verify that the filename is correct
+and that the contents are valid JSON.
+
+% MODULE_SPEC module specification error in %1: %2
+The given file does not appear to be a valid specification file. Please
+verify that the filename is correct and that its contents are a valid
+BIND10 module specification.
+
+% MANAGER_MOD_SPEC module specification not accepted by cfgmgr: %1
+The module specification file for this module was rejected by the
+configuration manager. The full error message answer from the
+configuration manager is appended to the log error. The most likely
+cause is that the module is of a different (specification file) version
+than the running configuration manager.
+
+% MANAGER_CONFIG error getting configuration from cfgmgr: %1
+The configuration manager returned an error when this module requested
+the configuration. The full error message answer from the configuration
+manager is appended to the log error. The most likely cause is that
+the module is of a different (command specification) version than the
+running configuration manager.
+
+% CCSESSION_MSG error in CC session message: %1
+There was a problem with an incoming message on the command and control
+channel. The message does not appear to be a valid command, and is
+missing a required element or contains an unknown data format. This
+most likely means that another BIND10 module is sending a bad message.
+The message itself is ignored by this module.
diff --git a/src/lib/config/tests/Makefile.am b/src/lib/config/tests/Makefile.am
index acb5a09..7153e09 100644
--- a/src/lib/config/tests/Makefile.am
+++ b/src/lib/config/tests/Makefile.am
@@ -22,10 +22,11 @@ run_unittests_SOURCES = ccsession_unittests.cc module_spec_unittests.cc config_d
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
run_unittests_LDADD = $(GTEST_LDADD)
-run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
run_unittests_LDADD += libfake_session.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
run_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
endif
diff --git a/src/lib/config/tests/ccsession_unittests.cc b/src/lib/config/tests/ccsession_unittests.cc
index 43663bd..3564d4b 100644
--- a/src/lib/config/tests/ccsession_unittests.cc
+++ b/src/lib/config/tests/ccsession_unittests.cc
@@ -264,10 +264,7 @@ TEST_F(CCSessionTest, checkCommand) {
session.addMessage(el("{ \"command\": \"bad_command\" }"), "Spec29", "*");
result = mccs.checkCommand();
- EXPECT_EQ(1, session.getMsgQueue()->size());
- msg = session.getFirstMessage(group, to);
- EXPECT_EQ("{ \"result\": [ 1, \"Command part in command message missing, empty, or not a list\" ] }", msg->str());
- EXPECT_EQ(0, result);
+ EXPECT_EQ(0, session.getMsgQueue()->size());
session.addMessage(el("{ \"command\": [ \"bad_command\" ] }"),
"Spec29", "*");
@@ -349,6 +346,18 @@ TEST_F(CCSessionTest, checkCommand2) {
EXPECT_EQ(2, mccs.getValue("item1")->intValue());
}
+std::string remote_module_name;
+int remote_item1(0);
+ConstElementPtr remote_config;
+ModuleCCSession *remote_mccs(NULL);
+
+void remoteHandler(const std::string& module_name, ConstElementPtr config) {
+ remote_module_name = module_name;
+ remote_item1 = remote_mccs->getRemoteConfigValue("Spec2", "item1")->
+ intValue();
+ remote_config = config;
+}
+
TEST_F(CCSessionTest, remoteConfig) {
std::string module_name;
int item1;
@@ -395,6 +404,91 @@ TEST_F(CCSessionTest, remoteConfig) {
session.getMessages()->add(createAnswer());
EXPECT_THROW(mccs.addRemoteConfig(ccspecfile("spec2.spec")), CCSessionError);
+
+ {
+ SCOPED_TRACE("With module name");
+ // Try adding it with downloading the spec from config manager
+ ModuleSpec spec(moduleSpecFromFile(ccspecfile("spec2.spec")));
+ session.getMessages()->add(createAnswer(0, spec.getFullSpec()));
+ session.getMessages()->add(createAnswer(0, el("{}")));
+
+ EXPECT_NO_THROW(module_name = mccs.addRemoteConfig("Spec2", NULL,
+ false));
+
+ EXPECT_EQ("Spec2", module_name);
+ EXPECT_NO_THROW(item1 =
+ mccs.getRemoteConfigValue(module_name,
+ "item1")->intValue());
+ EXPECT_EQ(1, item1);
+
+ mccs.removeRemoteConfig(module_name);
+ }
+
+ {
+ // Try adding it with a handler.
+ // Pass non-default value to see the handler is called after
+ // downloading the configuration, not too soon.
+ SCOPED_TRACE("With handler");
+ session.getMessages()->add(createAnswer(0, el("{ \"item1\": 2 }")));
+ remote_mccs = &mccs;
+ module_name = mccs.addRemoteConfig(ccspecfile("spec2.spec"),
+ remoteHandler);
+ {
+ SCOPED_TRACE("Before update");
+ EXPECT_EQ("Spec2", module_name);
+ EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
+ // Now check the parameters the remote handler stored
+ // This also checks it was called
+ EXPECT_EQ("Spec2", remote_module_name);
+ remote_module_name = "";
+ EXPECT_EQ(2, remote_item1);
+ remote_item1 = 0;
+ if (remote_config) {
+ EXPECT_EQ(2, remote_config->get("item1")->intValue());
+ } else {
+ ADD_FAILURE() << "Remote config not set";
+ }
+ remote_config.reset();
+ // Make sure normal way still works
+ item1 = mccs.getRemoteConfigValue(module_name,
+ "item1")->intValue();
+ EXPECT_EQ(2, item1);
+ }
+
+ {
+ SCOPED_TRACE("After update");
+ session.addMessage(el("{ \"command\": [ \"config_update\", "
+ "{ \"item1\": 3 } ] }"), module_name, "*");
+ mccs.checkCommand();
+ EXPECT_EQ("Spec2", remote_module_name);
+ remote_module_name = "";
+ EXPECT_EQ(3, remote_item1);
+ remote_item1 = 0;
+ if (remote_config) {
+ EXPECT_EQ(3, remote_config->get("item1")->intValue());
+ } else {
+ ADD_FAILURE() << "Remote config not set";
+ }
+ remote_config.reset();
+ // Make sure normal way still works
+ item1 = mccs.getRemoteConfigValue(module_name,
+ "item1")->intValue();
+ EXPECT_EQ(3, item1);
+ }
+
+ remote_mccs = NULL;
+ mccs.removeRemoteConfig(module_name);
+
+ {
+ SCOPED_TRACE("When removed");
+ // Make sure nothing is called any more
+ session.addMessage(el("{ \"command\": [ \"config_update\", "
+ "{ \"item1\": 4 } ] }"), module_name, "*");
+ EXPECT_EQ("", remote_module_name);
+ EXPECT_EQ(0, remote_item1);
+ EXPECT_FALSE(remote_config);
+ }
+ }
}
TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
@@ -432,4 +526,24 @@ TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
EXPECT_EQ(0, session.getMsgQueue()->size());
}
+TEST_F(CCSessionTest, initializationFail) {
+ // bad specification
+ EXPECT_THROW(ModuleCCSession(ccspecfile("spec8.spec"), session,
+ NULL, NULL), CCSessionInitError);
+
+ // file that does not exist
+ EXPECT_THROW(ModuleCCSession(ccspecfile("does_not_exist_spec"),
+ session, NULL, NULL),
+ CCSessionInitError);
+
+
+ session.getMessages()->add(createAnswer(1, el("\"just an error\"")));
+
+ EXPECT_FALSE(session.haveSubscription("Spec29", "*"));
+ EXPECT_THROW(ModuleCCSession(ccspecfile("spec29.spec"), session,
+ my_config_handler, my_command_handler),
+ CCSessionInitError);
+ EXPECT_TRUE(session.haveSubscription("Spec29", "*"));
+}
+
}
diff --git a/src/lib/config/tests/run_unittests.cc b/src/lib/config/tests/run_unittests.cc
index eeef955..19d2be1 100644
--- a/src/lib/config/tests/run_unittests.cc
+++ b/src/lib/config/tests/run_unittests.cc
@@ -14,9 +14,11 @@
#include <gtest/gtest.h>
#include <util/unittests/run_all.h>
+#include <log/logger_support.h>
int
main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
return (isc::util::unittests::run_all());
}
diff --git a/src/lib/cryptolink/crypto_hmac.cc b/src/lib/cryptolink/crypto_hmac.cc
index 9c35f60..14b43b3 100644
--- a/src/lib/cryptolink/crypto_hmac.cc
+++ b/src/lib/cryptolink/crypto_hmac.cc
@@ -17,6 +17,7 @@
#include <boost/scoped_ptr.hpp>
+#include <botan/version.h>
#include <botan/botan.h>
#include <botan/hmac.h>
#include <botan/hash.h>
@@ -35,6 +36,15 @@ getBotanHashAlgorithmName(isc::cryptolink::HashAlgorithm algorithm) {
case isc::cryptolink::SHA256:
return ("SHA-256");
break;
+ case isc::cryptolink::SHA224:
+ return ("SHA-224");
+ break;
+ case isc::cryptolink::SHA384:
+ return ("SHA-384");
+ break;
+ case isc::cryptolink::SHA512:
+ return ("SHA-512");
+ break;
case isc::cryptolink::UNKNOWN_HASH:
return ("Unknown");
break;
@@ -60,7 +70,8 @@ public:
getBotanHashAlgorithmName(hash_algorithm));
} catch (const Botan::Algorithm_Not_Found&) {
isc_throw(isc::cryptolink::UnsupportedAlgorithm,
- "Unknown hash algorithm: " + hash_algorithm);
+ "Unknown hash algorithm: " <<
+ static_cast<int>(hash_algorithm));
} catch (const Botan::Exception& exc) {
isc_throw(isc::cryptolink::LibraryError, exc.what());
}
@@ -70,12 +81,28 @@ public:
// If the key length is larger than the block size, we hash the
// key itself first.
try {
- if (secret_len > hash->HASH_BLOCK_SIZE) {
+ // use a temp var so we don't have blocks spanning
+ // preprocessor directives
+#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0)
+ size_t block_length = hash->hash_block_size();
+#elif BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,8,0)
+ size_t block_length = hash->HASH_BLOCK_SIZE;
+#else
+#error "Unsupported Botan version (need 1.8 or higher)"
+ // added to suppress irrelevant compiler errors
+ size_t block_length = 0;
+#endif
+ if (secret_len > block_length) {
Botan::SecureVector<Botan::byte> hashed_key =
hash->process(static_cast<const Botan::byte*>(secret),
secret_len);
hmac_->set_key(hashed_key.begin(), hashed_key.size());
} else {
+ // Botan 1.8 considers len 0 a bad key. 1.9 does not,
+ // but we won't accept it anyway, and fail early
+ if (secret_len == 0) {
+ isc_throw(BadKey, "Bad HMAC secret length: 0");
+ }
hmac_->set_key(static_cast<const Botan::byte*>(secret),
secret_len);
}
@@ -89,7 +116,15 @@ public:
~HMACImpl() { }
size_t getOutputLength() const {
+#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0)
+ return (hmac_->output_length());
+#elif BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,8,0)
return (hmac_->OUTPUT_LENGTH);
+#else
+#error "Unsupported Botan version (need 1.8 or higher)"
+ // added to suppress irrelevant compiler errors
+ return 0;
+#endif
}
void update(const void* data, const size_t len) {
@@ -144,8 +179,17 @@ public:
// Botan's verify_mac checks if len matches the output_length,
// which causes it to fail for truncated signatures, so we do
// the check ourselves
+ // SEE BELOW FOR TEMPORARY CHANGE
try {
Botan::SecureVector<Botan::byte> our_mac = hmac_->final();
+ if (len < getOutputLength()) {
+ // Currently we don't support truncated signature. To avoid
+ // validating too short signature accidently, we enforce the
+ // standard signature size for the moment.
+ // Once we support truncation correctly, this if-clause should
+ // (and the capitalized comment above) be removed.
+ return (false);
+ }
if (len == 0 || len > getOutputLength()) {
len = getOutputLength();
}
diff --git a/src/lib/cryptolink/cryptolink.h b/src/lib/cryptolink/cryptolink.h
index 1583136..d0f7d38 100644
--- a/src/lib/cryptolink/cryptolink.h
+++ b/src/lib/cryptolink/cryptolink.h
@@ -29,15 +29,19 @@ namespace cryptolink {
/// \brief Hash algorithm identifiers
enum HashAlgorithm {
- MD5 = 0, ///< MD5
- SHA1 = 1, ///< SHA-1
- SHA256 = 2, ///< SHA-256
- UNKNOWN_HASH = 3 ///< This value can be used in conversion
+ UNKNOWN_HASH = 0, ///< This value can be used in conversion
/// functions, to be returned when the
/// input is unknown (but a value MUST be
/// returned), for instance when the input
/// is a Name or a string, and the return
/// value is a HashAlgorithm.
+ MD5 = 1, ///< MD5
+ SHA1 = 2, ///< SHA-1
+ SHA256 = 3, ///< SHA-256
+ SHA224 = 4, ///< SHA-224
+ SHA384 = 5, ///< SHA-384
+ SHA512 = 6 ///< SHA-512
+
};
// Forward declaration for createHMAC()
diff --git a/src/lib/cryptolink/tests/crypto_unittests.cc b/src/lib/cryptolink/tests/crypto_unittests.cc
index 65018c6..339eb1b 100644
--- a/src/lib/cryptolink/tests/crypto_unittests.cc
+++ b/src/lib/cryptolink/tests/crypto_unittests.cc
@@ -233,18 +233,6 @@ TEST(CryptoLinkTest, HMAC_MD5_RFC2202_SIGN) {
0x79 };
doHMACTest(data4, secret4, 25, MD5, hmac_expected4, 16);
- const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
- 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
- 0x0c, 0x0c, 0x0c, 0x0c };
- const uint8_t hmac_expected5[] = { 0x56, 0x46, 0x1e, 0xf2, 0x34,
- 0x2e, 0xdc, 0x00, 0xf9, 0xba,
- 0xb9, 0x95, 0x69, 0x0e, 0xfd,
- 0x4c };
- doHMACTest("Test With Truncation", secret5, 16, MD5,
- hmac_expected5, 16);
- doHMACTest("Test With Truncation", secret5, 16, MD5,
- hmac_expected5, 12);
-
const uint8_t hmac_expected6[] = { 0x6b, 0x1a, 0xb7, 0xfe, 0x4b,
0xd7, 0xbf, 0x8f, 0x0b, 0x62,
0xe6, 0xce, 0x61, 0xb9, 0xd0,
@@ -261,6 +249,21 @@ TEST(CryptoLinkTest, HMAC_MD5_RFC2202_SIGN) {
std::string(80, 0xaa).c_str(), 80, MD5, hmac_expected7, 16);
}
+// Temporarily disabled
+TEST(CryptoLinkTest, DISABLED_HMAC_MD5_RFC2202_SIGN_TRUNCATED) {
+ const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c };
+ const uint8_t hmac_expected5[] = { 0x56, 0x46, 0x1e, 0xf2, 0x34,
+ 0x2e, 0xdc, 0x00, 0xf9, 0xba,
+ 0xb9, 0x95, 0x69, 0x0e, 0xfd,
+ 0x4c };
+ doHMACTest("Test With Truncation", secret5, 16, MD5,
+ hmac_expected5, 16);
+ doHMACTest("Test With Truncation", secret5, 16, MD5,
+ hmac_expected5, 12);
+}
+
//
// Test values taken from RFC 2202
//
@@ -302,19 +305,6 @@ TEST(CryptoLinkTest, HMAC_SHA1_RFC2202_SIGN) {
0x6c, 0x2d, 0x72, 0x35, 0xda };
doHMACTest(std::string(50, 0xcd), secret4, 25, SHA1, hmac_expected4, 20);
- const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
- 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
- 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
- 0x0c, 0x0c };
- const uint8_t hmac_expected5[] = { 0x4c, 0x1a, 0x03, 0x42, 0x4b,
- 0x55, 0xe0, 0x7f, 0xe7, 0xf2,
- 0x7b, 0xe1, 0xd5, 0x8b, 0xb9,
- 0x32, 0x4a, 0x9a, 0x5a, 0x04 };
- doHMACTest("Test With Truncation", secret5, 20, SHA1,
- hmac_expected5, 20);
- doHMACTest("Test With Truncation", secret5, 20, SHA1,
- hmac_expected5, 12);
-
const uint8_t hmac_expected6[] = { 0xaa, 0x4a, 0xe5, 0xe1, 0x52,
0x72, 0xd0, 0x0e, 0x95, 0x70,
0x56, 0x37, 0xce, 0x8a, 0x3b,
@@ -331,22 +321,103 @@ TEST(CryptoLinkTest, HMAC_SHA1_RFC2202_SIGN) {
std::string(80, 0xaa).c_str(), 80, SHA1, hmac_expected7, 20);
}
+// Temporarily disabled
+TEST(CryptoLinkTest, DISABLED_HMAC_SHA1_RFC2202_SIGN_TRUNCATED) {
+ const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c };
+ const uint8_t hmac_expected5[] = { 0x4c, 0x1a, 0x03, 0x42, 0x4b,
+ 0x55, 0xe0, 0x7f, 0xe7, 0xf2,
+ 0x7b, 0xe1, 0xd5, 0x8b, 0xb9,
+ 0x32, 0x4a, 0x9a, 0x5a, 0x04 };
+ doHMACTest("Test With Truncation", secret5, 20, SHA1,
+ hmac_expected5, 20);
+ doHMACTest("Test With Truncation", secret5, 20, SHA1,
+ hmac_expected5, 12);
+}
+
//
// Test values taken from RFC 4231
//
-TEST(CryptoLinkTest, HMAC_SHA256_RFC2202_SIGN) {
- const uint8_t secret[] = { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
- 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
- 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b };
- const uint8_t hmac_expected[] = { 0xb0, 0x34, 0x4c, 0x61, 0xd8,
+//
+// Test data from RFC4231, including secret key
+// and source data, they are common for sha224/256/384/512
+// so put them together in seperate space
+namespace {
+ static const uint8_t secret1[] = {
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b
+ };
+
+ static const uint8_t secret2[] = "Jefe";
+ static const uint8_t secret3[] = {
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa
+ };
+ static const uint8_t secret4[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
+ 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12,
+ 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+ 0x19
+ };
+ static const uint8_t secret5[] = {
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c
+ };
+
+ static uint8_t secret6[131];
+ static uint8_t secret7[131];
+
+ static const std::string data1("Hi There");
+ static const std::string data2("what do ya want for nothing?");
+ static const std::string data3(std::string(50, 0xdd));
+ static const std::string data4(std::string(50, 0xcd));
+ static const std::string data5("Test With Truncation");
+ static const std::string data6("Test Using Larger Than Block-Size Key - Hash Key First");
+ static const std::string data7("This is a test using a larger than block-size key and a"
+ " larger than block-size data. The key needs to be hashe"
+ "d before being used by the HMAC algorithm.");
+#define SECRECT(n) secret##n
+#define DATA(n) data##n
+#define HMAC_EXPECTED(n) hmac_expected##n
+
+#define RUN_NTH_TEST_CASE_FOR_ALG(index, hash_algorithm) do {\
+ doHMACTest(DATA(index), \
+ SECRECT(index), sizeof(SECRECT(index)), \
+ (hash_algorithm), HMAC_EXPECTED(index), \
+ sizeof(HMAC_EXPECTED(index)));\
+ }while(0)
+
+#define RUN_TEST_CASES_FOR_ALG(alg) do {\
+ memcpy(secret6, std::string(131, 0xaa).c_str(), 131); \
+ memcpy(secret7, std::string(131, 0xaa).c_str(), 131); \
+ RUN_NTH_TEST_CASE_FOR_ALG(1, alg); \
+ RUN_NTH_TEST_CASE_FOR_ALG(2, alg); \
+ RUN_NTH_TEST_CASE_FOR_ALG(3, alg); \
+ RUN_NTH_TEST_CASE_FOR_ALG(4, alg); \
+ RUN_NTH_TEST_CASE_FOR_ALG(5, alg); \
+ RUN_NTH_TEST_CASE_FOR_ALG(6, alg); \
+ RUN_NTH_TEST_CASE_FOR_ALG(7, alg); \
+ }while(0)
+
+
+};
+
+TEST(CryptoLinkTest, HMAC_SHA256_RFC4231_SIGN) {
+ const uint8_t hmac_expected1[] = { 0xb0, 0x34, 0x4c, 0x61, 0xd8,
0xdb, 0x38, 0x53, 0x5c, 0xa8,
0xaf, 0xce, 0xaf, 0x0b, 0xf1,
0x2b, 0x88, 0x1d, 0xc2, 0x00,
0xc9, 0x83, 0x3d, 0xa7, 0x26,
0xe9, 0x37, 0x6c, 0x2e, 0x32,
0xcf, 0xf7 };
- doHMACTest("Hi There", secret, 20, SHA256, hmac_expected, 32);
-
const uint8_t hmac_expected2[] = { 0x5b, 0xdc, 0xc1, 0x46, 0xbf,
0x60, 0x75, 0x4e, 0x6a, 0x04,
0x24, 0x26, 0x08, 0x95, 0x75,
@@ -354,13 +425,6 @@ TEST(CryptoLinkTest, HMAC_SHA256_RFC2202_SIGN) {
0x9d, 0x27, 0x39, 0x83, 0x9d,
0xec, 0x58, 0xb9, 0x64, 0xec,
0x38, 0x43 };
- doHMACTest("what do ya want for nothing?", "Jefe", 4, SHA256,
- hmac_expected2, 32);
-
- const uint8_t secret3[] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
- 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
- 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
- 0xaa, 0xaa };
const uint8_t hmac_expected3[] = { 0x77, 0x3e, 0xa9, 0x1e, 0x36,
0x80, 0x0e, 0x46, 0x85, 0x4d,
0xb8, 0xeb, 0xd0, 0x91, 0x81,
@@ -368,13 +432,6 @@ TEST(CryptoLinkTest, HMAC_SHA256_RFC2202_SIGN) {
0x3e, 0xf8, 0xc1, 0x22, 0xd9,
0x63, 0x55, 0x14, 0xce, 0xd5,
0x65, 0xfe };
- doHMACTest(std::string(50, 0xdd), secret3, 20, SHA256, hmac_expected3, 32);
-
- const uint8_t secret4[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
- 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
- 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12,
- 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
- 0x19 };
const uint8_t hmac_expected4[] = { 0x82, 0x55, 0x8a, 0x38, 0x9a,
0x44, 0x3c, 0x0e, 0xa4, 0xcc,
0x81, 0x98, 0x99, 0xf2, 0x08,
@@ -382,19 +439,10 @@ TEST(CryptoLinkTest, HMAC_SHA256_RFC2202_SIGN) {
0xe5, 0x78, 0xf8, 0x07, 0x7a,
0x2e, 0x3f, 0xf4, 0x67, 0x29,
0x66, 0x5b };
- doHMACTest(std::string(50, 0xcd), secret4, 25, SHA256, hmac_expected4, 32);
-
- const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
- 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
- 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
- 0x0c, 0x0c };
- const uint8_t hmac_expected5[] = { 0xa3, 0xb6, 0x16, 0x74, 0x73,
- 0x10, 0x0e, 0xe0, 0x6e, 0x0c,
- 0x79, 0x6c, 0x29, 0x55, 0x55,
- 0x2b };
- doHMACTest("Test With Truncation", secret5, 20, SHA256,
- hmac_expected5, 16);
-
+// const uint8_t hmac_expected5[] = { 0xa3, 0xb6, 0x16, 0x74, 0x73,
+// 0x10, 0x0e, 0xe0, 0x6e, 0x0c,
+// 0x79, 0x6c, 0x29, 0x55, 0x55,
+// 0x2b };
const uint8_t hmac_expected6[] = { 0x60, 0xe4, 0x31, 0x59, 0x1e,
0xe0, 0xb6, 0x7f, 0x0d, 0x8a,
0x26, 0xaa, 0xcb, 0xf5, 0xb7,
@@ -402,9 +450,6 @@ TEST(CryptoLinkTest, HMAC_SHA256_RFC2202_SIGN) {
0x37, 0x28, 0xc5, 0x14, 0x05,
0x46, 0x04, 0x0f, 0x0e, 0xe3,
0x7f, 0x54 };
- doHMACTest("Test Using Larger Than Block-Size Key - Hash Key First",
- std::string(131, 0xaa).c_str(), 131, SHA256, hmac_expected6, 32);
-
const uint8_t hmac_expected7[] = { 0x9b, 0x09, 0xff, 0xa7, 0x1b,
0x94, 0x2f, 0xcb, 0x27, 0x63,
0x5f, 0xbc, 0xd5, 0xb0, 0xe9,
@@ -412,10 +457,210 @@ TEST(CryptoLinkTest, HMAC_SHA256_RFC2202_SIGN) {
0x4f, 0x07, 0x13, 0x93, 0x8a,
0x7f, 0x51, 0x53, 0x5c, 0x3a,
0x35, 0xe2 };
- doHMACTest("This is a test using a larger than block-size key and a"
- " larger than block-size data. The key needs to be hashe"
- "d before being used by the HMAC algorithm.",
- std::string(131, 0xaa).c_str(), 131, SHA256, hmac_expected7, 32);
+
+ memcpy(secret6, std::string(131, 0xaa).c_str(), 131);
+ memcpy(secret7, std::string(131, 0xaa).c_str(), 131);
+ RUN_NTH_TEST_CASE_FOR_ALG(1, SHA256);
+ RUN_NTH_TEST_CASE_FOR_ALG(2, SHA256);
+ RUN_NTH_TEST_CASE_FOR_ALG(3, SHA256);
+ RUN_NTH_TEST_CASE_FOR_ALG(4, SHA256);
+ RUN_NTH_TEST_CASE_FOR_ALG(6, SHA256);
+ RUN_NTH_TEST_CASE_FOR_ALG(7, SHA256);
+
+}
+
+
+//
+// Test values taken from RFC 4231, test optional algorithm 224,384,512
+//
+TEST(CryptoLinkTest, DISABLED_HMAC_SHA224_RFC4231_SIGN) {
+ const uint8_t hmac_expected1[] = {
+ 0x89,0x6f,0xb1,0x12,0x8a,0xbb,0xdf,0x19,0x68,0x32,0x10,0x7c,
+ 0xd4,0x9d,0xf3,0x3f,0x47,0xb4,0xb1,0x16,0x99,0x12,0xba,0x4f,
+ 0x53,0x68,0x4b,0x22
+ };
+ const uint8_t hmac_expected2[] = {
+ 0xa3,0x0e,0x01,0x09,0x8b,0xc6,0xdb,0xbf,0x45,0x69,0x0f,0x3a,
+ 0x7e,0x9e,0x6d,0x0f,0x8b,0xbe,0xa2,0xa3,0x9e,0x61,0x48,0x00,
+ 0x8f,0xd0,0x5e,0x44
+ };
+
+ const uint8_t hmac_expected3[] = {
+ 0x7f,0xb3,0xcb,0x35,0x88,0xc6,0xc1,0xf6,0xff,0xa9,0x69,0x4d,
+ 0x7d,0x6a,0xd2,0x64,0x93,0x65,0xb0,0xc1,0xf6,0x5d,0x69,0xd1,
+ 0xec,0x83,0x33,0xea
+ };
+
+ const uint8_t hmac_expected4[] = {
+ 0x6c,0x11,0x50,0x68,0x74,0x01,0x3c,0xac,0x6a,0x2a,0xbc,0x1b,
+ 0xb3,0x82,0x62,0x7c,0xec,0x6a,0x90,0xd8,0x6e,0xfc,0x01,0x2d,
+ 0xe7,0xaf,0xec,0x5a
+ };
+
+// const uint8_t hmac_expected5[] = {
+// 0x0e,0x2a,0xea,0x68,0xa9,0x0c,0x8d,0x37,0xc9,0x88,0xbc,0xdb,0x9f,
+// 0xca,0x6f,0xa8
+// };
+
+ const uint8_t hmac_expected6[] = {
+ 0x95,0xe9,0xa0,0xdb,0x96,0x20,0x95,0xad,0xae,0xbe,0x9b,0x2d,0x6f,
+ 0x0d,0xbc,0xe2,0xd4,0x99,0xf1,0x12,0xf2,0xd2,0xb7,0x27,0x3f,0xa6,
+ 0x87,0x0e
+ };
+
+ const uint8_t hmac_expected7[] = {
+ 0x3a,0x85,0x41,0x66,0xac,0x5d,0x9f,0x02,0x3f,0x54,0xd5,0x17,0xd0,
+ 0xb3,0x9d,0xbd,0x94,0x67,0x70,0xdb,0x9c,0x2b,0x95,0xc9,0xf6,0xf5,
+ 0x65,0xd1
+ };
+
+ memcpy(secret6, std::string(131, 0xaa).c_str(), 131);
+ memcpy(secret7, std::string(131, 0xaa).c_str(), 131);
+ RUN_NTH_TEST_CASE_FOR_ALG(1, SHA224);
+ RUN_NTH_TEST_CASE_FOR_ALG(2, SHA224);
+ RUN_NTH_TEST_CASE_FOR_ALG(3, SHA224);
+ RUN_NTH_TEST_CASE_FOR_ALG(4, SHA224);
+ RUN_NTH_TEST_CASE_FOR_ALG(6, SHA224);
+ RUN_NTH_TEST_CASE_FOR_ALG(7, SHA224);
+}
+
+
+TEST(CryptoLinkTest, HMAC_SHA384_RFC4231_SIGN) {
+ const uint8_t hmac_expected1[] = {
+ 0xaf,0xd0,0x39,0x44,0xd8,0x48,0x95,0x62,0x6b,0x08,0x25,0xf4,
+ 0xab,0x46,0x90,0x7f,0x15,0xf9,0xda,0xdb,0xe4,0x10,0x1e,0xc6,
+ 0x82,0xaa,0x03,0x4c,0x7c,0xeb,0xc5,0x9c,0xfa,0xea,0x9e,0xa9,
+ 0x07,0x6e,0xde,0x7f,0x4a,0xf1,0x52,0xe8,0xb2,0xfa,0x9c,0xb6
+ };
+
+ const uint8_t hmac_expected2[] = {
+ 0xaf,0x45,0xd2,0xe3,0x76,0x48,0x40,0x31,0x61,0x7f,0x78,0xd2,
+ 0xb5,0x8a,0x6b,0x1b,0x9c,0x7e,0xf4,0x64,0xf5,0xa0,0x1b,0x47,
+ 0xe4,0x2e,0xc3,0x73,0x63,0x22,0x44,0x5e,0x8e,0x22,0x40,0xca,
+ 0x5e,0x69,0xe2,0xc7,0x8b,0x32,0x39,0xec,0xfa,0xb2,0x16,0x49
+ };
+
+ const uint8_t hmac_expected3[] = {
+ 0x88,0x06,0x26,0x08,0xd3,0xe6,0xad,0x8a,0x0a,0xa2,0xac,0xe0,
+ 0x14,0xc8,0xa8,0x6f,0x0a,0xa6,0x35,0xd9,0x47,0xac,0x9f,0xeb,
+ 0xe8,0x3e,0xf4,0xe5,0x59,0x66,0x14,0x4b,0x2a,0x5a,0xb3,0x9d,
+ 0xc1,0x38,0x14,0xb9,0x4e,0x3a,0xb6,0xe1,0x01,0xa3,0x4f,0x27
+ };
+
+ const uint8_t hmac_expected4[] = {
+ 0x3e,0x8a,0x69,0xb7,0x78,0x3c,0x25,0x85,0x19,0x33,0xab,0x62,
+ 0x90,0xaf,0x6c,0xa7,0x7a,0x99,0x81,0x48,0x08,0x50,0x00,0x9c,
+ 0xc5,0x57,0x7c,0x6e,0x1f,0x57,0x3b,0x4e,0x68,0x01,0xdd,0x23,
+ 0xc4,0xa7,0xd6,0x79,0xcc,0xf8,0xa3,0x86,0xc6,0x74,0xcf,0xfb,
+ };
+
+// const uint8_t hmac_expected5[] = {
+// 0x3a,0xbf,0x34,0xc3,0x50,0x3b,0x2a,0x23,0xa4,0x6e,0xfc,0x61,0x9b,
+// 0xae,0xf8,0x97,
+// };
+
+ const uint8_t hmac_expected6[] = {
+ 0x4e,0xce,0x08,0x44,0x85,0x81,0x3e,0x90,0x88,0xd2,0xc6,0x3a,0x04,
+ 0x1b,0xc5,0xb4,0x4f,0x9e,0xf1,0x01,0x2a,0x2b,0x58,0x8f,0x3c,0xd1,
+ 0x1f,0x05,0x03,0x3a,0xc4,0xc6,0x0c,0x2e,0xf6,0xab,0x40,0x30,0xfe,
+ 0x82,0x96,0x24,0x8d,0xf1,0x63,0xf4,0x49,0x52
+ };
+
+ const uint8_t hmac_expected7[] = {
+ 0x66,0x17,0x17,0x8e,0x94,0x1f,0x02,0x0d,0x35,0x1e,0x2f,0x25,0x4e,
+ 0x8f,0xd3,0x2c,0x60,0x24,0x20,0xfe,0xb0,0xb8,0xfb,0x9a,0xdc,0xce,
+ 0xbb,0x82,0x46,0x1e,0x99,0xc5,0xa6,0x78,0xcc,0x31,0xe7,0x99,0x17,
+ 0x6d,0x38,0x60,0xe6,0x11,0x0c,0x46,0x52,0x3e
+ };
+
+ memcpy(secret6, std::string(131, 0xaa).c_str(), 131);
+ memcpy(secret7, std::string(131, 0xaa).c_str(), 131);
+ RUN_NTH_TEST_CASE_FOR_ALG(1, SHA384);
+ RUN_NTH_TEST_CASE_FOR_ALG(2, SHA384);
+ RUN_NTH_TEST_CASE_FOR_ALG(3, SHA384);
+ RUN_NTH_TEST_CASE_FOR_ALG(4, SHA384);
+ RUN_NTH_TEST_CASE_FOR_ALG(6, SHA384);
+ RUN_NTH_TEST_CASE_FOR_ALG(7, SHA384);
+}
+
+TEST(CryptoLinkTest, HMAC_SHA512_RFC4231_SIGN) {
+ const uint8_t hmac_expected1[] = {
+ 0x87,0xaa,0x7c,0xde,0xa5,0xef,0x61,0x9d,0x4f,0xf0,0xb4,0x24,
+ 0x1a,0x1d,0x6c,0xb0,0x23,0x79,0xf4,0xe2,0xce,0x4e,0xc2,0x78,
+ 0x7a,0xd0,0xb3,0x05,0x45,0xe1,0x7c,0xde,0xda,0xa8,0x33,0xb7,
+ 0xd6,0xb8,0xa7,0x02,0x03,0x8b,0x27,0x4e,0xae,0xa3,0xf4,0xe4,
+ 0xbe,0x9d,0x91,0x4e,0xeb,0x61,0xf1,0x70,0x2e,0x69,0x6c,0x20,
+ 0x3a,0x12,0x68,0x54
+ };
+ const uint8_t hmac_expected2[] = {
+ 0x16,0x4b,0x7a,0x7b,0xfc,0xf8,0x19,0xe2,0xe3,0x95,0xfb,0xe7,
+ 0x3b,0x56,0xe0,0xa3,0x87,0xbd,0x64,0x22,0x2e,0x83,0x1f,0xd6,
+ 0x10,0x27,0x0c,0xd7,0xea,0x25,0x05,0x54,0x97,0x58,0xbf,0x75,
+ 0xc0,0x5a,0x99,0x4a,0x6d,0x03,0x4f,0x65,0xf8,0xf0,0xe6,0xfd,
+ 0xca,0xea,0xb1,0xa3,0x4d,0x4a,0x6b,0x4b,0x63,0x6e,0x07,0x0a,
+ 0x38,0xbc,0xe7,0x37
+ };
+
+ const uint8_t hmac_expected3[] = {
+ 0xfa,0x73,0xb0,0x08,0x9d,0x56,0xa2,0x84,0xef,0xb0,0xf0,0x75,
+ 0x6c,0x89,0x0b,0xe9,0xb1,0xb5,0xdb,0xdd,0x8e,0xe8,0x1a,0x36,
+ 0x55,0xf8,0x3e,0x33,0xb2,0x27,0x9d,0x39,0xbf,0x3e,0x84,0x82,
+ 0x79,0xa7,0x22,0xc8,0x06,0xb4,0x85,0xa4,0x7e,0x67,0xc8,0x07,
+ 0xb9,0x46,0xa3,0x37,0xbe,0xe8,0x94,0x26,0x74,0x27,0x88,0x59,
+ 0xe1,0x32,0x92,0xfb
+ };
+
+ const uint8_t hmac_expected4[] = {
+ 0xb0,0xba,0x46,0x56,0x37,0x45,0x8c,0x69,0x90,0xe5,0xa8,0xc5,
+ 0xf6,0x1d,0x4a,0xf7,0xe5,0x76,0xd9,0x7f,0xf9,0x4b,0x87,0x2d,
+ 0xe7,0x6f,0x80,0x50,0x36,0x1e,0xe3,0xdb,0xa9,0x1c,0xa5,0xc1,
+ 0x1a,0xa2,0x5e,0xb4,0xd6,0x79,0x27,0x5c,0xc5,0x78,0x80,0x63,
+ 0xa5,0xf1,0x97,0x41,0x12,0x0c,0x4f,0x2d,0xe2,0xad,0xeb,0xeb,
+ 0x10,0xa2,0x98,0xdd
+ };
+
+// const uint8_t hmac_expected5[] = {
+// 0x41,0x5f,0xad,0x62,0x71,0x58,0x0a,0x53,0x1d,0x41,0x79,0xbc,0x89,
+// 0x1d,0x87,0xa6,
+// };
+
+ const uint8_t hmac_expected6[] = {
+ 0x80,0xb2,0x42,0x63,0xc7,0xc1,0xa3,0xeb,0xb7,0x14,0x93,0xc1,0xdd,
+ 0x7b,0xe8,0xb4,0x9b,0x46,0xd1,0xf4,0x1b,0x4a,0xee,0xc1,0x12,0x1b,
+ 0x01,0x37,0x83,0xf8,0xf3,0x52,0x6b,0x56,0xd0,0x37,0xe0,0x5f,0x25,
+ 0x98,0xbd,0x0f,0xd2,0x21,0x5d,0x6a,0x1e,0x52,0x95,0xe6,0x4f,0x73,
+ 0xf6,0x3f,0x0a,0xec,0x8b,0x91,0x5a,0x98,0x5d,0x78,0x65,0x98
+ };
+
+ const uint8_t hmac_expected7[] = {
+ 0xe3,0x7b,0x6a,0x77,0x5d,0xc8,0x7d,0xba,0xa4,0xdf,0xa9,0xf9,0x6e,
+ 0x5e,0x3f,0xfd,0xde,0xbd,0x71,0xf8,0x86,0x72,0x89,0x86,0x5d,0xf5,
+ 0xa3,0x2d,0x20,0xcd,0xc9,0x44,0xb6,0x02,0x2c,0xac,0x3c,0x49,0x82,
+ 0xb1,0x0d,0x5e,0xeb,0x55,0xc3,0xe4,0xde,0x15,0x13,0x46,0x76,0xfb,
+ 0x6d,0xe0,0x44,0x60,0x65,0xc9,0x74,0x40,0xfa,0x8c,0x6a,0x58
+ };
+
+ memcpy(secret6, std::string(131, 0xaa).c_str(), 131);
+ memcpy(secret7, std::string(131, 0xaa).c_str(), 131);
+ RUN_NTH_TEST_CASE_FOR_ALG(1, SHA512);
+ RUN_NTH_TEST_CASE_FOR_ALG(2, SHA512);
+ RUN_NTH_TEST_CASE_FOR_ALG(3, SHA512);
+ RUN_NTH_TEST_CASE_FOR_ALG(4, SHA512);
+ RUN_NTH_TEST_CASE_FOR_ALG(6, SHA512);
+ RUN_NTH_TEST_CASE_FOR_ALG(7, SHA512);
+}
+
+TEST(CryptoLinkTest, DISABLED_HMAC_SHA256_RFC2202_SIGN_TRUNCATED) {
+ const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c };
+ const uint8_t hmac_expected5[] = { 0xa3, 0xb6, 0x16, 0x74, 0x73,
+ 0x10, 0x0e, 0xe0, 0x6e, 0x0c,
+ 0x79, 0x6c, 0x29, 0x55, 0x55,
+ 0x2b };
+ doHMACTest("Test With Truncation", secret5, 20, SHA256,
+ hmac_expected5, 16);
}
namespace {
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index adb1d41..e028186 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -7,7 +7,7 @@ AM_CPPFLAGS += $(SQLITE_CFLAGS)
AM_CXXFLAGS = $(B10_CXXFLAGS)
-CLEANFILES = *.gcno *.gcda
+CLEANFILES = *.gcno *.gcda messagedef.h messagedef.cc
lib_LTLIBRARIES = libdatasrc.la
libdatasrc_la_SOURCES = data_source.h data_source.cc
@@ -20,3 +20,16 @@ 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
+nodist_libdatasrc_la_SOURCES = messagedef.h messagedef.cc
+
+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
+libdatasrc_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
+
+BUILT_SOURCES = messagedef.h messagedef.cc
+messagedef.h messagedef.cc: Makefile messagedef.mes
+ $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/datasrc/messagedef.mes
+
+EXTRA_DIST = messagedef.mes
diff --git a/src/lib/datasrc/cache.cc b/src/lib/datasrc/cache.cc
index 6fff754..8e9487d 100644
--- a/src/lib/datasrc/cache.cc
+++ b/src/lib/datasrc/cache.cc
@@ -24,6 +24,7 @@
#include <list>
#include <datasrc/cache.h>
+#include <datasrc/logger.h>
using namespace std;
using namespace isc::dns;
@@ -204,16 +205,21 @@ public:
// HotCacheImpl constructor
HotCacheImpl::HotCacheImpl(int slots, bool enabled) :
enabled_(enabled), slots_(slots), count_(0)
-{}
+{
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_CACHE_CREATE);
+}
// Insert a cache node into the cache
inline void
HotCacheImpl::insert(const CacheNodePtr node) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_CACHE_INSERT).
+ arg(node->getRRset()->getName());
std::map<Question, CacheNodePtr>::const_iterator iter;
iter = map_.find(node->question);
if (iter != map_.end()) {
CacheNodePtr old = iter->second;
if (old && old->isValid()) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_CACHE_OLD_FOUND);
remove(old);
}
}
@@ -225,6 +231,7 @@ HotCacheImpl::insert(const CacheNodePtr node) {
++count_;
if (slots_ != 0 && count_ > slots_) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_CACHE_FULL);
remove(lru_.back());
}
}
@@ -245,6 +252,8 @@ HotCacheImpl::promote(CacheNodePtr node) {
// Remove a node from the LRU list and the map
void
HotCacheImpl::remove(ConstCacheNodePtr node) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_CACHE_REMOVE).
+ arg(node->getRRset()->getName());
lru_.erase(node->lru_entry_);
map_.erase(node->question);
--count_;
@@ -257,6 +266,7 @@ HotCache::HotCache(const int slots) {
// HotCache destructor
HotCache::~HotCache() {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_CACHE_DESTROY);
delete impl_;
}
@@ -303,18 +313,21 @@ HotCache::retrieve(const Name& n, const RRClass& c, const RRType& t,
std::map<Question, CacheNodePtr>::const_iterator iter;
iter = impl_->map_.find(Question(n, c, t));
if (iter == impl_->map_.end()) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_CACHE_NOT_FOUND).arg(n);
return (false);
}
CacheNodePtr node = iter->second;
if (node->isValid()) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_CACHE_FOUND).arg(n);
impl_->promote(node);
rrset = node->getRRset();
flags = node->getFlags();
return (true);
}
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_CACHE_EXPIRED).arg(n);
impl_->remove(node);
return (false);
}
@@ -328,6 +341,9 @@ HotCache::setSlots(const int slots) {
return;
}
+ logger.info(DATASRC_CACHE_SLOTS).arg(slots).arg(max(0, impl_->count_ -
+ slots));
+
while (impl_->slots_ != 0 && impl_->count_ > impl_->slots_) {
impl_->remove(impl_->lru_.back());
}
@@ -343,6 +359,11 @@ HotCache::getSlots() const {
void
HotCache::setEnabled(const bool e) {
impl_->enabled_ = e;
+ if (e) {
+ logger.info(DATASRC_CACHE_ENABLE);
+ } else {
+ logger.info(DATASRC_CACHE_DISABLE);
+ }
}
/// Indicate whether the cache is enabled
diff --git a/src/lib/datasrc/data_source.cc b/src/lib/datasrc/data_source.cc
index 548a811..4e1fcde 100644
--- a/src/lib/datasrc/data_source.cc
+++ b/src/lib/datasrc/data_source.cc
@@ -25,6 +25,7 @@
#include <datasrc/cache.h>
#include <datasrc/data_source.h>
#include <datasrc/query.h>
+#include <datasrc/logger.h>
#include <util/encode/base32hex.h>
#include <util/hash/sha1.h>
@@ -83,7 +84,7 @@ class ZoneInfo {
public:
ZoneInfo(DataSrc* ts,
const isc::dns::Name& n,
- const isc::dns::RRClass& c,
+ const isc::dns::RRClass& c,
const isc::dns::RRType& t = isc::dns::RRType::ANY()) :
top_source_(ts),
dsm_(((t == RRType::DS() && n.getLabelCount() != 1)
@@ -123,6 +124,8 @@ getAdditional(Query& q, ConstRRsetPtr rrset) {
const Rdata& rd(it->getCurrent());
if (rrset->getType() == RRType::NS()) {
const generic::NS& ns = dynamic_cast<const generic::NS&>(rd);
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_GET_NS_ADDITIONAL).
+ arg(ns.getNSName()).arg(rrset->getName());
q.tasks().push(QueryTaskPtr(
new QueryTask(q, ns.getNSName(),
Message::SECTION_ADDITIONAL,
@@ -130,6 +133,8 @@ getAdditional(Query& q, ConstRRsetPtr rrset) {
QueryTask::GETADDITIONAL)));
} else if (rrset->getType() == RRType::MX()) {
const generic::MX& mx = dynamic_cast<const generic::MX&>(rd);
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_GET_MX_ADDITIONAL).
+ arg(mx.getMXName()).arg(rrset->getName());
q.tasks().push(QueryTaskPtr(
new QueryTask(q, mx.getMXName(),
Message::SECTION_ADDITIONAL,
@@ -143,11 +148,14 @@ getAdditional(Query& q, ConstRRsetPtr rrset) {
// understand DNAME
void
synthesizeCname(QueryTaskPtr task, RRsetPtr rrset, RRsetList& target) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_SYNTH_CNAME).
+ arg(rrset->getName());
RdataIteratorPtr it = rrset->getRdataIterator();
// More than one DNAME RR in the RRset is illegal, so we only have
// to process the first one.
if (it->isLast()) {
+ logger.error(DATASRC_QUERY_EMPTY_DNAME).arg(rrset->getName());
return;
}
@@ -171,16 +179,20 @@ synthesizeCname(QueryTaskPtr task, RRsetPtr rrset, RRsetList& target) {
// to by a CNAME record
void
chaseCname(Query& q, QueryTaskPtr task, RRsetPtr rrset) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_FOLLOW_CNAME).
+ arg(rrset->getName());
RdataIteratorPtr it = rrset->getRdataIterator();
// More than one CNAME RR in the RRset is illegal, so we only have
// to process the first one.
if (it->isLast()) {
+ logger.error(DATASRC_QUERY_EMPTY_CNAME).arg(rrset->getName());
return;
}
// Stop chasing CNAMES after 16 lookups, to prevent loops
if (q.tooMany()) {
+ logger.error(DATASRC_QUERY_TOO_MANY_CNAMES).arg(rrset->getName());
return;
}
@@ -194,6 +206,8 @@ chaseCname(Query& q, QueryTaskPtr task, RRsetPtr rrset) {
// Check the cache for data which can answer the current query task.
bool
checkCache(QueryTask& task, RRsetList& target) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_CHECK_CACHE).
+ arg(task.qname).arg(task.qtype);
HotCache& cache = task.q.getCache();
RRsetList rrsets;
RRsetPtr rrset;
@@ -206,6 +220,9 @@ checkCache(QueryTask& task, RRsetList& target) {
// ANY queries must be handled by the low-level data source,
// or the results won't be guaranteed to be complete
if (task.qtype == RRType::ANY() || task.qclass == RRClass::ANY()) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA,
+ DATASRC_QUERY_NO_CACHE_ANY_SIMPLE).arg(task.qname).
+ arg(task.qtype).arg(task.qclass);
break;
}
@@ -235,6 +252,8 @@ checkCache(QueryTask& task, RRsetList& target) {
case QueryTask::AUTH_QUERY: // Find exact RRset or CNAME
if (task.qtype == RRType::ANY() || task.qclass == RRClass::ANY()) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_NO_CACHE_ANY_AUTH).
+ arg(task.qname).arg(task.qtype).arg(task.qclass);
break;
}
@@ -353,6 +372,8 @@ DataSrc::Result
doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
HotCache& cache = task.q.getCache();
RRsetPtr rrset;
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_DO_QUERY).arg(task.qname).
+ arg(task.qtype);
// First off, make sure at least we have a matching zone in some data
// source. We must do this before checking the cache, because it can
@@ -363,11 +384,14 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
const Name* const zonename = zoneinfo.getEnclosingZone();
if (ds == NULL) {
task.flags |= DataSrc::NO_SUCH_ZONE;
+ logger.info(DATASRC_QUERY_NO_ZONE).arg(task.qname).arg(task.qclass);
return (DataSrc::SUCCESS);
}
// Then check the cache for matching data
if (checkCache(task, target)) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_CACHED).
+ arg(task.qname).arg(task.qtype);
return (DataSrc::SUCCESS);
}
@@ -378,10 +402,13 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
DataSrc::Result result;
switch (task.op) {
case QueryTask::SIMPLE_QUERY:
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_IS_SIMPLE).
+ arg(task.qname).arg(task.qtype);
result = ds->findExactRRset(task.qname, task.qclass, task.qtype,
target, task.flags, zonename);
if (result != DataSrc::SUCCESS) {
+ logger.error(DATASRC_QUERY_SIMPLE_FAIL).arg(result);
return (result);
}
@@ -403,10 +430,13 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
return (result);
case QueryTask::AUTH_QUERY:
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_IS_AUTH).
+ arg(task.qname).arg(task.qtype);
result = ds->findRRset(task.qname, task.qclass, task.qtype,
target, task.flags, zonename);
if (result != DataSrc::SUCCESS) {
+ logger.error(DATASRC_QUERY_AUTH_FAIL).arg(result);
return (result);
}
@@ -439,10 +469,16 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
case QueryTask::GLUE_QUERY:
case QueryTask::NOGLUE_QUERY:
+ LOG_DEBUG(logger, DBG_TRACE_DATA, task.op == QueryTask::GLUE_QUERY ?
+ DATASRC_QUERY_IS_GLUE : DATASRC_QUERY_IS_NOGLUE).
+ arg(task.qname).arg(task.qtype);
result = ds->findAddrs(task.qname, task.qclass, target,
task.flags, zonename);
if (result != DataSrc::SUCCESS) {
+ logger.error(task.op == QueryTask::GLUE_QUERY ?
+ DATASRC_QUERY_GLUE_FAIL : DATASRC_QUERY_NOGLUE_FAIL).
+ arg(result);
return (result);
}
@@ -468,10 +504,13 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
return (result);
case QueryTask::REF_QUERY:
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_IS_REF).
+ arg(task.qname).arg(task.qtype);
result = ds->findReferral(task.qname, task.qclass, target,
task.flags, zonename);
if (result != DataSrc::SUCCESS) {
+ logger.error(DATASRC_QUERY_REF_FAIL).arg(result);
return (result);
}
@@ -505,6 +544,7 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
}
// Not reached
+ logger.error(DATASRC_QUERY_INVALID_OP);
return (DataSrc::ERROR);
}
@@ -516,6 +556,8 @@ inline void
addToMessage(Query& q, const Message::Section sect, RRsetPtr rrset,
bool no_dnssec = false)
{
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_ADD_RRSET).
+ arg(rrset->getName()).arg(rrset->getType());
Message& m = q.message();
if (no_dnssec) {
if (rrset->getType() == RRType::RRSIG() ||
@@ -534,6 +576,7 @@ addToMessage(Query& q, const Message::Section sect, RRsetPtr rrset,
// Copy referral information into the authority section of a message
inline void
copyAuth(Query& q, RRsetList& auth) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_COPY_AUTH);
BOOST_FOREACH(RRsetPtr rrset, auth) {
if (rrset->getType() == RRType::DNAME()) {
continue;
@@ -571,6 +614,9 @@ refQuery(const Query& q, const Name& name, ZoneInfo& zoneinfo,
// they'll be handled in a normal lookup in the zone.
inline bool
hasDelegation(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_DELEGATION).
+ arg(task->qname);
+
const Name* const zonename = zoneinfo.getEnclosingZone();
if (zonename == NULL) {
if (task->state == QueryTask::GETANSWER) {
@@ -636,6 +682,7 @@ addSOA(Query& q, ZoneInfo& zoneinfo) {
RRsetList soa;
const Name* const zonename = zoneinfo.getEnclosingZone();
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_ADD_SOA).arg(*zonename);
QueryTask newtask(q, *zonename, RRType::SOA(), QueryTask::SIMPLE_QUERY);
RETERR(doQueryTask(newtask, zoneinfo, soa));
if (newtask.flags != 0) {
@@ -649,6 +696,7 @@ addSOA(Query& q, ZoneInfo& zoneinfo) {
inline DataSrc::Result
addNSEC(Query& q, const Name& name, ZoneInfo& zoneinfo) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_ADD_NSEC).arg(name);
RRsetList nsec;
QueryTask newtask(q, name, RRType::NSEC(), QueryTask::SIMPLE_QUERY);
@@ -665,9 +713,11 @@ inline DataSrc::Result
getNsec3(Query& q, ZoneInfo& zoneinfo, string& hash, RRsetPtr& target) {
const DataSrc* ds = zoneinfo.getDataSource();
const Name* const zonename = zoneinfo.getEnclosingZone();
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_ADD_NSEC3).arg(*zonename);
if (ds == NULL) {
q.message().setRcode(Rcode::SERVFAIL());
+ logger.error(DATASRC_QUERY_NO_DS_NSEC3).arg(*zonename);
return (DataSrc::ERROR);
}
@@ -770,6 +820,7 @@ proveNX(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, const bool wildcard) {
const DataSrc* ds = zoneinfo.getDataSource();
if (ds == NULL) {
m.setRcode(Rcode::SERVFAIL());
+ logger.error(DATASRC_QUERY_NO_DS_NSEC).arg(*zonename);
return (DataSrc::ERROR);
}
ds->findPreviousName(task->qname, nsecname, zonename);
@@ -798,6 +849,7 @@ proveNX(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, const bool wildcard) {
// Attempt a wildcard lookup
inline DataSrc::Result
tryWildcard(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, bool& found) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_WILDCARD).arg(task->qname);
Message& m = q.message();
DataSrc::Result result;
found = false;
@@ -851,6 +903,8 @@ tryWildcard(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, bool& found) {
result = proveNX(q, task, zoneinfo, true);
if (result != DataSrc::SUCCESS) {
m.setRcode(Rcode::SERVFAIL());
+ logger.error(DATASRC_QUERY_WILDCARD_PROVENX_FAIL).
+ arg(task->qname).arg(result);
return (DataSrc::ERROR);
}
}
@@ -873,6 +927,8 @@ tryWildcard(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, bool& found) {
RRsetList auth;
if (!refQuery(q, *zonename, zoneinfo, auth)) {
+ logger.error(DATASRC_QUERY_WILDCARD_REFERRAL).arg(task->qname).
+ arg(result);
return (DataSrc::ERROR);
}
@@ -888,6 +944,8 @@ tryWildcard(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, bool& found) {
//
void
DataSrc::doQuery(Query& q) {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_QUERY_PROCESS).arg(q.qname()).
+ arg(q.qclass());
Message& m = q.message();
vector<RRsetPtr> additional;
@@ -905,6 +963,7 @@ DataSrc::doQuery(Query& q) {
// Can't query directly for RRSIG.
if (task->qtype == RRType::RRSIG()) {
m.setRcode(Rcode::REFUSED());
+ logger.warn(DATASRC_QUERY_RRSIG).arg(task->qname);
return;
}
@@ -912,6 +971,7 @@ DataSrc::doQuery(Query& q) {
if (task->op == QueryTask::SIMPLE_QUERY ||
task->op == QueryTask::REF_QUERY) {
m.setRcode(Rcode::SERVFAIL());
+ logger.error(DATASRC_QUERY_MISPLACED_TASK);
return;
}
@@ -931,6 +991,7 @@ DataSrc::doQuery(Query& q) {
result = doQueryTask(*task, zoneinfo, data);
if (result != SUCCESS) {
m.setRcode(Rcode::SERVFAIL());
+ logger.error(DATASRC_QUERY_TASK_FAIL).arg(result);
return;
}
@@ -940,6 +1001,7 @@ DataSrc::doQuery(Query& q) {
if (task->flags == NO_SUCH_ZONE) {
if (task->state == QueryTask::GETANSWER) {
m.setRcode(Rcode::REFUSED());
+ // No need to log it here, it was already logged in doQueryTask
return;
}
continue;
@@ -981,6 +1043,7 @@ DataSrc::doQuery(Query& q) {
RRsetList auth;
if (!refQuery(q, Name(*zonename), zoneinfo, auth) ||
!findRRsetFromList(auth, RRType::NS())) {
+ logger.error(DATASRC_QUERY_MISSING_NS).arg(*zonename);
isc_throw(DataSourceError,
"NS RR not found in " << *zonename << "/" <<
q.qclass());
@@ -1005,10 +1068,12 @@ DataSrc::doQuery(Query& q) {
continue;
default:
+ logger.error(DATASRC_UNEXPECTED_QUERY_STATE);
isc_throw (Unexpected, "unexpected query state");
}
} else if (result == ERROR || result == NOT_IMPLEMENTED) {
m.setRcode(Rcode::SERVFAIL());
+ logger.error(DATASRC_QUERY_FAIL);
return;
} else if ((task->flags & CNAME_FOUND) != 0) {
// The qname node contains a CNAME. Add a new task to the
@@ -1026,6 +1091,7 @@ DataSrc::doQuery(Query& q) {
m.setHeaderFlag(Message::HEADERFLAG_AA, false);
if (!refQuery(q, task->qname, zoneinfo, auth)) {
m.setRcode(Rcode::SERVFAIL());
+ logger.error(DATASRC_QUERY_BAD_REFERRAL).arg(task->qname);
return;
}
BOOST_FOREACH (RRsetPtr rrset, auth) {
@@ -1057,6 +1123,7 @@ DataSrc::doQuery(Query& q) {
result = tryWildcard(q, task, zoneinfo, wildcard_found);
if (result != SUCCESS) {
m.setRcode(Rcode::SERVFAIL());
+ logger.error(DATASRC_QUERY_WILDCARD_FAIL).arg(task->qname);
return;
}
@@ -1078,6 +1145,7 @@ DataSrc::doQuery(Query& q) {
result = addSOA(q, zoneinfo);
if (result != SUCCESS) {
+ logger.error(DATASRC_QUERY_MISSING_SOA).arg(*zonename);
isc_throw(DataSourceError,
"SOA RR not found in " << *zonename <<
"/" << q.qclass());
@@ -1094,6 +1162,7 @@ DataSrc::doQuery(Query& q) {
result = proveNX(q, task, zoneinfo, false);
if (result != DataSrc::SUCCESS) {
m.setRcode(Rcode::SERVFAIL());
+ logger.error(DATASRC_QUERY_PROVENX_FAIL).arg(task->qname);
return;
}
}
@@ -1102,6 +1171,7 @@ DataSrc::doQuery(Query& q) {
} else {
// Should never be reached!
m.setRcode(Rcode::SERVFAIL());
+ logger.error(DATASRC_QUERY_UNKNOWN_RESULT);
return;
}
}
@@ -1197,7 +1267,10 @@ DataSrc::findReferral(const Name& qname, const RRClass& qclass,
void
MetaDataSrc::addDataSrc(ConstDataSrcPtr data_src) {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_META_ADD);
if (getClass() != RRClass::ANY() && data_src->getClass() != getClass()) {
+ logger.error(DATASRC_META_ADD_CLASS_MISMATCH).
+ arg(data_src->getClass()).arg(getClass());
isc_throw(Unexpected, "class mismatch");
}
@@ -1206,6 +1279,7 @@ MetaDataSrc::addDataSrc(ConstDataSrcPtr data_src) {
void
MetaDataSrc::removeDataSrc(ConstDataSrcPtr data_src) {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_META_REMOVE);
std::vector<ConstDataSrcPtr>::iterator it, itr;
for (it = data_sources.begin(); it != data_sources.end(); ++it) {
if (*it == data_src) {
diff --git a/src/lib/datasrc/logger.cc b/src/lib/datasrc/logger.cc
new file mode 100644
index 0000000..846e43b
--- /dev/null
+++ b/src/lib/datasrc/logger.cc
@@ -0,0 +1,23 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/logger.h>
+
+namespace isc {
+namespace datasrc {
+
+isc::log::Logger logger("datasrc");
+
+}
+}
diff --git a/src/lib/datasrc/logger.h b/src/lib/datasrc/logger.h
new file mode 100644
index 0000000..7c2828d
--- /dev/null
+++ b/src/lib/datasrc/logger.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __DATASRC_LOGGER_H
+#define __DATASRC_LOGGER_H
+
+#include <log/macros.h>
+#include <datasrc/messagedef.h>
+
+/// \file logger.h
+/// \brief Data Source library global logger
+///
+/// This holds the logger for the data source library. It is a private header
+/// and should not be included in any publicly used header, only in local
+/// cc files.
+
+namespace isc {
+namespace datasrc {
+
+/// \brief The logger for this library
+extern isc::log::Logger logger;
+
+enum {
+ /// \brief Trace basic operations
+ DBG_TRACE_BASIC = 10,
+ /// \brief Trace data changes and lookups as well
+ DBG_TRACE_DATA = 20,
+ /// \brief Detailed even about how the lookups happen
+ DBG_TRACE_DETAILED = 50
+};
+
+}
+}
+
+#endif
diff --git a/src/lib/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index 5230ced..3c57d1b 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -24,6 +24,7 @@
#include <datasrc/memory_datasrc.h>
#include <datasrc/rbtree.h>
+#include <datasrc/logger.h>
using namespace std;
using namespace isc::dns;
@@ -94,6 +95,8 @@ struct MemoryZone::MemoryZoneImpl {
l > origin_labels;
--l, wname = wname.split(1)) {
if (wname.isWildcard()) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ADD_WILDCARD).
+ arg(name);
// Ensure a separate level exists for the "wildcarding" name,
// and mark the node as "wild".
DomainNode* node;
@@ -130,10 +133,13 @@ struct MemoryZone::MemoryZoneImpl {
// (depending on how we support DNSSEC). We should revisit it
// at that point.
if (!domain->empty()) {
+ LOG_ERROR(logger, DATASRC_MEM_CNAME_TO_NONEMPTY).
+ arg(rrset->getName());
isc_throw(AddError, "CNAME can't be added with other data for "
<< rrset->getName());
}
} else if (domain->find(RRType::CNAME()) != domain->end()) {
+ LOG_ERROR(logger, DATASRC_MEM_CNAME_COEXIST).arg(rrset->getName());
isc_throw(AddError, "CNAME and " << rrset->getType() <<
" can't coexist for " << rrset->getName());
}
@@ -151,6 +157,7 @@ struct MemoryZone::MemoryZoneImpl {
(rrset->getType() == RRType::NS() &&
domain->find(RRType::DNAME()) != domain->end())))
{
+ LOG_ERROR(logger, DATASRC_MEM_DNAME_NS).arg(rrset->getName());
isc_throw(AddError, "DNAME can't coexist with NS in non-apex "
"domain " << rrset->getName());
}
@@ -172,6 +179,8 @@ struct MemoryZone::MemoryZoneImpl {
// XXX: this is not only for CNAME or DNAME. We should generalize
// this code for all other "singleton RR types" (such as SOA) in a
// separate task.
+ LOG_ERROR(logger, DATASRC_MEM_SINGLETON).arg(rrset->getName()).
+ arg(rrset->getType());
isc_throw(AddError, "multiple RRs of singleton type for "
<< rrset->getName());
}
@@ -180,6 +189,8 @@ struct MemoryZone::MemoryZoneImpl {
if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN &&
compare.getRelation() != NameComparisonResult::EQUAL)
{
+ LOG_ERROR(logger, DATASRC_MEM_OUT_OF_ZONE).arg(rrset->getName()).
+ arg(origin_);
isc_throw(OutOfZone, "The name " << rrset->getName() <<
" is not contained in zone " << origin_);
}
@@ -194,10 +205,14 @@ struct MemoryZone::MemoryZoneImpl {
// behavior.
if (rrset->getName().isWildcard()) {
if (rrset->getType() == RRType::NS()) {
+ LOG_ERROR(logger, DATASRC_MEM_WILDCARD_NS).
+ arg(rrset->getName());
isc_throw(AddError, "Invalid NS owner name (wildcard): " <<
rrset->getName());
}
if (rrset->getType() == RRType::DNAME()) {
+ LOG_ERROR(logger, DATASRC_MEM_WILDCARD_DNAME).
+ arg(rrset->getName());
isc_throw(AddError, "Invalid DNAME owner name (wildcard): " <<
rrset->getName());
}
@@ -210,6 +225,8 @@ struct MemoryZone::MemoryZoneImpl {
*/
// Implementation of MemoryZone::add
result::Result add(const ConstRRsetPtr& rrset, DomainTree* domains) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ADD_RRSET).
+ arg(rrset->getName()).arg(rrset->getType()).arg(origin_);
// Sanitize input
addValidation(rrset);
@@ -271,6 +288,8 @@ struct MemoryZone::MemoryZoneImpl {
void addFromLoad(const ConstRRsetPtr& set, DomainTree* domains) {
switch (add(set, domains)) {
case result::EXIST:
+ LOG_ERROR(logger, DATASRC_MEM_DUP_RRSET).
+ arg(set->getName()).arg(set->getType());
isc_throw(dns::MasterLoadError, "Duplicate rrset: " <<
set->toText());
case result::SUCCESS:
@@ -307,6 +326,8 @@ struct MemoryZone::MemoryZoneImpl {
const Domain::const_iterator foundDNAME(node.getData()->find(
RRType::DNAME()));
if (foundDNAME != node.getData()->end()) {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_MEM_DNAME_ENCOUNTERED);
state->dname_node_ = &node;
state->rrset_ = foundDNAME->second;
// No more processing below the DNAME (RFC 2672, section 3
@@ -328,6 +349,8 @@ struct MemoryZone::MemoryZoneImpl {
return (false);
}
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_NS_ENCOUNTERED);
+
// BIND 9 checks if this node is not the origin. That's probably
// because it can support multiple versions for dynamic updates
// and IXFR, and it's possible that the callback is called at
@@ -363,6 +386,8 @@ struct MemoryZone::MemoryZoneImpl {
rrset, bool rename)
{
if (rename) {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_RENAME).
+ arg(rrset->getName()).arg(name);
/*
* We lose a signature here. But it would be wrong anyway, because
* the name changed. This might turn out to be unimportant in
@@ -385,6 +410,8 @@ struct MemoryZone::MemoryZoneImpl {
FindResult find(const Name& name, RRType type,
RRsetList* target, const FindOptions options) const
{
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_FIND).arg(name).
+ arg(type);
// Get the node
DomainNode* node(NULL);
FindState state(options);
@@ -411,12 +438,16 @@ struct MemoryZone::MemoryZoneImpl {
* is NULL.
*/
if (state.dname_node_ != NULL) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DNAME_FOUND).
+ arg(state.rrset_->getName());
// We were traversing a DNAME node (and wanted to go
// lower below it), so return the DNAME
return (FindResult(DNAME, prepareRRset(name, state.rrset_,
rename)));
}
if (state.zonecut_node_ != NULL) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DELEG_FOUND).
+ arg(state.rrset_->getName());
return (FindResult(DELEGATION, prepareRRset(name,
state.rrset_, rename)));
}
@@ -426,6 +457,8 @@ struct MemoryZone::MemoryZoneImpl {
// the zone but is empty. Treat it as NXRRSET.
if (node_path.getLastComparisonResult().getRelation() ==
NameComparisonResult::SUPERDOMAIN) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUPER_STOP).
+ arg(node_path.getAbsoluteName()).arg(name);
return (FindResult(NXRRSET, ConstRRsetPtr()));
}
@@ -463,6 +496,8 @@ struct MemoryZone::MemoryZoneImpl {
if (node_path.getLastComparisonResult().getRelation() ==
NameComparisonResult::COMMONANCESTOR && node_path.
getLastComparisonResult().getCommonLabels() > 1) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA,
+ DATASRC_MEM_WILDCARD_CANCEL).arg(name);
return (FindResult(NXDOMAIN, ConstRRsetPtr()));
}
Name wildcard(Name("*").concatenate(
@@ -485,6 +520,8 @@ struct MemoryZone::MemoryZoneImpl {
// fall through
case DomainTree::NOTFOUND:
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NOTFOUND).
+ arg(name);
return (FindResult(NXDOMAIN, ConstRRsetPtr()));
case DomainTree::EXACTMATCH: // This one is OK, handle it
break;
@@ -496,6 +533,8 @@ struct MemoryZone::MemoryZoneImpl {
// If there is an exact match but the node is empty, it's equivalent
// to NXRRSET.
if (node->isEmpty()) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DOMAIN_EMPTY).
+ arg(name);
return (FindResult(NXRRSET, ConstRRsetPtr()));
}
@@ -506,6 +545,8 @@ struct MemoryZone::MemoryZoneImpl {
if (node->getFlag(DomainNode::FLAG_CALLBACK) && node != origin_data_) {
found = node->getData()->find(RRType::NS());
if (found != node->getData()->end()) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA,
+ DATASRC_MEM_EXACT_DELEGATION).arg(name);
return (FindResult(DELEGATION, prepareRRset(name,
found->second, rename)));
}
@@ -521,23 +562,30 @@ struct MemoryZone::MemoryZoneImpl {
boost::const_pointer_cast<RRset>(prepareRRset(name,
found->second, rename)));
}
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ANY_SUCCESS).
+ arg(name);
return (FindResult(SUCCESS, ConstRRsetPtr()));
}
found = node->getData()->find(type);
if (found != node->getData()->end()) {
// Good, it is here
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUCCESS).arg(name).
+ arg(type);
return (FindResult(SUCCESS, prepareRRset(name, found->second,
rename)));
} else {
// Next, try CNAME.
found = node->getData()->find(RRType::CNAME());
if (found != node->getData()->end()) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_CNAME).arg(name);
return (FindResult(CNAME, prepareRRset(name, found->second,
rename)));
}
}
// No exact match or CNAME. Return NXRRSET.
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NXRRSET).arg(type).
+ arg(name);
return (FindResult(NXRRSET, ConstRRsetPtr()));
}
};
@@ -545,9 +593,13 @@ struct MemoryZone::MemoryZoneImpl {
MemoryZone::MemoryZone(const RRClass& zone_class, const Name& origin) :
impl_(new MemoryZoneImpl(zone_class, origin))
{
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_CREATE).arg(origin).
+ arg(zone_class);
}
MemoryZone::~MemoryZone() {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_DESTROY).arg(getOrigin()).
+ arg(getClass());
delete impl_;
}
@@ -576,6 +628,8 @@ MemoryZone::add(const ConstRRsetPtr& rrset) {
void
MemoryZone::load(const string& filename) {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_LOAD).arg(getOrigin()).
+ arg(filename);
// Load it into a temporary tree
MemoryZoneImpl::DomainTree tmp;
masterLoad(filename.c_str(), getOrigin(), getClass(),
@@ -588,6 +642,8 @@ MemoryZone::load(const string& filename) {
void
MemoryZone::swap(MemoryZone& zone) {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_SWAP).arg(getOrigin()).
+ arg(zone.getOrigin());
std::swap(impl_, zone.impl_);
}
@@ -628,6 +684,9 @@ MemoryDataSrc::addZone(ZonePtr zone) {
"Null pointer is passed to MemoryDataSrc::addZone()");
}
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_ADD_ZONE).
+ arg(zone->getOrigin()).arg(zone->getClass().toText());
+
const result::Result result = impl_->zone_table.addZone(zone);
if (result == result::SUCCESS) {
++impl_->zone_count;
@@ -637,6 +696,7 @@ MemoryDataSrc::addZone(ZonePtr zone) {
MemoryDataSrc::FindResult
MemoryDataSrc::findZone(const isc::dns::Name& name) const {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_FIND_ZONE).arg(name);
return (FindResult(impl_->zone_table.findZone(name).code,
impl_->zone_table.findZone(name).zone));
}
diff --git a/src/lib/datasrc/messagedef.mes b/src/lib/datasrc/messagedef.mes
new file mode 100644
index 0000000..2fc5c6b
--- /dev/null
+++ b/src/lib/datasrc/messagedef.mes
@@ -0,0 +1,498 @@
+# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$PREFIX DATASRC_
+$NAMESPACE isc::datasrc
+
+# \brief Messages for the data source library
+
+% CACHE_CREATE creating the hotspot cache
+Debug information that the hotspot cache was created at startup.
+
+% CACHE_DESTROY destroying the hotspot cache
+Debug information. The hotspot cache is being destroyed.
+
+% CACHE_INSERT inserting item '%1' into the cache
+Debug information. It means a new item is being inserted into the hotspot
+cache.
+
+% CACHE_OLD_FOUND older instance of cache item found, replacing
+Debug information. While inserting an item into the hotspot cache, an older
+instance of an item with the same name was found. The old instance will be
+removed. This should be directly followed by CACHE_REMOVE.
+
+% CACHE_FULL cache is full, dropping oldest
+Debug information. After inserting an item into the hotspot cache, the
+maximum number of items was exceeded, so the least recently used item will
+be dropped. This should be directly followed by CACHE_REMOVE.
+
+% CACHE_REMOVE removing '%1' from the cache
+Debug information. An item is being removed from the hotspot cache.
+
+% CACHE_NOT_FOUND the item '%1' was not found
+Debug information. It was attempted to look up an item in the hotspot cache,
+but it is not there.
+
+% CACHE_FOUND the item '%1' was found
+Debug information. An item was successfully looked up in the hotspot cache.
+
+% CACHE_EXPIRED the item '%1' is expired
+Debug information. There was an attempt to look up an item in the hotspot
+cache. And the item was actually there, but it was too old, so it was removed
+instead and nothing is reported (the external behaviour is the same as with
+CACHE_NOT_FOUND).
+
+% CACHE_SLOTS setting the cache size to '%1', dropping '%2' items
+The maximum allowed number of items of the hotspot cache is set to the given
+number. If there are too many, some of them will be dropped. The size of 0
+means no limit.
+
+% CACHE_ENABLE enabling the cache
+The hotspot cache is enabled from now on.
+
+% CACHE_DISABLE disabling the cache
+The hotspot cache is disabled from now on. It is not going to store
+information or return anything.
+
+% QUERY_SYNTH_CNAME synthesizing CNAME from DNAME on '%1'
+Debug information. While answering a query, a DNAME was met. The DNAME itself
+will be returned, but along with it a CNAME for clients which don't understand
+DNAMEs will be synthesized.
+
+% QUERY_EMPTY_DNAME the DNAME on '%1' is empty
+During an attempt to synthesize CNAME from this DNAME it was discovered the
+DNAME is empty (it has no records). This indicates problem with supplied data.
+
+% QUERY_GET_NS_ADDITIONAL addition of A/AAAA for '%1' requested by NS '%2'
+Debug information. While processing a query, a NS record was met. It
+references the mentioned address, so A/AAAA records for it are looked up
+and put it into the additional section.
+
+% QUERY_GET_MX_ADDITIONAL addition of A/AAAA for '%1' requested by MX '%2'
+Debug information. While processing a query, a MX record was met. It
+references the mentioned address, so A/AAAA records for it are looked up
+and put it into the additional section.
+
+% QUERY_FOLLOW_CNAME following CNAME at '%1'
+Debug information. The domain is a CNAME (or a DNAME and we created a CNAME
+for it already), so it's being followed.
+
+% QUERY_EMPTY_CNAME cNAME at '%1' is empty
+There was an CNAME and it was being followed. But it contains no records,
+so there's nowhere to go. There will be no answer. This indicates a problem
+with supplied data.
+We tried to follow
+
+% QUERY_TOO_MANY_CNAMES cNAME chain limit exceeded at '%1'
+A CNAME led to another CNAME and it led to another, and so on. After 16
+CNAMEs, the software gave up. Long CNAME chains are discouraged, and this
+might possibly be a loop as well. Note that some of the CNAMEs might have
+been synthesized from DNAMEs. This indicates problem with supplied data.
+
+% QUERY_CHECK_CACHE checking cache for '%1/%2'
+Debug information. While processing a query, lookup to the hotspot cache
+is being made.
+
+% QUERY_NO_CACHE_ANY_SIMPLE ignoring cache for ANY query (%1/%2 in %3 class)
+Debug information. The hotspot cache is ignored for ANY queries for consistency
+reasons.
+
+% QUERY_NO_CACHE_ANY_AUTH ignoring cache for ANY query (%1/%2 in %3 class)
+Debug information. The hotspot cache is ignored for authoritative ANY queries
+for consistency reasons.
+
+% DO_QUERY handling query for '%1/%2'
+Debug information. We're processing some internal query for given name and
+type.
+
+% QUERY_NO_ZONE no zone containing '%1' in class '%2'
+Lookup of domain failed because the data have no zone that contain the
+domain. Maybe someone sent a query to the wrong server for some reason.
+
+% QUERY_CACHED data for %1/%2 found in cache
+Debug information. The requested data were found in the hotspot cache, so
+no query is sent to the real data source.
+
+% QUERY_IS_SIMPLE simple query (%1/%2)
+Debug information. The last DO_QUERY is a simple query.
+
+% QUERY_IS_AUTH auth query (%1/%2)
+Debug information. The last DO_QUERY is an auth query.
+
+% QUERY_IS_GLUE glue query (%1/%2)
+Debug information. The last DO_QUERY is query for glue addresses.
+
+% QUERY_IS_NOGLUE query for non-glue addresses (%1/%2)
+Debug information. The last DO_QUERY is query for addresses that are not
+glue.
+
+% QUERY_IS_REF query for referral (%1/%2)
+Debug information. The last DO_QUERY is query for referral information.
+
+% QUERY_SIMPLE_FAIL the underlying data source failed with %1
+The underlying data source failed to answer the simple query. 1 means some
+error, 2 is not implemented. The data source should have logged the specific
+error already.
+
+% QUERY_AUTH_FAIL the underlying data source failed with %1
+The underlying data source failed to answer the authoritative query. 1 means
+some error, 2 is not implemented. The data source should have logged the
+specific error already.
+
+% QUERY_GLUE_FAIL the underlying data source failed with %1
+The underlying data source failed to answer the glue query. 1 means some error,
+2 is not implemented. The data source should have logged the specific error
+already.
+
+% QUERY_NOGLUE_FAIL the underlying data source failed with %1
+The underlying data source failed to answer the no-glue query. 1 means some
+error, 2 is not implemented. The data source should have logged the specific
+error already.
+
+% QUERY_REF_FAIL the underlying data source failed with %1
+The underlying data source failed to answer the query for referral information.
+1 means some error, 2 is not implemented. The data source should have logged
+the specific error already.
+
+% QUERY_INVALID_OP invalid query operation requested
+This indicates a programmer error. The DO_QUERY was called with unknown
+operation code.
+
+% QUERY_ADD_RRSET adding RRset '%1/%2' to message
+Debug information. An RRset is being added to the response message.
+
+% QUERY_COPY_AUTH copying authoritative section into message
+Debug information. The whole referral information is being copied into the
+response message.
+
+% QUERY_DELEGATION looking for delegation on the path to '%1'
+Debug information. The software is trying to identify delegation points on the
+way down to the given domain.
+
+% QUERY_ADD_SOA adding SOA of '%1'
+Debug information. A SOA record of the given zone is being added to the
+authority section of the response message.
+
+% QUERY_ADD_NSEC adding NSEC record for '%1'
+Debug information. A NSEC record covering this zone is being added.
+
+% QUERY_ADD_NSEC3 adding NSEC3 record of zone '%1'
+Debug information. A NSEC3 record for the given zone is being added to the
+response message.
+
+% QUERY_NO_DS_NSEC3 there's no DS record in the '%1' zone
+An attempt to add a NSEC3 record into the message failed, because the zone does
+not have any DS record. This indicates problem with the provided data.
+
+% QUERY_NO_DS_NSEC there's no DS record in the '%1' zone
+An attempt to add a NSEC record into the message failed, because the zone does
+not have any DS record. This indicates problem with the provided data.
+
+% QUERY_WILDCARD looking for a wildcard covering '%1'
+Debug information. A direct match wasn't found, so a wildcard covering the
+domain is being looked for now.
+
+% QUERY_WILDCARD_PROVENX_FAIL unable to prove nonexistence of '%1' (%2)
+While processing a wildcard, it wasn't possible to prove nonexistence of the
+given domain or record. The code is 1 for error and 2 for not implemented.
+
+% QUERY_WILDCARD_REFERRAL unable to find referral info for '%1' (%2)
+While processing a wildcard, a referral was met. But it wasn't possible to get
+enough information for it. The code is 1 for error, 2 for not implemented.
+
+% QUERY_PROCESS processing query '%1/%2' in the '%3' class
+Debug information. A sure query is being processed now.
+
+% QUERY_RRSIG unable to answer RRSIG query
+The server is unable to answer a direct query for RRSIG type, but was asked
+to do so.
+
+% QUERY_MISPLACED_TASK task of this type should not be here
+This indicates a programming error. A task was found in the internal task
+queue, but this kind of task wasn't designed to be inside the queue (it should
+be handled right away, not queued).
+
+% QUERY_TASK_FAIL task failed with %1
+The query subtask failed. The reason should have been reported by the subtask
+already. The code is 1 for error, 2 for not implemented.
+
+% QUERY_MISSING_NS missing NS records for '%1'
+NS records should have been put into the authority section. However, this zone
+has none. This indicates problem with provided data.
+
+% UNEXPECTED_QUERY_STATE unexpected query state
+This indicates a programming error. An internal task of unknown type was
+generated.
+
+% QUERY_FAIL query failed
+Some subtask of query processing failed. The reason should have been reported
+already. We are returning SERVFAIL.
+
+% QUERY_BAD_REFERRAL bad referral to '%1'
+The domain lives in another zone. But it is not possible to generate referral
+information for it.
+
+% QUERY_WILDCARD_FAIL error processing wildcard for '%1'
+During an attempt to cover the domain by a wildcard an error happened. The
+exact kind was hopefully already reported.
+
+% QUERY_MISSING_SOA the zone '%1' has no SOA
+The answer should have been a negative one (eg. of nonexistence of something).
+To do so, a SOA record should be put into the authority section, but the zone
+does not have one. This indicates problem with provided data.
+
+% QUERY_PROVENX_FAIL unable to prove nonexistence of '%1'
+The user wants DNSSEC and we discovered the entity doesn't exist (either
+domain or the record). But there was an error getting NSEC/NSEC3 record
+to prove the nonexistence.
+
+% QUERY_UNKNOWN_RESULT unknown result of subtask
+This indicates a programmer error. The answer of subtask doesn't look like
+anything known.
+
+% META_ADD adding a data source into meta data source
+Debug information. Yet another data source is being added into the meta data
+source. (probably at startup or reconfiguration)
+
+% META_ADD_CLASS_MISMATCH mismatch between classes '%1' and '%2'
+It was attempted to add a data source into a meta data source. But their
+classes do not match.
+
+% META_REMOVE removing data source from meta data source
+Debug information. A data source is being removed from meta data source.
+
+% MEM_ADD_WILDCARD adding wildcards for '%1'
+Debug information. Some special marks above each * in wildcard name are needed.
+They are being added now for this name.
+
+% MEM_CNAME_TO_NONEMPTY can't add CNAME to domain with other data in '%1'
+Someone or something tried to add a CNAME into a domain that already contains
+some other data. But the protocol forbids coexistence of CNAME with anything
+(RFC 1034, section 3.6.2). This indicates a problem with provided data.
+
+% MEM_CNAME_COEXIST can't add data to CNAME in domain '%1'
+This is the same problem as in MEM_CNAME_TO_NONEMPTY, but it happened the
+other way around -- adding some outher data to CNAME.
+
+% MEM_DNAME_NS dNAME and NS can't coexist in non-apex domain '%1'
+It was requested for DNAME and NS records to be put into the same domain
+which is not the apex (the top of the zone). This is forbidden by RFC
+2672, section 3. This indicates a problem with provided data.
+
+% MEM_SINGLETON trying to add multiple RRs for domain '%1' and type '%2'
+Some resource types are singletons -- only one is allowed in a domain
+(for example CNAME or SOA). This indicates a problem with provided data.
+
+% MEM_OUT_OF_ZONE domain '%1' doesn't belong to zone '%2'
+It was attempted to add the domain into a zone that shouldn't have it
+(eg. the domain is not subdomain of the zone origin). This indicates a
+problem with provided data.
+
+% MEM_WILDCARD_NS nS record in wildcard domain '%1'
+The software refuses to load NS records into a wildcard domain. It isn't
+explicitly forbidden, but the protocol is ambiguous about how this should
+behave and BIND 9 refuses that as well. Please describe your intention using
+different tools.
+
+% MEM_WILDCARD_DNAME dNAME record in wildcard domain '%1'
+The software refuses to load DNAME records into a wildcard domain. It isn't
+explicitly forbidden, but the protocol is ambiguous about how this should
+behave and BIND 9 refuses that as well. Please describe your intention using
+different tools.
+
+% MEM_ADD_RRSET adding RRset '%1/%2' into zone '%3'
+Debug information. An RRset is being added to the in-memory data source.
+
+% MEM_DUP_RRSET duplicate RRset '%1/%2'
+An RRset is being inserted into in-memory data source for a second time. The
+original version must be removed first. Note that loading master files where an
+RRset is split into multiple locations is not supported yet.
+
+% MEM_DNAME_ENCOUNTERED encountered a DNAME
+Debug information. While searching for the requested domain, a DNAME was
+encountered on the way. This may lead to redirection to a different domain and
+stop the search.
+
+% MEM_NS_ENCOUNTERED encountered a NS
+Debug information. While searching for the requested domain, a NS was
+encountered on the way (a delegation). This may lead to stop of the search.
+
+% MEM_RENAME renaming RRset from '%1' to '%2'
+Debug information. A RRset is being generated from a different RRset (most
+probably a wildcard). So it must be renamed to whatever the user asked for. In
+fact, it's impossible to rename RRsets with our libraries, so a new one is
+created and all resource records are copied over.
+
+% MEM_FIND find '%1/%2'
+Debug information. A search for the requested RRset is being started.
+
+% MEM_DNAME_FOUND DNAME found at '%1'
+Debug information. A DNAME was found instead of the requested information.
+
+% MEM_DELEG_FOUND delegation found at '%1'
+Debug information. A delegation point was found above the requested record.
+
+% MEM_SUPER_STOP stopped at superdomain '%1', domain '%2' is empty
+Debug information. The search stopped at a superdomain of the requested
+domain. The domain is a empty nonterminal, therefore it is treated as NXRRSET
+case (eg. the domain exists, but it doesn't have the requested record type).
+
+% MEM_WILDCARD_CANCEL wildcard match canceled for '%1'
+Debug information. A domain above wildcard was reached, but there's something
+below the requested domain. Therefore the wildcard doesn't apply here. This
+behaviour is specified by RFC 1034, section 4.3.3
+
+% MEM_NOTFOUND requested domain '%1' not found
+Debug information. The requested domain does not exist.
+
+% MEM_DOMAIN_EMPTY requested domain '%1' is empty
+Debug information. The requested domain exists in the tree of domains, but
+it is empty. Therefore it doesn't contain the requested resource type.
+
+% MEM_EXACT_DELEGATION delegation at the exact domain '%1'
+Debug information. There's a NS record at the requested domain. This means
+this zone is not authoritative for the requested domain, but a delegation
+should be followed. The requested domain is an apex of some zone.
+
+% MEM_ANY_SUCCESS ANY query for '%1' successful
+Debug information. The domain was found and an ANY type query is being answered
+by providing everything found inside the domain.
+
+% MEM_SUCCESS query for '%1/%2' successful
+Debug information. The requested record was found.
+
+% MEM_CNAME CNAME at the domain '%1'
+Debug information. The requested domain is an alias to a different domain,
+returning the CNAME instead.
+
+% MEM_NXRRSET no such type '%1' at '%2'
+Debug information. The domain exists, but it doesn't hold any record of the
+requested type.
+
+% MEM_CREATE creating zone '%1' in '%2' class
+Debug information. A representation of a zone for the in-memory data source is
+being created.
+
+% MEM_DESTROY destroying zone '%1' in '%2' class
+Debug information. A zone from in-memory data source is being destroyed.
+
+% MEM_LOAD loading zone '%1' from file '%2'
+Debug information. The content of master file is being loaded into the memory.
+
+% MEM_SWAP swapping contents of two zone representations ('%1' and '%2')
+Debug information. The contents of two in-memory zones are being exchanged.
+This is usual practice to do some manipulation in exception-safe manner -- the
+new data are prepared in a different zone object and when it works, they are
+swapped. The old one contains the new data and the other one can be safely
+destroyed.
+
+% MEM_ADD_ZONE adding zone '%1/%2'
+Debug information. A zone is being added into the in-memory data source.
+
+% MEM_FIND_ZONE looking for zone '%1'
+Debug information. A zone object for this zone is being searched for in the
+in-memory data source.
+
+% STATIC_CREATE creating the static datasource
+Debug information. The static data source (the one holding stuff like
+version.bind) is being created.
+
+% STATIC_BAD_CLASS static data source can handle CH only
+For some reason, someone asked the static data source a query that is not in
+the CH class.
+
+% STATIC_FIND looking for '%1/%2'
+Debug information. This resource record set is being looked up in the static
+data source.
+
+% SQLITE_FINDREC looking for record '%1/%2'
+Debug information. The SQLite data source is looking up records of given name
+and type in the database.
+
+% SQLITE_ENCLOSURE looking for zone containing '%1'
+Debug information. The SQLite data source is trying to identify, which zone
+should hold this domain.
+
+% SQLITE_ENCLOSURE_BAD_CLASS class mismatch looking for a zone ('%1' and '%2')
+The SQLite data source can handle only one class at a time and it was asked
+to identify which zone is holding data of a different class.
+
+% SQLITE_ENCLOSURE_NOTFOUND no zone contains it
+Debug information. The last SQLITE_ENCLOSURE query was unsuccessful, there's
+no such zone in our data.
+
+% SQLITE_PREVIOUS looking for name previous to '%1'
+Debug information. We're trying to look up name preceding the supplied one.
+
+% SQLITE_PREVIOUS_NO_ZONE no zone containing '%1'
+The SQLite data source tried to identify name preceding this one. But this
+one is not contained in any zone in the data source.
+
+% SQLITE_FIND_NSEC3 looking for NSEC3 in zone '%1' for hash '%2'
+Debug information. We're trying to look up a NSEC3 record in the SQLite data
+source.
+
+% SQLITE_FIND_NSEC3_NO_ZONE no such zone '%1'
+The SQLite data source was asked to provide a NSEC3 record for given zone.
+But it doesn't contain that zone.
+
+% SQLITE_FIND looking for RRset '%1/%2'
+Debug information. The SQLite data source is looking up a resource record
+set.
+
+% SQLITE_FIND_BAD_CLASS class mismatch looking for an RRset ('%1' and '%2')
+The SQLite data source was looking up an RRset, but the data source contains
+different class than the query was for.
+
+% SQLITE_FINDEXACT looking for exact RRset '%1/%2'
+Debug information. The SQLite data source is looking up an exact resource
+record.
+
+% SQLITE_FINDEXACT_BAD_CLASS class mismatch looking for an RRset ('%1' and '%2')
+The SQLite data source was looking up an exact RRset, but the data source
+contains different class than the query was for.
+
+% SQLITE_FINDADDRS looking for A/AAAA addresses for '%1'
+Debug information. The data source is looking up the addresses for given
+domain name.
+
+% SQLITE_FINDADDRS_BAD_CLASS class mismatch looking for addresses ('%1' and '%2')
+The SQLite data source was looking up A/AAAA addresses, but the data source
+contains different class than the query was for.
+
+% SQLITE_FINDREF looking for referral at '%1'
+Debug information. The SQLite data source is identifying if this domain is
+a referral and where it goes.
+
+% SQLITE_FINDREF_BAD_CLASS class mismatch looking for referral ('%1' and '%2')
+The SQLite data source was trying to identify, if there's a referral. But
+it contains different class than the query was for.
+
+% SQLITE_CREATE sQLite data source created
+Debug information. An instance of SQLite data source is being created.
+
+% SQLITE_DESTROY sQLite data source destroyed
+Debug information. An instance of SQLite data source is being destroyed.
+
+% SQLITE_SETUP setting up SQLite database
+The database for SQLite data source was found empty. It is assumed this is the
+first run and it is being initialized with current schema. It'll still contain
+no data, but it will be ready for use.
+
+% SQLITE_OPEN opening SQLite database '%1'
+Debug information. The SQLite data source is loading an SQLite database in
+the provided file.
+
+% SQLITE_CLOSE closing SQLite database
+Debug information. The SQLite data source is closing the database file.
diff --git a/src/lib/datasrc/sqlite3_datasrc.cc b/src/lib/datasrc/sqlite3_datasrc.cc
index ab910ba..22f035b 100644
--- a/src/lib/datasrc/sqlite3_datasrc.cc
+++ b/src/lib/datasrc/sqlite3_datasrc.cc
@@ -18,6 +18,7 @@
#include <sqlite3.h>
#include <datasrc/sqlite3_datasrc.h>
+#include <datasrc/logger.h>
#include <dns/rrttl.h>
#include <dns/rdata.h>
@@ -227,6 +228,8 @@ Sqlite3DataSrc::findRecords(const Name& name, const RRType& rdtype,
RRsetList& target, const Name* zonename,
const Mode mode, uint32_t& flags) const
{
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_SQLITE_FINDREC).arg(name).
+ arg(rdtype);
flags = 0;
int zone_id = (zonename == NULL) ? findClosest(name, NULL) :
findClosest(*zonename, NULL);
@@ -345,12 +348,17 @@ Sqlite3DataSrc::findClosest(const Name& name, unsigned int* position) const {
void
Sqlite3DataSrc::findClosestEnclosure(DataSrcMatch& match) const {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_ENCLOSURE).
+ arg(match.getName());
if (match.getClass() != getClass() && match.getClass() != RRClass::ANY()) {
+ LOG_ERROR(logger, DATASRC_SQLITE_ENCLOSURE_BAD_CLASS).arg(getClass()).
+ arg(match.getClass());
return;
}
unsigned int position;
if (findClosest(match.getName(), &position) == -1) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_ENCLOSURE_NOTFOUND);
return;
}
@@ -362,9 +370,11 @@ Sqlite3DataSrc::findPreviousName(const Name& qname,
Name& target,
const Name* zonename) const
{
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_PREVIOUS).arg(qname);
const int zone_id = (zonename == NULL) ?
findClosest(qname, NULL) : findClosest(*zonename, NULL);
if (zone_id < 0) {
+ LOG_ERROR(logger, DATASRC_SQLITE_PREVIOUS_NO_ZONE).arg(qname.toText());
return (ERROR);
}
@@ -402,8 +412,11 @@ Sqlite3DataSrc::findCoveringNSEC3(const Name& zonename,
string& hashstr,
RRsetList& target) const
{
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_FIND_NSEC3).
+ arg(zonename).arg(hashstr);
const int zone_id = findClosest(zonename, NULL);
if (zone_id < 0) {
+ LOG_ERROR(logger, DATASRC_SQLITE_FIND_NSEC3_NO_ZONE).arg(zonename);
return (ERROR);
}
@@ -484,7 +497,11 @@ Sqlite3DataSrc::findRRset(const Name& qname,
uint32_t& flags,
const Name* zonename) const
{
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_FIND).arg(qname).
+ arg(qtype);
if (qclass != getClass() && qclass != RRClass::ANY()) {
+ LOG_ERROR(logger, DATASRC_SQLITE_FIND_BAD_CLASS).arg(getClass()).
+ arg(qclass);
return (ERROR);
}
findRecords(qname, qtype, target, zonename, NORMAL, flags);
@@ -499,7 +516,11 @@ Sqlite3DataSrc::findExactRRset(const Name& qname,
uint32_t& flags,
const Name* zonename) const
{
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_FINDEXACT).arg(qname).
+ arg(qtype);
if (qclass != getClass() && qclass != RRClass::ANY()) {
+ LOG_ERROR(logger, DATASRC_SQLITE_FINDEXACT_BAD_CLASS).arg(getClass()).
+ arg(qclass);
return (ERROR);
}
findRecords(qname, qtype, target, zonename, NORMAL, flags);
@@ -523,7 +544,10 @@ Sqlite3DataSrc::findAddrs(const Name& qname,
uint32_t& flags,
const Name* zonename) const
{
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_FINDADDRS).arg(qname);
if (qclass != getClass() && qclass != RRClass::ANY()) {
+ LOG_ERROR(logger, DATASRC_SQLITE_FINDADDRS_BAD_CLASS).arg(getClass()).
+ arg(qclass);
return (ERROR);
}
findRecords(qname, RRType::ANY(), target, zonename, ADDRESS, flags);
@@ -537,8 +561,11 @@ Sqlite3DataSrc::findReferral(const Name& qname,
uint32_t& flags,
const Name* zonename) const
{
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_FINDREF).arg(qname);
if (qclass != getClass() && qclass != RRClass::ANY()) {
- return (ERROR);
+ LOG_ERROR(logger, DATASRC_SQLITE_FINDREF_BAD_CLASS).arg(getClass()).
+ arg(qclass);
+ return (ERROR);
}
findRecords(qname, RRType::ANY(), target, zonename, DELEGATION, flags);
return (SUCCESS);
@@ -546,9 +573,12 @@ Sqlite3DataSrc::findReferral(const Name& qname,
Sqlite3DataSrc::Sqlite3DataSrc() :
dbparameters(new Sqlite3Parameters)
-{}
+{
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_CREATE);
+}
Sqlite3DataSrc::~Sqlite3DataSrc() {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_DESTROY);
if (dbparameters->db_ != NULL) {
close();
}
@@ -635,6 +665,7 @@ checkAndSetupSchema(Sqlite3Initializer* initializer) {
initializer->params_.version_ = sqlite3_column_int(prepared, 0);
sqlite3_finalize(prepared);
} else {
+ logger.info(DATASRC_SQLITE_SETUP);
if (prepared != NULL) {
sqlite3_finalize(prepared);
}
@@ -664,6 +695,7 @@ checkAndSetupSchema(Sqlite3Initializer* initializer) {
//
void
Sqlite3DataSrc::open(const string& name) {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_OPEN).arg(name);
if (dbparameters->db_ != NULL) {
isc_throw(DataSourceError, "Duplicate SQLite open with " << name);
}
@@ -683,6 +715,7 @@ Sqlite3DataSrc::open(const string& name) {
//
DataSrc::Result
Sqlite3DataSrc::close(void) {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_CLOSE);
if (dbparameters->db_ == NULL) {
isc_throw(DataSourceError,
"SQLite data source is being closed before open");
diff --git a/src/lib/datasrc/static_datasrc.cc b/src/lib/datasrc/static_datasrc.cc
index 025078a..dee14b9 100644
--- a/src/lib/datasrc/static_datasrc.cc
+++ b/src/lib/datasrc/static_datasrc.cc
@@ -26,6 +26,7 @@
#include <datasrc/data_source.h>
#include <datasrc/static_datasrc.h>
+#include <datasrc/logger.h>
using namespace std;
using namespace isc::dns;
@@ -112,6 +113,7 @@ StaticDataSrcImpl::StaticDataSrcImpl() :
}
StaticDataSrc::StaticDataSrc() {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_STATIC_CREATE);
setClass(RRClass::CH());
impl_ = new StaticDataSrcImpl;
}
@@ -155,8 +157,11 @@ StaticDataSrc::findRRset(const Name& qname,
RRsetList& target, uint32_t& flags,
const Name* const zonename) const
{
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_STATIC_FIND).arg(qname).
+ arg(qtype);
flags = 0;
if (qclass != getClass() && qclass != RRClass::ANY()) {
+ LOG_ERROR(logger, DATASRC_STATIC_BAD_CLASS);
return (ERROR);
}
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index b86f9f7..471d802 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -27,16 +27,18 @@ 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 += logger_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
run_unittests_LDADD = $(GTEST_LDADD)
run_unittests_LDADD += $(SQLITE_LIBS)
-run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
run_unittests_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
-run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/datasrc/tests/logger_unittest.cc b/src/lib/datasrc/tests/logger_unittest.cc
new file mode 100644
index 0000000..df5a41c
--- /dev/null
+++ b/src/lib/datasrc/tests/logger_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <gtest/gtest.h>
+
+#include <datasrc/logger.h>
+
+using namespace isc::datasrc;
+
+namespace {
+
+TEST(CacheLogger, name) {
+ // This does not check the name only, but the fact the logger is created
+ // The dot is because of empty root logger
+ std::string name(logger.getName());
+ EXPECT_EQ(name.size() - 8, name.rfind(".datasrc")) <<
+ "Wrong logger name: " << name;
+}
+
+}
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h
index 8a657da..fcc53e9 100644
--- a/src/lib/dns/message.h
+++ b/src/lib/dns/message.h
@@ -601,6 +601,7 @@ private:
/// that ongoing state information will not be lost if the object
/// that originated the asynchronous call falls out of scope.
typedef boost::shared_ptr<Message> MessagePtr;
+typedef boost::shared_ptr<const Message> ConstMessagePtr;
/// Insert the \c Message as a string into stream.
///
diff --git a/src/lib/dns/python/Makefile.am b/src/lib/dns/python/Makefile.am
index 9162f4e..aa9d062 100644
--- a/src/lib/dns/python/Makefile.am
+++ b/src/lib/dns/python/Makefile.am
@@ -5,7 +5,16 @@ AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
pyexec_LTLIBRARIES = pydnspp.la
-pydnspp_la_SOURCES = pydnspp.cc pydnspp_common.cc
+pydnspp_la_SOURCES = pydnspp.cc pydnspp_common.cc pydnspp_towire.h
+pydnspp_la_SOURCES += name_python.cc name_python.h
+pydnspp_la_SOURCES += messagerenderer_python.cc messagerenderer_python.h
+pydnspp_la_SOURCES += rcode_python.cc rcode_python.h
+pydnspp_la_SOURCES += tsigkey_python.cc tsigkey_python.h
+pydnspp_la_SOURCES += tsigerror_python.cc tsigerror_python.h
+pydnspp_la_SOURCES += tsig_rdata_python.cc tsig_rdata_python.h
+pydnspp_la_SOURCES += tsigrecord_python.cc tsigrecord_python.h
+pydnspp_la_SOURCES += tsig_python.cc tsig_python.h
+
pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
@@ -13,19 +22,15 @@ pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
# rules
EXTRA_DIST = pydnspp_common.h
EXTRA_DIST += edns_python.cc
-EXTRA_DIST += messagerenderer_python.cc
EXTRA_DIST += message_python.cc
EXTRA_DIST += rrclass_python.cc
-EXTRA_DIST += name_python.cc
EXTRA_DIST += opcode_python.cc
-EXTRA_DIST += rcode_python.cc
EXTRA_DIST += rrset_python.cc
EXTRA_DIST += question_python.cc
EXTRA_DIST += rrttl_python.cc
EXTRA_DIST += rdata_python.cc
EXTRA_DIST += rrtype_python.cc
-EXTRA_DIST += tsigkey_python.cc
-EXTRA_DIST += tsig_python.cc
+EXTRA_DIST += tsigerror_python_inc.cc
# Python prefers .so, while some OSes (specifically MacOS) use a different
# suffix for dynamic objects. -module is necessary to work this around.
diff --git a/src/lib/dns/python/edns_python.cc b/src/lib/dns/python/edns_python.cc
index d781e89..83c3bfa 100644
--- a/src/lib/dns/python/edns_python.cc
+++ b/src/lib/dns/python/edns_python.cc
@@ -108,7 +108,7 @@ PyMethodDef EDNS_methods[] = {
// Most of the functions are not actually implemented and NULL here.
PyTypeObject edns_type = {
PyVarObject_HEAD_INIT(NULL, 0)
- "libdns_python.EDNS",
+ "pydnspp.EDNS",
sizeof(s_EDNS), // tp_basicsize
0, // tp_itemsize
(destructor)EDNS_destroy, // tp_dealloc
@@ -203,7 +203,7 @@ EDNS_init(s_EDNS* self, PyObject* args) {
// in this context so that we can share the try-catch logic with
// EDNS_createFromRR() (see below).
uint8_t extended_rcode;
- self->edns = createFromRR(*name->name, *rrclass->rrclass,
+ self->edns = createFromRR(*name->cppobj, *rrclass->rrclass,
*rrtype->rrtype, *rrttl->rrttl,
*rdata->rdata, extended_rcode);
return (self->edns != NULL ? 0 : -1);
@@ -334,7 +334,7 @@ EDNS_createFromRR(const s_EDNS* null_self, PyObject* args) {
return (NULL);
}
- edns_obj->edns = createFromRR(*name->name, *rrclass->rrclass,
+ edns_obj->edns = createFromRR(*name->cppobj, *rrclass->rrclass,
*rrtype->rrtype, *rrttl->rrttl,
*rdata->rdata, extended_rcode);
if (edns_obj->edns != NULL) {
diff --git a/src/lib/dns/python/message_python.cc b/src/lib/dns/python/message_python.cc
index e3cc53f..2842588 100644
--- a/src/lib/dns/python/message_python.cc
+++ b/src/lib/dns/python/message_python.cc
@@ -14,6 +14,9 @@
#include <exceptions/exceptions.h>
#include <dns/message.h>
+#include <dns/rcode.h>
+#include <dns/tsig.h>
+
using namespace isc::dns;
using namespace isc::util;
@@ -65,6 +68,7 @@ PyObject* Message_getOpcode(s_Message* self);
PyObject* Message_setOpcode(s_Message* self, PyObject* args);
PyObject* Message_getEDNS(s_Message* self);
PyObject* Message_setEDNS(s_Message* self, PyObject* args);
+PyObject* Message_getTSIGRecord(s_Message* self);
PyObject* Message_getRRCount(s_Message* self, PyObject* args);
// use direct iterators for these? (or simply lists for now?)
PyObject* Message_getQuestion(s_Message* self);
@@ -123,6 +127,11 @@ PyMethodDef Message_methods[] = {
{ "set_edns", reinterpret_cast<PyCFunction>(Message_setEDNS), METH_VARARGS,
"Set EDNS for the message."
},
+ { "get_tsig_record",
+ reinterpret_cast<PyCFunction>(Message_getTSIGRecord), METH_NOARGS,
+ "Return, if any, the TSIG record contained in the received message. "
+ "If no TSIG RR is set in the message, None will be returned."
+ },
{ "get_rr_count", reinterpret_cast<PyCFunction>(Message_getRRCount), METH_VARARGS,
"Returns the number of RRs contained in the given section." },
{ "get_question", reinterpret_cast<PyCFunction>(Message_getQuestion), METH_NOARGS,
@@ -336,15 +345,15 @@ Message_getRcode(s_Message* self) {
rcode = static_cast<s_Rcode*>(rcode_type.tp_alloc(&rcode_type, 0));
if (rcode != NULL) {
- rcode->rcode = NULL;
+ rcode->cppobj = NULL;
try {
- rcode->rcode = new Rcode(self->message->getRcode());
+ rcode->cppobj = new Rcode(self->message->getRcode());
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
} catch (...) {
PyErr_SetString(po_IscException, "Unexpected exception");
}
- if (rcode->rcode == NULL) {
+ if (rcode->cppobj == NULL) {
Py_DECREF(rcode);
return (NULL);
}
@@ -360,7 +369,7 @@ Message_setRcode(s_Message* self, PyObject* args) {
return (NULL);
}
try {
- self->message->setRcode(*rcode->rcode);
+ self->message->setRcode(*rcode->cppobj);
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
@@ -442,6 +451,29 @@ Message_setEDNS(s_Message* self, PyObject* args) {
}
PyObject*
+Message_getTSIGRecord(s_Message* self) {
+ try {
+ const TSIGRecord* tsig_record = self->message->getTSIGRecord();
+
+ if (tsig_record == NULL) {
+ Py_RETURN_NONE;
+ }
+ return (createTSIGRecordObject(*tsig_record));
+ } catch (const InvalidMessageOperation& ex) {
+ PyErr_SetString(po_InvalidMessageOperation, ex.what());
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure in getting TSIGRecord from message: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+ "getting TSIGRecord from message");
+ }
+ return (NULL);
+}
+
+PyObject*
Message_getRRCount(s_Message* self, PyObject* args) {
unsigned int section;
if (!PyArg_ParseTuple(args, "I", §ion)) {
@@ -652,13 +684,12 @@ Message_toWire(s_Message* self, PyObject* args) {
s_TSIGContext* tsig_ctx = NULL;
if (PyArg_ParseTuple(args, "O!|O!", &messagerenderer_type, &mr,
- &tsig_context_type, &tsig_ctx)) {
+ &tsigcontext_type, &tsig_ctx)) {
try {
if (tsig_ctx == NULL) {
self->message->toWire(*mr->messagerenderer);
} else {
- self->message->toWire(*mr->messagerenderer,
- *tsig_ctx->tsig_ctx);
+ self->message->toWire(*mr->messagerenderer, *tsig_ctx->cppobj);
}
// If we return NULL it is seen as an error, so use this for
// None returns
@@ -667,6 +698,11 @@ Message_toWire(s_Message* self, PyObject* args) {
PyErr_Clear();
PyErr_SetString(po_InvalidMessageOperation, imo.what());
return (NULL);
+ } catch (const TSIGContextError& ex) {
+ // toWire() with a TSIG context can fail due to this if the
+ // python program has a bug.
+ PyErr_SetString(po_TSIGContextError, ex.what());
+ return (NULL);
}
}
PyErr_Clear();
diff --git a/src/lib/dns/python/messagerenderer_python.cc b/src/lib/dns/python/messagerenderer_python.cc
index 85a4f17..e6f5d3e 100644
--- a/src/lib/dns/python/messagerenderer_python.cc
+++ b/src/lib/dns/python/messagerenderer_python.cc
@@ -12,39 +12,41 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <Python.h>
+
+#include <util/buffer.h>
+
#include <dns/messagerenderer.h>
-// For each class, we need a struct, a helper functions (init, destroy,
-// and static wrappers around the methods we export), a list of methods,
-// and a type description
+#include "pydnspp_common.h"
+#include "messagerenderer_python.h"
+
using namespace isc::dns;
+using namespace isc::dns::python;
using namespace isc::util;
// MessageRenderer
-// since we don't use *Buffer in the python version (but work with
-// the already existing bytearray type where we use these custom buffers
-// in c++, we need to keep track of one here.
-class s_MessageRenderer : public PyObject {
-public:
- OutputBuffer* outputbuffer;
- MessageRenderer* messagerenderer;
-};
+s_MessageRenderer::s_MessageRenderer() : outputbuffer(NULL),
+ messagerenderer(NULL)
+{
+}
-static int MessageRenderer_init(s_MessageRenderer* self);
-static void MessageRenderer_destroy(s_MessageRenderer* self);
+namespace {
+int MessageRenderer_init(s_MessageRenderer* self);
+void MessageRenderer_destroy(s_MessageRenderer* self);
-static PyObject* MessageRenderer_getData(s_MessageRenderer* self);
-static PyObject* MessageRenderer_getLength(s_MessageRenderer* self);
-static PyObject* MessageRenderer_isTruncated(s_MessageRenderer* self);
-static PyObject* MessageRenderer_getLengthLimit(s_MessageRenderer* self);
-static PyObject* MessageRenderer_getCompressMode(s_MessageRenderer* self);
-static PyObject* MessageRenderer_setTruncated(s_MessageRenderer* self);
-static PyObject* MessageRenderer_setLengthLimit(s_MessageRenderer* self, PyObject* args);
-static PyObject* MessageRenderer_setCompressMode(s_MessageRenderer* self, PyObject* args);
-static PyObject* MessageRenderer_clear(s_MessageRenderer* self);
+PyObject* MessageRenderer_getData(s_MessageRenderer* self);
+PyObject* MessageRenderer_getLength(s_MessageRenderer* self);
+PyObject* MessageRenderer_isTruncated(s_MessageRenderer* self);
+PyObject* MessageRenderer_getLengthLimit(s_MessageRenderer* self);
+PyObject* MessageRenderer_getCompressMode(s_MessageRenderer* self);
+PyObject* MessageRenderer_setTruncated(s_MessageRenderer* self);
+PyObject* MessageRenderer_setLengthLimit(s_MessageRenderer* self, PyObject* args);
+PyObject* MessageRenderer_setCompressMode(s_MessageRenderer* self, PyObject* args);
+PyObject* MessageRenderer_clear(s_MessageRenderer* self);
-static PyMethodDef MessageRenderer_methods[] = {
+PyMethodDef MessageRenderer_methods[] = {
{ "get_data", reinterpret_cast<PyCFunction>(MessageRenderer_getData), METH_NOARGS,
"Returns the data as a bytes() object" },
{ "get_length", reinterpret_cast<PyCFunction>(MessageRenderer_getLength), METH_NOARGS,
@@ -67,69 +69,14 @@ static PyMethodDef MessageRenderer_methods[] = {
{ NULL, NULL, 0, NULL }
};
-static PyTypeObject messagerenderer_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "pydnspp.MessageRenderer",
- sizeof(s_MessageRenderer), // tp_basicsize
- 0, // tp_itemsize
- (destructor)MessageRenderer_destroy,// tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- NULL, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "The MessageRenderer class encapsulates implementation details "
- "of rendering a DNS message into a buffer in wire format. "
- "In effect, it's simply responsible for name compression at least in the "
- "current implementation. A MessageRenderer class object manages the "
- "positions of names rendered in a buffer and uses that information to render "
- "subsequent names with compression.",
- NULL, // tp_traverse
- NULL, // tp_clear
- NULL, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- MessageRenderer_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)MessageRenderer_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
-static int
+int
MessageRenderer_init(s_MessageRenderer* self) {
self->outputbuffer = new OutputBuffer(4096);
self->messagerenderer = new MessageRenderer(*self->outputbuffer);
return (0);
}
-static void
+void
MessageRenderer_destroy(s_MessageRenderer* self) {
delete self->messagerenderer;
delete self->outputbuffer;
@@ -138,19 +85,19 @@ MessageRenderer_destroy(s_MessageRenderer* self) {
Py_TYPE(self)->tp_free(self);
}
-static PyObject*
+PyObject*
MessageRenderer_getData(s_MessageRenderer* self) {
return (Py_BuildValue("y#",
self->messagerenderer->getData(),
self->messagerenderer->getLength()));
}
-static PyObject*
+PyObject*
MessageRenderer_getLength(s_MessageRenderer* self) {
return (Py_BuildValue("I", self->messagerenderer->getLength()));
}
-static PyObject*
+PyObject*
MessageRenderer_isTruncated(s_MessageRenderer* self) {
if (self->messagerenderer->isTruncated()) {
Py_RETURN_TRUE;
@@ -159,23 +106,23 @@ MessageRenderer_isTruncated(s_MessageRenderer* self) {
}
}
-static PyObject*
+PyObject*
MessageRenderer_getLengthLimit(s_MessageRenderer* self) {
return (Py_BuildValue("I", self->messagerenderer->getLengthLimit()));
}
-static PyObject*
+PyObject*
MessageRenderer_getCompressMode(s_MessageRenderer* self) {
return (Py_BuildValue("I", self->messagerenderer->getCompressMode()));
}
-static PyObject*
+PyObject*
MessageRenderer_setTruncated(s_MessageRenderer* self) {
self->messagerenderer->setTruncated();
Py_RETURN_NONE;
}
-static PyObject*
+PyObject*
MessageRenderer_setLengthLimit(s_MessageRenderer* self,
PyObject* args)
{
@@ -195,7 +142,7 @@ MessageRenderer_setLengthLimit(s_MessageRenderer* self,
Py_RETURN_NONE;
}
-static PyObject*
+PyObject*
MessageRenderer_setCompressMode(s_MessageRenderer* self,
PyObject* args)
{
@@ -220,14 +167,71 @@ MessageRenderer_setCompressMode(s_MessageRenderer* self,
}
}
-static PyObject*
+PyObject*
MessageRenderer_clear(s_MessageRenderer* self) {
self->messagerenderer->clear();
Py_RETURN_NONE;
}
+} // end of unnamed namespace
// end of MessageRenderer
-
+namespace isc {
+namespace dns {
+namespace python {
+PyTypeObject messagerenderer_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.MessageRenderer",
+ sizeof(s_MessageRenderer), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)MessageRenderer_destroy,// tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The MessageRenderer class encapsulates implementation details "
+ "of rendering a DNS message into a buffer in wire format. "
+ "In effect, it's simply responsible for name compression at least in the "
+ "current implementation. A MessageRenderer class object manages the "
+ "positions of names rendered in a buffer and uses that information to render "
+ "subsequent names with compression.",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ MessageRenderer_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)MessageRenderer_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
// Module Initialization, all statics are initialized here
bool
@@ -260,5 +264,6 @@ initModulePart_MessageRenderer(PyObject* mod) {
return (true);
}
-
-
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/messagerenderer_python.h b/src/lib/dns/python/messagerenderer_python.h
new file mode 100644
index 0000000..3bb096e
--- /dev/null
+++ b/src/lib/dns/python/messagerenderer_python.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_MESSAGERENDERER_H
+#define __PYTHON_MESSAGERENDERER_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace util {
+class OutputBuffer;
+}
+namespace dns {
+class MessageRenderer;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object.
+//
+// since we don't use *Buffer in the python version (but work with
+// the already existing bytearray type where we use these custom buffers
+// in C++, we need to keep track of one here.
+class s_MessageRenderer : public PyObject {
+public:
+ s_MessageRenderer();
+ isc::util::OutputBuffer* outputbuffer;
+ MessageRenderer* messagerenderer;
+};
+
+extern PyTypeObject messagerenderer_type;
+
+bool initModulePart_MessageRenderer(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_MESSAGERENDERER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/name_python.cc b/src/lib/dns/python/name_python.cc
index b030ba1..d00c6f7 100644
--- a/src/lib/dns/python/name_python.cc
+++ b/src/lib/dns/python/name_python.cc
@@ -12,26 +12,18 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-//
-// Declaration of the custom exceptions
-// Initialization and addition of these go in the module init at the
-// end
-//
-static PyObject* po_EmptyLabel;
-static PyObject* po_TooLongName;
-static PyObject* po_TooLongLabel;
-static PyObject* po_BadLabelType;
-static PyObject* po_BadEscape;
-static PyObject* po_IncompleteName;
-static PyObject* po_InvalidBufferPosition;
-static PyObject* po_DNSMessageFORMERR;
+#include <Python.h>
-//
-// Declaration of enums
-// Initialization and addition of these go in the module init at the
-// end
-//
-static PyObject* po_NameRelation;
+#include <util/buffer.h>
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+
+#include "pydnspp_common.h"
+#include "messagerenderer_python.h"
+#include "name_python.h"
//
// Definition of the classes
@@ -41,21 +33,20 @@ static PyObject* po_NameRelation;
// and static wrappers around the methods we export), a list of methods,
// and a type description
using namespace isc::dns;
+using namespace isc::dns::python;
using namespace isc::util;
+using namespace isc::util::python;
+namespace {
// NameComparisonResult
-class s_NameComparisonResult : public PyObject {
-public:
- isc::dns::NameComparisonResult* ncr;
-};
-static int NameComparisonResult_init(s_NameComparisonResult*, PyObject*);
-static void NameComparisonResult_destroy(s_NameComparisonResult* self);
-static PyObject* NameComparisonResult_getOrder(s_NameComparisonResult* self);
-static PyObject* NameComparisonResult_getCommonLabels(s_NameComparisonResult* self);
-static PyObject* NameComparisonResult_getRelation(s_NameComparisonResult* self);
+int NameComparisonResult_init(s_NameComparisonResult*, PyObject*);
+void NameComparisonResult_destroy(s_NameComparisonResult* self);
+PyObject* NameComparisonResult_getOrder(s_NameComparisonResult* self);
+PyObject* NameComparisonResult_getCommonLabels(s_NameComparisonResult* self);
+PyObject* NameComparisonResult_getRelation(s_NameComparisonResult* self);
-static PyMethodDef NameComparisonResult_methods[] = {
+PyMethodDef NameComparisonResult_methods[] = {
{ "get_order", reinterpret_cast<PyCFunction>(NameComparisonResult_getOrder), METH_NOARGS,
"Returns the order" },
{ "get_common_labels", reinterpret_cast<PyCFunction>(NameComparisonResult_getCommonLabels), METH_NOARGS,
@@ -65,122 +56,61 @@ static PyMethodDef NameComparisonResult_methods[] = {
{ NULL, NULL, 0, NULL }
};
-static PyTypeObject name_comparison_result_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "pydnspp.NameComparisonResult",
- sizeof(s_NameComparisonResult), // tp_basicsize
- 0, // tp_itemsize
- (destructor)NameComparisonResult_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- NULL, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "This is a supplemental class used only as a return value of Name.compare(). "
- "It encapsulate a tuple of the comparison: ordering, number of common labels, "
- "and relationship as follows:\n"
- "- ordering: relative ordering under the DNSSEC order relation\n"
- "- labels: the number of common significant labels of the two names being"
- " compared\n"
- "- relationship: see NameComparisonResult.NameRelation\n",
- NULL, // tp_traverse
- NULL, // tp_clear
- NULL, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- NameComparisonResult_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)NameComparisonResult_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
-static int
+int
NameComparisonResult_init(s_NameComparisonResult*, PyObject*) {
PyErr_SetString(PyExc_NotImplementedError,
"NameComparisonResult can't be built directly");
return (-1);
}
-static void
+void
NameComparisonResult_destroy(s_NameComparisonResult* self) {
- delete self->ncr;
- self->ncr = NULL;
+ delete self->cppobj;
+ self->cppobj = NULL;
Py_TYPE(self)->tp_free(self);
}
-static PyObject*
+PyObject*
NameComparisonResult_getOrder(s_NameComparisonResult* self) {
- return (Py_BuildValue("i", self->ncr->getOrder()));
+ return (Py_BuildValue("i", self->cppobj->getOrder()));
}
-static PyObject*
+PyObject*
NameComparisonResult_getCommonLabels(s_NameComparisonResult* self) {
- return (Py_BuildValue("I", self->ncr->getCommonLabels()));
+ return (Py_BuildValue("I", self->cppobj->getCommonLabels()));
}
-static PyObject*
+PyObject*
NameComparisonResult_getRelation(s_NameComparisonResult* self) {
- return (Py_BuildValue("I", self->ncr->getRelation()));
+ return (Py_BuildValue("I", self->cppobj->getRelation()));
}
-
// end of NameComparisonResult
// Name
-
-class s_Name : public PyObject {
-public:
- isc::dns::Name* name;
- size_t position;
-};
-
-static int Name_init(s_Name* self, PyObject* args);
-static void Name_destroy(s_Name* self);
-
-static PyObject* Name_toWire(s_Name* self, PyObject* args);
-static PyObject* Name_toText(s_Name* self);
-static PyObject* Name_str(PyObject* self);
-static PyObject* Name_getLabelCount(s_Name* self);
-static PyObject* Name_at(s_Name* self, PyObject* args);
-static PyObject* Name_getLength(s_Name* self);
-
-static PyObject* Name_compare(s_Name* self, PyObject* args);
-static PyObject* Name_equals(s_Name* self, PyObject* args);
-
-static PyObject* Name_richcmp(s_Name* self, s_Name* other, int op);
-static PyObject* Name_split(s_Name* self, PyObject* args);
-static PyObject* Name_reverse(s_Name* self);
-static PyObject* Name_concatenate(s_Name* self, PyObject* args);
-static PyObject* Name_downcase(s_Name* self);
-static PyObject* Name_isWildCard(s_Name* self);
-
-static PyMethodDef Name_methods[] = {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_Name, Name> NameContainer;
+
+int Name_init(s_Name* self, PyObject* args);
+void Name_destroy(s_Name* self);
+
+PyObject* Name_toWire(s_Name* self, PyObject* args);
+PyObject* Name_toText(s_Name* self);
+PyObject* Name_str(PyObject* self);
+PyObject* Name_getLabelCount(s_Name* self);
+PyObject* Name_at(s_Name* self, PyObject* args);
+PyObject* Name_getLength(s_Name* self);
+
+PyObject* Name_compare(s_Name* self, PyObject* args);
+PyObject* Name_equals(s_Name* self, PyObject* args);
+
+PyObject* Name_richcmp(s_Name* self, s_Name* other, int op);
+PyObject* Name_split(s_Name* self, PyObject* args);
+PyObject* Name_reverse(s_Name* self);
+PyObject* Name_concatenate(s_Name* self, PyObject* args);
+PyObject* Name_downcase(s_Name* self);
+PyObject* Name_isWildCard(s_Name* self);
+
+PyMethodDef Name_methods[] = {
{ "at", reinterpret_cast<PyCFunction>(Name_at), METH_VARARGS,
"Returns the integer value of the name data at the specified position" },
{ "get_length", reinterpret_cast<PyCFunction>(Name_getLength), METH_NOARGS,
@@ -217,63 +147,7 @@ static PyMethodDef Name_methods[] = {
{ NULL, NULL, 0, NULL }
};
-static PyTypeObject name_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "pydnspp.Name",
- sizeof(s_Name), // tp_basicsize
- 0, // tp_itemsize
- (destructor)Name_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- Name_str, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "The Name class encapsulates DNS names.\n"
- "It provides interfaces to construct a name from string or wire-format data, "
- "transform a name into a string or wire-format data, compare two names, get "
- "access to various properties of a name, etc.",
- NULL, // tp_traverse
- NULL, // tp_clear
- (richcmpfunc)Name_richcmp, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- Name_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)Name_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- // Note: not sure if the following are correct. Added them just to
- // make the compiler happy.
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
-
-static int
+int
Name_init(s_Name* self, PyObject* args) {
const char* s;
PyObject* downcase = Py_False;
@@ -286,7 +160,7 @@ Name_init(s_Name* self, PyObject* args) {
try {
const std::string n(s);
- self->name = new Name(n, downcase == Py_True);
+ self->cppobj = new Name(n, downcase == Py_True);
self->position = 0;
} catch (const EmptyLabel&) {
PyErr_SetString(po_EmptyLabel, "EmptyLabel");
@@ -339,7 +213,7 @@ Name_init(s_Name* self, PyObject* args) {
InputBuffer buffer(bytes, len);
buffer.setPosition(position);
- self->name = new Name(buffer, downcase == Py_True);
+ self->cppobj = new Name(buffer, downcase == Py_True);
self->position = buffer.getPosition();
} catch (const InvalidBufferPosition&) {
PyErr_SetString(po_InvalidBufferPosition,
@@ -361,14 +235,14 @@ Name_init(s_Name* self, PyObject* args) {
return (-1);
}
-static void
+void
Name_destroy(s_Name* self) {
- delete self->name;
- self->name = NULL;
+ delete self->cppobj;
+ self->cppobj = NULL;
Py_TYPE(self)->tp_free(self);
}
-static PyObject*
+PyObject*
Name_at(s_Name* self, PyObject* args) {
int pos;
if (!PyArg_ParseTuple(args, "i", &pos)) {
@@ -382,7 +256,7 @@ Name_at(s_Name* self, PyObject* args) {
}
try {
- return (Py_BuildValue("I", self->name->at(pos)));
+ return (Py_BuildValue("I", self->cppobj->at(pos)));
} catch (const isc::OutOfRange&) {
PyErr_SetString(PyExc_IndexError,
"name index out of range");
@@ -390,22 +264,22 @@ Name_at(s_Name* self, PyObject* args) {
}
}
-static PyObject*
+PyObject*
Name_getLength(s_Name* self) {
- return (Py_BuildValue("i", self->name->getLength()));
+ return (Py_BuildValue("i", self->cppobj->getLength()));
}
-static PyObject*
+PyObject*
Name_getLabelCount(s_Name* self) {
- return (Py_BuildValue("i", self->name->getLabelCount()));
+ return (Py_BuildValue("i", self->cppobj->getLabelCount()));
}
-static PyObject*
+PyObject*
Name_toText(s_Name* self) {
- return (Py_BuildValue("s", self->name->toText().c_str()));
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
}
-static PyObject*
+PyObject*
Name_str(PyObject* self) {
// Simply call the to_text method we already defined
// str() is not defined in the c++ version, only to_text
@@ -415,7 +289,7 @@ Name_str(PyObject* self) {
const_cast<char*>("")));
}
-static PyObject*
+PyObject*
Name_toWire(s_Name* self, PyObject* args) {
PyObject* bytes;
s_MessageRenderer* mr;
@@ -424,7 +298,7 @@ Name_toWire(s_Name* self, PyObject* args) {
PyObject* bytes_o = bytes;
OutputBuffer buffer(Name::MAX_WIRE);
- self->name->toWire(buffer);
+ self->cppobj->toWire(buffer);
PyObject* name_bytes = PyBytes_FromStringAndSize(static_cast<const char*>(buffer.getData()), buffer.getLength());
PyObject* result = PySequence_InPlaceConcat(bytes_o, name_bytes);
// We need to release the object we temporarily created here
@@ -432,7 +306,7 @@ Name_toWire(s_Name* self, PyObject* args) {
Py_DECREF(name_bytes);
return (result);
} else if (PyArg_ParseTuple(args, "O!", &messagerenderer_type, &mr)) {
- self->name->toWire(*mr->messagerenderer);
+ self->cppobj->toWire(*mr->messagerenderer);
// If we return NULL it is seen as an error, so use this for
// None returns
Py_RETURN_NONE;
@@ -443,7 +317,7 @@ Name_toWire(s_Name* self, PyObject* args) {
return (NULL);
}
-static PyObject*
+PyObject*
Name_compare(s_Name* self, PyObject* args) {
s_Name* other;
@@ -452,26 +326,26 @@ Name_compare(s_Name* self, PyObject* args) {
s_NameComparisonResult* ret = PyObject_New(s_NameComparisonResult, &name_comparison_result_type);
if (ret != NULL) {
- ret->ncr = new NameComparisonResult(
- self->name->compare(*other->name));
+ ret->cppobj = new NameComparisonResult(
+ self->cppobj->compare(*other->cppobj));
}
return (ret);
}
-static PyObject*
+PyObject*
Name_equals(s_Name* self, PyObject* args) {
s_Name* other;
if (!PyArg_ParseTuple(args, "O!", &name_type, &other))
return (NULL);
- if (self->name->equals(*other->name))
+ if (self->cppobj->equals(*other->cppobj))
Py_RETURN_TRUE;
else
Py_RETURN_FALSE;
}
-static PyObject*
+PyObject*
Name_split(s_Name* self, PyObject* args) {
int first, n;
s_Name* ret = NULL;
@@ -485,14 +359,14 @@ Name_split(s_Name* self, PyObject* args) {
}
ret = PyObject_New(s_Name, &name_type);
if (ret != NULL) {
- ret->name = NULL;
+ ret->cppobj = NULL;
try {
- ret->name = new Name(self->name->split(first, n));
+ ret->cppobj = new Name(self->cppobj->split(first, n));
} catch(const isc::OutOfRange& oor) {
PyErr_SetString(PyExc_IndexError, oor.what());
- ret->name = NULL;
+ ret->cppobj = NULL;
}
- if (ret->name == NULL) {
+ if (ret->cppobj == NULL) {
Py_DECREF(ret);
return (NULL);
}
@@ -507,14 +381,14 @@ Name_split(s_Name* self, PyObject* args) {
}
ret = PyObject_New(s_Name, &name_type);
if (ret != NULL) {
- ret->name = NULL;
+ ret->cppobj = NULL;
try {
- ret->name = new Name(self->name->split(n));
+ ret->cppobj = new Name(self->cppobj->split(n));
} catch(const isc::OutOfRange& oor) {
PyErr_SetString(PyExc_IndexError, oor.what());
- ret->name = NULL;
+ ret->cppobj = NULL;
}
- if (ret->name == NULL) {
+ if (ret->cppobj == NULL) {
Py_DECREF(ret);
return (NULL);
}
@@ -526,14 +400,13 @@ Name_split(s_Name* self, PyObject* args) {
"No valid type in split argument");
return (ret);
}
-#include <iostream>
//
// richcmp defines the ==, !=, >, <, >= and <= operators in python
// It is translated to a function that gets 3 arguments, an object,
// an object to compare to, and an operator.
//
-static PyObject*
+PyObject*
Name_richcmp(s_Name* self, s_Name* other, int op) {
bool c;
@@ -545,22 +418,22 @@ Name_richcmp(s_Name* self, s_Name* other, int op) {
switch (op) {
case Py_LT:
- c = *self->name < *other->name;
+ c = *self->cppobj < *other->cppobj;
break;
case Py_LE:
- c = *self->name <= *other->name;
+ c = *self->cppobj <= *other->cppobj;
break;
case Py_EQ:
- c = *self->name == *other->name;
+ c = *self->cppobj == *other->cppobj;
break;
case Py_NE:
- c = *self->name != *other->name;
+ c = *self->cppobj != *other->cppobj;
break;
case Py_GT:
- c = *self->name > *other->name;
+ c = *self->cppobj > *other->cppobj;
break;
case Py_GE:
- c = *self->name >= *other->name;
+ c = *self->cppobj >= *other->cppobj;
break;
default:
PyErr_SetString(PyExc_IndexError,
@@ -574,13 +447,13 @@ Name_richcmp(s_Name* self, s_Name* other, int op) {
}
}
-static PyObject*
+PyObject*
Name_reverse(s_Name* self) {
s_Name* ret = PyObject_New(s_Name, &name_type);
if (ret != NULL) {
- ret->name = new Name(self->name->reverse());
- if (ret->name == NULL) {
+ ret->cppobj = new Name(self->cppobj->reverse());
+ if (ret->cppobj == NULL) {
Py_DECREF(ret);
return (NULL);
}
@@ -588,7 +461,7 @@ Name_reverse(s_Name* self) {
return (ret);
}
-static PyObject*
+PyObject*
Name_concatenate(s_Name* self, PyObject* args) {
s_Name* other;
@@ -598,7 +471,7 @@ Name_concatenate(s_Name* self, PyObject* args) {
s_Name* ret = PyObject_New(s_Name, &name_type);
if (ret != NULL) {
try {
- ret->name = new Name(self->name->concatenate(*other->name));
+ ret->cppobj = new Name(self->cppobj->concatenate(*other->cppobj));
} catch (const TooLongName& tln) {
PyErr_SetString(po_TooLongName, tln.what());
return (NULL);
@@ -607,23 +480,159 @@ Name_concatenate(s_Name* self, PyObject* args) {
return (ret);
}
-static PyObject*
+PyObject*
Name_downcase(s_Name* self) {
- self->name->downcase();
+ self->cppobj->downcase();
Py_INCREF(self);
return (self);
}
-static PyObject*
+PyObject*
Name_isWildCard(s_Name* self) {
- if (self->name->isWildcard()) {
+ if (self->cppobj->isWildcard()) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
}
}
// end of Name
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+
+//
+// Definition of the custom exceptions
+// Initialization and addition of these go in the module init at the
+// end
+//
+PyObject* po_EmptyLabel;
+PyObject* po_TooLongName;
+PyObject* po_TooLongLabel;
+PyObject* po_BadLabelType;
+PyObject* po_BadEscape;
+PyObject* po_IncompleteName;
+PyObject* po_InvalidBufferPosition;
+PyObject* po_DNSMessageFORMERR;
+//
+// Definition of enums
+// Initialization and addition of these go in the module init at the
+// end
+//
+PyObject* po_NameRelation;
+
+PyTypeObject name_comparison_result_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.NameComparisonResult",
+ sizeof(s_NameComparisonResult), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)NameComparisonResult_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "This is a supplemental class used only as a return value of Name.compare(). "
+ "It encapsulate a tuple of the comparison: ordering, number of common labels, "
+ "and relationship as follows:\n"
+ "- ordering: relative ordering under the DNSSEC order relation\n"
+ "- labels: the number of common significant labels of the two names being"
+ " compared\n"
+ "- relationship: see NameComparisonResult.NameRelation\n",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ NameComparisonResult_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)NameComparisonResult_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+PyTypeObject name_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.Name",
+ sizeof(s_Name), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)Name_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ Name_str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The Name class encapsulates DNS names.\n"
+ "It provides interfaces to construct a name from string or wire-format data, "
+ "transform a name into a string or wire-format data, compare two names, get "
+ "access to various properties of a name, etc.",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ (richcmpfunc)Name_richcmp, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ Name_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)Name_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ // Note: not sure if the following are correct. Added them just to
+ // make the compiler happy.
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
// Module Initialization, all statics are initialized here
bool
@@ -669,7 +678,7 @@ initModulePart_Name(PyObject* mod) {
addClassVariable(name_type, "COMPRESS_POINTER_MARK16", Py_BuildValue("I", Name::COMPRESS_POINTER_MARK16));
s_Name* root_name = PyObject_New(s_Name, &name_type);
- root_name->name = new Name(Name::ROOT_NAME());
+ root_name->cppobj = new Name(Name::ROOT_NAME());
PyObject* po_ROOT_NAME = root_name;
addClassVariable(name_type, "ROOT_NAME", po_ROOT_NAME);
@@ -706,3 +715,13 @@ initModulePart_Name(PyObject* mod) {
return (true);
}
+
+PyObject*
+createNameObject(const Name& source) {
+ NameContainer container = PyObject_New(s_Name, &name_type);
+ container.set(new Name(source));
+ return (container.release());
+}
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/name_python.h b/src/lib/dns/python/name_python.h
new file mode 100644
index 0000000..f8e793d
--- /dev/null
+++ b/src/lib/dns/python/name_python.h
@@ -0,0 +1,84 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_NAME_H
+#define __PYTHON_NAME_H 1
+
+#include <Python.h>
+
+#include <util/python/pycppwrapper_util.h>
+
+namespace isc {
+namespace dns {
+class NameComparisonResult;
+class Name;
+
+namespace python {
+
+//
+// Declaration of the custom exceptions
+// Initialization and addition of these go in the module init at the
+// end
+//
+extern PyObject* po_EmptyLabel;
+extern PyObject* po_TooLongName;
+extern PyObject* po_TooLongLabel;
+extern PyObject* po_BadLabelType;
+extern PyObject* po_BadEscape;
+extern PyObject* po_IncompleteName;
+extern PyObject* po_InvalidBufferPosition;
+extern PyObject* po_DNSMessageFORMERR;
+
+//
+// Declaration of enums
+// Initialization and addition of these go in the module init at the
+// end
+//
+extern PyObject* po_NameRelation;
+
+// The s_* Class simply covers one instantiation of the object.
+class s_NameComparisonResult : public PyObject {
+public:
+ s_NameComparisonResult() : cppobj(NULL) {}
+ NameComparisonResult* cppobj;
+};
+
+class s_Name : public PyObject {
+public:
+ s_Name() : cppobj(NULL), position(0) {}
+ Name* cppobj;
+ size_t position;
+};
+
+extern PyTypeObject name_comparison_result_type;
+extern PyTypeObject name_type;
+
+bool initModulePart_Name(PyObject* mod);
+
+/// This is A simple shortcut to create a python Name object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called with in a try block
+/// followed by necessary setup for python exception.
+PyObject* createNameObject(const Name& source);
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_NAME_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/pydnspp.cc b/src/lib/dns/python/pydnspp.cc
index 2138198..07abf71 100644
--- a/src/lib/dns/python/pydnspp.cc
+++ b/src/lib/dns/python/pydnspp.cc
@@ -32,22 +32,37 @@
#include <exceptions/exceptions.h>
#include <util/buffer.h>
+
#include <dns/exceptions.h>
#include <dns/name.h>
#include <dns/messagerenderer.h>
-#include <dns/python/pydnspp_common.h>
-
+#include "pydnspp_common.h"
+#include "messagerenderer_python.h"
+#include "name_python.h"
+#include "rcode_python.h"
+#include "tsigkey_python.h"
+#include "tsig_rdata_python.h"
+#include "tsigerror_python.h"
+#include "tsigrecord_python.h"
+#include "tsig_python.h"
+
+namespace isc {
+namespace dns {
+namespace python {
// For our 'general' isc::Exceptions
-static PyObject* po_IscException;
-static PyObject* po_InvalidParameter;
+PyObject* po_IscException;
+PyObject* po_InvalidParameter;
// For our own isc::dns::Exception
-static PyObject* po_DNSMessageBADVERS;
+PyObject* po_DNSMessageBADVERS;
+}
+}
+}
// order is important here!
-#include <dns/python/messagerenderer_python.cc>
-#include <dns/python/name_python.cc> // needs Messagerenderer
+using namespace isc::dns::python;
+
#include <dns/python/rrclass_python.cc> // needs Messagerenderer
#include <dns/python/rrtype_python.cc> // needs Messagerenderer
#include <dns/python/rrttl_python.cc> // needs Messagerenderer
@@ -55,17 +70,15 @@ static PyObject* po_DNSMessageBADVERS;
#include <dns/python/rrset_python.cc> // needs Rdata, RRTTL
#include <dns/python/question_python.cc> // needs RRClass, RRType, RRTTL,
// Name
-#include <dns/python/tsigkey_python.cc> // needs Name
-#include <dns/python/tsig_python.cc> // needs tsigkey
#include <dns/python/opcode_python.cc>
-#include <dns/python/rcode_python.cc>
#include <dns/python/edns_python.cc> // needs Messagerenderer, Rcode
#include <dns/python/message_python.cc> // needs RRset, Question
//
// Definition of the module
//
-static PyModuleDef pydnspp = {
+namespace {
+PyModuleDef pydnspp = {
{ PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
"pydnspp",
"Python bindings for the classes in the isc::dns namespace.\n\n"
@@ -80,10 +93,11 @@ static PyModuleDef pydnspp = {
NULL,
NULL
};
+}
PyMODINIT_FUNC
PyInit_pydnspp(void) {
- PyObject *mod = PyModule_Create(&pydnspp);
+ PyObject* mod = PyModule_Create(&pydnspp);
if (mod == NULL) {
return (NULL);
}
@@ -154,10 +168,21 @@ PyInit_pydnspp(void) {
return (NULL);
}
+ if (!initModulePart_TSIG(mod)) {
+ return (NULL);
+ }
+
+ if (!initModulePart_TSIGError(mod)) {
+ return (NULL);
+ }
+
+ if (!initModulePart_TSIGRecord(mod)) {
+ return (NULL);
+ }
+
if (!initModulePart_TSIGContext(mod)) {
return (NULL);
}
return (mod);
}
-
diff --git a/src/lib/dns/python/pydnspp_common.cc b/src/lib/dns/python/pydnspp_common.cc
index 6c26367..8ca763a 100644
--- a/src/lib/dns/python/pydnspp_common.cc
+++ b/src/lib/dns/python/pydnspp_common.cc
@@ -15,6 +15,9 @@
#include <Python.h>
#include <pydnspp_common.h>
+namespace isc {
+namespace dns {
+namespace python {
int
readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence) {
PyObject* el = NULL;
@@ -44,8 +47,15 @@ readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence) {
}
-void addClassVariable(PyTypeObject& c, const char* name,
- PyObject* obj)
-{
- PyDict_SetItemString(c.tp_dict, name, obj);
+int
+addClassVariable(PyTypeObject& c, const char* name, PyObject* obj) {
+ if (obj == NULL) {
+ PyErr_SetString(PyExc_ValueError,
+ "NULL object is specified for a class variable");
+ return (-1);
+ }
+ return (PyDict_SetItemString(c.tp_dict, name, obj));
+}
+}
+}
}
diff --git a/src/lib/dns/python/pydnspp_common.h b/src/lib/dns/python/pydnspp_common.h
index 32e2b78..ed90998 100644
--- a/src/lib/dns/python/pydnspp_common.h
+++ b/src/lib/dns/python/pydnspp_common.h
@@ -15,9 +15,22 @@
#ifndef __LIBDNS_PYTHON_COMMON_H
#define __LIBDNS_PYTHON_COMMON_H 1
-//
-// Shared functions for python/c API
-//
+#include <Python.h>
+
+#include <stdexcept>
+#include <string>
+
+#include <util/python/pycppwrapper_util.h>
+
+namespace isc {
+namespace dns {
+namespace python {
+// For our 'general' isc::Exceptions
+extern PyObject* po_IscException;
+extern PyObject* po_InvalidParameter;
+
+// For our own isc::dns::Exception
+extern PyObject* po_DNSMessageBADVERS;
// This function reads 'bytes' from a sequence
// This sequence can be anything that implements the Sequence interface,
@@ -31,6 +44,12 @@
// case nothing is removed
int readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence);
-void addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
-
+int addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
+} // namespace python
+} // namespace dns
+} // namespace isc
#endif // __LIBDNS_PYTHON_COMMON_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/pydnspp_towire.h b/src/lib/dns/python/pydnspp_towire.h
new file mode 100644
index 0000000..66362a0
--- /dev/null
+++ b/src/lib/dns/python/pydnspp_towire.h
@@ -0,0 +1,127 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 __LIBDNS_PYTHON_TOWIRE_H
+#define __LIBDNS_PYTHON_TOWIRE_H 1
+
+#include <Python.h>
+
+#include <stdexcept>
+#include <string>
+
+#include <dns/messagerenderer.h>
+
+#include <util/buffer.h>
+#include <util/python/pycppwrapper_util.h>
+
+#include "messagerenderer_python.h"
+
+namespace isc {
+namespace dns {
+namespace python {
+
+// The following two templated structures are a helper to use the same
+// toWire() template implementation for two types of toWire() methods:
+// return an integer or have no return value.
+template <typename CPPCLASS>
+struct ToWireCallVoid {
+ ToWireCallVoid(CPPCLASS& cppobj) : cppobj_(cppobj) {}
+ int operator()(AbstractMessageRenderer& renderer) const {
+ cppobj_.toWire(renderer);
+ return (0);
+ }
+ const CPPCLASS& cppobj_;
+};
+
+template <typename CPPCLASS>
+struct ToWireCallInt {
+ ToWireCallInt(CPPCLASS& cppobj) : cppobj_(cppobj) {}
+ int operator()(AbstractMessageRenderer& renderer) const {
+ return (cppobj_.toWire(renderer));
+ }
+ const CPPCLASS& cppobj_;
+};
+
+// This templated function gives a common implementation of the toWire()
+// wrapper for various libdns++ classes. PYSTRUCT and CPPCLASS are
+// (C++ binding of) python and (pure) C++ classes (e.g., s_Name and Name),
+// and TOWIRECALLER is either ToWireCallVoid<CPPCLASS> or
+// ToWireCallInt<CPPCLASS>, depending on the toWire() method of the class
+// returns a value or not.
+//
+// See, e.g., tsigrecord_python.cc for how to use it.
+//
+// This should be able to be used without modification for most classes that
+// have toWire(). But if the underlying toWire() has an extra argument, the
+// definition will need to be adjusted accordingly.
+template <typename PYSTRUCT, typename CPPCLASS, typename TOWIRECALLER>
+PyObject*
+toWireWrapper(const PYSTRUCT* const self, PyObject* args) {
+ try {
+ // To OutputBuffer version
+ PyObject* bytes; // this won't have own reference, no risk of leak.
+ if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
+ // render the object into a buffer (this can throw)
+ isc::util::OutputBuffer buffer(0);
+ self->cppobj->toWire(buffer);
+
+ // convert the rendered data into PyObject. This could leak later,
+ // so we need to store it in a container.
+ PyObject* rd_bytes = PyBytes_FromStringAndSize(
+ static_cast<const char*>(buffer.getData()),
+ buffer.getLength());
+ isc::util::python::PyObjectContainer rd_bytes_container(rd_bytes);
+
+ // concat the latest data to the given existing sequence. concat
+ // operation could fail, so we use a container to clean it up
+ // safely should that happen.
+ PyObject* result = PySequence_InPlaceConcat(bytes, rd_bytes);
+ isc::util::python::PyObjectContainer result_container(result);
+
+ return (result_container.release());
+ }
+
+ // To MessageRenderer version
+ s_MessageRenderer* renderer;
+ if (PyArg_ParseTuple(args, "O!", &messagerenderer_type, &renderer)) {
+ const unsigned int n = TOWIRECALLER(*self->cppobj)(
+ *renderer->messagerenderer);
+
+ return (Py_BuildValue("I", n));
+ }
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Failed to render an libdns++ object wire-format: "
+ + std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(po_IscException, "Unexpectedly failed to render an "
+ "libdns++ object wire-format.");
+ return (NULL);
+ }
+
+ PyErr_Clear();
+ PyErr_SetString(PyExc_TypeError,
+ "Incorrect arguments for a to_wire() method");
+ return (NULL);
+}
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __LIBDNS_PYTHON_TOWIRE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/question_python.cc b/src/lib/dns/python/question_python.cc
index 2889350..c702f85 100644
--- a/src/lib/dns/python/question_python.cc
+++ b/src/lib/dns/python/question_python.cc
@@ -144,7 +144,7 @@ Question_init(s_Question* self, PyObject* args) {
&rrclass_type, &rrclass,
&rrtype_type, &rrtype
)) {
- self->question = QuestionPtr(new Question(*name->name, *rrclass->rrclass,
+ self->question = QuestionPtr(new Question(*name->cppobj, *rrclass->rrclass,
*rrtype->rrtype));
return (0);
} else if (PyArg_ParseTuple(args, "y#|I", &b, &len, &position)) {
@@ -189,7 +189,7 @@ Question_getName(s_Question* self) {
// is this the best way to do this?
name = static_cast<s_Name*>(name_type.tp_alloc(&name_type, 0));
if (name != NULL) {
- name->name = new Name(self->question->getName());
+ name->cppobj = new Name(self->question->getName());
}
return (name);
diff --git a/src/lib/dns/python/rcode_python.cc b/src/lib/dns/python/rcode_python.cc
index b80a93c..b594ad3 100644
--- a/src/lib/dns/python/rcode_python.cc
+++ b/src/lib/dns/python/rcode_python.cc
@@ -12,9 +12,17 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <Python.h>
+
+#include <exceptions/exceptions.h>
+
#include <dns/rcode.h>
+#include "pydnspp_common.h"
+#include "rcode_python.h"
+
using namespace isc::dns;
+using namespace isc::dns::python;
//
// Declaration of the custom exceptions (None for this class)
@@ -27,25 +35,14 @@ using namespace isc::dns;
// and static wrappers around the methods we export), a list of methods,
// and a type description
-namespace {
//
// Rcode
//
-// We added a helper variable static_code here
-// Since we can create Rcodes dynamically with Rcode(int), but also
-// use the static globals (Rcode::NOERROR() etc), we use this
-// variable to see if the code came from one of the latter, in which
-// case Rcode_destroy should not free it (the other option is to
-// allocate new Rcodes for every use of the static ones, but this
-// seems more efficient).
-class s_Rcode : public PyObject {
-public:
- s_Rcode() : rcode(NULL), static_code(false) {}
- const Rcode* rcode;
- bool static_code;
-};
+// Trivial constructor.
+s_Rcode::s_Rcode() : cppobj(NULL), static_code(false) {}
+namespace {
int Rcode_init(s_Rcode* const self, PyObject* args);
void Rcode_destroy(s_Rcode* const self);
@@ -118,57 +115,6 @@ PyMethodDef Rcode_methods[] = {
{ NULL, NULL, 0, NULL }
};
-PyTypeObject rcode_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "pydnspp.Rcode",
- sizeof(s_Rcode), // tp_basicsize
- 0, // tp_itemsize
- (destructor)Rcode_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- Rcode_str, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "The Rcode class objects represent standard RCODEs"
- "of the header section of DNS messages.",
- NULL, // tp_traverse
- NULL, // tp_clear
- (richcmpfunc)Rcode_richcmp, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- Rcode_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)Rcode_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
int
Rcode_init(s_Rcode* const self, PyObject* args) {
long code = 0;
@@ -193,9 +139,9 @@ Rcode_init(s_Rcode* const self, PyObject* args) {
}
try {
if (ext_code == -1) {
- self->rcode = new Rcode(code);
+ self->cppobj = new Rcode(code);
} else {
- self->rcode = new Rcode(code, ext_code);
+ self->cppobj = new Rcode(code, ext_code);
}
self->static_code = false;
} catch (const isc::OutOfRange& ex) {
@@ -211,27 +157,27 @@ Rcode_init(s_Rcode* const self, PyObject* args) {
void
Rcode_destroy(s_Rcode* const self) {
// Depending on whether we created the rcode or are referring
- // to a global one, we do or do not delete self->rcode here
+ // to a global one, we do or do not delete self->cppobj here
if (!self->static_code) {
- delete self->rcode;
+ delete self->cppobj;
}
- self->rcode = NULL;
+ self->cppobj = NULL;
Py_TYPE(self)->tp_free(self);
}
PyObject*
Rcode_getCode(const s_Rcode* const self) {
- return (Py_BuildValue("I", self->rcode->getCode()));
+ return (Py_BuildValue("I", self->cppobj->getCode()));
}
PyObject*
Rcode_getExtendedCode(const s_Rcode* const self) {
- return (Py_BuildValue("I", self->rcode->getExtendedCode()));
+ return (Py_BuildValue("I", self->cppobj->getExtendedCode()));
}
PyObject*
Rcode_toText(const s_Rcode* const self) {
- return (Py_BuildValue("s", self->rcode->toText().c_str()));
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
}
PyObject*
@@ -245,7 +191,7 @@ PyObject*
Rcode_createStatic(const Rcode& rcode) {
s_Rcode* ret = PyObject_New(s_Rcode, &rcode_type);
if (ret != NULL) {
- ret->rcode = &rcode;
+ ret->cppobj = &rcode;
ret->static_code = true;
}
return (ret);
@@ -357,10 +303,10 @@ Rcode_richcmp(const s_Rcode* const self, const s_Rcode* const other,
PyErr_SetString(PyExc_TypeError, "Unorderable type; Rcode");
return (NULL);
case Py_EQ:
- c = (*self->rcode == *other->rcode);
+ c = (*self->cppobj == *other->cppobj);
break;
case Py_NE:
- c = (*self->rcode != *other->rcode);
+ c = (*self->cppobj != *other->cppobj);
break;
case Py_GT:
PyErr_SetString(PyExc_TypeError, "Unorderable type; Rcode");
@@ -374,6 +320,61 @@ Rcode_richcmp(const s_Rcode* const self, const s_Rcode* const other,
else
Py_RETURN_FALSE;
}
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+PyTypeObject rcode_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.Rcode",
+ sizeof(s_Rcode), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)Rcode_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ Rcode_str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The Rcode class objects represent standard RCODEs"
+ "of the header section of DNS messages.",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ reinterpret_cast<richcmpfunc>(Rcode_richcmp), // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ Rcode_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)Rcode_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
// Module Initialization, all statics are initialized here
bool
@@ -428,4 +429,6 @@ initModulePart_Rcode(PyObject* mod) {
return (true);
}
-} // end of unnamed namespace
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/rcode_python.h b/src/lib/dns/python/rcode_python.h
new file mode 100644
index 0000000..9b5e699
--- /dev/null
+++ b/src/lib/dns/python/rcode_python.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_RCODE_H
+#define __PYTHON_RCODE_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class Rcode;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object.
+//
+// We added a helper variable static_code here
+// Since we can create Rcodes dynamically with Rcode(int), but also
+// use the static globals (Rcode::NOERROR() etc), we use this
+// variable to see if the code came from one of the latter, in which
+// case Rcode_destroy should not free it (the other option is to
+// allocate new Rcodes for every use of the static ones, but this
+// seems more efficient).
+//
+// Follow-up note: we don't have to use the proxy function in the python lib;
+// we can just define class specific constants directly (see TSIGError).
+// We should make this cleanup later.
+class s_Rcode : public PyObject {
+public:
+ s_Rcode();
+ const Rcode* cppobj;
+ bool static_code;
+};
+
+extern PyTypeObject rcode_type;
+
+bool initModulePart_Rcode(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_RCODE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/rrset_python.cc b/src/lib/dns/python/rrset_python.cc
index c7d05d1..71a0710 100644
--- a/src/lib/dns/python/rrset_python.cc
+++ b/src/lib/dns/python/rrset_python.cc
@@ -168,7 +168,7 @@ RRset_init(s_RRset* self, PyObject* args) {
&rrtype_type, &rrtype,
&rrttl_type, &rrttl
)) {
- self->rrset = RRsetPtr(new RRset(*name->name, *rrclass->rrclass,
+ self->rrset = RRsetPtr(new RRset(*name->cppobj, *rrclass->rrclass,
*rrtype->rrtype, *rrttl->rrttl));
return (0);
}
@@ -197,8 +197,8 @@ RRset_getName(s_RRset* self) {
// is this the best way to do this?
name = static_cast<s_Name*>(name_type.tp_alloc(&name_type, 0));
if (name != NULL) {
- name->name = new Name(self->rrset->getName());
- if (name->name == NULL)
+ name->cppobj = new Name(self->rrset->getName());
+ if (name->cppobj == NULL)
{
Py_DECREF(name);
return (NULL);
@@ -265,7 +265,7 @@ RRset_setName(s_RRset* self, PyObject* args) {
if (!PyArg_ParseTuple(args, "O!", &name_type, &name)) {
return (NULL);
}
- self->rrset->setName(*name->name);
+ self->rrset->setName(*name->cppobj);
Py_RETURN_NONE;
}
diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am
index 9ee98c7..eb0c136 100644
--- a/src/lib/dns/python/tests/Makefile.am
+++ b/src/lib/dns/python/tests/Makefile.am
@@ -12,7 +12,10 @@ PYTESTS += rrset_python_test.py
PYTESTS += rrttl_python_test.py
PYTESTS += rrtype_python_test.py
PYTESTS += tsig_python_test.py
+PYTESTS += tsig_rdata_python_test.py
+PYTESTS += tsigerror_python_test.py
PYTESTS += tsigkey_python_test.py
+PYTESTS += tsigrecord_python_test.py
EXTRA_DIST = $(PYTESTS)
EXTRA_DIST += testutil.py
@@ -33,7 +36,7 @@ if ENABLE_PYTHON_COVERAGE
endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
- env PYTHONPATH=$(abs_top_srcdir)/src/lib/dns/.libs:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
+ env PYTHONPATH=$(abs_top_builddir)/src/lib/util/pyunittests/.libs:$(abs_top_srcdir)/src/lib/dns/.libs:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
TESTDATA_PATH=$(abs_top_srcdir)/src/lib/dns/tests/testdata:$(abs_top_builddir)/src/lib/dns/tests/testdata \
$(LIBRARY_PATH_PLACEHOLDER) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
diff --git a/src/lib/dns/python/tests/message_python_test.py b/src/lib/dns/python/tests/message_python_test.py
index 72807cc..41b9a67 100644
--- a/src/lib/dns/python/tests/message_python_test.py
+++ b/src/lib/dns/python/tests/message_python_test.py
@@ -422,7 +422,54 @@ test.example.com. 3600 IN A 192.0.2.2
factoryFromFile,
message_parse,
"message_fromWire9")
-
+
+ def test_from_wire_with_tsig(self):
+ # Initially there should be no TSIG
+ self.assertEqual(None, self.p.get_tsig_record())
+
+ # getTSIGRecord() is only valid in the parse mode.
+ self.assertRaises(InvalidMessageOperation, self.r.get_tsig_record)
+
+ factoryFromFile(self.p, "message_toWire2.wire")
+ tsig_rr = self.p.get_tsig_record()
+ self.assertEqual(Name("www.example.com"), tsig_rr.get_name())
+ self.assertEqual(85, tsig_rr.get_length())
+ self.assertEqual(TSIGKey.HMACMD5_NAME,
+ tsig_rr.get_rdata().get_algorithm())
+
+ # If we clear the message for reuse, the recorded TSIG will be cleared.
+ self.p.clear(Message.PARSE)
+ self.assertEqual(None, self.p.get_tsig_record())
+
+ def test_from_wire_with_tsigcompressed(self):
+ # Mostly same as fromWireWithTSIG, but the TSIG owner name is
+ # compressed.
+ factoryFromFile(self.p, "message_fromWire12.wire");
+ tsig_rr = self.p.get_tsig_record()
+ self.assertEqual(Name("www.example.com"), tsig_rr.get_name())
+ # len(www.example.com) = 17, but when fully compressed, the length is
+ # 2 bytes. So the length of the record should be 15 bytes shorter.
+ self.assertEqual(70, tsig_rr.get_length())
+
+ def test_from_wire_with_badtsig(self):
+ # Multiple TSIG RRs
+ self.assertRaises(DNSMessageFORMERR, factoryFromFile,
+ self.p, "message_fromWire13.wire")
+ self.p.clear(Message.PARSE)
+
+ # TSIG in the answer section (must be in additional)
+ self.assertRaises(DNSMessageFORMERR, factoryFromFile,
+ self.p, "message_fromWire14.wire")
+ self.p.clear(Message.PARSE)
+
+ # TSIG is not the last record.
+ self.assertRaises(DNSMessageFORMERR, factoryFromFile,
+ self.p, "message_fromWire15.wire")
+ self.p.clear(Message.PARSE)
+
+ # Unexpected RR Class (this will fail in constructing TSIGRecord)
+ self.assertRaises(DNSMessageFORMERR, factoryFromFile,
+ self.p, "message_fromWire16.wire")
if __name__ == '__main__':
unittest.main()
diff --git a/src/lib/dns/python/tests/tsig_python_test.py b/src/lib/dns/python/tests/tsig_python_test.py
index bffa0cf..7e5515d 100644
--- a/src/lib/dns/python/tests/tsig_python_test.py
+++ b/src/lib/dns/python/tests/tsig_python_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010 Internet Systems Consortium.
+# 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
@@ -13,17 +13,542 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-import unittest
+import base64, sys, time, unittest
from pydnspp import *
+from testutil import *
+from pyunittests_util import fix_current_time
+
+# bit-wise constant flags to configure DNS header flags for test
+# messages.
+QR_FLAG = 0x1
+AA_FLAG = 0x2
+RD_FLAG = 0x4
+
+COMMON_EXPECTED_MAC = b"\x22\x70\x26\xad\x29\x7b\xee\xe7\x21\xce\x6c\x6f\xff\x1e\x9e\xf3"
+DUMMY_DATA = b"\xdd" * 100
class TSIGContextTest(unittest.TestCase):
tsig_key = TSIGKey('www.example.com:SFuWd/q99SzF8Yzd1QbB9g==')
def setUp(self):
- # In the minimal implementation, we simply check constructing a
- # TSIGContext doesn't cause any disruption. We can add more tests
- # later.
+ # make sure we don't use faked time unless explicitly do so in tests
+ fix_current_time(None)
+ self.qid = 0x2d65
+ self.test_name = Name("www.example.com")
self.tsig_ctx = TSIGContext(self.tsig_key)
+ self.tsig_verify_ctx = TSIGContext(self.tsig_key)
+ self.keyring = TSIGKeyRing()
+ self.message = Message(Message.RENDER)
+ self.renderer = MessageRenderer()
+ self.test_class = RRClass.IN()
+ self.test_ttl = RRTTL(86400)
+ self.secret = base64.b64decode(b"SFuWd/q99SzF8Yzd1QbB9g==")
+ self.tsig_ctx = TSIGContext(TSIGKey(self.test_name,
+ TSIGKey.HMACMD5_NAME,
+ self.secret))
+ self.badkey_name = Name("badkey.example.com")
+ self.dummy_record = TSIGRecord(self.badkey_name,
+ TSIG("hmac-md5.sig-alg.reg.int. " + \
+ "1302890362 300 0 11621 " + \
+ "0 0"))
+
+ def tearDown(self):
+ # reset any faked current time setting (it would affect other tests)
+ fix_current_time(None)
+
+ # Note: intentionally use camelCase so that we can easily copy-paste
+ # corresponding C++ tests.
+ def createMessageAndSign(self, id, qname, ctx, message_flags=RD_FLAG,
+ qtype=RRType.A(), answer_data=None,
+ answer_type=None, add_question=True,
+ rcode=Rcode.NOERROR()):
+ self.message.clear(Message.RENDER)
+ self.message.set_qid(id)
+ self.message.set_opcode(Opcode.QUERY())
+ self.message.set_rcode(rcode)
+ if (message_flags & QR_FLAG) != 0:
+ self.message.set_header_flag(Message.HEADERFLAG_QR)
+ if (message_flags & AA_FLAG) != 0:
+ self.message.set_header_flag(Message.HEADERFLAG_AA)
+ if (message_flags & RD_FLAG) != 0:
+ self.message.set_header_flag(Message.HEADERFLAG_RD)
+ if add_question:
+ self.message.add_question(Question(qname, self.test_class, qtype))
+ if answer_data is not None:
+ if answer_type is None:
+ answer_type = qtype
+ answer_rrset = RRset(qname, self.test_class, answer_type,
+ self.test_ttl)
+ answer_rrset.add_rdata(Rdata(answer_type, self.test_class,
+ answer_data))
+ self.message.add_rrset(Message.SECTION_ANSWER, answer_rrset)
+ self.renderer.clear()
+ self.message.to_wire(self.renderer)
+
+ if ctx.get_state() == TSIGContext.STATE_INIT:
+ expected_new_state = TSIGContext.STATE_SENT_REQUEST
+ else:
+ expected_new_state = TSIGContext.STATE_SENT_RESPONSE
+ tsig = ctx.sign(id, self.renderer.get_data())
+
+ return tsig
+
+ # Note: intentionally use camelCase so that we can easily copy-paste
+ # corresponding C++ tests.
+ def createMessageFromFile(self, file):
+ self.message.clear(Message.PARSE)
+ self.received_data = read_wire_data(file)
+ self.message.from_wire(self.received_data)
+
+ # Note: intentionally use camelCase so that we can easily copy-paste
+ # corresponding C++ tests.
+ def commonSignChecks(self, tsig, expected_qid, expected_timesigned,
+ expected_mac, expected_error=0,
+ expected_otherdata=None,
+ expected_algorithm=TSIGKey.HMACMD5_NAME):
+ tsig_rdata = tsig.get_rdata()
+ self.assertEqual(expected_algorithm, tsig_rdata.get_algorithm())
+ self.assertEqual(expected_timesigned, tsig_rdata.get_timesigned())
+ self.assertEqual(300, tsig_rdata.get_fudge())
+ self.assertEqual(expected_mac, tsig_rdata.get_mac())
+ self.assertEqual(expected_qid, tsig_rdata.get_original_id())
+ self.assertEqual(expected_error, tsig_rdata.get_error())
+ self.assertEqual(expected_otherdata, tsig_rdata.get_other_data())
+
+ def test_initial_state(self):
+ # Until signing or verifying, the state should be INIT
+ self.assertEqual(TSIGContext.STATE_INIT, self.tsig_ctx.get_state())
+
+ # And there should be no error code.
+ self.assertEqual(TSIGError(Rcode.NOERROR()), self.tsig_ctx.get_error())
+
+ # Note: intentionally use camelCase so that we can easily copy-paste
+ # corresponding C++ tests.
+ def commonVerifyChecks(self, ctx, record, data, expected_error,
+ expected_new_state=\
+ TSIGContext.STATE_VERIFIED_RESPONSE):
+ self.assertEqual(expected_error, ctx.verify(record, data))
+ self.assertEqual(expected_error, ctx.get_error())
+ self.assertEqual(expected_new_state, ctx.get_state())
+
+ def test_from_keyring(self):
+ # Construct a TSIG context with an empty key ring. Key shouldn't be
+ # found, and the BAD_KEY error should be recorded.
+ ctx = TSIGContext(self.test_name, TSIGKey.HMACMD5_NAME, self.keyring)
+ self.assertEqual(TSIGContext.STATE_INIT, ctx.get_state())
+ self.assertEqual(TSIGError.BAD_KEY, ctx.get_error())
+ # check get_error() doesn't cause ref leak. Note: we can't
+ # realiably do this check for get_state(), as it returns an integer
+ # object, which could have many references
+ self.assertEqual(1, sys.getrefcount(ctx.get_error()))
+
+ # Add a matching key (we don't use the secret so leave it empty), and
+ # construct it again. This time it should be constructed with a valid
+ # key.
+ self.keyring.add(TSIGKey(self.test_name, TSIGKey.HMACMD5_NAME, b""))
+ ctx = TSIGContext(self.test_name, TSIGKey.HMACMD5_NAME, self.keyring)
+ self.assertEqual(TSIGContext.STATE_INIT, ctx.get_state())
+ self.assertEqual(TSIGError.NOERROR, ctx.get_error())
+
+ # Similar to the first case except that the key ring isn't empty but
+ # it doesn't contain a matching key.
+ ctx = TSIGContext(self.test_name, TSIGKey.HMACSHA1_NAME, self.keyring)
+ self.assertEqual(TSIGContext.STATE_INIT, ctx.get_state())
+ self.assertEqual(TSIGError.BAD_KEY, ctx.get_error())
+
+ ctx = TSIGContext(Name("different-key.example"),
+ TSIGKey.HMACMD5_NAME, self.keyring)
+ self.assertEqual(TSIGContext.STATE_INIT, ctx.get_state())
+ self.assertEqual(TSIGError.BAD_KEY, ctx.get_error())
+
+ # "Unknown" algorithm name will result in BADKEY, too.
+ ctx = TSIGContext(self.test_name, Name("unknown.algorithm"),
+ self.keyring)
+ self.assertEqual(TSIGContext.STATE_INIT, ctx.get_state())
+ self.assertEqual(TSIGError.BAD_KEY, ctx.get_error())
+
+ def test_sign(self):
+ fix_current_time(0x4da8877a)
+ tsig = self.createMessageAndSign(self.qid, self.test_name,
+ self.tsig_ctx)
+ self.commonSignChecks(tsig, self.qid, 0x4da8877a, COMMON_EXPECTED_MAC)
+
+ # Same test as sign, but specifying the key name with upper-case (i.e.
+ # non canonical) characters. The digest must be the same. It should
+ # actually be ensured at the level of TSIGKey, but we confirm that at
+ # this level, too.
+ def test_sign_using_uppercase_keyname(self):
+ fix_current_time(0x4da8877a)
+ cap_ctx = TSIGContext(TSIGKey(Name("WWW.EXAMPLE.COM"),
+ TSIGKey.HMACMD5_NAME, self.secret))
+ tsig = self.createMessageAndSign(self.qid, self.test_name, cap_ctx)
+ self.commonSignChecks(tsig, self.qid, 0x4da8877a, COMMON_EXPECTED_MAC)
+
+ # Same as the previous test, but for the algorithm name.
+ def test_sign_using_uppercase_algorithm_name(self):
+ fix_current_time(0x4da8877a)
+ cap_ctx = TSIGContext(TSIGKey(self.test_name,
+ Name("HMAC-md5.SIG-alg.REG.int"),
+ self.secret))
+ tsig = self.createMessageAndSign(self.qid, self.test_name, cap_ctx)
+ self.commonSignChecks(tsig, self.qid, 0x4da8877a, COMMON_EXPECTED_MAC)
+
+ # Sign the message using the actual time, and check the accuracy of it.
+ # We cannot reasonably predict the expected MAC, so don't bother to
+ # check it.
+ def test_sign_at_actual_time(self):
+ now = int(time.time())
+ tsig = self.createMessageAndSign(self.qid, self.test_name,
+ self.tsig_ctx)
+ tsig_rdata = tsig.get_rdata()
+
+ # Check the resulted time signed is in the range of [now, now + 5]
+ self.assertTrue(now <= tsig_rdata.get_timesigned())
+ self.assertTrue(now + 5 >= tsig_rdata.get_timesigned())
+
+ def test_bad_data(self):
+ self.assertRaises(TypeError, self.tsig_ctx.sign, None, 10)
+
+ def test_verify_bad_data(self):
+ # the data must at least hold the DNS message header and the specified
+ # TSIG.
+ bad_len = 12 + self.dummy_record.get_length() - 1
+ self.assertRaises(InvalidParameter, self.tsig_ctx.verify,
+ self.dummy_record, DUMMY_DATA[:bad_len])
+
+ def test_sign_using_hmacsha1(self):
+ fix_current_time(0x4dae7d5f)
+
+ secret = base64.b64decode(b"MA+QDhXbyqUak+qnMFyTyEirzng=")
+ sha1_ctx = TSIGContext(TSIGKey(self.test_name, TSIGKey.HMACSHA1_NAME,
+ secret))
+ qid = 0x0967
+ expected_mac = b"\x41\x53\x40\xc7\xda\xf8\x24\xed\x68\x4e\xe5\x86" + \
+ b"\xf7\xb5\xa6\x7a\x2f\xeb\xc0\xd3"
+ tsig = self.createMessageAndSign(qid, self.test_name, sha1_ctx)
+ self.commonSignChecks(tsig, qid, 0x4dae7d5f, expected_mac,
+ 0, None, TSIGKey.HMACSHA1_NAME)
+
+ def test_verify_then_sign_response(self):
+ fix_current_time(0x4da8877a)
+
+ self.createMessageFromFile("message_toWire2.wire")
+ self.commonVerifyChecks(self.tsig_verify_ctx,
+ self.message.get_tsig_record(),
+ self.received_data, TSIGError.NOERROR,
+ TSIGContext.STATE_RECEIVED_REQUEST)
+
+ tsig = self.createMessageAndSign(self.qid, self.test_name,
+ self.tsig_verify_ctx,
+ QR_FLAG|AA_FLAG|RD_FLAG,
+ RRType.A(), "192.0.2.1")
+
+ expected_mac = b"\x8f\xcd\xa6\x6a\x7c\xd1\xa3\xb9\x94\x8e\xb1\x86" + \
+ b"\x9d\x38\x4a\x9f"
+ self.commonSignChecks(tsig, self.qid, 0x4da8877a, expected_mac)
+
+ def test_verify_uppercase_names(self):
+ fix_current_time(0x4da8877a)
+
+ self.createMessageFromFile("tsig_verify9.wire")
+ self.commonVerifyChecks(self.tsig_verify_ctx,
+ self.message.get_tsig_record(),
+ self.received_data, TSIGError.NOERROR,
+ TSIGContext.STATE_RECEIVED_REQUEST)
+
+ def test_verify_forward_message(self):
+ fix_current_time(0x4da8877a)
+
+ self.createMessageFromFile("tsig_verify6.wire")
+ self.commonVerifyChecks(self.tsig_verify_ctx,
+ self.message.get_tsig_record(),
+ self.received_data, TSIGError.NOERROR,
+ TSIGContext.STATE_RECEIVED_REQUEST)
+
+ def test_sign_continuation(self):
+ fix_current_time(0x4da8e951)
+
+ axfr_qid = 0x3410
+ zone_name = Name("example.com")
+
+ tsig = self.createMessageAndSign(axfr_qid, zone_name, self.tsig_ctx,
+ 0, RRType.AXFR())
+
+ received_data = read_wire_data("tsig_verify1.wire")
+ self.commonVerifyChecks(self.tsig_verify_ctx, tsig, received_data,
+ TSIGError.NOERROR,
+ TSIGContext.STATE_RECEIVED_REQUEST)
+
+ tsig = self.createMessageAndSign(axfr_qid, zone_name,
+ self.tsig_verify_ctx,
+ AA_FLAG|QR_FLAG, RRType.AXFR(),
+ "ns.example.com. root.example.com." +\
+ " 2011041503 7200 3600 2592000 1200",
+ RRType.SOA())
+
+ received_data = read_wire_data("tsig_verify2.wire")
+ self.commonVerifyChecks(self.tsig_ctx, tsig, received_data,
+ TSIGError.NOERROR)
+
+ expected_mac = b"\x10\x24\x58\xf7\xf6\x2d\xdd\x7d\x63\x8d\x74" +\
+ b"\x60\x34\x13\x09\x68"
+ tsig = self.createMessageAndSign(axfr_qid, zone_name,
+ self.tsig_verify_ctx,
+ AA_FLAG|QR_FLAG, RRType.AXFR(),
+ "ns.example.com.", RRType.NS(),
+ False)
+ self.commonSignChecks(tsig, axfr_qid, 0x4da8e951, expected_mac)
+
+ received_data = read_wire_data("tsig_verify3.wire")
+ self.commonVerifyChecks(self.tsig_ctx, tsig, received_data,
+ TSIGError.NOERROR)
+
+ def test_badtime_response(self):
+ fix_current_time(0x4da8b9d6)
+
+ test_qid = 0x7fc4
+ tsig = self.createMessageAndSign(test_qid, self.test_name,
+ self.tsig_ctx, 0, RRType.SOA())
+
+ # "advance the clock" and try validating, which should fail due to
+ # BADTIME
+ fix_current_time(0x4da8be86)
+ self.commonVerifyChecks(self.tsig_verify_ctx, tsig, DUMMY_DATA,
+ TSIGError.BAD_TIME,
+ TSIGContext.STATE_RECEIVED_REQUEST)
+
+ # make and sign a response in the context of TSIG error.
+ tsig = self.createMessageAndSign(test_qid, self.test_name,
+ self.tsig_verify_ctx,
+ QR_FLAG, RRType.SOA(), None, None,
+ True, Rcode.NOTAUTH())
+
+ expected_otherdata = b"\x00\x00\x4d\xa8\xbe\x86"
+ expected_mac = b"\xd4\xb0\x43\xf6\xf4\x44\x95\xec\x8a\x01\x26" +\
+ b"\x0e\x39\x15\x9d\x76"
+
+ self.commonSignChecks(tsig, self.message.get_qid(), 0x4da8b9d6,
+ expected_mac,
+ 18, # error: BADTIME
+ expected_otherdata)
+
+ def test_badtime_response2(self):
+ fix_current_time(0x4da8b9d6)
+
+ tsig = self.createMessageAndSign(self.qid, self.test_name,
+ self.tsig_ctx, 0, RRType.SOA())
+
+ # "rewind the clock" and try validating, which should fail due to
+ # BADTIME
+ fix_current_time(0x4da8b9d6 - 600)
+ self.commonVerifyChecks(self.tsig_verify_ctx, tsig, DUMMY_DATA,
+ TSIGError.BAD_TIME,
+ TSIGContext.STATE_RECEIVED_REQUEST)
+
+ # Test various boundary conditions. We intentionally use the magic
+ # number of 300 instead of the constant variable for testing.
+ # In the okay cases, signature is not correct, but it's sufficient to
+ # check the error code isn't BADTIME for the purpose of this test.
+ def test_badtime_boundaries(self):
+ fix_current_time(0x4da8b9d6)
+
+ tsig = self.createMessageAndSign(self.qid, self.test_name,
+ self.tsig_ctx, 0, RRType.SOA())
+
+ fix_current_time(0x4da8b9d6 + 301)
+ self.assertEqual(TSIGError.BAD_TIME,
+ self.tsig_verify_ctx.verify(tsig, DUMMY_DATA))
+
+ fix_current_time(0x4da8b9d6 + 300)
+ self.assertNotEqual(TSIGError.BAD_TIME,
+ self.tsig_verify_ctx.verify(tsig, DUMMY_DATA))
+
+ fix_current_time(0x4da8b9d6 - 301)
+ self.assertEqual(TSIGError.BAD_TIME,
+ self.tsig_verify_ctx.verify(tsig, DUMMY_DATA))
+
+ fix_current_time(0x4da8b9d6 - 300)
+ self.assertNotEqual(TSIGError.BAD_TIME,
+ self.tsig_verify_ctx.verify(tsig, DUMMY_DATA))
+
+ def test_badtime_overflow(self):
+ fix_current_time(200)
+ tsig = self.createMessageAndSign(self.qid, self.test_name,
+ self.tsig_ctx, 0, RRType.SOA())
+
+ # This should be in the okay range, but since "200 - fudge" overflows
+ # and we compare them as 64-bit unsigned integers, it results in a
+ # false positive (we intentionally accept that).
+ fix_current_time(100)
+ self.assertEqual(TSIGError.BAD_TIME,
+ self.tsig_verify_ctx.verify(tsig, DUMMY_DATA))
+
+ def test_badsig_response(self):
+ fix_current_time(0x4da8877a)
+
+ # Try to sign a simple message with bogus secret. It should fail
+ # with BADSIG.
+ self.createMessageFromFile("message_toWire2.wire")
+ bad_ctx = TSIGContext(TSIGKey(self.test_name, TSIGKey.HMACMD5_NAME,
+ DUMMY_DATA))
+ self.commonVerifyChecks(bad_ctx, self.message.get_tsig_record(),
+ self.received_data, TSIGError.BAD_SIG,
+ TSIGContext.STATE_RECEIVED_REQUEST)
+
+ # Sign the same message (which doesn't matter for this test) with the
+ # context of "checked state".
+ tsig = self.createMessageAndSign(self.qid, self.test_name, bad_ctx)
+ self.commonSignChecks(tsig, self.message.get_qid(), 0x4da8877a, None,
+ 16) # 16: BADSIG
+
+ def test_badkey_response(self):
+ # A similar test as badsigResponse but for BADKEY
+ fix_current_time(0x4da8877a)
+ tsig_ctx = TSIGContext(self.badkey_name, TSIGKey.HMACMD5_NAME,
+ self.keyring)
+ self.commonVerifyChecks(tsig_ctx, self.dummy_record, DUMMY_DATA,
+ TSIGError.BAD_KEY,
+ TSIGContext.STATE_RECEIVED_REQUEST)
+
+ sig = self.createMessageAndSign(self.qid, self.test_name, tsig_ctx)
+ self.assertEqual(self.badkey_name, sig.get_name())
+ self.commonSignChecks(sig, self.qid, 0x4da8877a, None, 17) # 17: BADKEY
+
+ def test_badkey_for_response(self):
+ # "BADKEY" case for a response to a signed message
+ self.createMessageAndSign(self.qid, self.test_name, self.tsig_ctx)
+ self.commonVerifyChecks(self.tsig_ctx, self.dummy_record, DUMMY_DATA,
+ TSIGError.BAD_KEY,
+ TSIGContext.STATE_SENT_REQUEST)
+
+ # A similar case with a different algorithm
+ dummy_record = TSIGRecord(self.test_name,
+ TSIG("hmac-sha1. 1302890362 300 0 "
+ "11621 0 0"))
+ self.commonVerifyChecks(self.tsig_ctx, dummy_record, DUMMY_DATA,
+ TSIGError.BAD_KEY,
+ TSIGContext.STATE_SENT_REQUEST)
+
+ # According to RFC2845 4.6, if TSIG verification fails the client
+ # should discard that message and wait for another signed response.
+ # This test emulates that situation.
+ def test_badsig_then_validate(self):
+ fix_current_time(0x4da8877a)
+
+ self.createMessageAndSign(self.qid, self.test_name, self.tsig_ctx)
+ self.createMessageFromFile("tsig_verify4.wire")
+
+ self.commonVerifyChecks(self.tsig_ctx, self.message.get_tsig_record(),
+ self.received_data, TSIGError.BAD_SIG,
+ TSIGContext.STATE_SENT_REQUEST)
+
+ self.createMessageFromFile("tsig_verify5.wire")
+ self.commonVerifyChecks(self.tsig_ctx, self.message.get_tsig_record(),
+ self.received_data, TSIGError.NOERROR,
+ TSIGContext.STATE_VERIFIED_RESPONSE)
+
+ # Similar to the previous test, but the first response doesn't contain
+ # TSIG.
+ def test_nosig_then_validate(self):
+ fix_current_time(0x4da8877a)
+ self.createMessageAndSign(self.qid, self.test_name, self.tsig_ctx)
+
+ self.commonVerifyChecks(self.tsig_ctx, None, DUMMY_DATA,
+ TSIGError.FORMERR, TSIGContext.STATE_SENT_REQUEST)
+
+ self.createMessageFromFile("tsig_verify5.wire")
+ self.commonVerifyChecks(self.tsig_ctx, self.message.get_tsig_record(),
+ self.received_data, TSIGError.NOERROR,
+ TSIGContext.STATE_VERIFIED_RESPONSE)
+
+ # Similar to the previous test, but the first response results in BADTIME.
+ def test_badtime_then_validate(self):
+ fix_current_time(0x4da8877a)
+ tsig = self.createMessageAndSign(self.qid, self.test_name,
+ self.tsig_ctx)
+
+ # "advance the clock" and try validating, which should fail due to
+ # BADTIME
+ fix_current_time(0x4da8877a + 600)
+ self.commonVerifyChecks(self.tsig_ctx, tsig, DUMMY_DATA,
+ TSIGError.BAD_TIME, TSIGContext.STATE_SENT_REQUEST)
+
+ # revert the clock again.
+ fix_current_time(0x4da8877a)
+ self.createMessageFromFile("tsig_verify5.wire")
+ self.commonVerifyChecks(self.tsig_ctx, self.message.get_tsig_record(),
+ self.received_data, TSIGError.NOERROR,
+ TSIGContext.STATE_VERIFIED_RESPONSE)
+
+ # We don't allow empty MAC unless the TSIG error is BADSIG or BADKEY.
+ def test_empty_mac(self):
+ fix_current_time(0x4da8877a)
+
+ self.createMessageFromFile("tsig_verify7.wire")
+
+ self.commonVerifyChecks(self.tsig_verify_ctx,
+ self.message.get_tsig_record(),
+ self.received_data,
+ TSIGError.BAD_SIG,
+ TSIGContext.STATE_RECEIVED_REQUEST)
+
+ # If the empty MAC comes with a BADKEY error, the error is passed
+ # transparently.
+ self.createMessageFromFile("tsig_verify8.wire")
+ self.commonVerifyChecks(self.tsig_verify_ctx,
+ self.message.get_tsig_record(),
+ self.received_data,
+ TSIGError.BAD_KEY,
+ TSIGContext.STATE_RECEIVED_REQUEST)
+
+ # Once the context is used for sending a signed response, it shouldn't
+ # be used for further verification.
+ def test_verify_after_sendresponse(self):
+ fix_current_time(0x4da8877a)
+
+ self.createMessageFromFile("message_toWire2.wire")
+ self.tsig_verify_ctx.verify(self.message.get_tsig_record(),
+ self.received_data)
+ self.assertEqual(TSIGContext.STATE_RECEIVED_REQUEST,
+ self.tsig_verify_ctx.get_state())
+ self.createMessageAndSign(self.qid, self.test_name,
+ self.tsig_verify_ctx,
+ QR_FLAG|AA_FLAG|RD_FLAG, RRType.A(),
+ "192.0.2.1")
+ self.assertEqual(TSIGContext.STATE_SENT_RESPONSE,
+ self.tsig_verify_ctx.get_state())
+
+ # Now trying further verification.
+ self.createMessageFromFile("message_toWire2.wire")
+ self.assertRaises(TSIGContextError, self.tsig_verify_ctx.verify,
+ self.message.get_tsig_record(), self.received_data)
+
+ # Likewise, once the context verifies a response, it shouldn't for
+ # signing any more.
+ def test_sign_after_verified(self):
+ fix_current_time(0x4da8877a)
+
+ self.createMessageAndSign(self.qid, self.test_name, self.tsig_ctx)
+ self.createMessageFromFile("tsig_verify5.wire")
+ self.tsig_ctx.verify(self.message.get_tsig_record(),
+ self.received_data)
+ self.assertEqual(TSIGContext.STATE_VERIFIED_RESPONSE,
+ self.tsig_ctx.get_state())
+
+ # Now trying further signing.
+ self.assertRaises(TSIGContextError, self.createMessageAndSign,
+ self.qid, self.test_name, self.tsig_ctx)
+
+ # Too short MAC should be rejected.
+ # Note: when we implement RFC4635-based checks, the error code will
+ # (probably) be FORMERR.
+ def test_too_short_mac(self):
+ fix_current_time(0x4da8877a)
+ self.createMessageFromFile("tsig_verify10.wire")
+ self.commonVerifyChecks(self.tsig_verify_ctx,
+ self.message.get_tsig_record(),
+ self.received_data, TSIGError.BAD_SIG,
+ TSIGContext.STATE_RECEIVED_REQUEST)
if __name__ == '__main__':
unittest.main()
diff --git a/src/lib/dns/python/tests/tsig_rdata_python_test.py b/src/lib/dns/python/tests/tsig_rdata_python_test.py
new file mode 100644
index 0000000..7b861d6
--- /dev/null
+++ b/src/lib/dns/python/tests/tsig_rdata_python_test.py
@@ -0,0 +1,30 @@
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import unittest
+import sys
+from pydnspp import *
+
+class TSIGRdataTest(unittest.TestCase):
+ VALID_TEXT1 = "hmac-md5.sig-alg.reg.int. 1286779327 300 0 16020 BADKEY 0"
+ def test_from_string(self):
+ tsig = TSIG(self.VALID_TEXT1)
+ self.assertEqual(Name("hmac-md5.sig-alg.reg.int"),
+ tsig.get_algorithm())
+ # check there's no leak in creating the name object:
+ self.assertEqual(1, sys.getrefcount(tsig.get_algorithm()))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/lib/dns/python/tests/tsigerror_python_test.py b/src/lib/dns/python/tests/tsigerror_python_test.py
new file mode 100644
index 0000000..a968b6b
--- /dev/null
+++ b/src/lib/dns/python/tests/tsigerror_python_test.py
@@ -0,0 +1,97 @@
+# 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 unittest
+import sys
+from pydnspp import *
+
+class TSIGErrorTest(unittest.TestCase):
+ def test_from_code(self):
+ self.assertEqual(0, TSIGError(0).get_code())
+ self.assertEqual(18, TSIGError(18).get_code())
+ self.assertEqual(65535, TSIGError(65535).get_code())
+ self.assertRaises(ValueError, TSIGError, 65536)
+ self.assertRaises(ValueError, TSIGError, -1)
+ self.assertRaises(TypeError, TSIGError, "not yet supported")
+
+ def test_from_rcode(self):
+ # We use RCODE for code values from 0-15.
+ self.assertEqual(0, TSIGError(Rcode.NOERROR()).get_code())
+ self.assertEqual(15, TSIGError(Rcode(15)).get_code())
+
+ # From error code 16 TSIG errors define a separate space, so passing
+ # corresponding RCODE for such code values should be prohibited.
+ self.assertRaises(ValueError, TSIGError, Rcode(16))
+
+ def test_constants(self):
+ # We'll only test arbitrarily chosen subsets of the codes.
+ # This class is quite simple, so it should be suffice.
+ self.assertEqual(TSIGError.BAD_SIG_CODE, TSIGError(16).get_code())
+ self.assertEqual(TSIGError.BAD_KEY_CODE, TSIGError(17).get_code())
+ self.assertEqual(TSIGError.BAD_TIME_CODE, TSIGError(18).get_code())
+
+ self.assertEqual(0, TSIGError.NOERROR.get_code())
+ self.assertEqual(9, TSIGError.NOTAUTH.get_code())
+ self.assertEqual(14, TSIGError.RESERVED14.get_code())
+ self.assertEqual(TSIGError.BAD_SIG_CODE, TSIGError.BAD_SIG.get_code())
+ self.assertEqual(TSIGError.BAD_KEY_CODE, TSIGError.BAD_KEY.get_code())
+ self.assertEqual(TSIGError.BAD_TIME_CODE, TSIGError.BAD_TIME.get_code())
+
+ def test_equal(self):
+ self.assertTrue(TSIGError.NOERROR == TSIGError(Rcode.NOERROR()))
+ self.assertTrue(TSIGError(Rcode.NOERROR()) == TSIGError.NOERROR)
+
+ self.assertTrue(TSIGError.BAD_SIG == TSIGError(16))
+ self.assertTrue(TSIGError(16) == TSIGError.BAD_SIG)
+
+ def test_nequal(self):
+ self.assertTrue(TSIGError.BAD_KEY != TSIGError(Rcode.NOERROR()))
+ self.assertTrue(TSIGError(Rcode.NOERROR()) != TSIGError.BAD_KEY)
+
+ def test_to_text(self):
+ # TSIGError derived from the standard Rcode
+ self.assertEqual("NOERROR", TSIGError(Rcode.NOERROR()).to_text())
+
+ # Well known TSIG errors
+ self.assertEqual("BADSIG", TSIGError.BAD_SIG.to_text())
+ self.assertEqual("BADKEY", TSIGError.BAD_KEY.to_text())
+ self.assertEqual("BADTIME", TSIGError.BAD_TIME.to_text())
+
+ # Unknown (or not yet supported) codes. Simply converted as numeric.
+ self.assertEqual("19", TSIGError(19).to_text());
+ self.assertEqual("65535", TSIGError(65535).to_text());
+
+ # also check str() works same way
+ self.assertEqual("NOERROR", str(TSIGError(Rcode.NOERROR())))
+ self.assertEqual("BADSIG", str(TSIGError.BAD_SIG))
+
+ def test_to_rcode(self):
+ # TSIGError derived from the standard Rcode
+ self.assertEqual(Rcode.NOERROR(), TSIGError(Rcode.NOERROR()).to_rcode())
+
+ # Well known TSIG errors
+ self.assertEqual(Rcode.NOTAUTH(), TSIGError.BAD_SIG.to_rcode())
+ self.assertEqual(Rcode.NOTAUTH(), TSIGError.BAD_KEY.to_rcode())
+ self.assertEqual(Rcode.NOTAUTH(), TSIGError.BAD_TIME.to_rcode())
+
+ # Unknown (or not yet supported) codes are treated as SERVFAIL.
+ self.assertEqual(Rcode.SERVFAIL(), TSIGError(19).to_rcode())
+ self.assertEqual(Rcode.SERVFAIL(), TSIGError(65535).to_rcode())
+
+ # Check there's no redundant refcount (which would cause leak)
+ self.assertEqual(1, sys.getrefcount(TSIGError.BAD_SIG.to_rcode()))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/lib/dns/python/tests/tsigkey_python_test.py b/src/lib/dns/python/tests/tsigkey_python_test.py
index 97be501..516bea4 100644
--- a/src/lib/dns/python/tests/tsigkey_python_test.py
+++ b/src/lib/dns/python/tests/tsigkey_python_test.py
@@ -25,6 +25,9 @@ class TSIGKeyTest(unittest.TestCase):
TSIGKey.HMACMD5_NAME)
self.assertEqual(Name('hmac-sha1'), TSIGKey.HMACSHA1_NAME)
self.assertEqual(Name('hmac-sha256'), TSIGKey.HMACSHA256_NAME)
+ self.assertEqual(Name('hmac-sha224'), TSIGKey.HMACSHA224_NAME)
+ self.assertEqual(Name('hmac-sha384'), TSIGKey.HMACSHA384_NAME)
+ self.assertEqual(Name('hmac-sha512'), TSIGKey.HMACSHA512_NAME)
def test_init(self):
key = TSIGKey(self.key_name, TSIGKey.HMACMD5_NAME, self.secret)
@@ -68,6 +71,9 @@ class TSIGKeyTest(unittest.TestCase):
class TSIGKeyRingTest(unittest.TestCase):
key_name = Name('example.com')
+ md5_name = Name('hmac-md5.sig-alg.reg.int')
+ sha1_name = Name('hmac-sha1')
+ sha256_name = Name('hmac-sha256')
secret = b'someRandomData'
def setUp(self):
@@ -152,18 +158,26 @@ class TSIGKeyRingTest(unittest.TestCase):
def test_find(self):
self.assertEqual((TSIGKeyRing.NOTFOUND, None),
- self.keyring.find(self.key_name))
+ self.keyring.find(self.key_name, self.md5_name))
self.assertEqual(TSIGKeyRing.SUCCESS,
self.keyring.add(TSIGKey(self.key_name,
- TSIGKey.HMACSHA256_NAME,
+ self.sha256_name,
self.secret)))
- (code, key) = self.keyring.find(self.key_name)
+ (code, key) = self.keyring.find(self.key_name, self.sha256_name)
self.assertEqual(TSIGKeyRing.SUCCESS, code)
self.assertEqual(self.key_name, key.get_key_name())
self.assertEqual(TSIGKey.HMACSHA256_NAME, key.get_algorithm_name())
self.assertEqual(self.secret, key.get_secret())
+ (code, key) = self.keyring.find(Name('different-key.example'),
+ self.sha256_name)
+ self.assertEqual(TSIGKeyRing.NOTFOUND, code)
+ self.assertEqual(None, key)
+ (code, key) = self.keyring.find(self.key_name, self.md5_name)
+ self.assertEqual(TSIGKeyRing.NOTFOUND, code)
+ self.assertEqual(None, key)
+
self.assertRaises(TypeError, self.keyring.find, 1)
self.assertRaises(TypeError, self.keyring.find, 'should be a name')
self.assertRaises(TypeError, self.keyring.find, self.key_name, 0)
@@ -171,24 +185,28 @@ class TSIGKeyRingTest(unittest.TestCase):
def test_find_from_some(self):
self.assertEqual(TSIGKeyRing.SUCCESS,
self.keyring.add(TSIGKey(self.key_name,
- TSIGKey.HMACSHA256_NAME,
+ self.sha256_name,
self.secret)))
self.assertEqual(TSIGKeyRing.SUCCESS,
self.keyring.add(TSIGKey(Name('another.example'),
- TSIGKey.HMACMD5_NAME,
+ self.md5_name,
self.secret)))
self.assertEqual(TSIGKeyRing.SUCCESS,
self.keyring.add(TSIGKey(Name('more.example'),
- TSIGKey.HMACSHA1_NAME,
+ self.sha1_name,
self.secret)))
- (code, key) = self.keyring.find(Name('another.example'))
+ (code, key) = self.keyring.find(Name('another.example'), self.md5_name)
self.assertEqual(TSIGKeyRing.SUCCESS, code)
self.assertEqual(Name('another.example'), key.get_key_name())
self.assertEqual(TSIGKey.HMACMD5_NAME, key.get_algorithm_name())
self.assertEqual((TSIGKeyRing.NOTFOUND, None),
- self.keyring.find(Name('noexist.example')))
+ self.keyring.find(Name('noexist.example'),
+ self.sha1_name))
+ self.assertEqual((TSIGKeyRing.NOTFOUND, None),
+ self.keyring.find(Name('another.example'),
+ self.sha1_name))
if __name__ == '__main__':
unittest.main()
diff --git a/src/lib/dns/python/tests/tsigrecord_python_test.py b/src/lib/dns/python/tests/tsigrecord_python_test.py
new file mode 100644
index 0000000..813a23b
--- /dev/null
+++ b/src/lib/dns/python/tests/tsigrecord_python_test.py
@@ -0,0 +1,44 @@
+# 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 unittest
+import sys
+from pydnspp import *
+
+class TSIGRecordTest(unittest.TestCase):
+ def setUp(self):
+ self.test_name = Name("www.example.com")
+ self.test_rdata = TSIG("hmac-md5.sig-alg.reg.int. 1302890362 " + \
+ "300 16 2tra2tra2tra2tra2tra2g== " + \
+ "11621 0 0")
+ self.test_record = TSIGRecord(self.test_name, self.test_rdata)
+
+ def test_getname(self):
+ self.assertEqual(self.test_name, self.test_record.get_name())
+ self.assertEqual(1, sys.getrefcount(self.test_record.get_name()))
+
+ def test_get_length(self):
+ # see the C++ test for the magic number
+ self.assertEqual(85, self.test_record.get_length())
+
+ def test_to_text(self):
+ expected_text = "www.example.com. 0 ANY TSIG " + \
+ "hmac-md5.sig-alg.reg.int. 1302890362 300 16 " + \
+ "2tra2tra2tra2tra2tra2g== 11621 NOERROR 0\n"
+ self.assertEqual(expected_text, self.test_record.to_text())
+ self.assertEqual(expected_text, str(self.test_record))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/lib/dns/python/tsig_python.cc b/src/lib/dns/python/tsig_python.cc
index 2e6d986..db93a08 100644
--- a/src/lib/dns/python/tsig_python.cc
+++ b/src/lib/dns/python/tsig_python.cc
@@ -12,9 +12,30 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#define PY_SSIZE_T_CLEAN // need for "y#" below
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <exceptions/exceptions.h>
+
+#include <util/python/pycppwrapper_util.h>
+
#include <dns/tsig.h>
+#include "pydnspp_common.h"
+#include "name_python.h"
+#include "tsigkey_python.h"
+#include "tsigerror_python.h"
+#include "tsigrecord_python.h"
+#include "tsig_python.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::util::python;
using namespace isc::dns;
+using namespace isc::dns::python;
//
// Definition of the classes
@@ -24,13 +45,17 @@ using namespace isc::dns;
// and static wrappers around the methods we export), a list of methods,
// and a type description
-namespace {
-// The s_* Class simply covers one instantiation of the object
+//
+// TSIGContext
+//
-class s_TSIGContext : public PyObject {
-public:
- TSIGContext* tsig_ctx;
-};
+// Trivial constructor.
+s_TSIGContext::s_TSIGContext() : cppobj(NULL) {
+}
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_TSIGContext, TSIGContext> TSIGContextContainer;
//
// We declare the functions here, the definitions are below
@@ -41,6 +66,12 @@ public:
int TSIGContext_init(s_TSIGContext* self, PyObject* args);
void TSIGContext_destroy(s_TSIGContext* self);
+// Class specific methods
+PyObject* TSIGContext_getState(s_TSIGContext* self);
+PyObject* TSIGContext_getError(s_TSIGContext* self);
+PyObject* TSIGContext_sign(s_TSIGContext* self, PyObject* args);
+PyObject* TSIGContext_verify(s_TSIGContext* self, PyObject* args);
+
// These are the functions we export
// For a minimal support, we don't need them.
@@ -51,18 +82,180 @@ void TSIGContext_destroy(s_TSIGContext* self);
// 3. Argument type
// 4. Documentation
PyMethodDef TSIGContext_methods[] = {
+ { "get_state", reinterpret_cast<PyCFunction>(TSIGContext_getState),
+ METH_NOARGS,
+ "Return the current state of the context (mainly for tests)" },
+ { "get_error", reinterpret_cast<PyCFunction>(TSIGContext_getError),
+ METH_NOARGS,
+ "Return the TSIG error as a result of the latest verification" },
+ { "sign",
+ reinterpret_cast<PyCFunction>(TSIGContext_sign), METH_VARARGS,
+ "Sign a DNS message." },
+ { "verify",
+ reinterpret_cast<PyCFunction>(TSIGContext_verify), METH_VARARGS,
+ "Verify a DNS message." },
{ NULL, NULL, 0, NULL }
};
+int
+TSIGContext_init(s_TSIGContext* self, PyObject* args) {
+ try {
+ // "From key" constructor
+ const s_TSIGKey* tsigkey_obj;
+ if (PyArg_ParseTuple(args, "O!", &tsigkey_type, &tsigkey_obj)) {
+ self->cppobj = new TSIGContext(*tsigkey_obj->cppobj);
+ return (0);
+ }
+
+ // "From key param + keyring" constructor
+ PyErr_Clear();
+ const s_Name* keyname_obj;
+ const s_Name* algname_obj;
+ const s_TSIGKeyRing* keyring_obj;
+ if (PyArg_ParseTuple(args, "O!O!O!", &name_type, &keyname_obj,
+ &name_type, &algname_obj, &tsigkeyring_type,
+ &keyring_obj)) {
+ self->cppobj = new TSIGContext(*keyname_obj->cppobj,
+ *algname_obj->cppobj,
+ *keyring_obj->cppobj);
+ return (0);
+ }
+ } catch (const exception& ex) {
+ const string ex_what = "Failed to construct TSIGContext object: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (-1);
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in constructing TSIGContext");
+ return (-1);
+ }
+
+ PyErr_SetString(PyExc_TypeError,
+ "Invalid arguments to TSIGContext constructor");
+
+ return (-1);
+}
+
+void
+TSIGContext_destroy(s_TSIGContext* const self) {
+ delete self->cppobj;
+ self->cppobj = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+TSIGContext_getState(s_TSIGContext* self) {
+ return (Py_BuildValue("I", self->cppobj->getState()));
+}
+
+PyObject*
+TSIGContext_getError(s_TSIGContext* self) {
+ try {
+ PyObjectContainer container(createTSIGErrorObject(
+ self->cppobj->getError()));
+ return (Py_BuildValue("O", container.get()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpectedly failed to get TSIGContext error: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in TSIGContext.get_error");
+ }
+ return (NULL);
+}
+
+PyObject*
+TSIGContext_sign(s_TSIGContext* self, PyObject* args) {
+ long qid = 0;
+ const char* mac;
+ Py_ssize_t mac_size;
+
+ if (PyArg_ParseTuple(args, "ly#", &qid, &mac, &mac_size)) {
+ if (qid < 0 || qid > 0xffff) {
+ PyErr_SetString(PyExc_ValueError,
+ "TSIGContext.sign: QID out of range");
+ return (NULL);
+ }
+
+ try {
+ ConstTSIGRecordPtr record = self->cppobj->sign(qid, mac, mac_size);
+ return (createTSIGRecordObject(*record));
+ } catch (const TSIGContextError& ex) {
+ PyErr_SetString(po_TSIGContextError, ex.what());
+ } catch (const exception& ex) {
+ const string ex_what = "Unexpected failure in TSIG sign: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in TSIG sign");
+ }
+ } else {
+ PyErr_SetString(PyExc_TypeError,
+ "Invalid arguments to TSIGContext.sign");
+ }
+
+ return (NULL);
+}
+
+PyObject*
+TSIGContext_verify(s_TSIGContext* self, PyObject* args) {
+ const char* data;
+ Py_ssize_t data_len;
+ s_TSIGRecord* py_record;
+ PyObject* py_maybe_none;
+ TSIGRecord* record;
+
+ if (PyArg_ParseTuple(args, "O!y#", &tsigrecord_type, &py_record,
+ &data, &data_len)) {
+ record = py_record->cppobj;
+ } else if (PyArg_ParseTuple(args, "Oy#", &py_maybe_none, &data,
+ &data_len)) {
+ record = NULL;
+ } else {
+ PyErr_SetString(PyExc_TypeError,
+ "Invalid arguments to TSIGContext.verify");
+ return (NULL);
+ }
+ PyErr_Clear();
+
+ try {
+ const TSIGError error = self->cppobj->verify(record, data, data_len);
+ return (createTSIGErrorObject(error));
+ } catch (const TSIGContextError& ex) {
+ PyErr_SetString(po_TSIGContextError, ex.what());
+ } catch (const InvalidParameter& ex) {
+ PyErr_SetString(po_InvalidParameter, ex.what());
+ } catch (const exception& ex) {
+ const string ex_what = "Unexpected failure in TSIG verify: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected failure in TSIG verify");
+ }
+
+ return (NULL);
+}
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+// Definition of class specific exception(s)
+PyObject* po_TSIGContextError;
+
// This defines the complete type for reflection in python and
-// parsing of PyObject* to s_EDNS
+// parsing of PyObject* to s_TSIGContext
// Most of the functions are not actually implemented and NULL here.
-PyTypeObject tsig_context_type = {
+PyTypeObject tsigcontext_type = {
PyVarObject_HEAD_INIT(NULL, 0)
- "libdns_python.TSIGContext",
- sizeof(s_TSIGContext), // tp_basicsize
+ "pydnspp.TSIGContext",
+ sizeof(s_TSIGContext), // tp_basicsize
0, // tp_itemsize
- (destructor)TSIGContext_destroy, // tp_dealloc
+ reinterpret_cast<destructor>(TSIGContext_destroy), // tp_dealloc
NULL, // tp_print
NULL, // tp_getattr
NULL, // tp_setattr
@@ -77,16 +270,22 @@ PyTypeObject tsig_context_type = {
NULL, // tp_getattro
NULL, // tp_setattro
NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "The TSIGContext class maintains a context of a signed session of "
- "DNS transactions by TSIG.",
+
+ // We allow the python version of TSIGContext to act as a base class.
+ // From pure design point of view, this is wrong because it's not intended
+ // to be inherited. However, cryptographic operations are generally
+ // difficult to test, so it would be very advantageous if we can define
+ // a mock context class.
+ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, // tp_flags
+
+ "The TSIGContext class objects is...(COMPLETE THIS)",
NULL, // tp_traverse
NULL, // tp_clear
- NULL, // tp_richcompare
+ NULL, // tp_richcompare
0, // tp_weaklistoffset
NULL, // tp_iter
NULL, // tp_iternext
- TSIGContext_methods, // tp_methods
+ TSIGContext_methods, // tp_methods
NULL, // tp_members
NULL, // tp_getset
NULL, // tp_base
@@ -94,7 +293,7 @@ PyTypeObject tsig_context_type = {
NULL, // tp_descr_get
NULL, // tp_descr_set
0, // tp_dictoffset
- (initproc)TSIGContext_init, // tp_init
+ reinterpret_cast<initproc>(TSIGContext_init), // tp_init
NULL, // tp_alloc
PyType_GenericNew, // tp_new
NULL, // tp_free
@@ -108,50 +307,58 @@ PyTypeObject tsig_context_type = {
0 // tp_version_tag
};
-int
-TSIGContext_init(s_TSIGContext* self, PyObject* args) {
- const s_TSIGKey* tsigkey_obj;
-
- try {
- if (PyArg_ParseTuple(args, "O!", &tsigkey_type, &tsigkey_obj)) {
- self->tsig_ctx = new TSIGContext(*tsigkey_obj->tsigkey);
- return (0);
- }
- } catch (...) {
- PyErr_SetString(po_IscException, "Unexpected exception");
- return (-1);
- }
-
- PyErr_Clear();
- PyErr_SetString(PyExc_TypeError,
- "Invalid arguments to TSIGContext constructor");
-
- return (-1);
-}
-
-void
-TSIGContext_destroy(s_TSIGContext* const self) {
- delete self->tsig_ctx;
- self->tsig_ctx = NULL;
- Py_TYPE(self)->tp_free(self);
-}
-
// Module Initialization, all statics are initialized here
bool
initModulePart_TSIGContext(PyObject* mod) {
// We initialize the static description object with PyType_Ready(),
// then add it to the module. This is not just a check! (leaving
// this out results in segmentation faults)
- if (PyType_Ready(&tsig_context_type) < 0) {
+ if (PyType_Ready(&tsigcontext_type) < 0) {
return (false);
}
- Py_INCREF(&tsig_context_type);
- void* p = &tsig_context_type;
- PyModule_AddObject(mod, "TSIGContext", static_cast<PyObject*>(p));
+ void* p = &tsigcontext_type;
+ if (PyModule_AddObject(mod, "TSIGContext",
+ static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&tsigcontext_type);
+
+ try {
+ // Class specific exceptions
+ po_TSIGContextError = PyErr_NewException("pydnspp.TSIGContextError",
+ po_IscException, NULL);
+ PyObjectContainer(po_TSIGContextError).installToModule(
+ mod, "TSIGContextError");
+
+ // Constant class variables
+ installClassVariable(tsigcontext_type, "STATE_INIT",
+ Py_BuildValue("I", TSIGContext::INIT));
+ installClassVariable(tsigcontext_type, "STATE_SENT_REQUEST",
+ Py_BuildValue("I", TSIGContext::SENT_REQUEST));
+ installClassVariable(tsigcontext_type, "STATE_RECEIVED_REQUEST",
+ Py_BuildValue("I", TSIGContext::RECEIVED_REQUEST));
+ installClassVariable(tsigcontext_type, "STATE_SENT_RESPONSE",
+ Py_BuildValue("I", TSIGContext::SENT_RESPONSE));
+ installClassVariable(tsigcontext_type, "STATE_VERIFIED_RESPONSE",
+ Py_BuildValue("I",
+ TSIGContext::VERIFIED_RESPONSE));
- addClassVariable(tsig_context_type, "DEFAULT_FUDGE",
- Py_BuildValue("H", TSIGContext::DEFAULT_FUDGE));
+ installClassVariable(tsigcontext_type, "DEFAULT_FUDGE",
+ Py_BuildValue("H", TSIGContext::DEFAULT_FUDGE));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure in TSIGContext initialization: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in TSIGContext initialization");
+ return (false);
+ }
return (true);
}
-} // end of anonymous namespace
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/tsig_python.h b/src/lib/dns/python/tsig_python.h
new file mode 100644
index 0000000..f9b4f7b
--- /dev/null
+++ b/src/lib/dns/python/tsig_python.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_TSIGCONTEXT_H
+#define __PYTHON_TSIGCONTEXT_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class TSIGContext;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_TSIGContext : public PyObject {
+public:
+ s_TSIGContext();
+ TSIGContext* cppobj;
+};
+
+extern PyTypeObject tsigcontext_type;
+
+// Class specific exceptions
+extern PyObject* po_TSIGContextError;
+
+bool initModulePart_TSIGContext(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_TSIGCONTEXT_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/tsig_rdata_python.cc b/src/lib/dns/python/tsig_rdata_python.cc
new file mode 100644
index 0000000..4e4f287
--- /dev/null
+++ b/src/lib/dns/python/tsig_rdata_python.cc
@@ -0,0 +1,369 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/rdataclass.h>
+
+#include "pydnspp_common.h"
+#include "pydnspp_towire.h"
+#include "name_python.h"
+#include "tsig_rdata_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::dns::python;
+
+//
+// Definition of the classes
+//
+
+// For each class, we need a struct, a helper functions (init, destroy,
+// and static wrappers around the methods we export), a list of methods,
+// and a type description
+
+//
+// TSIG RDATA
+//
+
+// Trivial constructor.
+s_TSIG::s_TSIG() : cppobj(NULL) {
+}
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_TSIG, any::TSIG> TSIGContainer;
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+
+// General creation and destruction
+int TSIG_init(s_TSIG* self, PyObject* args);
+void TSIG_destroy(s_TSIG* self);
+
+// These are the functions we export
+// ADD/REMOVE/MODIFY THE FOLLOWING AS APPROPRIATE FOR THE ACTUAL CLASS.
+//
+PyObject* TSIG_toText(const s_TSIG* const self);
+PyObject* TSIG_getAlgorithm(const s_TSIG* const self);
+PyObject* TSIG_getTimeSigned(const s_TSIG* const self);
+PyObject* TSIG_getFudge(const s_TSIG* const self);
+PyObject* TSIG_getOriginalID(const s_TSIG* const self);
+PyObject* TSIG_getError(const s_TSIG* const self);
+PyObject* TSIG_getMAC(const s_TSIG* const self);
+PyObject* TSIG_getOtherData(const s_TSIG* const self);
+PyObject* TSIG_str(PyObject* self);
+PyObject* TSIG_richcmp(const s_TSIG* const self,
+ const s_TSIG* const other, int op);
+PyObject* TSIG_toWire(const s_TSIG* self, PyObject* args);
+
+// These are the functions we export
+// For a minimal support, we don't need them.
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef TSIG_methods[] = {
+ { "get_algorithm", reinterpret_cast<PyCFunction>(TSIG_getAlgorithm),
+ METH_NOARGS,
+ "Return the algorithm name." },
+ { "get_timesigned", reinterpret_cast<PyCFunction>(TSIG_getTimeSigned),
+ METH_NOARGS,
+ "Return the value of the Time Signed field. "
+ "The returned value does not exceed 2^48-1."
+ },
+ { "get_fudge", reinterpret_cast<PyCFunction>(TSIG_getFudge),
+ METH_NOARGS,
+ "Return the value of the Fudge field." },
+ { "get_original_id", reinterpret_cast<PyCFunction>(TSIG_getOriginalID),
+ METH_NOARGS,
+ "Return the value of the Original ID field." },
+ { "get_error", reinterpret_cast<PyCFunction>(TSIG_getError),
+ METH_NOARGS,
+ "Return the value of the Error field." },
+ { "get_mac", reinterpret_cast<PyCFunction>(TSIG_getMAC),
+ METH_NOARGS,
+ "Return the value of the MAC field."
+ "If it's empty, return None." },
+ { "get_other_data", reinterpret_cast<PyCFunction>(TSIG_getOtherData),
+ METH_NOARGS,
+ "Return the value of the Other Data field."
+ "If it's empty, return None." },
+ { "to_text", reinterpret_cast<PyCFunction>(TSIG_toText), METH_NOARGS,
+ "Returns the text representation" },
+ { "to_wire", reinterpret_cast<PyCFunction>(TSIG_toWire), METH_VARARGS,
+ "Converts the TSIG object to wire format.\n"
+ "The argument can be either a MessageRenderer or an object that "
+ "implements the sequence interface. If the object is mutable "
+ "(for instance a bytearray()), the wire data is added in-place.\n"
+ "If it is not (for instance a bytes() object), a new object is "
+ "returned" },
+ { NULL, NULL, 0, NULL }
+};
+
+int
+TSIG_init(s_TSIG* self, PyObject* args) {
+ try {
+ // constructor from string
+ const char* rdata_str;
+ if (PyArg_ParseTuple(args, "s", &rdata_str)) {
+ self->cppobj = new any::TSIG(string(rdata_str));
+ return (0);
+ }
+ } catch (const exception& ex) {
+ const string ex_what = "Failed to construct TSIG object: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (-1);
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in constructing TSIG");
+ return (-1);
+ }
+
+ PyErr_SetString(PyExc_TypeError,
+ "Invalid arguments to TSIG constructor");
+
+ return (-1);
+}
+
+void
+TSIG_destroy(s_TSIG* const self) {
+ delete self->cppobj;
+ self->cppobj = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+TSIG_getAlgorithm(const s_TSIG* const self) {
+ try {
+ return (createNameObject(self->cppobj->getAlgorithm()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Failed to get TSIG algorithm: " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+ "getting TSIG algorithm");
+ }
+ return (NULL);
+}
+
+PyObject*
+TSIG_getTimeSigned(const s_TSIG* const self) {
+ return (Py_BuildValue("K", self->cppobj->getTimeSigned()));
+}
+
+PyObject*
+TSIG_getFudge(const s_TSIG* const self) {
+ return (Py_BuildValue("H", self->cppobj->getFudge()));
+}
+
+PyObject*
+TSIG_getOriginalID(const s_TSIG* const self) {
+ return (Py_BuildValue("H", self->cppobj->getOriginalID()));
+}
+
+PyObject*
+TSIG_getError(const s_TSIG* const self) {
+ return (Py_BuildValue("H", self->cppobj->getError()));
+}
+
+PyObject*
+TSIG_getMAC(const s_TSIG* const self) {
+ return (Py_BuildValue("y#", self->cppobj->getMAC(),
+ self->cppobj->getMACSize()));
+}
+
+PyObject*
+TSIG_getOtherData(const s_TSIG* const self) {
+ return (Py_BuildValue("y#", self->cppobj->getOtherData(),
+ self->cppobj->getOtherLen()));
+}
+
+PyObject*
+TSIG_toText(const s_TSIG* const self) {
+ try {
+ // toText() could throw, so we need to catch any exceptions below.
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Failed to convert TSIG object to text: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+ "converting TSIG object to text");
+ }
+ return (NULL);
+}
+
+PyObject*
+TSIG_str(PyObject* self) {
+ // Simply call the to_text method we already defined
+ return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
+ const_cast<char*>("")));
+}
+
+PyObject*
+TSIG_toWire(const s_TSIG* const self, PyObject* args) {
+ typedef any::TSIG TSIGRdata;
+ return (toWireWrapper<s_TSIG, TSIGRdata, ToWireCallVoid<const TSIGRdata> >(
+ self, args));
+}
+
+PyObject*
+TSIG_richcmp(const s_TSIG* const self,
+ const s_TSIG* const other,
+ const int op)
+{
+ bool c = false;
+
+ // Check for null and if the types match. If different type,
+ // simply return False
+ if (other == NULL || (self->ob_type != other->ob_type)) {
+ Py_RETURN_FALSE;
+ }
+
+ // Only equals and not equals here, unorderable type
+ const int cmp = self->cppobj->compare(*other->cppobj);
+ switch (op) {
+ case Py_EQ:
+ c = (cmp == 0);
+ break;
+ case Py_NE:
+ c = (cmp != 0);
+ break;
+ case Py_GT:
+ c = (cmp > 0);
+ break;
+ case Py_GE:
+ c = (cmp >= 0);
+ break;
+ case Py_LT:
+ c = (cmp < 0);
+ break;
+ case Py_LE:
+ c = (cmp <= 0);
+ break;
+ default:
+ PyErr_SetString(PyExc_IndexError,
+ "Unhandled rich comparison operator for TSIG");
+ return (NULL);
+ }
+ if (c) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+}
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_TSIG
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject tsig_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.TSIG",
+ sizeof(s_TSIG), // tp_basicsize
+ 0, // tp_itemsize
+ reinterpret_cast<destructor>(TSIG_destroy), // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ TSIG_str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The TSIG class objects represents the TSIG RDATA as defined in RFC2845.",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ reinterpret_cast<richcmpfunc>(TSIG_richcmp), // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ TSIG_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ // At the moment, we leave tp_base NULL as we won't use this class
+ // in a polymorphic way for our immediate need.
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ reinterpret_cast<initproc>(TSIG_init), // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_TSIG(PyObject* mod) {
+ // We initialize the static description object with PyType_Ready(),
+ // then add it to the module. This is not just a check! (leaving
+ // this out results in segmentation faults)
+ if (PyType_Ready(&tsig_type) < 0) {
+ return (false);
+ }
+ void* p = &tsig_type;
+ if (PyModule_AddObject(mod, "TSIG", static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&tsig_type);
+
+ return (true);
+}
+
+PyObject*
+createTSIGObject(const any::TSIG& source) {
+ TSIGContainer container = PyObject_New(s_TSIG, &tsig_type);
+ container.set(new any::TSIG(source));
+ return (container.release());
+}
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/tsig_rdata_python.h b/src/lib/dns/python/tsig_rdata_python.h
new file mode 100644
index 0000000..e5e0c6c
--- /dev/null
+++ b/src/lib/dns/python/tsig_rdata_python.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_TSIG_H
+#define __PYTHON_TSIG_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace any {
+class TSIG;
+}
+}
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_TSIG : public PyObject {
+public:
+ s_TSIG();
+ const rdata::any::TSIG* cppobj;
+};
+
+extern PyTypeObject tsig_type;
+
+bool initModulePart_TSIG(PyObject* mod);
+
+/// This is A simple shortcut to create a python TSIG object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called with in a try block
+/// followed by necessary setup for python exception.
+PyObject* createTSIGObject(const rdata::any::TSIG& source);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_TSIG_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/tsigerror_python.cc b/src/lib/dns/python/tsigerror_python.cc
new file mode 100644
index 0000000..0ad4716
--- /dev/null
+++ b/src/lib/dns/python/tsigerror_python.cc
@@ -0,0 +1,370 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/tsigerror.h>
+
+#include "pydnspp_common.h"
+#include "rcode_python.h"
+#include "tsigerror_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns;
+using namespace isc::dns::python;
+
+//
+// Definition of the classes
+//
+
+// For each class, we need a struct, a helper functions (init, destroy,
+// and static wrappers around the methods we export), a list of methods,
+// and a type description
+
+//
+// TSIGError
+//
+
+// Trivial constructor.
+s_TSIGError::s_TSIGError() : cppobj(NULL) {
+}
+
+// Import pydoc text
+#include "tsigerror_python_inc.cc"
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_TSIGError, TSIGError> TSIGErrorContainer;
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+
+// General creation and destruction
+int TSIGError_init(s_TSIGError* self, PyObject* args);
+void TSIGError_destroy(s_TSIGError* self);
+
+// These are the functions we export
+PyObject* TSIGError_getCode(const s_TSIGError* const self);
+PyObject* TSIGError_toText(const s_TSIGError* const self);
+PyObject* TSIGError_toRcode(const s_TSIGError* const self);
+PyObject* TSIGError_str(PyObject* self);
+PyObject* TSIGError_richcmp(const s_TSIGError* const self,
+ const s_TSIGError* const other, int op);
+
+// These are the functions we export
+// For a minimal support, we don't need them.
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef TSIGError_methods[] = {
+ { "get_code", reinterpret_cast<PyCFunction>(TSIGError_getCode),
+ METH_NOARGS,
+ TSIGError_getCode_doc },
+ { "to_text", reinterpret_cast<PyCFunction>(TSIGError_toText), METH_NOARGS,
+ TSIGError_toText_doc },
+ { "to_rcode", reinterpret_cast<PyCFunction>(TSIGError_toRcode),
+ METH_NOARGS,
+ TSIGError_toRcode_doc },
+ { NULL, NULL, 0, NULL }
+};
+
+int
+TSIGError_init(s_TSIGError* self, PyObject* args) {
+ try {
+ // Constructor from the code value
+ long code = 0;
+ if (PyArg_ParseTuple(args, "l", &code)) {
+ if (code < 0 || code > 0xffff) {
+ PyErr_SetString(PyExc_ValueError, "TSIG error out of range");
+ return (-1);
+ }
+ self->cppobj = new TSIGError(code);
+ return (0);
+ }
+
+ // Constructor from Rcode
+ PyErr_Clear();
+ s_Rcode* py_rcode;
+ if (PyArg_ParseTuple(args, "O!", &rcode_type, &py_rcode)) {
+ self->cppobj = new TSIGError(*py_rcode->cppobj);
+ return (0);
+ }
+ } catch (const isc::OutOfRange& ex) {
+ const string ex_what = "Failed to construct TSIGError object: " +
+ string(ex.what());
+ PyErr_SetString(PyExc_ValueError, ex_what.c_str());
+ return (-1);
+ } catch (const exception& ex) {
+ const string ex_what = "Failed to construct TSIGError object: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (-1);
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in constructing TSIGError");
+ return (-1);
+ }
+
+ PyErr_SetString(PyExc_TypeError,
+ "Invalid arguments to TSIGError constructor");
+
+ return (-1);
+}
+
+void
+TSIGError_destroy(s_TSIGError* const self) {
+ delete self->cppobj;
+ self->cppobj = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+TSIGError_getCode(const s_TSIGError* const self) {
+ return (Py_BuildValue("I", self->cppobj->getCode()));
+}
+
+PyObject*
+TSIGError_toText(const s_TSIGError* const self) {
+ try {
+ // toText() could throw, so we need to catch any exceptions below.
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Failed to convert TSIGError object to text: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+ "converting TSIGError object to text");
+ }
+ return (NULL);
+}
+
+PyObject*
+TSIGError_str(PyObject* self) {
+ // Simply call the to_text method we already defined
+ return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
+ const_cast<char*>("")));
+}
+
+PyObject*
+TSIGError_toRcode(const s_TSIGError* const self) {
+ typedef CPPPyObjectContainer<s_Rcode, Rcode> RcodePyObjectContainer;
+
+ try {
+ RcodePyObjectContainer rcode_container(PyObject_New(s_Rcode,
+ &rcode_type));
+ rcode_container.set(new Rcode(self->cppobj->toRcode()));
+ return (rcode_container.release());
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Failed to convert TSIGError to Rcode: " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+ "converting TSIGError to Rcode");
+ }
+ return (NULL);
+}
+
+PyObject*
+TSIGError_richcmp(const s_TSIGError* const self,
+ const s_TSIGError* const other,
+ const int op)
+{
+ bool c = false;
+
+ // Check for null and if the types match. If different type,
+ // simply return False
+ if (other == NULL || (self->ob_type != other->ob_type)) {
+ Py_RETURN_FALSE;
+ }
+
+ // Only equals and not equals here, unorderable type
+ switch (op) {
+ case Py_LT:
+ PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+ return (NULL);
+ case Py_LE:
+ PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+ return (NULL);
+ case Py_EQ:
+ c = (*self->cppobj == *other->cppobj);
+ break;
+ case Py_NE:
+ c = (*self->cppobj != *other->cppobj);
+ break;
+ case Py_GT:
+ PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+ return (NULL);
+ case Py_GE:
+ PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+ return (NULL);
+ }
+ if (c) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+}
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_TSIGError
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject tsigerror_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.TSIGError",
+ sizeof(s_TSIGError), // tp_basicsize
+ 0, // tp_itemsize
+ reinterpret_cast<destructor>(TSIGError_destroy), // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ // THIS MAY HAVE TO BE CHANGED TO NULL:
+ TSIGError_str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ TSIGError_doc,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ // THIS MAY HAVE TO BE CHANGED TO NULL:
+ reinterpret_cast<richcmpfunc>(TSIGError_richcmp), // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ TSIGError_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ reinterpret_cast<initproc>(TSIGError_init), // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+namespace {
+// Trivial shortcut to create and install TSIGError constants.
+inline void
+installTSIGErrorConstant(const char* name, const TSIGError& val) {
+ TSIGErrorContainer container(PyObject_New(s_TSIGError, &tsigerror_type));
+ container.installAsClassVariable(tsigerror_type, name, new TSIGError(val));
+}
+}
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_TSIGError(PyObject* mod) {
+ // We initialize the static description object with PyType_Ready(),
+ // then add it to the module. This is not just a check! (leaving
+ // this out results in segmentation faults)
+ if (PyType_Ready(&tsigerror_type) < 0) {
+ return (false);
+ }
+ void* p = &tsigerror_type;
+ if (PyModule_AddObject(mod, "TSIGError", static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&tsigerror_type);
+
+ try {
+ // Constant class variables
+ // Error codes (bare values)
+ installClassVariable(tsigerror_type, "BAD_SIG_CODE",
+ Py_BuildValue("H", TSIGError::BAD_SIG_CODE));
+ installClassVariable(tsigerror_type, "BAD_KEY_CODE",
+ Py_BuildValue("H", TSIGError::BAD_KEY_CODE));
+ installClassVariable(tsigerror_type, "BAD_TIME_CODE",
+ Py_BuildValue("H", TSIGError::BAD_TIME_CODE));
+
+ // Error codes (constant objects)
+ installTSIGErrorConstant("NOERROR", TSIGError::NOERROR());
+ installTSIGErrorConstant("FORMERR", TSIGError::FORMERR());
+ installTSIGErrorConstant("SERVFAIL", TSIGError::SERVFAIL());
+ installTSIGErrorConstant("NXDOMAIN", TSIGError::NXDOMAIN());
+ installTSIGErrorConstant("NOTIMP", TSIGError::NOTIMP());
+ installTSIGErrorConstant("REFUSED", TSIGError::REFUSED());
+ installTSIGErrorConstant("YXDOMAIN", TSIGError::YXDOMAIN());
+ installTSIGErrorConstant("YXRRSET", TSIGError::YXRRSET());
+ installTSIGErrorConstant("NXRRSET", TSIGError::NXRRSET());
+ installTSIGErrorConstant("NOTAUTH", TSIGError::NOTAUTH());
+ installTSIGErrorConstant("NOTZONE", TSIGError::NOTZONE());
+ installTSIGErrorConstant("RESERVED11", TSIGError::RESERVED11());
+ installTSIGErrorConstant("RESERVED12", TSIGError::RESERVED12());
+ installTSIGErrorConstant("RESERVED13", TSIGError::RESERVED13());
+ installTSIGErrorConstant("RESERVED14", TSIGError::RESERVED14());
+ installTSIGErrorConstant("RESERVED15", TSIGError::RESERVED15());
+ installTSIGErrorConstant("BAD_SIG", TSIGError::BAD_SIG());
+ installTSIGErrorConstant("BAD_KEY", TSIGError::BAD_KEY());
+ installTSIGErrorConstant("BAD_TIME", TSIGError::BAD_TIME());
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure in TSIGError initialization: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in TSIGError initialization");
+ return (false);
+ }
+
+ return (true);
+}
+
+PyObject*
+createTSIGErrorObject(const TSIGError& source) {
+ TSIGErrorContainer container = PyObject_New(s_TSIGError, &tsigerror_type);
+ container.set(new TSIGError(source));
+ return (container.release());
+}
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/tsigerror_python.h b/src/lib/dns/python/tsigerror_python.h
new file mode 100644
index 0000000..735a480
--- /dev/null
+++ b/src/lib/dns/python/tsigerror_python.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_TSIGERROR_H
+#define __PYTHON_TSIGERROR_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class TSIGError;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_TSIGError : public PyObject {
+public:
+ s_TSIGError();
+ const TSIGError* cppobj;
+};
+
+extern PyTypeObject tsigerror_type;
+
+bool initModulePart_TSIGError(PyObject* mod);
+
+/// This is A simple shortcut to create a python TSIGError object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called with in a try block
+/// followed by necessary setup for python exception.
+PyObject* createTSIGErrorObject(const TSIGError& source);
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_TSIGERROR_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/tsigerror_python_inc.cc b/src/lib/dns/python/tsigerror_python_inc.cc
new file mode 100644
index 0000000..ed3b605
--- /dev/null
+++ b/src/lib/dns/python/tsigerror_python_inc.cc
@@ -0,0 +1,83 @@
+namespace {
+const char* const TSIGError_doc = "\n\
+TSIG errors.\n\
+\n\
+\n\
+The TSIGError class objects represent standard errors related to TSIG\n\
+protocol operations as defined in related specifications, mainly in\n\
+RFC2845.\n\
+\n\
+TSIGError(error_code)\n\
+\n\
+Constructor from the code value.\n\
+\n\
+Exceptions:\n\
+ None: \n\
+\n\
+Parameters:\n\
+ error_code: The underlying 16-bit error code value of the TSIGError.\n\
+\n\
+TSIGError(rcode)\n\
+\n\
+Constructor from Rcode.\n\
+\n\
+As defined in RFC2845, error code values from 0 to 15 (inclusive) are\n\
+derived from the DNS RCODEs, which are represented via the Rcode class\n\
+in this library. This constructor works as a converter from these\n\
+RCODEs to corresponding TSIGError objects.\n\
+\n\
+Exceptions:\n\
+ ValueError: Given rcode is not convertible to TSIGErrors.\n\
+\n\
+Parameters:\n\
+ rcode: the Rcode from which the TSIGError should be derived.\n\
+\n\
+";
+const char* const TSIGError_getCode_doc = "get_code() -> integer\n\
+\n\
+Returns the TSIGCode error code value.\n\
+\n\
+Exceptions:\n\
+ None: \n\
+\n\
+Return Value(s):\n\
+ The underlying code value corresponding to the TSIGError.\n\
+";
+const char* const TSIGError_toText_doc = "to_text() -> string\n\
+\n\
+Convert the TSIGError to a string.\n\
+\n\
+For codes derived from RCODEs up to 15, this method returns the same\n\
+string as Rcode.to_text() for the corresponding code. For other pre-\n\
+defined code values (see TSIGError.CodeValue), this method returns a\n\
+string representation of the \"mnemonic' used for the enum and\n\
+constant objects as defined in RFC2845. For example, the string for\n\
+code value 16 is \"BADSIG\", etc. For other code values it returns a\n\
+string representation of the decimal number of the value, e.g. \"32\",\n\
+\"100\", etc.\n\
+\n\
+Exceptions:\n\
+ None\n\
+\n\
+Return Value(s):\n\
+ A string representation of the TSIGError.\n\
+";
+const char* const TSIGError_toRcode_doc = "to_rcode() -> Rcode\n\
+\n\
+Convert the TSIGError to a Rcode.\n\
+\n\
+This method returns an Rcode object that is corresponding to the TSIG\n\
+error. The returned Rcode is expected to be used by a verifying server\n\
+to specify the RCODE of a response when TSIG verification fails.\n\
+\n\
+Specifically, this method returns Rcode.NOTAUTH() for the TSIG\n\
+specific errors, BADSIG, BADKEY, BADTIME, as described in RFC2845. For\n\
+errors derived from the standard Rcode (code 0-15), it returns the\n\
+corresponding Rcode. For others, this method returns Rcode.SERVFAIL()\n\
+as a last resort.\n\
+\n\
+Exceptions:\n\
+ None: \n\
+\n\
+";
+}
diff --git a/src/lib/dns/python/tsigkey_python.cc b/src/lib/dns/python/tsigkey_python.cc
index 906dfc0..f0906cb 100644
--- a/src/lib/dns/python/tsigkey_python.cc
+++ b/src/lib/dns/python/tsigkey_python.cc
@@ -12,12 +12,24 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <new>
+#include <Python.h>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/name.h>
#include <dns/tsigkey.h>
+#include <dns/rdata.h>
+#include "pydnspp_common.h"
+#include "name_python.h"
+#include "tsigkey_python.h"
+
+using namespace std;
+using namespace isc::util::python;
using namespace isc::dns;
-using namespace isc::dns::rdata;
+using namespace isc::dns::python;
//
// Definition of the classes
@@ -27,19 +39,15 @@ using namespace isc::dns::rdata;
// and static wrappers around the methods we export), a list of methods,
// and a type description
-namespace {
//
// TSIGKey
//
// The s_* Class simply covers one instantiation of the object
-class s_TSIGKey : public PyObject {
-public:
- s_TSIGKey() : tsigkey(NULL) {}
- TSIGKey* tsigkey;
-};
+s_TSIGKey::s_TSIGKey() : cppobj(NULL) {}
+namespace {
//
// We declare the functions here, the definitions are below
// the type definition of the object, since both can use the other
@@ -78,12 +86,105 @@ PyMethodDef TSIGKey_methods[] = {
{ NULL, NULL, 0, NULL }
};
+int
+TSIGKey_init(s_TSIGKey* self, PyObject* args) {
+ try {
+ const char* str;
+ if (PyArg_ParseTuple(args, "s", &str)) {
+ self->cppobj = new TSIGKey(str);
+ return (0);
+ }
+
+ PyErr_Clear();
+ const s_Name* key_name;
+ const s_Name* algorithm_name;
+ PyObject* bytes_obj;
+ const char* secret;
+ Py_ssize_t secret_len;
+ if (PyArg_ParseTuple(args, "O!O!O", &name_type, &key_name,
+ &name_type, &algorithm_name, &bytes_obj) &&
+ PyObject_AsCharBuffer(bytes_obj, &secret, &secret_len) == 0) {
+ if (secret_len == 0) {
+ secret = NULL;
+ }
+ self->cppobj = new TSIGKey(*key_name->cppobj,
+ *algorithm_name->cppobj,
+ secret, secret_len);
+ return (0);
+ }
+ } catch (const isc::InvalidParameter& ex) {
+ PyErr_SetString(po_InvalidParameter, ex.what());
+ return (-1);
+ } catch (...) {
+ PyErr_SetString(po_IscException, "Unexpected exception");
+ return (-1);
+ }
+
+ PyErr_Clear();
+ PyErr_SetString(PyExc_TypeError,
+ "Invalid arguments to TSIGKey constructor");
+
+ return (-1);
+}
+
+void
+TSIGKey_destroy(s_TSIGKey* const self) {
+ delete self->cppobj;
+ self->cppobj = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+TSIGKey_getKeyName(const s_TSIGKey* const self) {
+ try {
+ return (createNameObject(self->cppobj->getKeyName()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Failed to get key name of TSIGKey: " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+ "getting key name of TSIGKey");
+ }
+ return (NULL);
+}
+
+PyObject*
+TSIGKey_getAlgorithmName(const s_TSIGKey* const self) {
+ try {
+ return (createNameObject(self->cppobj->getAlgorithmName()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Failed to get algorithm name of TSIGKey: " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+ "getting algorithm name of TSIGKey");
+ }
+ return (NULL);
+}
+
+PyObject*
+TSIGKey_getSecret(const s_TSIGKey* const self) {
+ return (Py_BuildValue("y#", self->cppobj->getSecret(),
+ self->cppobj->getSecretLength()));
+}
+
+PyObject*
+TSIGKey_toText(const s_TSIGKey* self) {
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+}
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
// This defines the complete type for reflection in python and
// parsing of PyObject* to s_EDNS
// Most of the functions are not actually implemented and NULL here.
PyTypeObject tsigkey_type = {
PyVarObject_HEAD_INIT(NULL, 0)
- "libdns_python.TSIGKey",
+ "pydnspp.TSIGKey",
sizeof(s_TSIGKey), // tp_basicsize
0, // tp_itemsize
(destructor)TSIGKey_destroy, // tp_dealloc
@@ -132,89 +233,6 @@ PyTypeObject tsigkey_type = {
0 // tp_version_tag
};
-// A helper function to build a python "Name" object with error handling
-// encapsulated.
-s_Name*
-createNameObject(const Name& source) {
- s_Name* name = PyObject_New(s_Name, &name_type);
- if (name == NULL) {
- return (NULL);
- }
- name->name = new(nothrow) Name(source);
- if (name->name == NULL) {
- Py_DECREF(name);
- PyErr_SetString(po_IscException, "Allocating Name object failed");
- return (NULL);
- }
- return (name);
-}
-
-int
-TSIGKey_init(s_TSIGKey* self, PyObject* args) {
- const char* str;
-
- const s_Name* key_name;
- const s_Name* algorithm_name;
- PyObject* bytes_obj;
- const char* secret;
- Py_ssize_t secret_len;
-
-
- try {
- if (PyArg_ParseTuple(args, "s", &str)) {
- self->tsigkey = new TSIGKey(str);
- return (0);
- } else if (PyArg_ParseTuple(args, "O!O!O", &name_type, &key_name,
- &name_type, &algorithm_name, &bytes_obj) &&
- PyObject_AsCharBuffer(bytes_obj, &secret, &secret_len) != -1) {
- self->tsigkey = new TSIGKey(*key_name->name,
- *algorithm_name->name,
- secret, secret_len);
- return (0);
- }
- } catch (const isc::InvalidParameter& ex) {
- PyErr_SetString(po_InvalidParameter, ex.what());
- return (-1);
- } catch (...) {
- PyErr_SetString(po_IscException, "Unexpected exception");
- return (-1);
- }
-
- PyErr_Clear();
- PyErr_SetString(PyExc_TypeError,
- "Invalid arguments to TSIGKey constructor");
-
- return (-1);
-}
-
-void
-TSIGKey_destroy(s_TSIGKey* const self) {
- delete self->tsigkey;
- self->tsigkey = NULL;
- Py_TYPE(self)->tp_free(self);
-}
-
-PyObject*
-TSIGKey_getKeyName(const s_TSIGKey* const self) {
- return (createNameObject(self->tsigkey->getKeyName()));
-}
-
-PyObject*
-TSIGKey_getAlgorithmName(const s_TSIGKey* const self) {
- return (createNameObject(self->tsigkey->getAlgorithmName()));
-}
-
-PyObject*
-TSIGKey_getSecret(const s_TSIGKey* const self) {
- return (Py_BuildValue("y#", self->tsigkey->getSecret(),
- self->tsigkey->getSecretLength()));
-}
-
-PyObject*
-TSIGKey_toText(const s_TSIGKey* self) {
- return (Py_BuildValue("s", self->tsigkey->toText().c_str()));
-}
-
// Module Initialization, all statics are initialized here
bool
initModulePart_TSIGKey(PyObject* mod) {
@@ -224,33 +242,43 @@ initModulePart_TSIGKey(PyObject* mod) {
if (PyType_Ready(&tsigkey_type) < 0) {
return (false);
}
- Py_INCREF(&tsigkey_type);
void* p = &tsigkey_type;
if (PyModule_AddObject(mod, "TSIGKey", static_cast<PyObject*>(p)) != 0) {
- Py_DECREF(&tsigkey_type);
return (false);
}
+ Py_INCREF(&tsigkey_type);
- s_Name* name;
- if ((name = createNameObject(TSIGKey::HMACMD5_NAME())) == NULL) {
- goto cleanup;
- }
- addClassVariable(tsigkey_type, "HMACMD5_NAME", name);
- if ((name = createNameObject(TSIGKey::HMACSHA1_NAME())) == NULL) {
- goto cleanup;
- }
- addClassVariable(tsigkey_type, "HMACSHA1_NAME", name);
- if ((name = createNameObject(TSIGKey::HMACSHA256_NAME())) == NULL) {
- goto cleanup;
+ try {
+ // Constant class variables
+ installClassVariable(tsigkey_type, "HMACMD5_NAME",
+ createNameObject(TSIGKey::HMACMD5_NAME()));
+ installClassVariable(tsigkey_type, "HMACSHA1_NAME",
+ createNameObject(TSIGKey::HMACSHA1_NAME()));
+ installClassVariable(tsigkey_type, "HMACSHA256_NAME",
+ createNameObject(TSIGKey::HMACSHA256_NAME()));
+ installClassVariable(tsigkey_type, "HMACSHA224_NAME",
+ createNameObject(TSIGKey::HMACSHA224_NAME()));
+ installClassVariable(tsigkey_type, "HMACSHA384_NAME",
+ createNameObject(TSIGKey::HMACSHA384_NAME()));
+ installClassVariable(tsigkey_type, "HMACSHA512_NAME",
+ createNameObject(TSIGKey::HMACSHA512_NAME()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure in TSIGKey initialization: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in TSIGKey initialization");
+ return (false);
}
- addClassVariable(tsigkey_type, "HMACSHA256_NAME", name);
return (true);
-
- cleanup:
- Py_DECREF(&tsigkey_type);
- return (false);
}
+} // namespace python
+} // namespace dns
+} // namespace isc
//
// End of TSIGKey
//
@@ -263,12 +291,9 @@ initModulePart_TSIGKey(PyObject* mod) {
// The s_* Class simply covers one instantiation of the object
-class s_TSIGKeyRing : public PyObject {
-public:
- s_TSIGKeyRing() : keyring(NULL) {}
- TSIGKeyRing* keyring;
-};
+s_TSIGKeyRing::s_TSIGKeyRing() : cppobj(NULL) {}
+namespace {
//
// We declare the functions here, the definitions are below
// the type definition of the object, since both can use the other
@@ -296,56 +321,6 @@ PyMethodDef TSIGKeyRing_methods[] = {
{ NULL, NULL, 0, NULL }
};
-PyTypeObject tsigkeyring_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "libdns_python.TSIGKeyRing",
- sizeof(s_TSIGKeyRing), // tp_basicsize
- 0, // tp_itemsize
- (destructor)TSIGKeyRing_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- NULL, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "A simple repository of a set of TSIGKey objects.",
- NULL, // tp_traverse
- NULL, // tp_clear
- NULL, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- TSIGKeyRing_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)TSIGKeyRing_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
int
TSIGKeyRing_init(s_TSIGKeyRing* self, PyObject* args) {
if (!PyArg_ParseTuple(args, "")) {
@@ -355,8 +330,8 @@ TSIGKeyRing_init(s_TSIGKeyRing* self, PyObject* args) {
return (-1);
}
- self->keyring = new(nothrow) TSIGKeyRing();
- if (self->keyring == NULL) {
+ self->cppobj = new(nothrow) TSIGKeyRing();
+ if (self->cppobj == NULL) {
PyErr_SetString(po_IscException, "Allocating TSIGKeyRing failed");
return (-1);
}
@@ -366,14 +341,14 @@ TSIGKeyRing_init(s_TSIGKeyRing* self, PyObject* args) {
void
TSIGKeyRing_destroy(s_TSIGKeyRing* self) {
- delete self->keyring;
- self->keyring = NULL;
+ delete self->cppobj;
+ self->cppobj = NULL;
Py_TYPE(self)->tp_free(self);
}
PyObject*
TSIGKeyRing_size(const s_TSIGKeyRing* const self) {
- return (Py_BuildValue("I", self->keyring->size()));
+ return (Py_BuildValue("I", self->cppobj->size()));
}
PyObject*
@@ -383,7 +358,7 @@ TSIGKeyRing_add(const s_TSIGKeyRing* const self, PyObject* args) {
if (PyArg_ParseTuple(args, "O!", &tsigkey_type, &tsigkey)) {
try {
const TSIGKeyRing::Result result =
- self->keyring->add(*tsigkey->tsigkey);
+ self->cppobj->add(*tsigkey->cppobj);
return (Py_BuildValue("I", result));
} catch (...) {
PyErr_SetString(po_IscException, "Unexpected exception");
@@ -403,7 +378,7 @@ TSIGKeyRing_remove(const s_TSIGKeyRing* self, PyObject* args) {
if (PyArg_ParseTuple(args, "O!", &name_type, &key_name)) {
const TSIGKeyRing::Result result =
- self->keyring->remove(*key_name->name);
+ self->cppobj->remove(*key_name->cppobj);
return (Py_BuildValue("I", result));
}
@@ -416,17 +391,19 @@ TSIGKeyRing_remove(const s_TSIGKeyRing* self, PyObject* args) {
PyObject*
TSIGKeyRing_find(const s_TSIGKeyRing* self, PyObject* args) {
s_Name* key_name;
+ s_Name* algorithm_name;
- if (PyArg_ParseTuple(args, "O!", &name_type, &key_name)) {
+ if (PyArg_ParseTuple(args, "O!O!", &name_type, &key_name,
+ &name_type, &algorithm_name)) {
const TSIGKeyRing::FindResult result =
- self->keyring->find(*key_name->name);
+ self->cppobj->find(*key_name->cppobj, *algorithm_name->cppobj);
if (result.key != NULL) {
s_TSIGKey* key = PyObject_New(s_TSIGKey, &tsigkey_type);
if (key == NULL) {
return (NULL);
}
- key->tsigkey = new(nothrow) TSIGKey(*result.key);
- if (key->tsigkey == NULL) {
+ key->cppobj = new(nothrow) TSIGKey(*result.key);
+ if (key->cppobj == NULL) {
Py_DECREF(key);
PyErr_SetString(po_IscException,
"Allocating TSIGKey object failed");
@@ -440,6 +417,60 @@ TSIGKeyRing_find(const s_TSIGKeyRing* self, PyObject* args) {
return (NULL);
}
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+PyTypeObject tsigkeyring_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.TSIGKeyRing",
+ sizeof(s_TSIGKeyRing), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)TSIGKeyRing_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "A simple repository of a set of TSIGKey objects.",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ TSIGKeyRing_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)TSIGKeyRing_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
bool
initModulePart_TSIGKeyRing(PyObject* mod) {
@@ -463,5 +494,6 @@ initModulePart_TSIGKeyRing(PyObject* mod) {
return (true);
}
-
-} // end of unnamed namespace
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/tsigkey_python.h b/src/lib/dns/python/tsigkey_python.h
new file mode 100644
index 0000000..51b3ae7
--- /dev/null
+++ b/src/lib/dns/python/tsigkey_python.h
@@ -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.
+
+#ifndef __PYTHON_TSIGKEY_H
+#define __PYTHON_TSIGKEY_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class TSIGKey;
+class TSIGKeyRing;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_TSIGKey : public PyObject {
+public:
+ s_TSIGKey();
+ TSIGKey* cppobj;
+};
+
+class s_TSIGKeyRing : public PyObject {
+public:
+ s_TSIGKeyRing();
+ TSIGKeyRing* cppobj;
+};
+
+extern PyTypeObject tsigkey_type;
+extern PyTypeObject tsigkeyring_type;
+
+bool initModulePart_TSIGKey(PyObject* mod);
+bool initModulePart_TSIGKeyRing(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_TSIGKEY_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/tsigrecord_python.cc b/src/lib/dns/python/tsigrecord_python.cc
new file mode 100644
index 0000000..8a78b5e
--- /dev/null
+++ b/src/lib/dns/python/tsigrecord_python.cc
@@ -0,0 +1,311 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/tsigrecord.h>
+
+#include "pydnspp_common.h"
+#include "pydnspp_towire.h"
+#include "name_python.h"
+#include "tsig_rdata_python.h"
+#include "tsigrecord_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns;
+using namespace isc::dns::python;
+
+//
+// Definition of the classes
+//
+
+// For each class, we need a struct, a helper functions (init, destroy,
+// and static wrappers around the methods we export), a list of methods,
+// and a type description
+
+//
+// TSIGRecord
+//
+
+// Trivial constructor.
+s_TSIGRecord::s_TSIGRecord() : cppobj(NULL) {
+}
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_TSIGRecord, TSIGRecord> TSIGRecordContainer;
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+
+// General creation and destruction
+int TSIGRecord_init(s_TSIGRecord* self, PyObject* args);
+void TSIGRecord_destroy(s_TSIGRecord* self);
+PyObject* TSIGRecord_toText(const s_TSIGRecord* const self);
+PyObject* TSIGRecord_str(PyObject* self);
+PyObject* TSIGRecord_toWire(const s_TSIGRecord* self, PyObject* args);
+PyObject* TSIGRecord_getName(const s_TSIGRecord* self);
+PyObject* TSIGRecord_getLength(const s_TSIGRecord* self);
+PyObject* TSIGRecord_getRdata(const s_TSIGRecord* self);
+
+// These are the functions we export
+// For a minimal support, we don't need them.
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef TSIGRecord_methods[] = {
+ { "get_name", reinterpret_cast<PyCFunction>(TSIGRecord_getName),
+ METH_NOARGS,
+ "Return the owner name of the TSIG RR, which is the TSIG key name" },
+ { "get_length", reinterpret_cast<PyCFunction>(TSIGRecord_getLength),
+ METH_NOARGS,
+ "Return the length of the TSIG record" },
+ { "get_rdata", reinterpret_cast<PyCFunction>(TSIGRecord_getRdata),
+ METH_NOARGS,
+ "Return the RDATA of the TSIG RR" },
+ { "to_text", reinterpret_cast<PyCFunction>(TSIGRecord_toText), METH_NOARGS,
+ "Returns the text representation" },
+ { "to_wire", reinterpret_cast<PyCFunction>(TSIGRecord_toWire),
+ METH_VARARGS,
+ "Converts the TSIGRecord object to wire format.\n"
+ "The argument can be either a MessageRenderer or an object that "
+ "implements the sequence interface. If the object is mutable "
+ "(for instance a bytearray()), the wire data is added in-place.\n"
+ "If it is not (for instance a bytes() object), a new object is "
+ "returned" },
+ { NULL, NULL, 0, NULL }
+};
+
+int
+TSIGRecord_init(s_TSIGRecord* self, PyObject* args) {
+ try {
+ const s_Name* py_name;
+ const s_TSIG* py_tsig;
+ if (PyArg_ParseTuple(args, "O!O!", &name_type, &py_name,
+ &tsig_type, &py_tsig)) {
+ self->cppobj = new TSIGRecord(*py_name->cppobj, *py_tsig->cppobj);
+ return (0);
+ }
+ } catch (const exception& ex) {
+ const string ex_what = "Failed to construct TSIGRecord object: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (-1);
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in constructing TSIGRecord");
+ return (-1);
+ }
+
+ PyErr_SetString(PyExc_TypeError,
+ "Invalid arguments to TSIGRecord constructor");
+
+ return (-1);
+}
+
+// This is a template of typical code logic of python object destructor.
+// In many cases you can use it without modification, but check that carefully.
+void
+TSIGRecord_destroy(s_TSIGRecord* const self) {
+ delete self->cppobj;
+ self->cppobj = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+// This should be able to be used without modification as long as the
+// underlying C++ class has toText().
+PyObject*
+TSIGRecord_toText(const s_TSIGRecord* const self) {
+ try {
+ // toText() could throw, so we need to catch any exceptions below.
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Failed to convert TSIGRecord object to text: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+ "converting TSIGRecord object to text");
+ }
+ return (NULL);
+}
+
+PyObject*
+TSIGRecord_str(PyObject* self) {
+ // Simply call the to_text method we already defined
+ return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
+ const_cast<char*>("")));
+}
+
+PyObject*
+TSIGRecord_toWire(const s_TSIGRecord* const self, PyObject* args) {
+ typedef ToWireCallInt<const TSIGRecord> ToWireCall;
+ PyObject* (*towire_fn)(const s_TSIGRecord* const, PyObject*) =
+ toWireWrapper<s_TSIGRecord, TSIGRecord, ToWireCall>;
+ return (towire_fn(self, args));
+}
+
+PyObject*
+TSIGRecord_getName(const s_TSIGRecord* const self) {
+ try {
+ return (createNameObject(self->cppobj->getName()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Failed to get TSIGRecord name: " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+ "getting TSIGRecord name");
+ }
+ return (NULL);
+}
+
+PyObject*
+TSIGRecord_getLength(const s_TSIGRecord* const self) {
+ return (Py_BuildValue("H", self->cppobj->getLength()));
+}
+
+PyObject*
+TSIGRecord_getRdata(const s_TSIGRecord* const self) {
+ try {
+ return (createTSIGObject(self->cppobj->getRdata()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Failed to get TSIGRecord RDATA: " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+ "getting TSIGRecord RDATA");
+ }
+ return (NULL);
+}
+
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_TSIGRecord
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject tsigrecord_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.TSIGRecord",
+ sizeof(s_TSIGRecord), // tp_basicsize
+ 0, // tp_itemsize
+ reinterpret_cast<destructor>(TSIGRecord_destroy), // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ TSIGRecord_str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The TSIGRecord class objects is...(COMPLETE THIS)",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ TSIGRecord_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ reinterpret_cast<initproc>(TSIGRecord_init), // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_TSIGRecord(PyObject* mod) {
+ // We initialize the static description object with PyType_Ready(),
+ // then add it to the module. This is not just a check! (leaving
+ // this out results in segmentation faults)
+ if (PyType_Ready(&tsigrecord_type) < 0) {
+ return (false);
+ }
+ void* p = &tsigrecord_type;
+ if (PyModule_AddObject(mod, "TSIGRecord", static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&tsigrecord_type);
+
+ // The following template is the typical procedure for installing class
+ // variables. If the class doesn't have a class variable, remove the
+ // entire try-catch clauses.
+ try {
+ // Constant class variables
+ installClassVariable(tsigrecord_type, "TSIG_TTL",
+ Py_BuildValue("I", 0));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure in TSIGRecord initialization: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in TSIGRecord initialization");
+ return (false);
+ }
+
+ return (true);
+}
+
+PyObject*
+createTSIGRecordObject(const TSIGRecord& source) {
+ TSIGRecordContainer container = PyObject_New(s_TSIGRecord,
+ &tsigrecord_type);
+ container.set(new TSIGRecord(source));
+ return (container.release());
+}
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/tsigrecord_python.h b/src/lib/dns/python/tsigrecord_python.h
new file mode 100644
index 0000000..e0a3526
--- /dev/null
+++ b/src/lib/dns/python/tsigrecord_python.h
@@ -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.
+
+#ifndef __PYTHON_TSIGRECORD_H
+#define __PYTHON_TSIGRECORD_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class TSIGRecord;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_TSIGRecord : public PyObject {
+public:
+ s_TSIGRecord();
+ TSIGRecord* cppobj;
+};
+
+extern PyTypeObject tsigrecord_type;
+
+bool initModulePart_TSIGRecord(PyObject* mod);
+
+/// This is A simple shortcut to create a python TSIGRecord object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called with in a try block
+/// followed by necessary setup for python exception.
+PyObject* createTSIGRecordObject(const TSIGRecord& source);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_TSIGRECORD_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/any_255/tsig_250.cc b/src/lib/dns/rdata/any_255/tsig_250.cc
index 8211e7f..2557965 100644
--- a/src/lib/dns/rdata/any_255/tsig_250.cc
+++ b/src/lib/dns/rdata/any_255/tsig_250.cc
@@ -71,7 +71,7 @@ getToken(istringstream& iss, const string& full_input) {
string token;
iss >> token;
if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid TSIG text: parse error" <<
+ isc_throw(InvalidRdataText, "Invalid TSIG text: parse error " <<
full_input);
}
return (token);
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index 8a83aae..cb1bb1c 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -40,6 +40,10 @@ BUILT_SOURCES += rdata_tsig_toWire1.wire rdata_tsig_toWire2.wire
BUILT_SOURCES += rdata_tsig_toWire3.wire rdata_tsig_toWire4.wire
BUILT_SOURCES += rdata_tsig_toWire5.wire
BUILT_SOURCES += tsigrecord_toWire1.wire tsigrecord_toWire2.wire
+BUILT_SOURCES += tsig_verify1.wire tsig_verify2.wire tsig_verify3.wire
+BUILT_SOURCES += tsig_verify4.wire tsig_verify5.wire tsig_verify6.wire
+BUILT_SOURCES += tsig_verify7.wire tsig_verify8.wire tsig_verify9.wire
+BUILT_SOURCES += tsig_verify10.wire
# NOTE: keep this in sync with real file listing
# so is included in tarball
@@ -108,6 +112,10 @@ EXTRA_DIST += rdata_tsig_toWire1.spec rdata_tsig_toWire2.spec
EXTRA_DIST += rdata_tsig_toWire3.spec rdata_tsig_toWire4.spec
EXTRA_DIST += rdata_tsig_toWire5.spec
EXTRA_DIST += tsigrecord_toWire1.spec tsigrecord_toWire2.spec
+EXTRA_DIST += tsig_verify1.spec tsig_verify2.spec tsig_verify3.spec
+EXTRA_DIST += tsig_verify4.spec tsig_verify5.spec tsig_verify6.spec
+EXTRA_DIST += tsig_verify7.spec tsig_verify8.spec tsig_verify9.spec
+EXTRA_DIST += tsig_verify10.spec
.spec.wire:
./gen-wiredata.py -o $@ $<
diff --git a/src/lib/dns/tests/testdata/gen-wiredata.py.in b/src/lib/dns/tests/testdata/gen-wiredata.py.in
index 7a82bfd..fd98c6e 100755
--- a/src/lib/dns/tests/testdata/gen-wiredata.py.in
+++ b/src/lib/dns/tests/testdata/gen-wiredata.py.in
@@ -283,9 +283,8 @@ class NS(RR):
f.write('# NS name=%s\n' % (self.nsname))
f.write('%s\n' % nsname_wire)
-class SOA:
- # this currently doesn't support name compression within the RDATA.
- rdlen = -1 # auto-calculate
+class SOA(RR):
+ rdlen = None # auto-calculate
mname = 'ns.example.com'
rname = 'root.example.com'
serial = 2010012601
@@ -296,11 +295,9 @@ class SOA:
def dump(self, f):
mname_wire = encode_name(self.mname)
rname_wire = encode_name(self.rname)
- rdlen = self.rdlen
- if rdlen < 0:
- rdlen = int(20 + len(mname_wire) / 2 + len(str(rname_wire)) / 2)
- f.write('\n# SOA RDATA (RDLEN=%d)\n' % rdlen)
- f.write('%04x\n' % rdlen);
+ if self.rdlen is None:
+ self.rdlen = int(20 + len(mname_wire) / 2 + len(str(rname_wire)) / 2)
+ self.dump_header(f, self.rdlen)
f.write('# NNAME=%s RNAME=%s\n' % (self.mname, self.rname))
f.write('%s %s\n' % (mname_wire, rname_wire))
f.write('# SERIAL(%d) REFRESH(%d) RETRY(%d) EXPIRE(%d) MINIMUM(%d)\n' %
diff --git a/src/lib/dns/tests/testdata/tsig_verify1.spec b/src/lib/dns/tests/testdata/tsig_verify1.spec
new file mode 100644
index 0000000..687013a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify1.spec
@@ -0,0 +1,19 @@
+#
+# An example of signed AXFR request
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x3410
+arcount: 1
+[question]
+rrtype: AXFR
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8e951
+mac_size: 16
+mac: 0x35b2fd08268781634400c7c8a5533b13
+original_id: 0x3410
diff --git a/src/lib/dns/tests/testdata/tsig_verify10.spec b/src/lib/dns/tests/testdata/tsig_verify10.spec
new file mode 100644
index 0000000..33ce83e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify10.spec
@@ -0,0 +1,22 @@
+#
+# A simple DNS query message with TSIG signed whose MAC is too short
+# (only 1 byte)
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 1
+mac: 0x22
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify2.spec b/src/lib/dns/tests/testdata/tsig_verify2.spec
new file mode 100644
index 0000000..ff98ca3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify2.spec
@@ -0,0 +1,32 @@
+#
+# An example of signed AXFR response
+#
+
+[custom]
+sections: header:question:soa:tsig
+[header]
+id: 0x3410
+aa: 1
+qr: 1
+ancount: 1
+arcount: 1
+[question]
+rrtype: AXFR
+[soa]
+# note that names are compressed in this RR
+as_rr: True
+rr_name: ptr=12
+mname: ns.ptr=12
+rname: root.ptr=12
+serial: 2011041503
+refresh: 7200
+retry: 3600
+expire: 2592000
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8e951
+mac_size: 16
+mac: 0xbdd612cd2c7f9e0648bd6dc23713e83c
+original_id: 0x3410
diff --git a/src/lib/dns/tests/testdata/tsig_verify3.spec b/src/lib/dns/tests/testdata/tsig_verify3.spec
new file mode 100644
index 0000000..7e2f797
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify3.spec
@@ -0,0 +1,26 @@
+#
+# An example of signed AXFR response (continued)
+#
+
+[custom]
+sections: header:ns:tsig
+[header]
+id: 0x3410
+aa: 1
+qr: 1
+qdcount: 0
+ancount: 1
+arcount: 1
+[ns]
+# note that names are compressed in this RR
+as_rr: True
+rr_name: example.com.
+nsname: ns.ptr=12
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8e951
+mac_size: 16
+mac: 0x102458f7f62ddd7d638d746034130968
+original_id: 0x3410
diff --git a/src/lib/dns/tests/testdata/tsig_verify4.spec b/src/lib/dns/tests/testdata/tsig_verify4.spec
new file mode 100644
index 0000000..4ffbbcf
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify4.spec
@@ -0,0 +1,27 @@
+#
+# An example of signed DNS response with bogus MAC
+#
+
+[custom]
+sections: header:question:a:tsig
+[header]
+id: 0x2d65
+aa: 1
+qr: 1
+rd: 1
+ancount: 1
+arcount: 1
+[question]
+name: www.example.com
+[a]
+as_rr: True
+rr_name: ptr=12
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+# bogus MAC
+mac: 0xdeadbeefdeadbeefdeadbeefdeadbeef
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify5.spec b/src/lib/dns/tests/testdata/tsig_verify5.spec
new file mode 100644
index 0000000..a6cc643
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify5.spec
@@ -0,0 +1,26 @@
+#
+# An example of signed DNS response
+#
+
+[custom]
+sections: header:question:a:tsig
+[header]
+id: 0x2d65
+aa: 1
+qr: 1
+rd: 1
+ancount: 1
+arcount: 1
+[question]
+name: www.example.com
+[a]
+as_rr: True
+rr_name: ptr=12
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x8fcda66a7cd1a3b9948eb1869d384a9f
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify6.spec b/src/lib/dns/tests/testdata/tsig_verify6.spec
new file mode 100644
index 0000000..32e0818
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify6.spec
@@ -0,0 +1,21 @@
+#
+# Forwarded DNS query message with TSIG signed (header ID != orig ID)
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x1035
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify7.spec b/src/lib/dns/tests/testdata/tsig_verify7.spec
new file mode 100644
index 0000000..377578e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify7.spec
@@ -0,0 +1,21 @@
+#
+# DNS query message with TSIG that has empty MAC (invalidly)
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 0
+mac: ''
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify8.spec b/src/lib/dns/tests/testdata/tsig_verify8.spec
new file mode 100644
index 0000000..5432d4a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify8.spec
@@ -0,0 +1,23 @@
+#
+# DNS query message with TSIG that has empty MAC + BADKEY error
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 0
+mac: ''
+# 17: BADKEY
+error: 17
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify9.spec b/src/lib/dns/tests/testdata/tsig_verify9.spec
new file mode 100644
index 0000000..5888455
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify9.spec
@@ -0,0 +1,21 @@
+#
+# A simple DNS query message with TSIG signed, but TSIG key and algorithm
+# names have upper case characters (unusual)
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_name: WWW.EXAMPLE.COM
+algorithm: HMAC-MD5.SIG-ALG.REG.INT
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/tsig_unittest.cc b/src/lib/dns/tests/tsig_unittest.cc
index efa0490..55c3ac2 100644
--- a/src/lib/dns/tests/tsig_unittest.cc
+++ b/src/lib/dns/tests/tsig_unittest.cc
@@ -70,8 +70,13 @@ class TSIGTest : public ::testing::Test {
protected:
TSIGTest() :
tsig_ctx(NULL), qid(0x2d65), test_name("www.example.com"),
- test_class(RRClass::IN()), test_ttl(86400), message(Message::RENDER),
- buffer(0), renderer(buffer)
+ badkey_name("badkey.example.com"), test_class(RRClass::IN()),
+ test_ttl(86400), message(Message::RENDER), buffer(0), renderer(buffer),
+ dummy_data(1024, 0xdd), // should be sufficiently large for all tests
+ dummy_record(badkey_name, any::TSIG(TSIGKey::HMACMD5_NAME(),
+ 0x4da8877a,
+ TSIGContext::DEFAULT_FUDGE,
+ 0, NULL, qid, 0, 0, NULL))
{
// Make sure we use the system time by default so that we won't be
// confused due to other tests that tweak the time.
@@ -103,6 +108,8 @@ protected:
bool add_question = true,
Rcode rcode = Rcode::NOERROR());
+ void createMessageFromFile(const char* datafile);
+
// bit-wise constant flags to configure DNS header flags for test
// messages.
static const unsigned int QR_FLAG = 0x1;
@@ -111,14 +118,19 @@ protected:
boost::scoped_ptr<TSIGContext> tsig_ctx;
boost::scoped_ptr<TSIGContext> tsig_verify_ctx;
+ TSIGKeyRing keyring;
const uint16_t qid;
const Name test_name;
+ const Name badkey_name;
const RRClass test_class;
const RRTTL test_ttl;
Message message;
OutputBuffer buffer;
MessageRenderer renderer;
vector<uint8_t> secret;
+ vector<uint8_t> dummy_data;
+ const TSIGRecord dummy_record;
+ vector<uint8_t> received_data;
};
ConstTSIGRecordPtr
@@ -157,15 +169,27 @@ TSIGTest::createMessageAndSign(uint16_t id, const Name& qname,
renderer.clear();
message.toWire(renderer);
+ TSIGContext::State expected_new_state =
+ (ctx->getState() == TSIGContext::INIT) ?
+ TSIGContext::SENT_REQUEST : TSIGContext::SENT_RESPONSE;
ConstTSIGRecordPtr tsig = ctx->sign(id, renderer.getData(),
renderer.getLength());
- EXPECT_EQ(TSIGContext::SIGNED, ctx->getState());
+ EXPECT_EQ(expected_new_state, ctx->getState());
return (tsig);
}
void
-commonTSIGChecks(ConstTSIGRecordPtr tsig, uint16_t expected_qid,
+TSIGTest::createMessageFromFile(const char* datafile) {
+ message.clear(Message::PARSE);
+ received_data.clear();
+ UnitTestUtil::readWireData(datafile, received_data);
+ InputBuffer buffer(&received_data[0], received_data.size());
+ message.fromWire(buffer);
+}
+
+void
+commonSignChecks(ConstTSIGRecordPtr tsig, uint16_t expected_qid,
uint64_t expected_timesigned,
const uint8_t* expected_mac, size_t expected_maclen,
uint16_t expected_error = 0,
@@ -191,6 +215,17 @@ commonTSIGChecks(ConstTSIGRecordPtr tsig, uint16_t expected_qid,
expected_otherdata, expected_otherlen);
}
+void
+commonVerifyChecks(TSIGContext& ctx, const TSIGRecord* record,
+ const void* data, size_t data_len, TSIGError expected_error,
+ TSIGContext::State expected_new_state =
+ TSIGContext::VERIFIED_RESPONSE)
+{
+ EXPECT_EQ(expected_error, ctx.verify(record, data, data_len));
+ EXPECT_EQ(expected_error, ctx.getError());
+ EXPECT_EQ(expected_new_state, ctx.getState());
+}
+
TEST_F(TSIGTest, initialState) {
// Until signing or verifying, the state should be INIT
EXPECT_EQ(TSIGContext::INIT, tsig_ctx->getState());
@@ -199,6 +234,38 @@ TEST_F(TSIGTest, initialState) {
EXPECT_EQ(TSIGError(Rcode::NOERROR()), tsig_ctx->getError());
}
+TEST_F(TSIGTest, constructFromKeyRing) {
+ // Construct a TSIG context with an empty key ring. Key shouldn't be
+ // found, and the BAD_KEY error should be recorded.
+ TSIGContext ctx1(test_name, TSIGKey::HMACMD5_NAME(), keyring);
+ EXPECT_EQ(TSIGContext::INIT, ctx1.getState());
+ EXPECT_EQ(TSIGError::BAD_KEY(), ctx1.getError());
+
+ // Add a matching key (we don't use the secret so leave it empty), and
+ // construct it again. This time it should be constructed with a valid
+ // key.
+ keyring.add(TSIGKey(test_name, TSIGKey::HMACMD5_NAME(), NULL, 0));
+ TSIGContext ctx2(test_name, TSIGKey::HMACMD5_NAME(), keyring);
+ EXPECT_EQ(TSIGContext::INIT, ctx2.getState());
+ EXPECT_EQ(TSIGError::NOERROR(), ctx2.getError());
+
+ // Similar to the first case except that the key ring isn't empty but
+ // it doesn't contain a matching key.
+ TSIGContext ctx3(test_name, TSIGKey::HMACSHA1_NAME(), keyring);
+ EXPECT_EQ(TSIGContext::INIT, ctx3.getState());
+ EXPECT_EQ(TSIGError::BAD_KEY(), ctx3.getError());
+
+ TSIGContext ctx4(Name("different-key.example"), TSIGKey::HMACMD5_NAME(),
+ keyring);
+ EXPECT_EQ(TSIGContext::INIT, ctx4.getState());
+ EXPECT_EQ(TSIGError::BAD_KEY(), ctx4.getError());
+
+ // "Unknown" algorithm name will result in BADKEY, too.
+ TSIGContext ctx5(test_name, Name("unknown.algorithm"), keyring);
+ EXPECT_EQ(TSIGContext::INIT, ctx5.getState());
+ EXPECT_EQ(TSIGError::BAD_KEY(), ctx5.getError());
+}
+
// Example output generated by
// "dig -y www.example.com:SFuWd/q99SzF8Yzd1QbB9g== www.example.com
// QID: 0x2d65
@@ -213,7 +280,7 @@ TEST_F(TSIGTest, sign) {
{
SCOPED_TRACE("Sign test for query");
- commonTSIGChecks(createMessageAndSign(qid, test_name, tsig_ctx.get()),
+ commonSignChecks(createMessageAndSign(qid, test_name, tsig_ctx.get()),
qid, 0x4da8877a, common_expected_mac,
sizeof(common_expected_mac));
}
@@ -231,7 +298,7 @@ TEST_F(TSIGTest, signUsingUpperCasedKeyName) {
{
SCOPED_TRACE("Sign test for query using non canonical key name");
- commonTSIGChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid,
+ commonSignChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid,
0x4da8877a, common_expected_mac,
sizeof(common_expected_mac));
}
@@ -247,7 +314,7 @@ TEST_F(TSIGTest, signUsingUpperCasedAlgorithmName) {
{
SCOPED_TRACE("Sign test for query using non canonical algorithm name");
- commonTSIGChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid,
+ commonSignChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid,
0x4da8877a, common_expected_mac,
sizeof(common_expected_mac));
}
@@ -281,6 +348,19 @@ TEST_F(TSIGTest, signBadData) {
EXPECT_THROW(tsig_ctx->sign(0, &dummy_data, 0), InvalidParameter);
}
+TEST_F(TSIGTest, verifyBadData) {
+ // the data must at least hold the DNS message header and the specified
+ // TSIG.
+ EXPECT_THROW(tsig_ctx->verify(&dummy_record, &dummy_data[0],
+ 12 + dummy_record.getLength() - 1),
+ InvalidParameter);
+
+ // And the data must not be NULL.
+ EXPECT_THROW(tsig_ctx->verify(&dummy_record, NULL,
+ 12 + dummy_record.getLength()),
+ InvalidParameter);
+}
+
#ifdef ENABLE_CUSTOM_OPERATOR_NEW
// We enable this test only when we enable custom new/delete at build time
// We could enable/disable the test runtime using the gtest filter, but
@@ -293,11 +373,10 @@ TEST_F(TSIGTest, signExceptionSafety) {
// complicated and involves more memory allocation, so the test result
// won't be reliable.
- tsig_verify_ctx->verifyTentative(createMessageAndSign(qid, test_name,
- tsig_ctx.get()),
- TSIGError::BAD_KEY());
- // At this point the state should be changed to "CHECKED"
- ASSERT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState());
+ commonVerifyChecks(*tsig_verify_ctx, &dummy_record, &dummy_data[0],
+ dummy_data.size(), TSIGError::BAD_KEY(),
+ TSIGContext::RECEIVED_REQUEST);
+
try {
int dummydata;
isc::util::unittests::force_throw_on_new = true;
@@ -308,8 +387,8 @@ TEST_F(TSIGTest, signExceptionSafety) {
} catch (const std::bad_alloc&) {
isc::util::unittests::force_throw_on_new = false;
- // sign() threw, so the state should still be "CHECKED".
- EXPECT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState());
+ // sign() threw, so the state should still be RECEIVED_REQUEST
+ EXPECT_EQ(TSIGContext::RECEIVED_REQUEST, tsig_verify_ctx->getState());
}
isc::util::unittests::force_throw_on_new = false;
}
@@ -339,70 +418,130 @@ TEST_F(TSIGTest, signUsingHMACSHA1) {
};
{
SCOPED_TRACE("Sign test using HMAC-SHA1");
- commonTSIGChecks(createMessageAndSign(sha1_qid, test_name, &sha1_ctx),
+ commonSignChecks(createMessageAndSign(sha1_qid, test_name, &sha1_ctx),
sha1_qid, 0x4dae7d5f, expected_mac,
sizeof(expected_mac), 0, 0, NULL,
TSIGKey::HMACSHA1_NAME());
}
}
-// An example response to the signed query used for the "sign" test.
+// The first part of this test checks verifying the signed query used for
+// the "sign" test.
+// The second part of this test generates a signed response to the signed
+// query as follows:
// Answer: www.example.com. 86400 IN A 192.0.2.1
// MAC: 8fcda66a7cd1a3b9948eb1869d384a9f
-TEST_F(TSIGTest, signResponse) {
+TEST_F(TSIGTest, verifyThenSignResponse) {
isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
- ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
- tsig_ctx.get());
- tsig_verify_ctx->verifyTentative(tsig);
- EXPECT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState());
+ // This test data for the message test has the same wire format data
+ // as the message used in the "sign" test.
+ createMessageFromFile("message_toWire2.wire");
+ {
+ SCOPED_TRACE("Verify test for request");
+ commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::NOERROR(), TSIGContext::RECEIVED_REQUEST);
+ }
// Transform the original message to a response, then sign the response
// with the context of "verified state".
- tsig = createMessageAndSign(qid, test_name, tsig_verify_ctx.get(),
- QR_FLAG|AA_FLAG|RD_FLAG,
- RRType::A(), "192.0.2.1");
+ ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+ tsig_verify_ctx.get(),
+ QR_FLAG|AA_FLAG|RD_FLAG,
+ RRType::A(), "192.0.2.1");
const uint8_t expected_mac[] = {
0x8f, 0xcd, 0xa6, 0x6a, 0x7c, 0xd1, 0xa3, 0xb9,
0x94, 0x8e, 0xb1, 0x86, 0x9d, 0x38, 0x4a, 0x9f
};
{
SCOPED_TRACE("Sign test for response");
- commonTSIGChecks(tsig, qid, 0x4da8877a,
- expected_mac, sizeof(expected_mac));
+ commonSignChecks(tsig, qid, 0x4da8877a, expected_mac,
+ sizeof(expected_mac));
+ }
+}
+
+TEST_F(TSIGTest, verifyUpperCaseNames) {
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+ // This test data for the message test has the same wire format data
+ // as the message used in the "sign" test.
+ createMessageFromFile("tsig_verify9.wire");
+ {
+ SCOPED_TRACE("Verify test for request");
+ commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::NOERROR(), TSIGContext::RECEIVED_REQUEST);
+ }
+}
+
+TEST_F(TSIGTest, verifyForwardedMessage) {
+ // Similar to the first part of the previous test, but this test emulates
+ // the "forward" case, where the ID of the Header and the original ID in
+ // TSIG is different.
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+ createMessageFromFile("tsig_verify6.wire");
+ {
+ SCOPED_TRACE("Verify test for forwarded request");
+ commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::NOERROR(), TSIGContext::RECEIVED_REQUEST);
}
}
// Example of signing multiple messages in a single TCP stream,
// taken from data using BIND 9's "one-answer" transfer-format.
+// Request:
+// QID: 0x3410, flags (none)
+// Question: example.com/IN/AXFR
+// Time Signed: 0x4da8e951
+// MAC: 35b2fd08268781634400c7c8a5533b13
// First message:
// QID: 0x3410, flags QR, AA
// Question: example.com/IN/AXFR
// Answer: example.com. 86400 IN SOA ns.example.com. root.example.com. (
// 2011041503 7200 3600 2592000 1200)
-// Time Signed: 0x4da8e951
+// MAC: bdd612cd2c7f9e0648bd6dc23713e83c
// Second message:
-// Answer: example.com. 86400 IN NS ns.example.com.
-// MAC: 102458f7f62ddd7d638d746034130968
+// Answer: example.com. 86400 IN NS ns.example.com.
+// MAC: 102458f7f62ddd7d638d746034130968
TEST_F(TSIGTest, signContinuation) {
isc::util::detail::gettimeFunction = testGetTime<0x4da8e951>;
const uint16_t axfr_qid = 0x3410;
const Name zone_name("example.com");
- // Create and sign the AXFR request, then verify it.
- tsig_verify_ctx->verifyTentative(createMessageAndSign(axfr_qid, zone_name,
- tsig_ctx.get(), 0,
- RRType::AXFR()));
- EXPECT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState());
+ // Create and sign the AXFR request
+ ConstTSIGRecordPtr tsig = createMessageAndSign(axfr_qid, zone_name,
+ tsig_ctx.get(), 0,
+ RRType::AXFR());
+ // Then verify it (the wire format test data should contain the same
+ // message data, and verification should succeed).
+ received_data.clear();
+ UnitTestUtil::readWireData("tsig_verify1.wire", received_data);
+ {
+ SCOPED_TRACE("Verify AXFR query");
+ commonVerifyChecks(*tsig_verify_ctx, tsig.get(), &received_data[0],
+ received_data.size(), TSIGError::NOERROR(),
+ TSIGContext::RECEIVED_REQUEST);
+ }
- // Create and sign the first response message (we don't need the result
- // for the purpose of this test)
- createMessageAndSign(axfr_qid, zone_name, tsig_verify_ctx.get(),
- AA_FLAG|QR_FLAG, RRType::AXFR(),
- "ns.example.com. root.example.com. "
- "2011041503 7200 3600 2592000 1200",
- &RRType::SOA());
+ // Create and sign the first response message
+ tsig = createMessageAndSign(axfr_qid, zone_name, tsig_verify_ctx.get(),
+ AA_FLAG|QR_FLAG, RRType::AXFR(),
+ "ns.example.com. root.example.com. "
+ "2011041503 7200 3600 2592000 1200",
+ &RRType::SOA());
+
+ // Then verify it at the requester side.
+ received_data.clear();
+ UnitTestUtil::readWireData("tsig_verify2.wire", received_data);
+ {
+ SCOPED_TRACE("Verify first AXFR response");
+ commonVerifyChecks(*tsig_ctx, tsig.get(), &received_data[0],
+ received_data.size(), TSIGError::NOERROR());
+ }
// Create and sign the second response message
const uint8_t expected_mac[] = {
@@ -411,13 +550,20 @@ TEST_F(TSIGTest, signContinuation) {
};
{
SCOPED_TRACE("Sign test for continued response in TCP stream");
- commonTSIGChecks(createMessageAndSign(axfr_qid, zone_name,
- tsig_verify_ctx.get(),
- AA_FLAG|QR_FLAG, RRType::AXFR(),
- "ns.example.com.", &RRType::NS(),
- false),
- axfr_qid, 0x4da8e951,
- expected_mac, sizeof(expected_mac));
+ tsig = createMessageAndSign(axfr_qid, zone_name, tsig_verify_ctx.get(),
+ AA_FLAG|QR_FLAG, RRType::AXFR(),
+ "ns.example.com.", &RRType::NS(), false);
+ commonSignChecks(tsig, axfr_qid, 0x4da8e951, expected_mac,
+ sizeof(expected_mac));
+ }
+
+ // Then verify it at the requester side.
+ received_data.clear();
+ UnitTestUtil::readWireData("tsig_verify3.wire", received_data);
+ {
+ SCOPED_TRACE("Verify second AXFR response");
+ commonVerifyChecks(*tsig_ctx, tsig.get(), &received_data[0],
+ received_data.size(), TSIGError::NOERROR());
}
}
@@ -443,10 +589,13 @@ TEST_F(TSIGTest, badtimeResponse) {
RRType::SOA());
// "advance the clock" and try validating, which should fail due to BADTIME
- // (verifyTentative actually doesn't check the time, though)
isc::util::detail::gettimeFunction = testGetTime<0x4da8be86>;
- tsig_verify_ctx->verifyTentative(tsig, TSIGError::BAD_TIME());
- EXPECT_EQ(TSIGError::BAD_TIME(), tsig_verify_ctx->getError());
+ {
+ SCOPED_TRACE("Verify resulting in BADTIME due to expired SIG");
+ commonVerifyChecks(*tsig_verify_ctx, tsig.get(), &dummy_data[0],
+ dummy_data.size(), TSIGError::BAD_TIME(),
+ TSIGContext::RECEIVED_REQUEST);
+ }
// make and sign a response in the context of TSIG error.
tsig = createMessageAndSign(test_qid, test_name, tsig_verify_ctx.get(),
@@ -459,7 +608,7 @@ TEST_F(TSIGTest, badtimeResponse) {
};
{
SCOPED_TRACE("Sign test for response with BADTIME");
- commonTSIGChecks(tsig, message.getQid(), 0x4da8b9d6,
+ commonSignChecks(tsig, message.getQid(), 0x4da8b9d6,
expected_mac, sizeof(expected_mac),
18, // error: BADTIME
sizeof(expected_otherdata),
@@ -467,21 +616,86 @@ TEST_F(TSIGTest, badtimeResponse) {
}
}
+TEST_F(TSIGTest, badtimeResponse2) {
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6>;
+
+ ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+ tsig_ctx.get(), 0,
+ RRType::SOA());
+
+ // "rewind the clock" and try validating, which should fail due to BADTIME
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6 - 600>;
+ {
+ SCOPED_TRACE("Verify resulting in BADTIME due to too future SIG");
+ commonVerifyChecks(*tsig_verify_ctx, tsig.get(), &dummy_data[0],
+ dummy_data.size(), TSIGError::BAD_TIME(),
+ TSIGContext::RECEIVED_REQUEST);
+ }
+}
+
+TEST_F(TSIGTest, badtimeBoundaries) {
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6>;
+
+ // Test various boundary conditions. We intentionally use the magic
+ // number of 300 instead of the constant variable for testing.
+ // In the okay cases, signature is not correct, but it's sufficient to
+ // check the error code isn't BADTIME for the purpose of this test.
+ ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+ tsig_ctx.get(), 0,
+ RRType::SOA());
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6 + 301>;
+ EXPECT_EQ(TSIGError::BAD_TIME(),
+ tsig_verify_ctx->verify(tsig.get(), &dummy_data[0],
+ dummy_data.size()));
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6 + 300>;
+ EXPECT_NE(TSIGError::BAD_TIME(),
+ tsig_verify_ctx->verify(tsig.get(), &dummy_data[0],
+ dummy_data.size()));
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6 - 301>;
+ EXPECT_EQ(TSIGError::BAD_TIME(),
+ tsig_verify_ctx->verify(tsig.get(), &dummy_data[0],
+ dummy_data.size()));
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6 - 300>;
+ EXPECT_NE(TSIGError::BAD_TIME(),
+ tsig_verify_ctx->verify(tsig.get(), &dummy_data[0],
+ dummy_data.size()));
+}
+
+TEST_F(TSIGTest, badtimeOverflow) {
+ isc::util::detail::gettimeFunction = testGetTime<200>;
+ ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+ tsig_ctx.get(), 0,
+ RRType::SOA());
+
+ // This should be in the okay range, but since "200 - fudge" overflows
+ // and we compare them as 64-bit unsigned integers, it results in a false
+ // positive (we intentionally accept that).
+ isc::util::detail::gettimeFunction = testGetTime<100>;
+ EXPECT_EQ(TSIGError::BAD_TIME(),
+ tsig_verify_ctx->verify(tsig.get(), &dummy_data[0],
+ dummy_data.size()));
+}
+
TEST_F(TSIGTest, badsigResponse) {
isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
- // Sign a simple message, and force the verification to fail with
- // BADSIG.
- tsig_verify_ctx->verifyTentative(createMessageAndSign(qid, test_name,
- tsig_ctx.get()),
- TSIGError::BAD_SIG());
+ // Try to sign a simple message with bogus secret. It should fail
+ // with BADSIG.
+ createMessageFromFile("message_toWire2.wire");
+ TSIGContext bad_ctx(TSIGKey(test_name, TSIGKey::HMACMD5_NAME(),
+ &dummy_data[0], dummy_data.size()));
+ {
+ SCOPED_TRACE("Verify resulting in BADSIG");
+ commonVerifyChecks(bad_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::BAD_SIG(), TSIGContext::RECEIVED_REQUEST);
+ }
// Sign the same message (which doesn't matter for this test) with the
// context of "checked state".
{
SCOPED_TRACE("Sign test for response with BADSIG error");
- commonTSIGChecks(createMessageAndSign(qid, test_name,
- tsig_verify_ctx.get()),
+ commonSignChecks(createMessageAndSign(qid, test_name, &bad_ctx),
message.getQid(), 0x4da8877a, NULL, 0,
16); // 16: BADSIG
}
@@ -490,15 +704,203 @@ TEST_F(TSIGTest, badsigResponse) {
TEST_F(TSIGTest, badkeyResponse) {
// A similar test as badsigResponse but for BADKEY
isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
- tsig_verify_ctx->verifyTentative(createMessageAndSign(qid, test_name,
- tsig_ctx.get()),
- TSIGError::BAD_KEY());
+ tsig_ctx.reset(new TSIGContext(badkey_name, TSIGKey::HMACMD5_NAME(),
+ keyring));
+ {
+ SCOPED_TRACE("Verify resulting in BADKEY");
+ commonVerifyChecks(*tsig_ctx, &dummy_record, &dummy_data[0],
+ dummy_data.size(), TSIGError::BAD_KEY(),
+ TSIGContext::RECEIVED_REQUEST);
+ }
+
{
SCOPED_TRACE("Sign test for response with BADKEY error");
- commonTSIGChecks(createMessageAndSign(qid, test_name,
- tsig_verify_ctx.get()),
- message.getQid(), 0x4da8877a, NULL, 0,
- 17); // 17: BADKEYSIG
+ ConstTSIGRecordPtr sig = createMessageAndSign(qid, test_name,
+ tsig_ctx.get());
+ EXPECT_EQ(badkey_name, sig->getName());
+ commonSignChecks(sig, qid, 0x4da8877a, NULL, 0, 17); // 17: BADKEY
+ }
+}
+
+TEST_F(TSIGTest, badkeyForResponse) {
+ // "BADKEY" case for a response to a signed message
+ createMessageAndSign(qid, test_name, tsig_ctx.get());
+ {
+ SCOPED_TRACE("Verify a response resulting in BADKEY");
+ commonVerifyChecks(*tsig_ctx, &dummy_record, &dummy_data[0],
+ dummy_data.size(), TSIGError::BAD_KEY(),
+ TSIGContext::SENT_REQUEST);
+ }
+
+ // A similar case with a different algorithm
+ const TSIGRecord dummy_record2(test_name,
+ any::TSIG(TSIGKey::HMACSHA1_NAME(),
+ 0x4da8877a,
+ TSIGContext::DEFAULT_FUDGE,
+ 0, NULL, qid, 0, 0, NULL));
+ {
+ SCOPED_TRACE("Verify a response resulting in BADKEY due to bad alg");
+ commonVerifyChecks(*tsig_ctx, &dummy_record2, &dummy_data[0],
+ dummy_data.size(), TSIGError::BAD_KEY(),
+ TSIGContext::SENT_REQUEST);
+ }
+}
+
+TEST_F(TSIGTest, badsigThenValidate) {
+ // According to RFC2845 4.6, if TSIG verification fails the client
+ // should discard that message and wait for another signed response.
+ // This test emulates that situation.
+
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+ createMessageAndSign(qid, test_name, tsig_ctx.get());
+
+ createMessageFromFile("tsig_verify4.wire");
+ {
+ SCOPED_TRACE("Verify a response that should fail due to BADSIG");
+ commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::BAD_SIG(), TSIGContext::SENT_REQUEST);
+ }
+
+ createMessageFromFile("tsig_verify5.wire");
+ {
+ SCOPED_TRACE("Verify a response after a BADSIG failure");
+ commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::NOERROR(),
+ TSIGContext::VERIFIED_RESPONSE);
+ }
+}
+
+TEST_F(TSIGTest, nosigThenValidate) {
+ // Similar to the previous test, but the first response doesn't contain
+ // TSIG.
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+ createMessageAndSign(qid, test_name, tsig_ctx.get());
+
+ {
+ SCOPED_TRACE("Verify a response without TSIG that should exist");
+ commonVerifyChecks(*tsig_ctx, NULL, &dummy_data[0],
+ dummy_data.size(), TSIGError::FORMERR(),
+ TSIGContext::SENT_REQUEST);
+ }
+
+ createMessageFromFile("tsig_verify5.wire");
+ {
+ SCOPED_TRACE("Verify a response after a FORMERR failure");
+ commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::NOERROR(),
+ TSIGContext::VERIFIED_RESPONSE);
+ }
+}
+
+TEST_F(TSIGTest, badtimeThenValidate) {
+ // Similar to the previous test, but the first response results in BADTIME.
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+ ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+ tsig_ctx.get());
+
+ // "advance the clock" and try validating, which should fail due to BADTIME
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8877a + 600>;
+ {
+ SCOPED_TRACE("Verify resulting in BADTIME due to expired SIG");
+ commonVerifyChecks(*tsig_ctx, tsig.get(), &dummy_data[0],
+ dummy_data.size(), TSIGError::BAD_TIME(),
+ TSIGContext::SENT_REQUEST);
+ }
+
+ // revert the clock again.
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+ createMessageFromFile("tsig_verify5.wire");
+ {
+ SCOPED_TRACE("Verify a response after a BADTIME failure");
+ commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::NOERROR(),
+ TSIGContext::VERIFIED_RESPONSE);
+ }
+}
+
+TEST_F(TSIGTest, emptyMAC) {
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+ // We don't allow empty MAC unless the TSIG error is BADSIG or BADKEY.
+ createMessageFromFile("tsig_verify7.wire");
+ {
+ SCOPED_TRACE("Verify test for request");
+ commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::BAD_SIG(), TSIGContext::RECEIVED_REQUEST);
+ }
+
+ // If the empty MAC comes with a BADKEY error, the error is passed
+ // transparently.
+ createMessageFromFile("tsig_verify8.wire");
+ {
+ SCOPED_TRACE("Verify test for request");
+ commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::BAD_KEY(), TSIGContext::RECEIVED_REQUEST);
+ }
+}
+
+TEST_F(TSIGTest, verifyAfterSendResponse) {
+ // Once the context is used for sending a signed response, it shouldn't
+ // be used for further verification.
+
+ // The following are essentially the same as what verifyThenSignResponse
+ // does with simplification.
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+ createMessageFromFile("message_toWire2.wire");
+ tsig_verify_ctx->verify(message.getTSIGRecord(), &received_data[0],
+ received_data.size());
+ EXPECT_EQ(TSIGContext::RECEIVED_REQUEST, tsig_verify_ctx->getState());
+ createMessageAndSign(qid, test_name, tsig_verify_ctx.get(),
+ QR_FLAG|AA_FLAG|RD_FLAG, RRType::A(), "192.0.2.1");
+ EXPECT_EQ(TSIGContext::SENT_RESPONSE, tsig_verify_ctx->getState());
+
+ // Now trying further verification.
+ createMessageFromFile("message_toWire2.wire");
+ EXPECT_THROW(tsig_verify_ctx->verify(message.getTSIGRecord(),
+ &received_data[0],
+ received_data.size()),
+ TSIGContextError);
+}
+
+TEST_F(TSIGTest, signAfterVerified) {
+ // Likewise, once the context verifies a response, it shouldn't for
+ // signing any more.
+
+ // The following are borrowed from badsigThenValidate (without the
+ // intermediate failure)
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+ createMessageAndSign(qid, test_name, tsig_ctx.get());
+ createMessageFromFile("tsig_verify5.wire");
+ tsig_ctx->verify(message.getTSIGRecord(), &received_data[0],
+ received_data.size());
+ EXPECT_EQ(TSIGContext::VERIFIED_RESPONSE, tsig_ctx->getState());
+
+ // Now trying further signing.
+ EXPECT_THROW(createMessageAndSign(qid, test_name, tsig_ctx.get()),
+ TSIGContextError);
+}
+
+TEST_F(TSIGTest, tooShortMAC) {
+ // Too short MAC should be rejected.
+ // Note: when we implement RFC4635-based checks, the error code will
+ // (probably) be FORMERR.
+
+ isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+ createMessageFromFile("tsig_verify10.wire");
+ {
+ SCOPED_TRACE("Verify test for request");
+ commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::BAD_SIG(), TSIGContext::RECEIVED_REQUEST);
}
}
diff --git a/src/lib/dns/tests/tsigerror_unittest.cc b/src/lib/dns/tests/tsigerror_unittest.cc
index 5866587..bb08aef 100644
--- a/src/lib/dns/tests/tsigerror_unittest.cc
+++ b/src/lib/dns/tests/tsigerror_unittest.cc
@@ -93,6 +93,20 @@ TEST(TSIGErrorTest, toText) {
EXPECT_EQ("65535", TSIGError(65535).toText());
}
+TEST(TSIGErrorTest, toRcode) {
+ // TSIGError derived from the standard Rcode
+ EXPECT_EQ(Rcode::NOERROR(), TSIGError(Rcode::NOERROR()).toRcode());
+
+ // Well known TSIG errors
+ EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_SIG().toRcode());
+ EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_KEY().toRcode());
+ EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_TIME().toRcode());
+
+ // Unknown (or not yet supported) codes are treated as SERVFAIL.
+ EXPECT_EQ(Rcode::SERVFAIL(), TSIGError(19).toRcode());
+ EXPECT_EQ(Rcode::SERVFAIL(), TSIGError(65535).toRcode());
+}
+
// test operator<<. We simply confirm it appends the result of toText().
TEST(TSIGErrorTest, LeftShiftOperator) {
ostringstream oss;
diff --git a/src/lib/dns/tests/tsigkey_unittest.cc b/src/lib/dns/tests/tsigkey_unittest.cc
index 96696d5..20ee802 100644
--- a/src/lib/dns/tests/tsigkey_unittest.cc
+++ b/src/lib/dns/tests/tsigkey_unittest.cc
@@ -33,13 +33,16 @@ class TSIGKeyTest : public ::testing::Test {
protected:
TSIGKeyTest() : secret("someRandomData"), key_name("example.com") {}
string secret;
- Name key_name;
+ const Name key_name;
};
TEST_F(TSIGKeyTest, algorithmNames) {
EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), TSIGKey::HMACMD5_NAME());
EXPECT_EQ(Name("hmac-sha1"), TSIGKey::HMACSHA1_NAME());
EXPECT_EQ(Name("hmac-sha256"), TSIGKey::HMACSHA256_NAME());
+ EXPECT_EQ(Name("hmac-sha224"), TSIGKey::HMACSHA224_NAME());
+ EXPECT_EQ(Name("hmac-sha384"), TSIGKey::HMACSHA384_NAME());
+ EXPECT_EQ(Name("hmac-sha512"), TSIGKey::HMACSHA512_NAME());
// Also check conversion to cryptolink definitions
EXPECT_EQ(isc::cryptolink::MD5, TSIGKey(key_name, TSIGKey::HMACMD5_NAME(),
@@ -49,6 +52,15 @@ TEST_F(TSIGKeyTest, algorithmNames) {
EXPECT_EQ(isc::cryptolink::SHA256, TSIGKey(key_name,
TSIGKey::HMACSHA256_NAME(),
NULL, 0).getAlgorithm());
+ EXPECT_EQ(isc::cryptolink::SHA224, TSIGKey(key_name,
+ TSIGKey::HMACSHA224_NAME(),
+ NULL, 0).getAlgorithm());
+ EXPECT_EQ(isc::cryptolink::SHA384, TSIGKey(key_name,
+ TSIGKey::HMACSHA384_NAME(),
+ NULL, 0).getAlgorithm());
+ EXPECT_EQ(isc::cryptolink::SHA512, TSIGKey(key_name,
+ TSIGKey::HMACSHA512_NAME(),
+ NULL, 0).getAlgorithm());
}
TEST_F(TSIGKeyTest, construct) {
@@ -59,9 +71,13 @@ TEST_F(TSIGKeyTest, construct) {
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret.c_str(),
secret.size(), key.getSecret(), key.getSecretLength());
+ // "unknown" algorithm is only accepted with empty secret.
EXPECT_THROW(TSIGKey(key_name, Name("unknown-alg"),
secret.c_str(), secret.size()),
isc::InvalidParameter);
+ TSIGKey key2(key_name, Name("unknown-alg"), NULL, 0);
+ EXPECT_EQ(key_name, key2.getKeyName());
+ EXPECT_EQ(Name("unknown-alg"), key2.getAlgorithmName());
// The algorithm name should be converted to the canonical form.
EXPECT_EQ("hmac-sha1.",
@@ -69,6 +85,7 @@ TEST_F(TSIGKeyTest, construct) {
secret.c_str(),
secret.size()).getAlgorithmName().toText());
+ // Same for key name
EXPECT_EQ("example.com.",
TSIGKey(Name("EXAMPLE.CoM."), TSIGKey::HMACSHA256_NAME(),
secret.c_str(),
@@ -125,12 +142,18 @@ class TSIGKeyRingTest : public ::testing::Test {
protected:
TSIGKeyRingTest() :
key_name("example.com"),
+ md5_name("hmac-md5.sig-alg.reg.int"),
+ sha1_name("hmac-sha1"),
+ sha256_name("hmac-sha256"),
secretstring("anotherRandomData"),
secret(secretstring.c_str()),
secret_len(secretstring.size())
{}
TSIGKeyRing keyring;
- Name key_name;
+ const Name key_name;
+ const Name md5_name;
+ const Name sha1_name;
+ const Name sha256_name;
private:
const string secretstring;
protected:
@@ -150,8 +173,8 @@ TEST_F(TSIGKeyRingTest, add) {
EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add(
TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
secret, secret_len)));
- // keys are identified their names, the same name of key with a different
- // algorithm would be considered a duplicate.
+ // keys are identified by their names, the same name of key with a
+ // different algorithm would be considered a duplicate.
EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add(
TSIGKey(Name("example.com"), TSIGKey::HMACSHA1_NAME(),
secret, secret_len)));
@@ -203,44 +226,60 @@ TEST_F(TSIGKeyRingTest, removeFromSome) {
}
TEST_F(TSIGKeyRingTest, find) {
- EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.find(key_name).code);
- EXPECT_EQ(static_cast<const TSIGKey*>(NULL), keyring.find(key_name).key);
-
- EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
- TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
- secret, secret_len)));
- const TSIGKeyRing::FindResult result(keyring.find(key_name));
- EXPECT_EQ(TSIGKeyRing::SUCCESS, result.code);
- EXPECT_EQ(key_name, result.key->getKeyName());
- EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result.key->getAlgorithmName());
+ // If the keyring is empty the search should fail.
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.find(key_name, md5_name).code);
+ EXPECT_EQ(static_cast<const TSIGKey*>(NULL),
+ keyring.find(key_name, md5_name).key);
+
+ // Add a key and try to find it. Should succeed.
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(key_name, sha256_name,
+ secret, secret_len)));
+ const TSIGKeyRing::FindResult result1(keyring.find(key_name, sha256_name));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, result1.code);
+ EXPECT_EQ(key_name, result1.key->getKeyName());
+ EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result1.key->getAlgorithmName());
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret, secret_len,
- result.key->getSecret(),
- result.key->getSecretLength());
+ result1.key->getSecret(),
+ result1.key->getSecretLength());
+
+ // If either key name or algorithm doesn't match, search should fail.
+ const TSIGKeyRing::FindResult result2 =
+ keyring.find(Name("different-key.example"), sha256_name);
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND, result2.code);
+ EXPECT_EQ(static_cast<const TSIGKey*>(NULL), result2.key);
+
+ const TSIGKeyRing::FindResult result3 = keyring.find(key_name, md5_name);
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND, result3.code);
+ EXPECT_EQ(static_cast<const TSIGKey*>(NULL), result3.key);
}
TEST_F(TSIGKeyRingTest, findFromSome) {
// essentially the same test, but search a larger set
- EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
- TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
- secret, secret_len)));
- EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
- TSIGKey(Name("another.example"), TSIGKey::HMACMD5_NAME(),
- secret, secret_len)));
- EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
- TSIGKey(Name("more.example"), TSIGKey::HMACSHA1_NAME(),
- secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(key_name, sha256_name,
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(Name("another.example"),
+ md5_name,
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(Name("more.example"),
+ sha1_name,
+ secret, secret_len)));
const TSIGKeyRing::FindResult result(
- keyring.find(Name("another.example")));
+ keyring.find(Name("another.example"), md5_name));
EXPECT_EQ(TSIGKeyRing::SUCCESS, result.code);
EXPECT_EQ(Name("another.example"), result.key->getKeyName());
EXPECT_EQ(TSIGKey::HMACMD5_NAME(), result.key->getAlgorithmName());
EXPECT_EQ(TSIGKeyRing::NOTFOUND,
- keyring.find(Name("noexist.example")).code);
+ keyring.find(Name("noexist.example"), sha1_name).code);
EXPECT_EQ(static_cast<const TSIGKey*>(NULL),
- keyring.find(Name("noexist.example")).key);
+ keyring.find(Name("noexist.example"), sha256_name).key);
+
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND,
+ keyring.find(Name("another.example"), sha1_name).code);
+ EXPECT_EQ(static_cast<const TSIGKey*>(NULL),
+ keyring.find(Name("another.example"), sha256_name).key);
}
TEST(TSIGStringTest, TSIGKeyFromToString) {
@@ -248,6 +287,8 @@ TEST(TSIGStringTest, TSIGKeyFromToString) {
TSIGKey k2 = TSIGKey("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.");
TSIGKey k3 = TSIGKey("test.example:MSG6Ng==");
TSIGKey k4 = TSIGKey(Name("test.example."), Name("hmac-sha1."), NULL, 0);
+ // "Unknown" key with empty secret is okay
+ TSIGKey k5 = TSIGKey("test.example.::unknown");
EXPECT_EQ("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.",
k1.toText());
@@ -256,6 +297,8 @@ TEST(TSIGStringTest, TSIGKeyFromToString) {
EXPECT_EQ("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.",
k3.toText());
EXPECT_EQ("test.example.::hmac-sha1.", k4.toText());
+ EXPECT_EQ(Name("test.example."), k5.getKeyName());
+ EXPECT_EQ(Name("unknown"), k5.getAlgorithmName());
EXPECT_THROW(TSIGKey(""), isc::InvalidParameter);
EXPECT_THROW(TSIGKey(":"), isc::InvalidParameter);
@@ -266,7 +309,6 @@ TEST(TSIGStringTest, TSIGKeyFromToString) {
EXPECT_THROW(TSIGKey("test.example.:"), isc::InvalidParameter);
EXPECT_THROW(TSIGKey("test.example.:MSG6Ng==:"), isc::InvalidParameter);
EXPECT_THROW(TSIGKey("test.example.:MSG6Ng==:unknown"), isc::InvalidParameter);
-
}
diff --git a/src/lib/dns/tests/tsigrecord_unittest.cc b/src/lib/dns/tests/tsigrecord_unittest.cc
index a932b7f..e1b3e93 100644
--- a/src/lib/dns/tests/tsigrecord_unittest.cc
+++ b/src/lib/dns/tests/tsigrecord_unittest.cc
@@ -98,12 +98,11 @@ TEST_F(TSIGRecordTest, fromParams) {
// Unexpected class
EXPECT_THROW(TSIGRecord(test_name, RRClass::IN(), TSIGRecord::getTTL(),
- test_rdata, 85),
- DNSMessageFORMERR);
+ test_rdata, 85), DNSMessageFORMERR);
- // Unexpected TTL (simply ignored)
- EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
- RRTTL(3600), test_rdata, 85));
+ // Unexpected TTL
+ EXPECT_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+ RRTTL(3600), test_rdata, 85), DNSMessageFORMERR);
}
TEST_F(TSIGRecordTest, recordToWire) {
diff --git a/src/lib/dns/tsig.cc b/src/lib/dns/tsig.cc
index 8e8301d..714b2a5 100644
--- a/src/lib/dns/tsig.cc
+++ b/src/lib/dns/tsig.cc
@@ -16,7 +16,7 @@
#include <stdint.h>
-#include <cassert> // for the tentative verifyTentative()
+#include <cassert>
#include <vector>
#include <boost/shared_ptr.hpp>
@@ -44,6 +44,17 @@ namespace isc {
namespace dns {
namespace {
typedef boost::shared_ptr<HMAC> HMACPtr;
+
+// TSIG uses 48-bit unsigned integer to represent time signed.
+// Since gettimeWrapper() returns a 64-bit *signed* integer, we
+// make sure it's stored in an unsigned 64-bit integer variable and
+// represents a value in the expected range. (In reality, however,
+// gettimeWrapper() will return a positive integer that will fit
+// in 48 bits)
+uint64_t
+getTSIGTime() {
+ return (detail::gettimeWrapper() & 0x0000ffffffffffffULL);
+}
}
struct TSIGContext::TSIGContextImpl {
@@ -51,17 +62,172 @@ struct TSIGContext::TSIGContextImpl {
state_(INIT), key_(key), error_(Rcode::NOERROR()),
previous_timesigned_(0)
{}
+
+ // This helper method is used from verify(). It's expected to be called
+ // just before verify() returns. It updates internal state based on
+ // the verification result and return the TSIGError to be returned to
+ // the caller of verify(), so that verify() can call this method within
+ // its 'return' statement.
+ TSIGError postVerifyUpdate(TSIGError error, const void* digest,
+ uint16_t digest_len)
+ {
+ if (state_ == INIT) {
+ state_ = RECEIVED_REQUEST;
+ } else if (state_ == SENT_REQUEST && error == TSIGError::NOERROR()) {
+ state_ = VERIFIED_RESPONSE;
+ }
+ if (digest != NULL) {
+ previous_digest_.assign(static_cast<const uint8_t*>(digest),
+ static_cast<const uint8_t*>(digest) +
+ digest_len);
+ }
+ error_ = error;
+ return (error);
+ }
+
+ // The following three are helper methods to compute the digest for
+ // TSIG sign/verify in order to unify the common code logic for sign()
+ // and verify() and to keep these callers concise.
+ // These methods take an HMAC object, which will be updated with the
+ // calculated digest.
+ // Note: All methods construct a local OutputBuffer as a work space with a
+ // fixed initial buffer size to avoid intermediate buffer extension.
+ // This should be efficient enough, especially for fundamentally expensive
+ // operation like cryptographic sign/verify, but if the creation of the
+ // buffer in each helper method is still identified to be a severe
+ // performance bottleneck, we could have this class a buffer as a member
+ // variable and reuse it throughout the object's lifetime. Right now,
+ // we prefer keeping the scope for local things as small as possible.
+ void digestPreviousMAC(HMACPtr hmac) const;
+ void digestTSIGVariables(HMACPtr hmac, uint16_t rrclass, uint32_t rrttl,
+ uint64_t time_signed, uint16_t fudge,
+ uint16_t error, uint16_t otherlen,
+ const void* otherdata,
+ bool time_variables_only) const;
+ void digestDNSMessage(HMACPtr hmac, uint16_t qid, const void* data,
+ size_t data_len) const;
State state_;
- TSIGKey key_;
+ const TSIGKey key_;
vector<uint8_t> previous_digest_;
TSIGError error_;
uint64_t previous_timesigned_; // only meaningful for response with BADTIME
};
+void
+TSIGContext::TSIGContextImpl::digestPreviousMAC(HMACPtr hmac) const {
+ // We should have ensured the digest size fits 16 bits within this class
+ // implementation.
+ assert(previous_digest_.size() <= 0xffff);
+
+ OutputBuffer buffer(sizeof(uint16_t) + previous_digest_.size());
+ const uint16_t previous_digest_len(previous_digest_.size());
+ buffer.writeUint16(previous_digest_len);
+ if (previous_digest_len != 0) {
+ buffer.writeData(&previous_digest_[0], previous_digest_len);
+ }
+ hmac->update(buffer.getData(), buffer.getLength());
+}
+
+void
+TSIGContext::TSIGContextImpl::digestTSIGVariables(
+ HMACPtr hmac, uint16_t rrclass, uint32_t rrttl, uint64_t time_signed,
+ uint16_t fudge, uint16_t error, uint16_t otherlen, const void* otherdata,
+ bool time_variables_only) const
+{
+ // It's bit complicated, but we can still predict the necessary size of
+ // the data to be digested. So we precompute it to avoid possible
+ // reallocation inside OutputBuffer (not absolutely necessary, but this
+ // is a bit more efficient)
+ size_t data_size = 8;
+ if (!time_variables_only) {
+ data_size += 10 + key_.getKeyName().getLength() +
+ key_.getAlgorithmName().getLength();
+ }
+ OutputBuffer buffer(data_size);
+
+ if (!time_variables_only) {
+ key_.getKeyName().toWire(buffer);
+ buffer.writeUint16(rrclass);
+ buffer.writeUint32(rrttl);
+ key_.getAlgorithmName().toWire(buffer);
+ }
+ buffer.writeUint16(time_signed >> 32);
+ buffer.writeUint32(time_signed & 0xffffffff);
+ buffer.writeUint16(fudge);
+
+ if (!time_variables_only) {
+ buffer.writeUint16(error);
+ buffer.writeUint16(otherlen);
+ }
+
+ hmac->update(buffer.getData(), buffer.getLength());
+ if (!time_variables_only && otherlen > 0) {
+ hmac->update(otherdata, otherlen);
+ }
+}
+
+// In digestDNSMessage, we exploit some minimum knowledge of DNS message
+// format:
+// - the header section has a fixed length of 12 octets (MESSAGE_HEADER_LEN)
+// - the offset in the header section to the ID field is 0
+// - the offset in the header section to the ARCOUNT field is 10 (and the field
+// length is 2 octets)
+// We could construct a separate Message object from the given data, adjust
+// fields via the Message interfaces and then render it back to a separate
+// buffer, but that would be overkilling. The DNS message header has a
+// fixed length and necessary modifications are quite straightforward, so
+// we do the job using lower level interfaces.
+namespace {
+const size_t MESSAGE_HEADER_LEN = 12;
+}
+void
+TSIGContext::TSIGContextImpl::digestDNSMessage(HMACPtr hmac,
+ uint16_t qid, const void* data,
+ size_t data_len) const
+{
+ OutputBuffer buffer(MESSAGE_HEADER_LEN);
+ const uint8_t* msgptr = static_cast<const uint8_t*>(data);
+
+ // Install the original ID
+ buffer.writeUint16(qid);
+ msgptr += sizeof(uint16_t);
+
+ // Copy the rest of the header except the ARCOUNT field.
+ buffer.writeData(msgptr, 8);
+ msgptr += 8;
+
+ // Install the adjusted ARCOUNT (we don't care even if the value is bogus
+ // and it underflows; it would simply result in verification failure)
+ buffer.writeUint16(InputBuffer(msgptr, sizeof(uint16_t)).readUint16() - 1);
+ msgptr += 2;
+
+ // Digest the header and the rest of the DNS message
+ hmac->update(buffer.getData(), buffer.getLength());
+ hmac->update(msgptr, data_len - MESSAGE_HEADER_LEN);
+}
+
TSIGContext::TSIGContext(const TSIGKey& key) : impl_(new TSIGContextImpl(key))
{
}
+TSIGContext::TSIGContext(const Name& key_name, const Name& algorithm_name,
+ const TSIGKeyRing& keyring) : impl_(NULL)
+{
+ const TSIGKeyRing::FindResult result(keyring.find(key_name,
+ algorithm_name));
+ if (result.code == TSIGKeyRing::NOTFOUND) {
+ // If not key is found, create a dummy key with the specified key
+ // parameters and empty secret. In the common scenario this will
+ // be used in subsequent response with a TSIG indicating a BADKEY
+ // error.
+ impl_ = new TSIGContextImpl(TSIGKey(key_name, algorithm_name,
+ NULL, 0));
+ impl_->error_ = TSIGError::BAD_KEY();
+ } else {
+ impl_ = new TSIGContextImpl(*result.key);
+ }
+}
+
TSIGContext::~TSIGContext() {
delete impl_;
}
@@ -80,21 +246,20 @@ ConstTSIGRecordPtr
TSIGContext::sign(const uint16_t qid, const void* const data,
const size_t data_len)
{
+ if (impl_->state_ == VERIFIED_RESPONSE) {
+ isc_throw(TSIGContextError,
+ "TSIG sign attempt after verifying a response");
+ }
+
if (data == NULL || data_len == 0) {
isc_throw(InvalidParameter, "TSIG sign error: empty data is given");
}
TSIGError error(TSIGError::NOERROR());
- // TSIG uses 48-bit unsigned integer to represent time signed.
- // Since gettimeofdayWrapper() returns a 64-bit *signed* integer, we
- // make sure it's stored in an unsigned 64-bit integer variable and
- // represents a value in the expected range. (In reality, however,
- // gettimeofdayWrapper() will return a positive integer that will fit
- // in 48 bits)
- const uint64_t now = (detail::gettimeWrapper() & 0x0000ffffffffffffULL);
+ const uint64_t now = getTSIGTime();
// For responses adjust the error code.
- if (impl_->state_ == CHECKED) {
+ if (impl_->state_ == RECEIVED_REQUEST) {
error = impl_->error_;
}
@@ -107,11 +272,10 @@ TSIGContext::sign(const uint16_t qid, const void* const data,
now, DEFAULT_FUDGE, 0, NULL,
qid, error.getCode(), 0, NULL)));
impl_->previous_digest_.clear();
- impl_->state_ = SIGNED;
+ impl_->state_ = SENT_RESPONSE;
return (tsig);
}
- OutputBuffer variables(0);
HMACPtr hmac(CryptoLink::getCryptoLink().createHMAC(
impl_->key_.getSecret(),
impl_->key_.getSecretLength(),
@@ -121,87 +285,169 @@ TSIGContext::sign(const uint16_t qid, const void* const data,
// If the context has previous MAC (either the Request MAC or its own
// previous MAC), digest it.
if (impl_->state_ != INIT) {
- const uint16_t previous_digest_len(impl_->previous_digest_.size());
- variables.writeUint16(previous_digest_len);
- if (previous_digest_len != 0) {
- variables.writeData(&impl_->previous_digest_[0],
- previous_digest_len);
- }
- hmac->update(variables.getData(), variables.getLength());
+ impl_->digestPreviousMAC(hmac);
}
// Digest the message (without TSIG)
hmac->update(data, data_len);
- //
- // Digest TSIG variables. If state_ is SIGNED we skip digesting them
- // except for time related variables (RFC2845 4.4).
- //
- variables.clear();
- if (impl_->state_ != SIGNED) {
- impl_->key_.getKeyName().toWire(variables);
- TSIGRecord::getClass().toWire(variables);
- variables.writeUint32(TSIGRecord::TSIG_TTL);
- impl_->key_.getAlgorithmName().toWire(variables);
- }
+ // Digest TSIG variables.
+ // First, prepare some non constant variables.
const uint64_t time_signed = (error == TSIGError::BAD_TIME()) ?
impl_->previous_timesigned_ : now;
- variables.writeUint16(time_signed >> 32);
- variables.writeUint32(time_signed & 0xffffffff);
- variables.writeUint16(DEFAULT_FUDGE);
- hmac->update(variables.getData(), variables.getLength());
- variables.clear();
-
- if (impl_->state_ != SIGNED) {
- variables.writeUint16(error.getCode());
-
- // For BADTIME error, digest 6 bytes of other data.
- // (6 bytes = size of time signed value)
- variables.writeUint16((error == TSIGError::BAD_TIME()) ? 6 : 0);
- hmac->update(variables.getData(), variables.getLength());
-
- variables.clear();
- if (error == TSIGError::BAD_TIME()) {
- variables.writeUint16(now >> 32);
- variables.writeUint32(now & 0xffffffff);
- hmac->update(variables.getData(), variables.getLength());
- }
+ // For BADTIME error, we include 6 bytes of other data.
+ // (6 bytes = size of time signed value)
+ const uint16_t otherlen = (error == TSIGError::BAD_TIME()) ? 6 : 0;
+ OutputBuffer otherdatabuf(otherlen);
+ if (error == TSIGError::BAD_TIME()) {
+ otherdatabuf.writeUint16(now >> 32);
+ otherdatabuf.writeUint32(now & 0xffffffff);
}
- const uint16_t otherlen = variables.getLength();
+ const void* const otherdata =
+ (otherlen == 0) ? NULL : otherdatabuf.getData();
+ // Then calculate the digest. If state_ is SENT_RESPONSE we are sending
+ // a continued message in the same TCP stream so skip digesting
+ // variables except for time related variables (RFC2845 4.4).
+ impl_->digestTSIGVariables(hmac, TSIGRecord::getClass().getCode(),
+ TSIGRecord::TSIG_TTL, time_signed,
+ DEFAULT_FUDGE, error.getCode(),
+ otherlen, otherdata,
+ impl_->state_ == SENT_RESPONSE);
// Get the final digest, update internal state, then finish.
vector<uint8_t> digest = hmac->sign();
+ assert(digest.size() <= 0xffff); // cryptolink API should have ensured it.
ConstTSIGRecordPtr tsig(new TSIGRecord(
impl_->key_.getKeyName(),
any::TSIG(impl_->key_.getAlgorithmName(),
time_signed, DEFAULT_FUDGE,
digest.size(), &digest[0],
qid, error.getCode(), otherlen,
- otherlen == 0 ?
- NULL : variables.getData())));
+ otherdata)));
// Exception free from now on.
impl_->previous_digest_.swap(digest);
- impl_->state_ = SIGNED;
+ impl_->state_ = (impl_->state_ == INIT) ? SENT_REQUEST : SENT_RESPONSE;
return (tsig);
}
-void
-TSIGContext::verifyTentative(ConstTSIGRecordPtr tsig, TSIGError error) {
- const any::TSIG tsig_rdata = tsig->getRdata();
+TSIGError
+TSIGContext::verify(const TSIGRecord* const record, const void* const data,
+ const size_t data_len)
+{
+ if (impl_->state_ == SENT_RESPONSE) {
+ isc_throw(TSIGContextError,
+ "TSIG verify attempt after sending a response");
+ }
- impl_->error_ = error;
- if (error == TSIGError::BAD_TIME()) {
- impl_->previous_timesigned_ = tsig_rdata.getTimeSigned();
+ // This case happens when we sent a signed request and have received an
+ // unsigned response. According to RFC2845 Section 4.6 this case should be
+ // considered a "format error" (although the specific error code
+ // wouldn't matter much for the caller).
+ if (record == NULL) {
+ return (impl_->postVerifyUpdate(TSIGError::FORMERR(), NULL, 0));
+ }
+
+ const any::TSIG& tsig_rdata = record->getRdata();
+
+ // Reject some obviously invalid data
+ if (data_len < MESSAGE_HEADER_LEN + record->getLength()) {
+ isc_throw(InvalidParameter,
+ "TSIG verify: data length is invalid: " << data_len);
+ }
+ if (data == NULL) {
+ isc_throw(InvalidParameter, "TSIG verify: empty data is invalid");
+ }
+
+ // Check key: whether we first verify it with a known key or we verify
+ // it using the consistent key in the context. If the check fails we are
+ // done with BADKEY.
+ if (impl_->state_ == INIT && impl_->error_ == TSIGError::BAD_KEY()) {
+ return (impl_->postVerifyUpdate(TSIGError::BAD_KEY(), NULL, 0));
+ }
+ if (impl_->key_.getKeyName() != record->getName() ||
+ impl_->key_.getAlgorithmName() != tsig_rdata.getAlgorithm()) {
+ return (impl_->postVerifyUpdate(TSIGError::BAD_KEY(), NULL, 0));
+ }
+
+ // Check time: the current time must be in the range of
+ // [time signed - fudge, time signed + fudge]. Otherwise verification
+ // fails with BADTIME. (RFC2845 Section 4.6.2)
+ // Note: for simplicity we don't explicitly catch the case of too small
+ // current time causing underflow. With the fact that fudge is quite
+ // small and (for now) non configurable, it shouldn't be a real concern
+ // in practice.
+ const uint64_t now = getTSIGTime();
+ if (tsig_rdata.getTimeSigned() + DEFAULT_FUDGE < now ||
+ tsig_rdata.getTimeSigned() - DEFAULT_FUDGE > now) {
+ const void* digest = NULL;
+ size_t digest_len = 0;
+ if (impl_->state_ == INIT) {
+ digest = tsig_rdata.getMAC();
+ digest_len = tsig_rdata.getMACSize();
+ impl_->previous_timesigned_ = tsig_rdata.getTimeSigned();
+ }
+ return (impl_->postVerifyUpdate(TSIGError::BAD_TIME(), digest,
+ digest_len));
+ }
+
+ // TODO: signature length check based on RFC4635
+ // (Right now we enforce the standard signature length in libcryptolink)
+
+ // Handling empty MAC. While RFC2845 doesn't explicitly prohibit other
+ // cases, it can only reasonably happen in a response with BADSIG or
+ // BADKEY. We reject other cases as if it were BADSIG to avoid unexpected
+ // acceptance of a bogus signature. This behavior follows the BIND 9
+ // implementation.
+ if (tsig_rdata.getMACSize() == 0) {
+ TSIGError error = TSIGError(tsig_rdata.getError());
+ if (error != TSIGError::BAD_SIG() && error != TSIGError::BAD_KEY()) {
+ error = TSIGError::BAD_SIG();
+ }
+ return (impl_->postVerifyUpdate(error, NULL, 0));
+ }
+
+ HMACPtr hmac(CryptoLink::getCryptoLink().createHMAC(
+ impl_->key_.getSecret(),
+ impl_->key_.getSecretLength(),
+ impl_->key_.getAlgorithm()),
+ deleteHMAC);
+
+ // If the context has previous MAC (either the Request MAC or its own
+ // previous MAC), digest it.
+ if (impl_->state_ != INIT) {
+ impl_->digestPreviousMAC(hmac);
}
- // For simplicity we assume non empty digests.
- assert(tsig_rdata.getMACSize() != 0);
- impl_->previous_digest_.assign(
- static_cast<const uint8_t*>(tsig_rdata.getMAC()),
- static_cast<const uint8_t*>(tsig_rdata.getMAC()) +
- tsig_rdata.getMACSize());
+ //
+ // Digest DNS message (excluding the trailing TSIG RR and adjusting the
+ // QID and ARCOUNT header fields)
+ //
+ impl_->digestDNSMessage(hmac, tsig_rdata.getOriginalID(),
+ data, data_len - record->getLength());
+
+ // Digest TSIG variables. If state_ is VERIFIED_RESPONSE, it's a
+ // continuation of the same TCP stream and skip digesting them except
+ // for time related variables (RFC2845 4.4).
+ // Note: we use the constant values for RR class and TTL specified
+ // in RFC2845, not received values (we reject other values in constructing
+ // the TSIGRecord).
+ impl_->digestTSIGVariables(hmac, TSIGRecord::getClass().getCode(),
+ TSIGRecord::TSIG_TTL,
+ tsig_rdata.getTimeSigned(),
+ tsig_rdata.getFudge(), tsig_rdata.getError(),
+ tsig_rdata.getOtherLen(),
+ tsig_rdata.getOtherData(),
+ impl_->state_ == VERIFIED_RESPONSE);
- impl_->state_ = CHECKED;
+ // Verify the digest with the received signature.
+ if (hmac->verify(tsig_rdata.getMAC(), tsig_rdata.getMACSize())) {
+ return (impl_->postVerifyUpdate(TSIGError::NOERROR(),
+ tsig_rdata.getMAC(),
+ tsig_rdata.getMACSize()));
+ }
+
+ return (impl_->postVerifyUpdate(TSIGError::BAD_SIG(), NULL, 0));
}
+
} // namespace dns
} // namespace isc
diff --git a/src/lib/dns/tsig.h b/src/lib/dns/tsig.h
index fbcb1bb..bceec25 100644
--- a/src/lib/dns/tsig.h
+++ b/src/lib/dns/tsig.h
@@ -17,12 +17,27 @@
#include <boost/noncopyable.hpp>
+#include <exceptions/exceptions.h>
+
#include <dns/tsigerror.h>
#include <dns/tsigkey.h>
#include <dns/tsigrecord.h>
namespace isc {
namespace dns {
+
+/// An exception that is thrown for logic errors identified in TSIG
+/// sign/verify operations.
+///
+/// Note that this exception is not thrown for TSIG protocol errors such as
+/// verification failures. In general, this exception indicates an internal
+/// program bug.
+class TSIGContextError : public isc::Exception {
+public:
+ TSIGContextError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
/// TSIG session context.
///
/// The \c TSIGContext class maintains a context of a signed session of
@@ -59,8 +74,7 @@ namespace dns {
/// in this mode will identify the appropriate TSIG key (or internally record
/// an error if it doesn't find a key). The server will then verify the
/// query with the context, and generate a signed response using the same
-/// same context. (Note: this mode is not yet implemented and may change,
-/// see below).
+/// same context.
///
/// When multiple messages belong to the same TSIG session, either side
/// (signer or verifier) will keep using the same context. It records
@@ -68,8 +82,68 @@ namespace dns {
/// calls to \c sign() or \c verify() work correctly in terms of the TSIG
/// protocol.
///
-/// \note The \c verify() method is not yet implemented. The implementation
-/// and documentation should be updated in the corresponding task.
+/// \b Examples
+///
+/// This is a typical client application that sends a TSIG signed query
+/// and verifies the response.
+///
+/// \code
+/// // "renderer" is of MessageRenderer to render the message.
+/// // (TSIGKey would be configured from config or command line in real app)
+/// TSIGContext ctx(TSIGKey("key.example:MSG6Ng=="));
+/// Message message(Message::RENDER);
+/// message.addQuestion(Question(Name("www.example.com"), RRClass::IN(),
+/// RRType::A()));
+/// message.toWire(renderer, ctx);
+///
+/// // sendto, then recvfrom. received result in (data, data_len)
+///
+/// message.clear(Message::PARSE);
+/// InputBuffer buffer(data, data_len);
+/// message.fromWire(buffer);
+/// TSIGError tsig_error = ctx.verify(message.getTSIGRecord(),
+/// data, data_len);
+/// if (tsig_error == TSIGError::NOERROR()) {
+/// // okay. ctx can be continuously used if it's receiving subsequent
+/// // signed responses from a TCP stream.
+/// } else if (message.getRcode() == Rcode::NOTAUTH()) {
+/// // hard error. give up this transaction per RFC2845 4.6.
+/// } else {
+/// // Other error: discard response keep waiting with the same ctx
+/// // for another (again, RFC2845 4.6).
+/// } \endcode
+///
+/// And this is a typical server application that authenticates a signed
+/// query and returns a response according to the result.
+///
+/// \code
+/// // Assume "message" is of type Message for query handling and
+/// // "renderer" is of MessageRenderer to render responses.
+/// Message message(Message::RENDER);
+///
+/// TSIGKeyRing keyring; // this must be configured with keys somewhere
+///
+/// // Receive a query and store it in (data, data_len)
+/// InputBuffer buffer(data, data_len);
+/// message.clear(Message::PARSE);
+/// message.fromWire(buffer);
+///
+/// const TSIGRecord* tsig = message.getTSIGRecord();
+/// if (tsig != NULL) {
+/// TSIGContext ctx(tsig->getName(), tsig->getRdata().getAlgorithm(),
+/// keyring);
+/// ctx.verify(tsig, data, data_len);
+///
+/// // prepare response
+/// message.makeResponse();
+/// //...
+/// message.toWire(renderer, ctx);
+///
+/// // send the response data back to the client.
+/// // If this is a beginning of a signed session over a TCP and
+/// // server has more data to send to the client, this ctx
+/// // will be used to sign subsequent messages.
+/// } \endcode
///
/// <b>TCP Consideration</b>
///
@@ -110,8 +184,10 @@ public:
/// directly.
enum State {
INIT, ///< Initial state
- SIGNED, ///< Sign completed
- CHECKED ///< Verification completed (may or may not successfully)
+ SENT_REQUEST, ///< Client sent a signed request, waiting response
+ RECEIVED_REQUEST, ///< Server received a signed request
+ SENT_RESPONSE, ///< Server sent a signed response
+ VERIFIED_RESPONSE ///< Client successfully verified a response
};
/// \name Constructors and destructor
@@ -124,6 +200,10 @@ public:
/// \param key The TSIG key to be used for TSIG sessions with this context.
explicit TSIGContext(const TSIGKey& key);
+ /// Constructor from key parameters and key ring.
+ TSIGContext(const Name& key_name, const Name& algorithm_name,
+ const TSIGKeyRing& keyring);
+
/// The destructor.
~TSIGContext();
//@}
@@ -141,6 +221,13 @@ public:
/// complete TSIG RR into the message that has been signed so that it
/// will become a complete TSIG-signed message.
///
+ /// In general, this method is called once by a client to send a
+ /// signed request or one more times by a server to sign
+ /// response(s) to a signed request. To avoid allowing accidental
+ /// misuse, if this method is called after a "client" validates a
+ /// response, an exception of class \c TSIGContextError will be
+ /// thrown.
+ ///
/// \note Normal applications are not expected to call this method
/// directly; they will usually use the \c Message::toWire() method
/// with a \c TSIGContext object being a parameter and have the
@@ -165,6 +252,7 @@ public:
/// returns (without an exception being thrown), the internal state of
/// the \c TSIGContext won't be modified.
///
+ /// \exception TSIGContextError Context already verified a response.
/// \exception InvalidParameter \c data is NULL or \c data_len is 0
/// \exception cryptolink::LibraryError Some unexpected error in the
/// underlying crypto operation
@@ -179,6 +267,92 @@ public:
ConstTSIGRecordPtr sign(const uint16_t qid, const void* const data,
const size_t data_len);
+ /// Verify a DNS message.
+ ///
+ /// This method verifies given data along with the context and a given
+ /// TSIG in the form of a \c TSIGRecord object. The data to be verified
+ /// is generally expected to be a complete, wire-format DNS message,
+ /// exactly as received by the host, and ending with a TSIG RR.
+ /// After verification process this method updates its internal state,
+ /// and returns the result in the form of a \c TSIGError object.
+ /// Possible return values are (see the \c TSIGError class description
+ /// for the mnemonics):
+ ///
+ /// - \c NOERROR: The data has been verified correctly.
+ /// - \c FORMERR: \c TSIGRecord is not given (see below).
+ /// - \c BAD_KEY: Appropriate key is not found or specified key doesn't
+ /// match for the data.
+ /// - \c BAD_TIME: The current time doesn't fall in the range specified
+ /// in the TSIG.
+ /// - \c BAD_SIG: The signature given in the TSIG doesn't match against
+ /// the locally computed digest or is the signature is
+ /// invalid in other way.
+ ///
+ /// If this method is called by a DNS client waiting for a signed
+ /// response and the result is not \c NOERROR, the context can be used
+ /// to try validating another signed message as described in RFC2845
+ /// Section 4.6.
+ ///
+ /// If this method is called by a DNS server that tries to authenticate
+ /// a signed request, and if the result is not \c NOERROR, the
+ /// corresponding error condition is recorded in the context so that
+ /// the server can return a response indicating what was wrong by calling
+ /// \c sign() with the updated context.
+ ///
+ /// In general, this method is called once by a server for
+ /// authenticating a signed request or one more times by a client to
+ /// validate signed response(s) to a signed request. To avoid allowing
+ /// accidental misuse, if this method is called after a "server" signs
+ /// a response, an exception of class \c TSIGContextError will be thrown.
+ ///
+ /// The \c record parameter can be NULL; in that case this method simply
+ /// returns \c FORMERR as the case described in Section 4.6 of RFC2845,
+ /// i.e., receiving an unsigned response to a signed request. This way
+ /// a client can transparently pass the result of
+ /// \c Message::getTSIGRecord() without checking whether it's non NULL
+ /// and take an appropriate action based on the result of this method.
+ ///
+ /// This method handles the given data mostly as opaque. It digests
+ /// the data assuming it begins with a DNS header and ends with a TSIG
+ /// RR whose length is given by calling \c TSIGRecord::getLength() on
+ /// \c record, but otherwise it doesn't parse the data to confirm the
+ /// assumption. It's caller's responsibility to ensure the data is
+ /// valid and consistent with \c record. To avoid disruption, this
+ /// method performs minimal validation on the given \c data and \c record:
+ /// \c data must not be NULL; \c data_len must not be smaller than the
+ /// sum of the DNS header length (fixed, 12 octets) and the length of
+ /// the TSIG RR. If this check fails it throws an \c InvalidParameter
+ /// exception.
+ ///
+ /// One unexpected case that is not covered by this method is that a
+ /// client receives a signed response to an unsigned request. RFC2845 is
+ /// silent about such cases; BIND 9 explicitly identifies the case and
+ /// rejects it. With this implementation, the client can know that the
+ /// response contains a TSIG via the result of
+ /// \c Message::getTSIGRecord() and that it is an unexpected TSIG due to
+ /// the fact that it doesn't have a corresponding \c TSIGContext.
+ /// It's up to the client implementation whether to react to such a case
+ /// explicitly (for example, it could either ignore the TSIG and accept
+ /// the response or drop it).
+ ///
+ /// This method provides the strong exception guarantee; unless the method
+ /// returns (without an exception being thrown), the internal state of
+ /// the \c TSIGContext won't be modified.
+ ///
+ /// \todo Support intermediate TCP DNS messages without TSIG (RFC2845 4.4)
+ /// \todo Signature truncation support based on RFC4635
+ ///
+ /// \exception TSIGContextError Context already signed a response.
+ /// \exception InvalidParameter \c data is NULL or \c data_len is too small.
+ ///
+ /// \param record The \c TSIGRecord to be verified with \c data
+ /// \param data Points to the wire-format data (exactly as received) to
+ /// be verified
+ /// \param data_len The length of \c data in bytes
+ /// \return The \c TSIGError that indicates verification result
+ TSIGError verify(const TSIGRecord* const record, const void* const data,
+ const size_t data_len);
+
/// Return the current state of the context
///
/// \note
@@ -196,18 +370,6 @@ public:
/// \exception None
TSIGError getError() const;
- // This method is tentatively added for testing until a complete
- // verify() method is implemented. Once it's done this should be
- // removed, and corresponding tests should be updated.
- //
- // This tentative "verify" method changes the internal state of
- // the TSIGContext to the CHECKED as if it were verified (though possibly
- // unsuccessfully) with given tsig_rdata. If the error parameter is
- // given and not NOERROR, it's recorded inside the context so that the
- // subsequent sign() will behave accordingly.
- void verifyTentative(ConstTSIGRecordPtr tsig,
- TSIGError error = TSIGError::NOERROR());
-
/// \name Protocol constants and defaults
///
//@{
diff --git a/src/lib/dns/tsigerror.cc b/src/lib/dns/tsigerror.cc
index e63c9ab..36ef47d 100644
--- a/src/lib/dns/tsigerror.cc
+++ b/src/lib/dns/tsigerror.cc
@@ -49,6 +49,17 @@ TSIGError::toText() const {
}
}
+Rcode
+TSIGError::toRcode() const {
+ if (code_ <= MAX_RCODE_FOR_TSIGERROR) {
+ return (Rcode(code_));
+ }
+ if (code_ > BAD_TIME_CODE) {
+ return (Rcode::SERVFAIL());
+ }
+ return (Rcode::NOTAUTH());
+}
+
std::ostream&
operator<<(std::ostream& os, const TSIGError& error) {
return (os << error.toText());
diff --git a/src/lib/dns/tsigerror.h b/src/lib/dns/tsigerror.h
index 4463daf..8efd3ae 100644
--- a/src/lib/dns/tsigerror.h
+++ b/src/lib/dns/tsigerror.h
@@ -22,17 +22,11 @@
namespace isc {
namespace dns {
-
-class RRClass;
-
/// TSIG errors
///
/// The \c TSIGError class objects represent standard errors related to
/// TSIG protocol operations as defined in related specifications, mainly
/// in RFC2845.
-///
-/// (RCODEs) of the header section of DNS messages, and extended response
-/// codes as defined in the EDNS specification.
class TSIGError {
public:
/// Constants for pre-defined TSIG error values.
@@ -58,7 +52,7 @@ public:
///
/// \exception None
///
- /// \param code The underlying 16-bit error code value of the \c TSIGError.
+ /// \param error_code The underlying 16-bit error code value of the \c TSIGError.
explicit TSIGError(uint16_t error_code) : code_(error_code) {}
/// Constructor from \c Rcode.
@@ -125,6 +119,22 @@ public:
/// \return A string representation of the \c TSIGError.
std::string toText() const;
+ /// \brief Convert the \c TSIGError to a \c Rcode
+ ///
+ /// This method returns an \c Rcode object that is corresponding to
+ /// the TSIG error. The returned \c Rcode is expected to be used
+ /// by a verifying server to specify the RCODE of a response when
+ /// TSIG verification fails.
+ ///
+ /// Specifically, this method returns \c Rcode::NOTAUTH() for the
+ /// TSIG specific errors, BADSIG, BADKEY, BADTIME, as described in
+ /// RFC2845. For errors derived from the standard Rcode (code 0-15),
+ /// it returns the corresponding \c Rcode. For others, this method
+ /// returns \c Rcode::SERVFAIL() as a last resort.
+ ///
+ /// \exception None
+ Rcode toRcode() const;
+
/// A constant TSIG error object derived from \c Rcode::NOERROR()
static const TSIGError& NOERROR();
diff --git a/src/lib/dns/tsigkey.cc b/src/lib/dns/tsigkey.cc
index c899423..d7d60eb 100644
--- a/src/lib/dns/tsigkey.cc
+++ b/src/lib/dns/tsigkey.cc
@@ -42,8 +42,17 @@ namespace {
if (name == TSIGKey::HMACSHA256_NAME()) {
return (isc::cryptolink::SHA256);
}
- isc_throw(InvalidParameter,
- "Unknown TSIG algorithm is specified: " << name);
+ if (name == TSIGKey::HMACSHA224_NAME()) {
+ return (isc::cryptolink::SHA224);
+ }
+ if (name == TSIGKey::HMACSHA384_NAME()) {
+ return (isc::cryptolink::SHA384);
+ }
+ if (name == TSIGKey::HMACSHA512_NAME()) {
+ return (isc::cryptolink::SHA512);
+ }
+
+ return (isc::cryptolink::UNKNOWN_HASH);
}
}
@@ -74,7 +83,13 @@ TSIGKey::TSIGKey(const Name& key_name, const Name& algorithm_name,
if ((secret != NULL && secret_len == 0) ||
(secret == NULL && secret_len != 0)) {
isc_throw(InvalidParameter,
- "TSIGKey secret and its length are inconsistent");
+ "TSIGKey secret and its length are inconsistent: " <<
+ key_name << ":" << algorithm_name);
+ }
+ if (algorithm == isc::cryptolink::UNKNOWN_HASH && secret_len != 0) {
+ isc_throw(InvalidParameter,
+ "TSIGKey with unknown algorithm has non empty secret: " <<
+ key_name << ":" << algorithm_name);
}
impl_ = new TSIGKeyImpl(key_name, algorithm_name, algorithm, secret,
secret_len);
@@ -111,8 +126,15 @@ TSIGKey::TSIGKey(const std::string& str) : impl_(NULL) {
vector<uint8_t> secret;
isc::util::encode::decodeBase64(secret_str, secret);
+ if (algorithm == isc::cryptolink::UNKNOWN_HASH && !secret.empty()) {
+ isc_throw(InvalidParameter,
+ "TSIG key with unknown algorithm has non empty secret: "
+ << str);
+ }
+
impl_ = new TSIGKeyImpl(Name(keyname_str), algo_name, algorithm,
- &secret[0], secret.size());
+ secret.empty() ? NULL : &secret[0],
+ secret.size());
} catch (const Exception& e) {
// 'reduce' the several types of exceptions name parsing and
// Base64 decoding can throw to just the InvalidParameter
@@ -195,6 +217,24 @@ Name& TSIGKey::HMACSHA256_NAME() {
return (alg_name);
}
+const
+Name& TSIGKey::HMACSHA224_NAME() {
+ static Name alg_name("hmac-sha224");
+ return (alg_name);
+}
+
+const
+Name& TSIGKey::HMACSHA384_NAME() {
+ static Name alg_name("hmac-sha384");
+ return (alg_name);
+}
+
+const
+Name& TSIGKey::HMACSHA512_NAME() {
+ static Name alg_name("hmac-sha512");
+ return (alg_name);
+}
+
struct TSIGKeyRing::TSIGKeyRingImpl {
typedef map<Name, TSIGKey> TSIGKeyMap;
typedef pair<Name, TSIGKey> NameAndKey;
@@ -230,10 +270,11 @@ TSIGKeyRing::remove(const Name& key_name) {
}
TSIGKeyRing::FindResult
-TSIGKeyRing::find(const Name& key_name) {
+TSIGKeyRing::find(const Name& key_name, const Name& algorithm_name) const {
TSIGKeyRingImpl::TSIGKeyMap::const_iterator found =
impl_->keys.find(key_name);
- if (found == impl_->keys.end()) {
+ if (found == impl_->keys.end() ||
+ (*found).second.getAlgorithmName() != algorithm_name) {
return (FindResult(NOTFOUND, NULL));
}
return (FindResult(SUCCESS, &((*found).second)));
diff --git a/src/lib/dns/tsigkey.h b/src/lib/dns/tsigkey.h
index 02dd423..31211d1 100644
--- a/src/lib/dns/tsigkey.h
+++ b/src/lib/dns/tsigkey.h
@@ -68,12 +68,30 @@ public:
//@{
/// \brief Constructor from key parameters
///
- /// In the current implementation, \c algorithm_name must be a known
- /// algorithm to this implementation, which are defined via the
- /// <code>static const</code> member functions. For other names
- /// an exception of class \c InvalidParameter will be thrown.
- /// Note: This restriction may be too strict, and we may revisit it
- /// later.
+ /// \c algorithm_name should generally be a known algorithm to this
+ /// implementation, which are defined via the
+ /// <code>static const</code> member functions.
+ ///
+ /// Other names are still accepted as long as the secret is empty
+ /// (\c secret is \c NULL and \c secret_len is 0), however; in some cases
+ /// we might want to treat just the pair of key name and algorithm name
+ /// opaquely, e.g., when generating a response TSIG with a BADKEY error
+ /// because the algorithm is unknown as specified in Section 3.2 of
+ /// RFC2845 (in which case the algorithm name would be copied from the
+ /// request to the response, and for that purpose it would be convenient
+ /// if a \c TSIGKey object can hold a name for an "unknown" algorithm).
+ ///
+ /// \note RFC2845 does not specify which algorithm name should be used
+ /// in such a BADKEY response. The behavior of using the same algorithm
+ /// is derived from the BIND 9 implementation.
+ ///
+ /// It is unlikely that a TSIG key with an unknown algorithm is of any
+ /// use with actual crypto operation, so care must be taken when dealing
+ /// with such keys. (The restriction for the secret will prevent
+ /// accidental creation of such a dangerous key, e.g., due to misspelling
+ /// in a configuration file).
+ /// If the given algorithm name is unknown and non empty secret is
+ /// specified, an exception of type \c InvalidParameter will be thrown.
///
/// \c secret and \c secret_len must be consistent in that the latter
/// is 0 if and only if the former is \c NULL;
@@ -98,9 +116,12 @@ public:
/// <name>:<secret>[:<algorithm>]
/// Where <name> is a domain name for the key, <secret> is a
/// base64 representation of the key secret, and the optional
- /// algorithm is an algorithm identifier as specified in RFC4635
+ /// algorithm is an algorithm identifier as specified in RFC4635.
/// The default algorithm is hmac-md5.sig-alg.reg.int.
///
+ /// The same restriction about the algorithm name (and secret) as that
+ /// for the other constructor applies.
+ ///
/// Since ':' is used as a separator here, it is not possible to
/// use this constructor to create keys with a ':' character in
/// their name.
@@ -185,6 +206,9 @@ public:
static const Name& HMACMD5_NAME(); ///< HMAC-MD5 (RFC2845)
static const Name& HMACSHA1_NAME(); ///< HMAC-SHA1 (RFC4635)
static const Name& HMACSHA256_NAME(); ///< HMAC-SHA256 (RFC4635)
+ static const Name& HMACSHA224_NAME(); ///< HMAC-SHA256 (RFC4635)
+ static const Name& HMACSHA384_NAME(); ///< HMAC-SHA256 (RFC4635)
+ static const Name& HMACSHA512_NAME(); ///< HMAC-SHA256 (RFC4635)
//@}
private:
@@ -304,7 +328,9 @@ public:
/// Find a \c TSIGKey for the given name in the \c TSIGKeyRing.
///
/// It searches the internal storage for a \c TSIGKey whose name is
- /// \c key_name, and returns the result in the form of a \c FindResult
+ /// \c key_name and that uses the hash algorithm identified by
+ /// \c algorithm_name.
+ /// It returns the result in the form of a \c FindResult
/// object as follows:
/// - \c code: \c SUCCESS if a key is found; otherwise \c NOTFOUND.
/// - \c key: A pointer to the found \c TSIGKey object if one is found;
@@ -318,8 +344,9 @@ public:
/// This method never throws an exception.
///
/// \param key_name The name of the key to be found.
+ /// \param algorithm_name The name of the algorithm of the found key.
/// \return A \c FindResult object enclosing the search result (see above).
- FindResult find(const Name& key_name);
+ FindResult find(const Name& key_name, const Name& algorithm_name) const;
private:
struct TSIGKeyRingImpl;
TSIGKeyRingImpl* impl_;
diff --git a/src/lib/dns/tsigrecord.cc b/src/lib/dns/tsigrecord.cc
index 40ea6c2..9dd3f78 100644
--- a/src/lib/dns/tsigrecord.cc
+++ b/src/lib/dns/tsigrecord.cc
@@ -70,14 +70,16 @@ castToTSIGRdata(const rdata::Rdata& rdata) {
}
TSIGRecord::TSIGRecord(const Name& name, const RRClass& rrclass,
- const RRTTL&, // we ignore TTL
- const rdata::Rdata& rdata,
+ const RRTTL& ttl, const rdata::Rdata& rdata,
size_t length) :
key_name_(name), rdata_(castToTSIGRdata(rdata)), length_(length)
{
if (rrclass != getClass()) {
isc_throw(DNSMessageFORMERR, "Unexpected TSIG RR class: " << rrclass);
}
+ if (ttl != RRTTL(TSIG_TTL)) {
+ isc_throw(DNSMessageFORMERR, "Unexpected TSIG TTL: " << ttl);
+ }
}
const RRClass&
diff --git a/src/lib/dns/tsigrecord.h b/src/lib/dns/tsigrecord.h
index e42edf1..03de746 100644
--- a/src/lib/dns/tsigrecord.h
+++ b/src/lib/dns/tsigrecord.h
@@ -83,15 +83,39 @@ public:
/// TSIG).
///
/// According to RFC2845, a TSIG RR uses fixed RR class (ANY) and TTL (0).
- /// If the RR class is different from the expected one, this
+ /// If the RR class or TTL is different from the expected one, this
/// implementation considers it an invalid record and throws an exception
- /// of class \c DNSMessageFORMERR. On the other hand, the TTL is simply
- /// ignored (in that sense we could even omit that parameter, but it's
- /// still included if and when we want to change the policy). RFC2845
- /// is silent about what the receiver should do if it sees an unexpected
- /// RR class or TTL in a TSIG RR. This implementation simply follows what
- /// BIND 9 does (it is not clear why BIND 9 employs the "inconsistent"
- /// policy).
+ /// of class \c DNSMessageFORMERR.
+ ///
+ /// \note This behavior is not specified in the protocol specification,
+ /// but this implementation rejects unexpected values for the following
+ /// reasons (but in any case, this won't matter much in practice as
+ /// RFC2848 clearly states these fields always have the fixed values and
+ /// any sane implementation of TSIG signer will follow that):
+ /// According to the RFC editor (in a private communication), the intended
+ /// use of the TSIG TTL field is to signal protocol extensions (currently
+ /// no such extension is defined), so this field may actually be
+ /// validly non 0 in future. However, until the implementation supports
+ /// that extension it may not always be able to handle the extended
+ /// TSIG as intended; the extension may even affect digest computation.
+ /// There's a related issue on this point. Different implementations
+ /// interpret the RFC in different ways on the received TTL when
+ /// digesting the message: BIND 9 uses the received value (even if
+ /// it's not 0) as part of the TSIG variables; NLnet Labs' LDNS and NSD
+ /// always use a fixed constant of 0 regardless of the received TTL value.
+ /// This means if and when an extension with non 0 TTL is introduced
+ /// there will be interoperability problems in the form of verification
+ /// failure. By explicitly rejecting it (and subsequently returning
+ /// a response with a format error) we can indicate the source of the
+ /// problem more clearly than a "bad signature" TSIG error, which can
+ /// happen for various reasons. On the other hand, rejecting unexpected
+ /// RR classes is mostly for consistency; the RFC lists these two fields
+ /// in the same way, so it makes more sense to handle them equally.
+ /// (BIND 9 rejects unexpected RR classes for TSIG, but that is part of
+ /// general check on RR classes on received RRs; it generally requests
+ /// all classes are the same, and if the protocol specifies the use of
+ /// a particular class for a particular type of RR, it requests that
+ /// class be used).
///
/// Likewise, if \c rdata is not of type \c any::TSIG, an exception of
/// class DNSMessageFORMERR will be thrown. When the caller is a
@@ -142,8 +166,7 @@ public:
/// \param name The owner name of the TSIG RR
/// \param rrclass The RR class of the RR. Must be \c RRClass::ANY()
/// (see above)
- /// \param ttl The TTL of the RR. Expected to be a zero TTL, but
- /// actually ignored in this implementation.
+ /// \param ttl The TTL of the RR. Must be 0 (see above)
/// \param rdata The RDATA of the RR. Must be of type \c any::TSIG.
/// \param length The size of the RR (see above)
TSIGRecord(const Name& name, const RRClass& rrclass, const RRTTL& ttl,
diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am
index 537d08f..c27b3e4 100644
--- a/src/lib/log/Makefile.am
+++ b/src/lib/log/Makefile.am
@@ -16,7 +16,7 @@ liblog_la_SOURCES += logger_impl.cc logger_impl.h
liblog_la_SOURCES += logger_support.cc logger_support.h
liblog_la_SOURCES += messagedef.cc messagedef.h
liblog_la_SOURCES += message_dictionary.cc message_dictionary.h
-liblog_la_SOURCES += message_exception.h message_exception.cc
+liblog_la_SOURCES += message_exception.h
liblog_la_SOURCES += message_initializer.cc message_initializer.h
liblog_la_SOURCES += message_reader.cc message_reader.h
liblog_la_SOURCES += message_types.h
diff --git a/src/lib/log/README b/src/lib/log/README
index d01b12f..529eefc 100644
--- a/src/lib/log/README
+++ b/src/lib/log/README
@@ -44,7 +44,7 @@ stored definitions; this updated text is logged. However, to aid support,
the message identifier so in the example above, the message finally logged
would be something like:
- OPENIN, unable to open a.txt for input
+ FAC_OPENIN, unable to open a.txt for input
Using The System
@@ -55,17 +55,15 @@ The steps in using the system are:
mnemonic for the message, typically 6-12 characters long - and a message.
The file is described in more detail below.
- Ideally the file should have a file type of ".msg".
+ Ideally the file should have a file type of ".mes".
-2. Run it through the message compiler to produce the .h and .cc files. It
- is intended that this step be included in the build process. However,
- for now run the compiler (found in the "compiler" subdirectory) manually.
- The only argument is the name of the message file: it will produce as
- output two files, having the same name as the input file but with file
- types of ".h" and ".cc".
-
- The compiler is built in the "compiler" subdirectory of the "src/lib/log"
- directory.
+2. Run it through the message compiler to produce the .h and .cc files. This
+ step should be included in the build process. (For an example of how to
+ do this, see src/lib/nsas/Makefile.am.) During development though, the
+ message compiler (found in the "src/lib/log/compiler" directory) will need
+ to be run manually. The only argument is the name of the message file: it
+ will produce as output two files in the default directory, having the same
+ name as the input file but with file types of ".h" and ".cc".
3. Include the .h file in your source code to define message symbols, and
make sure that the .cc file is compiled and linked into your program -
@@ -96,11 +94,12 @@ An example file could be:
$PREFIX TEST_
$NAMESPACE isc::log
-TEST1 message %1 is much too large
-+ This message is a test for the general message code
-UNKNOWN unknown message
-+ Issued when the message is unknown.
+% TEST1 message %1 is much too large
+This message is a test for the general message code
+
+% UNKNOWN unknown message
+Issued when the message is unknown.
-- END --
@@ -117,23 +116,31 @@ Points to note:
* Lines starting $ are directives. At present, two directives are recognised:
- * $PREFIX, which has one argument: the string used to prefix symbols. If
- absent, there is no prefix to the symbols. (Prefixes are explained below.)
+ * $PREFIX, which has one optional argument: the string used to prefix symbols.
+ If absent, there is no prefix to the symbols. (Prefixes are explained below.)
+
* $NAMESPACE, which has one argument: the namespace in which the symbols are
- created. In the absence of a $NAMESPACE directive, symbols will be put
- in the global namespace.
+ created. In the absence of a $NAMESPACE directive, symbols will be put in
+ the anonymous namespace.
-* Lines starting + indicate an explanation for the preceding message. These
- are intended to be processed by a separate program and used to generate
- an error messages manual. However they are treated like comments by the
- message compiler. As with comments, these must be on a line by themselves;
- if inline, the text (including the leading "+") will be interpreted as
- part of the line.
+* Message lines. These start with a "%" and are followed by the message
+ identification and the message text, the latter including zero or more
+ replacement tokens, e.g.
-* Message lines. These comprise a symbol name and a message, which may
- include zero or more %1, %2... placeholder tokens. Symbol names will be
- upper-cased by the compiler.
+ % TEST message %1 is larger than the permitted length of %2
+ * There may be zero or more spaces between the leading "%" and the message
+ identification (which, in the example above, is the word "TEST").
+
+ * The replacement tokens are the strings "%1", "%2" etc. When a message
+ is logged, these are replaced with the arguments passed to the logging
+ call: %1 refers to the first argument, %2 to the second etc. Within the
+ message text, the placeholders can appear in any order, and placeholders
+ can be repeated.
+
+* Remaining lines indicate an explanation for the preceding message. These
+ are intended to be processed by a separate program and used to generate
+ an error messages manual. They are ignored by the message compiler.
Message Compiler
----------------
@@ -153,9 +160,8 @@ the form:
:
}
-The symbols define the keys in the global message dictionary.
-
-The namespace enclosing the symbols is set by the $NAMESPACE directive.
+The symbols define the keys in the global message dictionary, with the
+namespace enclosing the symbols set by the $NAMESPACE directive.
The "PREFIX_" part of the symbol name is the string defined in the $PREFIX
the argument to the directive. So "$PREFIX MSG_" would prefix the identifier
@@ -163,12 +169,20 @@ ABC with "MSG_" to give the symbol MSG_ABC. Similarly "$PREFIX E" would
prefix it with "E" to give the symbol EABC. If no $PREFIX is given, no
prefix appears (so the symbol in this example would be ABC).
+The prefix is "syntactic sugar". Generally all symbols in a given message file
+will be prefixed with the same set of letters. By extracting these into
+a separate prefix, it becomes easier to disambiguate the different symbols.
+
+There may be multiple $PREFIX directives in a file. A $PREFIX directive applies
+to all message definitions between it an the next $PREFIX directive. A $PREFIX
+directive with no arguments clears the current prefix.
+
2) A C++ source file (called <message-file-name>.cc) that holds the definitions
of the global symbols and code to insert the symbols and messages into the map.
Symbols are defined to be equal to strings holding the identifier, e.g.
- extern const isc::log::MessageID MSG_DUPLNS = "DUPLNS";
+ extern const isc::log::MessageID MSG_DUPLNS = "MSG_DUPLNS";
(The implementation allows symbols to be compared. However, use of strings
should not be assumed - a future implementation may change this.)
@@ -243,12 +257,10 @@ To use the current version of the logging:
highest-level debug messages and 99 for the lowest-level (and typically
more verbose) messages.
- c) Name of an external message file. This is the same as a standard message
- file, although it should not include any directives. (A single directive
- of a particular type will be ignored; multiple directives will cause the
- read of the file to fail with an error.) If a message is replaced, the
- message should include the same printf-format directives in the same order
- as the original message.
+ c) The external message file. If present, this is the same as a standard
+ message file, although it should not include any directives. (A single
+ directive of a particular type will be ignored; multiple directives will
+ cause the read of the file to fail with an error.)
4. Issue logging calls using methods on logger, e.g.
@@ -264,6 +276,30 @@ To use the current version of the logging:
At present, the only logging is to the console.
+Efficiency Considerations
+-------------------------
+A common pattern in logging is a debug call of the form:
+
+ logger.debug(dbglevel, MSGID).arg(expensive_call()).arg(...
+
+... where "expensive_call()" is a function call to obtain logging information
+that may be computationally intensive. Although the cost may be justified
+when debugging is enabled, the cost is still incurred even if debugging is
+disabled and the debug() method returns without outputting anything. (The
+same may be true of other logging levels, although there are likely to be
+fewer calls to logger.info(), logger.error() etc. throughout the code and
+they are less likely to be disabled.)
+
+For this reason, a set of macros is provided and are called using the
+construct:
+
+ LOG_DEBUG(logger, dbglevel, MSGID).arg(expensive_call()).arg(...
+ LOG_INFO(logger, MSGID).arg(expensive_call()...)
+
+If these are used, the arguments passed to the arg() method are not evaluated
+if the relevant logging level is disabled.
+
+
Severity Guidelines
===================
When using logging, the question arises, what severity should a message be
@@ -361,9 +397,6 @@ is only one piece of code that does this functionality.
Outstanding Issues
==================
* Ability to configure system according to configuration database.
-* Update the build procedure to create .cc and .h files from the .msg file
- during the build process. (Requires that the message compiler is built
- first.)
log4cxx Issues
diff --git a/src/lib/log/compiler/message.cc b/src/lib/log/compiler/message.cc
index eb9ddca..457a62e 100644
--- a/src/lib/log/compiler/message.cc
+++ b/src/lib/log/compiler/message.cc
@@ -50,17 +50,13 @@ static const char* VERSION = "1.0-0";
/// \li A .cc file containing code that adds the messages to the program's
/// message dictionary at start-up time.
///
-/// Alternatively, the program can produce a .py file that contains the
-/// message definitions.
-///
-
/// \b Invocation<BR>
/// The program is invoked with the command:
///
/// <tt>message [-v | -h | \<message-file\>]</tt>
///
-/// It reads the message file and writes out two files of the same name but with
-/// extensions of .h and .cc.
+/// It reads the message file and writes out two files of the same name in the
+/// default directory but with extensions of .h and .cc.
///
/// \-v causes it to print the version number and exit. \-h prints a help
/// message (and exits).
@@ -251,14 +247,13 @@ writeClosingNamespace(ostream& output, const vector<string>& ns) {
///
/// \param file Name of the message file. The header file is written to a
/// file of the same name but with a .h suffix.
-/// \param prefix Prefix string to use in symbols
/// \param ns Namespace in which the definitions are to be placed. An empty
/// string indicates no namespace.
/// \param dictionary Dictionary holding the message definitions.
void
-writeHeaderFile(const string& file, const string& prefix,
- const vector<string>& ns_components, MessageDictionary& dictionary)
+writeHeaderFile(const string& file, const vector<string>& ns_components,
+ MessageDictionary& dictionary)
{
Filename message_file(file);
Filename header_file(Filename(message_file.name()).useAsDefault(".h"));
@@ -271,7 +266,7 @@ writeHeaderFile(const string& file, const string& prefix,
try {
if (hfile.fail()) {
- throw MessageException(MSG_OPNMSGOUT, header_file.fullName(),
+ throw MessageException(MSG_OPENOUT, header_file.fullName(),
strerror(errno));
}
@@ -294,7 +289,7 @@ writeHeaderFile(const string& file, const string& prefix,
vector<string> idents = sortedIdentifiers(dictionary);
for (vector<string>::const_iterator j = idents.begin();
j != idents.end(); ++j) {
- hfile << "extern const isc::log::MessageID " << prefix << *j << ";\n";
+ hfile << "extern const isc::log::MessageID " << *j << ";\n";
}
hfile << "\n";
@@ -305,7 +300,7 @@ writeHeaderFile(const string& file, const string& prefix,
// Report errors (if any) and exit
if (hfile.fail()) {
- throw MessageException(MSG_MSGWRTERR, header_file.fullName(),
+ throw MessageException(MSG_WRITERR, header_file.fullName(),
strerror(errno));
}
@@ -354,8 +349,8 @@ replaceNonAlphaNum(char c) {
/// to it. But until BIND-10 is ported to Windows, we won't know.
void
-writeProgramFile(const string& file, const string& prefix,
- const vector<string>& ns_components, MessageDictionary& dictionary)
+writeProgramFile(const string& file, const vector<string>& ns_components,
+ MessageDictionary& dictionary)
{
Filename message_file(file);
Filename program_file(Filename(message_file.name()).useAsDefault(".cc"));
@@ -364,7 +359,7 @@ writeProgramFile(const string& file, const string& prefix,
ofstream ccfile(program_file.fullName().c_str());
try {
if (ccfile.fail()) {
- throw MessageException(MSG_OPNMSGOUT, program_file.fullName(),
+ throw MessageException(MSG_OPENOUT, program_file.fullName(),
strerror(errno));
}
@@ -387,7 +382,7 @@ writeProgramFile(const string& file, const string& prefix,
vector<string> idents = sortedIdentifiers(dictionary);
for (vector<string>::const_iterator j = idents.begin();
j != idents.end(); ++j) {
- ccfile << "extern const isc::log::MessageID " << prefix << *j <<
+ ccfile << "extern const isc::log::MessageID " << *j <<
" = \"" << *j << "\";\n";
}
ccfile << "\n";
@@ -422,7 +417,7 @@ writeProgramFile(const string& file, const string& prefix,
// Report errors (if any) and exit
if (ccfile.fail()) {
- throw MessageException(MSG_MSGWRTERR, program_file.fullName(),
+ throw MessageException(MSG_WRITERR, program_file.fullName(),
strerror(errno));
}
@@ -518,13 +513,10 @@ main(int argc, char* argv[]) {
vector<string> ns_components = splitNamespace(reader.getNamespace());
// Write the header file.
- writeHeaderFile(message_file, reader.getPrefix(), ns_components,
- dictionary);
+ writeHeaderFile(message_file, ns_components, dictionary);
// Write the file that defines the message symbols and text
- writeProgramFile(message_file, reader.getPrefix(), ns_components,
- dictionary);
-
+ writeProgramFile(message_file, ns_components, dictionary);
// Finally, warn of any duplicates encountered.
warnDuplicates(reader);
diff --git a/src/lib/log/log_formatter.h b/src/lib/log/log_formatter.h
index 07a024c..cda1d96 100644
--- a/src/lib/log/log_formatter.h
+++ b/src/lib/log/log_formatter.h
@@ -125,7 +125,11 @@ public:
///
/// \param arg The argument to place into the placeholder.
template<class Arg> Formatter& arg(const Arg& value) {
- return (arg(boost::lexical_cast<std::string>(value)));
+ if (logger_) {
+ return (arg(boost::lexical_cast<std::string>(value)));
+ } else {
+ return (*this);
+ }
}
/// \brief String version of arg.
Formatter& arg(const std::string& arg) {
diff --git a/src/lib/log/logger_support.cc b/src/lib/log/logger_support.cc
index 83147aa..e17c47d 100644
--- a/src/lib/log/logger_support.cc
+++ b/src/lib/log/logger_support.cc
@@ -24,7 +24,9 @@
/// These functions will be replaced once the code has been written to obtain
/// the logging parameters from the configuration database.
+#include <iostream>
#include <algorithm>
+#include <iostream>
#include <string>
#include <vector>
#include <boost/lexical_cast.hpp>
@@ -85,8 +87,12 @@ readLocalMessageFile(const char* file) {
logger.error(ident).arg(args[0]);
break;
- default: // 2 or more (2 should be the maximum)
+ case 2:
logger.error(ident).arg(args[0]).arg(args[1]);
+ break;
+
+ default: // 3 or more (3 should be the maximum)
+ logger.error(ident).arg(args[0]).arg(args[1]).arg(args[2]);
}
}
}
@@ -128,5 +134,76 @@ initLogger(const string& root, isc::log::Severity severity, int dbglevel,
}
}
+/// Logger Run-Time Initialization via Environment Variables
+void initLogger() {
+
+ // Root logger name is defined by the environment variable B10_LOGGER_ROOT.
+ // If not present, the name is "b10root".
+ const char* DEFAULT_ROOT = "b10root";
+ const char* root = getenv("B10_LOGGER_ROOT");
+ if (! root) {
+ root = DEFAULT_ROOT;
+ }
+
+ // Set the logging severity. The environment variable is
+ // B10_LOGGER_SEVERITY, and can be one of "DEBUG", "INFO", "WARN", "ERROR"
+ // of "FATAL". Note that the string must be in upper case with no leading
+ // of trailing blanks.
+ isc::log::Severity severity = isc::log::DEFAULT;
+ const char* sev_char = getenv("B10_LOGGER_SEVERITY");
+ if (sev_char) {
+ string sev_string(sev_char);
+ if (sev_string == "DEBUG") {
+ severity = isc::log::DEBUG;
+ } else if (sev_string == "INFO") {
+ severity = isc::log::INFO;
+ } else if (sev_string == "WARN") {
+ severity = isc::log::WARN;
+ } else if (sev_string == "ERROR") {
+ severity = isc::log::ERROR;
+ } else if (sev_string == "FATAL") {
+ severity = isc::log::FATAL;
+ } else {
+ std::cerr << "**ERROR** unrecognised logger severity of '"
+ << sev_string << "' - default severity will be used\n";
+ }
+ }
+
+ // If the severity is debug, get the debug level (environment variable
+ // B10_LOGGER_DBGLEVEL), which should be in the range 0 to 99.
+ int dbglevel = 0;
+ if (severity == isc::log::DEBUG) {
+ const char* dbg_char = getenv("B10_LOGGER_DBGLEVEL");
+ if (dbg_char) {
+ int level = 0;
+ try {
+ level = boost::lexical_cast<int>(dbg_char);
+ if (level < MIN_DEBUG_LEVEL) {
+ std::cerr << "**ERROR** debug level of " << level
+ << " is invalid - a value of " << MIN_DEBUG_LEVEL
+ << " will be used\n";
+ level = MIN_DEBUG_LEVEL;
+ } else if (level > MAX_DEBUG_LEVEL) {
+ std::cerr << "**ERROR** debug level of " << level
+ << " is invalid - a value of " << MAX_DEBUG_LEVEL
+ << " will be used\n";
+ level = MAX_DEBUG_LEVEL;
+ }
+ } catch (...) {
+ // Error, but not fatal to the test
+ std::cerr << "**ERROR** Unable to translate "
+ "B10_LOGGER_DBGLEVEL - a value of 0 will be used\n";
+ }
+ dbglevel = level;
+ }
+ }
+
+ /// Set the local message file
+ const char* localfile = getenv("B10_LOGGER_LOCALMSG");
+
+ // Initialize logging
+ initLogger(root, severity, dbglevel, localfile);
+}
+
} // namespace log
} // namespace isc
diff --git a/src/lib/log/logger_support.h b/src/lib/log/logger_support.h
index 6b5fdec..f4861b2 100644
--- a/src/lib/log/logger_support.h
+++ b/src/lib/log/logger_support.h
@@ -39,6 +39,37 @@ namespace log {
void initLogger(const std::string& root, isc::log::Severity severity,
int dbglevel, const char* file);
+
+/// \brief Run-Time Initialization from Environment
+///
+/// Performs run-time initialization of the logger via the setting of
+/// environment variables. These are:
+///
+/// B10_LOGGER_ROOT
+/// Name of the root logger. If not given, the string "b10root" will be used.
+///
+/// B10_LOGGER_SEVERITY
+/// Severity of messages that will be logged. This must be one of the strings
+/// "DEBUG", "INFO", "WARN", "ERROR", "FATAL". (Must be upper case and must
+/// not contain leading or trailing spaces.) If not specified (or if
+/// specified but incorrect), the default for the logging system will be used
+/// (currently INFO).
+///
+/// B10_LOGGER_DBGLEVEL
+/// Ignored if the level is not DEBUG, this should be a number between 0 and
+/// 99 indicating the logging severity. The default is 0. If outside these
+/// limits or if not a number, a value of 0 is used.
+///
+/// B10_LOGGER_LOCALMSG
+/// If defined, the path specification of a file that contains message
+/// definitions replacing ones in the default dictionary.
+///
+/// Any errors in the settings cause messages to be output to stderr.
+///
+/// This function is most likely to be called from unit test programs.
+
+void initLogger();
+
} // namespace log
} // namespace isc
diff --git a/src/lib/log/message_exception.cc b/src/lib/log/message_exception.cc
deleted file mode 100644
index 1a69ca5..0000000
--- a/src/lib/log/message_exception.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-/// \brief Body of Virtual Destructor
-
-#include <log/message_exception.h>
-
-namespace isc {
-namespace log {
-
-MessageException::~MessageException() throw() {
-}
-
-} // namespace log
-} // namespace isc
diff --git a/src/lib/log/message_exception.h b/src/lib/log/message_exception.h
index 30c6618..eebee89 100644
--- a/src/lib/log/message_exception.h
+++ b/src/lib/log/message_exception.h
@@ -19,6 +19,7 @@
#include <string>
#include <vector>
+#include <boost/lexical_cast.hpp>
#include <log/message_types.h>
namespace isc {
@@ -35,33 +36,47 @@ public:
/// \brief Constructor
///
- /// \param id Message identification
- MessageException(MessageID id) : id_(id)
- {}
+ /// \param id Message identification.
+ /// \param lineno Line number on which error occurred (if > 0).
+ MessageException(MessageID id, int lineno = 0) : id_(id)
+ {
+ if (lineno > 0) {
+ args_.push_back(boost::lexical_cast<std::string>(lineno));
+ }
+ }
/// \brief Constructor
///
- /// \param id Message identification
- /// \param arg1 First message argument
- MessageException(MessageID id, const std::string& arg1) : id_(id)
+ /// \param id Message identification.
+ /// \param arg1 First message argument.
+ /// \param lineno Line number on which error occurred (if > 0).
+ MessageException(MessageID id, const std::string& arg1, int lineno = 0)
+ : id_(id)
{
+ if (lineno > 0) {
+ args_.push_back(boost::lexical_cast<std::string>(lineno));
+ }
args_.push_back(arg1);
}
/// \brief Constructor
///
- /// \param id Message identification
- /// \param arg1 First message argument
- /// \param arg2 Second message argument
+ /// \param id Message identification.
+ /// \param arg1 First message argument.
+ /// \param arg2 Second message argument.
+ /// \param lineno Line number on which error occurred (if > 0).
MessageException(MessageID id, const std::string& arg1,
- const std::string& arg2) : id_(id)
+ const std::string& arg2, int lineno = 0) : id_(id)
{
+ if (lineno > 0) {
+ args_.push_back(boost::lexical_cast<std::string>(lineno));
+ }
args_.push_back(arg1);
args_.push_back(arg2);
}
/// \brief Destructor
- virtual ~MessageException() throw();
+ ~MessageException() throw() {}
/// \brief Return Message ID
///
diff --git a/src/lib/log/message_reader.cc b/src/lib/log/message_reader.cc
index 7281346..1a0b242 100644
--- a/src/lib/log/message_reader.cc
+++ b/src/lib/log/message_reader.cc
@@ -12,8 +12,10 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <cassert>
#include <errno.h>
#include <string.h>
+#include <iostream>
#include <iostream>
#include <fstream>
@@ -25,45 +27,50 @@
using namespace std;
-namespace isc {
-namespace log {
-
-// Virtual destructor.
-MessageReader::~MessageReader() {
+namespace {
+const char DIRECTIVE_FLAG = '$'; // Starts each directive
+const char MESSAGE_FLAG = '%'; // Starts each message
}
+namespace isc {
+namespace log {
+
// Read the file.
void
MessageReader::readFile(const string& file, MessageReader::Mode mode) {
- // Ensure the non-added collection is empty: this object might be
- // being reused.
+ // Ensure the non-added collection is empty: we could be re-using this
+ // object.
not_added_.clear();
- // Open the file
+ // Open the file.
ifstream infile(file.c_str());
if (infile.fail()) {
- throw MessageException(MSG_OPNMSGIN, file, strerror(errno));
+ throw MessageException(MSG_OPENIN, file, strerror(errno));
}
- // Loop round reading it.
+ // Loop round reading it. As we process the file one line at a time,
+ // keep a track of line number of aid diagnosis of problems.
string line;
getline(infile, line);
+ lineno_ = 0;
+
while (infile.good()) {
+ ++lineno_;
processLine(line, mode);
getline(infile, line);
}
// Why did the loop terminate?
if (!infile.eof()) {
- throw MessageException(MSG_MSGRDERR, file, strerror(errno));
+ throw MessageException(MSG_READERR, file, strerror(errno));
}
infile.close();
}
-// Parse a line of the file
+// Parse a line of the file.
void
MessageReader::processLine(const string& line, MessageReader::Mode mode) {
@@ -74,15 +81,16 @@ MessageReader::processLine(const string& line, MessageReader::Mode mode) {
if (text.empty()) {
; // Ignore blank lines
- } else if ((text[0] == '#') || (text[0] == '+')) {
- ; // Ignore comments or descriptions
-
- } else if (text[0] == '$') {
+ } else if (text[0] == DIRECTIVE_FLAG) {
parseDirective(text); // Process directives
- } else {
- parseMessage(text, mode); // Process other lines
+ } else if (text[0] == MESSAGE_FLAG) {
+ parseMessage(text, mode); // Process message definition line
+
+ } else {
+ ; // Other lines are extended message
+ // description so are ignored
}
}
@@ -99,130 +107,162 @@ MessageReader::parseDirective(const std::string& text) {
isc::util::str::uppercase(tokens[0]);
if (tokens[0] == string("$PREFIX")) {
parsePrefix(tokens);
+
} else if (tokens[0] == string("$NAMESPACE")) {
parseNamespace(tokens);
+
} else {
- throw MessageException(MSG_UNRECDIR, tokens[0]);
+
+ // Unrecognised directive
+ throw MessageException(MSG_UNRECDIR, tokens[0], lineno_);
}
}
// Process $PREFIX
-
void
MessageReader::parsePrefix(const vector<string>& tokens) {
- // Check argument count
-
- static string valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
- if (tokens.size() < 2) {
- throw MessageException(MSG_PRFNOARG);
- } else if (tokens.size() > 2) {
- throw MessageException(MSG_PRFEXTRARG);
-
- }
+ // Should not get here unless there is something in the tokens array.
+ assert(tokens.size() > 0);
- // As a style, we are going to have the symbols in uppercase
- string prefix = tokens[1];
- isc::util::str::uppercase(prefix);
+ // Process $PREFIX. With no arguments, the prefix is set to the empty
+ // string. One argument sets the prefix to the to its value and more than
+ // one argument is invalid.
+ if (tokens.size() == 1) {
+ prefix_ = "";
- // Token is potentially valid providing it only contains alphabetic
- // and numeric characters (and underscores) and does not start with a
- // digit.
- if ((prefix.find_first_not_of(valid) != string::npos) ||
- (std::isdigit(prefix[0]))) {
+ } else if (tokens.size() == 2) {
+ prefix_ = tokens[1];
- // Invalid character in string or it starts with a digit.
- throw MessageException(MSG_PRFINVARG, tokens[1]);
- }
+ // Token is potentially valid providing it only contains alphabetic
+ // and numeric characters (and underscores) and does not start with a
+ // digit.
+ if (invalidSymbol(prefix_)) {
+ throw MessageException(MSG_PRFINVARG, prefix_, lineno_);
+ }
- // All OK - unless the prefix has already been set.
+ } else {
- if (prefix_.size() != 0) {
- throw MessageException(MSG_DUPLPRFX);
+ // Too many arguments
+ throw MessageException(MSG_PRFEXTRARG, lineno_);
}
+}
- // Prefix has not been set, so set it and return success.
-
- prefix_ = prefix;
+// Check if string is an invalid C++ symbol. It is valid if comprises only
+// alphanumeric characters and underscores, and does not start with a digit.
+// (Owing to the logic of the rest of the code, we check for its invalidity,
+// not its validity.)
+bool
+MessageReader::invalidSymbol(const string& symbol) {
+ static const string valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789_";
+ return ( symbol.empty() ||
+ (symbol.find_first_not_of(valid_chars) != string::npos) ||
+ (std::isdigit(symbol[0])));
}
// Process $NAMESPACE. A lot of the processing is similar to that of $PREFIX,
// except that only limited checks will be done on the namespace (to avoid a
-// lot of parsing and separating out of the namespace components.)
+// lot of parsing and separating out of the namespace components.) Also, unlike
+// $PREFIX, there can only be one $NAMESPACE in a file.
void
MessageReader::parseNamespace(const vector<string>& tokens) {
// Check argument count
-
- static string valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_:"
- "abcdefghijklmnopqrstuvwxyz";
-
if (tokens.size() < 2) {
- throw MessageException(MSG_NSNOARG);
+ throw MessageException(MSG_NSNOARG, lineno_);
} else if (tokens.size() > 2) {
- throw MessageException(MSG_NSEXTRARG);
+ throw MessageException(MSG_NSEXTRARG, lineno_);
}
// Token is potentially valid providing it only contains alphabetic
- // and numeric characters (and underscores and colons).
- if (tokens[1].find_first_not_of(valid) != string::npos) {
-
- // Invalid character in string or it starts with a digit.
- throw MessageException(MSG_NSINVARG, tokens[1]);
+ // and numeric characters (and underscores and colons). As noted above,
+ // we won't be exhaustive - after all, and code containing the resultant
+ // namespace will have to be compiled, and the compiler will catch errors.
+ static const string valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789_:";
+ if (tokens[1].find_first_not_of(valid_chars) != string::npos) {
+ throw MessageException(MSG_NSINVARG, tokens[1], lineno_);
}
// All OK - unless the namespace has already been set.
if (ns_.size() != 0) {
- throw MessageException(MSG_DUPLNS);
+ throw MessageException(MSG_DUPLNS, lineno_);
}
// Prefix has not been set, so set it and return success.
-
ns_ = tokens[1];
}
// Process message. By the time this method is called, the line has been
-// stripped of leading and trailing spaces, and we believe that it is a line
-// defining a message. The first token on the line is converted to uppercase
-// and becomes the message ID; the rest of the line is the message text.
+// stripped of leading and trailing spaces. The first character of the string
+// is the message introducer, so we can get rid of that. The remainder is
+// a line defining a message.
+//
+// The first token on the line, when concatenated to the prefix and converted to
+// upper-case, is the message ID. The first of the line from the next token
+// on is the message text.
void
MessageReader::parseMessage(const std::string& text, MessageReader::Mode mode) {
static string delimiters("\t\n "); // Delimiters
+ // The line passed should be at least one character long and start with the
+ // message introducer (else we should not have got here).
+ assert((text.size() >= 1) && (text[0] == MESSAGE_FLAG));
+
+ // A line comprising just the message introducer is not valid.
+ if (text.size() == 1) {
+ throw MessageException(MSG_NOMSGID, text, lineno_);
+ }
+
+ // Strip off the introducer and any leading space after that.
+ string message_line = isc::util::str::trim(text.substr(1));
+
// Look for the first delimiter.
- size_t first_delim = text.find_first_of(delimiters);
+ size_t first_delim = message_line.find_first_of(delimiters);
if (first_delim == string::npos) {
// Just a single token in the line - this is not valid
- throw MessageException(MSG_NOMSGTXT, text);
+ throw MessageException(MSG_NOMSGTXT, message_line, lineno_);
}
- // Extract the first token into the message ID
- string ident = text.substr(0, first_delim);
+ // Extract the first token into the message ID, preceding it with the
+ // current prefix, then convert to upper-case. If the prefix is not set,
+ // perform the valid character check now - the string will become a C++
+ // symbol so we may as well identify problems early.
+ string ident = prefix_ + message_line.substr(0, first_delim);
+ if (prefix_.empty()) {
+ if (invalidSymbol(ident)) {
+ throw MessageException(MSG_INVMSGID, ident, lineno_);
+ }
+ }
+ isc::util::str::uppercase(ident);
// Locate the start of the message text
- size_t first_text = text.find_first_not_of(delimiters, first_delim);
+ size_t first_text = message_line.find_first_not_of(delimiters, first_delim);
if (first_text == string::npos) {
// ?? This happens if there are trailing delimiters, which should not
// occur as we have stripped trailing spaces off the line. Just treat
// this as a single-token error for simplicity's sake.
- throw MessageException(MSG_NOMSGTXT, text);
+ throw MessageException(MSG_NOMSGTXT, message_line, lineno_);
}
// Add the result to the dictionary and to the non-added list if the add to
// the dictionary fails.
bool added;
if (mode == ADD) {
- added = dictionary_->add(ident, text.substr(first_text));
+ added = dictionary_->add(ident, message_line.substr(first_text));
}
else {
- added = dictionary_->replace(ident, text.substr(first_text));
+ added = dictionary_->replace(ident, message_line.substr(first_text));
}
if (!added) {
not_added_.push_back(ident);
diff --git a/src/lib/log/message_reader.h b/src/lib/log/message_reader.h
index d07c7f2..eded9c6 100644
--- a/src/lib/log/message_reader.h
+++ b/src/lib/log/message_reader.h
@@ -64,10 +64,9 @@ public:
dictionary_(dictionary)
{}
-
/// \brief Virtual Destructor
- virtual ~MessageReader();
-
+ virtual ~MessageReader()
+ {}
/// \brief Get Dictionary
///
@@ -188,10 +187,24 @@ private:
/// \param tokens $NAMESPACE line split into tokens
void parseNamespace(const std::vector<std::string>& tokens);
+ /// \brief Check for invalid C++ symbol name
+ ///
+ /// The message ID (or concatenation of prefix and message ID) will be used
+ /// as the name of a symbol in C++ code. This function checks if the name
+ /// is invalid (contains anything other than alphanumeric characters or
+ /// underscores, or starts with a digit).
+ ///
+ /// \param symbol name to check to see if it is an invalid C++ symbol.
+ ///
+ /// \return true if the name is invalid, false if it is valid.
+ bool invalidSymbol(const std::string& symbol);
+
+
/// Attributes
MessageDictionary* dictionary_; ///< Dictionary to add messages to
MessageIDCollection not_added_; ///< List of IDs not added
+ int lineno_; ///< Number of last line read
std::string prefix_; ///< Argument of $PREFIX statement
std::string ns_; ///< Argument of $NAMESPACE statement
};
diff --git a/src/lib/log/messagedef.cc b/src/lib/log/messagedef.cc
index 46086d0..5cc89b3 100644
--- a/src/lib/log/messagedef.cc
+++ b/src/lib/log/messagedef.cc
@@ -1,4 +1,4 @@
-// File created from messagedef.mes on Thu May 5 16:57:11 2011
+// File created from messagedef.mes on Mon May 9 13:52:54 2011
#include <cstddef>
#include <log/message_types.h>
@@ -7,23 +7,23 @@
namespace isc {
namespace log {
-extern const isc::log::MessageID MSG_DUPLNS = "DUPLNS";
-extern const isc::log::MessageID MSG_DUPLPRFX = "DUPLPRFX";
-extern const isc::log::MessageID MSG_DUPMSGID = "DUPMSGID";
-extern const isc::log::MessageID MSG_IDNOTFND = "IDNOTFND";
-extern const isc::log::MessageID MSG_MSGRDERR = "MSGRDERR";
-extern const isc::log::MessageID MSG_MSGWRTERR = "MSGWRTERR";
-extern const isc::log::MessageID MSG_NOMSGTXT = "NOMSGTXT";
-extern const isc::log::MessageID MSG_NSEXTRARG = "NSEXTRARG";
-extern const isc::log::MessageID MSG_NSINVARG = "NSINVARG";
-extern const isc::log::MessageID MSG_NSNOARG = "NSNOARG";
-extern const isc::log::MessageID MSG_OPNMSGIN = "OPNMSGIN";
-extern const isc::log::MessageID MSG_OPNMSGOUT = "OPNMSGOUT";
-extern const isc::log::MessageID MSG_PRFEXTRARG = "PRFEXTRARG";
-extern const isc::log::MessageID MSG_PRFINVARG = "PRFINVARG";
-extern const isc::log::MessageID MSG_PRFNOARG = "PRFNOARG";
-extern const isc::log::MessageID MSG_RDLOCMES = "RDLOCMES";
-extern const isc::log::MessageID MSG_UNRECDIR = "UNRECDIR";
+extern const isc::log::MessageID MSG_DUPLNS = "MSG_DUPLNS";
+extern const isc::log::MessageID MSG_DUPMSGID = "MSG_DUPMSGID";
+extern const isc::log::MessageID MSG_IDNOTFND = "MSG_IDNOTFND";
+extern const isc::log::MessageID MSG_INVMSGID = "MSG_INVMSGID";
+extern const isc::log::MessageID MSG_NOMSGID = "MSG_NOMSGID";
+extern const isc::log::MessageID MSG_NOMSGTXT = "MSG_NOMSGTXT";
+extern const isc::log::MessageID MSG_NSEXTRARG = "MSG_NSEXTRARG";
+extern const isc::log::MessageID MSG_NSINVARG = "MSG_NSINVARG";
+extern const isc::log::MessageID MSG_NSNOARG = "MSG_NSNOARG";
+extern const isc::log::MessageID MSG_OPENIN = "MSG_OPENIN";
+extern const isc::log::MessageID MSG_OPENOUT = "MSG_OPENOUT";
+extern const isc::log::MessageID MSG_PRFEXTRARG = "MSG_PRFEXTRARG";
+extern const isc::log::MessageID MSG_PRFINVARG = "MSG_PRFINVARG";
+extern const isc::log::MessageID MSG_RDLOCMES = "MSG_RDLOCMES";
+extern const isc::log::MessageID MSG_READERR = "MSG_READERR";
+extern const isc::log::MessageID MSG_UNRECDIR = "MSG_UNRECDIR";
+extern const isc::log::MessageID MSG_WRITERR = "MSG_WRITERR";
} // namespace log
} // namespace isc
@@ -31,23 +31,23 @@ extern const isc::log::MessageID MSG_UNRECDIR = "UNRECDIR";
namespace {
const char* values[] = {
- "DUPLNS", "duplicate $NAMESPACE directive found",
- "DUPLPRFX", "duplicate $PREFIX directive found",
- "DUPMSGID", "duplicate message ID (%1) in compiled code",
- "IDNOTFND", "could not replace message for '%1': no such message identification",
- "MSGRDERR", "error reading from message file %1: %2",
- "MSGWRTERR", "error writing to %1: %2",
- "NOMSGTXT", "a line containing a message ID ('%1') and nothing else was found",
- "NSEXTRARG", "$NAMESPACE directive has too many arguments",
- "NSINVARG", "$NAMESPACE directive has an invalid argument ('%1')",
- "NSNOARG", "no arguments were given to the $NAMESPACE directive",
- "OPNMSGIN", "unable to open message file %1 for input: %2",
- "OPNMSGOUT", "unable to open %1 for output: %2",
- "PRFEXTRARG", "$PREFIX directive has too many arguments",
- "PRFINVARG", "$PREFIX directive has an invalid argument ('%1')",
- "PRFNOARG", "no arguments were given to the $PREFIX directive",
- "RDLOCMES", "reading local message file %1",
- "UNRECDIR", "unrecognised directive '%1'",
+ "MSG_DUPLNS", "line %1: duplicate $NAMESPACE directive found",
+ "MSG_DUPMSGID", "duplicate message ID (%1) in compiled code",
+ "MSG_IDNOTFND", "could not replace message text for '%1': no such message",
+ "MSG_INVMSGID", "line %1: invalid message identification '%2'",
+ "MSG_NOMSGID", "line %1: message definition line found without a message ID",
+ "MSG_NOMSGTXT", "line %1: line found containing a message ID ('%2') and no text",
+ "MSG_NSEXTRARG", "line %1: $NAMESPACE directive has too many arguments",
+ "MSG_NSINVARG", "line %1: $NAMESPACE directive has an invalid argument ('%2')",
+ "MSG_NSNOARG", "line %1: no arguments were given to the $NAMESPACE directive",
+ "MSG_OPENIN", "unable to open message file %1 for input: %2",
+ "MSG_OPENOUT", "unable to open %1 for output: %2",
+ "MSG_PRFEXTRARG", "line %1: $PREFIX directive has too many arguments",
+ "MSG_PRFINVARG", "line %1: $PREFIX directive has an invalid argument ('%2')",
+ "MSG_RDLOCMES", "reading local message file %1",
+ "MSG_READERR", "error reading from message file %1: %2",
+ "MSG_UNRECDIR", "line %1: unrecognised directive '%2'",
+ "MSG_WRITERR", "error writing to %1: %2",
NULL
};
diff --git a/src/lib/log/messagedef.h b/src/lib/log/messagedef.h
index 47867e6..79c8bab 100644
--- a/src/lib/log/messagedef.h
+++ b/src/lib/log/messagedef.h
@@ -1,4 +1,4 @@
-// File created from messagedef.mes on Thu May 5 16:57:11 2011
+// File created from messagedef.mes on Mon May 9 13:52:54 2011
#ifndef __MESSAGEDEF_H
#define __MESSAGEDEF_H
@@ -9,22 +9,22 @@ namespace isc {
namespace log {
extern const isc::log::MessageID MSG_DUPLNS;
-extern const isc::log::MessageID MSG_DUPLPRFX;
extern const isc::log::MessageID MSG_DUPMSGID;
extern const isc::log::MessageID MSG_IDNOTFND;
-extern const isc::log::MessageID MSG_MSGRDERR;
-extern const isc::log::MessageID MSG_MSGWRTERR;
+extern const isc::log::MessageID MSG_INVMSGID;
+extern const isc::log::MessageID MSG_NOMSGID;
extern const isc::log::MessageID MSG_NOMSGTXT;
extern const isc::log::MessageID MSG_NSEXTRARG;
extern const isc::log::MessageID MSG_NSINVARG;
extern const isc::log::MessageID MSG_NSNOARG;
-extern const isc::log::MessageID MSG_OPNMSGIN;
-extern const isc::log::MessageID MSG_OPNMSGOUT;
+extern const isc::log::MessageID MSG_OPENIN;
+extern const isc::log::MessageID MSG_OPENOUT;
extern const isc::log::MessageID MSG_PRFEXTRARG;
extern const isc::log::MessageID MSG_PRFINVARG;
-extern const isc::log::MessageID MSG_PRFNOARG;
extern const isc::log::MessageID MSG_RDLOCMES;
+extern const isc::log::MessageID MSG_READERR;
extern const isc::log::MessageID MSG_UNRECDIR;
+extern const isc::log::MessageID MSG_WRITERR;
} // namespace log
} // namespace isc
diff --git a/src/lib/log/messagedef.mes b/src/lib/log/messagedef.mes
index 077830c..51c04fa 100644
--- a/src/lib/log/messagedef.mes
+++ b/src/lib/log/messagedef.mes
@@ -12,108 +12,108 @@
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
-$PREFIX MSG_
-$NAMESPACE isc::log
-
# \brief Message Utility Message File
#
-# This is the source of the set of messages generated by the message and logging
-# components. The associated .h and .cc files are created by hand from this
-# file though and are not built during the build process; this is to avoid the
-# chicken-and-egg situation where we need the files to build the message
+# This is the source of the set of messages generated by the message and
+# logging components. The associated .h and .cc files are created by hand from
+# this file though and are not built during the build process; this is to avoid
+# the chicken-and-egg situation where we need the files to build the message
# compiler, yet we need the compiler to build the files.
-DUPMSGID duplicate message ID (%1) in compiled code
-+ Indicative of a programming error, when it started up, BIND10 detected that
-+ the given message ID had been registered by one or more modules. (All message
-+ IDs should be unique throughout BIND10.) This has no impact on the operation
-+ of the server other that erroneous messages may be logged. (When BIND10 loads
-+ the message IDs (and their associated text), if a duplicate ID is found it is
-+ discarded. However, when the module that supplied the duplicate ID logs that
-+ particular message, the text supplied by the module that added the original
-+ ID will be output - something that may bear no relation to the condition being
-+ logged.
-
-DUPLNS duplicate $NAMESPACE directive found
-+ When reading a message file, more than one $NAMESPACE directive was found. In
-+ this version of the code, such a condition is regarded as an error and the
-+ read will be abandoned.
-
-DUPLPRFX duplicate $PREFIX directive found
-+ When reading a message file, more than one $PREFIX directive was found. In
-+ this version of the code, such a condition is regarded as an error and the
-+ read will be abandoned.
-
-IDNOTFND could not replace message for '%1': no such message identification
-+ During start-up a local message file was read. A line with the listed
-+ message identification was found in the file, but the identification is not
-+ one contained in the compiled-in message dictionary. Either the message
-+ identification has been mis-spelled in the file, or the local file was used
-+ for an earlier version of the software and the message with that
-+ identification has been removed.
-+
-+ This message may appear a number of times in the file, once for every such
-+ unknown message identification.
-
-MSGRDERR error reading from message file %1: %2
-+ The specified error was encountered reading from the named message file.
-
-MSGWRTERR error writing to %1: %2
-+ The specified error was encountered by the message compiler when writing to
-+ the named output file.
-
-NSEXTRARG $NAMESPACE directive has too many arguments
-+ The $NAMESPACE directive takes a single argument, a namespace in which all the
-+ generated symbol names are placed. This error is generated when the
-+ compiler finds a $NAMESPACE directive with more than one argument.
-
-NSINVARG $NAMESPACE directive has an invalid argument ('%1')
-+ The $NAMESPACE argument should be a valid C++ namespace. The reader does a
-+ cursory check on its validity, checking that the characters in the namespace
-+ are correct. The error is generated when the reader finds an invalid
-+ character. (Valid are alphanumeric characters, underscores and colons.)
-
-NOMSGTXT a line containing a message ID ('%1') and nothing else was found
-+ Message definitions comprise lines starting with a message identification (a
-+ symbolic name for the message) and followed by the text of the message. This
-+ error is generated when a line is found in the message file that contains just
-+ the message identification and no text.
-
-NSNOARG no arguments were given to the $NAMESPACE directive
-+ The $NAMESPACE directive takes a single argument, a namespace in which all the
-+ generated symbol names are placed. This error is generated when the
-+ compiler finds a $NAMESPACE directive with no arguments.
-
-OPNMSGIN unable to open message file %1 for input: %2
-+ The program was not able to open the specified input message file for the
-+ reason given.
-
-OPNMSGOUT unable to open %1 for output: %2
-+ The program was not able to open the specified output file for the reason
-+ given.
-
-PRFEXTRARG $PREFIX directive has too many arguments
-+ The $PREFIX directive takes a single argument, a prefix to be added to the
-+ symbol names when a C++ .h file is created. This error is generated when the
-+ compiler finds a $PREFIX directive with more than one argument.
-
-PRFINVARG $PREFIX directive has an invalid argument ('%1')
-+ The $PREFIX argument is used in a symbol name in a C++ header file. As such,
-+ it must adhere to restrictions on C++ symbol names (e.g. may only contain
-+ alphanumeric characters or underscores, and may nor start with a digit). A
-+ $PREFIX directive was found with an argument (given in the message) that
-+ violates those restictions.
-
-PRFNOARG no arguments were given to the $PREFIX directive
-+ The $PREFIX directive takes a single argument, a prefix to be added to the
-+ symbol names when a C++ .h file is created. This error is generated when the
-+ compiler finds a $PREFIX directive with no arguments.
-
-RDLOCMES reading local message file %1
-+ This is an informational message output by BIND10 when it starts to read a
-+ local message file. (A local message file may replace the text of one of more
-+ messages; the ID of the message will not be changed though.)
-
-UNRECDIR unrecognised directive '%1'
-+ A line starting with a dollar symbol was found, but the first word on the line
-+ (shown in the message) was not a recognised message compiler directive.
+$PREFIX MSG_
+$NAMESPACE isc::log
+
+% DUPMSGID duplicate message ID (%1) in compiled code
+Indicative of a programming error, when it started up, BIND10 detected that
+the given message ID had been registered by one or more modules. (All message
+IDs should be unique throughout BIND10.) This has no impact on the operation
+of the server other that erroneous messages may be logged. (When BIND10 loads
+the message IDs (and their associated text), if a duplicate ID is found it is
+discarded. However, when the module that supplied the duplicate ID logs that
+particular message, the text supplied by the module that added the original
+ID will be output - something that may bear no relation to the condition being
+logged.
+
+% DUPLNS line %1: duplicate $NAMESPACE directive found
+When reading a message file, more than one $NAMESPACE directive was found. In
+this version of the code, such a condition is regarded as an error and the
+read will be abandoned.
+
+% IDNOTFND could not replace message text for '%1': no such message
+During start-up a local message file was read. A line with the listed
+message identification was found in the file, but the identification is not
+one contained in the compiled-in message dictionary. Either the message
+identification has been mis-spelled in the file, or the local file was used
+for an earlier version of the software and the message with that
+identification has been removed.
+
+This message may appear a number of times in the file, once for every such
+unknown message identification.
+
+% INVMSGID line %1: invalid message identification '%2'
+The concatenation of the prefix and the message identification is used as
+a symbol in the C++ module; as such it may only contain
+
+% NOMSGID line %1: message definition line found without a message ID
+Message definition lines are lines starting with a "%". The rest of the line
+should comprise the message ID and text describing the message. This error
+indicates the message compiler found a line in the message file comprising
+just the "%" and nothing else.
+
+% NOMSGTXT line %1: line found containing a message ID ('%2') and no text
+Message definition lines are lines starting with a "%". The rest of the line
+should comprise the message ID and text describing the message. This error
+is generated when a line is found in the message file that contains the
+leading "%" and the message identification but no text.
+
+% NSEXTRARG line %1: $NAMESPACE directive has too many arguments
+The $NAMESPACE directive takes a single argument, a namespace in which all the
+generated symbol names are placed. This error is generated when the
+compiler finds a $NAMESPACE directive with more than one argument.
+
+% NSINVARG line %1: $NAMESPACE directive has an invalid argument ('%2')
+The $NAMESPACE argument should be a valid C++ namespace. The reader does a
+cursory check on its validity, checking that the characters in the namespace
+are correct. The error is generated when the reader finds an invalid
+character. (Valid are alphanumeric characters, underscores and colons.)
+
+% NSNOARG line %1: no arguments were given to the $NAMESPACE directive
+The $NAMESPACE directive takes a single argument, a namespace in which all the
+generated symbol names are placed. This error is generated when the
+compiler finds a $NAMESPACE directive with no arguments.
+
+% OPENIN unable to open message file %1 for input: %2
+The program was not able to open the specified input message file for the
+reason given.
+
+% OPENOUT unable to open %1 for output: %2
+The program was not able to open the specified output file for the reason
+given.
+
+% PRFEXTRARG line %1: $PREFIX directive has too many arguments
+The $PREFIX directive takes a single argument, a prefix to be added to the
+symbol names when a C++ .h file is created. This error is generated when the
+compiler finds a $PREFIX directive with more than one argument.
+
+% PRFINVARG line %1: $PREFIX directive has an invalid argument ('%2')
+The $PREFIX argument is used in a symbol name in a C++ header file. As such,
+it must adhere to restrictions on C++ symbol names (e.g. may only contain
+alphanumeric characters or underscores, and may nor start with a digit).
+A $PREFIX directive was found with an argument (given in the message) that
+violates those restictions.
+
+% RDLOCMES reading local message file %1
+This is an informational message output by BIND10 when it starts to read a
+local message file. (A local message file may replace the text of one of more
+messages; the ID of the message will not be changed though.)
+
+% READERR error reading from message file %1: %2
+The specified error was encountered reading from the named message file.
+
+% WRITERR error writing to %1: %2
+The specified error was encountered by the message compiler when writing to
+the named output file.
+
+% UNRECDIR line %1: unrecognised directive '%2'
+A line starting with a dollar symbol was found, but the first word on the line
+(shown in the message) was not a recognised message compiler directive.
diff --git a/src/lib/log/tests/logger_support_test.cc b/src/lib/log/tests/logger_support_test.cc
index d0d5c53..0a2338b 100644
--- a/src/lib/log/tests/logger_support_test.cc
+++ b/src/lib/log/tests/logger_support_test.cc
@@ -93,13 +93,14 @@ int main(int argc, char** argv) {
initLogger("alpha", severity, dbglevel, localfile);
// Log a few messages
- LOG_FATAL(logger_ex, MSG_MSGWRTERR).arg("test1").arg("42");
- LOG_ERROR(logger_ex, MSG_UNRECDIR).arg("false");
- LOG_WARN(logger_dlm, MSG_MSGRDERR).arg("a.txt").arg("dummy test");
- LOG_INFO(logger_dlm, MSG_OPNMSGIN).arg("example.msg").arg("dummy test");
- LOG_DEBUG(logger_ex, 0, MSG_UNRECDIR).arg("[abc]");
- LOG_DEBUG(logger_ex, 24, MSG_UNRECDIR).arg("[24]");
- LOG_DEBUG(logger_ex, 25, MSG_UNRECDIR).arg("[25]");
- LOG_DEBUG(logger_ex, 26, MSG_UNRECDIR).arg("[26]");
+ LOG_FATAL(logger_ex, MSG_WRITERR).arg("test1").arg("42");
+ LOG_ERROR(logger_ex, MSG_RDLOCMES).arg("dummy/file");
+ LOG_WARN(logger_dlm, MSG_READERR).arg("a.txt").arg("dummy reason");
+ LOG_INFO(logger_dlm, MSG_OPENIN).arg("example.msg").arg("dummy reason");
+ LOG_DEBUG(logger_ex, 0, MSG_RDLOCMES).arg("dummy/0");
+ LOG_DEBUG(logger_ex, 24, MSG_RDLOCMES).arg("dummy/24");
+ LOG_DEBUG(logger_ex, 25, MSG_RDLOCMES).arg("dummy/25");
+ LOG_DEBUG(logger_ex, 26, MSG_RDLOCMES).arg("dummy/26");
+
return (0);
}
diff --git a/src/lib/log/tests/message_dictionary_unittest.cc b/src/lib/log/tests/message_dictionary_unittest.cc
index a92585c..ba33820 100644
--- a/src/lib/log/tests/message_dictionary_unittest.cc
+++ b/src/lib/log/tests/message_dictionary_unittest.cc
@@ -29,7 +29,7 @@ using namespace std;
// and the latter should be present.
static const char* values[] = {
- "DUPLNS", "duplicate $NAMESPACE directive found",
+ "MSG_DUPLNS", "duplicate $NAMESPACE directive found",
"NEWSYM", "new symbol added",
NULL
};
@@ -190,7 +190,7 @@ TEST_F(MessageDictionaryTest, GlobalTest) {
TEST_F(MessageDictionaryTest, GlobalLoadTest) {
vector<string>& duplicates = MessageInitializer::getDuplicates();
ASSERT_EQ(1, duplicates.size());
- EXPECT_EQ(string("DUPLNS"), duplicates[0]);
+ EXPECT_EQ(string("MSG_DUPLNS"), duplicates[0]);
string text = MessageDictionary::globalDictionary().getText("NEWSYM");
EXPECT_EQ(string("new symbol added"), text);
diff --git a/src/lib/log/tests/message_reader_unittest.cc b/src/lib/log/tests/message_reader_unittest.cc
index 36288f2..7b3ba5f 100644
--- a/src/lib/log/tests/message_reader_unittest.cc
+++ b/src/lib/log/tests/message_reader_unittest.cc
@@ -68,8 +68,8 @@ TEST_F(MessageReaderTest, BlanksAndComments) {
EXPECT_NO_THROW(reader_.processLine(" \n "));
EXPECT_NO_THROW(reader_.processLine("# This is a comment"));
EXPECT_NO_THROW(reader_.processLine("\t\t # Another comment"));
- EXPECT_NO_THROW(reader_.processLine(" + A description line"));
- EXPECT_NO_THROW(reader_.processLine("#+ A comment"));
+ EXPECT_NO_THROW(reader_.processLine(" A description line"));
+ EXPECT_NO_THROW(reader_.processLine("# A comment"));
EXPECT_NO_THROW(reader_.processLine(" +# A description line"));
// ... and (b) nothing gets added to either the map or the not-added section.
@@ -97,6 +97,15 @@ processLineException(MessageReader& reader, const char* what,
}
}
+// Check that it recognises invalid directives
+
+TEST_F(MessageReaderTest, InvalidDirectives) {
+
+ // Check that a "$" with nothing else generates an error
+ processLineException(reader_, "$", MSG_UNRECDIR);
+ processLineException(reader_, "$xyz", MSG_UNRECDIR);
+}
+
// Check that it can parse a prefix
TEST_F(MessageReaderTest, Prefix) {
@@ -104,8 +113,8 @@ TEST_F(MessageReaderTest, Prefix) {
// Check that no $PREFIX is present
EXPECT_EQ(string(""), reader_.getPrefix());
- // Check that a $PREFIX directive with no argument generates an error.
- processLineException(reader_, "$PREFIX", MSG_PRFNOARG);
+ // Check that a $PREFIX directive with no argument is OK
+ EXPECT_NO_THROW(reader_.processLine("$PREFIX"));
// Check a $PREFIX with multiple arguments is invalid
processLineException(reader_, "$prefix A B", MSG_PRFEXTRARG);
@@ -118,17 +127,19 @@ TEST_F(MessageReaderTest, Prefix) {
// A valid prefix should be accepted
EXPECT_NO_THROW(reader_.processLine("$PREFIX dlm__"));
- EXPECT_EQ(string("DLM__"), reader_.getPrefix());
+ EXPECT_EQ(string("dlm__"), reader_.getPrefix());
// And check that the parser fails on invalid prefixes...
processLineException(reader_, "$prefix 1ABC", MSG_PRFINVARG);
- // ... and rejects another valid one
- processLineException(reader_, "$PREFIX ABC", MSG_DUPLPRFX);
-
// Check that we can clear the prefix as well
reader_.clearPrefix();
EXPECT_EQ(string(""), reader_.getPrefix());
+
+ EXPECT_NO_THROW(reader_.processLine("$PREFIX dlm__"));
+ EXPECT_EQ(string("dlm__"), reader_.getPrefix());
+ EXPECT_NO_THROW(reader_.processLine("$PREFIX"));
+ EXPECT_EQ(string(""), reader_.getPrefix());
}
// Check that it can parse a namespace
@@ -173,8 +184,8 @@ TEST_F(MessageReaderTest, Namespace) {
TEST_F(MessageReaderTest, ValidMessageAddDefault) {
// Add a couple of valid messages
- reader_.processLine("GLOBAL1\t\tthis is message global one\n");
- reader_.processLine("GLOBAL2 this is message global two");
+ reader_.processLine("% GLOBAL1\t\tthis is message global one\n");
+ reader_.processLine("%GLOBAL2 this is message global two");
// ... and check them
EXPECT_EQ(string("this is message global one"),
@@ -191,9 +202,9 @@ TEST_F(MessageReaderTest, ValidMessageAddDefault) {
TEST_F(MessageReaderTest, ValidMessageAdd) {
// Add a couple of valid messages
- reader_.processLine("GLOBAL1\t\tthis is message global one\n",
+ reader_.processLine("%GLOBAL1\t\tthis is message global one\n",
MessageReader::ADD);
- reader_.processLine("GLOBAL2 this is message global two",
+ reader_.processLine("% GLOBAL2 this is message global two",
MessageReader::ADD);
// ... and check them
@@ -214,9 +225,9 @@ TEST_F(MessageReaderTest, ValidMessageReplace) {
dictionary_->add("GLOBAL2", "original global2 message");
// Replace a couple of valid messages
- reader_.processLine("GLOBAL1\t\tthis is message global one\n",
+ reader_.processLine("% GLOBAL1\t\tthis is message global one\n",
MessageReader::REPLACE);
- reader_.processLine("GLOBAL2 this is message global two",
+ reader_.processLine("% GLOBAL2 this is message global two",
MessageReader::REPLACE);
// ... and check them
@@ -237,14 +248,14 @@ TEST_F(MessageReaderTest, ValidMessageReplace) {
TEST_F(MessageReaderTest, Overflows) {
// Add a couple of valid messages
- reader_.processLine("GLOBAL1\t\tthis is message global one\n");
- reader_.processLine("GLOBAL2 this is message global two");
+ reader_.processLine("% GLOBAL1\t\tthis is message global one\n");
+ reader_.processLine("% GLOBAL2 this is message global two");
// Add a duplicate in ADD mode.
- reader_.processLine("GLOBAL1\t\tthis is a replacement for global one");
+ reader_.processLine("% GLOBAL1\t\tthis is a replacement for global one");
// Replace a non-existent one in REPLACE mode
- reader_.processLine("LOCAL\t\tthis is a new message",
+ reader_.processLine("% LOCAL\t\tthis is a new message",
MessageReader::REPLACE);
// Check what is in the dictionary.
diff --git a/src/lib/log/tests/run_time_init_test.sh.in b/src/lib/log/tests/run_time_init_test.sh.in
index 1010566..e48a781 100755
--- a/src/lib/log/tests/run_time_init_test.sh.in
+++ b/src/lib/log/tests/run_time_init_test.sh.in
@@ -29,48 +29,49 @@ passfail() {
# Create the local message file for testing
cat > $localmes << .
-NOTHERE this message is not in the global dictionary
-MSGRDERR replacement read error, parameters: '%1' and '%2'
-UNRECDIR replacement unrecognised directive message, parameter is '%1'
+\$PREFIX MSG_
+% NOTHERE this message is not in the global dictionary
+% READERR replacement read error, parameters: '%1' and '%2'
+% RDLOCMES replacement read local message file, parameter is '%1'
.
echo -n "1. runInitTest default parameters: "
cat > $tempfile << .
-FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
-ERROR [alpha.example] UNRECDIR, unrecognised directive 'false'
-WARN [alpha.dlm] MSGRDERR, error reading from message file a.txt: dummy test
-INFO [alpha.dlm] OPNMSGIN, unable to open message file example.msg for input: dummy test
+FATAL [alpha.example] MSG_WRITERR, error writing to test1: 42
+ERROR [alpha.example] MSG_RDLOCMES, reading local message file dummy/file
+WARN [alpha.dlm] MSG_READERR, error reading from message file a.txt: dummy reason
+INFO [alpha.dlm] MSG_OPENIN, unable to open message file example.msg for input: dummy reason
.
./logger_support_test | cut -d' ' -f3- | diff $tempfile -
passfail $?
echo -n "2. Severity filter: "
cat > $tempfile << .
-FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
-ERROR [alpha.example] UNRECDIR, unrecognised directive 'false'
+FATAL [alpha.example] MSG_WRITERR, error writing to test1: 42
+ERROR [alpha.example] MSG_RDLOCMES, reading local message file dummy/file
.
./logger_support_test -s error | cut -d' ' -f3- | diff $tempfile -
passfail $?
echo -n "3. Debug level: "
cat > $tempfile << .
-FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
-ERROR [alpha.example] UNRECDIR, unrecognised directive 'false'
-WARN [alpha.dlm] MSGRDERR, error reading from message file a.txt: dummy test
-INFO [alpha.dlm] OPNMSGIN, unable to open message file example.msg for input: dummy test
-DEBUG [alpha.example] UNRECDIR, unrecognised directive '[abc]'
-DEBUG [alpha.example] UNRECDIR, unrecognised directive '[24]'
-DEBUG [alpha.example] UNRECDIR, unrecognised directive '[25]'
+FATAL [alpha.example] MSG_WRITERR, error writing to test1: 42
+ERROR [alpha.example] MSG_RDLOCMES, reading local message file dummy/file
+WARN [alpha.dlm] MSG_READERR, error reading from message file a.txt: dummy reason
+INFO [alpha.dlm] MSG_OPENIN, unable to open message file example.msg for input: dummy reason
+DEBUG [alpha.example] MSG_RDLOCMES, reading local message file dummy/0
+DEBUG [alpha.example] MSG_RDLOCMES, reading local message file dummy/24
+DEBUG [alpha.example] MSG_RDLOCMES, reading local message file dummy/25
.
./logger_support_test -s debug -d 25 | cut -d' ' -f3- | diff $tempfile -
passfail $?
echo -n "4. Local message replacement: "
cat > $tempfile << .
-WARN [alpha.log] IDNOTFND, could not replace message for 'NOTHERE': no such message identification
-FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
-ERROR [alpha.example] UNRECDIR, replacement unrecognised directive message, parameter is 'false'
-WARN [alpha.dlm] MSGRDERR, replacement read error, parameters: 'a.txt' and 'dummy test'
+WARN [alpha.log] MSG_IDNOTFND, could not replace message text for 'MSG_NOTHERE': no such message
+FATAL [alpha.example] MSG_WRITERR, error writing to test1: 42
+ERROR [alpha.example] MSG_RDLOCMES, replacement read local message file, parameter is 'dummy/file'
+WARN [alpha.dlm] MSG_READERR, replacement read error, parameters: 'a.txt' and 'dummy reason'
.
./logger_support_test -s warn $localmes | cut -d' ' -f3- | diff $tempfile -
passfail $?
diff --git a/src/lib/nsas/Makefile.am b/src/lib/nsas/Makefile.am
index 5f05f1b..3ecbca7 100644
--- a/src/lib/nsas/Makefile.am
+++ b/src/lib/nsas/Makefile.am
@@ -4,23 +4,39 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/util -I$(top_builddir)/src/lib/util
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/nsas -I$(top_builddir)/src/lib/nsas
AM_CPPFLAGS += $(SQLITE_CFLAGS)
AM_CXXFLAGS = $(B10_CXXFLAGS)
-# Some versions of GCC warn about some versions of Boost regarding
-# missing initializer for members in its posix_time.
+# Some versions of GCC warn about some versions of Boost regarding missing
+# initializer for members in its posix_time.
# https://svn.boost.org/trac/boost/ticket/3477
# But older GCC compilers don't have the flag.
AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
-if USE_CLANGPP
# clang++ complains about unused function parameters in some boost header
# files.
+if USE_CLANGPP
AM_CXXFLAGS += -Wno-unused-parameter
endif
+# Define rule to build logging source files from message file
+nsasdef.h nsasdef.cc: nsasdef.mes
+ $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/nsas/nsasdef.mes
+
+# What is being built.
lib_LTLIBRARIES = libnsas.la
+
+# Tell Automake that the nsasdef.{cc,h} source files are created in the build
+# process, so it must create these before doing anything else. Although they
+# are a dependency of the library (so will be created from the message file
+# anyway), there is no guarantee as to exactly _when_ in the build they will be
+# created. As the .h file is included in other sources file (so must be
+# present when they are compiled), the safest option is to create it first.
+BUILT_SOURCES = nsasdef.h nsasdef.cc
+
+# Library sources. The generated files will not be in the distribution.
libnsas_la_SOURCES = address_entry.h address_entry.cc
libnsas_la_SOURCES += asiolink.h
libnsas_la_SOURCES += hash.cc hash.h
@@ -32,9 +48,16 @@ libnsas_la_SOURCES += nameserver_address.h nameserver_address.cc
libnsas_la_SOURCES += nameserver_entry.cc nameserver_entry.h
libnsas_la_SOURCES += nsas_entry_compare.h
libnsas_la_SOURCES += nsas_entry.h nsas_types.h
+libnsas_la_SOURCES += nsas_log.cc nsas_log.h
libnsas_la_SOURCES += zone_entry.cc zone_entry.h
libnsas_la_SOURCES += fetchable.h
libnsas_la_SOURCES += address_request_callback.h
libnsas_la_SOURCES += glue_hints.h glue_hints.cc
-CLEANFILES = *.gcno *.gcda
+nodist_libnsas_la_SOURCES = nsasdef.h nsasdef.cc
+
+# The message file should be in the distribution.
+EXTRA_DIST = nsasdef.mes
+
+# Make sure that the generated files are got rid of in a clean operation
+CLEANFILES = *.gcno *.gcda nsasdef.h nsasdef.cc
diff --git a/src/lib/nsas/nameserver_address_store.cc b/src/lib/nsas/nameserver_address_store.cc
index 3cef38d..ac55409 100644
--- a/src/lib/nsas/nameserver_address_store.cc
+++ b/src/lib/nsas/nameserver_address_store.cc
@@ -22,6 +22,7 @@
#include <dns/rdataclass.h>
#include <util/locks.h>
#include <util/lru_list.h>
+#include <log/logger.h>
#include "hash_table.h"
#include "hash_deleter.h"
@@ -31,6 +32,8 @@
#include "zone_entry.h"
#include "glue_hints.h"
#include "address_request_callback.h"
+#include "nsasdef.h"
+#include "nsas_log.h"
using namespace isc::dns;
using namespace std;
@@ -84,6 +87,8 @@ NameserverAddressStore::lookup(const string& zone, const RRClass& class_code,
boost::shared_ptr<AddressRequestCallback> callback, AddressFamily family,
const GlueHints& glue_hints)
{
+ LOG_DEBUG(nsas_logger, NSAS_DBG_TRACE, NSAS_LOOKUPZONE).arg(zone);
+
pair<bool, boost::shared_ptr<ZoneEntry> > zone_obj(
zone_hash_->getOrAdd(HashKey(zone, class_code),
boost::bind(newZone, resolver_, &zone, &class_code,
@@ -103,6 +108,8 @@ NameserverAddressStore::cancel(const string& zone,
const boost::shared_ptr<AddressRequestCallback>& callback,
AddressFamily family)
{
+ LOG_DEBUG(nsas_logger, NSAS_DBG_TRACE, NSAS_LOOKUPCANCEL).arg(zone);
+
boost::shared_ptr<ZoneEntry> entry(zone_hash_->get(HashKey(zone,
class_code)));
if (entry) {
diff --git a/src/lib/nsas/nameserver_entry.cc b/src/lib/nsas/nameserver_entry.cc
index 4035f79..65b2ec2 100644
--- a/src/lib/nsas/nameserver_entry.cc
+++ b/src/lib/nsas/nameserver_entry.cc
@@ -40,6 +40,7 @@
#include "address_entry.h"
#include "nameserver_address.h"
#include "nameserver_entry.h"
+#include "nsas_log.h"
using namespace isc::asiolink;
using namespace isc::nsas;
@@ -178,6 +179,9 @@ NameserverEntry::updateAddressRTTAtIndex(uint32_t rtt, size_t index,
new_rtt = 1;
}
addresses_[family][index].setRTT(new_rtt);
+ LOG_DEBUG(nsas_logger, NSAS_DBG_RTT, NSAS_SETRTT)
+ .arg(addresses_[family][index].getAddress().toText())
+ .arg(old_rtt).arg(new_rtt);
}
void
@@ -203,7 +207,7 @@ NameserverEntry::setAddressUnreachable(const IOAddress& address) {
* \short A callback into the resolver.
*
* Whenever we ask the resolver something, this is created and the answer is
- * fed back trough this. It holds a shared pointer to the entry so it is not
+ * fed back through this. It holds a shared pointer to the entry so it is not
* destroyed too soon.
*/
class NameserverEntry::ResolverCallback :
@@ -230,6 +234,7 @@ class NameserverEntry::ResolverCallback :
if (!response_message ||
response_message->getRcode() != isc::dns::Rcode::NOERROR() ||
response_message->getRRCount(isc::dns::Message::SECTION_ANSWER) == 0) {
+ LOG_ERROR(nsas_logger, NSAS_INVRESPSTR).arg(entry_->getName());
failureInternal(lock);
return;
}
@@ -243,7 +248,12 @@ class NameserverEntry::ResolverCallback :
if (response->getType() != type_ ||
response->getClass() != RRClass(entry_->getClass()))
{
- // TODO Log we got answer of different type
+ // Invalid response type or class
+ LOG_ERROR(nsas_logger, NSAS_INVRESPTC)
+ .arg(entry_->getName()).arg(type_)
+ .arg(entry_->getClass()).arg(response->getType())
+ .arg(response->getClass());
+
failureInternal(lock);
return;
}
@@ -264,8 +274,10 @@ class NameserverEntry::ResolverCallback :
}
}
// If we found it, use it. If not, create a new one.
- entries.push_back(found ? *found : AddressEntry(IOAddress(
- i->getCurrent().toText()), 1));
+ entries.push_back(found ? *found : AddressEntry(
+ IOAddress(address), 1));
+ LOG_DEBUG(nsas_logger, NSAS_DBG_RESULTS, NSAS_NSLKUPSUCC)
+ .arg(address).arg(entry_->getName());
}
// We no longer need the previous set of addresses, we have
@@ -310,6 +322,8 @@ class NameserverEntry::ResolverCallback :
* So mark the current address family as unreachable.
*/
virtual void failure() {
+ LOG_DEBUG(nsas_logger, NSAS_DBG_RESULTS, NSAS_NSLKUPFAIL)
+ .arg(type_).arg(entry_->getName());
Lock lock(entry_->mutex_);
failureInternal(lock);
}
@@ -422,6 +436,8 @@ NameserverEntry::askIP(isc::resolve::ResolverInterface* resolver,
// Ask for both types of addresses
// We are unlocked here, as the callback from that might want to lock
lock.unlock();
+
+ LOG_DEBUG(nsas_logger, NSAS_DBG_TRACE, NSAS_NSADDR).arg(getName());
askIP(resolver, RRType::A(), V4_ONLY);
askIP(resolver, RRType::AAAA(), V6_ONLY);
// Make sure we end the routine when we are not locked
diff --git a/src/lib/nsas/nsas_log.cc b/src/lib/nsas/nsas_log.cc
new file mode 100644
index 0000000..931b131
--- /dev/null
+++ b/src/lib/nsas/nsas_log.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// Defines the logger used by the NSAS
+
+#include "nsas/nsas_log.h"
+
+namespace isc {
+namespace nsas {
+
+isc::log::Logger nsas_logger("nsas");
+
+} // namespace nsas
+} // namespace isc
+
diff --git a/src/lib/nsas/nsas_log.h b/src/lib/nsas/nsas_log.h
new file mode 100644
index 0000000..9631988
--- /dev/null
+++ b/src/lib/nsas/nsas_log.h
@@ -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.
+
+#ifndef __NSAS_LOG__H
+#define __NSAS_LOG__H
+
+#include <log/macros.h>
+#include "nsasdef.h"
+
+namespace isc {
+namespace nsas {
+
+/// \brief NSAS Logging
+///
+/// Defines the levels used to output debug messages in the NSAS. Note that
+/// higher numbers equate to more verbose (and detailed) output.
+
+// The first level traces normal operations - asking the NSAS for an address,
+// and cancelling a lookup. It also records when the NSAS calls back to the
+// resolver to resolve something.
+const int NSAS_DBG_TRACE = 10;
+
+// The next level extends the normal operations and records the results of the
+// lookups.
+const int NSAS_DBG_RESULTS = 20;
+
+// Additional information on the usage of the names - the RTT values obtained
+// when queries were done.
+const int NSAS_DBG_RTT = 30;
+
+
+/// \brief NSAS Logger
+///
+/// Define the logger used to log messages. We could define it in multiple
+/// modules, but defining in a single module and linking to it saves time and
+/// space.
+extern isc::log::Logger nsas_logger; // isc::nsas::logger is the NSAS logger
+
+} // namespace nsas
+} // namespace isc
+
+#endif // __NSAS_LOG__H
diff --git a/src/lib/nsas/nsasdef.mes b/src/lib/nsas/nsasdef.mes
new file mode 100644
index 0000000..0f32d09
--- /dev/null
+++ b/src/lib/nsas/nsasdef.mes
@@ -0,0 +1,61 @@
+# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$PREFIX NSAS_
+$NAMESPACE isc::nsas
+
+% INVRESPSTR queried for %1 but got invalid response
+This message indicates an internal error in the nameserver address store
+component (NSAS) of the resolver. The NSAS made a query for a RR for the
+specified nameserver but received an invalid response. Either the success
+function was called without a DNS message or the message was invalid on some
+way. (In the latter case, the error should have been picked up elsewhere in
+the processing logic, hence the raising of the error here.)
+
+% INVRESPTC queried for %1 RR of type/class %2/%3, received response %4/%5
+This message indicates an internal error in the nameserver address store
+component (NSAS) of the resolver. The NSAS made a query for the given RR
+type and class, but instead received an answer with the given type and class.
+
+% LOOKUPCANCEL lookup for zone %1 has been cancelled
+A debug message, this is output when a NSAS (nameserver address store -
+part of the resolver) lookup for a zone has been cancelled.
+
+% LOOKUPZONE searching NSAS for nameservers for zone %1
+A debug message, this is output when a call is made to the nameserver address
+store (part of the resolver) to obtain the nameservers for the specified zone.
+
+% NSADDR asking resolver to obtain A and AAAA records for %1
+A debug message, the NSAS (nameserver address store - part of the resolver) is
+making a callback into the resolver to retrieve the address records for the
+specified nameserver.
+
+% NSLKUPFAIL failed to lookup any %1 for %2
+A debug message, the NSAS (nameserver address store - part of the resolver)
+has been unable to retrieve the specified resource record for the specified
+nameserver. This is not necessarily a problem - the nameserver may be
+unreachable, in which case the NSAS will try other nameservers in the zone.
+
+% NSLKUPSUCC found address %1 for %2
+A debug message, the NSAS (nameserver address store - part of the resolver)
+has retrieved the given address for the specified nameserver through an
+external query.
+
+% SETRTT reporting RTT for %1 as %2; new value is now %3
+A NSAS (nameserver address store - part of the resolver) debug message
+reporting the round-trip time (RTT) for a query made to the specified
+nameserver. The RTT has been updated using the value given and the new RTT is
+displayed. (The RTT is subject to a calculation that damps out sudden
+changes. As a result, the new RTT is not necessarily equal to the RTT
+reported.)
diff --git a/src/lib/nsas/tests/Makefile.am b/src/lib/nsas/tests/Makefile.am
index 3426551..38c6d93 100644
--- a/src/lib/nsas/tests/Makefile.am
+++ b/src/lib/nsas/tests/Makefile.am
@@ -52,6 +52,8 @@ run_unittests_LDADD += -lboost_thread
endif
run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
diff --git a/src/lib/nsas/tests/run_unittests.cc b/src/lib/nsas/tests/run_unittests.cc
index 35414e9..e469e03 100644
--- a/src/lib/nsas/tests/run_unittests.cc
+++ b/src/lib/nsas/tests/run_unittests.cc
@@ -12,16 +12,13 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <config.h>
-
#include <gtest/gtest.h>
+#include <log/logger_support.h>
#include <util/unittests/run_all.h>
-#include <dns/tests/unittest_util.h>
-
int
main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
-
+ isc::log::initLogger();
return (isc::util::unittests::run_all());
}
diff --git a/src/lib/python/Makefile.am b/src/lib/python/Makefile.am
index f7eb333..75e5afb 100644
--- a/src/lib/python/Makefile.am
+++ b/src/lib/python/Makefile.am
@@ -1,6 +1,7 @@
SUBDIRS = isc
python_PYTHON = bind10_config.py
+pythondir = $(pyexecdir)
# Explicitly define DIST_COMMON so ${python_PYTHON} is not included
# as we don't want the generated file included in distributed tarfile.
diff --git a/src/lib/python/bind10_config.py.in b/src/lib/python/bind10_config.py.in
index 3f2947d..4a38903 100644
--- a/src/lib/python/bind10_config.py.in
+++ b/src/lib/python/bind10_config.py.in
@@ -17,7 +17,37 @@
# variables to python scripts and libraries.
import os
-BIND10_MSGQ_SOCKET_FILE = os.path.join("@localstatedir@",
- "@PACKAGE_NAME@",
- "msgq_socket").replace("${prefix}",
- "@prefix@")
+def reload():
+ # In a function, for testing purposes
+ global BIND10_MSGQ_SOCKET_FILE
+ global DATA_PATH
+ global PLUGIN_PATHS
+ global PREFIX
+ BIND10_MSGQ_SOCKET_FILE = os.path.join("@localstatedir@",
+ "@PACKAGE_NAME@",
+ "msgq_socket").replace("${prefix}",
+ "@prefix@")
+ PREFIX = "@prefix@"
+
+ # If B10_FROM_SOURCE is set in the environment, we use data files
+ # from a directory relative to the value of that variable, or, if defined,
+ # relative to the value of B10_FROM_SOURCE_LOCALSTATEDIR. Otherwise
+ # we use the ones installed on the system.
+ # B10_FROM_SOURCE_LOCALSTATEDIR is specifically intended to be used for
+ # tests where we want to use variuos types of configuration within the test
+ # environment. (We may want to make it even more generic so that the path is
+ # passed from the boss process)
+ if "B10_FROM_SOURCE" in os.environ:
+ if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
+ DATA_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
+ else:
+ DATA_PATH = os.environ["B10_FROM_SOURCE"]
+ PLUGIN_PATHS = [DATA_PATH + '/src/bin/cfgmgr/plugins']
+ else:
+ DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
+ PLUGIN_PATHS = ["@prefix@/share/@PACKAGE@/config_plugins"]
+ # For testing the plugins so they can find their own spec files
+ if "B10_TEST_PLUGIN_DIR" in os.environ:
+ PLUGIN_PATHS = os.environ["B10_TEST_PLUGIN_DIR"].split(':')
+
+reload()
diff --git a/src/lib/python/isc/config/config_data.py b/src/lib/python/isc/config/config_data.py
index cee1d34..cc3f484 100644
--- a/src/lib/python/isc/config/config_data.py
+++ b/src/lib/python/isc/config/config_data.py
@@ -213,6 +213,15 @@ class ConfigData:
return spec['item_default'], True
return None, False
+ def get_default_value(self, identifier):
+ """Returns the default from the specification, or None if there
+ is no default"""
+ spec = find_spec_part(self.specification.get_config_spec(), identifier)
+ if spec and 'item_default' in spec:
+ return spec['item_default']
+ else:
+ return None
+
def get_module_spec(self):
"""Returns the ModuleSpec object associated with this ConfigData"""
return self.specification
diff --git a/src/lib/python/isc/config/tests/config_data_test.py b/src/lib/python/isc/config/tests/config_data_test.py
index 923e0b6..48099bb 100644
--- a/src/lib/python/isc/config/tests/config_data_test.py
+++ b/src/lib/python/isc/config/tests/config_data_test.py
@@ -237,6 +237,27 @@ class TestConfigData(unittest.TestCase):
self.assertEqual(None, value)
self.assertEqual(False, default)
+ def test_get_default_value(self):
+ self.assertEqual(1, self.cd.get_default_value("item1"))
+ self.assertEqual('default', self.cd.get_default_value("item6/value1"))
+ self.assertEqual(None, self.cd.get_default_value("item6/value2"))
+
+ # set some local values to something else, and see if we
+ # still get the default
+ self.cd.set_local_config({"item1": 2, "item6": { "value1": "asdf" } })
+
+ self.assertEqual((2, False), self.cd.get_value("item1"))
+ self.assertEqual(1, self.cd.get_default_value("item1"))
+ self.assertEqual(('asdf', False), self.cd.get_value("item6/value1"))
+ self.assertEqual('default', self.cd.get_default_value("item6/value1"))
+
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.cd.get_default_value,
+ "does_not_exist/value1")
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.cd.get_default_value,
+ "item6/doesnotexist")
+
def test_set_local_config(self):
self.cd.set_local_config({"item1": 2})
value, default = self.cd.get_value("item1")
diff --git a/src/lib/python/isc/notify/notify_out.py b/src/lib/python/isc/notify/notify_out.py
index 43dc7af..178a983 100644
--- a/src/lib/python/isc/notify/notify_out.py
+++ b/src/lib/python/isc/notify/notify_out.py
@@ -142,13 +142,17 @@ class NotifyOut:
if zone_id not in self._notify_infos:
return
+ # Has no slave servers, skip it.
+ if (len(self._notify_infos[zone_id].notify_slaves) <= 0):
+ return
+
with self._lock:
if (self.notify_num >= _MAX_NOTIFY_NUM) or (zone_id in self._notifying_zones):
if zone_id not in self._waiting_zones:
self._waiting_zones.append(zone_id)
else:
self._notify_infos[zone_id].prepare_notify_out()
- self.notify_num += 1
+ self.notify_num += 1
self._notifying_zones.append(zone_id)
def _dispatcher(self, started_event):
@@ -300,7 +304,7 @@ class NotifyOut:
try:
r_fds, w, e = select.select(valid_socks, [], [], block_timeout)
except select.error as err:
- if err.args[0] != EINTR:
+ if err.args[0] != errno.EINTR:
return {}, {}
if self._read_sock in r_fds: # user has called shutdown()
diff --git a/src/lib/python/isc/notify/tests/notify_out_test.py b/src/lib/python/isc/notify/tests/notify_out_test.py
index c4c149c..44725d0 100644
--- a/src/lib/python/isc/notify/tests/notify_out_test.py
+++ b/src/lib/python/isc/notify/tests/notify_out_test.py
@@ -99,36 +99,51 @@ class TestNotifyOut(unittest.TestCase):
self._notify._notify_infos[('example.org.', 'IN')] = MockZoneNotifyInfo('example.org.', 'IN')
self._notify._notify_infos[('example.org.', 'CH')] = MockZoneNotifyInfo('example.org.', 'CH')
- info = self._notify._notify_infos[('example.net.', 'IN')]
- info.notify_slaves.append(('127.0.0.1', 53))
- info.notify_slaves.append(('1.1.1.1', 5353))
+ net_info = self._notify._notify_infos[('example.net.', 'IN')]
+ net_info.notify_slaves.append(('127.0.0.1', 53))
+ net_info.notify_slaves.append(('1.1.1.1', 5353))
+ com_info = self._notify._notify_infos[('example.com.', 'IN')]
+ com_info.notify_slaves.append(('1.1.1.1', 5353))
+ com_ch_info = self._notify._notify_infos[('example.com.', 'CH')]
+ com_ch_info.notify_slaves.append(('1.1.1.1', 5353))
def tearDown(self):
self._db_file.close()
os.unlink(self._db_file.name)
def test_send_notify(self):
+ notify_out._MAX_NOTIFY_NUM = 2
+
self._notify.send_notify('example.net')
self.assertEqual(self._notify.notify_num, 1)
- self.assertEqual(self._notify._notifying_zones[0], ('example.net.','IN'))
+ self.assertEqual(self._notify._notifying_zones[0], ('example.net.', 'IN'))
self._notify.send_notify('example.com')
self.assertEqual(self._notify.notify_num, 2)
- self.assertEqual(self._notify._notifying_zones[1], ('example.com.','IN'))
+ self.assertEqual(self._notify._notifying_zones[1], ('example.com.', 'IN'))
- notify_out._MAX_NOTIFY_NUM = 3
+ # notify_num is equal to MAX_NOTIFY_NUM, append it to waiting_zones list.
self._notify.send_notify('example.com', 'CH')
- self.assertEqual(self._notify.notify_num, 3)
- self.assertEqual(self._notify._notifying_zones[2], ('example.com.','CH'))
-
- self._notify.send_notify('example.org.')
- self.assertEqual(self._notify._waiting_zones[0], ('example.org.', 'IN'))
- self._notify.send_notify('example.org.')
+ self.assertEqual(self._notify.notify_num, 2)
self.assertEqual(1, len(self._notify._waiting_zones))
+ # zone_id is already in notifying_zones list, append it to waiting_zones list.
+ self._notify.send_notify('example.net')
+ self.assertEqual(2, len(self._notify._waiting_zones))
+ self.assertEqual(self._notify._waiting_zones[1], ('example.net.', 'IN'))
+
+ # zone_id is already in waiting_zones list, skip it.
+ self._notify.send_notify('example.net')
+ self.assertEqual(2, len(self._notify._waiting_zones))
+
+ # has no slave masters, skip it.
self._notify.send_notify('example.org.', 'CH')
+ self.assertEqual(self._notify.notify_num, 2)
+ self.assertEqual(2, len(self._notify._waiting_zones))
+
+ self._notify.send_notify('example.org.')
+ self.assertEqual(self._notify.notify_num, 2)
self.assertEqual(2, len(self._notify._waiting_zones))
- self.assertEqual(self._notify._waiting_zones[1], ('example.org.', 'CH'))
def test_wait_for_notify_reply(self):
self._notify.send_notify('example.net.')
@@ -171,6 +186,7 @@ class TestNotifyOut(unittest.TestCase):
self._notify.send_notify('example.net.')
self._notify.send_notify('example.com.')
notify_out._MAX_NOTIFY_NUM = 2
+ # zone example.org. has no slave servers.
self._notify.send_notify('example.org.')
self._notify.send_notify('example.com.', 'CH')
@@ -179,17 +195,19 @@ class TestNotifyOut(unittest.TestCase):
self.assertEqual(0, info.notify_try_num)
self.assertEqual(info.get_current_notify_target(), ('1.1.1.1', 5353))
self.assertEqual(2, self._notify.notify_num)
+ self.assertEqual(1, len(self._notify._waiting_zones))
self._notify._notify_next_target(info)
self.assertEqual(0, info.notify_try_num)
self.assertIsNone(info.get_current_notify_target())
self.assertEqual(2, self._notify.notify_num)
- self.assertEqual(1, len(self._notify._waiting_zones))
+ self.assertEqual(0, len(self._notify._waiting_zones))
example_com_info = self._notify._notify_infos[('example.com.', 'IN')]
self._notify._notify_next_target(example_com_info)
- self.assertEqual(2, self._notify.notify_num)
- self.assertEqual(2, len(self._notify._notifying_zones))
+ self.assertEqual(1, self._notify.notify_num)
+ self.assertEqual(1, len(self._notify._notifying_zones))
+ self.assertEqual(0, len(self._notify._waiting_zones))
def test_handle_notify_reply(self):
self.assertEqual(notify_out._BAD_REPLY_PACKET, self._notify._handle_notify_reply(None, b'badmsg'))
diff --git a/src/lib/python/isc/testutils/Makefile.am b/src/lib/python/isc/testutils/Makefile.am
index 8f00a9b..dd032fb 100644
--- a/src/lib/python/isc/testutils/Makefile.am
+++ b/src/lib/python/isc/testutils/Makefile.am
@@ -1 +1 @@
-EXTRA_DIST = __init__.py parse_args.py
+EXTRA_DIST = __init__.py parse_args.py tsigctx_mock.py
diff --git a/src/lib/python/isc/testutils/tsigctx_mock.py b/src/lib/python/isc/testutils/tsigctx_mock.py
new file mode 100644
index 0000000..a9af9b9
--- /dev/null
+++ b/src/lib/python/isc/testutils/tsigctx_mock.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from pydnspp import *
+
+class MockTSIGContext(TSIGContext):
+ """Tthis is a mock of TSIGContext class for testing.
+ Via its "error" attribute, you can fake the result of verify(), thereby
+ you can test many of TSIG related tests without requiring actual crypto
+ setups. "error" should be either a TSIGError type value or a callable
+ object (typically a function). In the latter case, the callable object
+ will take the context as a parameter, and is expected to return a
+ TSIGError object.
+ """
+
+ def __init__(self, tsig_key):
+ super().__init__(tsig_key)
+ self.error = None
+ self.verify_called = 0 # number of verify() called
+
+ def sign(self, qid, data):
+ """Transparently delegate the processing to the super class.
+ It doesn't matter much anyway because normal applications that would
+ be implemented in Python normally won't call TSIGContext.sign()
+ directly.
+ """
+ return super().sign(qid, data)
+
+ def verify(self, tsig_record, data):
+ self.verify_called += 1
+ # call real "verify" so that we can notice any misue (which would
+ # result in exception.
+ super().verify(tsig_record, data)
+ return self.get_error()
+
+ def get_error(self):
+ if self.error is None:
+ return super().get_error()
+ if hasattr(self.error, '__call__'):
+ return self.error(self)
+ return self.error
diff --git a/src/lib/python/isc/util/Makefile.am b/src/lib/python/isc/util/Makefile.am
index 7ab8048..f6cbb78 100644
--- a/src/lib/python/isc/util/Makefile.am
+++ b/src/lib/python/isc/util/Makefile.am
@@ -1,5 +1,5 @@
SUBDIRS = . tests
-python_PYTHON = __init__.py process.py socketserver_mixin.py
+python_PYTHON = __init__.py process.py socketserver_mixin.py file.py
pythondir = $(pyexecdir)/isc/util
diff --git a/src/lib/python/isc/util/file.py b/src/lib/python/isc/util/file.py
new file mode 100644
index 0000000..faef9a8
--- /dev/null
+++ b/src/lib/python/isc/util/file.py
@@ -0,0 +1,29 @@
+# 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.
+
+"""Various functions for working with files and directories."""
+
+from os.path import exists, join
+
+def path_search(filename, paths):
+ """
+ Searches list of paths to find filename in one of them. The found one will
+ be returned or IOError will be returned if it isn't found.
+ """
+ for p in paths:
+ f = join(p, filename)
+ if exists(f):
+ return f
+ raise IOError("'" + filename + "' not found in " + str(paths))
diff --git a/src/lib/python/isc/util/tests/Makefile.am b/src/lib/python/isc/util/tests/Makefile.am
index f32fda0..0ce96de 100644
--- a/src/lib/python/isc/util/tests/Makefile.am
+++ b/src/lib/python/isc/util/tests/Makefile.am
@@ -1,5 +1,5 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = process_test.py socketserver_mixin_test.py
+PYTESTS = process_test.py socketserver_mixin_test.py file_test.py
EXTRA_DIST = $(PYTESTS)
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/util/tests/file_test.py b/src/lib/python/isc/util/tests/file_test.py
new file mode 100644
index 0000000..fb765d7
--- /dev/null
+++ b/src/lib/python/isc/util/tests/file_test.py
@@ -0,0 +1,32 @@
+# 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.util.file
+import unittest
+
+class FileTest(unittest.TestCase):
+ def test_search_path_find(self):
+ """Test it returns the first occurence of the file"""
+ self.assertEqual('./Makefile',
+ isc.util.file.path_search('Makefile',
+ ['/no/such/directory/', '.',
+ '../tests/']))
+
+ def test_search_path_notfound(self):
+ """Test it throws an exception when the file can't be found"""
+ self.assertRaises(IOError, isc.util.file.path_search, 'no file', ['/no/such/directory'])
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/src/lib/resolve/recursive_query.cc b/src/lib/resolve/recursive_query.cc
index 34411ee..b753cc9 100644
--- a/src/lib/resolve/recursive_query.cc
+++ b/src/lib/resolve/recursive_query.cc
@@ -191,13 +191,12 @@ private:
// Info for (re)sending the query (the question and destination)
Question question_;
+ // This is the query message got from client
+ ConstMessagePtr query_message_;
+
// This is where we build and store our final answer
MessagePtr answer_message_;
- // currently we use upstream as the current list of NS records
- // we should differentiate between forwarding and resolving
- boost::shared_ptr<AddressVector> upstream_;
-
// Test server - only used for testing. This takes precedence over all
// other servers if the port is non-zero.
std::pair<std::string, uint16_t> test_server_;
@@ -344,13 +343,8 @@ private:
}
}
- // 'general' send; if we are in forwarder mode, send a query to
- // a random nameserver in our forwarders list. If we are in
- // recursive mode, ask the NSAS to give us an address.
+ // 'general' send, ask the NSAS to give us an address.
void send(IOFetch::Protocol protocol = IOFetch::UDP) {
- // If are in forwarder mode, send it to a random
- // forwarder. If not, ask the NSAS for an address
- const int uc = upstream_->size();
protocol_ = protocol; // Store protocol being used for this
if (test_server_.second != 0) {
dlog("Sending upstream query (" + question_.toText() +
@@ -362,18 +356,6 @@ private:
test_server_.second, buffer_, this,
query_timeout_);
io_.get_io_service().post(query);
- } else if (uc > 0) {
- // TODO: use boost, or rand()-utility function we provide
- int serverIndex = rand() % uc;
- dlog("Sending upstream query (" + question_.toText() +
- ") to " + upstream_->at(serverIndex).first);
- ++outstanding_events_;
- gettimeofday(¤t_ns_qsent_time, NULL);
- IOFetch query(protocol, io_, question_,
- upstream_->at(serverIndex).first,
- upstream_->at(serverIndex).second, buffer_, this,
- query_timeout_);
- io_.get_io_service().post(query);
} else {
// Ask the NSAS for an address for the current zone,
// the callback will call the actual sendTo()
@@ -548,7 +530,6 @@ public:
RunningQuery(IOService& io,
const Question& question,
MessagePtr answer_message,
- boost::shared_ptr<AddressVector> upstream,
std::pair<std::string, uint16_t>& test_server,
OutputBufferPtr buffer,
isc::resolve::ResolverInterface::CallbackPtr cb,
@@ -560,8 +541,8 @@ public:
:
io_(io),
question_(question),
+ query_message_(),
answer_message_(answer_message),
- upstream_(upstream),
test_server_(test_server),
buffer_(buffer),
resolvercallback_(cb),
@@ -709,8 +690,7 @@ public:
incoming.fromWire(ibuf);
buffer_->clear();
- if (recursive_mode() &&
- incoming.getRcode() == Rcode::NOERROR()) {
+ if (incoming.getRcode() == Rcode::NOERROR()) {
done_ = handleRecursiveAnswer(incoming);
} else {
isc::resolve::copyResponseMessage(incoming, answer_message_);
@@ -744,13 +724,11 @@ public:
} else if (!done_ && retries_--) {
// Query timed out, but we have some retries, so send again
dlog("Timeout for " + question_.toText() + " to " + current_ns_address.getAddress().toText() + ", resending query");
- if (recursive_mode()) {
- current_ns_address.updateRTT(isc::nsas::AddressEntry::UNREACHABLE);
- }
+ current_ns_address.updateRTT(isc::nsas::AddressEntry::UNREACHABLE);
send();
} else {
// We are either already done, or out of retries
- if (recursive_mode() && result == IOFetch::TIME_OUT) {
+ if (result == IOFetch::TIME_OUT) {
dlog("Timeout for " + question_.toText() + " to " + current_ns_address.getAddress().toText() + ", giving up");
current_ns_address.updateRTT(isc::nsas::AddressEntry::UNREACHABLE);
}
@@ -767,12 +745,179 @@ public:
void makeSERVFAIL() {
isc::resolve::makeErrorMessage(answer_message_, Rcode::SERVFAIL());
}
-
- // Returns true if we are in 'recursive' mode
- // Returns false if we are in 'forwarding' mode
- // (i.e. if we have anything in upstream_)
- bool recursive_mode() const {
- return upstream_->empty();
+};
+
+class ForwardQuery : public IOFetch::Callback {
+private:
+ // The io service to handle async calls
+ IOService& io_;
+
+ // This is the query message got from client
+ ConstMessagePtr query_message_;
+
+ // This is where we build and store our final answer
+ MessagePtr answer_message_;
+
+ // List of nameservers to forward to
+ boost::shared_ptr<AddressVector> upstream_;
+
+ // Buffer to store the result.
+ OutputBufferPtr buffer_;
+
+ // This will be notified when we succeed or fail
+ isc::resolve::ResolverInterface::CallbackPtr resolvercallback_;
+
+ /*
+ * TODO Do something more clever with timeouts. In the long term, some
+ * computation of average RTT, increase with each retry, etc.
+ */
+ // Timeout information
+ int query_timeout_;
+
+ // TODO: replace by our wrapper
+ asio::deadline_timer client_timer;
+ asio::deadline_timer lookup_timer;
+
+ // Make FowardQuery deletes itself safely. for more information see
+ // the comments of outstanding_events in RunningQuery.
+ size_t outstanding_events_;
+
+ // If we have a client timeout, we call back with a failure message,
+ // but we do not stop yet. We use this variable to make sure we
+ // don't call back a second time later
+ bool callback_called_;
+
+ // send the query to the server.
+ void send(IOFetch::Protocol protocol = IOFetch::UDP) {
+ const int uc = upstream_->size();
+ buffer_->clear();
+ int serverIndex = rand() % uc;
+ ConstQuestionPtr question = *(query_message_->beginQuestion());
+ dlog("Sending upstream query (" + question->toText() +
+ ") to " + upstream_->at(serverIndex).first);
+ ++outstanding_events_;
+ // Forward the query, create the IOFetch with
+ // query message, so that query flags can be forwarded
+ // together.
+ IOFetch query(protocol, io_, query_message_,
+ upstream_->at(serverIndex).first,
+ upstream_->at(serverIndex).second,
+ buffer_, this, query_timeout_);
+
+ io_.get_io_service().post(query);
+ }
+
+public:
+ ForwardQuery(IOService& io,
+ ConstMessagePtr query_message,
+ MessagePtr answer_message,
+ boost::shared_ptr<AddressVector> upstream,
+ OutputBufferPtr buffer,
+ isc::resolve::ResolverInterface::CallbackPtr cb,
+ int query_timeout, int client_timeout, int lookup_timeout) :
+ io_(io),
+ query_message_(query_message),
+ answer_message_(answer_message),
+ upstream_(upstream),
+ buffer_(buffer),
+ resolvercallback_(cb),
+ query_timeout_(query_timeout),
+ client_timer(io.get_io_service()),
+ lookup_timer(io.get_io_service()),
+ outstanding_events_(0),
+ callback_called_(false)
+ {
+ // Setup the timer to stop trying (lookup_timeout)
+ if (lookup_timeout >= 0) {
+ lookup_timer.expires_from_now(
+ boost::posix_time::milliseconds(lookup_timeout));
+ ++outstanding_events_;
+ lookup_timer.async_wait(boost::bind(&ForwardQuery::lookupTimeout, this));
+ }
+
+ // Setup the timer to send an answer (client_timeout)
+ if (client_timeout >= 0) {
+ client_timer.expires_from_now(
+ boost::posix_time::milliseconds(client_timeout));
+ ++outstanding_events_;
+ client_timer.async_wait(boost::bind(&ForwardQuery::clientTimeout, this));
+ }
+
+ send();
+ }
+
+ virtual void lookupTimeout() {
+ if (!callback_called_) {
+ makeSERVFAIL();
+ callCallback(false);
+ }
+ assert(outstanding_events_ > 0);
+ --outstanding_events_;
+ stop();
+ }
+
+ virtual void clientTimeout() {
+ if (!callback_called_) {
+ makeSERVFAIL();
+ callCallback(false);
+ }
+ assert(outstanding_events_ > 0);
+ --outstanding_events_;
+ stop();
+ }
+
+ // If the callback has not been called yet, call it now
+ // If success is true, we call 'success' with our answer_message
+ // If it is false, we call failure()
+ void callCallback(bool success) {
+ if (!callback_called_) {
+ callback_called_ = true;
+ if (success) {
+ resolvercallback_->success(answer_message_);
+ } else {
+ resolvercallback_->failure();
+ }
+ }
+ }
+
+ virtual void stop() {
+ // if we cancel our timers, we will still get an event for
+ // that, so we cannot delete ourselves just yet (those events
+ // would be bound to a deleted object)
+ // cancel them one by one, both cancels should get us back
+ // here again.
+ // same goes if we have an outstanding query (can't delete
+ // until that one comes back to us)
+ lookup_timer.cancel();
+ client_timer.cancel();
+ if (outstanding_events_ > 0) {
+ return;
+ } else {
+ delete this;
+ }
+ }
+
+ // This function is used as callback from DNSQuery.
+ virtual void operator()(IOFetch::Result result) {
+ // XXX is this the place for TCP retry?
+ assert(outstanding_events_ > 0);
+ --outstanding_events_;
+ if (result != IOFetch::TIME_OUT) {
+ // we got an answer
+ Message incoming(Message::PARSE);
+ InputBuffer ibuf(buffer_->getData(), buffer_->getLength());
+ incoming.fromWire(ibuf);
+ isc::resolve::copyResponseMessage(incoming, answer_message_);
+ callCallback(true);
+ }
+
+ stop();
+ }
+
+ // Clear the answer parts of answer_message, and set the rcode
+ // to servfail
+ void makeSERVFAIL() {
+ isc::resolve::makeErrorMessage(answer_message_, Rcode::SERVFAIL());
}
};
@@ -815,7 +960,7 @@ RecursiveQuery::resolve(const QuestionPtr& question,
} else {
dlog("Message not found in cache, starting recursive query");
// It will delete itself when it is done
- new RunningQuery(io, *question, answer_message, upstream_,
+ new RunningQuery(io, *question, answer_message,
test_server_, buffer, callback,
query_timeout_, client_timeout_,
lookup_timeout_, retries_, nsas_,
@@ -869,7 +1014,7 @@ RecursiveQuery::resolve(const Question& question,
} else {
dlog("Message not found in cache, starting recursive query");
// It will delete itself when it is done
- new RunningQuery(io, question, answer_message, upstream_,
+ new RunningQuery(io, question, answer_message,
test_server_, buffer, crs, query_timeout_,
client_timeout_, lookup_timeout_, retries_,
nsas_, cache_, rtt_recorder_);
@@ -877,5 +1022,36 @@ RecursiveQuery::resolve(const Question& question,
}
}
+void
+RecursiveQuery::forward(ConstMessagePtr query_message,
+ MessagePtr answer_message,
+ OutputBufferPtr buffer,
+ DNSServer* server,
+ isc::resolve::ResolverInterface::CallbackPtr callback)
+{
+ // XXX: eventually we will need to be able to determine whether
+ // the message should be sent via TCP or UDP, or sent initially via
+ // UDP and then fall back to TCP on failure, but for the moment
+ // we're only going to handle UDP.
+ IOService& io = dns_service_.getIOService();
+
+ if (!callback) {
+ callback.reset(new isc::resolve::ResolverCallbackServer(server));
+ }
+
+ // TODO: general 'prepareinitialanswer'
+ answer_message->setOpcode(isc::dns::Opcode::QUERY());
+ ConstQuestionPtr question = *query_message->beginQuestion();
+ answer_message->addQuestion(*question);
+
+ // implement the simplest forwarder, which will pass
+ // everything throught without interpretation, except
+ // QID, port number. The response will not be cached.
+ // It will delete itself when it is done
+ new ForwardQuery(io, query_message, answer_message,
+ upstream_, buffer, callback, query_timeout_,
+ client_timeout_, lookup_timeout_);
+}
+
} // namespace asiodns
} // namespace isc
diff --git a/src/lib/resolve/recursive_query.h b/src/lib/resolve/recursive_query.h
index c082426..b9fb80d 100644
--- a/src/lib/resolve/recursive_query.h
+++ b/src/lib/resolve/recursive_query.h
@@ -141,6 +141,20 @@ public:
isc::util::OutputBufferPtr buffer,
DNSServer* server);
+ /// \brief Initiates forwarding for the given query.
+ ///
+ /// Others parameters are same with the parameters of
+ /// function resolve().
+ ///
+ /// \param query_message the full query got from client.
+ /// \param callback callback object
+ void forward(isc::dns::ConstMessagePtr query_message,
+ isc::dns::MessagePtr answer_message,
+ isc::util::OutputBufferPtr buffer,
+ DNSServer* server,
+ isc::resolve::ResolverInterface::CallbackPtr callback =
+ isc::resolve::ResolverInterface::CallbackPtr());
+
/// \brief Set Test Server
///
/// This method is *only* for unit testing the class. If set, it enables
diff --git a/src/lib/resolve/tests/recursive_query_unittest.cc b/src/lib/resolve/tests/recursive_query_unittest.cc
index 04a7803..4e939fa 100644
--- a/src/lib/resolve/tests/recursive_query_unittest.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest.cc
@@ -37,6 +37,7 @@
#include <nsas/nameserver_address_store.h>
#include <cache/resolver_cache.h>
+#include <resolve/resolve.h>
// IMPORTANT: We shouldn't directly use ASIO definitions in this test.
// In particular, we must not include asio.hpp in this file.
@@ -579,9 +580,12 @@ TEST_F(RecursiveQueryTest, forwarderSend) {
singleAddress(TEST_IPV4_ADDR, port));
Question q(Name("example.com"), RRClass::IN(), RRType::TXT());
+ Message query_message(Message::RENDER);
+ isc::resolve::initResponseMessage(q, query_message);
+
OutputBufferPtr buffer(new OutputBuffer(0));
MessagePtr answer(new Message(Message::RENDER));
- rq.resolve(q, answer, buffer, &server);
+ rq.forward(ConstMessagePtr(&query_message), answer, buffer, &server);
char data[4096];
size_t size = sizeof(data);
@@ -646,8 +650,41 @@ bool tryRead(int sock_, int recv_options, size_t max, int* num) {
return true;
}
+// Mock resolver callback for testing forward query.
+class MockResolverCallback : public isc::resolve::ResolverInterface::Callback {
+public:
+ enum ResultValue {
+ DEFAULT = 0,
+ SUCCESS = 1,
+ FAILURE = 2
+ };
+
+ MockResolverCallback(DNSServer* server):
+ result(DEFAULT),
+ server_(server->clone())
+ {}
+
+ ~MockResolverCallback() {
+ delete server_;
+ }
+
+ void success(const isc::dns::MessagePtr response) {
+ result = SUCCESS;
+ server_->resume(true);
+ }
+
+ void failure() {
+ result = FAILURE;
+ server_->resume(false);
+ }
-// Test it tries the correct amount of times before giving up
+ uint32_t result;
+private:
+ DNSServer* server_;
+};
+
+// Test query timeout, set query timeout is lower than client timeout
+// and lookup timeout.
TEST_F(RecursiveQueryTest, forwardQueryTimeout) {
// Prepare the service (we do not use the common setup, we do not answer
setDNSService();
@@ -669,26 +706,20 @@ TEST_F(RecursiveQueryTest, forwardQueryTimeout) {
Question question(Name("example.net"), RRClass::IN(), RRType::A());
OutputBufferPtr buffer(new OutputBuffer(0));
MessagePtr answer(new Message(Message::RENDER));
- query.resolve(question, answer, buffer, &server);
+ Message query_message(Message::RENDER);
+ isc::resolve::initResponseMessage(question, query_message);
+ boost::shared_ptr<MockResolverCallback> callback(new MockResolverCallback(&server));
+ query.forward(ConstMessagePtr(&query_message), answer, buffer, &server, callback);
// Run the test
io_service_->run();
-
- // Read up to 3 packets. Use some ad hoc timeout to prevent an infinite
- // block (see also recvUDP()).
- int recv_options = setSocketTimeout(sock_, 10, 0);
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 3, &num);
-
- // The query should 'succeed' with an error response
- EXPECT_TRUE(done);
- EXPECT_EQ(3, num);
- EXPECT_TRUE(read_success);
+ EXPECT_EQ(callback->result, MockResolverCallback::FAILURE);
}
// If we set client timeout to lower than querytimeout, we should
-// get a failure answer, but still see retries
-// (no actual answer is given here yet)
+// get a failure answer
+// (no actual answer is given here yet. TODO the returned error message
+// should be tested)
TEST_F(RecursiveQueryTest, forwardClientTimeout) {
// Prepare the service (we do not use the common setup, we do not answer
setDNSService();
@@ -703,36 +734,25 @@ TEST_F(RecursiveQueryTest, forwardClientTimeout) {
// Do the answer
const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- // Set it up to retry twice before client timeout fires
- // Since the lookup timer has not fired, it should retry
- // four times
RecursiveQuery query(*dns_service_,
*nsas_, cache_,
singleAddress(TEST_IPV4_ADDR, port),
singleAddress(TEST_IPV4_ADDR, port),
- 200, 480, 4000, 4);
- Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ 1000, 10, 4000, 4);
+ Question q(Name("example.net"), RRClass::IN(), RRType::A());
OutputBufferPtr buffer(new OutputBuffer(0));
- query.resolve(question, answer, buffer, &server);
+ Message query_message(Message::RENDER);
+ isc::resolve::initResponseMessage(q, query_message);
+ boost::shared_ptr<MockResolverCallback> callback(new MockResolverCallback(&server));
+ query.forward(ConstMessagePtr(&query_message), answer, buffer, &server, callback);
// Run the test
io_service_->run();
-
- // we know it'll fail, so make it a shorter timeout
- int recv_options = setSocketTimeout(sock_, 1, 0);
-
- // Try to read 4 times
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 4, &num);
-
- // The query should fail
- EXPECT_TRUE(done1);
- EXPECT_EQ(3, num);
- EXPECT_FALSE(read_success);
+ EXPECT_EQ(callback->result, MockResolverCallback::FAILURE);
}
-// If we set lookup timeout to lower than querytimeout*retries, we should
-// fail before the full amount of retries
+// If we set lookup timeout to lower than querytimeout, the lookup
+// will fail.
TEST_F(RecursiveQueryTest, forwardLookupTimeout) {
// Prepare the service (we do not use the common setup, we do not answer
setDNSService();
@@ -748,30 +768,22 @@ TEST_F(RecursiveQueryTest, forwardLookupTimeout) {
// Do the answer
const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- // Set up the test so that it will retry 5 times, but the lookup
- // timeout will fire after only 3 normal timeouts
RecursiveQuery query(*dns_service_,
*nsas_, cache_,
singleAddress(TEST_IPV4_ADDR, port),
singleAddress(TEST_IPV4_ADDR, port),
- 200, 4000, 480, 5);
+ 1000, 4000, 10, 5);
Question question(Name("example.net"), RRClass::IN(), RRType::A());
OutputBufferPtr buffer(new OutputBuffer(0));
- query.resolve(question, answer, buffer, &server);
+ Message query_message(Message::RENDER);
+ isc::resolve::initResponseMessage(question, query_message);
+
+ boost::shared_ptr<MockResolverCallback> callback(new MockResolverCallback(&server));
+ query.forward(ConstMessagePtr(&query_message), answer, buffer, &server, callback);
// Run the test
io_service_->run();
-
- int recv_options = setSocketTimeout(sock_, 1, 0);
-
- // Try to read 5 times, should stop after 3 reads
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 5, &num);
-
- // The query should fail and respond with an error
- EXPECT_TRUE(done);
- EXPECT_EQ(3, num);
- EXPECT_FALSE(read_success);
+ EXPECT_EQ(callback->result, MockResolverCallback::FAILURE);
}
// Set everything very low and see if this doesn't cause weird
@@ -791,8 +803,6 @@ TEST_F(RecursiveQueryTest, lowtimeouts) {
// Do the answer
const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- // Set up the test so that it will retry 5 times, but the lookup
- // timeout will fire after only 3 normal timeouts
RecursiveQuery query(*dns_service_,
*nsas_, cache_,
singleAddress(TEST_IPV4_ADDR, port),
@@ -800,21 +810,15 @@ TEST_F(RecursiveQueryTest, lowtimeouts) {
1, 1, 1, 1);
Question question(Name("example.net"), RRClass::IN(), RRType::A());
OutputBufferPtr buffer(new OutputBuffer(0));
- query.resolve(question, answer, buffer, &server);
+ Message query_message(Message::RENDER);
+ isc::resolve::initResponseMessage(question, query_message);
+
+ boost::shared_ptr<MockResolverCallback> callback(new MockResolverCallback(&server));
+ query.forward(ConstMessagePtr(&query_message), answer, buffer, &server, callback);
// Run the test
io_service_->run();
-
- int recv_options = setSocketTimeout(sock_, 1, 0);
-
- // Try to read 5 times, should stop after 3 reads
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 5, &num);
-
- // The query should fail and respond with an error
- EXPECT_TRUE(done);
- EXPECT_EQ(1, num);
- EXPECT_FALSE(read_success);
+ EXPECT_EQ(callback->result, MockResolverCallback::FAILURE);
}
// as mentioned above, we need a more better framework for this,
diff --git a/src/lib/server_common/Makefile.am b/src/lib/server_common/Makefile.am
index dfb3014..a3063ba 100644
--- a/src/lib/server_common/Makefile.am
+++ b/src/lib/server_common/Makefile.am
@@ -18,9 +18,12 @@ endif
lib_LTLIBRARIES = libserver_common.la
libserver_common_la_SOURCES = portconfig.h portconfig.cc
+libserver_common_la_SOURCES += keyring.h keyring.cc
libserver_common_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
libserver_common_la_LIBADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
libserver_common_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
+libserver_common_la_LIBADD += $(top_builddir)/src/lib/config/libcfgclient.la
libserver_common_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
+libserver_common_la_LIBADD += $(top_builddir)/src/lib/dns/libdns++.la
CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/server_common/keyring.cc b/src/lib/server_common/keyring.cc
new file mode 100644
index 0000000..f68db70
--- /dev/null
+++ b/src/lib/server_common/keyring.cc
@@ -0,0 +1,61 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <server_common/keyring.h>
+
+using namespace isc::dns;
+using namespace isc::data;
+
+namespace isc {
+namespace server_common {
+
+typedef boost::shared_ptr<TSIGKeyRing> KeyringPtr;
+
+KeyringPtr keyring;
+
+namespace {
+
+void
+updateKeyring(const std::string&, ConstElementPtr data) {
+ ConstElementPtr list(data->get("keys"));
+ KeyringPtr load(new TSIGKeyRing);
+ for (size_t i(0); i < list->size(); ++ i) {
+ load->add(TSIGKey(list->get(i)->stringValue()));
+ }
+ keyring.swap(load);
+}
+
+}
+
+void
+initKeyring(config::ModuleCCSession& session) {
+ if (keyring) {
+ // We are already initialized
+ return;
+ }
+ session.addRemoteConfig("tsig_keys", updateKeyring, false);
+}
+
+void
+deinitKeyring(config::ModuleCCSession& session) {
+ if (!keyring) {
+ // Not initialized, ignore it
+ return;
+ }
+ keyring.reset();
+ session.removeRemoteConfig("tsig_keys");
+}
+
+}
+}
diff --git a/src/lib/server_common/keyring.h b/src/lib/server_common/keyring.h
new file mode 100644
index 0000000..8832095
--- /dev/null
+++ b/src/lib/server_common/keyring.h
@@ -0,0 +1,96 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef ISC_SERVER_COMMON_KEYRING_H
+#define ISC_SERVER_COMMON_KEYRING_H
+
+#include <boost/shared_ptr.hpp>
+#include <dns/tsigkey.h>
+#include <config/ccsession.h>
+
+/**
+ * \file keyring.h
+ * \brief TSIG keyring loaded from configuration.
+ *
+ * This file contains routines for loading a TSIG key ring from
+ * the tsig_keys configuration section and keeping them up to date
+ * on updates.
+ *
+ * You simply initialize/load the keyring with isc::server_common::initKeyring
+ * and then just use the key ring in in isc::server_common::keyring. It is
+ * automatically reloaded, when the configuration updates, so you no longer
+ * needs to care about it.
+ *
+ * If you want to keep a key (or session) for longer time or your application
+ * is multithreaded, you might want to have a copy of the shared pointer.
+ * Otherwise an update might replace the keyring and delete the keys in the
+ * old one.
+ */
+
+namespace isc {
+
+namespace server_common {
+
+/**
+ * \brief The key ring itself
+ *
+ * This is where the key ring is stored. You can directly use it to your needs,
+ * but you need to call initKeyring first, otherwise you'll find a NULL pointer
+ * here only.
+ */
+extern boost::shared_ptr<dns::TSIGKeyRing> keyring;
+
+/**
+ * \brief Load the key ring for the first time
+ *
+ * This loads the key ring from configuration to keyring. It also registers for
+ * config updates, so from now on, it'll be kept up to date.
+ *
+ * You can unload the key ring with deinitKeyring.
+ *
+ * If it is already loaded, this function does nothing. So, if more than one
+ * part of an application needs to use the key ring, they all can just call
+ * this independently to ensure the keyring is loaded.
+ *
+ * \param session The configuration session used to talk to the config manager.
+ */
+void
+initKeyring(config::ModuleCCSession& session);
+
+/**
+ * \brief Unload the key ring
+ *
+ * This can be used to unload the key ring. It will reset the keyring to NULL
+ * and stop receiving updates of the configuration.
+ *
+ * The need for this function should be quite rare, as it isn't required to be
+ * called before application shutdown. And not calling it has only small
+ * performance penalty -- the keyring will be kept in memory and updated when
+ * the user changes configuration.
+ *
+ * This does nothing if the key ring is not loaded currently.
+ *
+ * \param session The configuration session used to talk to the config manager.
+ *
+ * \todo What do we do when the data that come are invalid? Should we ignore it,
+ * as walidity should have been checked already in the config manager, or
+ * throw? What about when we get an update and it's invalid?
+ */
+void
+deinitKeyring(config::ModuleCCSession& session);
+
+}
+}
+
+#endif
diff --git a/src/lib/server_common/tests/Makefile.am b/src/lib/server_common/tests/Makefile.am
index a593a60..ecdb2d9 100644
--- a/src/lib/server_common/tests/Makefile.am
+++ b/src/lib/server_common/tests/Makefile.am
@@ -27,11 +27,12 @@ if HAVE_GTEST
TESTS += run_unittests
run_unittests_SOURCES = run_unittests.cc
run_unittests_SOURCES += portconfig_unittest.cc
+run_unittests_SOURCES += keyring_test.cc
+nodist_run_unittests_SOURCES = data_path.h
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
run_unittests_LDADD = $(GTEST_LDADD)
-
run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/asiodns/libasiodns.la
@@ -39,6 +40,10 @@ run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
+run_unittests_LDADD += $(top_builddir)/src/lib/config/tests/libfake_session.la
endif
noinst_PROGRAMS = $(TESTS)
+
+EXTRA_DIST = testdata/spec.spec
diff --git a/src/lib/server_common/tests/data_path.h.in b/src/lib/server_common/tests/data_path.h.in
new file mode 100644
index 0000000..8ac0380
--- /dev/null
+++ b/src/lib/server_common/tests/data_path.h.in
@@ -0,0 +1,16 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#define TEST_DATA_PATH "@abs_srcdir@/testdata"
+#define PLUGIN_DATA_PATH "@top_srcdir@/src/bin/cfgmgr/plugins"
diff --git a/src/lib/server_common/tests/keyring_test.cc b/src/lib/server_common/tests/keyring_test.cc
new file mode 100644
index 0000000..6d2f226
--- /dev/null
+++ b/src/lib/server_common/tests/keyring_test.cc
@@ -0,0 +1,132 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <server_common/keyring.h>
+#include <server_common/tests/data_path.h>
+
+#include <config/tests/fake_session.h>
+#include <config/ccsession.h>
+#include <dns/name.h>
+
+#include <gtest/gtest.h>
+#include <memory>
+#include <string>
+
+using namespace isc::data;
+using namespace isc::config;
+using namespace isc::server_common;
+using namespace isc::dns;
+
+namespace {
+
+class KeyringTest : public ::testing::Test {
+public:
+ KeyringTest() :
+ session(ElementPtr(new ListElement), ElementPtr(new ListElement),
+ ElementPtr(new ListElement)),
+ specfile(std::string(TEST_DATA_PATH) + "/spec.spec")
+ {
+ session.getMessages()->add(createAnswer());
+ mccs.reset(new ModuleCCSession(specfile, session, NULL, NULL));
+ }
+ isc::cc::FakeSession session;
+ std::auto_ptr<ModuleCCSession> mccs;
+ std::string specfile;
+ void doInit() {
+ // Prepare the module specification for it and the config
+ session.getMessages()->
+ add(createAnswer(0,
+ moduleSpecFromFile(std::string(PLUGIN_DATA_PATH) +
+ "/tsig_keys.spec").
+ getFullSpec()));
+ session.getMessages()->add(createAnswer(0, Element::fromJSON(
+ "{\"keys\": [\"key:MTIzNAo=:hmac-sha1\"]}")));
+ // Now load it
+ EXPECT_NO_THROW(initKeyring(*mccs));
+ EXPECT_NE(keyring, boost::shared_ptr<TSIGKeyRing>()) <<
+ "No keyring even after init";
+ }
+};
+
+// Test usual use - init, using the keyring, update, deinit
+TEST_F(KeyringTest, keyring) {
+ // First, initialize it
+ {
+ SCOPED_TRACE("Init");
+ doInit();
+
+ // Make sure it contains the correct key
+ TSIGKeyRing::FindResult result(keyring->find(Name("key"),
+ TSIGKey::HMACSHA1_NAME()));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, result.code);
+ }
+
+ {
+ SCOPED_TRACE("Update");
+ session.addMessage(createCommand("config_update", Element::fromJSON(
+ "{\"keys\": [\"another:MTIzNAo=:hmac-sha256\"]}")),
+ "tsig_keys", "*");
+ mccs->checkCommand();
+
+ // Make sure it no longer contains the original key
+ TSIGKeyRing::FindResult result(keyring->find(Name("key"),
+ TSIGKey::HMACSHA1_NAME()));
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND, result.code);
+ // but it does contain the new one
+ TSIGKeyRing::FindResult result2 = keyring->find(Name("another"),
+ TSIGKey::HMACSHA256_NAME());
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, result2.code);
+ }
+
+ {
+ SCOPED_TRACE("Deinit");
+ deinitKeyring(*mccs);
+ EXPECT_EQ(keyring, boost::shared_ptr<TSIGKeyRing>()) <<
+ "The keyring didn't disappear";
+ }
+}
+
+// Init twice
+TEST_F(KeyringTest, initTwice) {
+ // It is NULL before
+ EXPECT_EQ(keyring, boost::shared_ptr<TSIGKeyRing>()) <<
+ "Someone forgot to deinit it before";
+ {
+ SCOPED_TRACE("First init");
+ doInit();
+ }
+ boost::shared_ptr<TSIGKeyRing> backup(keyring);
+ {
+ SCOPED_TRACE("Second init");
+ EXPECT_NO_THROW(initKeyring(*mccs)) <<
+ "It not only does something when it is already initialized, "
+ "it even throws at it";
+ }
+ EXPECT_EQ(backup, keyring) << "The second init replaced the data";
+ deinitKeyring(*mccs);
+}
+
+// deinit when not initialized
+TEST_F(KeyringTest, extraDeinit) {
+ // It is NULL before
+ EXPECT_EQ(boost::shared_ptr<TSIGKeyRing>(), keyring) <<
+ "Someone forgot to deinit it before";
+ // Check that it doesn't get confused when we do not have it initialized
+ EXPECT_NO_THROW(deinitKeyring(*mccs));
+ // It is still NULL
+ EXPECT_EQ(keyring, boost::shared_ptr<TSIGKeyRing>()) <<
+ "Where did it get something after deinit?";
+}
+
+}
diff --git a/src/lib/server_common/tests/testdata/spec.spec b/src/lib/server_common/tests/testdata/spec.spec
new file mode 100644
index 0000000..3e0a822
--- /dev/null
+++ b/src/lib/server_common/tests/testdata/spec.spec
@@ -0,0 +1,6 @@
+{
+ "module_spec": {
+ "module_name": "test"
+ }
+}
+
diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am
index 14cf0bf..3db9ac4 100644
--- a/src/lib/util/Makefile.am
+++ b/src/lib/util/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = . io unittests tests
+SUBDIRS = . io unittests tests pyunittests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/util -I$(top_builddir)/src/lib/util
@@ -22,5 +22,7 @@ libutil_la_SOURCES += encode/binary_from_base16.h
libutil_la_SOURCES += random/qid_gen.h random/qid_gen.cc
libutil_la_SOURCES += random/random_number_generator.h
+EXTRA_DIST = python/pycppwrapper_util.h
+
libutil_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/util/python/mkpywrapper.py.in b/src/lib/util/python/mkpywrapper.py.in
new file mode 100755
index 0000000..4bf7752
--- /dev/null
+++ b/src/lib/util/python/mkpywrapper.py.in
@@ -0,0 +1,100 @@
+#!@PYTHON@
+
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""This utility program generates a C++ header and implementation files
+that can be used as a template of C++ python binding for a C++ class.
+
+Usage: ./mkpywrapper.py ClassName
+(the script should be run on this directory)
+
+It will generate two files: classname_python.h and classname_python.cc,
+many of whose definitions are in the namespace isc::MODULE_NAME::python.
+By default MODULE_NAME will be 'dns' (because this tool is originally
+intended to be used for the C++ python binding of the DNS library), but
+can be changed via the -m command line option.
+
+The generated files contain code fragments that are commonly used in
+C++ python binding implementations. It will define a class named
+s_ClassName which is a derived class of PyModule and can meet the
+requirement of the CPPPyObjectContainer template class (see
+pycppwrapper_util.h). It also defines (and declares in the header file)
+"classname_type", which is of PyTypeObject and is intended to be used
+to define details of the python bindings for the ClassName class.
+
+In many cases the header file can be used as a startpoint of the
+binding development without modification. But you may want to make
+ClassName::cppobj a constant variable (and you should if you can).
+Many definitions of classname_python.cc should also be able to be used
+just as defined, but some will need to be changed or removed. In
+particular, you should at least adjust ClassName_init(). You'll
+probably also need to add more definitions to that file to provide
+complete features of the C++ class.
+"""
+
+import datetime, string, sys
+from optparse import OptionParser
+
+# Remember the current year to produce the copyright boilerplate
+YEAR = datetime.date.today().timetuple()[0]
+
+def dump_file(out_file, temp_file, class_name, module):
+ for line in temp_file.readlines():
+ line = line.replace("@YEAR@", str(YEAR))
+ line = line.replace("@CPPCLASS at _H", class_name.upper() + "_H")
+ line = line.replace("@CPPCLASS@", class_name)
+ line = line.replace("@cppclass@", class_name.lower())
+ line = line.replace("@MODULE@", module)
+ out_file.write(line)
+
+def dump_wrappers(class_name, output, module):
+ try:
+ if output == "-":
+ header_file = sys.stdout
+ else:
+ header_file = open(output + "_python.h", "w")
+ header_template_file = open("wrapper_template.h", "r")
+ if output == "-":
+ impl_file = sys.stdout
+ else:
+ impl_file = open(output + "_python.cc", "w")
+ impl_template_file = open("wrapper_template.cc", "r")
+ except:
+ sys.stderr.write('Failed to open C++ file(s)\n')
+ sys.exit(1)
+ dump_file(header_file, header_template_file, class_name, module)
+ dump_file(impl_file, impl_template_file, class_name, module)
+
+usage = '''usage: %prog [options] class_name'''
+
+if __name__ == "__main__":
+ parser = OptionParser(usage=usage)
+ parser.add_option('-o', '--output', action='store', dest='output',
+ default=None, metavar='FILE',
+ help='prefix of output file names [default: derived from the class name]')
+ parser.add_option('-m', '--module', action='store', dest='module',
+ default='dns',
+ help='C++ module name of the wrapper (for namespaces) [default: dns]')
+ (options, args) = parser.parse_args()
+
+ if len(args) == 0:
+ parser.error('input file is missing')
+
+ class_name = args[0]
+ if not options.output:
+ options.output = class_name.lower()
+
+ dump_wrappers(class_name, options.output, options.module)
diff --git a/src/lib/util/python/pycppwrapper_util.h b/src/lib/util/python/pycppwrapper_util.h
new file mode 100644
index 0000000..fd55c19
--- /dev/null
+++ b/src/lib/util/python/pycppwrapper_util.h
@@ -0,0 +1,308 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 __PYCPPWRAPPER_UTIL_H
+#define __PYCPPWRAPPER_UTIL_H 1
+
+#include <Python.h>
+
+#include <exceptions/exceptions.h>
+
+/**
+ * @file pycppwrapper_util.h
+ * @short Shared definitions for python/C(++) API
+ *
+ * This utility defines a set of convenient wrappers for the python C API
+ * to use it safely from our C++ bindings. The python C API has many pitfalls
+ * such as not-so-consistent reference count policies. Also, many existing
+ * examples are careless about error handling. It's easy to find on the net
+ * example (even of "production use") python extensions like this:
+ *
+ * \code
+ * new_exception = PyErr_NewException("mymodule.Exception", NULL, NULL);
+ * // new_exception can be NULL, in which case the call to
+ * // PyModule_AddObject will cause a surprising disruption.
+ * PyModule_AddObject(mymodule, "Exception", new_exception); \endcode
+ *
+ * When using the python C API with C++, we should also be careful about
+ * exception safety. The underlying C++ code (including standard C++ libraries
+ * and memory allocation) can throw exceptions, in which case we need to
+ * make sure any intermediate python objects are cleaned up (we also need to
+ * catch the C++ exceptions inside the binding and convert them to python
+ * errors, but that's a different subject). This is not a trivial task
+ * because the python objects are represented as bare C pointers (so there's
+ * no destructor) and we need to address the exception safety along with python
+ * reference counters (so we cannot naively apply standard smart pointers).
+ *
+ * This utility tries to help address these issues.
+ *
+ * Also, it's intentional that this is a header-only utility. This way the
+ * C++ loadable module won't depend on another C++ library (which is not
+ * necessarily wrong, but would increase management cost such as link-time
+ * troubles only for a small utility feature).
+ */
+
+namespace isc {
+namespace util {
+namespace python {
+
+/// This is thrown inside this utility when it finds a NULL pointer is passed
+/// when it should not be NULL.
+class PyCPPWrapperException : public isc::Exception {
+public:
+ PyCPPWrapperException(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// This helper class is similar to the standard autoptr and manages PyObject
+/// using some kind of RAII techniques. It is, however, customized for the
+/// python C API.
+///
+/// A PyObjectContainer object is constructed with a pointer to PyObject,
+/// which is often just created dynamically. The caller will eventually
+/// attach the object to a different python object (often a module or class)
+/// via specific methods or directly return it to the python interpreter.
+///
+/// There are two cases in destructing the object: with or without decreasing
+/// a reference to the PyObject. If the object is intended to be an argument
+/// to another python C library that increases the reference to the object for
+/// itself, we should normally release our own reference; otherwise the
+/// reference will leak and the object won't be garbage collected. Also, when
+/// an unexpected error happens in the form of C++ exception, we should
+/// release the reference to prevent resource leak.
+///
+/// In some other cases, we should simply give our reference to the caller.
+/// That is the case when the created object itself is a return value of
+/// an extended python method written in the C++ binding. Likewise, some
+/// python C library functions "steal" the reference. In these cases we
+/// should not decrease the reference; otherwise it would cause duplicate free.
+///
+/// By default, the destructor of this class releases the reference to the
+/// PyObject. If this behavior is desirable, you can extract the original
+/// bare pointer to the PyObject by the \c get() method. If you don't want
+/// the reference to be decreased, the original bare pointer should be
+/// extracted using the \c release() method.
+///
+/// There are two convenience methods for commonly used operations:
+/// \c installAsClassVariable() to add the PyObject as a class variable
+/// and \c installToModule to add the PyObject to a specified python module.
+/// These methods (at least to some extent) take care of the reference to
+/// the object (either release or keep) depending on the usage context so
+/// that the user don't have to worry about it.
+///
+/// On construction, this class expects the pointer can be NULL.
+/// If it happens it immediately throws a \c PyCPPWrapperException exception.
+/// This behavior is to convert failures in the python C API (such as
+/// PyObject_New() returning NULL) to C++ exception so that we can unify
+/// error handling in the style of C++ exceptions.
+///
+/// Examples 1: To create a tuple of two python objects, do this:
+///
+/// \code
+/// try {
+/// PyObjectContainer container0(Py_BuildValue("I", 0));
+/// PyObjectContainer container1(Py_BuildValue("s", cppobj.toText().c_str()));
+/// return (Py_BuildValue("OO", container0.get(), container1.get()));
+/// } catch { ... set python exception, etc ... } \endcode
+///
+/// Commonly deployed buggy implementation to achieve this would be like this:
+/// \code
+/// return (Py_BuildValue("OO", Py_BuildValue("I", 0),
+/// Py_BuildValue("s", cppobj.toText().c_str())));
+/// \endcode
+/// One clear bug of this code is that references to the element objects of
+/// the tuple will leak.
+/// (Assuming \c cppobj.toText() can throw) this code is also not exception
+/// safe; if \c cppobj.toText() throws the reference to the first object
+/// will leak, even if the code tried to do the necessary cleanup in the
+/// successful case.
+/// Further, this code naively passes the result of the first two calls to
+/// \c Py_BuildValue() to the third one even if they can be NULL.
+/// In this specific case, it happens to be okay because \c Py_BuildValue()
+/// accepts NULL and treats it as an indication of error. But not all
+/// python C library works that way (remember, the API is so inconsistent)
+/// and we need to refer to the API manual every time we have to worry about
+/// passing a NULL object to a library function. We'd certainly like to
+/// avoid such development overhead. The code using \c PyObjectContainer
+/// addresses all these problems.
+///
+/// Examples 2: Install a (constant) variable to a class.
+///
+/// \code
+/// try {
+/// // installClassVariable is a wrapper of
+/// // PyObjectContainer::installAsClassVariable. See below.
+/// installClassVariable(myclass_type, "SOME_CONSTANT",
+/// Py_BuildValue("I", 0));
+/// } catch { ... }
+/// \endcode
+///
+/// Examples 3: Install a custom exception to a module.
+///
+/// \code
+/// PyObject* new_exception; // publicly visible
+/// ...
+/// try {
+/// new_exception = PyErr_NewException("mymodule.NewException",
+/// NULL, NULL);
+/// PyObjectContainer(new_exception).installToModule(mymodule,
+/// "NewException");
+/// } catch { ... }
+/// \endcode
+///
+/// Note that \c installToModule() keeps the reference to \c new_exception
+/// by default. This is a common practice when we introduce a custom
+/// exception in a python biding written in C/C++. See the code comment
+/// of the method for more details.
+struct PyObjectContainer {
+ PyObjectContainer(PyObject* obj) : obj_(obj) {
+ if (obj_ == NULL) {
+ isc_throw(PyCPPWrapperException, "Unexpected NULL PyObject, "
+ "probably due to short memory");
+ }
+ }
+ virtual ~PyObjectContainer() {
+ if (obj_ != NULL) {
+ Py_DECREF(obj_);
+ }
+ }
+ PyObject* get() {
+ return (obj_);
+ }
+ PyObject* release() {
+ PyObject* ret = obj_;
+ obj_ = NULL;
+ return (ret);
+ }
+
+ // Install the enclosed PyObject to the specified python class 'pyclass'
+ // as a variable named 'name'.
+ void installAsClassVariable(PyTypeObject& pyclass, const char* name) {
+ if (PyDict_SetItemString(pyclass.tp_dict, name, obj_) < 0) {
+ isc_throw(PyCPPWrapperException, "Failed to set a class variable, "
+ "probably due to short memory");
+ }
+ // Ownership successfully transferred to the class object. We'll let
+ // it be released in the destructor.
+ }
+
+ // Install the enclosed PyObject to the specified module 'mod' as an
+ // object named 'name'.
+ // By default, this method explicitly keeps the reference to the object
+ // even after the module "steals" it. To cancel this behavior and give
+ // the reference to the module completely, the third parameter 'keep_ref'
+ // should be set to false.
+ void installToModule(PyObject* mod, const char* name,
+ bool keep_ref = true)
+ {
+ if (PyModule_AddObject(mod, name, obj_) < 0) {
+ isc_throw(PyCPPWrapperException, "Failed to add an object to "
+ "module, probably due to short memory");
+ }
+ // PyModule_AddObject has "stolen" the reference, so unless we
+ // have to retain it ourselves we don't (shouldn't) decrease it.
+ // However, we actually often need to keep our own reference because
+ // objects added to a module are often referenced via non local
+ // C/C++ variables in various places of the C/C++ code. In order
+ // for the code to run safely even if some buggy/evil python program
+ // performs 'del mod.obj', we need the extra reference. See, e.g.:
+ // http://docs.python.org/py3k/c-api/init.html#Py_Initialize
+ // http://mail.python.org/pipermail/python-dev/2005-June/054238.html
+ if (keep_ref) {
+ Py_INCREF(obj_);
+ }
+ obj_ = NULL;
+ }
+
+protected:
+ PyObject* obj_;
+};
+
+/// This templated class is a derived class of \c PyObjectContainer and
+/// manages C++-class based python objects.
+///
+/// The template parameter \c PYSTRUCT must be a derived class (structure) of
+/// \c PyObject that has a member variable named \c cppobj, which must be a
+/// a pointer to \c CPPCLASS (the second template parameter).
+///
+/// For example, to define a custom python class based on a C++ class, MyClass,
+/// we'd define a class (struct) named \c s_MyClass like this:
+/// \code
+/// class s_MyClass : public PyObject {
+/// public:
+/// s_MyClass() : cppobj(NULL) {}
+/// MyClass* cppobj;
+/// };
+/// \endcode
+///
+/// And, to build and return a python version of MyClass object, write the
+/// following C++ code:
+/// \code
+/// typedef CPPPyObjectContainer<s_MyClass, MyClass> MyContainer;
+/// try {
+/// // below, myclass_type is of \c PyTypeObject that defines
+/// // a python class (type) for MyClass
+/// MyContainer container(PyObject_New(s_MyClass, myclass_type));
+/// container.set(new MyClass());
+/// return (container.release()); // give the reference to the caller
+/// } catch { ... }
+/// \endcode
+///
+/// This code prevents bugs like NULL pointer dereference when \c PyObject_New
+/// fails or resource leaks when new'ing \c MyClass results in an exception.
+/// Note that we use \c release() (derived from the base class) instead of
+/// \c get(); in this case we should simply pass the reference generated in
+/// \c PyObject_New() to the caller.
+template <typename PYSTRUCT, typename CPPCLASS>
+struct CPPPyObjectContainer : public PyObjectContainer {
+ CPPPyObjectContainer(PYSTRUCT* obj) : PyObjectContainer(obj) {}
+
+ // This method associates a C++ object with the corresponding python
+ // object enclosed in this class.
+ void set(CPPCLASS* value) {
+ if (value == NULL) {
+ isc_throw(PyCPPWrapperException, "Unexpected NULL C++ object, "
+ "probably due to short memory");
+ }
+ static_cast<PYSTRUCT*>(obj_)->cppobj = value;
+ }
+
+ // This is a convenience short cut to associate a C++ object with the
+ // python object and install it to the specified python class \c pyclass
+ // as a variable named \c name.
+ void installAsClassVariable(PyTypeObject& pyclass, const char* name,
+ CPPCLASS* value)
+ {
+ set(value);
+ PyObjectContainer::installAsClassVariable(pyclass, name);
+ }
+};
+
+/// A shortcut function to install a python class variable.
+///
+/// It installs a python object \c obj to a specified class \c pyclass
+/// as a variable named \c name.
+inline void
+installClassVariable(PyTypeObject& pyclass, const char* name, PyObject* obj) {
+ PyObjectContainer(obj).installAsClassVariable(pyclass, name);
+}
+
+} // namespace python
+} // namespace util
+} // namespace isc
+#endif // __PYCPPWRAPPER_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/python/wrapper_template.cc b/src/lib/util/python/wrapper_template.cc
new file mode 100644
index 0000000..691e4bf
--- /dev/null
+++ b/src/lib/util/python/wrapper_template.cc
@@ -0,0 +1,309 @@
+// Copyright (C) @YEAR@ Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include "@cppclass at _python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::@MODULE@;
+using namespace isc::@MODULE@::python;
+
+//
+// Definition of the classes
+//
+
+// For each class, we need a struct, a helper functions (init, destroy,
+// and static wrappers around the methods we export), a list of methods,
+// and a type description
+
+//
+// @CPPCLASS@
+//
+
+// Trivial constructor.
+s_ at CPPCLASS@::s_ at CPPCLASS@() : cppobj(NULL) {
+}
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_ at CPPCLASS@, @CPPCLASS@> @CPPCLASS at Container;
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+
+// General creation and destruction
+int @CPPCLASS at _init(s_ at CPPCLASS@* self, PyObject* args);
+void @CPPCLASS at _destroy(s_ at CPPCLASS@* self);
+
+// These are the functions we export
+// ADD/REMOVE/MODIFY THE FOLLOWING AS APPROPRIATE FOR THE ACTUAL CLASS.
+//
+PyObject* @CPPCLASS at _toText(const s_ at CPPCLASS@* const self);
+PyObject* @CPPCLASS at _str(PyObject* self);
+PyObject* @CPPCLASS at _richcmp(const s_ at CPPCLASS@* const self,
+ const s_ at CPPCLASS@* const other, int op);
+
+// This is quite specific pydnspp. For other wrappers this should probably
+// be removed.
+PyObject* @CPPCLASS at _toWire(const s_ at CPPCLASS@* self, PyObject* args);
+
+// These are the functions we export
+// For a minimal support, we don't need them.
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef @CPPCLASS at _methods[] = {
+ { "to_text", reinterpret_cast<PyCFunction>(@CPPCLASS at _toText), METH_NOARGS,
+ "Returns the text representation" },
+ // This is quite specific pydnspp. For other wrappers this should probably
+ // be removed:
+ { "to_wire", reinterpret_cast<PyCFunction>(@CPPCLASS at _toWire), METH_VARARGS,
+ "Converts the @CPPCLASS@ object to wire format.\n"
+ "The argument can be either a MessageRenderer or an object that "
+ "implements the sequence interface. If the object is mutable "
+ "(for instance a bytearray()), the wire data is added in-place.\n"
+ "If it is not (for instance a bytes() object), a new object is "
+ "returned" },
+ { NULL, NULL, 0, NULL }
+};
+
+// This is a template of typical code logic of python class initialization
+// with C++ backend. You'll need to adjust it according to details of the
+// actual C++ class.
+int
+ at CPPCLASS@_init(s_ at CPPCLASS@* self, PyObject* args) {
+ try {
+ if (PyArg_ParseTuple(args, "REPLACE ME")) {
+ // YOU'LL NEED SOME VALIDATION, PREPARATION, ETC, HERE.
+ self->cppobj = new @CPPCLASS@(/*NECESSARY PARAMS*/);
+ return (0);
+ }
+ } catch (const exception& ex) {
+ const string ex_what = "Failed to construct @CPPCLASS@ object: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (-1);
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in constructing @CPPCLASS@");
+ return (-1);
+ }
+
+ PyErr_SetString(PyExc_TypeError,
+ "Invalid arguments to @CPPCLASS@ constructor");
+
+ return (-1);
+}
+
+// This is a template of typical code logic of python object destructor.
+// In many cases you can use it without modification, but check that carefully.
+void
+ at CPPCLASS@_destroy(s_ at CPPCLASS@* const self) {
+ delete self->cppobj;
+ self->cppobj = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+// This should be able to be used without modification as long as the
+// underlying C++ class has toText().
+PyObject*
+ at CPPCLASS@_toText(const s_ at CPPCLASS@* const self) {
+ try {
+ // toText() could throw, so we need to catch any exceptions below.
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Failed to convert @CPPCLASS@ object to text: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+ "converting @CPPCLASS@ object to text");
+ }
+ return (NULL);
+}
+
+PyObject*
+ at CPPCLASS@_str(PyObject* self) {
+ // Simply call the to_text method we already defined
+ return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
+ const_cast<char*>("")));
+}
+
+PyObject*
+ at CPPCLASS@_richcmp(const s_ at CPPCLASS@* const self,
+ const s_ at CPPCLASS@* const other,
+ const int op)
+{
+ bool c = false;
+
+ // Check for null and if the types match. If different type,
+ // simply return False
+ if (other == NULL || (self->ob_type != other->ob_type)) {
+ Py_RETURN_FALSE;
+ }
+
+ // Only equals and not equals here, unorderable type
+ switch (op) {
+ case Py_LT:
+ PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+ return (NULL);
+ case Py_LE:
+ PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+ return (NULL);
+ case Py_EQ:
+ c = (*self->cppobj == *other->cppobj);
+ break;
+ case Py_NE:
+ c = (*self->cppobj != *other->cppobj);
+ break;
+ case Py_GT:
+ PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+ return (NULL);
+ case Py_GE:
+ PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+ return (NULL);
+ }
+ if (c) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+}
+} // end of unnamed namespace
+
+namespace isc {
+namespace @MODULE@ {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_ at CPPCLASS@
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject @cppclass at _type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp. at CPPCLASS@",
+ sizeof(s_ at CPPCLASS@), // tp_basicsize
+ 0, // tp_itemsize
+ reinterpret_cast<destructor>(@CPPCLASS at _destroy), // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ // THIS MAY HAVE TO BE CHANGED TO NULL:
+ @CPPCLASS at _str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The @CPPCLASS@ class objects is...(COMPLETE THIS)",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ // THIS MAY HAVE TO BE CHANGED TO NULL:
+ reinterpret_cast<richcmpfunc>(@CPPCLASS at _richcmp), // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ @CPPCLASS at _methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ reinterpret_cast<initproc>(@CPPCLASS at _init), // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_ at CPPCLASS@(PyObject* mod) {
+ // We initialize the static description object with PyType_Ready(),
+ // then add it to the module. This is not just a check! (leaving
+ // this out results in segmentation faults)
+ if (PyType_Ready(&@cppclass at _type) < 0) {
+ return (false);
+ }
+ void* p = &@cppclass at _type;
+ if (PyModule_AddObject(mod, "@CPPCLASS@", static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&@cppclass at _type);
+
+ // The following template is the typical procedure for installing class
+ // variables. If the class doesn't have a class variable, remove the
+ // entire try-catch clauses.
+ try {
+ // Constant class variables
+ installClassVariable(@cppclass at _type, "REPLACE_ME",
+ Py_BuildValue("REPLACE ME"));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure in @CPPCLASS@ initialization: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in @CPPCLASS@ initialization");
+ return (false);
+ }
+
+ return (true);
+}
+
+PyObject*
+create at CPPCLASS@Object(const @CPPCLASS@& source) {
+ @CPPCLASS at Container container =
+ PyObject_New(s_ at CPPCLASS@, &@cppclass at _type);
+ container.set(new @CPPCLASS@(source));
+ return (container.release());
+}
+} // namespace python
+} // namespace @MODULE@
+} // namespace isc
diff --git a/src/lib/util/python/wrapper_template.h b/src/lib/util/python/wrapper_template.h
new file mode 100644
index 0000000..d68a658
--- /dev/null
+++ b/src/lib/util/python/wrapper_template.h
@@ -0,0 +1,59 @@
+// Copyright (C) @YEAR@ Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_ at CPPCLASS@_H
+#define __PYTHON_ at CPPCLASS@_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace @MODULE@ {
+class @CPPCLASS@;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_ at CPPCLASS@ : public PyObject {
+public:
+ s_ at CPPCLASS@();
+ @CPPCLASS@* cppobj;
+};
+
+extern PyTypeObject @cppclass at _type;
+
+bool initModulePart_ at CPPCLASS@(PyObject* mod);
+
+// Note: this utility function works only when @CPPCLASS@ is a copy
+// constructable.
+// And, it would only be useful when python binding needs to create this
+// object frequently. Otherwise, it would (or should) probably better to
+// remove the declaration and definition of this function.
+//
+/// This is A simple shortcut to create a python @CPPCLASS@ object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called with in a try block
+/// followed by necessary setup for python exception.
+PyObject* create at CPPCLASS@Object(const @CPPCLASS@& source);
+
+} // namespace python
+} // namespace @MODULE@
+} // namespace isc
+#endif // __PYTHON_ at CPPCLASS@_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/pyunittests/Makefile.am b/src/lib/util/pyunittests/Makefile.am
new file mode 100644
index 0000000..e8fefbd
--- /dev/null
+++ b/src/lib/util/pyunittests/Makefile.am
@@ -0,0 +1,14 @@
+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
+pyunittests_util_la_SOURCES = pyunittests_util.cc
+pyunittests_util_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
+pyunittests_util_la_LDFLAGS = $(PYTHON_LDFLAGS)
+
+# Python prefers .so, while some OSes (specifically MacOS) use a different
+# suffix for dynamic objects. -module is necessary to work this around.
+pyunittests_util_la_LDFLAGS += -module
+pyunittests_util_la_LIBADD = $(top_builddir)/src/lib/util/libutil.la
+pyunittests_util_la_LIBADD += $(PYTHON_LIB)
diff --git a/src/lib/util/pyunittests/pyunittests_util.cc b/src/lib/util/pyunittests/pyunittests_util.cc
new file mode 100644
index 0000000..d266c84
--- /dev/null
+++ b/src/lib/util/pyunittests/pyunittests_util.cc
@@ -0,0 +1,84 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <Python.h>
+
+#include <stdint.h>
+
+// see util/time_utilities.h
+namespace isc {
+namespace util {
+namespace detail {
+extern int64_t (*gettimeFunction)();
+}
+}
+}
+
+namespace {
+int64_t fake_current_time;
+
+int64_t
+getFakeTime() {
+ return (fake_current_time);
+}
+
+PyObject*
+fixCurrentTime(PyObject*, PyObject* args) {
+ PyObject* maybe_none;
+ if (PyArg_ParseTuple(args, "L", &fake_current_time)) {
+ isc::util::detail::gettimeFunction = getFakeTime;
+ } else if (PyArg_ParseTuple(args, "O", &maybe_none) &&
+ maybe_none == Py_None) {
+ isc::util::detail::gettimeFunction = NULL;
+ } else {
+ PyErr_SetString(PyExc_TypeError, "Invalid arguments to "
+ "pyunittests_util.fix_current_time");
+ return (NULL);
+ }
+
+ PyErr_Clear();
+ Py_RETURN_NONE;
+}
+
+PyMethodDef PyUnittestsUtilMethods[] = {
+ { "fix_current_time", fixCurrentTime, METH_VARARGS,
+ "Fix the current system time at the specified (fake) value.\n\n"
+ "This is useful for testing modules that depend on the current time.\n"
+ "Note that it only affects C++ modules that use gettimeWrapper() "
+ "defined in libutil, which allows a hook for testing.\n"
+ "If an integer (signed 64bit) is given, the current time will be fixed "
+ "to that value; if None is specified (which is the default) the use of "
+ "faked time will be canceled."
+ },
+ { NULL, NULL, 0, NULL}
+};
+
+PyModuleDef pyunittests_util = {
+ { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
+ "pyunittests_util",
+ "This module is a collection of utilities useful for testing "
+ "the BIND 10 C++ binding modules.",
+ -1,
+ PyUnittestsUtilMethods,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+} // end of unnamed namespace
+
+PyMODINIT_FUNC
+PyInit_pyunittests_util(void) {
+ return (PyModule_Create(&pyunittests_util));
+}
More information about the bind10-changes
mailing list